langchaint
Provider-neutral async LLM client over the official anthropic and openai SDKs. Alpha: the API is unstable and may change without notice.
The point
langchaint is the layer between an application's own agent loop and the provider SDKs.
Across providers it gives one message tree, one error taxonomy, one priced Usage, and one RateLimiter that owns retrying and pacing.
The application keeps the agent loop and states every billing-relevant choice itself, starting with prompt caching; langchaint defaults none of them.
Install
Requires Python >= 3.13.
The hard dependencies are pydantic and jsonschema; the provider SDKs are optional dependencies the application pins directly, and langchaint declares no extras.
pip install langchaint openai # or anthropic, or both
import langchaint needs neither SDK; importing langchaint.openai without the openai package raises a ModuleNotFoundError naming the package to install.
Example
import asyncio
from pydantic import BaseModel
from langchaint.openai import openai_model
class Sentiment(BaseModel):
label: str
confidence: float
async def main() -> None:
llm = openai_model("gpt-5.6-terra")
classifier = llm.bind(
system_prompt="Classify the sentiment of the user's message.",
response_format=Sentiment,
automatic_prompt_caching=False,
)
response = await classifier.generate_one("This is the best day I have had in months.")
print(response.output.label, response.usage.cost_in_usd)
asyncio.run(main())
bind(response_format=Sentiment) returns a BoundLLM[Sentiment], so response.output is a validated Sentiment instance; without response_format, output is the assistant text.
A bare str argument is shorthand for a conversation of one UserMessage holding that text.
examples/ holds runnable files and MIGRATING_FROM_LANGCHAIN.md, the LangChain call-for-call map.
What it has
Generation only via binding.
LLM.bind(...) freezes everything that determines the cacheable prompt prefix into a BoundLLM[OutputT], and changing parameters is rebind(...).
BoundLLM has generate_one, generate_many, and stream_one.
A constructor per backend returning a ready LLM.
openai_model(...), anthropic_model(...), anthropic_bedrock_model(...), and openai_bedrock_model(...); models outside a catalog are built directly from the re-exported adapter.
One accounting contract for success and failure.
Success is a Response[OutputT] and a terminal failure is a GenerationError, but both carry usage, the paid total across every attempt, and to_row flattens either to one row shape, so a mixed batch is one table.
Priced usage.
Usage partitions input tokens by cache outcome, counts reasoning output separately, and carries one cost per priced category, priced at the service tier the response reported; cost_in_usd is their sum and the raw SDK usage rides beside it.
One RateLimiter owning retrying and pacing.
One instance shared by several LLMs is one budget for the account they hit, gating every request start; a rate-limit error pauses admission for everyone sharing it.
The SDK clients are configured to never retry beneath langchaint, so attempt counts stay true.
User-stated prompt caching.
automatic_prompt_caching is a required keyword of bind with no default, because caching changes billing.
cache_breakpoint=True on a content part places a prompt-cache boundary at exactly that part; the wire mechanics are in the adapter module docstrings.
Streaming as a handle.
stream_one returns a StreamHandle: an async context manager that iterates str | ToolCall items, with await handle.final() returning the assembled Response.
Tools under one protocol.
PydanticTool, JSONSchemaTool (for tools discovered at run time, such as MCP tools), and CaptureTool (the structured exit for a tool_choice="required" loop) share the Tool protocol, so one ToolManager holds a mix and an application adds its own form by implementing Tool.
ToolManager.dispatch returns an outcome union; every arm carries the tool_message to append, and bad arguments or an unknown tool name become an outcome the model can correct rather than a raise.
Reasoning preserved across turns. Every provider reasoning element is re-sent verbatim on later requests, so tool-use continuations satisfy each provider's replay rules without application code.
OTel tracing as a wrapper.
langchaint.tracing wraps LLM, BoundLLM, StreamHandle, and ToolManager in Traced counterparts; capture_message_content is a required keyword with no default, because recording prompts is a privacy choice.
What it does not have
Deliberate absences, each with its reason recorded in CLAUDE.md or a module docstring:
- No agent class and no agent loop: the loop is ~15 lines of application code, shown in
examples/02_tool_loop.py, and a tool returns data, never a control-flow signal. - No per-call parameter overrides: changing parameters is
rebind. - No default for
automatic_prompt_caching: everybindstates it. - No
requests_per_minute: throughput under themax_in_flightbound follows request duration, while a client-side rate number goes stale with the account tier. - No Converse adapter for Bedrock: the adapters take the SDKs' bundled Bedrock clients.
- No provider-parameter passthrough dict: an unmapped provider parameter is reached by subclassing the concrete adapter.
- No hand-written wire types and no client-side guessing at provider rules: stream assembly and structured-output parsing are the SDK's, and invalid inputs are sent so the provider's own error surfaces.
- No delta, usage, or stop items in a stream: a stream yields
str | ToolCall, andusageandstop_reasonlive onfinal()'sResponse.
A Chat Completions adapter, and with it third-party compatible servers such as vLLM and Ollama, is not built yet; OpenAI support is the Responses API.
Layout
src/langchaint/ the neutral core; imports no SDK
src/langchaint/anthropic/ the anthropic backend
src/langchaint/openai/ the openai backend
src/langchaint/tracing/ the OTel tracing subpackage
examples/ runnable examples and MIGRATING_FROM_LANGCHAIN.md
CLAUDE.md design tenets, naming rules, and the per-module map
Module docstrings are the spec of record for mechanics; CLAUDE.md holds the design rules.
Verification
Run scripts/CI.sh; everything it runs must pass with zero errors.
The tests are offline and need no API keys.
Provider behavior claims are verified by introspection against anthropic 0.120.0 and openai 2.45.0.
Release files for langchaint 0.9.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 | |
|---|---|---|---|
| langchaint-0.9.0.tar.gz | 131.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| langchaint-0.9.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 273.1 kB
Release files / langchaint-0.9.0.tar.gz
| Download URL | langchaint-0.9.0.tar.gz |
|---|---|
| Size | 131.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c67ef7ae6e4e2b0720cee2e988f5d940444d6ecdf729264f99c91bcebf5c3e7a
|
|
BLAKE2b-256 checksum How to use checksums |
b1dcd751fe3d15b1b8cdcdf0f1f9f0455a059e69ae9e5bdbdee1db0942e60aa1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / langchaint-0.9.0-py3-none-any.whl
| Download URL | langchaint-0.9.0-py3-none-any.whl |
|---|---|
| Size | 141.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
dba47d5a3f9eca154310e967e9b7bbda91683114cc081b034c7c9080bdf0e652
|
|
BLAKE2b-256 checksum How to use checksums |
2bc1c3fb1bc94c245c652e6561d92466f18c422071b6d7291b45b3acaa54efa1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|