86 Commits

Author SHA1 Message Date
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
George Weale ae5118d5b2 fix: keep subpackages reachable as attributes of a lazy package
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 964141603
2026-08-13 09:53:45 -07:00
George Weale f324d1beed fix: select the mTLS endpoint only when a client certificate exists
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 963560219
2026-08-12 11:24:18 -07:00
George Weale c4575560e6 fix(tools): recognize Context | None as the context parameter
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 961440692
2026-08-08 09:27:17 -07:00
George Weale f828667ee0 fix: import jinja2 lazily so it is not a required dependency
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 961101849
2026-08-07 13:47:46 -07:00
Google Team Member 745de0ac13 feat: Stop using the obsolete Gemini 1.x / Gemini 2+ model-id check in ADK
Gemini 1.x is fully deprecated, so sorting Gemini model ids into "1.x"
and "or 2.0+" buckets no longer buys anything. Non-Gemini ids are unaffected: they still raise error.

PiperOrigin-RevId: 960655458
2026-08-06 20:15:47 -07:00
Ishaan 4a00a344cf feat: add Jinja2 templating with use_jinja2 flag
Merge https://github.com/google/adk-python/pull/6593

The existing regex-based substitution in `inject_session_state` cannot express
conditionals, loops, or filters, which limits how dynamic agent instructions
can be.

This change extracts the current regex logic into a private
`_render_with_regex` helper and adds a new private `_render_with_jinja2`
helper that sets up a Jinja2 async environment, exposes all session state
variables as top-level template variables, and provides an `artifact()`
async callable so templates can load artifact content inline.

A new `use_jinja2: bool = False` parameter is added to the public
`inject_session_state` function. When `False` (the default) the function
delegates to `_render_with_regex`, preserving full backward compatibility.
When `True`, it delegates to `_render_with_jinja2`, enabling Jinja2 syntax
such as `{{ var }}`, `{% if … %}`, `{% for … %}`, and artifact access via
`{{ artifact('name') }}`.

Unit tests covering basic variable substitution, conditionals, for-loops,
artifact loading, and undefined-variable errors are added to
`tests/unittests/utils/test_instructions_utils.py`.

Fixes #2942

PiperOrigin-RevId: 960497250
2026-08-06 14:17:39 -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 fae470f7bd fix: keep thought signatures when merging streamed text
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 960414933
2026-08-06 11:30:22 -07:00
George Weale aebb2a13b3 fix(models): keep streamed usage metadata when a later chunk reports none
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 959816931
2026-08-05 12:50:21 -07:00
George Weale d9c5a129d8 refactor: bind the tool declaration once in get_tools_info
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 957382413
2026-07-31 16:17:45 -07:00
Lucas Kang 26f3d454c7 feat: Add telemetry consent configuration endpoints and local writing utility
- Establish read_telemetry_consent and write_telemetry_consent utilities to store opt-in status locally in ~/.adk/config.json.
- Implement GET and POST '/config/telemetry' FastAPI endpoints inside dev_server.py.
- Prevent CSRF/XSRF forgery by requiring the 'x-adk-telemetry-request: true' header on all POST requests.
- Add unit tests verifying route access permissions and json persistence.

Co-authored-by: Lucas Kang <lucaskang@google.com>
PiperOrigin-RevId: 952873455
2026-07-23 11:41:01 -07:00
Haran Rajkumar 7c5008f2ba refactor(agents): move InstructionProvider alias into instructions_utils
Move the InstructionProvider type alias out of llm_agent.py into the
existing utils/instructions_utils.py module, and re-export it from
llm_agent so existing imports (and the public API) are unaffected. This
lets other agents (e.g. ManagedAgent) reuse the alias without importing
the heavy llm_agent module, and colocates it with inject_session_state,
the helper that consumes an InstructionProvider's output.

Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 951007235
2026-07-20 13:03:36 -07:00
George Weale 28c649a466 fix: strip markdown code fences before validating output_schema JSON
A model configured with both tools and an output_schema cannot be
hard-constrained to emit pure JSON, so it occasionally wraps the structured
output in a markdown code fence. Unwrap a fully-fenced payload in
validate_schema, the shared entry point for the classic, workflow, and
agent-as-tool output paths. Well-formed JSON never starts with a fence, so
this is a no-op on valid input.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 949192652
2026-07-16 15:00:16 -07:00
Charles Cheng 7006e33e98 fix: preserve non-ASCII characters in agent input
Merge https://github.com/google/adk-python/pull/6282

