Build an AI Support Agent in Python (From First Principles)
Most "AI agent" tutorials either hand you a framework and hide the mechanics, or stop at a chatbot that can't do anything. If you want to actually understand — and own — a support agent, Python from first principles is the clearest way to see what's really happening: it's a loop that calls a model, runs the tools the model asks for, and feeds the results back until the ticket is handled. This guide builds that in plain Python with real code, then is honest about the large gap between working code and a system you'd let answer a customer at 2am — and where a platform closes it.
No framework here on purpose. Frameworks (and platforms) are mostly there to remove the boilerplate you're about to see; seeing it first is how you know what they're doing for you.
What a support agent is, 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.)
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," ~20 lines:
def run_agent(ticket_text):
messages = [{"role": "user", "content": ticket_text}]
while True:
resp = client.messages.create(
model="claude-sonnet-4-6", max_tokens=1024,
system=SYSTEM_PROMPT, tools=TOOLS, messages=messages)
if resp.stop_reason != "tool_use":
return resp.content[0].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 (you read tool_calls off the response instead of tool_use blocks). But right now 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 "production" adds 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.
The shortcut: keep the Python thinking, 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, Front, Intercom, HubSpot, Help Scout, Salesforce, Jira Service Management, Pylon); 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; Studies grade the agent against a batch of real tickets so changes are measured.
From scratch (Python) vs. 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 + Studies |
| Time to production | Weeks + ongoing | Days |
So which should you build?
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 and wire your first tool in minutes — bringing the same design thinking, minus the boilerplate.
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 something like Macha's Studies.
Resolve tickets automatically with AI agents
Macha's AI agents work on top of the help desk you already use — no code.
Shopify
Stripe
Slack
Notion
Google Workspace
Confluence

