Skip to main content

Fast LangChain Server

Production HTTP server for LangChain/LangGraph agents — OpenAI-compatible API, streaming, session memory, authentication, middleware, and agent discovery.

License: MIT Python 3.11+ PyPI version

Overview

Fast LangChain Server transforms any LangChain/LangGraph agent into a production-ready HTTP service. Instantiate Server, call run() — or expose server.app to any ASGI server.

Features

  • Single entry point — one class (Server) to create, configure, and run
  • OpenAI-Compatible API — Drop-in compatible with OpenAI clients (/v1/chat/completions)
  • Streaming — Real-time token streaming via Server-Sent Events (SSE)
  • Session Memory — Conversation history with local or Redis backends
  • Authentication — Pluggable AuthProvider: API keys, JWT Bearer tokens, or compose multiple via |
  • Middleware — Composable chain: AgentMiddleware instances go through the internal chain; Starlette/ASGI classes go to app.add_middleware()
  • Authorization — Per-endpoint AuthCheck rules with built-ins: require_scopes, allow_own_session, etc.
  • Lifespan — Composable startup/shutdown lifecycle via @lifespan decorator and | operator
  • A2A Protocol — Agent-to-Agent JSON-RPC 2.0 with autonomous execution support (enabled by default)
  • OpenTelemetry — Distributed tracing with W3C TraceContext propagation
  • Production Ready — Health checks, structured logging, Docker-ready

Quick Start

Installation

pip install fast-langchain-server

Minimal example

# agent.py
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from fast_langchain_server import Server
import os

@tool
def add(a: float, b: float) -> float:
    """Add two numbers."""
    return a + b

model = ChatOpenAI(
    model=os.getenv("MODEL_NAME"),
    api_key=os.getenv("MODEL_API_KEY"),
    base_url=os.getenv("MODEL_API_URL"),
)

agent = create_agent(model=model, tools=[add])

server = Server(
    agent,
    tools=[add],
    agent_name="my-agent",
    agent_description="A helpful assistant with math capabilities",
)

# Expose for uvicorn / gunicorn
app = server.app

Configure environment variables in .env:

MODEL_NAME=gpt-4o
MODEL_API_URL=https://api.openai.com/v1
MODEL_API_KEY=sk-...

Run:

uvicorn agent:app

Or run directly from code:

if __name__ == "__main__":
    server.run()                    # uses AGENT_PORT / default 8000
    server.run(port=9000)           # explicit port
    server.run(port=9000, reload=True)  # dev mode

CLI

# Auto-discover a Server instance or agent in agent.py
fast-langchain-server run agent.py

# Explicit attribute
fast-langchain-server run agent.py:server --port 9000 --reload

Server configuration

All constructor parameters fall back to environment variables when not provided:

server = Server(
    agent,                                    # required
    tools=[add, search],                      # exposed in discovery card
    agent_name="my-agent",                    # AGENT_NAME env var
    agent_description="Does math and search", # AGENT_DESCRIPTION env var
    a2a=True,                                 # enable A2A JSON-RPC (default True)
    lifespan=DEFAULT_LIFESPAN | my_lifespan,  # composable startup/shutdown
    memory=my_memory_backend,                 # inject a custom Memory instance
    # any AgentServerSettings field:
    agent_port=9000,                          # AGENT_PORT
    memory_type="redis",                      # MEMORY_TYPE
    memory_redis_url="redis://localhost:6379",
)

run() parameters

run() accepts all uvicorn.run() keyword arguments:

server.run(
    host="0.0.0.0",
    port=8080,
    reload=True,           # dev only — incompatible with workers
    workers=4,
    log_level="warning",
    access_log=True,
    ssl_keyfile="key.pem",
    ssl_certfile="cert.pem",
)

API Endpoints

Method Endpoint Description
GET /health Liveness probe
GET /ready Readiness probe
POST /v1/chat/completions OpenAI-compatible chat (streaming + non-streaming)
GET /.well-known/agent.json A2A agent discovery card
GET /memory/sessions List active sessions
DELETE /memory/sessions/{id} Delete a session
POST / A2A JSON-RPC 2.0 (when a2a=True, default)

Chat completions

