Skip to content

Production Agent API with FastAPI

View the complete sample on GitHub

Related documentation: Strands Agents documentation, Evaluation SDK, Session management

A single runnable Python service, in the shape of a benchmark chatbot repo (FastAPI, three tools, one model call), rebuilt on Strands Agents. It wires the production concerns you actually ship with: an OpenAI or Bedrock provider, max turns and a token budget, cancellation on client disconnect, a SQLite-backed session store built on the shipped RepositorySessionManager, one MCP server, OpenTelemetry export, and a 10-case Strands Evals regression suite.

Overview

InformationDetails
Agent ArchitectureSingle-agent
Native ToolsNone
Custom Toolsget_current_time, calculate, get_weather
MCP ServersOne bundled server (knowledge-base) exposing lookup_faq, list_faq_topics
Use Case VerticalTechnical / production starter
ComplexityAdvanced
Model ProviderAmazon Bedrock (default) or OpenAI
SDK UsedStrands Agents SDK + Strands Agents Evals

Architecture

Architecture Diagram

A user calls the FastAPI service, which runs a Strands agent per request. Each POST /chat applies per-request lifecycle limits (max turns and a token budget) and a cancel signal wired to client disconnect. The agent makes one model call per turn against Amazon Bedrock (or OpenAI), calls its three local tools plus the tools loaded from the MCP server at startup, and persists conversation history in the SQLite session store built on RepositorySessionManager. OpenTelemetry export is configured at startup.

Key Features

  • FastAPI service with POST /chat and GET /health.
  • Three tools plus one model call per turn, the shape of the benchmark repo.
  • Native lifecycle controls: max turns and a token budget per request.
  • Cancellation on client disconnect via a cancel_signal.
  • A SQLite session store implemented on the shipped RepositorySessionManager.
  • One MCP server, connected at startup with its tools loaded into the agent.
  • OpenTelemetry export (OTLP endpoint, or console for local runs).
  • A 10-case Strands Evals regression suite scoring output and trajectory.

Prerequisites

  • Python 3.10+
  • uv for dependency management
  • For Bedrock (default): AWS credentials in the default credential chain and model access for the configured model
  • For OpenAI (optional): an OPENAI_API_KEY

Setup

  1. Configure environment variables:

    Terminal window
    cd python/05-technical-use-cases/fastapi-strands-production-starter
    cp .env.example .env
    # Edit .env: choose MODEL_PROVIDER, set model ids, limits, MCP and OTel settings
  2. Install dependencies:

    Terminal window
    uv venv
    uv pip install -e ".[evals,openai]" # drop "openai" if you only use Bedrock

Usage

The service loads the MCP server’s tools at startup, so start the MCP server first, then the API.

Start the MCP server (one terminal):

Terminal window
uv run python -m mcp_server.server

Start the API (another terminal):

Terminal window
uv run uvicorn app.main:app --port 8080

Send a chat request:

Terminal window
curl -X POST http://localhost:8080/chat \
-H 'Content-Type: application/json' \
-d '{"message": "What time is it in Tokyo?", "session_id": "demo"}'

Send another request with the same session_id to continue the conversation — history is restored from the session store.

OpenAI or Bedrock provider

The agent runs on either provider, selected by MODEL_PROVIDER:

  • MODEL_PROVIDER=bedrock (default) uses BedrockModel with AWS_REGION and BEDROCK_MODEL_ID.
  • MODEL_PROVIDER=openai uses OpenAIModel with OPENAI_API_KEY and OPENAI_MODEL_ID.

The provider is built in app/agent_factory.py::build_model; the OpenAI import is lazy so a Bedrock-only install needs no OpenAI dependency.

Max turns and token budget

Every request passes a per-invocation Limits (built from MAX_TURNS, MAX_OUTPUT_TOKENS, MAX_TOTAL_TOKENS) to invoke_async. When a cap is reached, the agent loop stops gracefully at a turn boundary with a stop_reason of limit_turns, limit_total_tokens, or limit_output_tokens — no exception, and agent.messages stays reinvokable. See app/config.py::Settings.limits.

