Production AI agent with tools
A bounded, observable agent that uses real tools to handle support queries end to end.
- Python
- FastAPI
- Anthropic API
- Postgres
Telugu lo nerchuko · Watch in Telugu
The Week 3 agent ran in a script. This project turns that loop into a service real users can hit: an HTTP API, real tools backed by a database, hard limits on steps and cost, and the logging you need to debug it in production. It is the capstone of everything in the course.
Overview
You will build a customer-support agent exposed over FastAPI. It answers questions by calling tools — order lookup, knowledge-base search (RAG), and a refund action that requires confirmation — inside a bounded loop, with every step logged. This combines tool use, RAG, agent loops, evals, and deployment from the learn track into one system.
The problem
Support questions vary: some need a live order status, some need a policy answer, some need an action taken. A single prompt cannot do all three reliably. An agent with the right tools can, but only if it is bounded (no runaway loops), guarded (no unauthorised refunds), and observable (you can see why it did what it did). Building that safely is the hard part.
Architecture
A FastAPI endpoint receives a question, runs the agent loop, and returns the answer. The loop calls tools that hit Postgres and a vector store. Every step is logged; destructive tools require explicit confirmation.
Client --> FastAPI /ask --> agent loop (bounded)
|--> get_order(id) --> Postgres
|--> search_kb(query) --> vector store (RAG)
|--> issue_refund(id) --> guarded + confirmed
|
step logs + cost meterTech stack
- Python + FastAPI for the service
- Anthropic API for the model and tool use
- Postgres for orders, plus pgvector (or a vector DB) for the knowledge base
- Structured logging and a per-request token/step budget
Build steps
- Define the tools the agent may use.
TOOLS = [
{"name": "get_order", "description": "Get order status by id.",
"input_schema": {"type": "object",
"properties": {"order_id": {"type": "string"}}, "required": ["order_id"]}},
{"name": "search_kb", "description": "Search the help knowledge base.",
"input_schema": {"type": "object",
"properties": {"query": {"type": "string"}}, "required": ["query"]}},
{"name": "issue_refund", "description": "Refund an order. Requires confirmed=true.",
"input_schema": {"type": "object",
"properties": {"order_id": {"type": "string"}, "confirmed": {"type": "boolean"}},
"required": ["order_id", "confirmed"]}},
]- Implement the tools, guarding the destructive one.
def issue_refund(order_id: str, confirmed: bool):
if not confirmed:
return {"error": "Refund requires confirmation. Ask the user first."}
# ... perform the refund, idempotently ...
return {"order_id": order_id, "refunded": True}
TOOL_FUNCS = {"get_order": get_order, "search_kb": search_kb, "issue_refund": issue_refund}- Run a bounded, logged agent loop.
import logging
from anthropic import Anthropic
client, log = Anthropic(), logging.getLogger("agent")
def run_agent(question: str, max_steps: int = 6):
messages = [{"role": "user", "content": question}]
for step in range(max_steps):
resp = client.messages.create(
model="claude-sonnet-4-6", max_tokens=1024,
tools=TOOLS, messages=messages,
)
log.info("step", extra={"n": step, "stop": resp.stop_reason,
"in": resp.usage.input_tokens, "out": resp.usage.output_tokens})
messages.append({"role": "assistant", "content": resp.content})
if resp.stop_reason != "tool_use":
return resp.content[0].text
results = []
for b in resp.content:
if b.type == "tool_use":
out = TOOL_FUNCS[b.name](**b.input)
log.info("tool", extra={"name": b.name, "args": b.input})
results.append({"type": "tool_result", "tool_use_id": b.id, "content": str(out)})
messages.append({"role": "user", "content": results})
return "I could not complete that. A human agent will follow up."- Expose it over HTTP.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Ask(BaseModel):
question: str
@app.post("/ask")
def ask(body: Ask):
return {"answer": run_agent(body.question)}Source code
Reference implementation with tools, RAG, logging, and tests: AnythingWithSandy/prod-agent.
Demo
No public demo — the agent acts on real order data, so run it against your own seeded database from the repo. The repo includes a seed script and sample questions.
Deployment
Containerise the FastAPI app, run it behind your own backend (never expose the API key to clients), and set the model key, database URL, and budgets as environment secrets. Run the eval set (Week 4) against every change, and watch step count, latency, and cost per request in your logs.
Bonus challenge
Add an eval suite that scores the agent on a set of real support tickets, including adversarial ones that try to trigger an unconfirmed refund. Add streaming so the user sees progress, and a fallback that hands off to a human when confidence is low or the step limit is hit.