langchain-claude-agent-sdk
A LangChain BaseChatModel backed by Anthropic's official
claude-agent-sdk.
Lets LangChain and LangGraph code run against the bundled claude CLI so
usage counts against your local Claude authentication — Max/Pro subscription
or API key — instead of per-token API billing.
Project status: alpha (
0.1.0). The core surface is complete — non-streaminginvoke/ainvoke, streamingastream,bind_toolsfor both built-in Claude Code tools and custom tools via in-process MCP, and thelangchain-testsstandard conformance harness — and the package is usable in real LangChain / LangGraph workloads today. SeePHASES.mdfor the full phase tracker andCHANGELOG.mdfor release notes.
Why this package
The LangChain ecosystem implicitly assumes per-token API billing. Modest multi-agent workflows can burn meaningful money against that model. The same workflow run under a Claude Max subscription is effectively free at the same volume — but the LangChain wiring assumes API access.
ChatClaudeAgent is a drop-in chat model that routes calls through the
local claude CLI via claude-agent-sdk. Existing LangChain / LangGraph
code keeps working; the bill changes shape.
Audience. LangChain users who already pay for Claude; researchers building multi-agent designs at non-trivial scale; anyone whose LangChain bill has caught their attention.
Installation
pip install langchain-claude-agent-sdk
# or
uv add langchain-claude-agent-sdk
You must also have the claude CLI installed and authenticated locally. The
Agent SDK invokes it as a subprocess.
# Install the CLI (see https://docs.claude.com/en/docs/claude-code for current instructions)
npm install -g @anthropic-ai/claude-code
# Authenticate (one-time)
claude login
Credentials live in ~/.claude/.credentials.json. This package does not
take an api_key; auth is delegated entirely to the CLI you already use.
Quickstart
from langchain_claude_agent_sdk import ChatClaudeAgent
from langchain_core.messages import SystemMessage, HumanMessage
model = ChatClaudeAgent()
response = model.invoke(
[
SystemMessage("You answer in haiku."),
HumanMessage("What is asyncio for?"),
]
)
print(response.content)
print("tokens:", response.usage_metadata)
print("session:", response.response_metadata["session_id"])
print("cost (USD):", response.response_metadata["total_cost_usd"])
Async works the same:
result = await model.ainvoke([HumanMessage("hello")])
Inside an existing event loop, use ainvoke — the sync invoke will refuse
to run inside a running loop rather than dispatch surprisingly to a
thread pool.
Multi-turn
LangGraph and the standard RunnableWithMessageHistory patterns work
out of the box. Under the hood:
- If the most recent
AIMessagein the history carriesresponse_metadata["session_id"](whichChatClaudeAgentalways populates from the CLI'sResultMessage), the next call setsClaudeAgentOptions.resume=<session_id>and forwards only messages after that turn. The CLI keeps real session state; prior turns are not re-billed. - For arbitrary user-constructed histories without a session id, prior turns
are serialized as a plain-text transcript prepended to the latest
HumanMessage. This is a fallback for compatibility, not the recommended path.
Structured output
ChatClaudeAgent.with_structured_output(schema) returns a Runnable that
parses the model's reply against a Pydantic class, TypedDict, JSON
schema dict, or OpenAI/Anthropic tool-spec dict. It is overridden from
the canonical BaseChatModel default because the SDK has no runtime
knob to force tool selection (tool_choice="any" is unenforceable), so
the default's "bind schema as tool, parse tool_calls" path returns
None against the real CLI. This package renders the schema into the
system prompt and parses the assistant's JSON reply directly.
from pydantic import BaseModel, Field
class Plan(BaseModel):
topic: str = Field(description="Subject of the plan")
steps: list[str] = Field(default_factory=list)
structured = model.with_structured_output(Plan)
plan = await structured.ainvoke("Plan three steps for studying FORTRAN.")
assert isinstance(plan, Plan)
include_raw=True returns the canonical
{"raw": AIMessage, "parsed": Plan | None, "parsing_error": Exception | None}
shape so callers can inspect response_metadata (cost, session_id,
token usage) and recover from parse failures. The method and strict
kwargs are accepted for signature parity with langchain-anthropic /
langchain-openai but route through the same JSON-prompting impl —
there is no function_calling path the SDK can deliver reliably.
How this compares
langchain-claude-agent-sdk (this) |
langchain-claude-code (existing) |
|
|---|---|---|
| Backend | Anthropic's official claude-agent-sdk |
Custom subprocess + JSON parsing |
AIMessage.usage_metadata |
✅ populated, with cache_creation / cache_read buckets |
❌ omitted (LangChain contract violation) |
| Tool calls field | ✅ AIMessage.tool_calls (standard) |
❌ stashed in response_metadata (breaks LangGraph agent loops) |
| Multi-turn | ✅ session resume + transcript fallback | varies |
| Typed exceptions / message types | ✅ from the official SDK | hand-rolled |
| In-process MCP for custom tools | ✅ via the official SDK's create_sdk_mcp_server |
hand-rolled |
langchain-tests standard harness |
✅ unit harness passes; integration harness opt-in | not advertised |
Streaming (astream) |
✅ chunk per content block, usage on final chunk | varies |
with_structured_output |
✅ JSON-prompting override (works around unenforceable tool_choice) |
varies |
If you're already on langchain-claude-code and hitting either of the
correctness deltas above, this package is the migration target.
Configuration
ChatClaudeAgent constructor parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
model |
str | None |
None |
Forwarded to the CLI. None lets the CLI pick. |
append_system_prompt |
bool |
True |
If True, SystemMessage content is appended to Claude Code's tool-enabling preset so tools keep working. If False, it replaces the preset. |
max_turns |
int | None |
None |
Cap on agent turns per call. |
cwd |
str | None |
None |
Working directory for the CLI. |
extra_options |
dict[str, Any] |
{} |
Escape hatch for any ClaudeAgentOptions field not exposed above. |
CLI-only goodies surfaced on AIMessage.response_metadata:
session_id,total_cost_usd,num_turns,duration_ms,duration_api_ms,model_name,stop_reason,thinking(preservedThinkingBlocks), andis_error(only when the run failed).
For static-typed access to these keys, the package exports a
ClaudeAgentResponseMetadata(TypedDict, total=False):
from typing import cast
from langchain_claude_agent_sdk import ChatClaudeAgent, ClaudeAgentResponseMetadata
response = model.invoke([HumanMessage("hi")])
meta = cast(ClaudeAgentResponseMetadata, response.response_metadata)
meta["session_id"] # IDE autocomplete + mypy field-name checking
meta["total_cost_usd"] # typed as float
LangChain's AIMessage.response_metadata is typed as dict[str, Any]
upstream, so the cast is needed to recover the field-level types.
Billing and Terms of Service
This package routes traffic through your local claude CLI auth. That auth
may be a Max/Pro subscription, an API key, or both. Read Anthropic's
Terms of Service before redistributing. The intended audience is
individuals and teams running under their own Claude authentication, not
multi-tenant SaaS resellers.
Economics note (effective 2026-06-15). Per Anthropic's Agent SDK pricing announcement, Agent SDK usage draws from a separate monthly Agent SDK credit pool (Pro $20, Max 5× $100, Max 20× $200) — not unmetered Max conversation windows. Overage falls back to API metering at standard rates. Plan accordingly.
Project documents
CHANGELOG.md— release notes, Keep a Changelog format.CONTRIBUTING.md— dev setup, test/lint conventions, commit style, release process.SECURITY.md— vulnerability reporting and supported versions.CODE_OF_CONDUCT.md— Contributor Covenant 2.1.PHASES.md— phase tracker; the source of truth for what's done and what's next across development sessions.DESIGN.md— historical design exploration that preceded this package. Preserved for context only.
Development
make install # uv sync --extra dev
make check # ruff + mypy + pytest unit tests
make test # unit tests only
make test-integration # tests marked @pytest.mark.integration (hit the real CLI)
For runnable examples (including the LangGraph integration), install
the examples extra:
uv sync --extra dev --extra examples
# then, e.g.
uv run python examples/06_structured_output.py
The full developer workflow — including pre-commit hooks, Conventional
Commits, and how releases are cut — is documented in
CONTRIBUTING.md.
License
MIT. Copyright © 2026 Blake Rhodes.
This project is not affiliated with or endorsed by Anthropic, PBC. "Claude" and "Claude Code" are trademarks of Anthropic.
Metadata
Release files for langchain-claude-agent-sdk 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| langchain_claude_agent_sdk-0.2.0.tar.gz | 64.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| langchain_claude_agent_sdk-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 91.4 kB
Release files / langchain_claude_agent_sdk-0.2.0.tar.gz
| Download URL | langchain_claude_agent_sdk-0.2.0.tar.gz |
|---|---|
| Size | 64.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
564d845764d4e823add8615b9bbb2d1586cbb8aa4635daed9791eeba9f2d2321
|
|
BLAKE2b-256 checksum How to use checksums |
e0cf642e1633503de502b46fc0d9d651c751640684a3c68073ee1942caf5053c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.8
|
Release files / langchain_claude_agent_sdk-0.2.0-py3-none-any.whl
| Download URL | langchain_claude_agent_sdk-0.2.0-py3-none-any.whl |
|---|---|
| Size | 27.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ebc102702e660fcf406a2ca5d4531274aaff82e1139b8e1c2fd0bd5642342e0e
|
|
BLAKE2b-256 checksum How to use checksums |
9319cad275c4586c25af05288be9e5cf6261146fc4f07762aeda4fbfe35015ff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.8
|