You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
OpenAIResponsesLlm drops the keys beside "result" in a tool's dict return #7836
Describe the Bug: OpenAIResponsesLlm drops every key except result from a tool's dict return. _serialize_json_value (integrations/openai/_openai_responses_llm.py) unwraps any mapping that has a non-None result key and sends only that value as the function_call_output. ADK wraps a non-dict tool return as exactly {"result": value}, but a tool's own dict can also have a result key next to other data. For example, {"result": "found 2 contracts", "contracts": [...], "next_page_token": "abc"} reaches the model as just found 2 contracts.
AnthropicLlm had the same bug, fixed in #7073: it now unwraps only when the keys are exactly {"result"}.
Steps to Reproduce:
Give an LlmAgent using OpenAIResponsesLlm(model="gpt-5") a function tool that returns {"result": "found 2 contracts", "contracts": ["NDA-12", "MSA-7"], "next_page_token": "abc"}.
Let the model call it and inspect the function_call_output item in the next responses.create input.
Expected Behavior:
The whole dict is sent as JSON. Only the bare {"result": value} wrapper is unwrapped.
Observed Behavior:
function_call_output sent to OpenAI: found 2 contracts
Environment Details:
ADK Library Version (pip show google-adk): 2.13.0 (also on main at 75fb788)
Desktop OS: macOS
Python Version (python -V): 3.11.16
Model Information:
Are you using LiteLLM: No
Which model is being used: gpt-5 via OpenAIResponsesLlm
@adityasingh2400 Reproduced this on 2.13.0 and main: only “found 2 contracts” reaches the model while contracts/next_page_token get dropped.
A workaround that worked locally: unwrap only when value.keys() == {'result'} in _serialize_json_value (the same rule AnthropicLlm got in #7073) so any other dict goes out as full JSON. Could you give that a try on your side and check that the bare {"result": x} returns and the MCP content outputs still behave the same?
Once you’ve confirmed it holds up #7837 looks like the right place for it. Will flag it for review. #7847 looks like it covers the same change so it’s worth settling on one of the two.
Thanks for reproducing it. #7837 already uses exactly that rule (value.keys() == {'result'}), and I re-checked both cases you mentioned on the PR branch:
A bare wrapper is still unwrapped. test_function_response_unwraps_adk_result_wrapper checks {"result": "sunny"} → sunny and {"result": [1, 2]} → [1, 2].
MCP content outputs are unchanged. The content branch returns before the result check is reached, and the existing test_function_response_serializes_mcp_content_as_text passes unmodified.
tests/unittests/integrations/openai passes (227 tests). The PR also has an end-to-end run with a real Runner showing the full dict reaching function_call_output.
On the duplicate: #7837 was opened with this issue, about three hours before #7847 (19:05 vs. 22:09 UTC), so I'd suggest keeping #7837. Happy to go with whichever you prefer.
Activity
surajksharma07 commentedon Oct 30, 2026
@adityasingh2400 Reproduced this on 2.13.0 and main: only “found 2 contracts” reaches the model while contracts/next_page_token get dropped.
A workaround that worked locally: unwrap only when value.keys() == {'result'} in _serialize_json_value (the same rule AnthropicLlm got in #7073) so any other dict goes out as full JSON. Could you give that a try on your side and check that the bare {"result": x} returns and the MCP content outputs still behave the same?
Once you’ve confirmed it holds up #7837 looks like the right place for it. Will flag it for review. #7847 looks like it covers the same change so it’s worth settling on one of the two.
adityasingh2400 commentedon Oct 31, 2026
Thanks for reproducing it. #7837 already uses exactly that rule (
value.keys() == {'result'}), and I re-checked both cases you mentioned on the PR branch:test_function_response_unwraps_adk_result_wrapperchecks{"result": "sunny"}→sunnyand{"result": [1, 2]}→[1, 2].contentbranch returns before theresultcheck is reached, and the existingtest_function_response_serializes_mcp_content_as_textpasses unmodified.tests/unittests/integrations/openaipasses (227 tests). The PR also has an end-to-end run with a realRunnershowing the full dict reachingfunction_call_output.On the duplicate: #7837 was opened with this issue, about three hours before #7847 (19:05 vs. 22:09 UTC), so I'd suggest keeping #7837. Happy to go with whichever you prefer.