How Do You Build an AI Support Agent in Python? (2026, From First Principles)
A Python support agent is a short loop that calls a model, runs the tools it asks for, and feeds the results back until the ticket is handled. The code below builds that loop, a Zendesk client and a webhook, then lists the eight production jobs the loop leaves out.
Key takeaways
- An AI support agent in Python is a tool-calling loop of about 20 lines that calls the model, runs requested tools, and feeds results back until done.
- The example Python loop uses the anthropic package, an ANTHROPIC_API_KEY and the claude-sonnet-5 model, and joins text blocks for the final answer.
- The same Python loop works with the OpenAI Python SDK by reading tool_calls from Chat Completions instead of Anthropic tool_use blocks.
- Production support agents need real help-desk clients, a webhook trigger, retrieval over a vector store such as pgvector or FAISS, guardrails and an evaluation harness.
- A pure Python support agent takes weeks to reach production plus ongoing maintenance, while a hosted platform such as Macha brings that to days.
An AI support agent in Python is a loop of about 20 lines: send the ticket to a model with a list of tools, run whichever tool functions the model asks for, append the results, and repeat until the model returns a final answer. The example below uses the anthropic package and claude-sonnet-5, Anthropic's current Sonnet model (checked 24 September 2026). No framework here on purpose. Frameworks (and platforms) mostly remove the boilerplate you're about to see; seeing it first is how you know what they're doing for you.
| Piece | What it is in Python | Covered below |
|---|---|---|
| Model client | anthropic.Anthropic() with ANTHROPIC_API_KEY | The agent loop |
| Tools | Plain functions plus a JSON schema per function | The agent loop |
| Loop | ~20 lines: call, run tools, append results, repeat | The agent loop |
| Help-desk client | requests calls to the Zendesk REST API | From stubs to real tools |
| Trigger | A FastAPI webhook per new ticket | From stubs to real tools |
| Production layer | Retrieval, guardrails, hosting, observability, evaluation | What production adds |
What does a support agent do, mechanically?
Four jobs in a loop: understand the ticket, fetch context (customer, orders, the right help-center article) by calling tools, act (reply, tag, escalate), and know when to stop. In Python, "tools" are just functions plus a JSON schema the model can request. (If you're weighing build-vs-buy before writing any code, our AI agents for customer service overview covers the landscape.)
How do you write the agent loop in plain Python?
First, pip install anthropic and set your ANTHROPIC_API_KEY. Then define your tools as functions and describe them to the model — Anthropic's tool-use guide documents the exact block shapes:
import anthropic
client = anthropic.Anthropic()
def get_customer(email): ... # your DB / help-desk API
def get_recent_orders(customer_id): ...
def reply_to_ticket(ticket_id, body): ...
TOOLS = [{
"name": "get_customer",
"description": "Look up a customer by email.",
"input_schema": {"type": "object",
"properties": {"email": {"type": "string"}}, "required": ["email"]},
}] # ... one entry per function
DISPATCH = {"get_customer": get_customer,
"get_recent_orders": get_recent_orders,
"reply_to_ticket": reply_to_ticket}
Then the loop itself. This is the whole "agent", about 20 lines. The final answer joins the text blocks rather than reading content[0], because a response can also carry thinking blocks:
def run_agent(ticket_text):
messages = [{"role": "user", "content": ticket_text}]
while True:
resp = client.messages.create(
model="claude-sonnet-5", max_tokens=1024,
system=SYSTEM_PROMPT, tools=TOOLS, messages=messages)
if resp.stop_reason != "tool_use":
return "".join(b.text for b in resp.content
if b.type == "text") # final answer
messages.append({"role": "assistant", "content": resp.content})
results = []
for block in resp.content:
if block.type == "tool_use":
fn = DISPATCH[block.name]
out = fn(**block.input) # run your function
results.append({"type": "tool_result",
"tool_use_id": block.id, "content": str(out)})
messages.append({"role": "user", "content": results})
Run it and you get a real interaction: the model calls your tools, then answers.
>>> run_agent("Where's my order #4471? — [email protected]")
[tool] get_customer(email='[email protected]') -> {'id': 'c_88', 'plan': 'Pro'}
[tool] get_recent_orders(customer_id='c_88') -> [{'id': 4471, 'status': 'shipped', 'eta': 'Jul 6'}]
"Hi Jane — order #4471 shipped and should arrive Jul 6. Here's your tracking link…"
That's the whole loop working, and the same shape applies to the OpenAI Python SDK (with Chat Completions you read tool_calls off the response instead of tool_use blocks). Swapping models is a one-line change to model=; older IDs such as claude-sonnet-4-6 are listed by Anthropic as legacy but still available. Right now, though, the tools are stubs.
From stubs to real tools
The stubs are where a demo becomes real work. reply_to_ticket has to actually call your help desk's REST API, with auth and error handling:
import requests
ZENDESK = "https://yourco.zendesk.com/api/v2"
AUTH = (f"{EMAIL}/token", API_TOKEN)
def reply_to_ticket(ticket_id, body):
r = requests.put(f"{ZENDESK}/tickets/{ticket_id}.json",
json={"ticket": {"comment": {"body": body, "public": True}}},
auth=AUTH, timeout=10)
r.raise_for_status() # you handle 429s, retries, token refresh
return {"status": "sent"}
And the agent has to be triggered by a new ticket, so you stand up a webhook:
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/zendesk-webhook")
async def on_ticket(req: Request):
payload = await req.json()
verify_signature(req) # untrusted input — verify + dedupe
run_agent(payload["ticket"]["description"])
return {"ok": True}
Multiply that by every tool (get_customer, get_recent_orders, a search_kb retriever over your embedded help center) and every internal system. It runs — but it's still nowhere near production, and Python makes that painfully explicit, because you wrote every line, so every gap is yours.
Where Claude Code and Codex help
You can have Claude Code or Codex scaffold all of the above — the tool functions, the dispatch loop, the help-desk client, the tests — from a one-line description, then refine. It writes the boilerplate fast; it doesn't decide your tools, guardrails, or escalation rules, and it won't run the agent in production.
What does production add to those 20 lines?
Here's the honest gap, and in pure Python every item is code you write and own:
- Real tools, not stubs —
get_customer/reply_to_ticketbecome Zendesk/Freshdesk API clients: OAuth and token refresh, pagination, rate-limit handling, and a webhook endpoint (Flask/FastAPI) to trigger the agent per new ticket, with signature verification and idempotency. - Retrieval — chunk and embed your help center, store vectors (pgvector/FAISS), back the
search_kbtool, and re-embed when docs change. - Guardrails — grounding checks, PII redaction, action limits (cap refund amounts), prompt-injection defenses (tickets are untrusted input), and an
escalatepath with a clean human handoff. - State & robustness — conversation/context-window management,
try/exceptwith retries and backoff around every model and tool call, and a queue so a spike doesn't drop tickets. - Hosting 24/7 — a cloud host, secrets management, workers, a dead-letter queue, autoscaling.
- Observability — log the full trace of every run (tools called, context, decision), plus latency/cost metrics and alerting.
- Evaluation — a harness over real historical tickets with automated scoring, run as a regression before every prompt or model change.
- Maintenance — model deprecations, help-desk API changes, KB re-embedding, on-call.
That's weeks of work and permanent upkeep — and none of it is the interesting 20 lines. It's the undifferentiated plumbing every support agent needs.
How do you keep the Python design and drop the plumbing?
The valuable part of what you just built is the design — which tools, what guardrails, when to escalate. Macha is the layer that runs that design without the plumbing underneath it:
- Tools without clients — Custom Tools turn any REST API into an agent tool (define endpoint + auth, or let the AI builder create it from a sentence). Your help desk is a native connector (Zendesk, Freshdesk, Gorgias, Front, HubSpot or Intercom), and so are tools such as Shopify, Stripe, Slack and Jira; internal APIs plug in the same way. No
requestsclient to write or maintain, no webhook endpoint to host. - Grounding, hosting, retries, queue — attach your help center as a source; the agent runs in the cloud, triggered by tickets, with retries handled.
- Observability + eval: agent analytics show every run and tool call; simulations replay past tickets against the agent, and continuous evaluation grades every autonomous run against rules you set, so changes are measured.
How does pure Python compare with running on a platform?
| Concern | Pure Python | Python design + Macha |
|---|---|---|
| Agent loop | You write it (~20 lines) | Built in |
| Tools / API clients | You write + maintain each | Custom Tools / native connectors |
| Retrieval | You build + run a vector store | Attach sources; handled |
| Guardrails / escalation | You build enforcement | You configure it |
| Hosting, queue, retries | Your infra | Runs in the cloud |
| Observability + eval | You build both | Agent analytics, simulations + continuous evaluation |
| Time to production | Weeks + ongoing | Days |
Should you build it in pure Python or on a platform?
Write it in pure Python if you're learning how agents work, you need total control of the runtime, or you have constraints (on-prem, data-residency) a SaaS can't meet — the 20-line loop is genuinely worth understanding either way. (See our from-scratch vs. platform breakdown for the full comparison.) Reach for a platform when resolving support is the goal and you'd rather not maintain eight moving parts of plumbing forever. You can start free on Macha with $50 of free usage and no credit card, and bring the same design thinking without the boilerplate. Paid plans are priced per ticket, from $299 a month for 750 tickets.
FAQ
Do I need LangChain or an agents SDK? No — the loop above is all an agent fundamentally is. Frameworks add structure (and their own concepts) on top; a platform removes the infrastructure underneath. Start raw to understand it, then decide what you actually want to own.
Does this work with OpenAI instead of Anthropic? Yes — same shape with the OpenAI Python SDK: you read tool_calls off the response, run your functions, and append tool messages back. The production gap (steps 1–8) is identical regardless of model.
Can Claude Code or Codex just build the whole thing? They'll write the code fast — loop, tool functions, API client, tests. They won't make the product decisions or host, monitor, and evaluate the agent in production.
How do I know it's good before it touches customers? Grade it against real historical tickets with automated scoring, re-run before every change. Build the harness yourself or use Macha's simulations and continuous evaluation.

