LangChain adapter

Connect a LangChain / LangGraph agent to the Astro runtime

View as Markdown

astropods-adapter-langchain wraps a LangChain executor — either langchain.agents.create_agent or langgraph.prebuilt.create_react_agent — and connects it to the Astro messaging sidecar. It runs astream(stream_mode="updates") under the hood, translates graph updates into the shared StreamHooks lifecycle, and auto-configures OTEL tracing.

Install

$pip install astropods-adapter-langchain

Requires Python 3.10+.

Quick start

1from langchain_anthropic import ChatAnthropic
2from langchain.agents import create_agent
3from astropods_adapter_langchain import LangChainAdapter, serve
4
5llm = ChatAnthropic(model="claude-sonnet-4-6")
6system_prompt = "You are a helpful assistant."
7agent = create_agent(llm, tools=[], system_prompt=system_prompt)
8
9adapter = LangChainAdapter(agent, name="My Agent", system_prompt=system_prompt)
10serve(adapter)

serve(adapter) connects to the messaging sidecar at localhost:9090 (or GRPC_SERVER_ADDR) and blocks until SIGINT / SIGTERM. Under ast dev and in production the address is injected for you.

API

LangChainAdapter(executor, name, system_prompt?, tools?, voice?)

ParameterTypeRequiredNotes
executorLangChain agentyesA compiled executor, e.g. from create_agent or create_react_agent.
namestrnoDisplay name. Defaults to "LangChain Agent".
system_promptstrnoShown in the playground’s config panel.
toolslistnoLangChain tool objects. Populates the playground tool list.
voiceVoiceProvidernoOptional STT provider (e.g. OpenAIVoice).

serve(adapter, options?)

Re-exported from astropods-adapter-core. Connects the adapter and blocks until shutdown. Also calls setup_observability() so OTEL tracing comes up automatically when OTEL_EXPORTER_OTLP_ENDPOINT is set.

ServeOptions

FieldTypeDefaultNotes
server_addressstros.environ.get("GRPC_SERVER_ADDR", "localhost:9090")Override the messaging gRPC address.
1from astropods_adapter_langchain import ServeOptions, serve
2serve(adapter, ServeOptions(server_address="astro-messaging:9090"))

How the executor maps to hooks

LangChainAdapter consumes executor.astream(..., stream_mode="updates"). Each graph update produces zero or more hook calls:

Update keySourceHook call
modellangchain.agents.create_agent (LangChain ≥ 0.3)on_chunk(text) for the assistant message body, then on_finish().
agentlanggraph.prebuilt.create_react_agent (LangGraph < 1.0)Same as model. The adapter handles both node names transparently.
toolsTool execution stepon_status_update({status: 'PROCESSING', custom_message: '...'}) and on_status_update({status: 'ANALYZING', ...}) around the tool call.

LangChain streams complete messages, not token-by-token deltas — so the user sees the reply appear all at once, not as a typing animation. (This is a LangChain limitation, not the adapter’s.) Choose Mastra if streaming-per-token UX matters.

The adapter also handles two LangChain content shapes:

  • OpenAI-stylecontent: "string" directly.
  • Anthropic-stylecontent: [{"type": "text", "text": "..."}, ...] content blocks.

Both shapes are flattened to text before calling on_chunk().

Memory

LangChain doesn’t have built-in conversation memory. To persist context across turns, pass options.conversation_id into your own store (Redis, a database, a LangGraph checkpointer, etc.) inside your tool or executor wiring.

Voice (STT)

Pass an optional voice provider to enable audio input:

1from astropods_adapter_langchain import LangChainAdapter, OpenAIVoice, serve
2
3adapter = LangChainAdapter(
4 agent,
5 name="My Agent",
6 voice=OpenAIVoice(), # uses OPENAI_API_KEY
7)
8serve(adapter)

OpenAIVoice is bundled for convenience — it implements the VoiceProvider protocol against OpenAI’s Whisper API. You can pass any object with an async listen(data: bytes, config) -> str method.

The voice flow:

LangChain has no native TTS path, so the adapter does STT only. If you need TTS, post-process on_chunk() text and call hooks.on_audio_chunk() yourself (or use the Mastra adapter, which has TTS built in).

Tracing

When OTEL_EXPORTER_OTLP_ENDPOINT is set, serve() automatically instruments LangChain so every LLM call and tool invocation produces a trace span. No code changes needed — Astro sets the env var on deployed agents.

Example: an agent with tools

1from langchain_core.tools import tool
2from langchain_anthropic import ChatAnthropic
3from langchain.agents import create_agent
4from astropods_adapter_langchain import LangChainAdapter, serve
5
6@tool
7def customer_lookup(customer_id: str) -> dict:
8 """Look up a customer by ID."""
9 import requests
10 return requests.get(f"https://api.example.com/customers/{customer_id}").json()
11
12llm = ChatAnthropic(model="claude-sonnet-4-6")
13system_prompt = "Help the user troubleshoot. Use customer_lookup when you need account details."
14
15agent = create_agent(llm, tools=[customer_lookup], system_prompt=system_prompt)
16
17adapter = LangChainAdapter(
18 agent,
19 name="Support Agent",
20 system_prompt=system_prompt,
21 tools=[customer_lookup], # Populates the playground tool list
22)
23serve(adapter)

The tools= argument on LangChainAdapter is for playground display only — the actual tool wiring happens when you pass tools into create_agent. Pass the same list to both so the playground reflects what the agent can actually do.

Local development

$ast dev project start

ast dev runs the messaging sidecar on localhost:9090, sets GRPC_SERVER_ADDR, and opens the bundled playground at http://localhost:8080. The same serve(adapter) code works locally and in production.

Troubleshooting

SymptomLikely cause
Response appears all at once, not streamedLangChain’s astream returns complete messages per node. Use Mastra for token-by-token streaming.
Could not transcribe audioSTT returned an empty string. Check the audio encoding matches what your VoiceProvider expects.
Tools show up in code but not in the playgroundYou passed tools into create_agent but not into LangChainAdapter(..., tools=...).
No traces in the OTEL backendOTEL_EXPORTER_OTLP_ENDPOINT not set, or the opentelemetry-instrumentation-langchain package isn’t installed.