Skip to content

Commit 0b7355b

Browse files
GWealecopybara-github
authored andcommitted
fix: match rubric verdicts by echoed id so paraphrased rubrics are not dropped
RubricBasedEvaluator mapped each auto-rater verdict back to its rubric by exact normalized property text. When the judge paraphrased the property text (e.g. kanji/hiragana rewrites), nothing matched and every verdict was silently dropped, leaving the invocation unscored. Each rubric now carries a short id that the judge is asked to echo, and verdicts are matched by id first, falling back to normalized property text so existing behavior is preserved. Close #6171 Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 951036528
1 parent 79a879f commit 0b7355b

5 files changed

Lines changed: 211 additions & 19 deletions

File tree

src/google/adk/evaluation/rubric_based_evaluator.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
class RubricResponse(EvalBaseModel):
4343
"""Internal data model to represent a rubric's response from the auto-rater."""
4444

45+
rubric_id: Optional[str] = None
4546
property_text: Optional[str] = None
4647
rationale: Optional[str] = None
4748
score: Optional[float] = None
@@ -56,7 +57,8 @@ def parse(self, auto_rater_response: str) -> list[RubricResponse]:
5657
raise NotImplementedError
5758

5859

59-
_PROPERTY_PATTERN = r"(?<=Property: )(.*)"
60+
_ID_PATTERN = r"(?m)^\s*ID: (.*)$"
61+
_PROPERTY_PATTERN = r"(?m)^\s*Property: (.*)$"
6062
_RATIONALE_PATTERN = r"(?<=Rationale: )(.*)"
6163
_VERDICT_PATTERN = r"(?<=Verdict: )(.*)"
6264

@@ -66,7 +68,8 @@ class DefaultAutoRaterResponseParser(AutoRaterResponseParser):
6668

6769
def parse(self, auto_rater_response: str) -> list[RubricResponse]:
6870
"""Returns a list of RubricResponse parsed from the AutoRater's response."""
69-
properties = re.findall(_PROPERTY_PATTERN, auto_rater_response)
71+
property_matches = list(re.finditer(_PROPERTY_PATTERN, auto_rater_response))
72+
id_matches = list(re.finditer(_ID_PATTERN, auto_rater_response))
7073
rationales = re.findall(_RATIONALE_PATTERN, auto_rater_response)
7174
scores = []
7275

@@ -81,13 +84,27 @@ def parse(self, auto_rater_response: str) -> list[RubricResponse]:
8184
scores.append(score)
8285

8386
# A partial parse can silently omit a failed rubric and inflate the score.
84-
if not len(properties) == len(rationales) == len(scores):
87+
if not len(property_matches) == len(rationales) == len(scores):
8588
return []
8689

8790
rubric_responses = []
88-
for p, r, s in zip(properties, rationales, scores, strict=True):
91+
for i, (property_match, rationale, score) in enumerate(
92+
zip(property_matches, rationales, scores, strict=True)
93+
):
94+
# Match each id to the property it immediately precedes (not by index) so
95+
# an omitted id line can't shift a later id onto an earlier property.
96+
previous_start = property_matches[i - 1].start() if i > 0 else -1
97+
rubric_id = None
98+
for id_match in id_matches:
99+
if previous_start < id_match.start() < property_match.start():
100+
rubric_id = id_match.group(1).strip() or None
89101
rubric_responses.append(
90-
RubricResponse(property_text=p.strip(), rationale=r.strip(), score=s)
102+
RubricResponse(
103+
rubric_id=rubric_id,
104+
property_text=property_match.group(1).strip(),
105+
rationale=rationale.strip(),
106+
score=score,
107+
)
91108
)
92109

