to_gemini
Python · package toolnexus · SPEC §4 · python/src/toolnexus/adapters.py
def to_gemini(tools: list[Tool]) -> list[dict[str, Any]]Turns a list[Tool] into the tools array a Gemini generateContent call expects. Gemini nests
differently from the other two providers: one entry containing a functionDeclarations list,
not one entry per tool. The return type is still a list because that is the shape of Gemini’s
tools field.
When to use it
Section titled “When to use it”When you are driving Gemini yourself — google-genai, the Vertex AI endpoint, or raw HTTP against
v1beta/models/…:generateContent — and need the tools value for the request body.
Why this and not the alternative
Section titled “Why this and not the alternative”tk.to_gemini() on a Toolkit is this same function applied to that
toolkit’s tools — the method when you have a toolkit, the free function when you have a bare list.
Examples
Section titled “Examples”1. One tool to Gemini schema
Section titled “1. One tool to Gemini schema”The extra layer is the whole story: [{ "functionDeclarations": [...] }].
from toolnexus import to_gemini, 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_gemini([weather])
# ONE entry, whatever the number of tools.assert len(schema) == 1assert list(schema[0].keys()) == ["functionDeclarations"]
decls = schema[0]["functionDeclarations"]assert len(decls) == 1assert decls[0]["name"] == "get_weather"assert decls[0]["description"] == "Current weather for a city"# `input_schema` is renamed to `parameters`, as in the OpenAI adapter.assert decls[0]["parameters"]["properties"]["city"] == {"type": "string"}
print("ok:", decls[0]["name"])2. Many tools, still one entry — into a request body
Section titled “2. Many tools, still one entry — into a request body”import jsonfrom toolnexus import to_gemini, 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 = { "contents": [{"role": "user", "parts": [{"text": "search for adapters"}]}], "tools": to_gemini(tools),}
# Two tools, still a single `tools` entry — they collapse into functionDeclarations.assert len(body["tools"]) == 1decls = body["tools"][0]["functionDeclarations"]assert [d["name"] for d in decls] == ["search", "ping"]# Plain JSON — serializes with no custom encoder.assert len(json.dumps(body)) > 0
print("ok:", ", ".join(d["name"] for d in decls))3. Round-tripping a functionCall back to the tool
Section titled “3. Round-tripping a functionCall back to the tool”Gemini returns functionCall.args as a real object (like Anthropic, unlike OpenAI), and expects
a functionResponse part back, keyed by tool name.
import asynciofrom toolnexus import to_gemini, 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_gemini(tools)
# What a model would send back for that schema.part = {"functionCall": {"name": "get_weather", "args": {"city": "Chennai"}}}
async def main(): call = part["functionCall"] called = next(t for t in tools if t.name == call["name"]) res = await called.execute(call["args"]) assert res.is_error is False
# What you send back on the next turn — matched by name, not by an id. response_part = { "functionResponse": {"name": call["name"], "response": {"output": res.output}} } assert response_part["functionResponse"]["response"]["output"] == "sunny in Chennai"
# An empty tool list still produces the wrapper, with no declarations inside. assert to_gemini([]) == [{"functionDeclarations": []}]
print("ok:", schema[0]["functionDeclarations"][0]["name"], "->", res.output)
asyncio.run(main())| Path | From | Notes |
|---|---|---|
[0] |
— | Always exactly one entry, whatever the tool count. |
[0]["functionDeclarations"] |
the tool list | One declaration per tool, in order. |
…[]["name"] |
Tool.name |
What comes back in functionCall.name. |
…[]["description"] |
Tool.description |
|
…[]["parameters"] |
Tool.input_schema |
Renamed — input_schema → parameters. |
See also
Section titled “See also”to_openai·to_anthropiccreate_toolkit—tk.to_gemini()is this, applied to the toolkitcreate_client— calls the adapter for you