Skip to content

Python: [Bug]: agent_framework_messages_to_agui drops parallel function_result contents #7979

Description

@likebean

Description

When a single Agent Framework Message with role="tool" contains multiple function_result contents (common for parallel tool calls), agent_framework_messages_to_agui emits only one AG-UI tool message — the last function_result — and silently drops the others.

Expected: one AG-UI role="tool" message per function_result (matching each call_id).

Actual: a single AG-UI tool message with only the last call_id / result. Upstream assistant messages still list all tool_calls, so clients (e.g. assistant-ui fromAgUiMessages) treat the missing results as pending tool approvals and render Allow / Deny on historical chats.

Steps to reproduce

  1. Run an agent that issues parallel tool calls in one turn (e.g. three read_skill_resource / three search tools).
  2. Persist the conversation as Agent Framework messages. Observe that parallel results are often stored as one role="tool" message whose contents is a list of several function_result items (same message_id).
  3. Convert with agent_framework_messages_to_agui(...).
  4. Inspect the AG-UI output: only one of the function_results survives.

Root cause (code)

In python/packages/ag-ui/agent_framework_ag_ui/_message_adapters.py, agent_framework_messages_to_agui loops contents and overwrites a single tool_result_call_id / content_text for every function_result:

elif content.type == "function_result":
    tool_result_call_id = content.call_id
    content_text = content.result if content.result is not None else ""

Then it appends one AG-UI dict. Parallel results in the same Message are collapsed.

Code Sample

from agent_framework import Message, Role
from agent_framework._types import FunctionCallContent, FunctionResultContent  # adjust imports to current public API
from agent_framework_ag_ui._message_adapters import agent_framework_messages_to_agui

# Assistant announces three parallel calls
assistant = Message(
    role="assistant",
    message_id="asst-1",
    contents=[
        FunctionCallContent(call_id="call_a", name="tool_x", arguments="{}"),
        FunctionCallContent(call_id="call_b", name="tool_x", arguments="{}"),
        FunctionCallContent(call_id="call_c", name="tool_x", arguments="{}"),
    ],
)

# Framework often stores all results on one tool message
tool = Message(
    role="tool",
    message_id="tool-batch-1",
    contents=[
        FunctionResultContent(call_id="call_a", result="result-a"),
        FunctionResultContent(call_id="call_b", result="result-b"),
        FunctionResultContent(call_id="call_c", result="result-c"),
    ],
)

agui = agent_framework_messages_to_agui([assistant, tool])
tool_msgs = [m for m in agui if m.get("role") == "tool"]
assert len(tool_msgs) == 3, f"expected 3 tool messages, got {len(tool_msgs)}: {tool_msgs}"
# Today: len(tool_msgs) == 1 and toolCallId is only "call_c"

Error Messages / Stack Traces

No exception is raised. The failure is silent data loss during conversion, which surfaces in AG-UI / assistant-ui history as unresolved tool calls (requires-action / Allow–Deny).

Package Versions

  • agent-framework / agent-framework-core (local checkout of microsoft/agent-framework, Python packages under python/packages/)
  • agent-framework-ag-ui (python/packages/ag-ui)
  • Observed while using AG-UI session history hydration against persisted Agent Framework messages

Python Version

Python 3.12

Additional Context

Suggested fix: when converting a Message, emit one AG-UI tool message per function_result content (each with its own toolCallId / content), instead of collapsing to the last result. Text / function_call handling on the same message can stay as today (or follow existing conventions if mixed contents are supported).

Happy to send a PR if maintainers agree with expanding one framework tool message into multiple AG-UI tool messages.

Metadata

Metadata

Labels

pythonUsage: [Issues, PRs], Target: Python

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions