Skip to content

Commit b0cdecf

Browse files
he-yufengcopybara-github
authored andcommitted
fix: include grounding metadata in rubric judge prompt
Merge #5834 Fixes #5831 PiperOrigin-RevId: 964952922
1 parent 983c280 commit b0cdecf

7 files changed

Lines changed: 184 additions & 12 deletions

src/google/adk/evaluation/eval_case.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ class InvocationEvent(EvalBaseModel):
6767
content: Optional[genai_types.Content] = None
6868
"""The content of the event."""
6969

70+
grounding_metadata: Optional[genai_types.GroundingMetadata] = None
71+
"""Grounding metadata emitted with the event."""
72+
7073

7174
class InvocationEvents(EvalBaseModel):
7275
"""A container for events that occur during the course of an invocation."""

src/google/adk/evaluation/evaluation_generator.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -975,7 +975,7 @@ def convert_events_to_eval_invocations(
975975
):
976976
app_details = app_details_per_invocation[invocation_id]
977977

978-
events_to_add = []
978+
events_to_add: list[Event] = []
979979
for event in events:
980980
current_author = (event.author or _DEFAULT_AUTHOR).lower()
981981

@@ -999,23 +999,44 @@ def convert_events_to_eval_invocations(
999999
final_response = event.content
10001000
final_event = event
10011001

1002+
should_add_event = event.grounding_metadata is not None
10021003
for p in event.content.parts:
10031004
if (
10041005
p.function_call
10051006
or p.function_response
10061007
or p.text
10071008
or p.inline_data
10081009
):
1009-
events_to_add.append(event)
1010+
should_add_event = True
10101011
break
1011-
1012-
invocation_events = [
1013-
InvocationEvent(author=e.author, content=e.content)
1014-
for e in events_to_add
1015-
if final_event is None
1016-
or e is not final_event
1017-
or e.get_function_calls()
1018-
]
1012+
if should_add_event:
1013+
events_to_add.append(event)
1014+
elif event.grounding_metadata is not None:
1015+
events_to_add.append(event)
1016+
1017+
invocation_events = []
1018+
for e in events_to_add:
1019+
# Keep the final event only when it carries tool calls (so the judge
1020+
# still sees the function call) or grounding metadata; every other
1021+
# event is always included.
1022+
if (
1023+
final_event is not None
1024+
and e is final_event
1025+
and not e.get_function_calls()
1026+
and not e.grounding_metadata
1027+
):
1028+
continue
1029+
invocation_events.append(
1030+
InvocationEvent(
1031+
author=e.author,
1032+
content=(
1033+
e.content
1034+
if e is not final_event or e.get_function_calls()
1035+
else None
1036+
),
1037+
grounding_metadata=e.grounding_metadata,
1038+
)
1039+
)
10191040
invocations.append(
10201041
Invocation(
10211042
invocation_id=invocation_id,

src/google/adk/evaluation/llm_as_judge_utils.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import enum
1818
import statistics
1919
from typing import Any
20+
from typing import cast
2021
from typing import Optional
2122
from typing import Union
2223

@@ -155,6 +156,20 @@ class _ToolCallsAndResponses(EvalBaseModel):
155156
tool_calls_and_response: list[_ToolCallAndResponse]
156157

157158

159+
class _GroundingMetadataEntry(EvalBaseModel):
160+
"""Internal data model to capture grounding metadata from an invocation."""
161+
162+
step: int
163+
author: Optional[str] = None
164+
grounding_metadata: genai_types.GroundingMetadata
165+
166+
167+
class _GroundingMetadataEntries(EvalBaseModel):
168+
"""Internal data model used for serializing grounding metadata."""
169+
170+
grounding_metadata: list[_GroundingMetadataEntry]
171+
172+
158173
def get_tool_calls_and_responses_as_json_str(
159174
intermediate_data: Optional[IntermediateDataType],
160175
) -> str:
@@ -189,3 +204,37 @@ def get_tool_calls_and_responses_as_json_str(
189204
exclude_defaults=True,
190205
exclude_none=True,
191206
)
207+
208+
209+
def get_grounding_metadata_as_json_str(
210+
intermediate_data: Optional[IntermediateDataType],
211+
) -> str:
212+
"""Returns a JSON string representation of grounding metadata."""
213+
if not isinstance(intermediate_data, InvocationEvents):
214+
return "No grounding metadata was provided."
215+
216+
grounding_metadata = []
217+
for idx, invocation_event in enumerate(intermediate_data.invocation_events):
218+
if invocation_event.grounding_metadata:
219+
grounding_metadata.append(
220+
_GroundingMetadataEntry(
221+
step=idx,
222+
author=invocation_event.author,
223+
grounding_metadata=invocation_event.grounding_metadata,
224+
)
225+
)
226+
227+
if not grounding_metadata:
228+
return "No grounding metadata was provided."
229+
230+
return cast(
231+
str,
232+
_GroundingMetadataEntries(
233+
grounding_metadata=grounding_metadata
234+
).model_dump_json(
235+
indent=2,
236+
exclude_unset=True,
237+
exclude_defaults=True,
238+
exclude_none=True,
239+
),
240+
)

src/google/adk/evaluation/rubric_based_final_response_quality_v1.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from .eval_case import InvocationEvents
2626
from .eval_metrics import EvalMetric
2727
from .eval_metrics import RubricsBasedCriterion
28+
from .llm_as_judge_utils import get_grounding_metadata_as_json_str
2829
from .llm_as_judge_utils import get_text_from_content
2930
from .llm_as_judge_utils import get_tool_calls_and_responses_as_json_str
3031
from .llm_as_judge_utils import get_tool_declarations_as_json_str
@@ -45,8 +46,9 @@
4546
4647
# Key Evaluation Principles
4748
Your evaluation must follow a two-part process: first, collect trusted evidence from the agent's work, and second, judge the final answer against it.
48-
1. **Establish Trusted Evidence from Tool Calls**: You must first examine the agent's tool calls to determine if they are procedurally sound, meaning that the agent used the appropriate tools with logical parameters to address the user's prompt.
49-
* Your ONLY sources of truth are the <user_prompt> and the direct output ('tool_response') from PROCEDURALLY SOUND tool calls found in the <response_steps>. Examples of procedural flaws include:
49+
1. **Establish Trusted Evidence from Tool Calls and Grounding**: You must first examine the agent's tool calls to determine if they are procedurally sound, meaning that the agent used the appropriate tools with logical parameters to address the user's prompt.
50+
* Your ONLY sources of truth are the <user_prompt>, the direct output ('tool_response') from PROCEDURALLY SOUND tool calls found in the <response_steps>, and model-supplied grounding metadata found in <grounding_metadata>.
51+
* Grounding metadata is trusted evidence for model-internal tools such as google_search whose raw search results may not appear as function tool responses. Examples of procedural flaws include:
5052
* The agent failed to call a tool that will enable it to answer the user's prompt despite having all the necessary parameters to do so.
5153
* The agent called the tool with incorrect or missing parameters.
5254
* The agent called a tool that does not exist, or called a tool with a parameter that does not exist.
@@ -222,6 +224,9 @@
222224
<response_steps>
223225
{response_steps}
224226
</response_steps>
227+
<grounding_metadata>
228+
{grounding_metadata}
229+
</grounding_metadata>
225230
<final_answer>
226231
{final_response}
227232
</final_answer>
@@ -303,6 +308,9 @@ def format_auto_rater_prompt(
303308
response_steps = get_tool_calls_and_responses_as_json_str(
304309
actual_invocation.intermediate_data
305310
)
311+
grounding_metadata = get_grounding_metadata_as_json_str(
312+
actual_invocation.intermediate_data
313+
)
306314

307315
app_details = actual_invocation.app_details
308316
if app_details:
@@ -333,6 +341,7 @@ def format_auto_rater_prompt(
333341
tool_declarations=tool_declarations,
334342
user_input=user_input,
335343
response_steps=response_steps,
344+
grounding_metadata=grounding_metadata,
336345
final_response=final_response,
337346
rubrics=rubrics_text,
338347
)

tests/unittests/evaluation/test_evaluation_generator.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,31 @@ def test_convert_multi_agent_final_responses(
294294
assert intermediate_events[0].author == "agent1"
295295
assert intermediate_events[0].content.parts[0].text == "First response"
296296

297+
def test_convert_preserves_grounding_metadata_from_final_response(
298+
self,
299+
):
300+
"""Tests final grounding metadata is available to evaluators."""
301+
grounding_metadata = types.GroundingMetadata(
302+
web_search_queries=["recent AI news"]
303+
)
304+
events = [
305+
_build_event("user", [types.Part(text="What's new in AI?")], "inv1"),
306+
Event(
307+
author="agent",
308+
content=types.Content(parts=[types.Part(text="Here are sources.")]),
309+
invocation_id="inv1",
310+
grounding_metadata=grounding_metadata,
311+
),
312+
]
313+
314+
invocations = EvaluationGenerator.convert_events_to_eval_invocations(events)
315+
316+
assert len(invocations) == 1
317+
invocation_events = invocations[0].intermediate_data.invocation_events
318+
assert len(invocation_events) == 1
319+
assert invocation_events[0].content is None
320+
assert invocation_events[0].grounding_metadata == grounding_metadata
321+
297322

298323
class TestNormalizeLiveTranscriptions:
299324
"""Test cases for EvaluationGenerator._normalize_live_transcriptions method."""

tests/unittests/evaluation/test_llm_as_judge_utils.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from google.adk.evaluation.evaluator import EvalStatus
2727
from google.adk.evaluation.llm_as_judge_utils import get_average_rubric_score
2828
from google.adk.evaluation.llm_as_judge_utils import get_eval_status
29+
from google.adk.evaluation.llm_as_judge_utils import get_grounding_metadata_as_json_str
2930
from google.adk.evaluation.llm_as_judge_utils import get_text_from_content
3031
from google.adk.evaluation.llm_as_judge_utils import get_tool_calls_and_responses_as_json_str
3132
from google.adk.evaluation.llm_as_judge_utils import get_tool_declarations_as_json_str
@@ -362,3 +363,36 @@ def test_get_tool_calls_and_responses_as_json_str_with_invocation_events_multipl
362363
]
363364
}
364365
assert json.loads(json_str) == expected_json
366+
367+
368+
def test_get_grounding_metadata_as_json_str_with_invocation_events():
369+
"""Tests grounding metadata is serialized for LLM-as-judge prompts."""
370+
grounding_metadata = genai_types.GroundingMetadata(
371+
web_search_queries=["recent AI news"]
372+
)
373+
intermediate_data = InvocationEvents(
374+
invocation_events=[
375+
InvocationEvent(
376+
author="agent",
377+
content=None,
378+
grounding_metadata=grounding_metadata,
379+
)
380+
]
381+
)
382+
383+
json_str = get_grounding_metadata_as_json_str(intermediate_data)
384+
parsed = json.loads(json_str)
385+
386+
assert parsed["grounding_metadata"][0]["step"] == 0
387+
assert parsed["grounding_metadata"][0]["author"] == "agent"
388+
assert parsed["grounding_metadata"][0]["grounding_metadata"][
389+
"web_search_queries"
390+
] == ["recent AI news"]
391+
392+
393+
def test_get_grounding_metadata_as_json_str_without_metadata():
394+
"""Tests empty grounding metadata serialization."""
395+
assert (
396+
get_grounding_metadata_as_json_str(InvocationEvents())
397+
== "No grounding metadata was provided."
398+
)

tests/unittests/evaluation/test_rubric_based_final_response_quality_v1.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,37 @@ def test_format_auto_rater_prompt_with_intermediate_data(
182182
assert '"result": "ok"' in prompt
183183

184184

185+
def test_format_auto_rater_prompt_with_grounding_metadata(
186+
evaluator: RubricBasedFinalResponseQualityV1Evaluator,
187+
):
188+
"""Tests grounding metadata is included as trusted evidence."""
189+
grounding_metadata = genai_types.GroundingMetadata(
190+
web_search_queries=["recent AI news"]
191+
)
192+
invocation = Invocation(
193+
user_content=genai_types.Content(
194+
parts=[genai_types.Part(text="What's new in AI?")]
195+
),
196+
final_response=genai_types.Content(
197+
parts=[genai_types.Part(text="Here are sources.")]
198+
),
199+
intermediate_data=InvocationEvents(
200+
invocation_events=[
201+
InvocationEvent(
202+
author="agent",
203+
content=None,
204+
grounding_metadata=grounding_metadata,
205+
)
206+
]
207+
),
208+
)
209+
prompt = evaluator.format_auto_rater_prompt(invocation, None)
210+
211+
assert "<grounding_metadata>" in prompt
212+
assert "recent AI news" in prompt
213+
assert "model-supplied grounding metadata" in prompt
214+
215+
185216
def test_format_auto_rater_prompt_with_app_details_no_tools(
186217
evaluator: RubricBasedFinalResponseQualityV1Evaluator,
187218
):

0 commit comments

Comments
 (0)