212 Commits

Author SHA1 Message Date
George Weale ece924c2dd fix: deliver parallel sub-agent failures to the caller
Close #5455

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970014840
2026-08-24 12:41:52 -07:00
Google Team Member 75679db3fa feat: add ADK_MAX_LLM_CALLS environment variable to configure max LLM calls limit
Continuous Integration / Pre-commit Linter (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Has been cancelled
Copybara PR Handler / close-imported-pr (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.10) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.11) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.12) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.13) (push) Has been cancelled
PiperOrigin-RevId: 968115816
2026-08-20 16:14:11 -07:00
Kathy Wu d42c634bd6 feat: support auth_scheme and auth_credential in RemoteA2aAgent
`RemoteA2aAgent` could not authenticate its calls; a caller had to bake a static
token into a custom `httpx_client`. Every other ADK component with a remote
endpoint takes an `auth_scheme`/`auth_credential` pair.

Accept that pair and an optional `credential_key`. `CredentialManager` resolves
the credential once per invocation, and the headers go on the card fetch and the
message send; with nothing to send, the agent emits `adk_request_credential`.
The interceptors and the derived key are per agent, so one agent's token cannot
reach another agent's host.

`build_auth_headers` also stops sending `Bearer None` for a tokenless OAuth2
credential. The interactive round trip needs an `LlmAgent` parent; the
`AgentRegistry` path resolves server-side.

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 968025847
2026-08-20 13:38:19 -07:00
Kathy Wu 2aea8595fb fix: stop RemoteA2aAgent forwarding credential requests to the remote peer
`_construct_message_parts_from_session` rebuilds the outgoing message from
the session history. It drops `function_response` parts that carry
credential material, but not `function_call` parts. An
`adk_request_credential` call carries a serialized `AuthConfig` in its
arguments, including `raw_auth_credential` (an OAuth2 client secret or a
service account key), and `BaseLlmFlow` appends such an event to the session
whenever a toolset asks the client for a credential. A `RemoteA2aAgent` in
that session then replays the secret to the remote peer.

Drop credential-bearing `function_call` parts too. The scrub runs before
`_present_other_agent_message`, which renders a `function_call` as text with
its arguments inlined, so a later scrub would be too late.

A call counts as credential-bearing by name, or by shape when the name is not
one we know. Only `adk_request_credential` counts by name; the mock auth call
is left alone, because its args hold the peer's own prompt and it stands in for
the peer's last text part. The shape check reads the AuthConfig out of the
`authConfig` field of the AuthToolArguments envelope. A response carries the
AuthConfig flat, so the response-side check reads the top level. Reading the
top level of a request would match nothing, and would drop any ordinary call
that takes an `auth_scheme` argument.

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 967478046
2026-08-19 16:50:09 -07:00
Shangjie Chen f3250bd965 fix: Fix built-in search tools in agent hierarchy when transfer_to_agent is present
Gemini models reject requests that combine built-in search tools (such as
GoogleSearchTool or VertexAiSearchTool) with function declarations (such as
transfer_to_agent) with "400 INVALID_ARGUMENT: Tool use with function calling
is unsupported".

This change addresses this in two ways:
1. When built-in search tools (GoogleSearchTool / VertexAiSearchTool) have
   `bypass_multi_tools_limit=True` in an agent hierarchy, `multiple_tools`
   now accounts for transfer targets so the tool is converted to its
   function-tool equivalent (GoogleSearchAgentTool / DiscoveryEngineSearchTool),
   allowing it to cleanly coexist with `transfer_to_agent`.
2. When built-in search tools are used without bypass in an agent hierarchy,
   `_AgentTransferLlmRequestProcessor` skips injecting `transfer_to_agent`
   and transfer instructions, ensuring only the built-in search tool is sent
   to the model without conflict.

Co-authored-by: Shangjie Chen <deanchen@google.com>
PiperOrigin-RevId: 967423502
2026-08-19 14:52:55 -07:00
George Weale f3b59fd62e fix(workflow): declare abc.ABC on BaseNode so abstract subclasses type-check
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 967414592
2026-08-19 14:36:39 -07:00
prasanna8585 924d802f5b fix: block yaml and ruamel deserialization in agent-config code references
Merge https://github.com/google/adk-python/pull/6646

Reject yaml and ruamel unsafe/full loaders in config agent to prevent RCE.
Note: ruamel is a transitive dev/test dependency, but is blocked to prevent RCE if present in the environment.
PiperOrigin-RevId: 967340296
2026-08-19 12:12:55 -07:00
Jinni Gu 0fd681e7d2 feat: support sub-agent escalation event in ParallelAgent
Merge https://github.com/google/adk-python/pull/5105

Closes: #5104
PiperOrigin-RevId: 967017854
2026-08-18 23:52:29 -07:00
Kathy Wu 9ffe8be6f9 fix: fence relayed agent output so it cannot pose as instructions
When one agent hands off to another, `_present_other_agent_message` replays the
first agent's turn to the second as a `role="user"` message -- the same channel
the real user speaks on -- interpolating the text straight into
`[agent] said: ...`. Nothing marks where the quoted transcript ends, so a
payload the first agent was talked into emitting reads to the second agent as a
fresh directive. Anyone who can chat to a low-privilege front-end agent can
therefore aim instructions at whatever tools the agent it transfers to holds.

Every relayed payload -- text, thoughts, tool arguments, tool results -- is now
quoted between explicit markers, and the leading part of the message states
that what sits between them is data to read and not instructions to follow.
Markers occurring inside a payload are elided first, so quoted content cannot
close its own block and carry on speaking as the framework.

The markers, the preamble and the quoting helpers live in
`flows/llm_flows/_fencing.py`. The unit tests and the conformance harness both
have to spell the expected framing, so it sits in a module of its own rather
than inside `contents.py`, where they would have to reach for private names.

This raises the bar rather than closing the class: a model can still be talked
round by text it was told to distrust. What it removes is the structural
ambiguity that made a relayed payload indistinguishable from a user turn.

Relayed turns now cost the preamble plus two marker lines per part, and
anything matching on the old `For context: [x] said: y` shape needs updating.
The conformance replay harness is one such matcher, and now reduces a relayed
turn to the payload it carries before comparing, so recordings cut before the
fencing still replay.

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 966694665
2026-08-18 11:08:56 -07:00
Ahmed-Ali-313 7c2af5f9c1 fix: improve clarity and actionable context in error messages
Merge https://github.com/google/adk-python/pull/6487

PiperOrigin-RevId: 964922800
2026-08-14 15:33:07 -07:00
Google Team Member 72f3ff5cfb feat(a2a): add native task mode support to RemoteA2aAgent
- Support mode="task" in RemoteA2aAgent to natively execute task-oriented tools.
- Ensure RemoteA2aAgent in task mode registers the finish_task tool definition.
- Fix task scope aggregation during delegation using a two-pass active task scope search in the runner.
- Correctly map terminal states and status values inside A2aAgentExecutor.

PiperOrigin-RevId: 964898667
2026-08-14 14:43:58 -07:00
Google Team Member b381f6c234 ADK changes
PiperOrigin-RevId: 964481835
2026-08-13 22:12:45 -07:00
weiguangli-io 8963a04846 fix: filter thought parts from A2A client user-facing response
Merge https://github.com/google/adk-python/pull/4686

Fixes #4676

PiperOrigin-RevId: 964433048
2026-08-13 19:52:28 -07:00
George Weale 66930e6526 fix(agents): report which toolset an agent lost when one fails to load
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 964417035
2026-08-13 19:02:17 -07:00
Google Team Member aec7aa33c8 fix(a2a): flatten human-input responses on resume to avoid mixing them with text
RemoteA2aAgent now rewrites human-input pause responses (adk_request_input,
adk_request_confirmation, adk_request_credential, and the mock input/auth calls)
to text before forwarding them to a remote agent on resume, matched by the
function call name. This stops Runner._validate_new_message from rejecting a
resumed message that mixes function responses with text. Credential (AuthConfig)
payloads are dropped instead of forwarded, and real long-running tool responses
are preserved so the peer can resume them by id.

PiperOrigin-RevId: 963124368
2026-08-11 18:29:16 -07:00
George Weale 4f58306477 fix(a2a): adopt a directly supplied agent card's description
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 961611760
2026-08-08 21:54:43 -07:00
Kathy Wu a16f6da331 fix: block the whole standard library in agent-config code references
The denylist for YAML code references named dangerous standard library modules
one by one, so anything it missed stayed reachable: it had `profile` but not
`cProfile`, `pdb` but not `bdb`, `trace`, `timeit` or `pydoc`. Several of those
execute a string you hand them and need no constructor `args`, so naming one as
a tool or callback slipped past both existing mitigations and ran arbitrary
code.

