A LangGraph ReAct agent backed by Amazon Bedrock Mantle via its OpenAI-compatible API. It answers weather questions using a get_weather tool (powered by wttr.in).
LangChain is used only for primitives (ChatOpenAI, @tool). The agent loop is a compiled LangGraph from langgraph.prebuilt.create_react_agent.
- Python 3.10+ (local dev) or Docker
- A Bedrock Mantle API key and base URL for your region
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtCreate a .env file in the project root:
OPENAI_API_KEY=bedrock-api-key-***
OPENAI_BASE_URL=https://bedrock-mantle.ap-south-1.api.aws/v1
OPENAI_MODEL=openai.gpt-oss-20b
PORT=80Default model is openai.gpt-oss-20b — the cheapest Bedrock Mantle option (~$0.07/1M input, ~$0.30/1M output vs 2× for 120b).
python agent.pyuvicorn app:app --reload --port 8000curl http://localhost:8000/health
curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{"message": "What is the weather in Paris?"}'Interactive API docs: http://localhost:8000/docs
Create a .env file with your Bedrock credentials, then:
docker compose up --buildThe service listens on http://localhost:80 (host and container both use port 80).
curl -X POST http://localhost/chat \
-H "Content-Type: application/json" \
-d '{"message": "Is it warm in Mumbai right now?"}'Stop the service:
docker compose downagent.py # LangGraph agent factory, invoke logic, CLI
app.py # FastAPI HTTP server
session_store.py # In-memory sessions (not LangGraph checkpointing)
tools.py # Weather tool
Dockerfile
docker-compose.yml
requirements.txt
ChatOpenAIcalls Bedrock Mantle at/v1/chat/completions.create_react_agent(LangGraph) runs a ReAct loop: the model may call tools, then answer.get_weatherfetches live data from wttr.in and returns a short summary.- Sessions use an in-app
SessionStorekeyed bythread_id. Each HTTP/CLI turn invokes the graph with only the current user message; recent cities from prior turns are injected as short context. This is not LangGraph’s checkpointer — the graph does not persist message history across invokes.
Each conversation is keyed by a thread_id. Reuse the same ID so follow-up questions can use session context (e.g. recently mentioned cities).
- First message without
thread_id→ server creates one and returns it - Follow-up messages → send the same
thread_idback - Sessions live in memory inside the process (lost on restart)
Example multi-turn flow:
# Start a session
curl -X POST http://localhost/chat \
-H "Content-Type: application/json" \
-d '{"message": "What is the weather in Paris?"}'
# → {"reply": "...", "thread_id": "abc-123"}
# Continue the same session
curl -X POST http://localhost/chat \
-H "Content-Type: application/json" \
-d '{"message": "What about London?", "thread_id": "abc-123"}'
# End a session (optional)
curl -X DELETE http://localhost/sessions/abc-123With docker-compose, each container has its own SessionStore. For multiple replicas, use a shared store (e.g. Redis) or adopt a LangGraph checkpointer backed by Redis/Postgres if you want full graph state persistence.
When the caller sends an x-session-id header on /chat, it is forwarded on the LLM calls so the Akto gateway can apply session-based guardrails. If the caller omits the header, no session header is sent upstream:
curl -X POST http://localhost/chat \
-H "Content-Type: application/json" \
-H "x-session-id: my-session-1" \
-d '{"message": "What is the weather in Paris?"}'| Method | Path | Description |
|---|---|---|
| GET | /health |
Liveness check |
| POST | /chat |
Send a message, get a reply |
| DELETE | /sessions/{thread_id} |
Clear a conversation session |
POST /chat body:
{
"message": "What's the weather in Paris?",
"thread_id": "optional-existing-session-id"
}Response:
{
"reply": "...",
"thread_id": "abc-123"
}