# Non-streaming
curl -X POST http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"messages": [{"role": "user", "content": "What is 2+2?"}]}'

# Streaming
curl -N -X POST http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"messages": [{"role": "user", "content": "Tell me a joke"}], "stream": true}'

Pass session_id in the body or via X-Session-ID header to continue a conversation:

curl -X POST http://localhost:8000/v1/chat/completions \
  -d '{"messages": [{"role": "user", "content": "And 3+3?"}], "session_id": "abc123"}'

Agent discovery card

curl http://localhost:8000/.well-known/agent.json | jq
{
  "name": "math-agent",
  "description": "A helpful math agent",
  "url": "http://localhost:8000",
  "skills": [{"id": "add", "name": "add", "description": "Add two numbers."}],
  "capabilities": {"streaming": true, "memory": true, "a2a": true}
}

Skills are populated from the tools parameter. Each tool's name and docstring become the skill's name and description.


Authentication

from fast_langchain_server import Server, AuthMiddleware, EnvAPIKeyProvider, JWTProvider

server = Server(agent, tools=[...])

# Option A: API keys from AGENT_API_KEYS env var (comma-separated)
server.add_middleware(AuthMiddleware(provider=EnvAPIKeyProvider()))

# Option B: JWT Bearer tokens
server.add_middleware(AuthMiddleware(
    provider=JWTProvider(
        jwks_url="https://auth.example.com/.well-known/jwks.json",
        audience="my-agent",
    )
))

# Option C: compose providers (JWT first, API key fallback)
server.add_middleware(AuthMiddleware(
    provider=JWTProvider(...) | EnvAPIKeyProvider()
))

Requests must include the token in one of:

Authorization: Bearer <token>
X-API-Key: <token>

/health, /ready, /.well-known/agent.json are excluded from auth by default.

Available providers

Provider Description
APIKeyProvider(keys) Static dict {"key": "owner"}
EnvAPIKeyProvider(env_var) Comma-separated keys from env var (default: AGENT_API_KEYS)
JWTProvider(jwks_url, audience) Validates Bearer tokens against JWKS. Requires PyJWT[crypto]
MultiAuth(*providers) Tries providers in order — created automatically via |

Custom provider

from fast_langchain_server import AuthProvider, AuthToken

class MyDatabaseProvider(AuthProvider):
    async def verify_token(self, token: str) -> AuthToken | None:
        user = await db.lookup_token(token)
        if user:
            return AuthToken(subject=user.id, scopes=user.scopes, raw=token)
        return None

server.add_middleware(AuthMiddleware(provider=MyDatabaseProvider()))

Middleware

add_middleware() routes by type:

  • AgentMiddleware instance → added to the internal request-processing chain
  • Any other class → forwarded to app.add_middleware() (Starlette/ASGI level)
from fast_langchain_server import Server, AuthMiddleware, TimingMiddleware, RateLimitMiddleware, EnvAPIKeyProvider
from starlette.middleware.cors import CORSMiddleware

server = Server(agent, tools=[...])

(
    server
    .add_middleware(TimingMiddleware())
    .add_middleware(AuthMiddleware(provider=EnvAPIKeyProvider()))
    .add_middleware(RateLimitMiddleware(max_rpm=60))
    .add_middleware(CORSMiddleware, allow_origins=["*"])  # ASGI-level
)

Built-in middlewares

Middleware Description
AuthMiddleware(provider) Verifies tokens and sets ctx.auth_token
TimingMiddleware(log_level) Logs elapsed time per request
RateLimitMiddleware(max_rpm) Token-bucket rate limiter per session (default: 60 req/min)
A2AClientMiddleware(agents) Lets the agent delegate to remote A2A-compatible agents via tools

Agent-to-Agent (A2A) Delegation

Allow your agent to call other A2A-compatible agents over HTTP. Each remote agent becomes a tool the LLM can invoke.

from fast_langchain_server import A2AClientMiddleware, RemoteAgentConfig
from langchain.agents import create_agent

# Option A: explicit config
a2a = A2AClientMiddleware(agents=[
    RemoteAgentConfig(
        url="http://math-agent:8001",
        name="math_agent",
        description="Solves arithmetic and algebra problems.",
    ),
    RemoteAgentConfig(
        url="http://search-agent:8002",
        name="search_agent",
        description="Searches the web for current information.",
    ),
])

