agent-adapter migrate¶
migrate reads your Python files and lists every framework tool definition it finds. With
--diff it also prints a suggested patch that moves those tools onto a Registry.
It is read-only. It parses each file with Python's ast module: it never imports, runs or
writes your code. You review the patch and apply it yourself.
flowchart LR
P["PATH: a file or a folder"] --> W["walk *.py<br/>(skips hidden, venv, build folders)"]
W --> A["ast.parse, per file"]
A -->|syntax error| S["skipped, with the line"]
A --> F["findings: framework, line, tool, action"]
F --> R["report on stdout"]
F -->|--diff| D["suggested unified diff on stdout"]
Usage¶
agent-adapter migrate src/ # report only
agent-adapter migrate src/ --diff > migrate.patch # report, then the patch
agent-adapter migrate app.py --framework langchain # one framework only
| Argument | Meaning |
|---|---|
PATH |
one .py file, or a folder scanned recursively |
--framework |
auto (default: every framework), or one of langchain, crewai, openai-agents, adk, pydantic-ai, llamaindex, smolagents, agno, strands, openai, anthropic |
--diff |
print the suggested unified diff after the report |
The exit code is 0, even when files are skipped. It is 2 when PATH does not exist.
What it finds¶
| Framework | Finds | Suggests |
|---|---|---|
| LangChain | @tool, StructuredTool.from_function(f), BaseTool subclasses |
@registry.tool, registry.wrap(f) |
| CrewAI | @tool("name"), BaseTool subclasses |
@registry.tool(name=...) |
| OpenAI Agents SDK | @function_tool, name_override= |
@registry.tool(name=...) |
| Google ADK | FunctionTool(func=f), plain functions in Agent(tools=[...]) |
registry.wrap(f), @registry.tool |
| Pydantic AI | @agent.tool, @agent.tool_plain, Tool(f) |
@registry.tool; RunContext becomes ToolContext |
| LlamaIndex | FunctionTool.from_defaults(fn=f) |
registry.wrap(f, name=...) |
| smolagents | @tool, Tool subclasses |
@registry.tool |
| Agno, Strands | @tool, plain functions in Agent(tools=[...]) |
@registry.tool |
| raw OpenAI or Anthropic | tool schema dicts ("type": "function", "input_schema") |
reported only |
Names are matched by where they are imported from, so from langchain_core.tools import
tool as lc_tool is found too. A function named tool that you wrote yourself is not.
The report¶
agent-adapter migrate: read-only, no file was changed
app.py:10: langchain @tool `ticket_status`: use @registry.tool
app.py:22: langchain @tool `escalate`: manual: its options have no direct match
tools.py:6: crewai BaseTool subclass `opening_hours`: manual: move its run method into a @registry.tool function
legacy/broken.py: skipped, syntax error at line 5
3 tool definition(s) in 2 file(s); 2 need a manual step
langchain: give it structured_tools(registry, executor=executor) from agent_adapter.integrations.langchain
The last lines name the bridge for each framework found. Pass its result where the framework takes its tools, so every call goes through the executor.
The diff¶
For each file with an automatic finding, the patch:
- swaps the framework decorator for
@registry.tool, keeping a stringname=ordescription=; - turns wrapper calls into
registry.wrap(f); - adds
@registry.toolto plain functions passed intools=[...]; - drops a framework import name once nothing else uses it;
- adds, after the imports,
registry = Registry(), oneExecutorand the bridge import.
-from langchain_core.tools import tool
+from agent_adapter import Executor, Registry
+from agent_adapter.integrations.langchain import structured_tools
+
+registry = Registry()
+executor = Executor(registry) # add approval=, hooks= and limits= here
-@tool("refund_ticket")
+@registry.tool(name="refund_ticket")
def refund(ticket_id: str, amount: float) -> str:
Apply it with git apply migrate.patch (or patch -p1), then run your tests.
Left for you¶
These stay as they are and are marked manual in the report:
- decorator or wrapper options with no direct match, such as
return_direct=Trueor ADK'srequire_confirmation=True; useconfirm=and an approval port instead; - a name that is not a valid tool name (
"Ticket price"); BaseToolsubclasses: move the body of_runinto a function;- raw schema dicts: write the function, then render the schema with
chat_tools(registry)oranthropic_tools(registry).
Files are skipped, never fatal, when they are symlinks, over 2 MB, not decodable, or not valid Python.
Files¶
| File | Does |
|---|---|
agent_adapter/cli/migrate.py |
the command: walks PATH, reads each file, prints the report and diff |
agent_adapter/cli/_migrate_rules.py |
which import paths count as framework tools, and each framework's bridge |
agent_adapter/cli/_migrate_scan.py |
finds tool definitions in one parsed module and plans the edits |
agent_adapter/cli/_migrate_diff.py |
applies the edits to a copy of the text and renders the diff |
The step-by-step guides: LangChain tools and raw tool calling.