Skip to content

to_anthropic

Python · package toolnexus · SPEC §4 · python/src/toolnexus/adapters.py

def to_anthropic(tools: list[Tool]) -> list[dict[str, Any]]

Turns a list[Tool] into the tools array the Anthropic Messages API expects. It is the flattest of the three adapters: Anthropic’s tool schema is almost exactly toolnexus’ own Tool, so the mapping copies three fields and renames nothing.

When you are calling POST /v1/messages yourself — with the anthropic SDK, over httpx, or through a Bedrock/Vertex gateway that speaks the Anthropic wire format — and need the value to put in the request’s tools field.

tk.to_anthropic() on a Toolkit is this same function applied to that toolkit’s tools — use the method when you have a toolkit, the free function when you have a bare list (a filtered subset, a hand-built list, one tool).

Note what does not happen: no {"type": "function"} wrapper, and input_schema keeps its name.

from toolnexus import to_anthropic, define_tool
def get_weather(city: str) -> str:
"""Current weather for a city."""
return f"sunny in {city}"
weather = define_tool(get_weather, name="get_weather", description="Current weather for a city")
schema = to_anthropic([weather])
assert len(schema) == 1
# Three keys, flat — nothing nested under "function".
assert sorted(schema[0].keys()) == ["description", "input_schema", "name"]
assert schema[0]["name"] == "get_weather"
assert schema[0]["description"] == "Current weather for a city"
# The key stays `input_schema`, exactly as on Tool — unlike OpenAI's `parameters`.
assert schema[0]["input_schema"]["properties"]["city"] == {"type": "string"}
print("ok:", schema[0]["name"])

2. Feeding it straight into a Messages request body

Section titled “2. Feeding it straight into a Messages request body”

The output is plain dicts, designed to drop into tools verbatim alongside system and messages.

import json
from toolnexus import to_anthropic, define_tool
def search(q: str) -> str:
"""Search the docs."""
return f"results for {q}"
def ping() -> str:
"""Health check."""
return "pong"
tools = [
define_tool(search, name="search", description="Search the docs"),
define_tool(ping, name="ping", description="Health check"),
]
body = {
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "search for adapters"}],
"tools": to_anthropic(tools),
}
# Order is preserved, one entry per tool.
assert [t["name"] for t in body["tools"]] == ["search", "ping"]
# A no-argument tool still carries a valid object schema — Anthropic rejects a bare {}.
assert body["tools"][1]["input_schema"]["type"] == "object"
assert body["tools"][1]["input_schema"]["properties"] == {}
# Plain JSON — serializes with no custom encoder.
assert len(json.dumps(body)) > 0
print("ok:", ", ".join(t["name"] for t in body["tools"]))

3. Round-tripping a tool_use block back to the tool

Section titled “3. Round-tripping a tool_use block back to the tool”

Schema out, tool_use in. Anthropic sends input as a real object, so unlike OpenAI there is nothing to json.loads — hand it to execute as-is.

import asyncio
from toolnexus import to_anthropic, define_tool
def get_weather(city: str) -> str:
"""Current weather for a city."""
return f"sunny in {city}"
tools = [define_tool(get_weather, name="get_weather", description="Current weather for a city")]
schema = to_anthropic(tools)
# What a model would send back for that schema.
block = {
"type": "tool_use",
"id": "toolu_01",
"name": "get_weather",
"input": {"city": "Chennai"},
}
async def main():
called = next(t for t in tools if t.name == block["name"])
# `input` is already a dict — no parsing step.
res = await called.execute(block["input"])
assert res.is_error is False
# What you send back on the next turn.
tool_result = {
"type": "tool_result",
"tool_use_id": block["id"],
"content": res.output,
"is_error": res.is_error,
}
assert tool_result["content"] == "sunny in Chennai"
assert tool_result["is_error"] is False
# An empty tool list is valid — it just means "no tools this turn".
assert to_anthropic([]) == []
print("ok:", schema[0]["name"], "->", tool_result["content"])
asyncio.run(main())
Path From Notes
[]["name"] Tool.name What the model calls back with, in tool_use.name.
[]["description"] Tool.description What the model reads to decide whether to call it.
[]["input_schema"] Tool.input_schema Copied under the same key — no rename.