RestApiTool: integer / boolean / array header and cookie parameters crash the agent run (httpx: Header value must be str or bytes)
@llalitkumarrr đang làm issue này rồi.
Từ ngày 18/9/2026.
Đánh giá
Issue này chưa được đánh giá.
Mô tả
🔴 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:
pip install google-adk(reproduced onmain@ f33d4923 / 2.9.0).- Run the script below: a real
RunnerplusLlmAgentplusOpenAPIToolsetwhose spec has an integer and a boolean header parameter. The model is mocked and HTTP goes to anhttpx.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.
- Ngôn ngữ chính
- Python
- Star
- 21.6k
- Fork
- 4k
- Merge trung bình
- 13 giờ 49 phút
- Pull request đã merge (30 ngày)
- 10
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của google/adk-python
-
mcp
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
google/adk-python#7217 · 3 bình luận · 1 người được giao ·
-
tools
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
google/adk-python#7206 · 1 bình luận · 1 người được giao ·
-
request clarification tools
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 86/100
google/adk-python#7205 · 2 bình luận · 1 người được giao ·
-
mcp
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
google/adk-python#7196 · 1 bình luận · 1 người được giao ·
-
eval request clarification
Độ khó 1/5 1-3 giờ Mức phù hợp với người mới 86/100
google/adk-python#7146 · 2 bình luận · 1 người được giao ·
Tất cả issue của google/adk-python
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
anthropics/skills#1811 · 1 bình luận ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
speaches-ai/speaches#678 ·
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
datalayer/mcp-compose#42 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
conda-forge/spacy-feedstock#177 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
UKGovernmentBEIS/inspect_evals#2523 ·