Skip to content

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 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.

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.

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) == 1
assert list(schema[0].keys()) == ["functionDeclarations"]
decls = schema[0]["functionDeclarations"]
assert len(decls) == 1
assert 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 json
from 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"]) == 1
decls = 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 asyncio
from 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_schemaparameters.