`input_schema` inputs containing non-Latin characters (Hebrew, Chinese, etc.) reach the LLM as `\uXXXX` escapes, which bloats prompt tokens (~6x for Hebrew) and degrades model responses, as reported in #6279.

The escaping comes from `json.dumps` being called with its default `ensure_ascii=True` on the LLM-bound input text. Two paths were affected:

- `utils/content_utils.py` - `to_user_content()` for `dict`/`list` node input (used by `workflow/_llm_agent_wrapper.py`)
- `flows/llm_flows/contents.py` – `_build_task_input_user_content()`, which rebuilds a delegated task's function-call args as the sub-agent's first user turn. This path takes priority over the wrapper fallback, so it affects the common chat/root → task sub-agent delegation case.

Both now serialize with `ensure_ascii=False`, matching how the output-schema path already serializes responses (`_output_schema_processor.py`), fixed earlier in #2936/#2937.

Fixes #6279
Closes #6282

Co-authored-by: Jason Zhang <jasoncz@google.com>
PiperOrigin-RevId: 947866027
2026-07-14 13:17:09 -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 6d13a6d29e refactor(utils): extract shared to_user_content helper
Move the node-input-to-user-Content coercion out of
`workflow/_llm_agent_wrapper.py` into `utils/content_utils.py`
(`to_user_content`) so other node implementations can reuse it. The wrapper and
ManagedAgent now call the shared helper; behavior is unchanged. Adds unit tests
for the helper.

Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 945912127
2026-07-10 15:12:17 -07:00
Haran Rajkumar 446d7a68cc chore(agents): send ADK tracking headers on ManagedAgent's genai client
ManagedAgent talks to the Managed Agents / Interactions API directly and,
unlike LlmAgent/Gemini, built its google.genai Client without ADK tracking
headers, so its traffic was not attributable to `google-adk` in Google's
usage pipeline. Set the x-goog-api-client / user-agent tracking headers on
the Client that ManagedAgent constructs (both the enterprise/Vertex and
developer-API backends), giving parity with models/google_llm.py.

Adds a get_tracking_http_options() helper to
utils/_google_client_headers.py. A caller-injected api_client is left
untouched. This change tags at client construction only; per-request header
propagation is a separate follow-up.

Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 945766856
2026-07-10 10:07:31 -07:00
Shangjie Chen 6f66814e19 feat: Add strict input schema validation for LlmAgent workflow nodes
In preparation for natively supporting task-mode agents as Workflow nodes, we need to enforce input boundaries at the wrapper level. Currently, bad `node_input` bypasses validation and generates invalid JSON strings for the LLM.

Co-authored-by: Shangjie Chen <deanchen@google.com>
PiperOrigin-RevId: 944894496
2026-07-08 22:14:25 -07:00
Xuan Yang 3466586bab feat: Add mTLS support to Google API tools
Co-authored-by: Xuan Yang <xygoogle@google.com>
PiperOrigin-RevId: 941350837
2026-07-01 16:20:39 -07:00
George Weale ffe41f050c fix: use mTLS endpoint for Google OAuth2 token requests
When a client certificate is configured, OAuth2 token exchange and refresh
now present the certificate and target the *.mtls.googleapis.com endpoint so
Context-Aware Access / token binding is honored. Gated to *.googleapis.com
token endpoints with a client cert available; third-party providers and
non-cert environments are unchanged.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 941319195
2026-07-01 15:13:05 -07:00
George Weale 6aad10df0e feat: expose SKIP_THOUGHT_SIGNATURE_VALIDATOR constant
Gemini thinking models require a thought_signature on generated parts, and the
backend rejects replayed parts that lack one. Callers who synthesize
conversation history must set b'skip_thought_signature_validator' on the
fabricated part to bypass validation, and several ADK consumers hardcode that
byte-string independently. Expose it once so they can depend on a single
constant.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 941277709
2026-07-01 13:53:51 -07:00
Kathy Wu 8aff5141e3 fix: Update custom gemini llm connection logic to be used for all 3_x models, not just 3.1
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 940680554
2026-06-30 15:11:07 -07:00
George Weale 59970b6109 perf: remove state injection when instruction has no placeholders
inject_session_state runs an async regex substitution over the
instruction on every LLM call to fill {state} and {artifact.x}
placeholders. The pattern ({+[^{}]*}+) requires a '{', so a template
that contains no '{' can never match and is returned unchanged.

