v2.3.0
2952 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a2bc0d8522 | chore: update last-release-sha for next release v2.3.0 | ||
|
|
e4f23de5fc | chore(release/candidate): release 2.3.0 (#6150) | ||
|
|
0cb4c81492 |
fix(skills): enforce utf-8 encoding when materializing skill files on Windows
Merge https://github.com/google/adk-python/pull/5820 ### Link to Issue or Description of Change **1. Link to an existing issue (if applicable):** - Closes: #5819 - Related: #5819 **2. Or, if no issue exists, describe the change:** **Problem:** When running the ADK on Windows, executing a skill script via `_SkillScriptCodeExecutor` fails with a `UnicodeEncodeError` if the skill's resources (references, assets, or scripts) contain non-ASCII characters. The generated wrapper script writes these files without specifying an encoding, causing Windows to fall back to its system locale encoding (e.g., `cp1252`). **Solution:** By explicitly setting `encoding='utf-8'` when `mode == 'w'` in the generated wrapper script, we ensure that text files are correctly written regardless of the system's default locale encoding. Binary assets (`mode == 'wb'`) continue to be handled properly without an encoding argument. Co-authored-by: Xuan Yang <xygoogle@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5820 from mc-marcocheng:fix/skill-toolset-utf8 eecabb62d28e59dd17839f20846027d596d58551 PiperOrigin-RevId: 933844065 |
||
|
|
81b8067155 |
fix: api-registry to fetch all services
API Registry no longer supports enabling apis. To get all the apis, one needs to pass an additional filter to fetch the apis. This allows the library to fetch all available apis. Closes #5478 Co-authored-by: Haran Rajkumar <haranrk@google.com> PiperOrigin-RevId: 933840827 |
||
|
|
910e1c1321 |
fix: prevent ReDoS in code block extraction
Merge https://github.com/google/adk-python/pull/6118 ## Summary - Replace regular expression-based code block extraction with a simple and safe string-find based search. This avoids exponential backtracking (ReDoS) when processing long or repeating inputs with missing trailing delimiters. - Add unit tests to verify standard behavior and test against ReDoS vulnerability. Co-authored-by: Kathy Wu <wukathy@google.com> PiperOrigin-RevId: 933834549 |
||
|
|
5cfef0173d |
fix(eval): handle unevaluated final response v2 results
Merge https://github.com/google/adk-python/pull/5728 ## Summary Fixes a small aggregation edge case in `FinalResponseMatchV2Evaluator`: when every per-invocation result is skipped or not evaluated, the evaluator currently divides by zero while computing the overall score. ## Root Cause `aggregate_invocation_results()` filters out results whose `score` is `None` or whose `eval_status` is `NOT_EVALUATED`, but it unconditionally computes: ```python overall_score = num_valid / num_evaluated ``` If all judge samples fail to produce a usable score, `num_evaluated` remains `0` and evaluation crashes instead of returning a not-evaluated aggregate result. Other ADK evaluators handle this condition by returning `overall_score=None` and `overall_eval_status=NOT_EVALUATED`. ## Change - Return an `EvaluationResult` with `overall_score=None` and `overall_eval_status=NOT_EVALUATED` when no FinalResponseMatchV2 invocation results are evaluable. - Add a focused regression test for all-skipped/all-not-evaluated invocation results. ## Validation ```bash uv sync --extra test uv run pytest tests/unittests/evaluation/test_final_response_match_v2.py ``` Result: `18 passed, 20 warnings`. Full unit suite was not run; this patch is limited to FinalResponseMatchV2 aggregation and its targeted unit test file. Co-authored-by: Haran Rajkumar <haranrk@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5728 from pragnyanramtha:pragnyan/final-response-v2-no-eval-guard 3d5ab736d7a6ae0e411a52d387472a0dde37e2b8 PiperOrigin-RevId: 933818272 |
||
|
|
a546bcf743 |
fix(auth): handle missing client-credentials scopes safely
Merge https://github.com/google/adk-python/pull/5348 ## Summary - Normalize OAuth scopes so the client-credentials/M2M flow no longer crashes with `AttributeError: 'NoneType' object has no attribute 'keys'` when scopes are absent. - Add a regression test for the client-credentials flow with missing scopes. Fixes #5345 Co-authored-by: Haran Rajkumar <haranrk@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5348 from sqsge:codex/fix-openapi-m2m-scopes f73e34792c61015fc822b05968ab47692bd40443 PiperOrigin-RevId: 933815068 |
||
|
|
4340208b17 |
fix: remove live event buffering in runner
Keep the ADK events in their original order as they arrive. In the original code, if the output transcriptions are long, the function call and response events are only appended at the very end of transcription. If any downstream program consumes the events, they are in the wrong order. Co-authored-by: Liang Wu <wuliang@google.com> PiperOrigin-RevId: 933812997 |
||
|
|
054da5d00e |
fix(planners): keep all leading parallel function calls
Merge https://github.com/google/adk-python/pull/6141 ### Link to Issue or Description of Change No existing issue — describing the bug here. **Problem:** `PlanReActPlanner.process_planning_response` drops every parallel function call except the first when the model's response **starts** with a function call. The trailing-group collector is guarded by: ```python first_fc_part_index = -1 for i in range(len(response_parts)): if response_parts[i].function_call: ... first_fc_part_index = i break ... if first_fc_part_index > 0: # <-- bug j = first_fc_part_index + 1 while j < len(response_parts): ... ``` `first_fc_part_index` is the index of the first function call (sentinel `-1`). When the first part is a function call its index is `0`, so `> 0` is false and the loop that collects the rest of the parallel call group never runs — the first call is kept, the rest are silently dropped. Responses that begin with text (index `>= 1`) work, which is why this wasn't noticed. Gemini emitting a group of parallel function calls as the first parts of a turn is a realistic case (and is what the planner instruction encourages). **Solution:** Change the guard to `>= 0` so a leading function call is handled the same as one preceded by text. ### Testing Plan **Unit Tests:** - [x] Added `tests/unittests/planners/test_plan_re_act_planner.py`. - [x] All unit tests pass locally. `test_preserves_all_leading_parallel_function_calls` is **red on `main`** (returns only `["get_weather"]`) and **green** with this change (returns `["get_weather", "get_time"]`). A companion test confirms the leading-text case still works. ``` $ pytest tests/unittests/planners/test_plan_re_act_planner.py -q 2 passed $ pytest tests/unittests/flows/llm_flows/test_nl_planning.py -q 7 passed ``` pyink + isort clean. ### Checklist - [x] I have read the CONTRIBUTING.md document. - [x] I have performed a self-review of my own code. - [x] I have added tests that prove my fix is effective. - [x] New and existing unit tests pass locally with my changes. Co-authored-by: Yifan Wang <wanyif@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/6141 from nyxst4ck:fix/planner-leading-parallel-function-calls 7058f5b23e3d281e061d98f4b656e7f0a8ac6787 PiperOrigin-RevId: 933812571 |
||
|
|
980845103a |
fix: call to sanitize schema for complex union types
Merge https://github.com/google/adk-python/pull/5366 ### Link to Issue or Description of Change **1. Link to an existing issue (if applicable):** - Closes: [#5364](https://github.com/google/adk-python/issues/5364) **Problem:** Using python functions with dicts in its signature breaks Gemini schema. **Solution:** Sanitize schema same way it's done in [McpTool](https://github.com/google/adk-python/blob/b3e99628ee1b87b61badf56e67f8ddee15e6fe54/src/google/adk/tools/mcp_tool/mcp_tool.py#L204) ### Testing Plan **Unit Tests:** - [x] I have added or updated unit tests for my change. - [x] All unit tests pass locally. ``` > pytest ./tests/unittests ... =================================================================================== 5531 passed, 2233 warnings in 98.83s (0:01:38) =================================================================================== ``` **Manual End-to-End (E2E) Tests:** * Install changes locally instead of PyPi ``` google-adk = { path = "<path-to-fork>/adk-python", editable = true } uv sync --all-packages --group dev ... Installed 2 packages in 2ms ~ adk==0.1.0 (from file:///Users/...) - google-adk==1.31.0 + google-adk==1.31.0 (from file:///...fork/adk-python) ``` * Follow "Steps to Reproduce" from Issue link ``` The current UTC timestamp is 2026-04-17T11:46:10.953127+00:00. ``` ### 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. - [ ] Any dependent changes have been merged and published in downstream modules. ### Additional context Follow up: https://github.com/google/adk-python/pull/5000 Co-authored-by: Bo Yang <ybo@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5366 from EugeneYushin:sanitize-schema e4a88f993bdf1b9c02df87f7e4f4817729c1f311 PiperOrigin-RevId: 933812097 |
||
|
|
065f4aed46 |
fix(a2a): suppress part_metadata in Vertex AI mode
convert_a2a_part_to_genai_part unconditionally mapped A2A metadata onto genai_types.Part.part_metadata. The google-genai SDK only accepts that field in Gemini Developer API mode and raises a client-side ValueError in Vertex AI / Enterprise mode, breaking A2A sub-agent tool calls and multi-turn loops. Resolve the variant via get_google_llm_variant() and drop part_metadata for all part branches when the backend is VERTEX_AI. Native fields (thought, thought_signature) are unaffected. PiperOrigin-RevId: 933687953 |
||
|
|
b9e7fcade1 |
fix(a2a): render HITL interrupt when prompt is in a data part
A2A input/auth-required prompts sent as a DataPart became an opaque inline_data JSON blob, so no HITL function call was produced and the client rendered nothing. Extract the prompt from the data part so these tasks surface a proper HITL function call. Adds unit tests. PiperOrigin-RevId: 933586203 |
||
|
|
f0ec997bc0 |
fix(sessions): Further fixes for DatabaseSessionService
- Fix timezone inconsistency in append_event where it used local naive time for Postgres (now uses UTC naive). - Fix potential MissingGreenlet in create_session by generating UUID in Python and calling to_session before commit. - Add regression test for create_session. Co-authored-by: Shangjie Chen <deanchen@google.com> PiperOrigin-RevId: 933531685 |
||
|
|
63841c3333 |
fix(adk): propagate exceptions from run_node in standalone mode
In standalone mode (outside of a workflow), `Context.run_node` was ignoring errors in the child context and returning `None`. This change ensures that `DynamicNodeFailError` is raised on failure, aligning the behavior with workflow mode. PiperOrigin-RevId: 933504063 |
||
|
|
e7a673ccd0 |
feat(eval): expose user_simulator_config in generate_responses
Merge https://github.com/google/adk-python/pull/5733 ### Link to Issue or Description of Change **1. Link to an existing issue (if applicable):** N/A **2. Or, if no issue exists, describe the change:** **Problem:** `EvaluationGenerator.generate_responses` constructs a `UserSimulatorProvider()` with no arguments, so the LLM-backed path always runs with the default `BaseUserSimulatorConfig`. There is no way for a caller to override the user-simulation model, max-allowed invocations, or custom instructions when driving multi-turn conversations through `LlmBackedUserSimulator`. **Solution:** Add an optional `user_simulator_config` parameter to `generate_responses` and forward it to `UserSimulatorProvider(...)`. Callers can now pass an `LlmBackedUserSimulatorConfig` to customize the LLM-backed simulator. The behavior is backward compatible: - When the argument is omitted, `UserSimulatorProvider` falls back to `BaseUserSimulatorConfig()` exactly as before. - Static eval cases are unaffected: the config is ignored by `StaticUserSimulator`. ### Testing Plan **Unit Tests:** - [x] I have added or updated unit tests for my change. - [x] All unit tests pass locally. A unit test for the proposed change was added to `tests/unittests/evaluation/test_evaluation_generator.py`: `TestGenerateResponses::test_generate_responses_forwards_llm_backed_user_simulator_config` All tests pass: ``` > uv run pytest tests/unittests/ -rs ... ================================== short test summary info ================================== SKIPPED [1] tests/unittests/integrations/crewai/test_crewai_tool.py:20: Requires Python 3.10+ ================ 5770 passed, 1 skipped, 2358 warnings in 129.40s (0:02:09) ================ ``` The skipped test is not related to this change — it skips on `main` as well. **Manual End-to-End (E2E) Tests:** A reference setup lives at https://github.com/primenko-v/adk-x-mlflow (tag `pr-demo/user-simulator-config`). It loads an `LlmBackedUserSimulatorConfig` from YAML and forwards it to `EvaluationGenerator.generate_responses` via the new `user_simulator_config` parameter — see [`src/mlflow_adk/simulate.py`](https://github.com/primenko-v/adk-x-mlflow/blob/pr-demo/user-simulator-config/src/mlflow_adk/simulate.py#L74-L79). To reproduce (requires GOOGLE_CLOUD_PROJECT and ADC via `gcloud auth application-default login`): ```bash git clone --recurse-submodules --branch pr-demo/user-simulator-config \ https://github.com/primenko-v/adk-x-mlflow.git cd adk-x-mlflow cp .env.example .env # fill in GOOGLE_CLOUD_PROJECT uv sync uv run python -m mlflow_adk.simulate --no-mlflow --output-traces traces.jsonl ``` ### 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. Co-authored-by: Ankur Sharma <ankusharma@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5733 from primenko-v:propagate-user-simulator-config 24209b6b5522b93b3564cefe3f0a1c66cfe80ccf PiperOrigin-RevId: 933503403 |
||
|
|
1ac69a9033 |
feat: Add log_level option for adk run CLI
Merge https://github.com/google/adk-python/pull/3674 Co-authored-by: Xuan Yang <xygoogle@google.com> PiperOrigin-RevId: 933452646 |
||
|
|
f022307db3 |
fix: avoid UserWarning in _build_response_log when response has funct…
Merge https://github.com/google/adk-python/pull/6127 # Fix: `_build_response_log()` triggers genai SDK UserWarning on every tool call Closes #4685 ## What's the problem? Every time an ADK agent invokes a tool, the debug logger calls `_build_response_log(response)`, which includes `resp.text` in its f-string: ```python return f""" LLM Response: ... Text: {resp.text} # ← triggers UserWarning ... """ ``` The `GenerateContentResponse.text` property in the google-genai SDK raises a `UserWarning` whenever the response contains non-text parts — which is exactly the case when the model responds with a `function_call`. This means **every single tool invocation floods the log with warnings** like: ``` UserWarning: Warning: there are non-text parts in the response: ['function_call'],returning concatenated text result from text parts, check `response.parts` directly to inspect non-text parts warnings.warn( ``` Since `_build_response_log` is only called inside `if logger.isEnabledFor(logging.DEBUG)`, this hits any developer who enables debug logging — which is common when debugging agents. ## Root cause `GenerateContentResponse.text` is a convenience property that warns when mixed content is present. Accessing it in a log formatter silently poisons the log output whenever agents use tools. ## Fix Replace `resp.text` with a manual join of only the text parts from `resp.candidates`, bypassing the warning entirely: ```python # Before return f""" ... Text: {resp.text} ... """ # After — safe extraction with no warning text_parts = [] if resp.candidates: for candidate in resp.candidates: if candidate.content and candidate.content.parts: text_parts.extend( p.text for p in candidate.content.parts if p.text is not None ) text = ''.join(text_parts) return f""" ... Text: {text} ... """ ``` This produces identical output when only text parts are present, and correctly shows an empty string (rather than a warning) when the response is a function call — which is the right behavior for a debug log. ## Files changed - `src/google/adk/models/google_llm.py` — 10-line change inside `_build_response_log()`, no other logic touched Co-authored-by: Yifan Wang <wanyif@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/6127 from AdeevMardia2008:fix/response-log-text-warning 011c153ebba123f0762d140dbe5ac526c47cd3a2 PiperOrigin-RevId: 933440866 |
||
|
|
d9f189c7a3 |
fix: improve error message when beautifulsoup4/lxml not installed for load_web_page
Merge https://github.com/google/adk-python/pull/4853 ## Description The built-in `load_web_page` tool requires `beautifulsoup4` and `lxml`, which are available via the `[extensions]` optional dependency group. When a user installs `google-adk` without the `[extensions]` extra and calls `load_web_page`, they get a raw `ModuleNotFoundError: No module named 'bs4'` with no guidance on how to resolve it. This change wraps the deferred imports in a `try/except` to provide a clear, actionable error message: ``` ImportError: load_web_page requires the "beautifulsoup4" and "lxml" packages. Install them with: pip install google-adk[extensions] ``` Fixes #4852 Co-authored-by: Liang Wu <wuliang@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4853 from brucearctor:fix/load-web-page-import-error 27aa20eb54d9b408e02a502ffd9ca0825e498cf6 PiperOrigin-RevId: 933416379 |
||
|
|
06959b95ed |
fix(sessions): Prevent MissingGreenlet after append_event with asyncpg
Merges https://github.com/google/adk-python/pull/5814 Co-authored-by: Shangjie Chen <deanchen@google.com> PiperOrigin-RevId: 933406737 |
||
|
|
8f852603a4 |
fix(live): history_config rejection on Vertex/Enterprise Live sessions
Merge https://github.com/google/adk-python/pull/6035 **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 **Problem:** On the Vertex AI / Gemini Enterprise Agent Platform backend, ADK auto-injects `history_config` into the Live setup message when seeding conversation history. That backend has no `history_config` field and rejects it with `ValueError: history_config parameter is only supported in Gemini Developer API mode, not in Gemini Enterprise Agent Platform mode` **Solution:** Gate the history_config auto-injection to the Gemini Developer API backend only (`isinstance(llm, Gemini)` and `llm._api_backend == GoogleLLMVariant.GEMINI_API`). On Vertex, history is already seeded via the sanctioned `send_history` (`send_client_content`) path. ### Testing Plan **Unit Tests:** - [X] I have added or updated unit tests for my change. - [X] All unit tests pass locally. $ pytest tests/unittests/flows/llm_flows/test_base_llm_flow.py -k history_config 2 passed, 35 deselected, 4 warnings in 0.78s **Manual End-to-End (E2E) Tests:** Verified intended functionality in ADK web. ### 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 N/A COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/6035 from allen-stephen:fix/live-history-config-bug cf8e1fc054f28dc9392989a4260ed2cff78fdc01 PiperOrigin-RevId: 933402607 |
||
|
|
780b0ab159 |
fix(eval): preserve custom eval metadata
Merge https://github.com/google/adk-python/pull/5922 ## Summary - allow evaluation models to preserve caller-provided metadata fields - add a regression test covering extra fields on `SessionInput` and `EvalCase` Fixes #5906 ## To verify - `PYTHONPATH=src python -m pytest tests/unittests/evaluation/test_eval_case.py -q` - `python -m pyink --check src/google/adk/evaluation/common.py tests/unittests/evaluation/test_eval_case.py` - `python -m ruff check src/google/adk/evaluation/common.py tests/unittests/evaluation/test_eval_case.py` - `git diff --check` Co-authored-by: Shangjie Chen <deanchen@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5922 from he-yufeng:fix/eval-extra-metadata-fresh 12bfd3cd640a2df6b47e2ad17443447bd483cd1d PiperOrigin-RevId: 933397529 |
||
|
|
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 |
||
|
|
c66dc1dec4 |
docs: update llms.txt and remove build script
The machine-readable llms.txt and llms-full.txt files are no longer hosted statically in this repository. They are now automatically generated and hosted on the adk.dev documentation site. This updates the text files to redirect to the new URLs and removes the obsolete build_llms_txt.py script. Closes https://github.com/google/adk-python/issues/6108 Co-authored-by: Haran Rajkumar <haranrk@google.com> PiperOrigin-RevId: 933317323 |
||
|
|
f39d75b99e |
fix(adk): propagate isolation_scope to prevent history filtering loops
PiperOrigin-RevId: 933314484 |
||
|
|
69ecf079b3 |
fix: make DatabaseSessionService visible in API docs
Fixes #4331 Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 933310174 |
||
|
|
1ad348d6f7 |
fix: preserve function call ids for litellm models
Close #2621 Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 933306904 |
||
|
|
573f04344d |
feat(gemma4): support Gemma4 in Gemini
Merge https://github.com/google/adk-python/pull/5117 The main intent of this PR is to clarify Gemma 3 classes and add Gemma 4 sample now that Gemma 4 is released. As part of that work, this PR adds `gemma-4-*` to the allowlist for Gemini models for use in ADK. This PR: - Adds `gemma-4-*` to the allowlist for Gemini models - Updates docstrings for Gemma, Gemma3Ollama, and GemmaFunctionCallingMixin to clarify they are Gemma 3-only - Adds Gemma 4 usage guidance pointing to Gemini/LiteLlm classes - Adds hello_world_gemma4 sample using standard Gemini class - Adds header comments and READMEs to existing Gemma 3 samples - Adds registry non-collision test for Gemma 4 model strings - Updates registration comments in models/__init__.py ### Testing Plan _Please describe the tests that you ran to verify your changes. This is required for all PRs that are not small documentation or typo fixes._ **Unit Tests:** - [ X ] I have added or updated unit tests for my change. - [ X ] All unit tests pass locally. _Please include a summary of passed `pytest` results._ ``` ❯ pytest tests/unittests/models/test_google_llm.py ... tests/unittests/models/test_google_llm.py ............................................... [100%] ... =============================================================================== 47 passed, 3 warnings in 4.40s =============================================================================== ❯ pytest tests/unittests/models/test_gemma_llm.py .... tests/unittests/models/test_gemma_llm.py ...................... [100%] ===================================================================================== 22 passed in 1.05s ===================================================================================== ``` **Manual End-to-End (E2E) Tests:** Ran `adk run contributing/samples/hello_world_gemma4` with success ### 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 Co-authored-by: George Weale <gweale@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5117 from douglas-reid:gemma4-docs-clarity 9c9096103231377bee388d2df21ca878bd0dedd3 PiperOrigin-RevId: 933295498 |
||
|
|
8a294af52d |
fix: Extract grounding_metadata from Live API server_content
Merge https://github.com/google/adk-python/pull/4213 ## Summary Fixes #3542 - VertexAiSearchTool query instability where grounding metadata is intermittently missing from Live API responses. ### Root Cause The Live API's `receive()` method in `gemini_llm_connection.py` extracted various fields from Live API messages (usage_metadata, server_content, tool_call, session_resumption_update) but **never extracted `grounding_metadata`** from `server_content`. This prevented agents from accessing grounding data from Vertex AI Search, even when the backend provided it. ### Changes #### Modified `src/google/adk/models/gemini_llm_connection.py` - Added tracking variable `last_grounding_metadata` to accumulate grounding across messages. - Added tracking variable `tool_call_metadata` to handle grounding metadata for buffered tool calls correctly. - Extract `grounding_metadata` from `message.server_content.grounding_metadata` and accumulate it. - Include accumulated `grounding_metadata` in `LlmResponse` when yielding: - Content responses with parts (only if turn is not complete). - Buffered tool call responses (using `tool_call_metadata` to match the time they were received). - Turn complete responses (using current message's grounding or accumulated). - Interrupted responses. - Full text responses (via `__build_full_text_response`). - Added warning log when incomplete grounding_metadata is detected (has `retrieval_queries` but missing `grounding_chunks`). #### Updated `tests/unittests/models/test_gemini_llm_connection.py` - Fixed existing tests to: - Explicitly set `grounding_metadata = None` on mock server_content objects (in helper). - Use real `types.GroundingMetadata` instead of `autospec` mock in standalone grounding test to avoid `AttributeError` on `retrieval_queries`. - Added four new tests: - `test_receive_extracts_grounding_metadata` - verifies grounding_metadata is extracted and included in content responses. - `test_receive_grounding_metadata_reset_after_tool_call` - verifies grounding_metadata is reset after tool call. - `test_receive_grounding_metadata_accumulates_across_messages` - verifies accumulation. - `test_receive_interrupted_with_pending_text_preserves_flag` - verifies interrupted flag. ### Test Results All 50 tests pass. ### Impact This fix ensures that grounding data from Vertex AI Search is properly extracted and attached to `LlmResponse` events, allowing agents to access `event.grounding_metadata.grounding_chunks` when available. ============= Commits ============== -- cb635f04468a62487a0c5ae4ac7c862910a5d137 by Vedant Madane <vedantnm@gmail.com>: ensure grounding metadata is correctly propagated in live API - Correctly handle search results and citations in grounding metadata. - Fix metadata parsing logic in gemini_llm_connection.py. - Add unit tests for grounding metadata extraction. Signed-off-by: Vedant Madane <vedantnm@gmail.com> Co-authored-by: Shangjie Chen <deanchen@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4213 from VedantMadane:fix/live-api-grounding-metadata cb635f04468a62487a0c5ae4ac7c862910a5d137 PiperOrigin-RevId: 933291436 |
||
|
|
8b09c48f57 |
fix(tools): handle missing 'request' key in AgentTool.run_async fallb…
Merge https://github.com/google/adk-python/pull/5678 Closes #1084 (KeyError half — the AttributeError half was fixed Nov 2025) Co-authored-by: Xuan Yang <xygoogle@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5678 from phinglaspure123:fix/agent-tool-keyerror-request 67b318796e418c83ad25a650cb94cddd71bb3a0e PiperOrigin-RevId: 933288076 |
||
|
|
f9be94c624 |
fix(tools): convert image/svg+xml to text in LoadArtifactsTool
Merge https://github.com/google/adk-python/pull/5694 **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: #5693 **2. Or, if no issue exists, describe the change:** **Problem:** `_is_inline_mime_type_supported` in `LoadArtifactsTool` returns `True` for `image/svg+xml` via the `image/` prefix match at `src/google/adk/tools/load_artifacts_tool.py:32-36`, so SVG artifacts get forwarded to Gemini as inline image data. Gemini rejects every SVG MIME variant with `400 INVALID_ARGUMENT - Unsupported MIME type: image/svg+xml`, instead of being text-converted like CSV / JSON / XML are since #4028. Empirically verified against `gemini-2.5-flash` via `google-genai==1.75.0` that `image/svg+xml`, `image/svg`, `application/svg+xml` and `image/xml` are all rejected by Gemini, while `image/png/jpeg/webp/avif/gif`, `audio/*`, `video/*` and `application/pdf` are accepted. So SVG is the only sub-case under a "supported prefix" that needs special handling today. **Solution:** Add an explicit denylist for subtypes that match a supported prefix but that Gemini rejects, and route SVG through the existing text-fallback path. Same shape as the #4028 fix. `src/google/adk/tools/load_artifacts_tool.py`: - Add `_GEMINI_UNSUPPORTED_INLINE_SUBTYPES = frozenset({'image/svg+xml'})` and short-circuit `_is_inline_mime_type_supported` on a hit, so SVG falls through to `_as_safe_part_for_llm`'s text-decoding branch instead of being forwarded inline. - Add `'image/svg+xml'` to `_TEXT_LIKE_MIME_TYPES` so the fallback path utf-8 decodes the SVG markup into a text `Part`, instead of returning the binary placeholder (`[Binary artifact: ...]`). SVG is XML so this is a reasonable representation for the model. ```diff +_GEMINI_UNSUPPORTED_INLINE_SUBTYPES = frozenset({ + 'image/svg+xml', +}) _TEXT_LIKE_MIME_TYPES = frozenset({ 'application/csv', 'application/json', 'application/xml', + 'image/svg+xml', }) def _is_inline_mime_type_supported(mime_type): normalized = _normalize_mime_type(mime_type) if not normalized: return False + if normalized in _GEMINI_UNSUPPORTED_INLINE_SUBTYPES: + return False return normalized.startswith(_GEMINI_SUPPORTED_INLINE_MIME_PREFIXES) or ( normalized in _GEMINI_SUPPORTED_INLINE_MIME_TYPES ) ``` ### Testing Plan **Unit Tests:** - [x] I have added or updated unit tests for my change. - [x] All unit tests pass locally. Added `test_load_artifacts_converts_svg_to_text` in `tests/unittests/tools/test_load_artifacts_tool.py`, mirroring the existing `test_load_artifacts_converts_unsupported_mime_to_text` (CSV) shape. It asserts that after `load_artifacts_tool.process_llm_request` runs: - `artifact_part.inline_data is None` (SVG is no longer forwarded as inline image data) - `artifact_part.text == svg_bytes.decode('utf-8')` (the SVG markup is delivered as a text Part) ``` $ pytest tests/unittests/tools/test_load_artifacts_tool.py -v ============================= test session starts ============================== platform darwin -- Python 3.13.5, pytest-8.4.2 collected 8 items tests/unittests/tools/test_load_artifacts_tool.py::test_load_artifacts_converts_unsupported_mime_to_text PASSED tests/unittests/tools/test_load_artifacts_tool.py::test_load_artifacts_converts_base64_unsupported_mime_to_text PASSED tests/unittests/tools/test_load_artifacts_tool.py::test_load_artifacts_keeps_supported_mime_types PASSED tests/unittests/tools/test_load_artifacts_tool.py::test_load_artifacts_converts_svg_to_text PASSED tests/unittests/tools/test_load_artifacts_tool.py::test_maybe_base64_to_bytes_decodes_standard_base64 PASSED tests/unittests/tools/test_load_artifacts_tool.py::test_maybe_base64_to_bytes_decodes_urlsafe_base64 PASSED tests/unittests/tools/test_load_artifacts_tool.py::test_maybe_base64_to_bytes_returns_none_for_invalid PASSED tests/unittests/tools/test_load_artifacts_tool.py::test_get_declaration_with_json_schema_feature_enabled PASSED ========================= 8 passed, 1 warning in 3.61s ========================= ``` The 1 warning is from a pre-existing test that exercises a `[WIP]` feature flag (`JSON_SCHEMA_FOR_FUNC_DECL`) and is unrelated to this change. **Manual End-to-End (E2E) Tests:** Reproduced the underlying Gemini API behaviour outside ADK with the `google-genai` SDK that ADK depends on: ```python # google-genai==1.75.0, gemini-2.5-flash from google import genai from google.genai import types client = genai.Client(api_key="...") with open("logo.svg", "rb") as f: part = types.Part.from_bytes(data=f.read(), mime_type="image/svg+xml") client.models.generate_content( model="gemini-2.5-flash", contents=["Describe this.", part], ) ``` Before this PR, the same code path inside ADK (`LoadArtifactsTool` → `_as_safe_part_for_llm` → inline forward) ends in: ``` google.genai.errors.ClientError: 400 INVALID_ARGUMENT. {'error': {'code': 400, 'message': 'Unsupported MIME type: image/svg+xml', 'status': 'INVALID_ARGUMENT'}} ``` After this PR, the SVG artifact is delivered to the model as a text `Part` containing the SVG markup, and the agent run completes successfully. Control formats (`image/png/jpeg/webp/avif/gif`, `audio/mpeg/mp3`, `video/mp4/webm`, `application/pdf`) are unchanged: still forwarded as inline data and accepted by Gemini. ### 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 Local pre-commit (`isort`, `pyink`, `addlicense`, `end-of-file-fixer`, `trailing-whitespace`) all pass on the two edited files. Related issue history: - #4028 closed by `fdc98d5c`. Same shape of bug for `application/csv`. This PR is the missed sub-case under the `image/` prefix. Co-authored-by: George Weale <gweale@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5694 from 1wos:fix/image-svg-xml-unsupported 7b0be51fd91f2b87270faa996be25e0dbd309ff5 PiperOrigin-RevId: 933282374 |
||
|
|
ea65345042 |
chore(cli): Improve operator safety for generated .env files
Merge https://github.com/google/adk-python/pull/5427 ## Summary - `adk create` always writes a `.env` file. - The scaffold now also creates or updates `.gitignore` with `.env`. - Existing `.gitignore` entries are preserved, and `.env` is not duplicated. ## Why If ADK creates `.env` by default, it should also ignore that file by default. This avoids relying on operator accuracy for a predictable secret-handling risk. ## Testing - `PYTHONPATH=src pytest tests/unittests/cli/utils/test_cli_create.py` - `PYTHONPATH=src pytest tests/unittests/cli/utils/test_cli_tools_click.py::test_cli_create_cmd_invokes_run_cmd tests/unittests/cli/test_cli_tools_click_option_mismatch.py::test_adk_create` - Manual smoke test: `adk create l1` generated `.gitignore` containing `.env` ## Notes - `git diff --check` passed. - `pyink` and `isort` were not available in the current uv environment, so formatter checks could not be run locally. Co-authored-by: Shangjie Chen <deanchen@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5427 from milaforge:codex/gitignore-env-on-create 67c46ebe3af2e8064bbf5ee924bd00f05aae3a7b PiperOrigin-RevId: 933268974 |
||
|
|
b15c8a0fe1 |
feat: report cached token counts for Anthropic and OpenAI models
Populate usage_metadata.cached_content_token_count from provider usage so cache reads stop being reported as misses (matches LiteLlm). Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 933261404 |
||
|
|
8c92cdef50 |
docs: clarify context cache min_tokens gating and 4096-token minimum
Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 933258124 |
||
|
|
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 |
||
|
|
fe56f31951 |
fix: log diagnostics for empty or unparseable rubric auto-rater output
Close #5732 Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 933244512 |
||
|
|
5b16a867d0 |
fix(eval): include function-call events in invocation_events when skip_summarization is set
Merge https://github.com/google/adk-python/pull/5417 ### Link to Issue or Description of Change Fixes #5410 ### Description `EvaluationGenerator.convert_events_to_eval_invocations` builds `invocation_events` (the intermediate tool-call record used by `TrajectoryEvaluator`) by collecting all qualifying events and then excluding the `final_event` from the list. The final event is identified via `event.is_final_response()`, but `is_final_response()` returns `True` for **any** event with `skip_summarization=True` — even events that contain `function_call` parts (e.g. tools that use `skip_summarization` to surface their result directly without an LLM summarization step). Those events were silently dropped from `invocation_events`, causing `get_all_tool_calls()` to return `[]` for the actual invocation. The result: `tool_trajectory_avg_score` was always **0.0** even when the tool name and args matched the expected exactly. **Root cause:** `is_final_response()` conflates "final user-visible response" with "should be excluded from tool trajectory". When `skip_summarization=True` the function-call event is both the final response *and* an intermediate step that must appear in the trajectory. **Fix:** in the list comprehension that builds `invocation_events`, keep an event even when it equals `final_event` if it contains function calls: ```python # before if e is not final_event # after if e is not final_event or e.get_function_calls() ``` ### Changes - `src/google/adk/evaluation/evaluation_generator.py`: one-line fix - `tests/unittests/evaluation/test_evaluation_generator.py`: regression test that verifies tool calls are preserved when `skip_summarization=True` - `tests/unittests/evaluation/test_trajectory_evaluator.py`: end-to-end tests for `InvocationEvents` intermediate_data format (exact match → 1.0, mismatch → 0.0) ### Testing Plan ``` pytest tests/unittests/evaluation/test_trajectory_evaluator.py \ tests/unittests/evaluation/test_evaluation_generator.py -v ======================== 47 passed in 1.23s ============================ ``` Co-authored-by: George Weale <gweale@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5417 from Koushik-Salammagari:fix/trajectory-eval-skip-summarization ce8087f4a5aac4c804ea39cce19670fc448c72fc PiperOrigin-RevId: 933236523 |
||
|
|
9a6cf60fa8 |
fix(eval): handle failed inference results without invocations
Merge https://github.com/google/adk-python/pull/5878 ## What changed - Return a failed `EvalCaseResult` when inference failed before producing any invocations. - Preserve the existing session lookup path when a failed inference still has a session id. - Add a regression test for `InferenceResult(status=FAILURE, inferences=None)`. - Clean up two existing lint issues in the touched eval test file so the local changed-file ruff check passes. This prevents the eval runner from replacing the original inference error with `TypeError: object of type 'NoneType' has no len()`. Fixes #5876 ## To verify - `.\.venv\Scripts\python.exe -m py_compile src\google\adk\evaluation\local_eval_service.py tests\unittests\evaluation\test_local_eval_service.py` - `.\.venv\Scripts\python.exe -m pytest tests\unittests\evaluation\test_local_eval_service.py -k "failed_without_inferences or evaluate_single_inference_result" -q --basetemp .tmp\pytest` - `.\.venv\Scripts\python.exe -m ruff check src\google\adk\evaluation\local_eval_service.py tests\unittests\evaluation\test_local_eval_service.py` - `.\.venv\Scripts\python.exe -m pyink --check src\google\adk\evaluation\local_eval_service.py tests\unittests\evaluation\test_local_eval_service.py` - `git diff --check` Co-authored-by: George Weale <gweale@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5878 from he-yufeng:fix/eval-failed-inference-none 5ed777d2939d03b891615da9e215b807536809ad PiperOrigin-RevId: 933176248 |
||
|
|
4024467f76 |
ADK changes
Co-authored-by: Shangjie Chen <deanchen@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5854 from kkj333:fix/artifact-display-name-5833 eda1ec5bfadf95d30e783c449c7e5a1718cc8bde PiperOrigin-RevId: 933174265 |
||
|
|
7307c11bf6 |
test(otel): Add functional test for telemetry with MCP toolset
Co-authored-by: Max Ind <maxind@google.com> PiperOrigin-RevId: 933053369 |
||
|
|
23c0826f4a |
refactor(otel): Add pure functions for constructing stable and experimental semconv logs
Co-authored-by: Max Ind <maxind@google.com> PiperOrigin-RevId: 933035924 |
||
|
|
60c55ad745 |
fix: fix vertex_ai_session_service crashing when Agent Engine passes full resource names instead of short session IDs
PiperOrigin-RevId: 932964014 |
||
|
|
f8e9195d3d |
fix(planners): allow BuiltInPlanner subclasses to override process_planning_response
Merge https://github.com/google/adk-python/pull/4141 ## Summary Fixes #4133 ### Problem When users create a subclass of `BuiltInPlanner` and override `process_planning_response()`, the method was never called because the response processor used `isinstance(planner, BuiltInPlanner)` which returns `True` for all subclasses. ### Solution Changed the check to detect whether `process_planning_response` has been overridden: ```python # Before if not planner or isinstance(planner, BuiltInPlanner): return # After if ( not planner or type(planner).process_planning_response is BuiltInPlanner.process_planning_response ): return ``` This ensures: - `BuiltInPlanner` itself is skipped (returns `None`) - Subclasses **without** override are skipped (avoids side effects) - Subclasses **with** override have their method called ### Testing Added 3 new tests: 1. `test_overridden_subclass_process_planning_response_called` - Regression test for #4133 2. `test_base_builtin_planner_process_planning_response_not_called` - Verifies base class is skipped 3. `test_non_overridden_subclass_process_planning_response_not_called` - Verifies non-overriding subclasses are also skipped Co-authored-by: George Weale <gweale@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4141 from maru0804:fix/4133-planner-process-planning-response 8d5732396d4784f174d29d4c3f07ec6b3adfb55f PiperOrigin-RevId: 932853783 |
||
|
|
8e2b06dd64 |
fix: preserve empty GCS text artifacts
Merge https://github.com/google/adk-python/pull/5724 ## Summary - allow `GcsArtifactService` to save `Part(text="")` as a valid text artifact - load GCS blobs via `get_blob()` so missing objects are distinct from zero-byte objects - add regression coverage for saving and loading an empty GCS text artifact ## Context The file artifact backend already persists empty text artifacts, but the GCS backend checked `artifact.text` by truthiness and rejected empty strings as missing payloads. It also treated `download_as_bytes() == b""` as a missing artifact, which collapses a valid zero-byte GCS object into `None`. This keeps the existing GCS retrieval shape for text artifacts: they load as `inline_data` with `text/plain`, matching current non-empty text behavior. ## Validation - `uv run --extra test pytest tests/unittests/artifacts/test_artifact_service.py -q` -> 60 passed - `uv run --extra dev pyink --check src/google/adk/artifacts/gcs_artifact_service.py tests/unittests/artifacts/test_artifact_service.py` -> passed - `uv run --extra dev isort --check-only src/google/adk/artifacts/gcs_artifact_service.py tests/unittests/artifacts/test_artifact_service.py` -> passed - `python3 -m py_compile src/google/adk/artifacts/gcs_artifact_service.py tests/unittests/artifacts/test_artifact_service.py` -> passed - `git diff --check` -> passed Co-authored-by: Bo Yang <ybo@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5724 from pragnyanramtha:pragnyan/gcs-empty-text-artifacts 66aba44c0ec58b43ce41a24154fe8e51b18254c4 PiperOrigin-RevId: 932789425 |
||
|
|
7a11b50cb3 |
docs(openapi): improve docs for session model
Merge https://github.com/google/adk-python/pull/5031 ### Description This PR improves the generated OpenAPI/Swagger documentation for session management by adding clearer metadata and richer schema docs for the Session model (descriptions + examples). ### What changed - Session model docs: add field descriptions/examples for so the API schema is self-explanatory. ### Why The OpenAPI output was correct but lacked enough context (field meanings and examples) for users reading Swagger UI or generating clients. ### Testing Documentation-only change (OpenAPI metadata / schema docs). - Unit tests: Not run. Co-authored-by: Shangjie Chen <deanchen@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5031 from akshay-kumar-bm:docs/openapi-session-endpoint-docs-update 04ea27ea8ea2d893a97c72b100edaff46afca03d PiperOrigin-RevId: 932788042 |
||
|
|
24a1b26a78 |
docs(skills): fix broken refs in adk-workflow skill
Merge https://github.com/google/adk-python/pull/6044 ## Summary Fixes 5 documentation bugs in the `adk-workflow` skill on `v2`. Each is empirically verified against `google-adk==2.2.0` and `v2` source. Full verification transcript inline below. ### Fixes in this PR 1. **`advanced-patterns.md`** — Remove the hard-coded local-filesystem URL `file:///Users/deanchen/Desktop/...` left in a doc link; replace with the relative path `dynamic-nodes.md`. 2. **`testing.md`** — All examples imported from `tests.unittests.*`, which is not in the published `google-adk` wheel. Rewrite to use the public `from google.adk.runners import InMemoryRunner` plus a small inline `run()` helper. Three rewritten snippets (basic workflow, state, parallel worker) were executed end-to-end against `google-adk==2.2.0` and pass. The `MockModel` section is replaced with a `FakeLlm(BaseLlm)` pattern that uses only public symbols. 3. **`llm-agent-nodes.md`** — The doc claims "LlmAgentWrapper outputs `types.Content`, NOT `str`." The source (`_llm_agent_wrapper.process_llm_agent_output`) sets `event.output = text` (a `str`) when `output_schema` is unset, and the validated dict when set. Rewrite the section, the table, and drop the "use `Any` and extract text" workaround that depended on the wrong claim. 4. **`parallel-and-fanout.md` + `import-paths.md`** — Drop `from google.adk.workflow._parallel_worker import ParallelWorker`. The class doesn't exist under that name; only the private `_ParallelWorker` does. The same files already document the public API (`parallel_worker=True` flag on `@node` or `LlmAgent`) — rewrite samples to use it consistently. 5. **`state-and-events.md` (+ one cross-reference in `advanced-patterns.md`)** — Drop `triggered_by`, `in_nodes`, `execution_id`, `retry_count` from the `Context` property tables and code samples. None of them exist on `Context` in v2 source (verified by `grep` in `src/google/adk/agents/context.py`). Rename `retry_count` → `attempt_count` (the live name). Also drop `get_next_child_execution_id` from the methods table for the same reason. ### Scope All five fixes target the same file tree (`.agents/skills/adk-workflow/references/`) with the same concern: "skill docs reference symbols/imports/paths that don't exist." Per CONTRIBUTING.md "small, focused PRs", they're bundled because each is a surgical edit and they share verification setup. Happy to split if reviewers prefer. ## Testing plan Doc-only changes. No source code or behavior modified. 1. **Pip-install reproduction** of every bug claimed in a clean venv with `google-adk==2.2.0`. See "Verification details" below for the verbatim `ImportError`, `hasattr == False`, and source quotes that prove each claim. 2. **Rewritten `testing.md` snippets executed end-to-end** against `google-adk==2.2.0`: - `test_simple_workflow` — PASSED - `test_state_management` — PASSED - `test_parallel_worker` — PASSED 3. **Pre-commit hooks** ran clean on the changed files. `mdformat` is excluded for `.agents/` by the repo's `.pre-commit-config.yaml`, and the other hooks (`isort`, `pyink`, `addlicense`) target Python/shell files only. ## Notes for reviewers - All claims are pinned to `2.2.0` + `v2` HEAD as of the date of this PR. - The `testing.md` rewrite is the largest delta (~500 lines), but almost every line either drops a `tests.unittests.*` import or replaces a `testing_utils.X` call with public-API equivalents. - A previously-considered "bug" (parallel-worker naming as `{name}__{index}`) was dropped from this PR after confirming it had already been fixed on v2 to use `{name}@{run_id}` with `run_id` starting at `"1"`. --- ## Verification details Setup: ```bash uv venv --python 3.13 .venv && source .venv/bin/activate uv pip install google-adk python -c "import google.adk; print(google.adk.__version__)" # -> 2.2.0 ``` ### Bug 1 — Hard-coded `file:///` URL ``` $ grep -n 'file:///' .agents/skills/adk-workflow/references/advanced-patterns.md 38:See the dedicated [Dynamic Node Scheduling Reference](file:///Users/deanchen/Desktop/adk-workflow/.agents/skills/adk-workflow/references/dynamic-nodes.md) for detailed rules, examples, and best practices. ``` A developer's local filesystem path leaked into the published skill. ### Bug 2 — `tests.unittests...` imports unreachable from `pip install` ```python >>> from tests.unittests.workflow import testing_utils ModuleNotFoundError: No module named 'tests' >>> from tests.unittests.testing_utils import InMemoryRunner, MockModel ModuleNotFoundError: No module named 'tests' ``` The `tests/` directory ships only in the source repo, not in the installed `google-adk` wheel. Any user copying these snippets gets `ModuleNotFoundError`. The PR rewrites samples to use the public `from google.adk.runners import InMemoryRunner` and demonstrates a publicly-importable mock pattern (subclass `BaseLlm`). The three rewritten snippets (basic, state, parallel) were run end-to-end against `google-adk==2.2.0` and all three passed. ### Bug 3 — `LlmAgentWrapper` output type doc is wrong The skill claims: *"LlmAgentWrapper outputs `types.Content`, NOT `str`."* The source in both `2.2.0` and `v2` says otherwise. From `src/google/adk/workflow/_llm_agent_wrapper.py`: ```python def process_llm_agent_output(agent: Any, ctx: Context, event: Event) -> None: ... text = ( ''.join(p.text for p in event.content.parts if p.text and not p.thought) if event.content.parts else '' ) if agent.output_schema: if text.strip(): output = validate_schema(agent.output_schema, text) else: output = None else: output = text # <-- str, not types.Content ... event.output = output ``` When `output_schema` is unset, `event.output` is the concatenated string of the model's text parts. When `output_schema=MyModel` is set, it's the validated `model_dump()` dict. The PR rewrites the section, table, and the "use `Any` and extract text" workaround that depended on the wrong claim. ### Bug 4 — `_parallel_worker.ParallelWorker` is not importable The class doesn't exist under that name — only the underscore-prefixed `_ParallelWorker` does, and that path is private: ```python >>> from google.adk.workflow._parallel_worker import ParallelWorker ImportError: cannot import name 'ParallelWorker' from 'google.adk.workflow._parallel_worker' >>> from google.adk.workflow._parallel_worker import _ParallelWorker >>> _ParallelWorker <class 'google.adk.workflow._parallel_worker._ParallelWorker'> ``` The recommended public API is the `parallel_worker=True` flag — already documented as preferred in the same files. Verified end-to-end: ```python from google.adk.workflow import node, Workflow @node(parallel_worker=True) def double(node_input: int) -> int: return node_input * 2 # Workflow constructs OK; `double` is an internal _ParallelWorker # under the hood — no user-visible private-API surface needed. ``` PR drops the private import from `parallel-and-fanout.md` and the `import-paths.md` table, and rewrites samples to use the flag. ### Bug 5 — Removed `Context` properties documented as live ```python >>> from google.adk.agents.context import Context >>> for name in ['triggered_by', 'in_nodes', 'execution_id', 'retry_count', 'attempt_count']: ... print(name, hasattr(Context, name)) triggered_by False in_nodes False execution_id False retry_count False attempt_count True ``` The same is true on `v2` source — `grep` for those names in `src/google/adk/agents/context.py` returns nothing, while `attempt_count` has 4 hits. Code samples using `ctx.retry_count` / `ctx.triggered_by` raise `AttributeError`. PR removes the four absent properties from the docs and renames `retry_count` → `attempt_count` everywhere it's mentioned. `get_next_child_execution_id` is also gone from `Context` on v2; the PR removes it from the methods table for the same reason. Co-authored-by: Shangjie Chen <deanchen@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/6044 from freddypatota:fix/adk-workflow-skill-doc-bugs 4a61d38ac468d19222475802056a0b80c1c9cc6a PiperOrigin-RevId: 932770088 |
||
|
|
ffc9677154 |
feat: add create_http_options to ContextCacheConfig for cache creation timeout
Merge https://github.com/google/adk-python/pull/4702 Close #4703 Co-authored-by: Xuan Yang <xygoogle@google.com> PiperOrigin-RevId: 932753541 |
||
|
|
4aaf494760 |
fix: skip crewai test on ImportError for pytest 9.1 compatibility
Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 932734764 |
||
|
|
225fafc6d5 |
docs: add beginner explanation for single agent example
Merge https://github.com/google/adk-python/pull/4099 ### Description of the change **Problem:** New users may find it unclear what an “agent” represents in ADK before encountering the first code example in the README. **Solution:** Added a short beginner-friendly explanation before the single-agent example to clarify what an ADK agent is and what the example demonstrates. This improves readability and onboarding without changing any existing behavior or code. --- ### Testing Plan This change is documentation-only and does not affect runtime behavior. No tests were required or run. --- ### Checklist - [x] I have read the CONTRIBUTING.md document. - [x] I have performed a self-review of my own change. - [ ] I have commented my code, particularly in hard-to-understand areas. (Not applicable – documentation only) - [ ] I have added tests that prove my fix is effective or that my feature works. (Not applicable – documentation only) - [ ] New and existing unit tests pass locally with my changes. (Not applicable – documentation only) - [ ] I have manually tested my changes end-to-end. (Not applicable – documentation only) - [ ] Any dependent changes have been merged and published in downstream modules. (Not applicable) --- ### Additional context This change is intended to improve the onboarding experience for users exploring ADK for the first time. Co-authored-by: Shangjie Chen <deanchen@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4099 from yukthagangadhari5:docs-beginner-note ec2e677812d6d7f73afc777c43c229b63c7b7d02 PiperOrigin-RevId: 932731929 |
||
|
|
883ff98aef |
fix(flows): terminate invocation at tool-level EUC
Merge https://github.com/google/adk-python/pull/5638 ### Link to Issue or Description of Change **1. Link to an existing issue (if applicable):** - Closes: #5637 This change adds `invocation_context.end_invocation = True` after the auth event yield in `_postprocess_handle_function_calls_async`, mirroring the existing termination signal in `_resolve_toolset_auth`. Tool-level auth now terminates symmetrically with toolset-level auth at the EUC, instead of continuing for one more LLM call. ### Testing Plan **Unit Tests:** - [x] I have added or updated unit tests for my change. - [x] All unit tests pass locally. Three existing tests in `test_functions_request_euc.py` had assertions tied to the trailing post-EUC LLM call: - `test_function_request_euc`: adds `assert len(mock_model.requests) == 1` to anchor the new termination behavior. - `test_function_get_auth_response`: `events[-3]` → `events[-2]` for the auth event lookup, since the auth event is now second-to-last. - `test_function_get_auth_response_partial`: same `events[-3]` → `events[-2]` change, plus the two `len(mock_model.requests)` assertions drop by 1 (3 → 2 and 4 → 3). ``` $ pytest tests/unittests/flows/llm_flows/test_functions_request_euc.py ======================== 3 passed, 17 warnings in 1.31s ======================== $ pytest tests/unittests/ =============== 5695 passed, 2308 warnings in 122.89s (0:02:02) ================ ``` **Manual End-to-End (E2E) Tests:** A self-contained Runner-based reproduction is at https://github.com/doughayden/adk-issue-examples/tree/main/04-tool_level_auth_continuation. The agent definition (`agent.py`) wires up an `OpenAPIToolset` against a local OAuth2 test server. `main.py` constructs an `InMemoryRunner`, applies the workaround for #5327 (`get_auth_config = lambda: None`) at runtime to land on the tool-level auth path, and sends a tool-triggering prompt. The `--apply-fix` flag monkey-patches the proposed fix to demonstrate the resolution end-to-end. Without the fix: ``` 👤 User: What's the weather in San Francisco? 🌤️ Weather Assistant event stream: [function_call] get_weather by WeatherAssistant [auth_event] adk_request_credential by WeatherAssistant [function_response] get_weather by WeatherAssistant [post_euc_text] WeatherAssistant: "I'm sorry, I cannot retrieve the weather for San Francisco at the moment. It ..." Event counts: function_calls: 1 auth_events: 1 function_responses: 1 text_events: 1 post_euc_text_events: 1 ✅ Bug reproduced: 1 text event(s) after the EUC (agent loop continued past adk_request_credential). ``` With the fix: ``` 👤 User: What's the weather in San Francisco? 🌤️ Weather Assistant event stream: [function_call] get_weather by WeatherAssistant [auth_event] adk_request_credential by WeatherAssistant [function_response] get_weather by WeatherAssistant Event counts: function_calls: 1 auth_events: 1 function_responses: 1 text_events: 0 post_euc_text_events: 0 ✅ Fix verified: no LLM events after the EUC. ``` ### 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. - [ ] Any dependent changes have been merged and published in downstream modules. ### Additional context **Alternative considered:** A reorder of the yields (yield `auth_event` last so `last_event.is_final_response()` returns True) would also fix the loop termination in a single iteration without needing the flag. I went with `end_invocation = True` to preserve the observable event order and to match the existing pattern in `_resolve_toolset_auth`. Happy to switch if maintainers prefer the reorder. **Related:** The same yield site at lines 1126-1130 also produces `tool_confirmation_event` for HITL with the same `long_running_tool_ids` shape and the same termination gap. This PR scopes to `auth_event` only. Happy to open a follow-up PR with the same fix for `tool_confirmation_event` if the team agrees with the approach here. Co-authored-by: George Weale <gweale@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5638 from doughayden:fix/tool-level-auth-terminates-at-euc 0a04d30aae4a284c8234218ac4e82e1f723bc9c1 PiperOrigin-RevId: 932731604 |
||
|
|
2e28e5d1e1 |
docs: remove stale -b v2 flag from clone command in CONTRIBUTING.md
Merge https://github.com/google/adk-python/pull/6121 The Development Setup section instructs contributors to clone the repo with `-- -b v2`, which points to the old `v2` branch. Active development happens on `main` (the default branch). Contributors who follow this instruction verbatim end up on an outdated branch and miss recent changes. Remove the `-b v2` flag so `gh repo clone` checks out the default branch (`main`) as expected. Co-authored-by: Shangjie Chen <deanchen@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/6121 from Goutham-Annem:docs/fix-contributing-clone-branch 8cefeb050333ec18e6e0115b1fb201af5561734c PiperOrigin-RevId: 932669214 |