Commit Graph

2138 Commits

Author SHA1 Message Date
Shangjie Chen c986ff0fce fix: prevent duplicate function execution when support_cfc is enabled
When support_cfc=True is used in run_async, delegate to run_live directly to prevent duplicate tool execution.

Co-authored-by: Shangjie Chen <deanchen@google.com>
PiperOrigin-RevId: 967506529
2026-08-19 18:04:44 -07:00
George Weale c244a9c833 perf: run local code execution in a plain child interpreter
UnsafeLocalCodeExecutor ran each program as a multiprocessing spawn child,
which had to import this package before it could run a single line, costing
about 2.3 seconds per execution. It now runs the program in a plain child
interpreter, the shape ContainerCodeExecutor already uses, which brings a
trivial program down to about 35 milliseconds. The result now comes from the
child's exit status and pipes rather than a queue the child has to write to,
so a program that dies without reporting anything no longer leaves the agent
waiting forever, and the traceback the model is shown no longer opens with a
frame from inside this package.

One behavior change follows from taking the result from the exit status: a
program calling sys.exit(0) is now reported as having succeeded. It was
previously reported as a failure, because the spawn child raised SystemExit
before it could write to the result queue.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 967496975
2026-08-19 17:39:16 -07:00
Ishaan ac8dad2580 fix: strip internal planning tags from PlanReActPlanner output
Merge https://github.com/google/adk-python/pull/6709

References #3378

PiperOrigin-RevId: 967495179
2026-08-19 17:34:13 -07:00
Google Team Member 3f2d399cd9 fix: resolve server disconnect logs
PiperOrigin-RevId: 967479906
2026-08-19 16:55:28 -07:00
George Weale deee6d2c47 fix(flows): pair a function response with the call it answers
Close #6761

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 967478519
2026-08-19 16:51: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
George Weale b4a9acbcb9 test(sessions): run the shared contract tests against more backends
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 967478023
2026-08-19 16:49:07 -07:00
Kathy Wu 4e68bad199 refactor: key the MCP agent-server session map on the connection
`to_mcp_server` keeps one ADK session per MCP connection, so successive tool
calls on that connection form a single conversation. It keyed that map on
`ctx.session`.

That key is correct on MCP SDK 1.x, where the server builds one session object
per connection. It is wrong on 2.x: the server builds a fresh `ServerSession`
for every inbound request and holds the connection on the session's private
`_connection`. The key would change on every call, so every tool call would
start a new conversation. Nothing raises. The agent just forgets.

Route the key through `_connection_key`, which reads `_connection` when the SDK
provides it and falls back to the session when it does not. That gives one key
per connection on both versions, and leaves 1.x behaviour unchanged.

The fallback degrades to one session per request on purpose. It must not fall
back to an object shared by all connections, because separate clients would
then share one conversation.

The private attribute is a stopgap. SDK 2.x already defines a public
`mcp.server.context.Context.connection`, but the server does not hand that
class to tool functions yet, so a tool's `Context` has no public route to its
connection.

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 967462559
2026-08-19 16:14:37 -07:00
Kathy Wu 3819b4e16c chore(integrations): send the ADK client label on registry API requests
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 967455221
2026-08-19 15:59:59 -07:00
Google Team Member 66908e4c61 fix: count tool call and response chars in compaction
PiperOrigin-RevId: 967437247
2026-08-19 15:20:52 -07:00
George Weale 69a3ca5e11 fix(mcp): evict idle sessions from the MCP session pool
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 967426735
2026-08-19 14:58:49 -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 2aa2b469b0 perf: run the Google credential refresh off the event loop
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 967420389
2026-08-19 14:46:52 -07:00
George Weale 5b59139e0e fix(workflow): resume node auth from the node's own auth config
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 967419219
2026-08-19 14:44:21 -07:00
George Weale 3f9e6bec37 fix: sanitize state deltas before writing them to JSON state columns
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 967414738
2026-08-19 14:37:13 -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
George Weale 51231cd4ac perf: key the Pub/Sub publisher cache on its options' value
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 967414460
2026-08-19 14:35:16 -07:00
George Weale 94475c9a76 fix(artifacts): Publish file artifact versions atomically
FileArtifactService chose the next version by listing the versions directory
and then created that directory as a separate step. Two saves racing on the
same artifact read the same version list, and the loser's mkdir raised
FileExistsError. An interrupted save was worse: the version directory existed
but held no payload, and because version discovery counts any integer-named
directory, that empty directory became the latest version and load_artifact
returned None instead of the previous good content.

