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):

app.py
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:

pyproject.toml
[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 9090

Test 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

app.py
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

app.py
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.