-
Notifications
You must be signed in to change notification settings - Fork 3.4k
fix(eval): include intermediate text in final response match #5698
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,7 +25,10 @@ | |
| from .app_details import AppDetails | ||
| from .common import EvalBaseModel | ||
| from .eval_case import get_all_tool_calls_with_responses | ||
| from .eval_case import IntermediateData | ||
| from .eval_case import IntermediateDataType | ||
| from .eval_case import Invocation | ||
| from .eval_case import InvocationEvents | ||
| from .eval_metrics import RubricScore | ||
| from .evaluator import EvalStatus | ||
|
|
||
|
|
@@ -44,8 +47,32 @@ class Label(enum.Enum): | |
|
|
||
|
|
||
| def get_text_from_content( | ||
| content: Optional[genai_types.Content], | ||
| content: Optional[Union[genai_types.Content, Invocation]], | ||
| *, | ||
| include_intermediate_responses_in_final: bool = False, | ||
| ) -> Optional[str]: | ||
| if isinstance(content, Invocation): | ||
| if not include_intermediate_responses_in_final: | ||
| return get_text_from_content(content.final_response) | ||
|
|
||
| parts: list[str] = [] | ||
| if isinstance(content.intermediate_data, InvocationEvents): | ||
| for event in content.intermediate_data.invocation_events: | ||
| text = get_text_from_content(event.content) | ||
| if text: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While elif isinstance(content.intermediate_data, IntermediateData):
for author, parts in content.intermediate_data.intermediate_responses:
text = get_text_from_content(genai_types.Content(parts=parts))
if text:
parts.append(text)(Note: you'll need to import |
||
| parts.append(text) | ||
| elif isinstance(content.intermediate_data, IntermediateData): | ||
| for _, response_parts in content.intermediate_data.intermediate_responses: | ||
| text = get_text_from_content(genai_types.Content(parts=response_parts)) | ||
| if text: | ||
| parts.append(text) | ||
|
|
||
| final_text = get_text_from_content(content.final_response) | ||
| if final_text: | ||
| parts.append(final_text) | ||
|
|
||
| return "\n".join(parts) if parts else None | ||
|
|
||
| if content and content.parts: | ||
| return "\n".join([p.text for p in content.parts if p.text]) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ | |
| from google.adk.evaluation.app_details import AgentDetails | ||
| from google.adk.evaluation.app_details import AppDetails | ||
| from google.adk.evaluation.eval_case import IntermediateData | ||
| from google.adk.evaluation.eval_case import Invocation | ||
| from google.adk.evaluation.eval_case import InvocationEvent | ||
| from google.adk.evaluation.eval_case import InvocationEvents | ||
| from google.adk.evaluation.eval_rubrics import RubricScore | ||
|
|
@@ -45,7 +46,7 @@ def test_get_text_from_content_with_content_and_none_parts(): | |
| def test_get_text_from_content_with_empty_parts(): | ||
| """Tests get_text_from_content with an empty parts list.""" | ||
| content = genai_types.Content(parts=[]) | ||
| assert get_text_from_content(content) == None | ||
| assert get_text_from_content(content) is None | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch on updating this to use |
||
|
|
||
|
|
||
| def test_get_text_from_content_with_parts_but_no_text(): | ||
|
|
@@ -88,6 +89,75 @@ def test_get_text_from_content_with_mixed_parts(): | |
| assert get_text_from_content(content) == "Hello\nWorld" | ||
|
|
||
|
|
||
| def test_get_text_from_content_with_invocation_full_response(): | ||
| invocation = Invocation( | ||
| user_content=genai_types.Content(parts=[genai_types.Part(text="user")]), | ||
| intermediate_data=InvocationEvents( | ||
| invocation_events=[ | ||
| InvocationEvent( | ||
| author="agent", | ||
| content=genai_types.Content( | ||
| parts=[genai_types.Part(text="thinking aloud")] | ||
| ), | ||
| ), | ||
| InvocationEvent( | ||
| author="tool", | ||
| content=genai_types.Content( | ||
| parts=[ | ||
| genai_types.Part( | ||
| function_call=genai_types.FunctionCall( | ||
| name="lookup" | ||
| ) | ||
| ) | ||
| ] | ||
| ), | ||
| ), | ||
| ] | ||
| ), | ||
| final_response=genai_types.Content( | ||
| parts=[genai_types.Part(text="final answer")] | ||
| ), | ||
| ) | ||
|
|
||
| assert get_text_from_content(invocation) == "final answer" | ||
| assert ( | ||
| get_text_from_content( | ||
| invocation, include_intermediate_responses_in_final=True | ||
| ) | ||
| == "thinking aloud\nfinal answer" | ||
| ) | ||
|
|
||
|
|
||
| def test_get_text_from_content_with_intermediate_data_full_response(): | ||
| invocation = Invocation( | ||
| user_content=genai_types.Content(parts=[genai_types.Part(text="user")]), | ||
| intermediate_data=IntermediateData( | ||
| intermediate_responses=[ | ||
| ("agent", [genai_types.Part(text="legacy intro")]), | ||
| ( | ||
| "tool", | ||
| [ | ||
| genai_types.Part( | ||
| function_call=genai_types.FunctionCall(name="lookup") | ||
| ) | ||
| ], | ||
| ), | ||
| ] | ||
| ), | ||
| final_response=genai_types.Content( | ||
| parts=[genai_types.Part(text="final answer")] | ||
| ), | ||
| ) | ||
|
|
||
| assert get_text_from_content(invocation) == "final answer" | ||
| assert ( | ||
| get_text_from_content( | ||
| invocation, include_intermediate_responses_in_final=True | ||
| ) | ||
| == "legacy intro\nfinal answer" | ||
| ) | ||
|
|
||
|
|
||
| def test_get_eval_status_with_none_score(): | ||
| """Tests get_eval_status returns NOT_EVALUATED for a None score.""" | ||
| assert get_eval_status(score=None, threshold=0.5) == EvalStatus.NOT_EVALUATED | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding
or ""forreferenceandresponseto ensure the prompt remains valid JSON-like structure even ifget_text_from_contentreturnsNone.This would be consistent with the change you made in
rubric_based_final_response_quality_v1.py.