Static instructions with no placeholders are the common case, so
short-circuit before the scan and skip the regex pass and the
per-call async substitution setup. Behavior is unchanged.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 940535088
2026-06-30 10:43:05 -07:00
Haran Rajkumar f11d19d25b feat(tools): resolve built-in tools for managed-agent requests
Add an is_managed_agent flag to LlmRequest so the google_search and url_context
built-in tools resolve their server-side config even when no Gemini model is
set. The flag defaults to False, so existing flows are unchanged.

Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 939997610
2026-06-29 13:23:51 -07:00
George Weale 932a9b5615 fix: surface error for empty STOP model turn in non-streaming mode
An empty model turn (finish_reason=STOP with no content parts) was silently
becoming an empty final response. The non-streaming flow now surfaces it as a
MODEL_RETURNED_NO_CONTENT error event. Detection lives in the flow and only in
non-streaming mode, not in LlmResponse.create, so streaming consumers that
batch parts across chunks (where a terminal finish-only chunk legitimately
follows earlier content) are unaffected.

Close #5631

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 939915020
2026-06-29 10:46:19 -07:00
George Weale 3c7d65a59e chore: drop GitHub issue links from test docstrings
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 938669352
2026-06-26 10:50:23 -07:00
Xuan Yang 1b030dc82d chore: mark mtls_utils.py as private
Co-authored-by: Xuan Yang <xygoogle@google.com>
PiperOrigin-RevId: 937487133
2026-06-24 12:16:10 -07:00
Google Team Member 62b9700737 fix: implement dynamic mtls endpoint resolution for parameter manager
Replace hardcoded regional URLs with a helper function that prioritizes
.mtls. endpoints when client certificates are present and enabled,
fulfilling mTLS/CAA requirements.

PiperOrigin-RevId: 937310286
2026-06-24 06:55:42 -07:00
kenny 816a87f356 feat: Support provider-prefixed Gemini model IDs
Merge https://github.com/google/adk-python/pull/5555

## Summary

- Extract Gemini model names from LiteLLM-compatible provider-prefixed IDs such as `gemini/gemini-2.5-flash`, `vertex_ai/gemini-2.5-flash`, and `openrouter/google/gemini-2.5-pro:online`
- Keep malformed Vertex `projects/...` paths from being treated as valid Gemini IDs
- Add an OpenRouter-through-LiteLLM sample showing `OPENROUTER_API_KEY`, `api_base`, and `openrouter/...` model usage

## Why

ADK's Gemini tool checks already normalize native model IDs and Vertex/Apigee paths, but provider-prefixed LiteLLM model IDs were left as-is. That made Google Search reject routed Gemini IDs even when the underlying model name was Gemini. This updates the shared model-name utility so existing Gemini classification paths work for provider-prefixed IDs without special-casing one provider.

Refs #2709.

## Tests

- `uv run --extra test pytest tests/unittests/utils/test_model_name_utils.py tests/unittests/tools/test_google_search_tool.py`
- `uv run --extra dev pyink --check --diff --config pyproject.toml src/google/adk/utils/model_name_utils.py tests/unittests/utils/test_model_name_utils.py tests/unittests/tools/test_google_search_tool.py contributing/samples/hello_world_openrouter/agent.py contributing/samples/hello_world_openrouter/__init__.py`
- `uv run --extra test python -m py_compile contributing/samples/hello_world_openrouter/agent.py`

Co-authored-by: George Weale <gweale@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5555 from kenrogers:codex/provider-prefixed-gemini-models 60234b4f35c4b28a2c43ce217b1d29a12d75415a
PiperOrigin-RevId: 936889403
2026-06-23 14:22:17 -07:00
Shangjie Chen 59fe9b3bb8 fix: Rollback instruction util refactoring as its breaking internal customers
Co-authored-by: Shangjie Chen <deanchen@google.com>
PiperOrigin-RevId: 935006608
2026-06-19 12:04:50 -07:00
Bo Yang 9ecbaed5d6 fix: Fix regression in instructions_utils placeholder matching
Co-authored-by: Bo Yang <ybo@google.com>
PiperOrigin-RevId: 934602575
2026-06-18 16:24:33 -07:00
Bo Yang 20ba01c2ca fix: Fix instructions_utils matching invalid nested paths
Co-authored-by: Bo Yang <ybo@google.com>
PiperOrigin-RevId: 934582581
2026-06-18 15:40:09 -07:00
Jainish 94c43a269d feat(utils): Add support for nested state access in template injection
Merge https://github.com/google/adk-python/pull/3673

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

- Closes: #575
- Solves: https://github.com/google/adk-python-community/issues/6

**2. Or, if no issue exists, describe the change:**

**Problem:**
Previously, `inject_session_state()` only supported flat state access (e.g., `{user_name}`), preventing users from accessing nested properties within state objects. This limitation forced developers to either flatten their state structure or manually handle template replacement, reducing code readability and flexibility when working with complex, hierarchical state structures.

**Solution:**
Added support for nested state access in template injection using dot notation with optional chaining. The implementation adds a `_get_nested_value()` helper function that:
- Traverses dot-separated paths through nested dictionaries and objects
- Supports both dictionary access (`__getitem__`) and attribute access (`getattr`)
- Handles optional chaining with `?` operator for safe navigation
- Returns empty strings for None values or missing optional paths
- Raises `KeyError` for missing required paths
- Maintains compatibility with existing prefixed state variables (app:, user:, temp:)

This solution was chosen because it:
- Maintains backward compatibility with existing flat state access
- Follows common patterns from JavaScript/TypeScript (optional chaining)
- Provides clear error messages for debugging
- Works seamlessly with both dictionary-based and object-based state

### Testing Plan

**Unit Tests:**

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

**Summary of pytest results:**
```bash
$ uv run pytest ./tests/unittests/utils/test_instructions_utils.py -v
OUT

=========================================================================================================================================== test session starts ============================================================================================================================================
platform darwin -- Python 3.11.13, pytest-9.0.1, pluggy-1.6.0 -- /Users/jainish/os/adk-python/.venv/bin/python3
cachedir: .pytest_cache
rootdir: /Users/jainish/os/adk-python
configfile: pyproject.toml
plugins: mock-3.15.1, langsmith-0.4.29, xdist-3.8.0, anyio-4.10.0, asyncio-1.3.0
asyncio: mode=Mode.AUTO, debug=False, asyncio_default_fixture_loop_scope=function, asyncio_default_test_loop_scope=function
collected 27 items

tests/unittests/utils/test_instructions_utils.py::test_inject_session_state PASSED                                                                                                                                                                                                                   [  3%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_artifact PASSED                                                                                                                                                                                                     [  7%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_optional_state PASSED                                                                                                                                                                                               [ 11%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_missing_state_raises_key_error PASSED                                                                                                                                                                               [ 14%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_missing_artifact_raises_key_error PASSED                                                                                                                                                                            [ 18%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_invalid_state_name_returns_original PASSED                                                                                                                                                                          [ 22%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_invalid_prefix_state_name_returns_original PASSED                                                                                                                                                                   [ 25%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_valid_prefix_state PASSED                                                                                                                                                                                           [ 29%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_multiple_variables_and_artifacts PASSED                                                                                                                                                                             [ 33%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_empty_artifact_name_raises_key_error PASSED                                                                                                                                                                         [ 37%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_artifact_service_not_initialized_raises_value_error PASSED                                                                                                                                                               [ 40%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_optional_missing_artifact_returns_empty PASSED                                                                                                                                                                      [ 44%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_none_state_value_returns_empty PASSED                                                                                                                                                                               [ 48%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_optional_missing_state_returns_empty PASSED                                                                                                                                                                         [ 51%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_nested_dict_access PASSED                                                                                                                                                                                           [ 55%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_deep_nested_access PASSED                                                                                                                                                                                           [ 59%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_optional_nested_access_existing PASSED                                                                                                                                                                              [ 62%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_optional_nested_access_missing PASSED                                                                                                                                                                               [ 66%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_optional_nested_missing_root PASSED                                                                                                                                                                                 [ 70%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_nested_none_value PASSED                                                                                                                                                                                            [ 74%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_optional_nested_none_value PASSED                                                                                                                                                                                   [ 77%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_missing_nested_key_raises_error PASSED                                                                                                                                                                              [ 81%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_required_parent_missing_raises_error PASSED                                                                                                                                                                         [ 85%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_nested_and_prefixed_state PASSED                                                                                                                                                                                    [ 88%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_mixed_nested_and_flat_state PASSED                                                                                                                                                                                  [ 92%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_numeric_nested_values PASSED                                                                                                                                                                                        [ 96%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_nested_object_attribute_access PASSED                                                                                                                                                                               [100%]

======================== 27 passed in 0.89s ==========================
```

Added 12 comprehensive test cases covering:

- Basic and deep nested dictionary access
- Optional chaining with existing and missing values
- None value handling in nested paths
- Error handling for missing required keys
- Prefixed state variables with nesting (app:, user:, temp:)
- Mixed nested and flat state access patterns
- Numeric nested values
- Object attribute access vs dictionary access

**Manual End-to-End (E2E) Tests:** Created a sample agent to demonstrate the feature (located at `contributing/samples/nested_state_agent/`, not included in this PR). Setup:

