From LangChain tools¶
Your tools are LangChain @tool functions. Move them into a registry so any framework
can use them, while LangChain agents keep working.
flowchart LR
L["LangChain @tool"] -- "step 1: import_langchain" --> REG[("Registry")]
P["plain function"] -- "step 2: @registry.tool" --> REG
REG -- "step 3: structured_tools" --> LC["LangChain agent"]
REG --> OTHER["OpenAI, MCP, other frameworks"]
LC --> EX["Executor"]
OTHER --> EX
Mapping¶
| LangChain | Agent Adapter |
|---|---|
@tool from langchain_core.tools |
@registry.tool |
args_schema= |
type hints, Annotated[..., Field(...)], or args_model= |
return_direct, callbacks |
events and hooks (LoggingHook, your own Hook) |
RunnableConfig["configurable"]["thread_id"] |
read by exported tools as ctx.thread_id |
HumanInTheLoopMiddleware, interrupts |
confirm= plus an approval port |
tool.invoke(args) |
await executor.run("name", args) |
Step 1: import, no rewrite¶
from langchain_core.tools import tool
from agent_adapter.integrations.langchain import import_langchain
def shout(text: str) -> str:
"""Upper-case a text."""
return text.upper()
legacy_tool = tool(shout) # what `@tool` did in the old module
legacy = Registry()
# policy options (timeout, retries, confirm, ...) apply to imported tools too
import_langchain(legacy, [legacy_tool], prefix="lc_", timeout=5)
return Executor(legacy).run("lc_shout", {"text": "faq"})
Imported tools run through the executor like any other, with the policy you pass.
Step 2: rewrite as plain functions¶
@registry.tool(kind="search", parallel_safe=True, timeout=10) # was: @tool from langchain_core
def word_count(text: str) -> int:
"""Count the words in a text."""
return len(text.split())
executor = Executor(registry) # approval, budgets, cache and ledger live here now
result = await executor.run_call(ToolCall(name="word_count", arguments={"text": "a b c"}))
print(result.value, result.status)
Step 3: give LangChain the registry¶
from agent_adapter.integrations.langchain import structured_tools
tools = structured_tools(registry, executor=executor) # pass to bind_tools / an agent
call = {"name": "word_count", "args": {"text": "one two"}, "id": "call_1", "type": "tool_call"}
return tools[0].ainvoke(call) # a ToolMessage
Output¶
Without langchain-core installed, the example prints 3 ok and skips steps 1 and 3.
Gained and lost¶
| Gained | Lost |
|---|---|
| tools without a LangChain dependency | LangChain-only features on the tool object (return_direct, tool callbacks) |
| the same tools for every other framework and model API | |
| approval with one-use decisions, budgets, caching, ledger |
Checklist¶
- [ ]
pip install "agent-adapter[langchain]"for steps 1 and 3. - [ ] Each tool rewritten keeps its name, so prompts and traces still match.
- [ ] Agents get
structured_tools(registry, executor=executor), not the old list. - [ ] Remove
langchain_coreimports from tool modules once step 2 is done.
Files¶
| File | Does |
|---|---|
examples/guide_migrate_langchain.py |
the import, the rewrite and the export |
tests/docs/test_migration.py |
runs it offline; the LangChain steps run when langchain-core is installed |