Skip to content

OCIGenAILlm silently drops all but the first tool result when multiple tools are called in parallel #6589

Description

@Harshitmishra001

🔴 Required Information

Describe the Bug:
When an agent backed by OCIGenAILlm has two or more tools called in
parallel in a single turn, only the result of the first tool is
forwarded to the OCI model API on the next request. All other tool results
are silently dropped.

The root cause is in _content_to_oci_message() in
src/google/adk/integrations/oci/_oci_genai_llm.py. The function correctly
collects all function_response parts from the Content into a list
called tool_results:

tool_results: list[tuple[str, str]] = []  # (tool_call_id, result_text)

for part in content.parts or []:
    ...
    elif part.function_response:
      result = part.function_response.response or {}
      tool_results.append((
          part.function_response.id or "",
          json.dumps(result) if isinstance(result, dict) else str(result),
      ))

But when it builds the OCI message, it unpacks only tool_results[0] and
immediately returns a single ToolMessage, discarding every subsequent
entry (line 201):

if tool_results:
    call_id, result_text = tool_results[0]   # ← everything after [0] is lost
    return oci_models.ToolMessage(
        role=oci_models.ToolMessage.ROLE_TOOL,
        tool_call_id=call_id,
        content=[oci_models.TextContent(type="TEXT", text=result_text)],
    )

The caller, _build_chat_details(), uses a strict one-Content-to-one-message
comprehension:

messages = [_content_to_oci_message(c) for c in llm_request.contents or []]

Because it always inserts the return value as a single element, it cannot
compensate on its own — it would also need updating to flatten a list.

This is reachable in any production agent that uses parallel tool calls, not
a hypothetical edge case: ADK's own flow layer merges individual tool-response
events into a single Content carrying multiple function_response parts
via merge_parallel_function_response_events() in
src/google/adk/flows/llm_flows/functions.py (lines 1406–1458), which runs on
both the standard and live-streaming code paths (lines 524 and 773 of the
same file).

Steps to Reproduce:

  1. Install google-adk with the OCI extra (pip install google-adk[oci]).
  2. Construct a Content with two function_response parts, simulating the
    output of merge_parallel_function_response_events after two tools have
    been called in parallel:
from google.genai import types
from google.adk.integrations.oci._oci_genai_llm import _content_to_oci_message

part_a = types.Part.from_function_response(
    name="get_weather", response={"temp": "22°C"}
)
part_a.function_response.id = "call_A"

part_b = types.Part.from_function_response(
    name="get_price", response={"price": "$150"}
)
part_b.function_response.id = "call_B"

content = types.Content(role="user", parts=[part_a, part_b])
result = _content_to_oci_message(content)

print(result.tool_call_id)  # "call_A" — call_B's result is gone entirely
  1. Alternatively, run a full agent session with OCIGenAILlm and two or
    more tools where the model issues parallel tool calls, and inspect the
    outgoing messages array or the resulting API error/response.

Expected Behavior:
_content_to_oci_message should return one ToolMessage per
function_response part when the Content carries more than one — exactly
as _content_to_message_param() in src/google/adk/models/lite_llm.py
already does for the equivalent LiteLLM path (returns a list of tool
messages when multiple are present; a single message otherwise). Its caller
already flattens that list-or-single-message return value into the outgoing
messages array — the OCI integration would need the same pattern in both
_content_to_oci_message and _build_chat_details.

Observed Behavior:
Only tool_results[0] is converted into a ToolMessage; every later entry
in tool_results is computed and then discarded. The final OCI messages
array contains an AssistantMessage whose tool_calls lists both
call_A and call_B, but only one matching ToolMessage (call_A). OCI
GenAI's chat API follows the OpenAI-compatible convention that every
tool_call_id from the preceding assistant turn must have a matching tool
response — a request missing one is either rejected by the backend as
malformed, or, depending on the model, answered without ever having access
to the missing tool's result.

Environment Details:

  • ADK Library Version (pip show google-adk): 2.6.2 (from src/google/adk/version.py: version = "2.6.2" at commit 1a0c3bd, 2026-08-04
  • Desktop OS: Windows 11
  • Python Version (python -V): Python 3.11.9

Model Information:

  • Are you using LiteLLM: No — this is specific to the OCI Generative AI
    integration (OCIGenAILlm). LiteLLM's equivalent code path
    (_content_to_message_param in lite_llm.py) already handles this
    correctly, and is cited above as the precedent/reference implementation.
  • Which model is being used: N/A — reproduces at the Content-to-message
    conversion level, independent of which model is configured behind OCI's
    serving mode.

🟡 Optional Information

Regression:
No — the OCI integration was added this release cycle, so this isn't a
regression from previously-working behavior.

Additional Context:

  • Bug location: src/google/adk/integrations/oci/_oci_genai_llm.py,
    _content_to_oci_message(), line 201.
  • _build_chat_details() (same file) also assumes a strict
    one-Content-to-one-message mapping and would need a matching update.
  • The sibling LiteLLM integration (src/google/adk/models/lite_llm.py,
    _content_to_message_param, lines 1124–1186) already solves this
    correctly and can serve as the implementation reference.
  • Existing unit test coverage (tests/unittests/integrations/oci/test_oci_genai_llm.py,
    test_content_to_oci_message_function_response, line 216) only covers the
    single-tool-result case. No test in either the unit or integration suite
    covers multiple simultaneous tool results.
  • Searched open and closed issues and PRs for _content_to_oci_message,
    tool_results[0], _oci_genai_llm.py, and combinations of OCI with
    parallel/function_response/tool — no existing issue or PR covers
    this.

I'm working on a PR for this and will submit it shortly.

How often has this issue occurred?:

  • Always (100%) — deterministic whenever a single turn involves more than
    one tool call.

Metadata

Metadata

Assignees

Labels

tools[Component] This issue is related to tools

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions