feat(agents): add include_sources for per-agent content source filtering#5925
feat(agents): add include_sources for per-agent content source filtering#5925bofenghuang wants to merge 1 commit into
Conversation
Add `include_sources: list[str] | None` to `LlmAgent` as an orthogonal axis to the existing `include_contents` temporal-window control. Where `include_contents` answers "how far back?", `include_sources` answers "from whom?" — allowing agents in a multi-agent pipeline to declare an allowlist of content sources rather than receiving every narrative-cast peer output. Reserved source names: 'user' (plain human messages), 'self' (this agent's own prior model turns), and any agent name matched directly against event.author before narrative casting occurs. Filtering runs at the event level inside _get_contents(), before _present_other_agent_message() converts authorship into embedded text, so source identity is read from structured metadata rather than parsed from "[agent_name] said:" strings. Function call/response pairing is preserved: FC responses for the current agent's own calls are tied to 'self' (dropped together with their calls when 'self' is absent), and another agent's FC responses are dropped when that agent's call is also filtered. Live-mode events are handled by mapping event.author == agent_name to the 'self' reserved name, since _is_other_agent_reply() returns True for all non-user events in live sessions. Raises ValueError when include_sources=[] (use None to disable).
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
|
Response from ADK Triaging Agent Hello @bofenghuang, thank you for submitting this pull request! We noticed that the Google Contributor License Agreement (CLA) check has failed for this PR. According to our contribution guidelines, all contributions must be accompanied by a signed CLA before we can accept and review your pull request. Please visit Google CLA page to see your current agreements or to sign a new one. Once signed, the check should automatically update or you can request a re-run. Thank you for your contribution, and we look forward to reviewing your PR once the CLA is completed! |
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
1. Link to an existing issue (if applicable):
SequentialAgenthas access to all previous conversations #22072. Or, if no issue exists, describe the change:
Problem:
In multi-agent pipelines, every peer agent's output is narrative-cast into a
role='user'content entry ("For context: [agent_name] said: ..."). The only existing control isinclude_contents('default'/'none'), which is a session-history scope control — it determines how much of the conversation history is visible. There is no agent-level source control: no way to say which agents within that history a given agent should see.Issue #2207 documents this problem. The
before_model_callbackworkaround proposed there has several limitations that make it a poor fit for production use:[agent_name] said:against the internal narrative format. If ADK changes that format, the workaround silently breaks with no warning.before_model_callbackon the agent runs after plugin callbacks (seebase_llm_flow.py:_handle_before_model_callback). LLM observability tools (e.g. Braintrust) hook in as plugins and therefore capturellm_request.contentsbefore the workaround strips the peer entries. What observability reports and what the model actually receives are different — making traces unreliable for debugging.Solution:
Add
include_sources: list[str] | NonetoLlmAgent— a declarative per-agent source control that answers "from which agents?", orthogonal to the existing session-history scope control (include_contents).Reserved names:
'user'(plain human messages),'self'(this agent's own prior model turns), any other string is matched directly againstevent.author.The filter runs inside
_get_contents()at the event level, before_present_other_agent_message()converts authorship into embedded text. Source identity is read fromevent.authormetadata — no text parsing, no format coupling. Since filtering happens at the request-building stage (before any callbacks), observability plugins always see the same contents the model receives. Function call/response pairs are preserved: FC responses for the current agent's calls are tied to'self'(dropped together with their calls), and another agent's FC responses are dropped when that agent's FC call is also filtered. Live-mode sessions are handled by mappingevent.author == agent_nameto the'self'reserved name.include_sources=[]raisesValueErrorat construction time (useNoneto disable filtering).Testing Plan
Unit Tests:
New file
tests/unittests/flows/llm_flows/test_contents_source_filter.py— 20 unit tests directly on_get_contents()and_get_current_turn_contents()covering: no-op (None), each reserved name, combinations, named agent filtering, FC/FR pair preservation, FC/FR co-dropping, other-agent FC response attribution, live-mode self-mapping, and_get_current_turn_contentspropagation.Extended
tests/unittests/agents/test_llm_agent_include_contents.pywith 4 integration tests: empty-list validation,Nonedefault, user-only isolation in a sequential pipeline (the exact case from #2207), and multi-turn composition.Manual End-to-End (E2E) Tests:
Verified with a sequential pipeline (upstream → downstream) using MockModel:
The exact pipeline from issue #2207 can be replaced with:
Checklist
Additional context
Two known edge cases deferred to follow-up:
author='model'): treated as other-agent with name'model'; dropped by['user', 'self']if compaction is enabled.'user'-authored event containing FC responses to calls from multiple agents is classified as other-reply based on the first other-agent response found./cc @Jacksunwei @rohityan