```bash
cd contributing/samples/nested_state_agent
adk run .
```

Agent code:

```python3
import logging

from google.adk.agents import Agent
from google.adk.agents.callback_context import CallbackContext
from google.adk.agents.readonly_context import ReadonlyContext
from google.adk.utils.instructions_utils import inject_session_state

def inject_nested_state(callback_context: CallbackContext):
  callback_context.state["user"] = {
      "name": "John",
      "profile": {"age": 24, "role": "Software Engineer"},
  }
  logging.info("State populated with nested user object.")

async def build_instruction(readonly_context: ReadonlyContext) -> str:
  template = (
      "Current user is {user?.name} and {user?.profile?.role}. Please greet"
      " them by name and designation."
  )
  return await inject_session_state(template, readonly_context)

root_agent = Agent(
    name="nested_state_agent",
    model="gemini-2.0-flash-lite",
    instruction=build_instruction,
    before_agent_callback=[inject_nested_state],
)
```

**Expected behavior:**

- Agent receives instruction: "Current user is John and Software Engineer. Please greet them by name and designation."
- Agent responds with greeting including the user's name and role
- Missing fields with optional chaining (?) return empty strings instead of raising errors

**Actual output:**
```
INFO: State populated with nested user object.
Agent: Hello John, Software Engineer! How can I help you today?
```

 **Result:** Nested state values correctly injected into instruction template

#### Checklist

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

**Additional context**

**Note:** This PR re-implements the solution for issue #575. A previous implementation existed but was not merged due to merge conflicts. This is a fresh implementation with the same functionality. Feature highlights:

-  Backward compatible with existing flat state access
-  Supports deeply nested structures: {user.profile.settings.theme}
-  Safe navigation with ?: {user?.profile?.department?} returns "" if missing
-  Works with both dict and object attributes
-  Compatible with prefixed state: {app:config.api.endpoint}
-  Clear error messages for debugging required fields

**Files changed:**

- src/google/adk/utils/instructions_utils.py - Core implementation (+92 lines)
- tests/unittests/utils/test_instructions_utils.py - Test coverage (+278 lines)

---

**Key improvements made:**
1.  Followed the exact template structure with all required sections
2.  Filled in all checkboxes appropriately
3.  Included actual pytest output summary as requested
4.  Provided clear E2E testing instructions with expected vs actual output
5.  Added context about this being a re-implementation
6.  Used proper markdown formatting throughout
7.  Kept your example code but formatted it better within the E2E section
8.  Made the testing plan more detailed and actionable

Co-authored-by: Bo Yang <ybo@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/3673 from Jainish-S:feat/575-nested-state-template 2fe5321631e63c1dd753e71933e85eb6679969bc
PiperOrigin-RevId: 933958647
2026-06-17 15:33:32 -07:00
Google Team Member ff95d2f712 fix(models): surface error when model returns STOP with empty content
Merge https://github.com/google/adk-python/pull/5636

Tighten LlmResponse.create() so a Gemini candidate with empty parts and finish_reason=STOP no longer passes through as a successful empty response. It now routes to the error branch with error_code='MODEL_RETURNED_NO_CONTENT' and a descriptive error_message, so callers see an actionable error event instead of a silent empty final agent output. Reproduces against gemini-2.5-flash-lite when the second turn after a tool call returns zero output tokens.

Also broadens the skip-empty guard in BaseLlmFlow._postprocess_async to treat Content(parts=[]) as no-content (defense in depth) and updates the two existing tests that codified the old behavior.

**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: #5631

**2. Or, if no issue exists, describe the change:**

**Problem:**

With `gemini-2.5-flash-lite` and an `LlmAgent` that calls a tool, the run can sometimes terminate with `final_output: ""`.

The reported flow is:

1. The model returns a `function_call`, such as a `python_executor` tool call.
2. ADK executes the tool successfully and emits the function-response event.
3. The follow-up model response returns `Content(role="model", parts=[])` with `finish_reason=STOP` and zero output tokens.
4. ADK treats that empty model response as the final event, causing the agent's final output to become an empty string.

This happened because `LlmResponse.create()` accepted `finish_reason=STOP` as a successful response even when `content.parts` was empty. In addition, the skip-empty guard in `BaseLlmFlow._postprocess_async` only checked whether `llm_response.content` existed, so a `Content` object with `parts=[]` could still pass through as a final response.

**Solution:**

This PR tightens `LlmResponse.create()` so a Gemini candidate with empty parts and `finish_reason=STOP` no longer passes through as a successful empty response.

Instead, it routes to the error branch with:

- `error_code="MODEL_RETURNED_NO_CONTENT"`
- a descriptive `error_message`

This gives callers an actionable error event instead of a silent empty final agent output.

This PR also broadens the skip-empty guard in `BaseLlmFlow._postprocess_async` to treat `Content(parts=[])` as no content unless an error is present. This acts as defense in depth and prevents empty content objects from being emitted as meaningful final responses.

This approach was preferred over adding retry behavior because it keeps the change small, avoids extra latency/cost, and surfaces the underlying model behavior clearly to callers. Non-`STOP` empty responses, such as `MAX_TOKENS` or `SAFETY`, continue to preserve their existing `finish_reason` as the error code.

### Testing Plan

**Unit Tests:**

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

Added/updated coverage includes:

- `LlmResponse.create()` returns `error_code="MODEL_RETURNED_NO_CONTENT"` when a candidate has `finish_reason=STOP` with empty parts.
- `LlmResponse.create()` returns the same no-content error when candidate content is missing with `finish_reason=STOP`.
- Non-empty content with `finish_reason=STOP` still succeeds.
- Non-`STOP` empty responses preserve their existing finish reason as the error code.
- `BaseLlmFlow` surfaces an error event for the post-tool empty response case instead of emitting a silent empty final event.
- Existing tests that codified the old empty-response behavior were updated.

Passed locally:

```bash
pytest tests/unittests/models/test_llm_response.py \
  tests/unittests/flows/llm_flows/test_base_llm_flow.py \
  tests/unittests/utils/test_streaming_utils.py -q

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

_Please include a summary of passed `pytest` results._

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

_Please provide instructions on how to manually test your changes, including any
necessary setup or configuration. Please provide logs or screenshots to help
reviewers better understand the fix._

The original issue was reproduced from the reported model response shape, where the second model turn after a successful tool call returned zero output tokens with finish_reason=STOP and empty content.parts.
This PR verifies the behavior with unit-level regression coverage instead of relying on a live model call, since the original model behavior is nondeterministic.
Manual reproduction recipe matching the original report:
Define an LlmAgent using gemini-2.5-flash-lite, a python_executor-style tool, functionCallingConfig.mode=AUTO, and automatic function calling enabled.
Send a HumanEval-style Python code-completion prompt.
When the second model turn returns empty parts with finish_reason=STOP, ADK should now surface error_code="MODEL_RETURNED_NO_CONTENT" with a non-empty error message instead of silently returning final_output: "".

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

_Add any other context or screenshots about the feature request here._
The originally reported response shape:

```json
{
  "role": "model",
  "text": "",
  "content": { "parts": [], "role": "model" },
  "raw_response": {
    "finish_reason": "STOP",
    "usage_metadata": { "candidates_token_count": 0 }
  }
}

PiperOrigin-RevId: 933348446
2026-06-16 16:00:59 -07:00
George Weale 423cd28c92 fix(models): surface error when model returns STOP with empty content
Merge https://github.com/google/adk-python/pull/5636

Tighten LlmResponse.create() so a Gemini candidate with empty parts and finish_reason=STOP no longer passes through as a successful empty response. It now routes to the error branch with error_code='MODEL_RETURNED_NO_CONTENT' and a descriptive error_message, so callers see an actionable error event instead of a silent empty final agent output. Reproduces against gemini-2.5-flash-lite when the second turn after a tool call returns zero output tokens.

Also broadens the skip-empty guard in BaseLlmFlow._postprocess_async to treat Content(parts=[]) as no-content (defense in depth) and updates the two existing tests that codified the old behavior.

**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: #5631

**2. Or, if no issue exists, describe the change:**

**Problem:**

With `gemini-2.5-flash-lite` and an `LlmAgent` that calls a tool, the run can sometimes terminate with `final_output: ""`.

The reported flow is:

1. The model returns a `function_call`, such as a `python_executor` tool call.
2. ADK executes the tool successfully and emits the function-response event.
3. The follow-up model response returns `Content(role="model", parts=[])` with `finish_reason=STOP` and zero output tokens.
4. ADK treats that empty model response as the final event, causing the agent's final output to become an empty string.

This happened because `LlmResponse.create()` accepted `finish_reason=STOP` as a successful response even when `content.parts` was empty. In addition, the skip-empty guard in `BaseLlmFlow._postprocess_async` only checked whether `llm_response.content` existed, so a `Content` object with `parts=[]` could still pass through as a final response.

**Solution:**

This PR tightens `LlmResponse.create()` so a Gemini candidate with empty parts and `finish_reason=STOP` no longer passes through as a successful empty response.

Instead, it routes to the error branch with:

- `error_code="MODEL_RETURNED_NO_CONTENT"`
- a descriptive `error_message`

This gives callers an actionable error event instead of a silent empty final agent output.

This PR also broadens the skip-empty guard in `BaseLlmFlow._postprocess_async` to treat `Content(parts=[])` as no content unless an error is present. This acts as defense in depth and prevents empty content objects from being emitted as meaningful final responses.

This approach was preferred over adding retry behavior because it keeps the change small, avoids extra latency/cost, and surfaces the underlying model behavior clearly to callers. Non-`STOP` empty responses, such as `MAX_TOKENS` or `SAFETY`, continue to preserve their existing `finish_reason` as the error code.

### Testing Plan

**Unit Tests:**

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

Added/updated coverage includes:

- `LlmResponse.create()` returns `error_code="MODEL_RETURNED_NO_CONTENT"` when a candidate has `finish_reason=STOP` with empty parts.
- `LlmResponse.create()` returns the same no-content error when candidate content is missing with `finish_reason=STOP`.
- Non-empty content with `finish_reason=STOP` still succeeds.
- Non-`STOP` empty responses preserve their existing finish reason as the error code.
- `BaseLlmFlow` surfaces an error event for the post-tool empty response case instead of emitting a silent empty final event.
- Existing tests that codified the old empty-response behavior were updated.

Passed locally:

```bash
pytest tests/unittests/models/test_llm_response.py \
  tests/unittests/flows/llm_flows/test_base_llm_flow.py \
  tests/unittests/utils/test_streaming_utils.py -q

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

_Please include a summary of passed `pytest` results._

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

_Please provide instructions on how to manually test your changes, including any
necessary setup or configuration. Please provide logs or screenshots to help
reviewers better understand the fix._

The original issue was reproduced from the reported model response shape, where the second model turn after a successful tool call returned zero output tokens with finish_reason=STOP and empty content.parts.
This PR verifies the behavior with unit-level regression coverage instead of relying on a live model call, since the original model behavior is nondeterministic.
Manual reproduction recipe matching the original report:
Define an LlmAgent using gemini-2.5-flash-lite, a python_executor-style tool, functionCallingConfig.mode=AUTO, and automatic function calling enabled.
Send a HumanEval-style Python code-completion prompt.
When the second model turn returns empty parts with finish_reason=STOP, ADK should now surface error_code="MODEL_RETURNED_NO_CONTENT" with a non-empty error message instead of silently returning final_output: "".

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

_Add any other context or screenshots about the feature request here._
The originally reported response shape:

```json
{
  "role": "model",
  "text": "",
  "content": { "parts": [], "role": "model" },
  "raw_response": {
    "finish_reason": "STOP",
    "usage_metadata": { "candidates_token_count": 0 }
  }
}

Co-authored-by: George Weale <gweale@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5636 from Oppong08:fix-empty-final-output-after-tool-call 545b9699ff711af32e9574d0b5f6fbabf2d12f4d
PiperOrigin-RevId: 933249937
2026-06-16 12:55:58 -07:00
Wei (Jack) Sun 87538d2350 test: Suppress experimental feature warnings in unit tests
Merge https://github.com/google/adk-python/pull/6087

## Summary

- Unit tests instantiate many `@experimental`-decorated classes, flooding test output with `[EXPERIMENTAL]` `UserWarning` messages.
- Set `ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS=true` in `tests/unittests/conftest.py` (alongside the existing `ADK_ALLOW_WIP_FEATURES`) to silence them session-wide.
- The four `*_no_parens` / `*_empty_parens` decorator tests that assert the warning fires now `monkeypatch.delenv` the suppress var first, matching the pattern already used by their sibling tests, so they remain valid under the new default.

## Test plan

- [x] `uv run pytest tests/unittests/utils/test_feature_decorator.py tests/unittests/features/test_feature_decorator.py` passes
- [x] Verified a real `@experimental` class (`InMemoryCredentialService`) emits 0 `[EXPERIMENTAL]` warnings with the var set, 2 without

Co-authored-by: Wei Sun (Jack) <weisun@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/6087 from google:test/suppress-experimental-warnings 23c43d9188fe33529b41d08325eb1c41ac5dddd6
PiperOrigin-RevId: 930824931
2026-06-11 17:27:46 -07:00
Stephen Allen 463040fdca feat(live): support Live API translation config in RunConfig
Merge https://github.com/google/adk-python/pull/6083

### Link to Issue or Description of Change

Add support for live translation with the Live API -> https://ai.google.dev/gemini-api/docs/live-api/live-translate