Block the standard library outright via `sys.stdlib_module_names`. Configs only
ever name the agent's own package, `google.adk`, or a third-party integration,
so nothing legitimate breaks and the list stops needing a revisit every Python
release. The explicit denylist stays for names that are no longer reported as
standard library but remain importable, such as `distutils` and CPython's
`test` packages.

A denylist still cannot cover third-party packages, which the loader resolves
by name, so this narrows the surface rather than closing it.

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 961151524
2026-08-07 15:31:05 -07:00
George Weale 456524d714 test: add unit tests for public symbols that had no coverage
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 960421043
2026-08-06 11:41:06 -07:00
George Weale 490946ba32 perf: memoize the model adapter resolved from LlmAgent.model
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 959996143
2026-08-05 19:08:20 -07:00
George Weale 19e2a7283f fix: scope LangGraph checkpointer thread id to app and user
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 959983732
2026-08-05 18:30:15 -07:00
George Weale 3bb10115d3 fix: stop LlmAgent resume path from ending parent on pause
Close #6017

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 959970905
2026-08-05 17:56:16 -07:00
George Weale 7f82142adb refactor(types): make google.adk.models pass strict mypy
Not annotations-only. This is one component's slice of a repo-wide typing
cleanup, and the wider change was found to contain behavior changes that have
not all been individually triaged, so please review it as a functional change.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 958623646
2026-08-03 15:57:49 -07:00
George Weale 83b71e68a9 chore: remove tracker references from comments and docstrings
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 956763571
2026-07-30 15:20:54 -07:00
Kathy Wu 6d8045c23f feat: Extend HTTP trace debugging to MCP Toolset operations
Previously, HTTP trace debugging was only captured during MCP tool execution (in
`McpTool.run_async`) and stored in `ToolContext.custom_metadata`. This left other
MCP HTTP calls, such as session initialization and tool listing (`list_tools`),
untraced, making it difficult to debug failures in these phases.

This CL extends the tracing capability:
- Exposes `custom_metadata` on `ReadonlyContext` as a read-only property.
- Keeps `custom_metadata` on `Context` as a mutable property.
- Wraps `McpToolset._execute_with_session` with `_http_debug_var` to capture HTTP
  traces during session creation and toolset operations (e.g., `get_tools`,
  `read_resource`).
- Appends captured traces directly to the underlying `_invocation_context._custom_metadata`
  using protected access, ensuring that traces are populated even when a
  `ReadonlyContext` is passed (like during tool listing), while still keeping the
  `ReadonlyContext` public API strictly read-only.
- Adds unit tests to verify that HTTP traces are captured for both mutable and
  read-only contexts.

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 956759438
2026-07-30 15:12:41 -07:00
George Weale 16cbb7d1c6 fix: constrain the RPC targets of a network-fetched A2A agent card
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 956644793
2026-07-30 11:37:23 -07:00
Harineko0 5a12ee0998 fix(a2a): Promote RemoteA2aAgent response to workflow node output
Merge https://github.com/google/adk-python/pull/5852

## Link to Issue or Description of Change

### Problem

When a `RemoteA2aAgent` is used as a static node in a `Workflow` graph that feeds into a `JoinNode`, the joined output contains `None` for every `RemoteA2aAgent` predecessor.

Reproducer (simplified from a real coordinator graph):

```python
parallel_investigation_join = JoinNode(name="parallel_investigation_join")
Workflow(
    edges=[
        ("START", account_context_agent, parallel_investigation_join),  # RemoteA2aAgent
        ("START", ticket_history_agent, parallel_investigation_join),   # RemoteA2aAgent
        ("START", diagnostics_agent, parallel_investigation_join),      # RemoteA2aAgent
        ...
    ]
)
```

Observed `JoinNode` input:

```yaml
parallel_investigation_join:
  account_context_agent: null
  ticket_history_agent: null
  diagnostics_agent: null
```