A save now reserves a version by creating a hidden `.{version}.pending`
directory, which mkdir makes atomic and mutually exclusive, writes the payload
and metadata inside it, and publishes the result with a single os.replace onto
`versions/{version}`. Version discovery ignores the staging directories, so a
version becomes visible only once it is complete, and a save that raises
removes its own staging directory. A save whose version list went stale
re-checks before publishing and takes the next free version instead, so
os.replace always lands on a name that does not exist. That last part matters
on Windows, where os.replace maps to MoveFileEx and reports an error when the
destination names an existing directory.

The guarantee is scoped to process-level faults. An exception or a signal never
publishes a partial version, but nothing is fsynced, so a power loss can still
make the rename durable ahead of the file contents and expose an empty
`versions/{version}`. Full durability would need explicit fsyncs and is not
addressed here, and version directories that earlier releases already left
empty are not repaired.

Behavior change: published version numbers are no longer guaranteed to be
contiguous. A reservation abandoned by a signal or a host failure leaves its
`.{version}.pending` directory behind; that directory is never read and never
published, but it holds its version number permanently. There is deliberately
no in-process sweep, because nothing distinguishes an abandoned reservation
from a slow concurrent write, so reclaiming those directories is left to the
operator. The class-level layout comment records the staging directory and the
non-contiguity.

The public artifact-service API and the completed on-disk version layout are
unchanged.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 967413806
2026-08-19 14:33:45 -07:00
Liang Wu 7616c78a1e fix: trigger Gemini 3.x Live response after sending conversation history
Gemini 3.x Live does not begin generating from `send_client_content` alone; it
starts only once it receives realtime input. As a result, seeding a new live
connection with prior conversation history left the model silent until the user
spoke again. This is most visible right after an agent transfer, where the
sub-agent is expected to reply immediately.

`GeminiLlmConnection.send_history` now follows the replayed history with a
minimal placeholder realtime input for Gemini 3.x Live models, gated on the
history ending with a user turn (the same condition that sets `turn_complete`).
Histories that end on a model turn still leave the model waiting for new user
input, preserving the documented `send_history` behavior. Other model families
are unaffected.

Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 967411170
2026-08-19 14:28:55 -07:00
Chris Kinzel bb86bdd737 fix: prevent contextvars leak across async generators
Merge https://github.com/google/adk-python/pull/5725

Fixes #5722

PiperOrigin-RevId: 967359464
2026-08-19 12:53:29 -07:00
Ashutosh0x 8d2f2779e6 fix: validate SQL identifiers in Spanner search tool
Merge https://github.com/google/adk-python/pull/5952

Fixes #5913

PiperOrigin-RevId: 967358801
2026-08-19 12:52:20 -07:00
Kathy Wu 8989aeadce fix(auth): take the auth scheme from the request, not the client's response
The auth preprocessor built the token exchange out of whatever the client echoed
back, including the `auth_scheme`. That scheme names the token endpoint the
exchange posts to, so a client could point it at a server it controls. It also
honoured responses for function call IDs the session never issued, where there
is no request to check them against at all.

Both now come from the `adk_request_credential` call this server issued. A
response with no matching call is dropped.

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 967358755
2026-08-19 12:51:21 -07:00
Gaurav Gandhi 023f45c3e5 fix: resolve NameError in legacy create-eval-set route
Merge https://github.com/google/adk-python/pull/6681

PiperOrigin-RevId: 967357331
2026-08-19 12:49:41 -07:00
Anas Khan 1ed8d48620 fix: guard against Content with no parts in _content_to_message_param
Merge https://github.com/google/adk-python/pull/6312

Avoid raising TypeError when types.Content has parts=None or parts=[] in LiteLLM adapter.