### Testing Plan

**Unit Tests:**

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

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

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/6083 from allen-stephen:feat/enable-live-translate 6a0f5b7525bfc985f51b25e2d7f12a55ca14b75d
PiperOrigin-RevId: 930782786
2026-06-11 15:52:05 -07:00
Haran Rajkumar 4e85e9c335 feat(utils): add GOOGLE_GENAI_USE_ENTERPRISE env var with deprecation fallback
Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 929280981
2026-06-09 10:41:54 -07:00
Google Team Member 342b59d55c fix: propagate model_version and other metadata in streaming responses
PiperOrigin-RevId: 928881287
2026-06-08 18:39:22 -07:00
Shangjie Chen 0337d19c47 chore: sync Google internal changes to GitHub (#6022) 2026-06-08 15:11:24 -07:00
Xuan Yang cd81f7bde9 fix(streaming): Ensure final partial=False frame is always yielded
The `StreamingResponseAggregator.close()` method previously returned `None` if it didn't accumulate text or parts, such as for safety blocks or pure function calls. This caused clients (e.g., Vertex AI Reasoning Engine) to hang indefinitely waiting for a `partial=False` termination frame, and caused loops to break prematurely.

This fix ensures `close()` always returns a final `LlmResponse(partial=False)` as long as a response exists, carrying over any `error_code`, `error_message`, and `usage_metadata`, regardless of whether `PROGRESSIVE_SSE_STREAMING` is enabled. Added parameterized unit tests to verify behavior across both streaming modes.

Fixes #3754

Change-Id: I40d3b4a14cf36e830454d1a0432786de2e8aa3c3
2026-06-04 14:46:07 -07:00
Sasha Sobran 162279358c chore: switch main to v2.0.0 GA (transition to v2)
Co-authored-by: Bo Yang <ybo@google.com>
Co-authored-by: Wei Sun (Jack) <weisun@google.com>
Co-authored-by: George Weale <gweale@google.com>
Co-authored-by: Swapnil Agarwal <swapnilag@google.com>
Co-authored-by: Xuan Yang <xygoogle@google.com>
Co-authored-by: Shangjie Chen <deanchen@google.com>
Co-authored-by: Yifan Wang <wanyif@google.com>
Co-authored-by: Kathy Wu <wukathy@google.com>
2026-05-19 02:01:33 +00:00
George Weale ec54bd439e perf(utils): cache find_context_parameter introspection
Adds @functools.lru_cache to find_context_parameter so the inspect.signature
+ typing.get_type_hints lookup runs once per function, not on every MCP
confirmation callback or declaration build. No public surface change.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 916204929
2026-05-15 15:14:36 -07:00
George Weale 9c5de58cfa fix(cache): handle fingerprint-only metadata in performance analyzer
The analyzer crashed on `sum([None, ...])` whenever any event had
fingerprint-only cache metadata (cache_name=None, invocations_used=None),
which happens on every session's first turn. Also fixes `cache_refreshes`
over-counting None as a unique cache instance.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 912722966
2026-05-08 15:26:12 -07:00
Wei Sun (Jack) 3117e09136 chore: further fix header-check via 2025 --> 2026
Co-authored-by: Wei Sun (Jack) <weisun@google.com>
PiperOrigin-RevId: 911737937
2026-05-06 22:16:55 -07:00
Google Team Member b58ce57b67 chore: Enable gemini EAP models for gemini-builtin tools
PiperOrigin-RevId: 911554133
2026-05-06 14:35:42 -07:00
Google Team Member ed8b31ce5f chore: migrate from gemini-1.* and gemini-2.0* to gemini-2.5-*
`gemini-1.*` and `gemini-2.0*` models are respectively deprecated and scheduled for shutdown on June 1, 2026. `gemini-2.5*` models are their successors.
No regressions in unit tests:
```
========================================================================================== 5583 passed, 2237 warnings in 84.91s (0:01:24) ===========================================================================================
```

PiperOrigin-RevId: 907663315
2026-04-29 10:34:22 -07:00
Xuan Yang fe4181718d fix: Generate IDs for FunctionCalls when processing streaming LLM responses
Close: #4609

Co-authored-by: Xuan Yang <xygoogle@google.com>
PiperOrigin-RevId: 890144644
2026-03-26 18:05:46 -07:00
Xuan Yang 22fc332c95 fix: Support resolving string annotations for find_context_parameter
Co-authored-by: Xuan Yang <xygoogle@google.com>
PiperOrigin-RevId: 884686010
2026-03-16 16:18:35 -07:00