Originally posted by tschokokuki April 21, 2026
Situation
Since the latest version of agent-framework-ag-ui tools can return text and alter state deterministically. Which is already great! 👌
@tool
async def get_weather(city: str) -> Content:
data = await _fetch_weather(city)
return state_update(
text=f"Weather in {city}: {data['temp']}°C {data['conditions']}",
state={"weather": {"city": city, **data}},
)
Problem
The get_weather example works fine for simple text. But suppose data is a large structured object (e.g. a list of work items, a chart dataset) that the UI should render as a component — not as text. My hypothesis is, that there is currently no way to send rich JSON to the UI as function_result without also sending it to the LLM, where it tends to be repeated or re-summarised verbatim. Correct?
Today _emit_tool_result_common uses the same value for both:
ToolCallResultEvent.content — what the UI renders
-flow.tool_results[].content— what is fed back to the LLM
additional_properties already travels on the Content object but is never read by the emitter to override either result slot.
Proposed solution
Since additional_properties is already on Content, the emitter could honour two reserved keys — no signature change required, full backward compat:
TOOL_RESULT_KEY = "__ag_ui_tool_result__" # overrides ToolCallResultEvent.content
TOOL_RESULT_LLM_KEY = "__ag_ui_tool_result_for_llm__" # overrides what LLM receives
And expose them via state_update:
@tool
async def get_weather(city: str) -> Content:
data = await _fetch_weather(city)
return state_update(
tool_result=data, # UI gets full JSON
tool_result_for_llm=f"{data['temp']}°C, {data['conditions']}", # LLM gets short text
state={"weather": data},
)
Why not use state as a workaround?
state is global and persistent. Tool results are ephemeral per-turn. Mixing them forces the frontend to clean up after every tool call and breaks in parallel multi-tool flows.
Discussed in #5391
Originally posted by tschokokuki April 21, 2026
Situation
Since the latest version of agent-framework-ag-ui tools can return text and alter state deterministically. Which is already great! 👌
Problem
The
get_weatherexample works fine for simple text. But supposedatais a large structured object (e.g. a list of work items, a chart dataset) that the UI should render as a component — not as text. My hypothesis is, that there is currently no way to send rich JSON to the UI as function_result without also sending it to the LLM, where it tends to be repeated or re-summarised verbatim. Correct?Today
_emit_tool_result_commonuses the same value for both:ToolCallResultEvent.content— what the UI renders-
flow.tool_results[].content— what is fed back to the LLMadditional_propertiesalready travels on the Content object but is never read by the emitter to override either result slot.Proposed solution
Since additional_properties is already on Content, the emitter could honour two reserved keys — no signature change required, full backward compat:
And expose them via state_update:
Why not use state as a workaround?
state is global and persistent. Tool results are ephemeral per-turn. Mixing them forces the frontend to clean up after every tool call and breaks in parallel multi-tool flows.