Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Python: [Bug]: Gemini chat client can't call the harness tool set #6954

Description

@antsok

Description

Selecting a Google model and starting a run of a harness agent (create_harness_agent) over GeminiChatClient crashes the stream during request preparation — before or at the first model call — for two independent, Gemini-specific reasons. The same agent and tool set run unchanged on Ollama, the OpenAI-compatible clients, and Azure Foundry.

  1. Tool parameter schemas. _prepare_tools builds each types.FunctionDeclaration with parameters=tool.parameters(). That field is a google.genai types.Schema (an OpenAPI-3.0 subset) that rejects the JSON-Schema keywords the harness's own tools emit$ref/$defs (the built-in todos_add(todos: list[_TodoAddItemSchema]) tool) and oneOf (any tool with an optional/union object argument). Pydantic raises ValidationError at declaration-build time, so no request is ever sent.

  2. Built-in web search mixed with function calling. The harness adds a web-search tool by default (create_harness_agent(disable_web_search=False)); for a client that supports it, that becomes client.get_web_search_tool(). On Gemini that is a native types.Tool(google_search=...) built-in. Gemini rejects a request that mixes a built-in tool with function declarations unless tool_config.include_server_side_tool_invocations is enabled — which the client never sets.

Either defect alone is fatal; 1 fires first, so 2 is only reachable after 1 is fixed. Both are Gemini-only: Ollama doesn't implement SupportsWebSearchTool, and the OpenAI-compatible/Foundry clients accept $ref/$defs/oneOf schemas.

Verified against: upstream main @ 0260ea0e (2026-07-03); installed agent-framework-gemini 1.0.0a260630, agent-framework-core 1.10.0, google-genai 1.75.0, pydantic 2.13.4
Provider under test: Gemini Developer API (Google AI Studio), api_key auth, model gemini-3.5-flash (also reproduces on gemini-3.1-flash-lite; independent of model)

2. Symptoms

User-visible (any AG-UI frontend; observed with CopilotKit): the user picks a Google model, sends a message, and the reply is a generic "An internal error has occurred while streaming events." No tokens ever stream.