Root cause: `RemoteA2aAgent` inherits the default `BaseAgent._run_impl`, which iterates `run_async` and yields events without ever setting `event.output` or `event.node_info.message_as_output`. As a result, `NodeRunner._track_event_in_context` leaves `ctx.output` as `None`, and `Workflow._handle_completion` never records an entry in `loop_state.node_outputs` for that predecessor. `JoinNode` then sees `None` for it.

`LlmAgent` already solves the equivalent problem by overriding `_run_impl` and promoting the model's text reply to `event.output` (via `process_llm_agent_output` in `_llm_agent_wrapper.py`). `RemoteA2aAgent` had no equivalent hook.

### Solution

Add a workflow-only override of `_run_impl` on `RemoteA2aAgent` that mirrors `LlmAgent`'s behavior. For each event yielded by `BaseAgent._run_impl`, a new `_promote_response_to_output` helper joins the text of all parts that are **not** thoughts, function calls, or function responses, assigns it to `event.output`, and sets `event.node_info.message_as_output = True` (consistent with `LlmAgent`, prevents `NodeRunner._flush_output_and_deltas` from emitting a duplicate trailing output event).

The helper skips:

- partial events (streaming chunks)
- events not authored by this agent
- events whose `event.output` is already set
- events whose content carries only thoughts (streaming `working` / `submitted` task statuses that the legacy `_handle_a2a_response` marks `thought=True`)
- events whose content carries only function calls (the `input_required` / `auth_required` mock function call inserted by `_create_mock_function_call_for_required_user_input` — those should remain interrupts, not outputs)
- events whose A2A task state is non-final (`submitted`, `working`, `input-required`, `auth-required`, `unknown`). The v2 integration path (`_handle_a2a_response_v2`) delegates to converters that do **not** mark streaming `working` text as `thought=True`, so the thought filter alone is not enough. Without this guard, a `working` text event and the subsequent `completed` text event would each try to set `event.output`, causing `NodeRunner` to raise `ValueError: Output already set` on the second event and aborting the run before the real final answer ever surfaced. The state is read from `event.custom_metadata['a2a:response']['status']['state']`, which `_run_async_impl` already stamps before yield. Plain `A2AMessage` responses (no status field) and terminal task states (`completed`, `failed`, `canceled`, `rejected`) still promote.

In addition, `_run_impl` short-circuits after the first successful promotion. This protects against the case where a server emits multiple terminal-state events for one run (e.g. a `completed` status update followed by trailing artifact updates on the same already-completed task) — only the first terminal event becomes the node's output, subsequent ones pass through untouched.

Scope is intentionally narrow: only the agent boundary is touched. `to_adk_event.py` and the workflow scheduler are unchanged, since the same workaround (promoting content → output at the agent layer) is what `LlmAgent` does and what keeps the fix local.

## Testing Plan

### Unit Tests

- [x] I have added or updated unit tests for my change.
- [x] All unit tests pass locally.

Added `TestRemoteA2aAgentWorkflowOutput` in `tests/unittests/agents/test_remote_a2a_agent.py` (20 cases).

### Manual End-to-End (E2E) Tests