# Option B: auto-discover from agent cards
a2a = await A2AClientMiddleware.discover(
    "http://math-agent:8001",
    "http://search-agent:8002",
)

agent = create_agent(model=model, tools=[...], middleware=[a2a])
server = Server(agent, agent_name="orchestrator")

The middleware:

  • Adds a tool for each remote agent
  • Injects system prompts guiding when to delegate
  • Handles JSON-RPC 2.0 calls with automatic polling
  • Supports session_id for multi-turn conversations with remote agents

Custom middleware

from fast_langchain_server import AgentMiddleware

class LoggingMiddleware(AgentMiddleware):
    async def on_request(self, ctx, call_next):
        print(f"Request: session={ctx.session_id}")
        result = await call_next(ctx)
        print(f"Done: session={ctx.session_id}")
        return result

server.add_middleware(LoggingMiddleware())

Hooks:

Hook When it runs
on_request(ctx, call_next) Once per chat request — wraps the full lifecycle
on_agent_run(ctx, call_next) Immediately before/after the LangGraph agent runs

Authorization

from fast_langchain_server import AuthorizationMiddleware, require_scopes, allow_own_session

server.add_middleware(AuthorizationMiddleware({
    "/v1/chat/completions": require_scopes("chat"),
    "/memory/sessions":     require_scopes("admin"),
}))

Built-in checks

Check Description
require_scopes(*scopes) All listed scopes must be present in the token
allow_any_authenticated() Any valid token is sufficient
allow_own_session() Token subject must own the requested session
deny_all() Always denies (maintenance mode)
all_of(*checks) AND — all checks must pass
any_of(*checks) OR — at least one check must pass

Lifespan

from fast_langchain_server import Server, lifespan, DEFAULT_LIFESPAN

@lifespan
async def db_lifespan(server):
    server.lifespan_context["db"] = await connect_db()
    yield {}
    await server.lifespan_context["db"].close()

@lifespan
async def cache_lifespan(server):
    server.lifespan_context["cache"] = await connect_redis()
    yield {}
    await server.lifespan_context["cache"].aclose()

server = Server(
    agent,
    tools=[...],
    lifespan=DEFAULT_LIFESPAN | db_lifespan | cache_lifespan,
)

Composes left-to-right (enters left first, exits right first — LIFO). DEFAULT_LIFESPAN handles OTel init, startup/shutdown logging, the autonomous loop, and graceful teardown.


A2A Protocol

Agent-to-Agent is enabled by default (a2a=True). Disable it with a2a=False:

server = Server(agent, tools=[...])          # a2a=True, POST / mounted
server = Server(agent, tools=[...], a2a=False)  # no A2A endpoint

When enabled, the /.well-known/agent.json card includes "a2a": true and "supportedProtocols": ["jsonrpc"].


AgentContext

Available throughout the middleware chain and agent execution:

ctx.session_id      # resolved session identifier
ctx.request_id      # UUID for this request
ctx.user_input      # last user message
ctx.model           # model name from the request
ctx.request         # raw Starlette Request object (None for A2A entry points)
ctx.headers         # lowercased HTTP headers dict (derived from ctx.request)
ctx.otel_context    # W3C TraceContext for distributed tracing
ctx.auth_token      # set by AuthMiddleware (or None)

ctx.set_meta("key", value)
ctx.get_meta("key", default)

await ctx.emit_progress("tool_call", "search_web")  # push SSE progress event

The raw request object exposes anything from the incoming HTTP request:

async def on_request(self, ctx: AgentContext, call_next):
    if ctx.request is not None:
        client_ip = ctx.request.client.host
        cookies = ctx.request.cookies
        query_params = dict(ctx.request.query_params)
    return await call_next(ctx)

ctx.headers is a convenience property that returns a lower-cased dict of all request headers. It returns {} when ctx.request is None (e.g. A2A calls).


Configuration reference

All settings fall back to environment variables:

Variable Default Description
AGENT_NAME auto-generated Agent identifier
MODEL_API_URL — LLM API base URL
MODEL_NAME — Model name (gpt-4o, llama3.2…)
MODEL_API_KEY not-needed API key
MODEL_TEMPERATURE 0.7 Sampling temperature
AGENT_DESCRIPTION "AI Agent" Human-readable description
AGENT_INSTRUCTIONS — System prompt
AGENT_PORT 8000 HTTP port
AGENT_LOG_LEVEL INFO Log level
AGENT_ACCESS_LOG false Uvicorn access log
MEMORY_ENABLED true Enable session memory
MEMORY_TYPE local local | redis | null
MEMORY_REDIS_URL — Redis connection URL
MEMORY_CONTEXT_LIMIT 20 Messages per request
AGENT_API_KEYS — Comma-separated API keys (EnvAPIKeyProvider)
TASK_MANAGER_TYPE local local | none (overridden by a2a=)
AUTONOMOUS_GOAL — Goal for autonomous execution loop
AUTONOMOUS_INTERVAL_SECONDS 0 Interval between autonomous runs
OTEL_ENABLED false Enable OpenTelemetry
OTEL_EXPORTER_OTLP_ENDPOINT — OTel collector endpoint

Docker

docker run -p 8000:8000 \
  -e MODEL_API_URL=https://api.openai.com/v1 \
  -e MODEL_NAME=gpt-4o \
  -e MODEL_API_KEY=sk-... \
  -v $(pwd)/agent.py:/app/agent.py \
  fast-langchain-server

Architecture

HTTP Request
     │
     ▼
 [FastAPI app]  — builds AgentContext
     │
     ▼
 [AuthMiddleware]           verify token → ctx.auth_token
     │
     ▼
 [AuthorizationMiddleware]  check scopes per endpoint
     │
     ▼
 [RateLimitMiddleware]      token bucket per session
     │
     ▼
 [TimingMiddleware]         measure elapsed time
     │
     ▼
 [Server._run_agent / _stream_response]
     │
     ▼
 [LangGraph agent.ainvoke / astream]
     │
     ▼
 [Memory backend]           save messages
     │
     ▼
 HTTP Response / SSE stream

Module map:

Module Purpose
server.py Server — single entry point
context.py AgentContext — per-request state object
auth.py AuthProvider, APIKeyProvider, EnvAPIKeyProvider, JWTProvider, MultiAuth
middleware.py AgentMiddleware, AuthMiddleware, TimingMiddleware, RateLimitMiddleware
authorization.py AuthorizationMiddleware, require_scopes, allow_own_session, …
lifespan.py @lifespan, ComposedLifespan, DEFAULT_LIFESPAN
memory.py LocalMemory, RedisMemory, NullMemory
a2a.py A2A protocol, LocalTaskManager, JSON-RPC handlers
a2a_client.py A2AClientMiddleware — delegate to remote A2A agents
telemetry.py OpenTelemetry init and trace context propagation
cli.py fast-langchain-server CLI

Development

make dev     # install dev dependencies
make test    # run tests (185 tests)
make lint    # lint
make run     # local hot-reload

License

MIT — see LICENSE for details.

Metadata

Release files for fast-langchain-server 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fast-langchain-server 0.6.0
File Size Uploaded
fast_langchain_server-0.6.0.tar.gz 87.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fast-langchain-server 0.6.0
File Interpreter ABI Platform
fast_langchain_server-0.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 140.4 kB

Release files / fast_langchain_server-0.6.0.tar.gz

Download URL fast_langchain_server-0.6.0.tar.gz
Size 87.9 kB
Tags Source
SHA-256 checksum
How to use checksums
39345419276e7f5b5757f18bf7c67188b2495024db946138ad3862912ec41c4a
BLAKE2b-256 checksum
How to use checksums
7bdef1ee14f60b077ff30bf73fdcc1e6e03f63eb379591947adca1f0c1cee09a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.12

Release files / fast_langchain_server-0.6.0-py3-none-any.whl

Download URL fast_langchain_server-0.6.0-py3-none-any.whl
Size 52.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b322572ee436138689144feea74b27a4755aa513f0ec588cc8db75b49085d126
BLAKE2b-256 checksum
How to use checksums
ac4acd1bed7f3dceefbf4c85c3ca84bd9f7227ab6c36741f40741331b68afad9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.12

Release history Release notifications | RSS feed

This release

0.6.0 This release

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page