LangServe — route registration, streaming, and playground failures
LangServe turns any LCEL chain into a FastAPI service with automatic streaming, batch, and playground endpoints. Deployment failures are usually about route setup, CORS, or streaming plumbing — not the chain itself.
Quick fix (TL;DR)
/invoke, /batch, /stream, /stream_log, plus /playground. Fix deployment issues by (a) calling add_routes(app, chain, path="/my-chain") with unique paths per chain, (b) enabling CORS if the playground is served on a different origin, (c) using async chains for streaming and configuring your ASGI server to not buffer, and (d) declaring ConfigurableFieldSpec when the chain needs session_id or user_id from clients.Real error messages you'll see
These are the exact strings returned by the LangChain framework and its integrations when this error occurs. Copy-paste-searching any of them should land on this page.
ValueError: Path "/chain" is already used by another chain. Use distinct paths.
Access to fetch at http://api.example.com/chain/playground_stream from origin http://localhost:5173 has been blocked by CORS policy
# curl --no-buffer http://api/chain/stream — chunks arrive fine # Same chain behind nginx — response arrives as one blob # proxy_buffering not disabled
Reference
LangServe endpoints auto-created by add_routes
| Endpoint | Purpose |
|---|---|
POST /invoke | Sync invocation |
POST /batch | Batched invocation |
POST /stream | Server-sent events with chunks |
POST /stream_log | Detailed streaming log with intermediate steps |
POST /stream_events | Streaming events (v2) |
GET /playground | Interactive UI for testing |
GET /input_schema | JSON schema for chain input |
GET /output_schema | JSON schema for chain output |
GET /config_schema | JSON schema for configurable fields |
Root causes, ranked by frequency
Based on developer reports across LangChain forums, GitHub issues, and Discord community during 2025–2026.
- 24%Non-unique paths across add_routes calls. Two chains registered at "/chain" or nested paths collide.
- 18%CORS not configured. Playground served on different origin from API; browser blocks.
- 14%Streaming buffered by nginx / proxy. Missing
proxy_buffering offand related settings. - 10%Sync chain in async LangServe context. Blocking the event loop kills throughput.
- 8%Configurable fields not declared. Playground input has no field for
session_id; user cannot test. - 7%Large per-request payloads exceed proxy limit. nginx 413 on RAG queries with large context.
- 7%Missing dependencies for playground.
langserve[all]extra not installed. - 12%Authentication not integrated. Endpoints publicly accessible; anyone can invoke.
Fixes — copy-paste solutions
Register chains with unique paths and clear input/output schemas
Use add_routes(app, chain, path="/chain-name"). Each chain gets its own subpath and its own set of endpoints. Declare input_type/output_type when the chain's inferred schema is not friendly.
from fastapi import FastAPI from langserve import add_routes from langchain_openai import ChatOpenAI from langchain_anthropic import ChatAnthropic from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from pydantic import BaseModel app = FastAPI(title="MyApp LLM Service") # Chain 1: OpenAI-backed general QA qa_prompt = ChatPromptTemplate.from_template("Answer briefly: {question}") qa_chain = qa_prompt | ChatOpenAI(model="gpt-4o-mini") | StrOutputParser() # Chain 2: Claude-backed summarisation summarise_prompt = ChatPromptTemplate.from_template( "Summarise this in 3 bullet points:\n\n{text}" ) summarise_chain = ( summarise_prompt | ChatAnthropic(model="claude-haiku-4-5-20251001") | StrOutputParser() ) # Explicit input schemas for the playground UI class QAInput(BaseModel): question: str class SummariseInput(BaseModel): text: str # Register each chain at a unique path add_routes(app, qa_chain, path="/qa", input_type=QAInput, output_type=str) add_routes(app, summarise_chain, path="/summarise", input_type=SummariseInput, output_type=str) # For the app to run: # pip install "langserve[all]" fastapi uvicorn # uvicorn langserve_app:app --host 0.0.0.0 --port 8000 # Endpoints created: # POST /qa/invoke # POST /qa/batch # POST /qa/stream # GET /qa/playground # POST /summarise/invoke # ...
input_type/output_type Pydantic models give the playground UI proper form fields. Without them, the playground shows a raw JSON textarea — usable but ugly.Enable CORS and configure ASGI server for streaming
For playground on a different origin, enable CORS. For streaming to work end to end, disable proxy buffering and configure uvicorn correctly.
from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from langserve import add_routes app = FastAPI() # CORS — allow the playground origin (and any other client) app.add_middleware( CORSMiddleware, allow_origins=[ "https://app.example.com", # your prod frontend "http://localhost:5173", # local dev ], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], expose_headers=["*"], ) add_routes(app, my_chain, path="/chain") # Run with uvicorn — do NOT enable response buffering # uvicorn langserve_app:app \ # --host 0.0.0.0 --port 8000 \ # --workers 4 \ # --loop uvloop \ # --http h11 \ # --limit-concurrency 200 \ # # NO --proxy-headers unless you actually have a proxy
# nginx site block for LangServe server { listen 443 ssl http2; server_name api.example.com; location /chain/ { proxy_pass http://langserve:8000; proxy_http_version 1.1; # === CRITICAL for streaming === proxy_buffering off; # do not buffer response proxy_cache off; # do not cache streamed proxy_read_timeout 300s; # long timeout for slow streams proxy_connect_timeout 10s; proxy_send_timeout 300s; # Forward SSE / event-stream properly proxy_set_header Connection ""; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # === Increase body size for large RAG payloads === client_max_body_size 32M; proxy_read_timeout 300s; } }
Declare configurable fields for session_id and other per-request config
When your chain uses RunnableWithMessageHistory or other configurable fields, declare them via configurable_fields so they appear in the playground and are documented in the schema.
from langchain_core.runnables import ConfigurableFieldSpec from langchain_core.runnables.history import RunnableWithMessageHistory from langserve import add_routes # ... your chat chain and history factory here ... chain_with_history = RunnableWithMessageHistory( chain, get_session_history, input_messages_key="input", history_messages_key="history", history_factory_config=[ ConfigurableFieldSpec( id="user_id", annotation=str, name="User ID", description="Unique user identifier", default="", ), ConfigurableFieldSpec( id="conversation_id", annotation=str, name="Conversation ID", description="Conversation ID within the user's history", default="", ), ], ) # Register with LangServe — configurable fields become playground form inputs add_routes( app, chain_with_history, path="/chat", per_req_config_modifier=lambda config, req: { **config, "configurable": { "user_id": req.headers.get("x-user-id", ""), "conversation_id": req.headers.get("x-conversation-id", ""), } }, ) # The per_req_config_modifier lets you pull config from HTTP headers/auth # instead of trusting the client body. Critical for multi-tenant apps.
user_id in the HTTP session / JWT — never trust it from the request body. The per_req_config_modifier is where you enforce this.Prevention checklist
Ship these seven safeguards once and this error stops appearing in your logs.
- One chain per path in
add_routes; use distinct paths like/qa,/summarise. - Declare explicit Pydantic
input_type/output_typefor good playground UX. - Enable CORS with an explicit list of allowed origins; do not use
allow_origins=["*"]with credentials. - For streaming:
proxy_buffering offin nginx and any other proxy in the path. - Declare configurable fields with
ConfigurableFieldSpecfor session/user routing. - Use
per_req_config_modifierto enforce security-sensitive config (user_id from auth, not body). - Deploy behind an authentication layer — never expose LangServe endpoints publicly without auth.
Frequently asked questions
playground_type=None in add_routes.APIKeyHeader or JWT dependency to the LangServe router before add_routes. LangServe respects standard FastAPI auth patterns.Related errors & hubs
Get the weekly AI-error digest
New fixes, provider status recaps, and one deep tutorial — every Tuesday. 8,400+ engineers.