PiperOrigin-RevId: 967357294
2026-08-19 12:48:40 -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
aryanpatel2121 0dbb88c7c4 feat: parallelize LLM-as-judge evaluation using asyncio.gather()
Continuous Integration / Pre-commit Linter (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
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 / 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
Merge https://github.com/google/adk-python/pull/3960

This PR improves the performance of `LlmAsJudge.evaluate_invocations()` by
running LLM evaluation calls concurrently instead of executing them one-by-one.

PiperOrigin-RevId: 967276232
2026-08-19 10:14:10 -07:00
Google Team Member d0b33a0569 fix: cache read write token counts in LiteLLM and Anthropic models
Extract cache creation (write) tokens from LiteLLM and Anthropic model
usage metadata and map them to the GenerateContentResponseUsageMetadata
object. This ensures they are recorded in telemetry, allowing correct cost
calculations for prompt caching with providers like Bedrock.

Close #5835

PiperOrigin-RevId: 967259812
2026-08-19 09:44:47 -07:00
prasanna8585 1cd6f464e5 fix: redact credentials from generate_content_config.http_options in debug logs
Merge https://github.com/google/adk-python/pull/6546

PiperOrigin-RevId: 967026896
2026-08-19 00:16:10 -07:00
Nikhil Chaudhary ff4567df38 fix: pass file metadata tuple to Azure for PDF uploads
Merge https://github.com/google/adk-python/pull/6548

Fixes #6539

PiperOrigin-RevId: 967023375
2026-08-19 00:07:02 -07:00
Tony Coconate eaad2f83b9 fix: prevent duplicate OAuth prompts and fix tool resumption
Merge https://github.com/google/adk-python/pull/5985

PiperOrigin-RevId: 967017952
2026-08-18 23:53:38 -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 aa6a07bc97 refactor: share the credential-to-HTTP-header conversion
`McpToolset._get_auth_headers` held the only complete mapping from a resolved
`AuthCredential` to request headers: OAuth2 access tokens, HTTP bearer, HTTP
basic, other HTTP schemes, `additional_headers`, and header-located API keys.
`McpTool._get_headers` already duplicates it, and the next component that needs
to authenticate an outgoing request would be writing the third copy.

Move it to `google.adk.auth._auth_headers.build_auth_headers` and have
`McpToolset` delegate. The extracted function is the existing body with the
auth scheme passed in as an argument instead of read off `self._auth_config`.
The only differences are the text of the API-key warning and the logger it is
emitted on; no test asserts either. The existing `test_mcp_toolset_auth.py`
cases still exercise the mapping through the toolset, and the new tests cover
the function directly, including branches those cases cannot reach: a
non-bearer HTTP scheme, `additional_headers` with no usable scheme or token,
and an API key with no scheme at all.

`McpTool._get_headers` is deliberately left alone: it raises where the toolset
logs and warns on unexchanged service-account credentials, so folding it in
would not be a pure refactor.

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 966867565
2026-08-18 16:28:24 -07:00
Fnu Abdullah 26381552c0 fix: disable Windows glob expansion for CLI args
This prevents Click from expanding wildcard arguments on Windows (e.g. `*` in `--allow_origins "*"`), avoiding errors when Click attempts to parse expanded filenames as unexpected positional arguments.

Fixes #6248

PiperOrigin-RevId: 966756317
2026-08-18 12:57:19 -07:00
Jonathan Hill 2876987a78 refactor: introduce safe_json_loads helper and migrate selected callsites
This CL introduces `_json_utils.safe_json_loads` to provide a uniform ValueError
when JSON parsing fails, wrapping the underlying json.JSONDecodeError.
Initial callsites in evaluations, sessions, and some models have been migrated.

Merge https://github.com/google/adk-python/pull/5858

PiperOrigin-RevId: 966722835
2026-08-18 11:54:55 -07:00
Aarav Mittal a30858b11c feat: preserve field descriptions in set_model_response schema
Merge https://github.com/google/adk-python/pull/6715

Fixes #6707

PiperOrigin-RevId: 966698658
2026-08-18 11:15:20 -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
chelsealong deda5b30e8 fix: keep non-text static_instruction as a stable request prefix
Merge https://github.com/google/adk-python/pull/6653

Fixes #6652

PiperOrigin-RevId: 966680116
2026-08-18 10:42:54 -07:00
Asjad Abbas 42a4a5f0e7 fix: resolve Claude 5 model names in the LLM registry
Merge https://github.com/google/adk-python/pull/6558

PiperOrigin-RevId: 966679423
2026-08-18 10:41:28 -07:00
George Weale 39f43eb4c0 test(telemetry): assert spans and metrics report the same facts
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 966599365
2026-08-18 08:07:07 -07:00
Max Ind 0514657998 test(telemetry): Record every scenario under both instrumentations
ADK instruments inference itself, unless it detects
opentelemetry-instrumentation-google-genai, in which case it stands down and
that library's telemetry is what users get. Only the first was ever tested.

Every case now runs twice -- once over a MockModel with ADK's own
instrumentation, once over a real Gemini with the instrumentor and a
mocked-out google.genai SDK. The goldens stay what they were, ADK's own
recording pinned in full. How the instrumentor's recording of the same run
differs is in functional_divergences.json: one group per span, log or metric,
one entry per slot within it, with an example of what each side recorded, the
tests it turns up in, and the kind (adk_bug, otel_bug or desired_behavior)
and reason a developer owes for it. A gap that is new, or recorded without an
explanation, fails the case that produces it, so a new one cannot appear
silently.

The gaps therefore live in one reviewable file rather than smeared through
every golden. The cost is that the instrumentor's recording cannot be rebuilt
from disk: only the native path is pinned value for value, and the other is
held to having no unexplained gap against it.

functional/_divergences.py is the whole of it. It overlays the two recordings
as JSON, so it descends into JSON-valued attributes too: two payloads that
differ in one field diverge in that field rather than wholesale. Divergences
are named owner-relative -- the span, log or metric they sit on, plus the
route within it -- so one gap is one entry however deep it sits, however
often it recurs, and in however many goldens.

25 divergences, all explained. The first adk_bug among them: ADK leaves
error.type off the inference span on a failed call, reporting the failure
only on the enclosing spans and the duration metric.

The MCP scenario's server now resolves the same tool the canned conversation
calls, so one conversation drives every scenario and the MCP recording covers
a tool call arriving through MCP rather than only the tool definitions.

test_smoke.py pins the private internals the harness reaches into -- the
semconv stability cache it resets per case, the SDK call the instrumentor has
to wrap, and the MCP session manager it patches -- so an upgrade that renames
one fails there rather than as a puzzling golden diff.

Co-authored-by: Max Ind <maxind@google.com>
PiperOrigin-RevId: 966591641
2026-08-18 07:51:23 -07:00
Max Ind f25daed85e test(telemetry): Order the recorded telemetry by when it was emitted
The functional goldens sorted a span's children by name and its log records
by their own contents. That is deterministic, but it reads nothing like the
run it records: the tool call sorts before the inference that asked for it,
and a golden diff moves records around for reasons that have nothing to do
with the change under review.

The scenarios are single-threaded, so emission order is deterministic too.
Child spans are now ordered by start time and log records by their observed
timestamp, and the goldens read down the run.

Co-authored-by: Max Ind <maxind@google.com>
PiperOrigin-RevId: 966554122
2026-08-18 06:17:08 -07:00
Google Team Member 5d84905cec refactor: consolidate a2a compat well-known-type imports
Import timestamp_pb2 / struct_pb2 once at module scope on the 1.x.x path,
instead of lazily at each call site.

PiperOrigin-RevId: 966541598
2026-08-18 05:47:06 -07:00
Osamaali313 029c17b338 fix: read long-running function name from data, not metadata
Merge https://github.com/google/adk-python/pull/6296

Fixes #6295

PiperOrigin-RevId: 966470802
2026-08-18 02:36:45 -07:00
Google Team Member 6e0facf937 feat(telemetry): Expand telemetry for load_skill_resource span
PiperOrigin-RevId: 966463730
2026-08-18 02:18:42 -07:00
Google Team Member d6290a0b2e feat: guard against SQL injection vulnerabilities in BigQuery tools
This change introduces several security enhancements to BigQuery query tools:
- Strict identifier validation for table and column names to prevent injection.
- Secure escaping of string literals.
- Subquery validation using dry-run execution to ensure only SELECT statements are executed in dynamic contexts.
- Improved exception handling to distinguish between query validation errors and infrastructure failures.
- Input type validation for parameters like horizon and anomaly thresholds.
- Comprehensive parameterized test suites for security boundary cases.

PiperOrigin-RevId: 966430775
2026-08-18 00:57:14 -07:00
nikkie babb11c83c feat(eval): support custom metrics in AgentEvaluator
Merge https://github.com/google/adk-python/pull/4344

**Please ensure you have read the [contribution guide](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) before creating a pull request.**

### Link to Issue or Description of Change

**1. Link to an existing issue (if applicable):**

- Closes: #4343

**Problem:**
`AgentEvaluator.evaluate` did not register custom metrics from `EvalConfig`, so custom metrics worked in `adk eval` but not in pytest-based evals.

**Solution:**
Align `AgentEvaluator` with the CLI eval flow by registering custom metrics via a per-run metric registry and a shared default `MetricInfo` helper. The per-run registry is a fork of `DEFAULT_METRIC_EVALUATOR_REGISTRY` (new `MetricEvaluatorRegistry.fork()`), so the custom metrics declared by one eval config never leak into another run.

### Testing Plan

Add unit coverage for the registration behavior and a lightweight integration example that uses a custom metric.

**Unit Tests:**

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

```
% pytest tests/unittests/evaluation tests/unittests/cli

614 passed, 308 warnings in 12.74s
```

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

```
% pytest tests/integration/test_with_test_file.py::test_with_custom_metric

tests/integration/test_with_test_file.py .                               [100%]

1 passed, 12 warnings in 3.57s
```

### 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

This change keeps `AgentEvaluator` behavior consistent with `adk eval` while avoiding CLI-layer dependencies.

`fork()` returns an isolated copy seeded with the source registry's contents, rather than a bare `MetricEvaluatorRegistry()`. The seeding is what keeps this backwards compatible: registering an `Evaluator` subclass on `DEFAULT_METRIC_EVALUATOR_REGISTRY` is the only way to plug one in, since an eval config can only name a scoring function. Callers who do that today (including ones replacing the evaluator behind a standard metric name) would otherwise silently fall back to the stock evaluator, with no error and a different score. Covered by `test_evaluate_eval_set_keeps_evaluators_from_the_default_registry`.

Co-authored-by: Yi Liu <yiliuly@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4344 from ftnext:agent-evaluator-support-custom-metric 3c844a9817d16954c683b164a88a86a82e8f9cf1
PiperOrigin-RevId: 966416454
2026-08-18 00:17:15 -07:00
Shangjie Chen c9323d5861 fix(sessions): dedupe InMemorySessionService events by equality not id
Co-authored-by: Shangjie Chen <deanchen@google.com>
PiperOrigin-RevId: 966353096
2026-08-17 21:24:37 -07:00
chelsealong 81873e28c5 test: add test for final chunk replacing accumulated artifact
Verify that the final non-partial event of a stream replaces the artifact (append=False) because it contains the accumulated content.

PiperOrigin-RevId: 966350716
2026-08-17 21:17:26 -07:00
chelsealong caac070837 fix: skip non-agent directories in AgentLoader.list_agents()
Merge https://github.com/google/adk-python/pull/6668

PiperOrigin-RevId: 966348937
2026-08-17 21:12:06 -07:00
Aarav Mittal 989721746a fix: use OAuth2 client-credentials scheme for OpenAPI SA helpers
Merge https://github.com/google/adk-python/pull/6660

Change service-account OpenAPI helpers to return an OAuth2 client-credentials scheme so CredentialManager can perform token exchange.

Also, bypass the credential service caching for all SERVICE_ACCOUNT credentials. This ensures we don't cache exchanged tokens that cannot be refreshed, but means token exchange will run on each tool execution if the manager/exchanger is not reused.

Fixes #6656

PiperOrigin-RevId: 966305100
2026-08-17 19:00:50 -07:00