Download src/service_analysis/execution.py from FlyingNunchucks/14-data-analysis-agent: direct link, hf CLI and curl.
- Browser
- Download file 10.9 kB
-
https://huggingface.co/spaces/FlyingNunchucks/14-data-analysis-agent/resolve/main/src/service_analysis/execution.py
- Command line
-
hf download hf://spaces/FlyingNunchucks/14-data-analysis-agent/src/service_analysis/execution.py
-
curl -L -o execution.py https://huggingface.co/spaces/FlyingNunchucks/14-data-analysis-agent/resolve/main/src/service_analysis/execution.py
10.9 kB
| """Application-owned bounded execution with real, streamable lifecycle events.""" | |
| from typing import Literal | |
| from uuid import uuid4 | |
| from pydantic import ValidationError | |
| from service_analysis.schemas import Contract | |
| from service_analysis.analysis import ( | |
| CalculationMismatch, SummaryResult, ComparisonResult, _dataset_id, | |
| summarize_resolution, compare_periods, | |
| ) | |
| from service_analysis.decomposition import DecompositionResult, decompose_case_mix | |
| from service_analysis.workload import WorkloadResult, analyze_workload | |
| from service_analysis.planning import ( | |
| MAX_STEPS, ToolCall, SummaryCall, ComparisonCall, DecompositionCall, | |
| PlanRejected, compile_plan, | |
| ) | |
| ToolResult = SummaryResult | ComparisonResult | DecompositionResult | WorkloadResult | |
| StepStatus = Literal["completed", "partial", "blocked", "failed", "skipped"] | |
| class StepOutcome(Contract): | |
| step_id: str | |
| call: ToolCall | |
| depends_on: tuple[str, ...] | |
| status: StepStatus | |
| result: ToolResult | None = None | |
| code: str | None = None | |
| class RunEvent(Contract): | |
| run_id: str | |
| sequence: int | |
| kind: Literal["run_started", "plan_accepted", "plan_rejected", "correction_requested", | |
| "followup_requested", "followup_declined", "planner_failed", | |
| "step_started", "step_completed", "step_partial", "step_blocked", | |
| "step_failed", "step_skipped", "budget_exhausted", "run_finished"] | |
| round_number: int | None = None | |
| step_id: str | None = None | |
| result_id: str | None = None | |
| code: str | None = None | |
| class PlanningContext(Contract): | |
| round_number: int | |
| remaining_steps: int | |
| outcomes: tuple[StepOutcome, ...] | |
| rejection_code: str | None = None | |
| class RunReport(Contract): | |
| run_id: str | |
| dataset_id: str | |
| status: Literal["completed", "partial", "blocked", "failed", "rejected"] | |
| accepted_rounds: int | |
| correction_used: bool | |
| tool_calls: int | |
| reserved_steps: int | |
| outcomes: tuple[StepOutcome, ...] | |
| events: tuple[RunEvent, ...] | |
| # Registry is application-owned. Plans can name only the closed ToolCall union. | |
| def _summary(dataset, call): | |
| return summarize_resolution(dataset, call.period, call.grouping) | |
| def _comparison(dataset, call): | |
| return compare_periods(dataset, call.baseline, call.comparison, call.grouping) | |
| def _decomposition(dataset, call): | |
| return decompose_case_mix(dataset, call.baseline, call.comparison) | |
| def _workload(dataset, call): | |
| return analyze_workload(dataset, call.opening_period) | |
| _HANDLERS = { | |
| "summarize": (_summary, SummaryResult), | |
| "compare": (_comparison, ComparisonResult), | |
| "decompose": (_decomposition, DecompositionResult), | |
| "workload": (_workload, WorkloadResult), | |
| } | |
| def _scope(call): | |
| if isinstance(call, (SummaryCall, ComparisonCall)): | |
| return "resolution" if call.grouping == "overall" else call.grouping | |
| return "resolution" if isinstance(call, DecompositionCall) else "workload" | |
| def _validated_result(value, result_type, call, dataset_id): | |
| if not isinstance(value, result_type): | |
| raise ValueError("Unexpected result type") | |
| # Revalidate even model instances; do not trust a constructed/bypassed object. | |
| result = result_type.model_validate(value.model_dump(mode="json")) | |
| if result.dataset_id != dataset_id: | |
| raise ValueError("Result belongs to a different dataset") | |
| if isinstance(call, SummaryCall): | |
| valid = result.period == call.period and result.grouping == call.grouping | |
| elif isinstance(call, (ComparisonCall, DecompositionCall)): | |
| comparison = result if isinstance(call, ComparisonCall) else result.overall | |
| valid = comparison.baseline.period == call.baseline and comparison.comparison.period == call.comparison | |
| if isinstance(call, ComparisonCall): | |
| valid = valid and comparison.baseline.grouping == call.grouping and comparison.comparison.grouping == call.grouping | |
| else: | |
| valid = result.opening_period == call.opening_period | |
| if not valid: | |
| raise ValueError("Result does not match requested analysis") | |
| return result | |
| def run_analysis_iter(dataset, initial_plan, *, correction=None, followup=None): | |
| """Yield actual RunEvent objects, followed by one final RunReport. | |
| correction(context) may supply one replacement plan across the entire run. | |
| followup(context) may supply one additional plan or None. These optional | |
| adapters are application code, not tool names or executable model output. | |
| No inference provider is invoked by this module. Exhaust the iterator to | |
| finish; abandoning it performs no further work and has no resume guarantee. | |
| """ | |
| run_id = "run-" + uuid4().hex | |
| dataset_id = _dataset_id(dataset) | |
| events, outcomes, compiled_history = [], [], [] | |
| tool_calls = accepted_rounds = 0 | |
| correction_used = False | |
| terminal_issue = None | |
| def event(kind, **values): | |
| value = RunEvent(run_id=run_id, sequence=len(events) + 1, kind=kind, **values) | |
| events.append(value) | |
| return value | |
| def context(round_number, rejection=None): | |
| return PlanningContext(round_number=round_number, | |
| remaining_steps=MAX_STEPS - len(compiled_history), | |
| outcomes=tuple(outcomes), rejection_code=rejection) | |
| yield event("run_started") | |
| pending = initial_plan | |
| for round_number in (0, 1): | |
| if round_number: | |
| if followup is None: | |
| break | |
| if len(compiled_history) == MAX_STEPS: | |
| terminal_issue = "STEP_BUDGET_EXHAUSTED" | |
| yield event("budget_exhausted", round_number=round_number, code=terminal_issue) | |
| break | |
| yield event("followup_requested", round_number=round_number) | |
| try: | |
| pending = followup(context(round_number)) | |
| except Exception: | |
| terminal_issue = "FOLLOWUP_ADAPTER_FAILED" | |
| yield event("planner_failed", round_number=round_number, code=terminal_issue) | |
| break | |
| if pending is None: | |
| yield event("followup_declined", round_number=round_number) | |
| break | |
| try: | |
| steps = compile_plan(pending, dataset.coverage, tuple(compiled_history)) | |
| except PlanRejected as rejection: | |
| yield event("plan_rejected", round_number=round_number, code=rejection.code) | |
| if correction is None or correction_used: | |
| terminal_issue = rejection.code | |
| break | |
| correction_used = True | |
| yield event("correction_requested", round_number=round_number, code=rejection.code) | |
| try: | |
| replacement = correction(context(round_number, rejection.code)) | |
| except Exception: | |
| terminal_issue = "CORRECTION_ADAPTER_FAILED" | |
| yield event("planner_failed", round_number=round_number, code=terminal_issue) | |
| break | |
| try: | |
| steps = compile_plan(replacement, dataset.coverage, tuple(compiled_history)) | |
| except PlanRejected as second: | |
| terminal_issue = second.code | |
| yield event("plan_rejected", round_number=round_number, code=second.code) | |
| break | |
| compiled_history.extend(steps) | |
| accepted_rounds += 1 | |
| yield event("plan_accepted", round_number=round_number) | |
| for step in steps: | |
| previous = {value.step_id: value for value in outcomes} | |
| if any(previous[dependency].status != "completed" for dependency in step.depends_on): | |
| outcome = StepOutcome(**step.model_dump(), status="skipped", code="DEPENDENCY_NOT_COMPLETE") | |
| else: | |
| try: | |
| dataset.require(_scope(step.call)) | |
| except ValueError: | |
| outcome = StepOutcome(**step.model_dump(), status="blocked", code="DATA_QUALITY_BLOCKED") | |
| else: | |
| yield event("step_started", round_number=round_number, step_id=step.step_id) | |
| tool_calls += 1 | |
| handler, result_type = _HANDLERS[step.call.operation] | |
| try: | |
| value = handler(dataset, step.call) | |
| except CalculationMismatch: | |
| outcome = StepOutcome(**step.model_dump(), status="failed", code="CALCULATION_MISMATCH") | |
| except ValidationError: | |
| outcome = StepOutcome(**step.model_dump(), status="failed", code="TOOL_OUTPUT_INVALID") | |
| except ValueError: | |
| outcome = StepOutcome(**step.model_dump(), status="blocked", code="TOOL_INPUT_REJECTED") | |
| except Exception: | |
| outcome = StepOutcome(**step.model_dump(), status="failed", code="TOOL_EXECUTION_FAILED") | |
| else: | |
| try: | |
| result = _validated_result(value, result_type, step.call, dataset_id) | |
| except (ValueError, TypeError, ValidationError): | |
| outcome = StepOutcome(**step.model_dump(), status="failed", code="TOOL_OUTPUT_INVALID") | |
| else: | |
| status = "completed" if result.status == "complete" else "partial" | |
| outcome = StepOutcome(**step.model_dump(), status=status, result=result) | |
| outcomes.append(outcome) | |
| yield event("step_" + outcome.status, round_number=round_number, | |
| step_id=step.step_id, code=outcome.code, | |
| result_id=outcome.result.result_id if outcome.result is not None else None) | |
| if outcomes and all(value.status == "completed" for value in outcomes) and terminal_issue is None: | |
| status = "completed" | |
| elif any(value.result is not None for value in outcomes): | |
| status = "partial" | |
| elif any(value.status == "failed" for value in outcomes) or terminal_issue in ( | |
| "FOLLOWUP_ADAPTER_FAILED", "CORRECTION_ADAPTER_FAILED" | |
| ): | |
| status = "failed" | |
| elif outcomes: | |
| status = "blocked" | |
| else: | |
| status = "rejected" | |
| yield event("run_finished", code=status) | |
| yield RunReport(run_id=run_id, dataset_id=dataset_id, status=status, | |
| accepted_rounds=accepted_rounds, correction_used=correction_used, | |
| tool_calls=tool_calls, reserved_steps=len(compiled_history), | |
| outcomes=tuple(outcomes), events=tuple(events)) | |
| def run_analysis(dataset, initial_plan, *, correction=None, followup=None): | |
| """Convenience wrapper returning the final report from the same event loop.""" | |
| final = None | |
| for item in run_analysis_iter(dataset, initial_plan, correction=correction, followup=followup): | |
| if isinstance(item, RunReport): | |
| final = item | |
| return final | |