Cancellation on client disconnect

POST /chat starts a background watcher that polls request.is_disconnected() and sets a threading.Event passed to invoke_async as cancel_signal. If the caller goes away, the agent stops promptly at its next cancellation-safe point and returns stop_reason="cancelled". See app/main.py::_watch_disconnect.

SQLite session store built on RepositorySessionManager

The SDK ships File, S3, and Repository session managers, among others; SQLite is not a first-party one. app/session_store.py provides it by implementing the SessionRepository interface (8 CRUD methods) and combining it with RepositorySessionManager — the same pattern the built-in FileSessionManager uses. Sessions, agents, and per-message rows persist in one SQLite file (SESSION_DB_PATH), so conversations survive restarts.

One MCP server

mcp_server/server.py is a single MCP server exposed over Streamable HTTP (FastMCP), providing a small knowledge base (lookup_faq, list_faq_topics). At startup the API connects to it with the Strands MCPClient (MCP_SERVER_URL) and loads its tools alongside the local tools, so the agent can call them in the same turn.

OTel export

app/agent_factory.py::configure_telemetry sets up OpenTelemetry once at startup with StrandsTelemetry: it exports to OTEL_EXPORTER_OTLP_ENDPOINT when set, and otherwise falls back to the console exporter so traces are visible during local runs. OTEL_SERVICE_NAME names the service in traces.

10-case Strands Evals regression suite

evals/eval_suite.py defines ten Cases spanning each tool (time, math, weather), general knowledge, reasoning, and a no-tool greeting, with expected_trajectory on the tool cases. An Experiment scores the agent’s responses with the LLM-based OutputEvaluator and the tool choices with the TrajectoryEvaluator.

Run the suite:

Terminal window
uv run python -m evals.eval_suite

Results print to the console and are saved to fastapi_starter_evaluation.json. The suite runs both the agent under test and the judge on Amazon Bedrock (Claude), regardless of the MODEL_PROVIDER configured for the service — evals/eval_suite.py builds the eval agent with BedrockModel() directly. So this step needs AWS credentials with Bedrock access even if you run the API on OpenAI.

If you only have an OPENAI_API_KEY, run the OpenAI variant instead — same cases and rubrics, with both the agent and the judge on OpenAI:

Terminal window
uv run python -m evals.eval_suite_openai

Cleanup

This sample provisions no cloud infrastructure. To remove local artifacts:

Terminal window
rm -f sessions.db fastapi_starter_evaluation.json
rm -rf .venv

Troubleshooting

SymptomLikely CauseFix
/chat returns 503 mentioning the providerProvider credentials/model access missing or expiredConfigure the provider’s credentials and model access (see below)
Your session has expiredAWS login session expiredReauthenticate (e.g. aws login), then retry
MissingDependencyException: ... botocore[crt]AWS login/SSO credential provider needs the crt extraIt’s already a dependency; ensure the install completed (uv pip install -e .)
Startup logs “could not connect” to MCPMCP server not running / wrong MCP_SERVER_URLStart python -m mcp_server.server; check the URL/port
error while attempting to bind on address ... address already in usePort 8080 is already taken by another processStart on a free port (--port 8081), or free 8080 first
stop_reason is limit_turnsRequest hit the MAX_TURNS capRaise MAX_TURNS, or accept it as the intended budget
Eval run errors on the judge modelEvaluators need Bedrock accessConfigure AWS credentials with access to the judge model

Additional Resources

Disclaimer

This sample is provided for educational and demonstration purposes only. It is not intended for production use without further development, testing, and hardening. For production, consider a shared session store (the SDK ships S3), authentication and input validation on the API, a managed OTLP backend, and appropriate content filtering and safety measures.