I ran the failing workflow described in the **Problem** section against [a real ADK app](https://github.com/gdsc-osaka/customer-support-agent-example/blob/2b2882ed0bd0e918aeaaa38b2e420f129db47dc1/agents/coordinator/agent.py#L27): a `Workflow` graph whose `START` fans out into multiple `RemoteA2aAgent` nodes that all feed into a single `JoinNode`. Each remote specialist runs as its own A2A server; the coordinator runs the workflow and forwards the joined dict to a downstream synthesis step.

Before the fix:

<img width="1624" height="1060" alt="Screenshot 2026-05-26 at 15 32 06" src="https://github.com/user-attachments/assets/22ddb04b-4c1d-4c75-a2ec-d4f1e87bc825" />

After the fix:

<img width="1580" height="1016" alt="Screenshot 2026-05-26 at 15 30 25" src="https://github.com/user-attachments/assets/0848a663-504f-4e60-99ff-81aa70daf164" />

### Checklist

- [x] I have read the [CONTRIBUTING.md](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) document.
- [x] I have performed a self-review of my own code.
- [x] I have commented my code, particularly in hard-to-understand areas.
- [x] I have added tests that prove my fix is effective or that my feature works.
- [x] New and existing unit tests pass locally with my changes.
- [x] I have manually tested my changes end-to-end.
- [x] Any dependent changes have been merged and published in downstream modules.

### Additional context

The fix mirrors the pattern `LlmAgent` already uses (`process_llm_agent_output` in `src/google/adk/workflow/_llm_agent_wrapper.py`), keeping output-promotion at the agent boundary rather than touching the workflow scheduler or the A2A converters. This minimizes blast radius and avoids regressions in non-workflow usages of `RemoteA2aAgent`, where the `_run_impl` path is not exercised.

Co-authored-by: George Weale <gweale@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5852 from Harineko0:fix/remote-a2a-agent-workflow-output 0f262921e5ccbe24fbb33427dd378fad857cd8da
PiperOrigin-RevId: 955426327
2026-07-28 13:10:02 -07:00
George Weale 425dda1904 fix: canonicalize context cache fingerprint for stable hashing
Serialize the fingerprint with sorted JSON keys, exclude_none, and a
stable tool order so reordered tools or SDK field drift no longer change
the hash. Existing sessions take a one-time cache miss while the stored
fingerprint is recomputed.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 955368831
2026-07-28 11:22:58 -07:00
Google Team Member 3dd1156c33 feat(a2a): support per-invocation auth headers when fetching agent cards
The agent card fetch could only be authenticated via a pre-built httpx
client with credentials fixed at construction time; request_interceptors
only wrap send_message, so the card GET stayed unauthenticated.

Add card_request_interceptors on A2aRemoteAgentConfig (symmetric with
request_interceptors). Each CardRequestInterceptor.before_request is an
async hook returning a typed A2aCardRequestConfig whose headers are injected
into the card request.

PiperOrigin-RevId: 955226237
2026-07-28 06:40:04 -07:00
George Weale 31392bad27 fix: close A2A response stream when the caller stops consuming
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 954655682
2026-07-27 08:57:28 -07:00
George Weale 4c6f22e8e6 fix: allow http_options.extra_body in generate_content_config
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 953974464
2026-07-25 16:09:14 -07:00
George Weale 472e4635fb fix: reject base_url and extra_body in generate_content_config
An agent-level `generate_content_config.http_options.base_url` is copied into
every LlmRequest and overrides the client transport, so the configured API key
and the full prompt/response traffic are sent to that host. Nothing rejected
it, so a supplied agent config (including a YAML one) could redirect a
credentialed model call to an arbitrary endpoint.

`http_options.extra_body` is recursively merged into the serialized request
body just before it is sent, and the merge aligns the incoming key case to the
target, so it can overwrite `systemInstruction`, `tools` and `generationConfig`
— the exact fields the other three checks in this validator exist to reject.
It bypassed all of them.

Reject both in the field validator. Request-time `http_options` such as
headers, timeout, and retry options are unaffected; `base_url` belongs on the
model or its client, which is already why `RunConfig.http_options` deliberately
does not merge it.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 953629510
2026-07-24 17:28:48 -07:00
Brumbelow 088be86040 fix: emit only new artifact parts on streaming artifact updates
Merge https://github.com/google/adk-python/pull/6386

Fixes #6343

PiperOrigin-RevId: 952172971
2026-07-22 09:40:56 -07:00
Google Team Member 79ba5aff17 fix: propagate custom metadata from RunConfig to InvocationContext
Ensure custom metadata defined in RunConfig is copied to InvocationContext's private custom metadata attribute during model post-initialization. This enables tools to retrieve context-dependent metadata during execution.

PiperOrigin-RevId: 951981807
2026-07-22 02:30:32 -07:00
Haran Rajkumar 49fdc26e40 feat(agents): forward ManagedAgent instruction as system_instruction
Resolve ManagedAgent.instruction (with {placeholder} state injection for
strings; InstructionProvider callables bypass injection) and forward it to
the Managed Agents API as the interaction's system_instruction on every
turn, including chained turns. An empty instruction sends nothing.

Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 951220238
2026-07-20 21:04:45 -07:00
Haran Rajkumar 720af3d5c9 feat(agents): add instruction field to ManagedAgent
Add an `instruction` field (str or InstructionProvider) to ManagedAgent
and a `canonical_instruction` resolver mirroring LlmAgent. This task only
adds the field and resolver; forwarding it to the Managed Agents API is a
follow-up change.

Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 951193016
2026-07-20 19:43:07 -07:00
George Weale 82197740a6 feat: add state_delta support to LiveRequest for live mode
The non-live runner path can apply session state changes via a state_delta,
but the live/bidi flow offered no equivalent, so live callers could not seed
or update session state alongside their input. Add an optional state_delta
field to LiveRequest and, in the live flow, apply it as a separate
state-delta event so it always takes effect regardless of content, partial,
or function-response requests. The field defaults to None, preserving
existing behavior.

Close #4220

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 951162326
2026-07-20 18:22:24 -07:00
George Weale 551372bcd8 fix: require patched async LangGraph runtime
The old LangGraph version range allowed releases affected by a published security advisory, and the adapter called synchronous state and invocation APIs from an async path, including a state lookup with no checkpointer. This requires a patched LangGraph release and moves the adapter to the async compiled-state APIs (CompiledStateGraph, aget_state, ainvoke), skipping the state lookup when the graph has no checkpointer.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 950895002
2026-07-20 09:48:39 -07:00
Shangjie Chen 54344edfb9 fix(agents): skip output_key processing on intermediate conversational text in task-mode LlmAgent
Task-mode LlmAgents produce intermediate conversational text responses (such as clarification questions or greetings) before completing their task via finish_task.

When output_key and output_schema were configured on a task-mode agent, ADK attempted to validate intermediate conversational text against output_schema during state_delta processing, causing a ValidationError crash on non-JSON text.

- Skip output_key state_delta processing on intermediate text responses for task-mode agents (mode="task") in __maybe_save_output_to_state and _save_output_to_state.
- Preserve output_schema validation for non-task agents.
- Ensure task-mode finish_task validated output is written to output_key state_delta on task completion.
- Add unit tests for task-mode output_key handling.

Co-authored-by: Shangjie Chen <deanchen@google.com>
PiperOrigin-RevId: 949357458
2026-07-16 21:56:43 -07:00
George Weale 98fc03690b fix: grow eligible Gemini cache prefixes
When a cache expired, renewal recreated the original prefix, so completed turns could stay outside every refreshed cache. It also assumed a 4,096-token floor for every model, which skipped eligible Gemini 2.5 prefixes between 2,048 and 4,095 tokens. This grows the cache to the latest validated prefix and applies the documented 2,048-token floor for Gemini 2.5 while keeping 4,096 for Gemini 3.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 949034094
2026-07-16 10:08:33 -07:00
HenD.YA faa1744254 fix: block posix, nt, _posixsubprocess, and _socket modules in agent config validation
Merge https://github.com/google/adk-python/pull/6389

PiperOrigin-RevId: 948150331
2026-07-15 00:16:50 -07:00
George Weale 4fdc94c791 fix: honor server-reported Gemini cache expiry
The client derived cache expiry from the local clock right after creation, but network delay or server-side normalization can push the real expiry off that estimate and lead to reuse at the wrong boundary. This records the expire_time the API returns when present, and falls back to the local TTL only when the response omits it.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 947961020
2026-07-14 16:12:23 -07:00
George Weale 8de4237e18 fix: scope Gemini cache identity
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 947912687
2026-07-14 14:39:19 -07:00
苏紫辰 ee7174da1f fix(models): Allow context caches with empty content prefixes
Merge https://github.com/google/adk-python/pull/6367

### Link to Issue or Description of Change

- Closes: #6363

**Problem:**

[PR #6067](https://github.com/google/adk-python/pull/6067) correctly changed fingerprint-only cache metadata to use the cacheable conversation prefix rather than all request contents. This keeps trailing user contents, including request-scoped dynamic instructions, out of the stable cache identity.

For an initial request containing only the latest user content, the cacheable conversation prefix is legitimately empty. ADK therefore stores fingerprint-only metadata with `contents_count == 0`. It does not create a cache on this initial request because no token count from a previous response is available yet.

On the next request, ADK uses the stored count to calculate the current fingerprint. When the system instruction and tools remain unchanged, that fingerprint matches the stored fingerprint. If the system instruction and tools are large enough to meet Gemini’s cache-size requirement, ADK makes its first real cache-creation attempt.

The stored count is deliberately reused because it defines the prefix covered by the matched fingerprint. Even if the later request now has a non-empty cacheable conversation prefix, ADK still slices it with the stored zero. This produces `llm_request.contents[:0] == []`, which is passed as `CreateCachedContentConfig(contents=[])`.

The Google Gen AI SDK distinguishes an omitted `contents` field from an explicit empty list. It rejects `contents=[]` with `ValueError: contents are required`, while `contents=None` omits only the conversation contents and still allows the system instruction and tools to be included in the cache request.

ADK catches the exception, logs a warning, and continues the model request without a cache, so the request itself does not crash. Because no `cache_name` was created, the caller preserves the fingerprint-only metadata and its original `contents_count == 0`. This behavior is intentional so transient cache-creation failures can retry the same cache identity.

As long as the system instruction and tools remain unchanged, the next request again matches the same fingerprint using the preserved count. ADK then retries the same invalid `contents=[]` configuration, logs another warning, and again receives no `cache_name`. This cycle repeats on every matching request, so the cache is never established.

**Solution:**

Pass `None` when the cacheable conversation prefix is empty so the SDK omits the `contents` field. Preserve the existing list behavior for non-empty prefixes.

The existing zero-prefix lifecycle test now verifies that the SDK-bound configuration uses `config.contents is None`.

### Testing Plan

**Unit Tests:**

- [x] I have added or updated unit tests for my change.
- [x] All unit tests pass locally.

Focused test result:

- `tests/unittests/agents/test_gemini_context_cache_manager.py`: 33 passed, 5 warnings.

The five warnings are unrelated to this change: four are `BaseAgentConfig` deprecation warnings, and one reports that the experimental `AGENT_CONFIG` feature is enabled.

The relevant context-cache tests passed under tox with Python 3.10 through 3.14.

Additional validation:

- Ruff passed.
- Pyink passed.
- Pre-commit and compliance checks for the changed files passed.
- The source distribution and wheel built successfully.
- The wheel installed and imported successfully in a clean Python 3.12 environment.
- `git diff --check` passed.

**Manual End-to-End (E2E) Tests:**

Not run because this environment cannot create and inspect a real Google cache resource.

The official SDK documentation and source confirm the relevant boundary behavior:

- [`CreateCachedContentConfig`](https://googleapis.github.io/python-genai/genai.html#genai.types.CreateCachedContentConfig) allows `contents` to be `None` and defines `system_instruction` and `tools` as separate fields.
- The [SDK cache request builder](https://github.com/googleapis/python-genai/blob/v2.11.0/google/genai/caches.py#L270-L292) only transforms `contents` when it is not `None`; `system_instruction` and `tools` are handled independently.
- The SDK’s [`t_contents()` implementation](https://github.com/googleapis/python-genai/blob/v2.11.0/google/genai/_transformers.py#L476-L482) explicitly rejects an empty list with `ValueError("contents are required.")`.

Therefore, `None` omits the optional field, while `[]` enters the transformer and is rejected. The unit test verifies that ADK produces the valid side of this boundary.

### Checklist

- [x] I have read the [CONTRIBUTING.md](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) document.
- [x] I have performed a self-review of my own code.
- [x] I have commented my code, particularly in hard-to-understand areas. No additional comments were needed for this focused boundary conversion.
- [x] I have added tests that prove my fix is effective or that my feature works.
- [x] New and existing unit tests pass locally with my changes.
- [ ] I have manually tested my changes end-to-end.
- [x] Any dependent changes have been merged and published in downstream modules. Not applicable; there are no dependent changes.

### Additional context

The root cause of the missed regression in the existing tests is that `caches.create()` is mocked and therefore does not reproduce the Google Gen AI SDK’s complete request transformation and validation behavior. The mock accepted `contents=[]` and returned success, so the existing zero-prefix lifecycle test passed even though the real SDK rejects that value before sending the request.

No user-facing API or documentation changes are required.

Co-authored-by: Yi Liu <yiliuly@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/6367 from hxaxd:fix/gemini-cache-empty-contents 22f1a8b6fbb724194f37d8c9d061b4d925ee3527
PiperOrigin-RevId: 947285615
2026-07-13 15:47:02 -07:00
Haran Rajkumar 0a62d39ff9 chore(agents): tag ManagedAgent traffic with a +managed_agent version suffix
ManagedAgent and Gemini(use_interactions_api=True) both reach the Interactions
API and surface identically as tool_name=google-adk in Google's usage pipeline,
with no way to tell them apart. Thread an optional framework_label through
merge_tracking_headers / get_tracking_headers / get_client_labels /
_get_default_labels, and have ManagedAgent emit google-adk/<version>+managed_agent
on the per-request extra_headers it sends to interactions.create, so its traffic
is distinguishable via the tool_version dimension while tool_name stays
google-adk. The suffix is applied on the request-time header path because that is
what reaches the Interactions wire (the per-request extra_headers override the
genai client's construction-time headers; verified by live capture). An explicit
framework_label takes precedence over the Agent Engine (+remote_reasoning_engine)
suffix; all other callers of merge_tracking_headers keep the no-arg default and
are unchanged. Follow-up to the ManagedAgent tracking-headers change.

Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 947239702
2026-07-13 14:19:30 -07:00
Haran Rajkumar fc98549244 feat(agents): wrap single_turn non-LlmAgent sub-agents as inline tools
Relax the parent `LlmAgent` auto-wrapping loop so any sub-agent declaring
`mode='single_turn'` is exposed as an inline `_SingleTurnAgentTool`, not just
`LlmAgent` sub-agents. This lets a `ManagedAgent` with `mode='single_turn'` be
called like a tool while staying excluded from LLM-transfer targets. `LlmAgent`
sub-agents still default to `mode='chat'` (unchanged), and sub-agents that do
not declare `mode` are never wrapped.

Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 945960582
2026-07-10 17:04:08 -07:00
Haran Rajkumar a680cea874 feat(agents): bridge node_input to user_content for ManagedAgent nodes
Override `ManagedAgent._run_impl` so that, when the agent runs as a node (e.g.
as a single-turn tool), the parent's tool-call argument arriving as `node_input`
is surfaced as the agent's `user_content` and forwarded to the interactions API.
When `node_input` is `None` (classic agent-tree run), behavior matches
`BaseAgent._run_impl`. Uses the shared `node_input_to_content` helper.

Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 945949834
2026-07-10 16:35:57 -07:00
Google Team Member ce2e4caf14 fix: Resolve scheduler leakage and make scheduler instantiation explicit
- Remove implicit on-demand fabrication of DynamicNodeScheduler during child Context derivation to avoid mutating parent context state during execution.
- Always instantiate and set the DynamicNodeScheduler at the root level in the runner.
- Clear `_workflow_scheduler` on execution exit (in both runner and workflow loops) to prevent scheduler lifetime leakage.
- Update tests to explicitly set `rerun_on_resume = True` where resumption of mock nodes is expected.
- Always rerun Workflow nodes on resume to allow their internal loops to correctly drive resumption of child nodes.
- Use `weakref` for `_workflow_scheduler` in `Context` to prevent reference cycle between scheduler, tasks, and contexts.

PiperOrigin-RevId: 945945253
2026-07-10 16:25:29 -07:00
Haran Rajkumar da50578bc5 feat(agents): add single_turn mode field to ManagedAgent
Add a `mode` field to `ManagedAgent` accepting `'single_turn'` (or `None`, the
default). `single_turn` marks the agent to run as an inline single-turn tool of
a parent `LlmAgent` -- the recommended replacement for `AgentTool` -- while
`None` leaves it usable as an LLM-transfer target. This commit only adds and
validates the field; the runtime wiring follows in later commits.

Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 945930825
2026-07-10 15:53:40 -07:00
Shahin Saadati c0fa51b33f feat: add skill to verify markdown code snippets
Merge https://github.com/google/adk-python/pull/6212

The skill extracts Python code blocks from a markdown file, executes each in an isolated environment, and generates a report covering load, run, and coverage status.

Fixes #6211

Also includes fixes for:
- Weakref GC of mock scheduler in test_context.
- check_new_py_files.sh path matching in monorepo environments.

PiperOrigin-RevId: 945889882
2026-07-10 14:21:13 -07:00
Haran Rajkumar ed915b95e4 chore(agents): merge request-time headers on ManagedAgent interactions
ManagedAgent now forwards per-request headers on its Interactions API calls:
RunConfig.http_options.headers merged with ADK tracking headers (via
merge_tracking_headers), passed through _create_interactions as extra_headers.
This gives parity with google_llm's request-time header merge, so user-supplied
headers and ADK identifiers ride on every ManagedAgent request.

Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 945824470
2026-07-10 11:59:01 -07:00