93110
return rubric_responses
@@ -406,14 +423,21 @@ def convert_auto_rater_response_to_score(
406423
rubric_scores = []
407424

408425
normalized_rubric_to_rubric_map = {}
426+
rubric_by_id = {}
409427
for r in self.get_effective_rubrics_list():
410428
normalized_rubric_to_rubric_map[
411429
_normalize_text(r.rubric_content.text_property)
412430
] = r
431+
rubric_by_id[r.rubric_id] = r
413432

414433
for rubric_response in rubric_responses:
415-
normalized_rubric_text = _normalize_text(rubric_response.property_text)
416-
rubric = normalized_rubric_to_rubric_map.get(normalized_rubric_text, None)
434+
rubric = None
435+
if rubric_response.rubric_id:
436+
rubric = rubric_by_id.get(rubric_response.rubric_id)
437+
if rubric is None:
438+
rubric = normalized_rubric_to_rubric_map.get(
439+
_normalize_text(rubric_response.property_text)
440+
)
417441
if rubric:
418442
rubric_scores.append(
419443
RubricScore(

src/google/adk/evaluation/rubric_based_final_response_quality_v1.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@
7070
6. Output the final verdict in the required output format.
7171
7272
# Output Format (repeat this format for every property, starting with a new line):
73-
Property: [Repeat the property, word for word, without making any changes. Keep everything including punctuation and capitalization as-is.]
73+
ID: [Copy the id shown in the "[id: ...]" tag for this property, verbatim. Omit this line only if no tag is shown.]
74+
Property: [Repeat the property text that follows the "[id: ...]" tag, word for word, without making any changes and without the tag. Keep everything including punctuation and capitalization as-is.]
7475
Evidence: [List all trusted evidence from tool calls or the user prompt that is relevant to the property (referencing the Step Index). Alternatively, if either no trusted evidence is required, or no trusted evidence exists (e.g., flawed process, missing tool call, tool error), explain why.]
7576
Rationale: [Explain your reasoning, detailing how the evidence (or lack thereof) supports or contradicts the final answer, or why the property is not applicable.]
7677
Verdict: [yes|no]
@@ -149,46 +150,53 @@
149150
</response>
150151
151152
<properties>
152-
* The final answer correctly identifies the total number of employees.
153-
* The final answer correctly identifies the name of Alice Smith's manager, or correctly states that it cannot be determined and why.
154-
* The final answer correctly states the average salary for the Marketing department.
155-
* The final answer correctly identifies the employee with the highest salary.
156-
* The final answer correctly identifies the gender of the employee with the highest salary, or correctly states that it cannot be determined and why.
157-
* The final answer is formatted as a numbered list.
158-
* If the company has fewer than 100 employees, then the final answer states that it has fewer than 100 employees.
153+
* [id: 1] The final answer correctly identifies the total number of employees.
154+
* [id: 2] The final answer correctly identifies the name of Alice Smith's manager, or correctly states that it cannot be determined and why.
155+
* [id: 3] The final answer correctly states the average salary for the Marketing department.
156+
* [id: 4] The final answer correctly identifies the employee with the highest salary.
157+
* [id: 5] The final answer correctly identifies the gender of the employee with the highest salary, or correctly states that it cannot be determined and why.
158+
* [id: 6] The final answer is formatted as a numbered list.
159+
* [id: 7] If the company has fewer than 100 employees, then the final answer states that it has fewer than 100 employees.
159160
</properties>
160161
161162
## Output
163+
ID: 1
162164
Property: The final answer correctly identifies the total number of employees.
163165
Evidence: The trusted evidence is "110 employees". The tool call in Step 0 is procedurally sound and provides the total number of employees (110) by calling the load_hr_data_from_file tool with the correct file name.
164166
Rationale: The final answer's claim ("110 employees") is fully consistent with the trusted evidence.
165167
Verdict: yes
166168
169+
ID: 2
167170
Property: The final answer correctly identifies the name of Alice Smith's manager, or correctly states that it cannot be determined and why.
168171
Evidence: No trusted evidence exists. The agent did not perform a tool call to determine the manager of Alice Smith, despite having the necessary information (the employee name) and access to the necessary tools (get_manager) to do so.
169172
Rationale: The agent incorrectly stated that the final answer cannot be determined, despite having the necessary information (the employee name) and tools (get_manager) to determine it.
170173
Verdict: no
171174
175+
ID: 3
172176
Property: The final answer correctly states the average salary for the Marketing department.
173177
Evidence: No trusted evidence exists for the Marketing department's average salary. The tool call in Step 1 is procedurally flawed; the agent searched for "Engineering" instead of "Marketing".
174178
Rationale: There is no trusted evidence for the Marketing department's average salary.
175179
Verdict: no
176180
181+
ID: 4
177182
Property: The final answer correctly identifies the employee with the highest salary.
178183
Evidence: The trusted evidence is "John Smith". The tool call in Step 2 produces trusted evidence for the employee with the highest salary by calling the load_hr_data_from_file tool with the correct file name and then using the idxmax() method to find the employee with the highest salary.
179184
Rationale: The final answer's claim ("John Doe") is inconsistent with the trusted evidence ("John Smith").
180185
Verdict: no
181186
187+
ID: 5
182188
Property: The final answer correctly identifies the gender of the employee with the highest salary, or correctly states that it cannot be determined and why.
183189
Evidence: No trusted evidence exists. The agent did not perform a tool call to determine the gender of the employee with the highest salary.
184190
Rationale: There is no trusted evidence to confirm the gender of the employee with the highest salary that the final answer states (male). Even if the gender is coincidentally actually male, the claim in the final answer cannot be unambiguously verified using the evidence.
185191
Verdict: no
186192
193+
ID: 7
187194
Property: If the company has fewer than 100 employees, then the final answer should state that it has fewer than 100 employees.
188195
Evidence: The trusted evidence is "110 employees". The tool call in Step 0 correctly counts the total number of employees as 110 by calling the load_hr_data_from_file tool with the correct file name.
189196
Rationale: The total number of employees is 110, so the condition for this property (fewer than 100 employees) was not met. Therefore, the property is not applicable to this response.
190197
Verdict: yes
191198
199+
ID: 6
192200
Property: The final answer is formatted as a numbered list.
193201
Evidence: N/A. Trusted evidence from tool calls or the user prompt is not required in order to determine the format of the final answer.
194202
Rationale: The final answer is formatted as a numbered list from 1 to 4, e.g. "1. The total number of employees is 110\n2...".
@@ -287,7 +295,7 @@ def format_auto_rater_prompt(
287295
)
288296

289297
rubrics_text = "\n".join([
290-
f"* {r.rubric_content.text_property}"
298+
f"* [id: {r.rubric_id}] {r.rubric_content.text_property}"
291299
for r in self._effective_rubrics_list
292300
])
293301

src/google/adk/evaluation/rubric_based_multi_turn_trajectory_evaluator.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
STEP 3: Follow the steps outlined in STEP 2, thinking out loud. As you think, refer to specific assistant responses and count their turn numbers within the <conversation_history>.
6060
STEP 4: Review the thoughts and the original property.
6161
STEP 5: Output the final verdict.
62+
ID: [[Copy the "id" field of this property from the properties list, verbatim. Omit this line only if the property has no "id" field.]]
6263
Property: [[Repeat the property in STEP 1 again.]]
6364
Rationale: [[Explain your reasoning for the verdict.]]
6465
Verdict: [[yes|no]]
@@ -69,6 +70,7 @@
6970
STEP 3: ...
7071
STEP 4: ...
7172
STEP 5: ...
73+
ID: ...
7274
Property: ...
7375
Rationale: ...
7476
Verdict: ...
@@ -80,6 +82,7 @@
8082
STEP 3: Reviewing the <conversation_history>, I find that in Assistant Turn 2, the agent includes the function call default_api.grammar_check(sentence="the dog walks on the a park"). This matches the required function name.
8183
STEP 4: The assistant successfully called the specified function in Turn 2.
8284
STEP 5: yes
85+
ID: 1
8386
Property: Does the agent run function call 'default_api.grammar_check'?
8487
Rationale: The agent's response in Assistant Turn 2 contains the function call 'default_api.grammar_check' within a proper code block and with the correct function name.
8588
Verdict: yes
@@ -89,6 +92,7 @@
8992
STEP 3: In Assistant Turn 2, the agent's response includes the function call default_api.grammar_check(sentence="the dog walks on the a park"). The parameter 'sentence' is present, and the value assigned to it is "the dog walks on the a park", which is identical to the reference value.
9093
STEP 4: The parameter 'sentence' is present and its value is exactly the same as the reference value in Assistant Turn 2.
9194
STEP 5: yes
95+
ID: 2
9296
Property: The sentence parameter in the grammar_check function call must be 'the dog walks on the a park'.
9397
Rationale: The agent's response in Assistant Turn 2 includes the 'sentence' parameter in the function call 'default_api.grammar_check', and the value assigned to it is exactly the same as the reference value.
9498
Verdict: yes
@@ -100,6 +104,7 @@
100104
STEP 3: Reviewing the <conversation_history>, I find that in Assistant Turn 2, the agent includes a function call default_api.get_web_search_results. This does not match the required 'default_api.search_via_perplexity'. I checked all other assistant turns and no other function call matches.
101105
STEP 4: The agent did not use the function 'default_api.search_via_perplexity' in any turn.
102106
STEP 5: no
107+
ID: 1
103108
Property: Does the agent run function call 'default_api.search_via_perplexity'?
104109
Rationale: The agent called 'default_api.get_web_search_results' in Assistant Turn 2, not 'default_api.search_via_perplexity'. The correct function was not called in any turn.
105110
Verdict: no
@@ -109,6 +114,7 @@
109114
STEP 3: Based on the previous evaluation, the agent did not include a function call 'default_api.search_via_perplexity' in any of its turns. Therefore, this property cannot be fulfilled.
110115
STEP 4: The property cannot be fulfilled because the agent did not use the specified function call.
111116
STEP 5: no
117+
ID: 2
112118
Property: Does the agent provide function call 'default_api.search_via_perplexity' with input parameter 'keyword' that is valid compared to the reference 'keyword'= 'GPT-4o vs GPT-3.5 cost comparison' and based on the following guideline? Guideline for 'keyword': 'The wording can differ. The agent response is valid if it conveys similar core content as the reference response. Less efficient and minor inaccurate phrasing is acceptable.'
113119
Rationale: The agent did not use the function call 'default_api.search_via_perplexity' in any of its responses throughout the conversation.
114120
Verdict: no
@@ -353,6 +359,7 @@ def format_auto_rater_prompt(
353359
rubrics_list = []
354360
for r in self._effective_rubrics_list:
355361
rubrics_dict = {
362+
"id": r.rubric_id,
356363
"property": r.rubric_content.text_property,
357364
}
358365
if r.type:

src/google/adk/evaluation/rubric_based_tool_use_quality_v1.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,13 @@
4242
"no": The agent's response did not fulfill the property.
4343
4444
# For each property started with a new line, follow these steps:
45-
STEP 1: Repeat the property, word for word, without making any changes. Keep everything including punctuation and capitalization as-is.
45+
STEP 1: Repeat the property text (excluding the "[id: ...]" tag), word for word, without making any changes. Keep everything including punctuation and capitalization as-is.
4646
STEP 2: Determine the steps needed to **exactly**, **precisely** and **completely** determine whether the agent's response fulfilled the property.
4747
STEP 3: Follow the steps outlined in STEP 2, thinking out loud.
4848
STEP 4: Review the thoughts and the original property.
4949
STEP 5: Output the final verdict.
50-
Property: [[Repeat the property in STEP 1 again.]]
50+
ID: [[Copy the id shown in the "[id: ...]" tag for this property, verbatim. Omit this line only if no tag is shown.]]
51+
Property: [[Repeat the property in STEP 1 again, without the "[id: ...]" tag.]]
5152
Rationale: [[Explain your reasoning for the verdict.]]
5253
Verdict: [[yes|no]]
5354
@@ -57,6 +58,7 @@
5758
STEP 3: ...
5859
STEP 4: ...
5960
STEP 5: ...
61+
ID: ...
6062
Property: ...
6163
Rationale: ...
6264
Verdict: ...
@@ -69,6 +71,7 @@
6971
STEP 3: The response includes a function call 'default_api.grammar_check'.
7072
STEP 4: The function call format and the function name are correct.
7173
STEP 5: yes
74+
ID: 1
7275
Property: Does the agent run function call 'default_api.grammar_check'?
7376
Rationale: The agent's response contains the function call 'default_api.grammar_check' within a proper code block and with the correct function name.
7477
Verdict: yes
@@ -78,6 +81,7 @@
7881
STEP 3: The agent's response includes the function call `default_api.grammar_check(sentence="the dog walks on the a park")`. The parameter 'sentence' is present, and the value assigned to it is "the dog walks on the a park", which is identical to the reference value.
7982
STEP 4: The parameter 'sentence' is present and its value is exactly the same as the reference value.
8083
STEP 5: yes
84+
ID: 2
8185
Property: Does the agent provide function call 'default_api.grammar_check' with input parameter 'sentence' that is valid compared to the reference 'sentence'= 'the dog walks on the a park' and based on the following guideline? Guideline for 'sentence': 'The wording can differ. The agent response is valid if it conveys similar core content as the reference response. Less efficient and minor inaccurate phrasing is acceptable. The default value is None, if the reference response includes this parameter with value equal to the default value but it is not provided in the agent response, then evaluate it as valid.'
8286
Rationale: The agent's response includes the 'sentence' parameter in the function call 'default_api.grammar_check', and the value assigned to it is exactly the same as the reference value, thus satisfying the given guideline.
8387
Verdict: yes
@@ -89,6 +93,7 @@
8993
STEP 3: The response includes a function call `default_api.get_web_search_results`, which does not match 'default_api.search_via_perplexity'.
9094
STEP 4: The function name does not match.
9195
STEP 5: no
96+
ID: 1
9297
Property: Does the agent run function call 'default_api.search_via_perplexity'?
9398
Rationale: The agent called 'default_api.get_web_search_results', not 'default_api.search_via_perplexity'.
9499
Verdict: no
@@ -98,6 +103,7 @@
98103
STEP 3: N/A
99104
STEP 4: N/A
100105
STEP 5: yes
106+
ID: 2
101107
Property: Does the agent provide function call 'default_api.search_via_perplexity' with input parameter 'keyword' that is valid compared to the reference 'keyword'= 'GPT-4o vs GPT-3.5 cost comparison' and based on the following guideline? Guideline for 'keyword': 'The wording can differ. The agent response is valid if it conveys similar core content as the reference response. Less efficient and minor inaccurate phrasing is acceptable.'
102108
Rationale: The agent did not use the function call 'default_api.search_via_perplexity'.
103109
Verdict: yes
@@ -178,7 +184,7 @@ def format_auto_rater_prompt(
178184
)
179185

180186
rubrics_text = "\n".join([
181-
f"* {r.rubric_content.text_property}"
187+
f"* [id: {r.rubric_id}] {r.rubric_content.text_property}"
182188
for r in self._effective_rubrics_list
183189
])
184190

0 commit comments

Comments
 (0)