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
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
`_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
`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
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
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
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
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
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
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
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
`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
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
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
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
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
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
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
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
Verify that the final non-partial event of a stream replaces the artifact (append=False) because it contains the accumulated content.
PiperOrigin-RevId: 966350716
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