Backend traceback (defect #1), captured via OpenTelemetry logs:

File ".../agent_framework_gemini/_chat_client.py", line 915, in _prepare_tools
    types.FunctionDeclaration(
pydantic_core._pydantic_core.ValidationError: 2 validation errors for FunctionDeclaration
parameters.properties.todos.items.$ref
  Extra inputs are not permitted [type=extra_forbidden, input_value='#/$defs/_TodoAddItemSchema', ...]
parameters.$defs
  Extra inputs are not permitted [type=extra_forbidden, input_value={'_TodoAddItemSchema': {...}}, ...]

A tool with an optional object argument fails the same way on parameters.properties.<arg>.oneOf.

Backend traceback (defect 2, once 1 is fixed):

File ".../agent_framework_gemini/_chat_client.py", line 541, in _stream
    async for chunk in await self._genai_client.aio.models.generate_content_stream(...)
google.genai.errors.ClientError: 400 Bad Request. {
  "error": { "code": 400,
    "message": "Please enable tool_config.include_server_side_tool_invocations to use Built-in tools with Function calling.",
    "status": "INVALID_ARGUMENT" } }

3. Reproduction

Both are reproducible in isolation against the live Gemini Developer API — no framework harness required.

3.1 Defect 1 — tool schema ($ref/$defs, oneOf)

from typing import TypedDict
from typing_extensions import NotRequired
from agent_framework import tool
from google.genai import types

class _TodoAddItemSchema(TypedDict):   # must be module-scope for the forward ref
    title: str
    description: NotRequired[str]

@tool(name="todos_add")
async def todos_add(todos: list[_TodoAddItemSchema]) -> str:
    "Add todos."
    return "ok"

schema = todos_add.parameters()        # -> {"$defs": {...}, "properties": {"todos": {"items": {"$ref": "#/$defs/_TodoAddItemSchema"}, ...}}, ...}
types.FunctionDeclaration(name="todos_add", description="x", parameters=schema)   # ValidationError: $ref / $defs "Extra inputs are not permitted"

# oneOf variant (optional free-form object arg) fails the same way:
oneof = {"type": "object", "properties": {"args": {"oneOf": [{"type": "object"}, {"type": "null"}]}}}
types.FunctionDeclaration(name="t", description="x", parameters=oneof)            # ValidationError: parameters.properties.args.oneOf

This is exactly what create_harness_agent triggers: its TodoProvider.todos_add tool produces the $ref/$defs schema above.

3.2 Defect 2 — built-in tool + function calling

import asyncio
from google import genai
from google.genai import types

client = genai.Client(api_key="<GEMINI_API_KEY>")   # Developer API / Google AI Studio
fn = types.Tool(function_declarations=[types.FunctionDeclaration(
    name="get_weather", description="Get weather",
    parameters_json_schema={"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]})])
search = client and types.Tool(google_search=types.GoogleSearch())   # what the harness auto-adds on Gemini
auto = types.FunctionCallingConfig(mode="AUTO")

async def go(tool_config):
    stream = await client.aio.models.generate_content_stream(
        model="gemini-3.5-flash", contents="What's the weather in Paris?",
        config=types.GenerateContentConfig(tools=[search, fn], tool_config=tool_config))
    async for _ in stream: pass

asyncio.run(go(types.ToolConfig(function_calling_config=auto)))                                       # 400 INVALID_ARGUMENT (as above)
asyncio.run(go(types.ToolConfig(function_calling_config=auto, include_server_side_tool_invocations=True)))  # OK

End-to-end: run create_harness_agent(..., disable_web_search=False) (the default) over GeminiChatClient with any function tools and send any prompt.

Code Sample

Error Messages / Stack Traces

Package Versions

agent-framework-gemini: 1.0.0a260630, agent-framework-core: 1.10.0

Python Version

Python 3.12

Additional Context

4. Root cause

4.1 Defect 1 — _prepare_tools passes raw JSON Schema to the OpenAPI-subset parameters

RawGeminiChatClient._prepare_tools (installed build _chat_client.py:883) builds declarations directly from the tool's JSON schema:

types.FunctionDeclaration(
    name=tool.name,
    description=tool.description or "",
    parameters=tool.parameters(),   # line ~915: raw JSON Schema -> types.Schema
)

types.FunctionDeclaration.parameters is typed types.Schema, the OpenAPI-3.0 subset, which disallows $ref, $defs, definitions, and oneOf (only anyOf is modelled, and unions must be inlined). Pydantic rejects the extra keys with extra_forbidden. Tools whose parameters reference a nested model (Pydantic/TypedDict → $ref + $defs) or carry a union (oneOf) therefore can never be declared. This is a well-known Gemini-API constraint that peer SDKs hit and solve by flattening or by using JSON-Schema passthrough (see §6).

flowchart LR
    A["tool.parameters()<br/>JSON Schema w/ $ref/$defs or oneOf"] --> B["types.FunctionDeclaration(parameters=...)"]
    B --> C{"types.Schema<br/>(OpenAPI-3.0 subset)"}
    C -->|"$ref / $defs / oneOf"| D["pydantic ValidationError<br/>extra_forbidden"]
    style D fill:#fdd,stroke:#c00
Loading

4.2 Defect 2 — the harness adds a built-in tool the client never flags for

create_harness_agent (core _harness/_agent.py, disable_web_search: bool = False by default) appends a web-search tool when the client supports it:

if not disable_web_search:
    if isinstance(client, SupportsWebSearchTool):
        assembled_tools.append(client.get_web_search_tool())   # Gemini -> types.Tool(google_search=...)
    else:
        logger.warning("Web search tool not available: client %r does not implement SupportsWebSearchTool. ...")

GeminiChatClient implements SupportsWebSearchTool, so a native google_search built-in is added and passed through _prepare_tools unchanged, alongside the function declarations. On the Gemini Developer API, mixing a built-in tool with function declarations requires tool_config.include_server_side_tool_invocations=True; the client's _prepare_config/_prepare_tool_config never set it, so the API returns 400. (Ollama's client is not SupportsWebSearchTool, so no built-in is added — hence the crash is Gemini-only.)

Platform nuance (confirmed by peer reports, §6): Vertex AI allows the combination without the flag and rejects the field as unknown, so the flag must be set only on the Developer-API path (the client already tracks this as self._vertexai).

5. Proposed upstream fix

Both fixes live in agent-framework-gemini; neither requires changes to the harness or other providers.

  1. Declare tools with parameters_json_schema instead of parameters. google-genai (≥ the pinned 1.75.0) exposes FunctionDeclaration.parameters_json_schema, forwarded to the API as parametersJsonSchema, which the model interprets as full JSON Schema ($ref/$defs, oneOf/anyOf, nullable unions). This offloads the whole compatibility surface to the API rather than lossily transcribing into types.Schema by hand. Exactly one of parameters / parameters_json_schema may be set. (Google's own guidance for complex schemas is to use parametersJsonSchema.) A stricter alternative — flatten $ref/$defs and map oneOfanyOf into types.Schema — is more code and still lossy for constructs the subset can't express.

  2. Enable include_server_side_tool_invocations when a built-in tool is present. In _prepare_config (after building tools), when any entry is a native types.Tool and the client is not in Vertex mode (self._vertexai), set tool_config.include_server_side_tool_invocations = True (creating the ToolConfig if absent). This is Google's prescribed fix and preserves web-search grounding (a documented, intended capability — the framework's own surfaces render web_search/search_tool_result content).

Optional hardening: _prepare_tools could dedupe identical native built-ins (the harness path can present two google_search tools); the API tolerates the duplicate with the flag on, so this is cosmetic.

6. Prior art / duplicate check (2026-07-07)

No existing microsoft/agent-framework issue reports either defect. Adjacent MAF issues found (different failure modes): #3434 (Python, Gemini streaming text lost with usage — closed), #3390 (.NET, Gemini image upload 400), #5490 (.NET tool calling), #6467 (.NET structured-output vs tool calls).

The underlying Gemini-API behaviors are corroborated across the ecosystem, which both confirms the diagnosis and points at the accepted fixes:

Ref Relevance
google-gemini/gemini-cli#13326, #13142 Gemini API rejects MCP tool schemas using $defs/$ref; fix is schema flattening or JSON-Schema passthrough — same as defect 1
google/adk-python#3995 Google's own ADK mis-parses $defs/$ref — the constraint is API-side, not framework-specific
pydantic/pydantic-ai#1598 Recursive $refs unsupported by Gemini — motivates the cycle fallback in any flatten-based fix
vercel/ai#14369 Gemini rejects $ref/$defs (400 INVALID_ARGUMENT) — same error class as defect 1
vercel/ai#13911 Combining provider-defined (built-in) tools with function tools on Gemini 3: Google AI Studio needs include_server_side_tool_invocations=true; Vertex AI works without it and errors if set — directly validates defect 2's fix and the Vertex gating

7. Verification

  • Defect 1: the §3.1 repro raises ValidationError on parameters=; the identical schema on parameters_json_schema= builds cleanly and is accepted by the live API (generate_content_stream streams) for both the $ref/$defs and oneOf shapes.
  • Defect 2: the §3.2 repro returns the 400 without the flag and succeeds with it, against gemini-3.5-flash on the Developer API (also with a duplicated google_search tool).
  • End-to-end: with the companion app-side shim applied (both fixes), a harness agent on gemini-3.5-flash over AG-UI/CopilotKit completed a full turn — todos_add and other tools executed, an artifact was saved, and the plan streamed — with zero streaming errors and clean telemetry (0 ValidationError, 0 400s). Full app test suite: 128 passed, including 10 new tests covering both crash shapes and both fixes.

8. Suggested issue framing

  • Primary issue: "Gemini chat client can't declare tools whose parameters use $ref/$defs/oneOf (built-in todos_add crashes every harness agent)" — defect 1; recommend parameters_json_schema.
  • Separable second issue: "Gemini rejects harness web-search built-in mixed with function calls (missing include_server_side_tool_invocations)" — defect 2; note the Developer-API-vs-Vertex gating.

Metadata

Metadata

Labels

agentsUsage: [Issues, PRs], Target: Single agentpythonUsage: [Issues, PRs], Target: PythonreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow

Type

Fields

No fields configured for Bug.

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions