Skip to main content
Charter is not an agent framework and does not ship an agent loop. It is the layer your framework calls through, so an adapter is a projection of a Tool: it hands the framework an argument schema and a coroutine, and execution stays in Charter. That means tools stay built once. The credential lookup happens below every adapter, so serving many users works unchanged whichever one you use — or none.

OpenAI

openai_tools.py
Plain function definitions — the same JSON Schema Tool.to_json_schema produces — so this needs no extra. The same output works anywhere the OpenAI tool format is accepted.

LangChain

langchain_tools.py
Returns a StructuredTool per tool, whose args_schema is the Charter LLM view (Tool.llm_schema) and whose coroutine is Tool.ainvoke. LangChain supplies the calling convention; it does not touch serialization, casing, or field policy. Validation failures surface the way LangChain expects, so a model that sends a malformed argument gets a message it can act on rather than a stack trace. See tool validation and error handling.
Do not let a client flatten the schema on the way out. Charter emits $ref/$defs, and support for references in tool schemas is uneven, so some code resolves them before sending. Resolving is multiplicative, and against a cyclic schema — Linear’s GraphQL filters, for instance — there is no size it converges to. langchain_core 1.6.1 does this in convert_to_openai_function, which calls dereference_refs and discards $defs; on linear.teams_list it had not returned after 45 seconds and had allocated 1.92 GB.to_langchain_tools does not do this. If something downstream of you does, the fix is to remove the cycle with a projection rather than to bound the expansion.
This one needs the extra:
The import is deferred, so a missing extra is a clear error at the moment you build the tools rather than a dependency everyone carries:

Sending schemas on demand

A ToolSession sends the schemas of the tools the model has reached for, plus a ToolSearch that loads the rest, so Gmail and Stripe together cost 563 tokens on the first turn instead of 15,661. Its tool list grows, which means the loop has to rebuild that list every turn. Charter ships the loop rather than asking you to remember:
Both run every Charter tool through Tool.ainvoke exactly as a statically bound tool would be. A loaded tool is the same fully typed tool it would have been. to_openai_tools and to_langchain_tools both accept a session too, if you own your loop and want to drive the projection yourself. See run and CharterMiddleware.
Serving over the Model Context Protocol is a compile target rather than an adapter, and has its own page.