Skip to content

Commit 81c588f

Browse files
authored
Merge branch 'google:main' into fix/skill-toolset-instruction-tool-filter
2 parents db58ecd + 4b002c4 commit 81c588f

119 files changed

Lines changed: 3898 additions & 543 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.pre-commit-config.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,11 @@ repos:
7272
exclude: (?i)SKILL\.md$
7373
additional_dependencies:
7474
- mdformat-gfm
75+
- repo: https://github.com/codespell-project/codespell
76+
rev: v2.4.2
77+
hooks:
78+
- id: codespell
79+
# Configuration (skip globs, ignored words) lives in the
80+
# [tool.codespell] table in pyproject.toml.
81+
additional_dependencies:
82+
- tomli
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# ADK evaluation samples
2+
3+
## Overview
4+
5+
A family of single-concept samples that each show one way to evaluate the *same*
6+
shared home-automation agent with the `adk eval` CLI. Every sample points `adk eval` at one agent and differs only in its eval data and criteria, so you can
7+
compare evaluation techniques (deterministic reference matching, custom metrics,
8+
LLM-as-a-judge, rubrics, and user simulation) side by side.
9+
10+
## The shared agent
11+
12+
`home_automation_agent/` is a small agent that controls smart-home devices and
13+
temperatures. Its five tools (`get_device_info`, `set_device_info`,
14+
`get_temperature`, `set_temperature`, `list_devices`) are deterministic, backed by
15+
in-memory state, so eval trajectories are reproducible. The module exposes
16+
`reset_data()`, which `adk eval` calls to reset that state between eval cases.
17+
18+
Every sample evaluates this same agent. `adk eval` takes the agent path and the
19+
eval-set path as two separate arguments, so each sub-sample folder holds only
20+
eval data and its criteria config, never a copy of the agent code.
21+
22+
## How evaluation runs
23+
24+
`adk eval` runs in two phases: (1) live inference, where it actually runs the
25+
agent against each eval input to produce responses and tool calls, and then (2)
26+
scoring, where it compares that output against the case's criteria. Because phase 1
27+
runs the real agent, a model credential is required for every sample, even the
28+
deterministic ones. Provide a Gemini API key in `home_automation_agent/.env`,
29+
or configure Vertex.
30+
Samples that use an LLM judge or a user simulator make additional model calls, but
31+
they resolve through the same model registry and credentials.
32+
33+
Because live responses vary from run to run, the deterministic, reference-based
34+
criteria use lenient response thresholds (e.g. `response_match_score` at
35+
`0.5`) so that harmless phrasing differences don't fail an otherwise-correct
36+
answer.
37+
38+
## Samples
39+
40+
| Sample | Concept | Criteria |
41+
| ------------------------------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------- |
42+
| [`basic_criteria`](./basic_criteria/) | Deterministic, reference-based scoring | `tool_trajectory_avg_score`, `response_match_score` |
43+
| [`test_file_vs_evalset`](./test_file_vs_evalset/) | `.test.json` vs `.evalset.json` conventions | `tool_trajectory_avg_score`, `response_match_score` |
44+
| [`custom_metric`](./custom_metric/) | Write your own metric | `temperature_safety_score` (custom) |
45+
| [`llm_judge_match`](./llm_judge_match/) | LLM-judged semantic match | `final_response_match_v2` |
46+
| [`rubric_criteria`](./rubric_criteria/) | LLM-judged quality via rubrics | `rubric_based_final_response_quality_v1`, `rubric_based_tool_use_quality_v1` |
47+
| [`user_simulation`](./user_simulation/) | Dynamically simulated user turns | `hallucinations_v1`, `per_turn_user_simulator_quality_v1` |
48+
49+
## Graph
50+
51+
```mermaid
52+
graph TD
53+
A[home_automation_agent] --> B(get_device_info)
54+
A --> C(set_device_info)
55+
A --> D(get_temperature)
56+
A --> E(set_temperature)
57+
A --> F(list_devices)
58+
```
59+
60+
## Related Guides
61+
62+
- Evaluation overview: https://adk.dev/evaluate/
63+
- Evaluation criteria reference: https://adk.dev/evaluate/criteria/
64+
- User simulation guide: https://adk.dev/evaluate/user-sim/
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Basic evaluation criteria
2+
3+
## Overview
4+
5+
Evaluates the shared home-automation agent with the two deterministic,
6+
reference-based criteria:
7+
8+
- `tool_trajectory_avg_score`: does the agent call the right tools with the
9+
right args? Each expected tool call (name + args) is compared against what the
10+
agent actually did.
11+
- `response_match_score`: ROUGE-1 word overlap between the agent's final
12+
response and a reference answer.
13+
14+
Both criteria are computed locally with no judge model, so this sample needs only
15+
a model credential for the agent's own inference (a Gemini API key or Vertex).
16+
17+
## Sample Inputs
18+
19+
The eval set (`home_automation.evalset.json`) contains two single-turn cases:
20+
21+
- `Turn off device_2.`
22+
- `What is the temperature in the Living Room?`
23+
24+
## How To
25+
26+
Run the sample from the workspace root:
27+
28+
```bash
29+
adk eval contributing/samples/evaluation/home_automation_agent \
30+
contributing/samples/evaluation/basic_criteria/home_automation.evalset.json \
31+
--config_file_path contributing/samples/evaluation/basic_criteria/eval_config.json \
32+
--print_detailed_results
33+
```
34+
35+
`adk eval` takes the agent folder and the eval-set file as two separate
36+
arguments, so this folder holds only eval data (`home_automation.evalset.json`),
37+
the criteria config (`eval_config.json`), and this README, with no agent code.
38+
39+
### `match_type` for tool trajectory
40+
41+
`tool_trajectory_avg_score` has a `match_type` (set to `EXACT` here in
42+
`eval_config.json`) that controls how the expected and actual tool calls are
43+
compared:
44+
45+
- `EXACT`: the actual tool calls must match the expected calls one-for-one, in
46+
the same order, with identical args. Use this when the trajectory is fully
47+
deterministic (as in this sample).
48+
- `IN_ORDER`: the expected calls must appear in the given order, but extra
49+
actual calls in between are tolerated. Useful when the agent may take
50+
additional, harmless steps.
51+
- `ANY_ORDER`: the expected calls must all appear, but order does not matter.
52+
Useful when the agent may reorder independent tool calls.
53+
54+
The `threshold` is `1.0`, so every expected call must match for the case to pass.
55+
56+
### Why `response_match_score` uses a `0.6` threshold
57+
58+
`adk eval` runs live inference, so the exact wording of the agent's final
59+
response varies from run to run (for example, "I have turned off device_2." vs
60+
"device_2 has been switched off."). `response_match_score` is a ROUGE-1 score,
61+
which measures word overlap rather than exact-string equality, so it tolerates
62+
this phrasing variation. The `0.6` threshold requires the response to share most
63+
of its wording with the reference while still allowing some rewording. Raise it
64+
toward `1.0` for stricter wording, lower it to tolerate more paraphrasing.
65+
66+
### Expectations captured from a real run
67+
68+
The expected `tool_uses` (tool names and args) in `home_automation.evalset.json`
69+
were captured from an actual `adk eval` run of the agent: run with
70+
`--print_detailed_results`, read the printed Actual-vs-Expected, then set the
71+
expected values to match what the agent really produced. The reference
72+
`final_response` for each case is an independently written natural answer (not a
73+
copy of the model output), which is exactly what ROUGE-1 is designed to tolerate.
74+
75+
## Related Guides
76+
77+
- Evaluation overview: https://adk.dev/evaluate/
78+
- Evaluation criteria reference: https://adk.dev/evaluate/criteria/
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"criteria": {
3+
"tool_trajectory_avg_score": {
4+
"threshold": 1.0,
5+
"match_type": "EXACT"
6+
},
7+
"response_match_score": 0.6
8+
}
9+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{
2+
"eval_set_id": "basic_criteria",
3+
"name": "Basic deterministic criteria",
4+
"description": "Single-turn cases scored by tool trajectory and ROUGE-1.",
5+
"eval_cases": [
6+
{
7+
"eval_id": "turn_off_bedroom_device",
8+
"conversation": [
9+
{
10+
"invocation_id": "basic-1",
11+
"user_content": {
12+
"parts": [{"text": "Turn off device_2."}],
13+
"role": "user"
14+
},
15+
"final_response": {
16+
"parts": [{"text": "device_2 is now turned off."}],
17+
"role": "model"
18+
},
19+
"intermediate_data": {
20+
"tool_uses": [
21+
{"name": "set_device_info",
22+
"args": {"device_id": "device_2", "status": "OFF"}}
23+
],
24+
"intermediate_responses": []
25+
}
26+
}
27+
],
28+
"session_input": {
29+
"app_name": "home_automation_agent",
30+
"user_id": "user",
31+
"state": {}
32+
}
33+
},
34+
{
35+
"eval_id": "get_living_room_temperature",
36+
"conversation": [
37+
{
38+
"invocation_id": "basic-2",
39+
"user_content": {
40+
"parts": [{"text": "What is the temperature in the Living Room?"}],
41+
"role": "user"
42+
},
43+
"final_response": {
44+
"parts": [{"text": "The Living Room is 22 degrees Celsius."}],
45+
"role": "model"
46+
},
47+
"intermediate_data": {
48+
"tool_uses": [
49+
{"name": "get_temperature", "args": {"location": "Living Room"}}
50+
],
51+
"intermediate_responses": []
52+
}
53+
}
54+
],
55+
"session_input": {
56+
"app_name": "home_automation_agent",
57+
"user_id": "user",
58+
"state": {}
59+
}
60+
}
61+
]
62+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Custom evaluation metric
2+
3+
## Overview
4+
5+
This sample shows how to write and register your own evaluation metric when the
6+
built-in criteria can't express the rule you care about.
7+
8+
`temperature_safety.py` defines `temperature_safety_score`, a metric that
9+
inspects the agent's *actual* tool calls and fails if any `set_temperature` call
10+
requests a value outside the safe range of 18-30 Celsius. This is a
11+
safety/business rule the built-in criteria (`tool_trajectory_avg_score`,
12+
`response_match_score`, …) can't express, because it checks the *values* passed
13+
to a specific tool rather than comparing against a reference trajectory.
14+
15+
## Sample Inputs
16+
17+
The eval set (`home_automation.evalset.json`) contains one single-turn case:
18+
19+
- `Set the Bedroom to 21 degrees.`
20+
21+
## How To
22+
23+
### The metric function
24+
25+
A custom metric is any callable with this signature that returns an
26+
`EvaluationResult`:
27+
28+
```python
29+
def temperature_safety_score(
30+
eval_metric: EvalMetric,
31+
actual_invocations: list[Invocation],
32+
expected_invocations: Optional[list[Invocation]],
33+
conversation_scenario: Optional[ConversationScenario],
34+
) -> EvaluationResult:
35+
```
36+
37+
The function may be sync or async. Inside it:
38+
39+
- Read the agent's actual tool calls for each invocation with
40+
`get_all_tool_calls(invocation.intermediate_data)`. This returns
41+
`google.genai.types.FunctionCall` objects, each with a `.name` and `.args`, so
42+
you can inspect exactly what the agent called and with which arguments.
43+
- Return an `EvaluationResult`. Set `overall_eval_status` (PASSED/FAILED) and a
44+
matching `per_invocation_results` entry for every invocation. `adk eval`
45+
derives pass/fail from the status, not from `overall_score` alone. A metric
46+
that sets only a score leaves the status at `NOT_EVALUATED`, and the case is
47+
reported as not passed even with a perfect score. (When the status is not
48+
`NOT_EVALUATED`, `adk eval` also requires one `per_invocation_results` entry
49+
per invocation.)
50+
51+
### Registering the metric
52+
53+
The metric is wired in via `custom_metrics` in `eval_config.json`:
54+
55+
```json
56+
{
57+
"criteria": {
58+
"temperature_safety_score": 1.0
59+
},
60+
"custom_metrics": {
61+
"temperature_safety_score": {
62+
"code_config": {"name": "temperature_safety.temperature_safety_score"},
63+
"description": "Fails if any set_temperature call is outside 18-30 Celsius."
64+
}
65+
}
66+
}
67+
```
68+
69+
The metric name appears in both `criteria` (with its threshold) and
70+
`custom_metrics`. The `code_config.name` is a dotted path: everything before the
71+
last dot is the *module* (`temperature_safety`) and the last segment is the
72+
*function* (`temperature_safety_score`).
73+
74+
### Running the sample
75+
76+
`adk eval` resolves `code_config.name` by calling
77+
`importlib.import_module("temperature_safety")`, which searches `sys.path`. The
78+
metric module lives in this sample folder, which is not on `sys.path` by default,
79+
so put the folder on `PYTHONPATH` when you run the eval:
80+
81+
```bash
82+
PYTHONPATH=contributing/samples/evaluation/custom_metric \
83+
adk eval contributing/samples/evaluation/home_automation_agent \
84+
contributing/samples/evaluation/custom_metric/home_automation.evalset.json \
85+
--config_file_path contributing/samples/evaluation/custom_metric/eval_config.json \
86+
--print_detailed_results
87+
```
88+
89+
Run it from the workspace root. Without the `PYTHONPATH` prefix you'll get
90+
`ImportError: Could not import custom metric function ...`.
91+
92+
The shipped case sets the Bedroom to a valid 21 Celsius, so the metric scores
93+
1.0 (PASSED):
94+
95+
```
96+
custom_metric:
97+
Tests passed: 1
98+
Tests failed: 0
99+
...
100+
Metric: temperature_safety_score, Status: PASSED, Score: 1.0, Threshold: 1.0
101+
```
102+
103+
An unsafe value (for example, `set_temperature(location="Bedroom", temperature=45)`) would score 0.0 (FAILED). We keep the agent well-behaved
104+
and demonstrate the passing path rather than forcing an unsafe call from live
105+
inference; the FAIL branch is exactly the `18 <= temperature <= 30` check in
106+
`temperature_safety.py`.
107+
108+
## Related Guides
109+
110+
- Evaluation overview: https://adk.dev/evaluate/
111+
- Evaluation criteria reference: https://adk.dev/evaluate/criteria/
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"criteria": {
3+
"temperature_safety_score": 1.0
4+
},
5+
"custom_metrics": {
6+
"temperature_safety_score": {
7+
"code_config": {"name": "temperature_safety.temperature_safety_score"},
8+
"description": "Fails if any set_temperature call is outside 18-30 Celsius."
9+
}
10+
}
11+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"eval_set_id": "custom_metric",
3+
"name": "Custom metric: temperature safety",
4+
"description": "A valid set_temperature call; the custom metric scores 1.0.",
5+
"eval_cases": [
6+
{
7+
"eval_id": "set_bedroom_safe_temp",
8+
"conversation": [
9+
{
10+
"invocation_id": "cm-1",
11+
"user_content": {
12+
"parts": [{"text": "Set the Bedroom to 21 degrees."}],
13+
"role": "user"
14+
},
15+
"final_response": {
16+
"parts": [{"text": "Temperature in Bedroom set to 21C."}],
17+
"role": "model"
18+
},
19+
"intermediate_data": {
20+
"tool_uses": [
21+
{"name": "set_temperature",
22+
"args": {"location": "Bedroom", "temperature": 21}}
23+
],
24+
"intermediate_responses": []
25+
}
26+
}
27+
],
28+
"session_input": {
29+
"app_name": "home_automation_agent",
30+
"user_id": "user",
31+
"state": {}
32+
}
33+
}
34+
]
35+
}

0 commit comments

Comments
 (0)