TL;DR
The tutorial version takes an afternoon. The version you can put in front of customers takes a different set of decisions — here are all seven.
→ See how this applies to your business (free 30-min call)Python is the default choice for building AI agents, and correctly so — the SDKs are first-class, the ecosystem is deep, and every provider ships a Python client before anything else. You can have a working agent loop in about fifty lines.
The gap between that and something you can put in front of paying customers is where most projects stall. Not because Python is limiting, but because the tutorials all skip the same seven decisions. Here they are, in the order they'll bite you.
Decision 1: Async From the Start, Not Later
The single most common structural regret. Tutorials use synchronous clients because they read better. Production agents spend the overwhelming majority of their wall-clock time waiting on network I/O — model calls, database lookups, CRM writes, calendar checks.
With synchronous code, every one of those waits blocks the thread. Handling twenty concurrent conversations means twenty threads or twenty processes, and your memory footprint and complexity both balloon.
With async, a single process handles hundreds of concurrent conversations comfortably, because waiting on I/O costs almost nothing. Every major provider ships an async client. Use it.
The important part: retrofitting async is painful. It's contagious through your call stack — one synchronous database call in the middle of an async agent blocks the whole event loop and you get mysterious latency that profilers don't obviously explain. Choose async on day one and make every I/O path async, including your database driver and HTTP client.
The one exception: if your agent is genuinely a batch script processing a queue serially, synchronous is fine and simpler. Anything user-facing should be async.
Decision 2: Typed Structured Outputs, Not String Parsing
Getting reliable structured data out of a model is the difference between a toy and a system. The bad version parses JSON out of a text response with a regex and hopes. That works about 92% of the time, which at a thousand conversations a day means eighty failures.
The right approach in Python: define your output shape as a Pydantic model, use the provider's structured output or tool-calling mode so the model is constrained to produce conforming output, and validate on the way in.
This gives you three things at once. Your tool parameter schemas generate directly from type hints, so definitions and validation never drift apart. Your IDE knows what fields exist. And a validation failure becomes a clean retry with the error message fed back to the model, rather than an unhandled exception three layers up.
Make validation failure a first-class path: catch it, append the specific validation error to the conversation, and let the model correct itself. One retry fixes the vast majority of malformed outputs.
Decision 3: Retries That Distinguish Failure Types
Every provider call needs retry logic, and the naive version — retry three times on any exception — is actively harmful. You'll hammer a rate limit into a longer ban and retry non-retryable errors pointlessly.
Classify errors and respond appropriately:
Set an explicit timeout on every call. The default in most HTTP clients is far too generous — a hung request holding a slot for two minutes is worse than a fast failure with a graceful fallback.
Decision 4: Structured Logging With a Session ID
Print statements do not survive contact with production. What you need is structured, queryable logs with a session identifier threaded through the entire request.
Log per iteration: session ID, iteration number, model, input token count, output token count, computed cost, latency, which tool was called, tool arguments, tool result size, and any error. Emit as JSON so it's queryable.
Python's contextvars are the clean way to do this in async code — set the session ID once at request entry and every log line within that task picks it up without threading a parameter through fifteen functions.
The test of whether your logging is adequate: a customer complains about a conversation from Tuesday. Can you pull the full transcript with tool calls and costs in under sixty seconds? If not, you'll be guessing.
Decision 5: Keep Deterministic Logic Out of the Model
Python is a perfectly good programming language. Use it.
Pricing calculations, service area checks, business hours, eligibility rules, escalation thresholds — all of these have correct answers computable in ordinary code. Every one you delegate to the model is a place it can be creative when you needed it to be right.
The right architecture is a thin layer of language understanding around a lot of boring, unit-testable business logic. The model decides what the customer wants and what to say. Your code decides what's true.
This has a testing benefit that compounds: the deterministic parts get normal unit tests with normal assertions, and you can run thousands of them in seconds. Only the language-understanding boundary needs the slow, expensive, probabilistic evaluation.
Decision 6: Costs Tracked Per Request, Capped Hard
Python makes it easy to write a loop that costs $2,000 overnight. The controls are simple and almost nobody adds them until after the first incident.
Track cumulative token usage per session and enforce a ceiling. Track cost per conversation and log it alongside the outcome, so you can compute cost per booked appointment rather than just cost per thousand calls. Alert on cost velocity — spend per hour compared to a rolling baseline — because monthly totals tell you about a disaster after it finished.
And tier your models. In most agent systems, a meaningful share of calls are classification, routing, or extraction that a cheap model handles identically. Route by task type, not by habit. Halving the model bill is usually a config change, not a rewrite.
Decision 7: Evaluation as Code, Run Before Every Change
The habit that separates teams that improve from teams that thrash: a regression suite of real cases.
Collect fifty to two hundred actual conversations with known correct outcomes. Store them as fixtures. Write an evaluation harness that replays them through the current agent and scores the results — exact match where there's a right answer, a model-based judge where quality is subjective.
Run it before every prompt change, every model version bump, and every tool modification. Track the score over time.
Without this, prompt engineering is superstition. Someone tweaks wording, the three cases they tried by hand look better, and they ship a regression that shows up as a quiet drop in booking rate three weeks later. With it, you know within minutes whether a change helped.
A prompt change without an eval run is a deployment without a test suite. We stopped accepting that in normal software twenty years ago.
The Deployment Shape That Works
For a customer-facing Python agent, the boring, reliable architecture:
An async web framework accepting webhooks and requests, returning immediately after enqueueing.
A queue between intake and processing, so a traffic spike buffers instead of dropping. This is the single highest-value component people skip.
Async workers running the agent loop, scaled horizontally.
A datastore for conversation state and structured logs, with the session ID indexed.
Integration writes back to the CRM, calendar, and ad platform conversion APIs — the part that converts a conversation into a business outcome.
In the lead systems we run, that final integration layer is where most of the value sits. The agent reaching a lead within 90 seconds matters, but the qualification outcome flowing back into ad optimization is what took client accounts to a 9.2× peak ROAS.
The Honest Timeline
A working agent on your laptop: an afternoon. An agent handling real customers reliably, with retries, logging, evals, cost controls, and integrations: three to six weeks for a competent Python developer who has done it before, longer if not.
That ratio surprises people, and it's the reason so many AI pilots never reach production. The model was never the hard part.
If you'd rather have the production version running than build it twice, [book a free strategy call](/book) and we'll scope what your agent needs to do and what it takes to run it properly.
Free Weekly Briefing
One AI Marketing Tactic.
Every Tuesday. Free.
What's actually working across our client accounts right now — ROAS moves, follow-up sequences, creative angles. The stuff that isn't in any blog post yet.
No spam. Unsubscribe anytime. 1,200+ business owners already in.