Description
The skip_summarization text-append in __build_response_event
(src/google/adk/flows/llm_flows/functions.py) is not scoped to AgentTool,
so it fires for every tool that sets skip_summarization. For non-AgentTool
tools whose function response is an internal acknowledgement, this surfaces the
raw response payload to the user as visible text.
Background
PR #5974 (closing #3881) added this block so an AgentTool's output is not lost
in UIs that don't render function responses:
# src/google/adk/flows/llm_flows/functions.py (current main)
has_displayable_result = display_result is not None and display_result != {
'result': None
}
if (
tool_context.actions.skip_summarization
and 'error' not in function_result
and has_displayable_result
):
if isinstance(display_result, str):
result_text = display_result
else:
result_text = json.dumps(display_result, ensure_ascii=False, default=str)
content.parts.append(types.Part.from_text(text=result_text))
Issue #3881 is specifically about AgentTool (its title: "The agent doesn't
work as expected when skip_summarization is set to True in AgentTool"), and the
PR title is "ensure AgentTool text output when skip_summarization is True". But
the condition only checks tool_context.actions.skip_summarization — there is no
isinstance(tool, AgentTool) guard, so the behavior applies to all tools.
Why this is a problem
skip_summarization is also set by tools for the opposite reason: their
function response is an internal acknowledgement that should not be summarized
or shown. UI / widget-rendering tools are the common case — they return
something like {"status": "ok"} and set skip_summarization=True because the
user-facing result is a rendered widget, not text.
With the unguarded append, that ack is force-converted into a text Part. A
text part then bypasses the typical UI/SSE filters that strip
functionResponse / functionCall / thought parts, so the raw payload
({"status": "ok", ...}) is surfaced to the user as visible text. We observed
this as a consistent, user-visible regression after upgrading to a build that
contains #5974: render-style tools began leaking their ack JSON into the chat
transcript.
Steps to reproduce
- Define a non-AgentTool (e.g. a
FunctionTool) that sets
tool_context.actions.skip_summarization = True and returns a dict such as
{"status": "ok"}.
- Invoke it through the normal function-calling flow.
- The resulting function-response event contains an extra
Part.from_text(...)
with the serialized dict, in addition to the functionResponse part.
Expected: only the functionResponse part is present (the ack is not shown as
text). Actual: a text part carrying the serialized response is appended.
Proposed fix
Guard the append with isinstance(tool, AgentTool) so it matches the original
intent — AgentTool keeps its text output; other tools that opt out of
summarization do not have their function response duplicated as text.
A PR implementing this (with a regression test) is attached.
Environment
Description
The
skip_summarizationtext-append in__build_response_event(
src/google/adk/flows/llm_flows/functions.py) is not scoped toAgentTool,so it fires for every tool that sets
skip_summarization. For non-AgentTooltools whose function response is an internal acknowledgement, this surfaces the
raw response payload to the user as visible text.
Background
PR #5974 (closing #3881) added this block so an
AgentTool's output is not lostin UIs that don't render function responses:
Issue #3881 is specifically about
AgentTool(its title: "The agent doesn'twork as expected when skip_summarization is set to True in AgentTool"), and the
PR title is "ensure AgentTool text output when skip_summarization is True". But
the condition only checks
tool_context.actions.skip_summarization— there is noisinstance(tool, AgentTool)guard, so the behavior applies to all tools.Why this is a problem
skip_summarizationis also set by tools for the opposite reason: theirfunction response is an internal acknowledgement that should not be summarized
or shown. UI / widget-rendering tools are the common case — they return
something like
{"status": "ok"}and setskip_summarization=Truebecause theuser-facing result is a rendered widget, not text.
With the unguarded append, that ack is force-converted into a text
Part. Atext part then bypasses the typical UI/SSE filters that strip
functionResponse/functionCall/thoughtparts, so the raw payload(
{"status": "ok", ...}) is surfaced to the user as visible text. We observedthis as a consistent, user-visible regression after upgrading to a build that
contains #5974: render-style tools began leaking their ack JSON into the chat
transcript.
Steps to reproduce
FunctionTool) that setstool_context.actions.skip_summarization = Trueand returns a dict such as{"status": "ok"}.Part.from_text(...)with the serialized dict, in addition to the
functionResponsepart.Expected: only the
functionResponsepart is present (the ack is not shown astext). Actual: a text part carrying the serialized response is appended.
Proposed fix
Guard the append with
isinstance(tool, AgentTool)so it matches the originalintent —
AgentToolkeeps its text output; other tools that opt out ofsummarization do not have their function response duplicated as text.
A PR implementing this (with a regression test) is attached.
Environment
mainskip_summarization is True"