|
36 | 36 | from ..agents.llm_agent import Agent |
37 | 37 | from ..agents.run_config import RunConfig |
38 | 38 | from ..agents.run_config import StreamingMode |
| 39 | +from ..apps.app import App |
39 | 40 | from ..artifacts.base_artifact_service import BaseArtifactService |
40 | 41 | from ..artifacts.in_memory_artifact_service import InMemoryArtifactService |
41 | 42 | from ..events.event import Event |
42 | 43 | from ..flows.llm_flows.functions import handle_function_calls_live |
43 | 44 | from ..memory.base_memory_service import BaseMemoryService |
44 | 45 | from ..memory.in_memory_memory_service import InMemoryMemoryService |
45 | 46 | from ..models.llm_request import LlmRequest |
| 47 | +from ..plugins.base_plugin import BasePlugin |
46 | 48 | from ..runners import Runner |
47 | 49 | from ..sessions.base_session_service import BaseSessionService |
48 | 50 | from ..sessions.in_memory_session_service import InMemorySessionService |
|
73 | 75 | _DEFAULT_AUTHOR = "agent" |
74 | 76 |
|
75 | 77 |
|
| 78 | +def _build_eval_runner_kwargs( |
| 79 | + root_agent: Agent, |
| 80 | + app_name: str, |
| 81 | + app: Optional[App], |
| 82 | + internal_eval_plugins: list[BasePlugin], |
| 83 | +) -> dict[str, Any]: |
| 84 | + """Returns the Runner kwargs used to evaluate `root_agent`. |
| 85 | +
|
| 86 | + When `app` is provided, the Runner is built from a copy of the App with the |
| 87 | + internal eval plugins merged into `app.plugins`, so the App's |
| 88 | + `context_cache_config`, `resumability_config`, and any other |
| 89 | + application-wide configuration participate in the eval run. The copy leaves |
| 90 | + the caller's App instance untouched, and `root_agent` is overridden so the |
| 91 | + Runner targets the agent the caller asked to evaluate, which may be a |
| 92 | + sub-agent. When `app` is None, the Runner is built from the bare |
| 93 | + `root_agent` with only the internal eval plugins. |
| 94 | + """ |
| 95 | + if app is None: |
| 96 | + return { |
| 97 | + "app_name": app_name, |
| 98 | + "agent": root_agent, |
| 99 | + "plugins": internal_eval_plugins, |
| 100 | + } |
| 101 | + |
| 102 | + runner_app = app.model_copy( |
| 103 | + update={ |
| 104 | + "plugins": list(app.plugins) + internal_eval_plugins, |
| 105 | + "root_agent": root_agent, |
| 106 | + } |
| 107 | + ) |
| 108 | + return {"app": runner_app, "app_name": app_name} |
| 109 | + |
| 110 | + |
76 | 111 | class EvalCaseResponses(BaseModel): |
77 | 112 | """Contains multiple responses associated with an EvalCase. |
78 | 113 |
|
@@ -349,20 +384,30 @@ async def _process_query( |
349 | 384 | """Process a query using the agent and evaluation dataset.""" |
350 | 385 | module_path = f"{module_name}" |
351 | 386 | agent_module = importlib.import_module(module_path) |
352 | | - root_agent = agent_module.agent.root_agent |
| 387 | + # Prefer the wrapping `App` when the module exposes one, so that |
| 388 | + # `app.plugins`, context-cache, and resumability configs participate |
| 389 | + # in eval runs the same way they do for `adk web` / `adk run`. |
| 390 | + app_obj = getattr(agent_module.agent, "app", None) |
| 391 | + if isinstance(app_obj, App): |
| 392 | + root_agent = app_obj.root_agent |
| 393 | + else: |
| 394 | + app_obj = None |
| 395 | + root_agent = agent_module.agent.root_agent |
353 | 396 |
|
354 | 397 | reset_func = getattr(agent_module.agent, "reset_data", None) |
355 | 398 |
|
356 | 399 | agent_to_evaluate = root_agent |
357 | 400 | if agent_name: |
358 | | - agent_to_evaluate = root_agent.find_agent(agent_name) |
359 | | - assert agent_to_evaluate, f"Sub-Agent `{agent_name}` not found." |
| 401 | + found_agent = root_agent.find_agent(agent_name) |
| 402 | + assert found_agent, f"Sub-Agent `{agent_name}` not found." |
| 403 | + agent_to_evaluate = found_agent |
360 | 404 |
|
361 | 405 | return await EvaluationGenerator._generate_inferences_from_root_agent( |
362 | 406 | agent_to_evaluate, |
363 | 407 | user_simulator=user_simulator, |
364 | 408 | reset_func=reset_func, |
365 | 409 | initial_session=initial_session, |
| 410 | + app=app_obj, |
366 | 411 | ) |
367 | 412 |
|
368 | 413 | @staticmethod |
@@ -467,8 +512,14 @@ async def _generate_inferences_from_root_agent_live( |
467 | 512 | artifact_service: Optional[BaseArtifactService] = None, |
468 | 513 | memory_service: Optional[BaseMemoryService] = None, |
469 | 514 | live_timeout_seconds: int = DEFAULT_LIVE_TIMEOUT_SECONDS, |
| 515 | + app: Optional[App] = None, |
470 | 516 | ) -> list[Invocation]: |
471 | | - """Scrapes the root agent in coordination with the user simulator in live mode.""" |
| 517 | + """Scrapes the root agent in coordination with the user simulator in live mode. |
| 518 | +
|
| 519 | + Mirrors `_generate_inferences_from_root_agent`: when `app` is provided the |
| 520 | + Runner carries the App's plugins and configuration, otherwise the bare |
| 521 | + `root_agent` is used. |
| 522 | + """ |
472 | 523 | if not session_service: |
473 | 524 | session_service = InMemorySessionService() |
474 | 525 |
|
@@ -504,13 +555,21 @@ async def _generate_inferences_from_root_agent_live( |
504 | 555 | request_intercepter_plugin = _RequestIntercepterPlugin( |
505 | 556 | name="request_intercepter_plugin" |
506 | 557 | ) |
507 | | - async with Runner( |
| 558 | + runner_kwargs = _build_eval_runner_kwargs( |
| 559 | + root_agent=root_agent, |
508 | 560 | app_name=app_name, |
509 | | - agent=root_agent, |
| 561 | + app=app, |
| 562 | + internal_eval_plugins=[ |
| 563 | + request_intercepter_plugin, |
| 564 | + ensure_retry_options_plugin, |
| 565 | + ], |
| 566 | + ) |
| 567 | + |
| 568 | + async with Runner( |
| 569 | + **runner_kwargs, |
510 | 570 | artifact_service=artifact_service, |
511 | 571 | session_service=session_service, |
512 | 572 | memory_service=memory_service, |
513 | | - plugins=[request_intercepter_plugin, ensure_retry_options_plugin], |
514 | 573 | ) as runner: |
515 | 574 | events: list[Event] = [] |
516 | 575 |
|
@@ -573,8 +632,17 @@ async def _generate_inferences_from_root_agent( |
573 | 632 | session_service: Optional[BaseSessionService] = None, |
574 | 633 | artifact_service: Optional[BaseArtifactService] = None, |
575 | 634 | memory_service: Optional[BaseMemoryService] = None, |
| 635 | + app: Optional[App] = None, |
576 | 636 | ) -> list[Invocation]: |
577 | | - """Scrapes the root agent in coordination with the user simulator.""" |
| 637 | + """Scrapes the root agent in coordination with the user simulator. |
| 638 | +
|
| 639 | + If `app` is provided, the eval Runner is built from a copy of the App |
| 640 | + with internal eval plugins merged into `app.plugins`, preserving the |
| 641 | + App's `context_cache_config`, `resumability_config`, and any other |
| 642 | + application-wide configuration. Otherwise the Runner is built from |
| 643 | + the bare `root_agent` with only the internal eval plugins, matching |
| 644 | + the legacy behavior. |
| 645 | + """ |
578 | 646 |
|
579 | 647 | if not session_service: |
580 | 648 | session_service = InMemorySessionService() |
@@ -611,13 +679,21 @@ async def _generate_inferences_from_root_agent( |
611 | 679 | ensure_retry_options_plugin = EnsureRetryOptionsPlugin( |
612 | 680 | name="ensure_retry_options" |
613 | 681 | ) |
614 | | - async with Runner( |
| 682 | + runner_kwargs = _build_eval_runner_kwargs( |
| 683 | + root_agent=root_agent, |
615 | 684 | app_name=app_name, |
616 | | - agent=root_agent, |
| 685 | + app=app, |
| 686 | + internal_eval_plugins=[ |
| 687 | + request_intercepter_plugin, |
| 688 | + ensure_retry_options_plugin, |
| 689 | + ], |
| 690 | + ) |
| 691 | + |
| 692 | + async with Runner( |
| 693 | + **runner_kwargs, |
617 | 694 | artifact_service=artifact_service, |
618 | 695 | session_service=session_service, |
619 | 696 | memory_service=memory_service, |
620 | | - plugins=[request_intercepter_plugin, ensure_retry_options_plugin], |
621 | 697 | ) as runner: |
622 | 698 | events: list[Event] = [] |
623 | 699 | while True: |
|
0 commit comments