FastAgentic vs raw FastAPI
You already know FastAPI. Here's exactly what FastAgentic adds on top — and the 500+ lines of boilerplate it saves you from writing every time you ship an agent.
FastAgentic is not a replacement for FastAPI. It’s built on FastAPI. The question isn’t “FastAPI or FastAgentic” — it’s “FastAPI alone, or FastAPI with a purpose-built agent runtime on top?”
What you write in raw FastAPI
Here’s a stripped-down production agent endpoint on raw FastAPI. This is the minimum that an audit team would accept:
@app.post("/research")
async def research(
query: ResearchQuery,
user: User = Depends(current_user),
db: Database = Depends(get_db),
) -> StreamingResponse:
# 1. Check auth scopes
if "agent.run" not in user.scopes:
raise HTTPException(403)
# 2. Check rate limit
if await rate_limiter.exceeded(user.tenant_id):
raise HTTPException(429)
# 3. Check budget
if await budget.exceeded(user.tenant_id):
raise HTTPException(402)
# 4. Persist run metadata
run_id = await db.runs.insert(user_id=user.id, query=query.text)
# 5. Stream the agent
async def stream():
total_tokens = 0
try:
async for chunk in agent.astream(query.text):
total_tokens += chunk.usage.total_tokens
await db.runs.append_event(run_id, chunk)
yield f"data: {json.dumps(chunk.dict())}\n\n"
except Exception as e:
await db.runs.mark_failed(run_id, str(e))
raise
finally:
# 6. Record cost
await cost_tracker.record(
user_id=user.id,
tenant_id=user.tenant_id,
run_id=run_id,
tokens=total_tokens,
model=agent.model_name,
)
await db.runs.mark_complete(run_id)
return StreamingResponse(stream(), media_type="text/event-stream")
That’s ~40 lines and it still doesn’t have: MCP exposure, A2A surfacing, resumption after crash, structured OTel spans, PII masking, audit log, or policy engine. Add those and you’re at ~150 lines per endpoint. Across a dozen endpoints, that’s 1,500+ lines of near-duplicated boilerplate that somebody on your team has to keep in sync forever.
The same thing in FastAgentic
from fastagentic import App, agent_endpoint
from fastagentic.adapters import PydanticAIAdapter
app = App(
auth="oidc",
cost_tracker=RedisCostTracker(budget_per_tenant=100.0),
checkpoint_store=PostgresCheckpointStore(url=DB_URL),
)
@agent_endpoint(
"/research",
adapter=PydanticAIAdapter(agent),
policies=["agent.run"],
)
async def research(query: ResearchQuery) -> ResearchResult:
"""Answer research questions with cited sources."""
...
You get: auth, rate limits, budgets, streaming, audit logs, cost tracking, durable checkpoints, MCP exposure, A2A exposure, and OpenTelemetry spans. One decorator.
The boilerplate, categorized
| Concern | Raw FastAPI lines | FastAgentic |
|---|---|---|
| Auth middleware | ~30 | auth="oidc" |
| Rate limiting | ~25 | policy |
| Cost tracking | ~40 | cost_tracker= |
| Budget enforcement | ~20 | automatic |
| Streaming | ~35 | automatic |
| Durable checkpoints | ~80 | checkpoint_store= |
| MCP tool exposure | ~60 | automatic |
| A2A skill exposure | ~50 | automatic |
| OTel spans | ~25 | automatic |
| Audit logs | ~30 | automatic |
| Per endpoint | ~395 | ~5 |
What FastAgentic doesn’t take away
- FastAPI routing, middleware, dependency injection, and testing.
- Pydantic 2 models for request/response.
- Uvicorn + Gunicorn deployment.
- Your existing
Depends(),BackgroundTasks, andResponseusage. - pytest + httpx for testing.
If you can read FastAPI code, you can read FastAgentic code. It is FastAPI code — with the repetitive parts deleted.
When raw FastAPI is still the right call
- You’re writing a non-agent service that happens to call an LLM once.
- You have exotic requirements that the opinionated defaults would fight.
- You’re learning FastAPI and want zero magic.
Everywhere else, let FastAgentic delete the boilerplate.
Need FastAPI, LangGraph, or agent platform expertise?
Neul Labs — the team behind FastAgentic — takes on a limited number of consulting engagements each quarter. We help teams ship agents to production, fix broken LangGraph pipelines, and design governance for multi-tenant LLM platforms.