Files
Tao Chen 8d379168b2 Python: Improve python sample validation workflow (#7350)
* Add skill to replace hardcoded foundry project endpoint and model

* Include more samples and fix migration samples part 1

* Fix migration samples

* Replace Foundry hosted agent validation skill

* Fix hosted agent file sample

* Fix agent result format

* Reorganize jobs

* Update discovery heuristic for apps

* Split agents into even more jobs

* Add toolbox endpoint

* Add more pre configured resources

* Fix using deployed agent sample

* Add sample status

* Add playbook

* Exclude hidden folder in sample discovery

* Install autogen dependencies

* Grant azure search RBAC role

* Increase timeout for magentic

* Build search resouce id deterministically

* Remove grant in the workflow

* Move azure cli login closer to when the sample actually runs

* Refactor playbook

* Fix using deployed agent sample

* Actually save the playbooks

* Fix action syntax error

* Fix magentic sample

* Address copilot comments

* Fix link inspection

* Address comments

* Correct README

* Fix playbook path

* Remove trailing space
2026-08-03 22:28:53 +00:00

87 lines
3.4 KiB
Python

# Copyright (c) Microsoft. All rights reserved.
"""Executor that replays cached playbooks before falling back to the agent."""
import asyncio
import logging
from agent_framework import Executor, WorkflowContext, handler
from sample_validation.models import (
DiscoveryResult,
ReplayResult,
RunResult,
RunStatus,
SampleInfo,
ValidationConfig,
)
from sample_validation.playbook import Playbook, PlaybookStore, replay_playbook
logger = logging.getLogger(__name__)
class ReplayCachedPlaybooksExecutor(Executor):
"""Replay cached playbooks and split samples into cached vs. agent-bound.
For every discovered sample we look for a playbook whose recorded hash still
matches the sample's current content. Matching playbooks are replayed
deterministically (no LLM). Samples that pass are returned as cached
results; samples without a valid playbook, or whose replay fails, are passed
on for agent-driven validation (which will refresh the playbook).
"""
def __init__(self, config: ValidationConfig) -> None:
super().__init__(id="replay_cached_playbooks")
self.config = config
self._store = PlaybookStore(config.playbooks_dir) if config.playbooks_dir else None
@handler
async def replay(self, discovery: DiscoveryResult, ctx: WorkflowContext[ReplayResult]) -> None:
"""Attempt cached replay for each sample, then forward the remainder."""
samples = discovery.samples
if not self.config.use_cache or self._store is None or not samples:
await ctx.send_message(ReplayResult(remaining_samples=samples, cached_results=[]))
return
store = self._store
candidates: list[tuple[SampleInfo, Playbook]] = []
remaining: list[SampleInfo] = []
for sample in samples:
playbook = store.is_valid_for(sample)
if playbook is None:
remaining.append(sample)
else:
candidates.append((sample, playbook))
print(
f"\nCache check: {len(candidates)} sample(s) have a valid playbook, "
f"{len(remaining)} need the agent."
)
cached_results: list[RunResult] = []
if candidates:
semaphore = asyncio.Semaphore(max(1, self.config.max_parallel_workers))
async def _run(sample: SampleInfo, playbook: Playbook) -> tuple[SampleInfo, RunResult]:
async with semaphore:
result = await replay_playbook(
playbook,
self.config.python_root,
self.config.samples_dir,
)
return sample, result
for coro in asyncio.as_completed([_run(s, pb) for s, pb in candidates]):
sample, result = await coro
if result.status == RunStatus.SUCCESS:
cached_results.append(result)
print(f"[cache hit] {sample.relative_path}")
else:
# Replay failed: hand the sample to the agent to re-validate and refresh the playbook.
logger.info(f"Cached replay failed for {sample.relative_path}: {result.error}")
print(f"[cache miss] {sample.relative_path} (replay failed, will use agent)")
remaining.append(sample)
await ctx.send_message(ReplayResult(remaining_samples=remaining, cached_results=cached_results))