RestApiTool: integer / boolean / array header and cookie parameters crash the agent run (httpx: Header value must be str or bytes)

Abierto
#7,164 0 comentarios 0 reacciones 1 asignado Ver en GitHub

@llalitkumarrr ya está trabajando en esto.

Desde el 18/9/2026.

Evaluación

Este issue todavía no se ha evaluado.

Descripción

tools

🔴 Required Information

Describe the Bug:

RestApiTool._prepare_request_params() passes header and cookie parameter values to httpx unchanged:

elif param_location == "header":
  header_params[original_k] = v
elif param_location == "cookie":
  cookie_params[original_k] = v

httpx serializes query values itself (5 becomes "5", False becomes "false"), but it only accepts str/bytes for headers and cookies. So when an OpenAPI spec declares an integer, number, boolean, or array header or cookie parameter, which is common in enterprise APIs (X-Page-Size, X-Api-Version, X-Dry-Run, ...), and the model fills it with a value of that type, the request can't be built. The TypeError isn't turned into a tool error, so the whole agent run fails.

This is separate from #7099, which handles None values for the same parameters.

Steps to Reproduce:

  1. pip install google-adk (reproduced on main @ f33d4923 / 2.9.0).
  2. Run the script below: a real Runner plus LlmAgent plus OpenAPIToolset whose spec has an integer and a boolean header parameter. The model is mocked and HTTP goes to an httpx.MockTransport.

Expected Behavior:
The request is sent with X-Page-Size: 50 and X-Include-Archived: false (OpenAPI's default simple style, and how httpx already encodes query values), and the tool result goes back to the model.

Observed Behavior:

  File ".../httpx/_models.py", line 81, in _normalize_header_value
    raise TypeError(f"Header value must be str or bytes, not {type(value)}")
TypeError: Header value must be str or bytes, not <class 'int'>

The exception propagates out of Runner.run_async(). An integer cookie parameter fails the same way (TypeError: expected string or bytes-like object, got 'int' from http.cookiejar). A boolean header would be sent as True/False even if it got through, rather than true/false.

Environment Details:

  • ADK Library Version (pip show google-adk): 2.9.0 (main @ f33d4923)
  • Desktop OS: Windows 11
  • Python Version (python -V): 3.12.10

Model Information:

  • Are you using LiteLLM: No
  • Which model is being used: N/A (mocked model; the bug is in request building)

🟡 Optional Information

Minimal Reproduction Code:

import asyncio

import httpx
from google.adk.agents.llm_agent import LlmAgent
from google.adk.models.base_llm import BaseLlm
from google.adk.models.llm_response import LlmResponse
from google.adk.runners import InMemoryRunner
from google.adk.tools.openapi_tool.openapi_spec_parser.openapi_toolset import OpenAPIToolset
from google.genai import types

spec = {
    "openapi": "3.0.0",
    "info": {"title": "Invoices", "version": "1"},
    "servers": [{"url": "https://erp.example.com"}],
    "paths": {"/invoices": {"get": {
        "operationId": "listInvoices",
        "parameters": [
            {"name": "X-Page-Size", "in": "header", "required": True, "schema": {"type": "integer"}},
            {"name": "X-Include-Archived", "in": "header", "schema": {"type": "boolean"}},
        ],
        "responses": {"200": {"description": "ok"}},
    }}},
}
sent = {}


def handler(request: httpx.Request):
  sent.update(request.headers)
  return httpx.Response(200, json={"invoices": []})


class FakeModel(BaseLlm):
  model: str = "fake"
  calls: int = 0

  async def generate_content_async(self, llm_request, stream=False):
    self.calls += 1
    part = (types.Part.from_function_call(
                name="list_invoices", args={"x_page_size": 50, "x_include_archived": False})
            if self.calls == 1 else types.Part.from_text(text="done"))
    yield LlmResponse(content=types.Content(role="model", parts=[part]))


agent = LlmAgent(
    name="ap_agent",
    model=FakeModel(),
    tools=[OpenAPIToolset(
        spec_dict=spec,
        httpx_client_factory=lambda: httpx.AsyncClient(transport=httpx.MockTransport(handler)),
    )],
)


async def main():
  runner = InMemoryRunner(agent=agent)
  session = await runner.session_service.create_session(app_name=runner.app_name, user_id="u")
  async for _ in runner.run_async(
      user_id="u", session_id=session.id,
      new_message=types.Content(role="user", parts=[types.Part(text="list invoices")])):
    pass
  print("headers sent:", {k: v for k, v in sent.items() if k.startswith("x-")})


asyncio.run(main())

Suggested fix: serialize header and cookie values before handing them to httpx: numbers as str(v), booleans as true/false, arrays comma-joined. Leave other values alone, so None stays #7099's concern. I have a PR ready with tests.

How often has this issue occurred?:

  • Always (100%): any non-string header or cookie parameter value.
Lenguaje dominante
Python
Estrellas
21.6k
Forks
4k
Merge medio
13 h 49 min
PR fusionados (30 d)
10

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de google/adk-python

Todos los issues de google/adk-python

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.