Python Getting Started
This guide walks you through a minimal Python agent first using LangChain, then adds incremental memory-service integration. The goal is to keep code changes small while unlocking memory features one step at a time.
Make sure you have completed Python Dev Setup first.
Also complete Step 2 on that page (build local memory-service-langchain wheel + UV_FIND_LINKS); this is temporary until the package is released.
Step 1: Start with a Minimal Agent
Starting checkpoint: python/examples/langchain/doc-checkpoints/01-basic-agent
Create a minimal LangChain agent and expose it over HTTP with FastAPI (no memory-service imports yet):
from __future__ import annotations
import os
from typing import Any
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import PlainTextResponse
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
def extract_assistant_text(result: Any) -> str:
if not isinstance(result, dict):
return str(result)
messages = result.get("messages")
if not messages:
return str(result)
# LangChain quickstart pattern: read the final assistant message directly.
message = messages[-1]
text = getattr(message, "text", None)
if isinstance(text, str) and text:
return text
content = getattr(message, "content", "")
if isinstance(content, str):
return content
return str(content)
openai_base_url = os.getenv("OPENAI_BASE_URL")
if openai_base_url and not openai_base_url.rstrip("/").endswith("/v1"):
openai_base_url = openai_base_url.rstrip("/") + "/v1"
if openai_base_url:
os.environ.setdefault("OPENAI_API_BASE", openai_base_url)
model = ChatOpenAI(
model=os.getenv("OPENAI_MODEL", "gpt-4o"),
openai_api_base=openai_base_url,
api_key=os.getenv("OPENAI_API_KEY", "not-needed-for-tests"),
)
agent = create_agent(
model=model,
tools=[],
system_prompt="You are a Python memory-service demo agent.",
)
app = FastAPI(title="Python LangChain Quickstart Agent")
@app.get("/ready")
async def ready() -> dict[str, str]:
return {"status": "ok"}
@app.post("/chat")
async def chat(request: Request) -> PlainTextResponse:
user_message = (await request.body()).decode("utf-8").strip()
if not user_message:
raise HTTPException(400, "message is required")
result = agent.invoke({"messages": [{"role": "user", "content": user_message}]})
return PlainTextResponse(extract_assistant_text(result)) create_agent() builds a LangChain agent graph with the given model. Passing an empty tools=[] list gives a pure chat agent with no tool calls. The system_prompt primes every conversation with the agent’s role.
The POST /chat endpoint accepts a raw text body and returns raw text — no JSON serialization, keeping curl testing simple.
OPENAI_MODEL, OPENAI_BASE_URL, and OPENAI_API_KEY are read from environment variables, so you can point the same code at any OpenAI-compatible model endpoint without edits.
Add LangChain dependencies:
[project]
name = "langchain-doc-checkpoint-01-basic-agent"
version = "0.1.0"
description = "Memory Service Python docs checkpoint app"
requires-python = ">=3.10"
dependencies = [
"fastapi>=0.115.0,<1.0.0",
"langchain>=1.0.0,<2.0.0",
"langchain-openai>=1.0.0,<2.0.0",
"httpx>=0.28.0,<1.0.0",
"uvicorn>=0.34.0,<1.0.0"
]
[tool.uv]
package = false Run the app:
cd python/examples/langchain/doc-checkpoints/01-basic-agent
uv sync --frozen
uv run uvicorn app:app --host 0.0.0.0 --port 9090Test it with curl:
curl -NsSfX POST http://localhost:9090/chat \
-H "Content-Type: text/plain" \
-d "Hi, who are you?" Example output:
I am a Python memory-service demo agent. Step 2: Enable Memory-Backed Conversations
Starting checkpoint: python/examples/langchain/doc-checkpoints/02-with-memory
Checkpoint 02 is built from checkpoint 01 with three additions.
Add a checkpointer and wire it into the agent
checkpointer = MemoryServiceCheckpointSaver.from_env()
agent = create_agent(
model=model,
tools=[],
checkpointer=checkpointer,
system_prompt="You are a Python memory-service demo agent.",
)
app = FastAPI(title="Python LangChain Agent With Conversation Memory") What changed: MemoryServiceCheckpointSaver.from_env() is created and passed to create_agent() as checkpointer=.
Why: Without a checkpointer, the LangChain agent is stateless — each call starts with an empty message list. The MemoryServiceCheckpointSaver persists the full message thread after every agent step to the Memory Service context channel, keyed by thread_id. On the next call with the same thread_id, the agent loads that thread and continues the conversation where it left off.
install_fastapi_authorization_middleware(app) is also added. It reads the Authorization: Bearer <token> header from every request and stores it in a request-scoped context variable. The checkpointer automatically reads this token when making Memory Service API calls — you don’t need to pass it explicitly.
Change the endpoint to accept a conversation_id
async def ready() -> dict[str, str]:
return {"status": "ok"}
install_fastapi_authorization_middleware(app)
@app.post("/chat/{conversation_id}")
async def chat(conversation_id: str, request: Request) -> PlainTextResponse:
user_message = (await request.body()).decode("utf-8").strip()
if not user_message:
raise HTTPException(400, "message is required") What changed: The route changes from POST /chat to POST /chat/{conversation_id}, and the agent invocation passes {"configurable": {"thread_id": conversation_id}}.
Why: The thread_id in configurable is the key the checkpointer uses to load and save state. By mapping it to conversation_id from the URL path, each unique URL path corresponds to one persistent conversation thread. Two calls to /chat/abc share the same message history; a call to /chat/xyz starts a fresh one.
Make sure Memory Service and Keycloak are running, then define a helper to get a user token:
function get-token() {
curl -sSfX POST http://localhost:8081/realms/memory-service/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=memory-service-client" \
-d "client_secret=change-me" \
-d "grant_type=password" \
-d "username=bob" \
-d "password=bob" \
| jq -r '.access_token'
}
curl -NsSfX POST http://localhost:9090/chat/46711077-9752-4cb1-ae45-9bad182503fc \
-H "Authorization: Bearer $(get-token)" \
-H "Content-Type: text/plain" \
-d "Hi, I'm Hiram, who are you?" Example output:
Hi Hiram! I am a Python memory-service demo agent. curl -NsSfX POST http://localhost:9090/chat/46711077-9752-4cb1-ae45-9bad182503fc \
-H "Authorization: Bearer $(get-token)" \
-H "Content-Type: text/plain" \
-d "Who am I?" Example output:
You are Hiram. Next Steps
Continue to Conversation History to record user/AI turns and expose conversation APIs for frontend clients.