Skip to content

LangChain

Available · export and import · pip install "agent-adapter[langchain]" (langchain-core)

LangChain is an export target and an import source. Exported tools run through an Executor, so hooks, approval, timeouts, retries, the cache and the ledger apply to every call LangChain (or LangGraph) makes.

Functions

Function (agent_adapter.integrations.langchain) Returns
structured_tool(spec, *, executor=None) one StructuredTool
structured_tools(registry, *, executor=None) every tool exported to langchain (honours targets)
import_langchain(registry, tools, *, prefix="", **policy) the new ToolSpecs, added all or nothing
INTEGRATION the integration object: export_tool, bind_context(tool_call)

executor=None uses the default executor (exec.defaults.default_executor()).

Export

from agent_adapter.integrations.langchain import structured_tools

executor = Executor(registry)  # hooks, approval, cache, ledger apply to every call
tools = structured_tools(registry, executor=executor)  # pass to bind_tools / an agent
call = {"name": "shipping_rate", "args": {"region": "us"}, "id": "call_1", "type": "tool_call"}
message = await tools[0].ainvoke(call)  # a ToolMessage; ctx.call_id == "call_1"
sequenceDiagram
  participant A as LangChain agent
  participant T as StructuredTool
  participant E as Executor
  participant S as ToolSpec
  A->>T: ainvoke(ToolCall: name, args, id)
  T->>E: run_spec(spec, args, ctx(call_id = id))
  E->>S: validate, hooks, approval, call
  S-->>E: value
  E-->>T: value (or ToolException)
  T-->>A: ToolMessage(tool_call_id = id)
What How
Schema the model sees spec.json_schema() as a JSON-schema args_schema (LangChain drops titles)
Validation the spec's own, once (LangChain does not validate a JSON-schema args_schema)
Call id the ToolCall id becomes ctx.call_id; a plain dict input gets a fresh one
Run id current_context() inside an adapter run; else config["configurable"]["run_id"], else ["thread_id"] (LangGraph's, also ctx.thread_id); else a fresh run per call. Pending approvals (DeferredApproval) resume only with a stable run id: pass the same config when you invoke again. With thread_id, run guards such as once_per_run span the thread
Model-visible errors (ToolArgsInvalid, ToolError, ApprovalDenied, GuardrailBlocked, RunLimitExceeded) ToolException → an error ToolMessage (status="error")
Other errors (ToolTimeout, ToolFailed, ApprovalPending, ...) raised from ainvoke
asyncio.CancelledError always propagates
Sync invoke runs the call with run_sync (a worker thread when a loop is running)
confirm=True tools the executor's approval port decides; edited_args are applied

Import

from agent_adapter.integrations.langchain import import_langchain

[spec] = import_langchain(registry, [word_count], prefix="lc_")
count = await executor.run("lc_word_count", {"text": "one two three"})
What How
Accepts any BaseTool: @tool functions, StructuredTool, subclasses
Schema convert_to_openai_function(tool)["parameters"]: what LangChain sends a model
InjectedToolArg parameters left out of the schema and not supplied: give them defaults, or bind them before import. A required one that is missing fails in LangChain's own validation, which the executor reports as ToolFailed
Names prefix + name, characters outside [A-Za-z0-9_] become _ (add-numbers → add_numbers)
Policy kind="langchain", tags {"langchain", *tool.tags}; **policy overrides (confirm=, timeout=, ...)
ToolException ToolError with the same message
Arguments the tool's own argument model rejects ToolArgsInvalid, checked before the call (other exceptions from the tool propagate; the executor turns them into ToolFailed)
Results what tool.ainvoke(args) returns: for content_and_artifact tools only the content (LangChain drops the artifact without a ToolCall)