agentevolve-optimizer 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_evolve/__init__.py +722 -0
- agent_evolve/agentic.py +2800 -0
- agent_evolve/api.py +767 -0
- agent_evolve/application/__init__.py +1580 -0
- agent_evolve/application/action_allocation.py +744 -0
- agent_evolve/application/action_allocation_frame.py +347 -0
- agent_evolve/application/action_allocation_frame_commit.py +185 -0
- agent_evolve/application/action_allocation_frame_commit_v3.py +184 -0
- agent_evolve/application/action_allocation_frame_v3.py +338 -0
- agent_evolve/application/action_archive_value.py +497 -0
- agent_evolve/application/action_evidence_consistency.py +455 -0
- agent_evolve/application/action_forecast_partitioning.py +1471 -0
- agent_evolve/application/action_metric_projection.py +211 -0
- agent_evolve/application/action_role_value.py +680 -0
- agent_evolve/application/action_score_authorities.py +363 -0
- agent_evolve/application/action_structural_signature.py +116 -0
- agent_evolve/application/action_target_realization.py +402 -0
- agent_evolve/application/agentic_evolution.py +7734 -0
- agent_evolve/application/agentic_portfolio_residual_expert.py +835 -0
- agent_evolve/application/anchor_residual_identification.py +463 -0
- agent_evolve/application/archive_conditioned_action_target.py +208 -0
- agent_evolve/application/artifact_journal.py +246 -0
- agent_evolve/application/artifact_replay.py +347 -0
- agent_evolve/application/budgeted_optimizer.py +1828 -0
- agent_evolve/application/calibrated_campaign.py +485 -0
- agent_evolve/application/calibrated_current_prefix_forecast_opportunity.py +322 -0
- agent_evolve/application/calibrated_positive_gain_opportunity.py +1581 -0
- agent_evolve/application/campaign_capacity_recourse.py +254 -0
- agent_evolve/application/campaign_contextual_outcomes.py +119 -0
- agent_evolve/application/campaign_diagnostic_blocks.py +930 -0
- agent_evolve/application/campaign_evidence_registry.py +262 -0
- agent_evolve/application/campaign_execution.py +2537 -0
- agent_evolve/application/campaign_generation_audit.py +942 -0
- agent_evolve/application/campaign_learning.py +1812 -0
- agent_evolve/application/campaign_learning_runtime.py +1977 -0
- agent_evolve/application/campaign_search_phase.py +227 -0
- agent_evolve/application/campaign_selector_context_extension.py +220 -0
- agent_evolve/application/campaign_variation_envelope.py +649 -0
- agent_evolve/application/campaign_variation_trace.py +451 -0
- agent_evolve/application/candidate_archive_consequence.py +128 -0
- agent_evolve/application/causal_opportunity_portfolio_gate.py +385 -0
- agent_evolve/application/composite_outcome_updater.py +145 -0
- agent_evolve/application/composition_portfolio_selection.py +363 -0
- agent_evolve/application/concurrent_stage.py +144 -0
- agent_evolve/application/contextual_action_allocation.py +181 -0
- agent_evolve/application/contextual_campaign_outcomes.py +267 -0
- agent_evolve/application/contextual_campaign_planning.py +1366 -0
- agent_evolve/application/contextual_delayed_credit.py +651 -0
- agent_evolve/application/contextual_search_controller.py +2374 -0
- agent_evolve/application/current_prefix_forecast_opportunity.py +714 -0
- agent_evolve/application/decision_metric_projection.py +112 -0
- agent_evolve/application/derived_action_semantics.py +129 -0
- agent_evolve/application/detailed_evaluation.py +449 -0
- agent_evolve/application/earned_lineage.py +1011 -0
- agent_evolve/application/effective_choice_audit.py +484 -0
- agent_evolve/application/empirical_consequence_calibration.py +908 -0
- agent_evolve/application/evaluation_accounting.py +325 -0
- agent_evolve/application/evaluation_cache.py +199 -0
- agent_evolve/application/evaluation_escrow.py +547 -0
- agent_evolve/application/evaluation_recourse.py +253 -0
- agent_evolve/application/event_recorder.py +151 -0
- agent_evolve/application/evolution_campaign.py +1840 -0
- agent_evolve/application/executable_hypothesis.py +323 -0
- agent_evolve/application/factorial_branch_pilot.py +772 -0
- agent_evolve/application/finite_acquisition_capacity_recourse.py +672 -0
- agent_evolve/application/finite_acquisition_residual_expert.py +373 -0
- agent_evolve/application/finite_acquisition_variation_envelope.py +802 -0
- agent_evolve/application/finite_action_hypothesis_semantics.py +446 -0
- agent_evolve/application/finite_action_selection.py +188 -0
- agent_evolve/application/finite_action_set.py +306 -0
- agent_evolve/application/finite_action_transition.py +537 -0
- agent_evolve/application/finite_variation_eligibility.py +296 -0
- agent_evolve/application/forecast_geometry_portfolio.py +799 -0
- agent_evolve/application/forecast_opportunity_shadow_calibration.py +316 -0
- agent_evolve/application/front_proximity_admission.py +311 -0
- agent_evolve/application/front_proximity_parent_basis.py +458 -0
- agent_evolve/application/frozen_hurdle_score.py +659 -0
- agent_evolve/application/g3_causal_screen.py +2257 -0
- agent_evolve/application/g3_causal_validation.py +1046 -0
- agent_evolve/application/g3_postseal_curation.py +818 -0
- agent_evolve/application/gated_agentic_generator.py +205 -0
- agent_evolve/application/generation_feedback.py +293 -0
- agent_evolve/application/generative_proposal_journal.py +185 -0
- agent_evolve/application/geometry_conditional_elasticity.py +453 -0
- agent_evolve/application/global_wave_action_allocation.py +1151 -0
- agent_evolve/application/head_mass_conditional_seat.py +268 -0
- agent_evolve/application/identifiable_reflection_evidence.py +1147 -0
- agent_evolve/application/identifiable_reflection_learning.py +395 -0
- agent_evolve/application/identifiable_reflection_request.py +364 -0
- agent_evolve/application/in_memory_residual_archive.py +341 -0
- agent_evolve/application/insight_memory.py +1804 -0
- agent_evolve/application/live_runtime_manifest.py +758 -0
- agent_evolve/application/llm_task_queue.py +769 -0
- agent_evolve/application/matched_finite_action_block.py +409 -0
- agent_evolve/application/materialized_action_broker.py +2328 -0
- agent_evolve/application/materialized_action_constraints.py +83 -0
- agent_evolve/application/materialized_variation.py +211 -0
- agent_evolve/application/multi_option_evolution.py +1536 -0
- agent_evolve/application/outcome_adaptive_action_racing.py +2827 -0
- agent_evolve/application/outcome_adaptive_residual_campaign_runtime.py +580 -0
- agent_evolve/application/outcome_adaptive_residual_portfolio_evolution.py +3671 -0
- agent_evolve/application/outcome_conditioned_portfolio_selection.py +1374 -0
- agent_evolve/application/outcome_relation.py +193 -0
- agent_evolve/application/paired_allocation_comparison.py +241 -0
- agent_evolve/application/paired_block_schedule.py +127 -0
- agent_evolve/application/parent_measurement.py +226 -0
- agent_evolve/application/pareto_archive.py +811 -0
- agent_evolve/application/portfolio_campaign_runtime.py +4739 -0
- agent_evolve/application/portfolio_evolution.py +2950 -0
- agent_evolve/application/portfolio_hypothesis_observations.py +814 -0
- agent_evolve/application/portfolio_memory_attribution.py +581 -0
- agent_evolve/application/portfolio_memory_dose.py +788 -0
- agent_evolve/application/portfolio_memory_matched_control.py +938 -0
- agent_evolve/application/portfolio_memory_transfer.py +297 -0
- agent_evolve/application/portfolio_optimization_memory.py +363 -0
- agent_evolve/application/portfolio_outcome_feedback.py +1613 -0
- agent_evolve/application/portfolio_projection.py +335 -0
- agent_evolve/application/portfolio_recombination.py +2032 -0
- agent_evolve/application/post_evolution_reflection.py +834 -0
- agent_evolve/application/postcommit_rank_authority.py +245 -0
- agent_evolve/application/precommitted_portfolio_racing.py +2762 -0
- agent_evolve/application/prequential_archive_opportunity_calibration.py +1154 -0
- agent_evolve/application/prequential_residual_exploration.py +343 -0
- agent_evolve/application/prequential_score_portfolio.py +954 -0
- agent_evolve/application/projections.py +292 -0
- agent_evolve/application/protected_action_committee.py +1027 -0
- agent_evolve/application/protected_branch_pilot.py +376 -0
- agent_evolve/application/protected_current_prefix_forecast_opportunity.py +552 -0
- agent_evolve/application/provider_replay.py +910 -0
- agent_evolve/application/rank_balanced_causal_pilot.py +1372 -0
- agent_evolve/application/recombination_residual_expert.py +403 -0
- agent_evolve/application/reflection_workflow.py +571 -0
- agent_evolve/application/region_conditional_credit.py +911 -0
- agent_evolve/application/residual_campaign_runtime.py +531 -0
- agent_evolve/application/residual_headroom_campaign_runtime.py +459 -0
- agent_evolve/application/residual_headroom_ledger.py +1544 -0
- agent_evolve/application/residual_learning_transaction.py +396 -0
- agent_evolve/application/residual_portfolio_evolution.py +1228 -0
- agent_evolve/application/residual_reachability.py +749 -0
- agent_evolve/application/residual_stage_credit.py +499 -0
- agent_evolve/application/same_prefix_paired_audit.py +1580 -0
- agent_evolve/application/semantic_coverage_score_portfolio.py +838 -0
- agent_evolve/application/sequential_lineage_allocation.py +1017 -0
- agent_evolve/application/sequential_market_replay.py +1395 -0
- agent_evolve/application/sequential_residual_campaign_runtime.py +305 -0
- agent_evolve/application/sequential_residual_portfolio_evolution.py +940 -0
- agent_evolve/application/single_score_action_allocation.py +299 -0
- agent_evolve/application/source_exposure_allocation.py +906 -0
- agent_evolve/application/staged_memory.py +210 -0
- agent_evolve/application/stratified_cold_start_allocation.py +732 -0
- agent_evolve/application/support_guarded_hurdle_score.py +549 -0
- agent_evolve/application/target_conditioned_action_forecast.py +595 -0
- agent_evolve/application/target_conditioned_campaign.py +566 -0
- agent_evolve/application/treatment_assignment.py +201 -0
- agent_evolve/application/trusted_objective_evidence.py +217 -0
- agent_evolve/application/two_stage_action_evolution.py +1131 -0
- agent_evolve/application/v8lite_allocation_policy.py +1083 -0
- agent_evolve/application/v9_candidate_policy.py +1303 -0
- agent_evolve/bootstrap.py +108 -0
- agent_evolve/campaign_presets.py +517 -0
- agent_evolve/campaign_profiles.py +452 -0
- agent_evolve/campaign_variation_topology.py +288 -0
- agent_evolve/campaign_workload.py +950 -0
- agent_evolve/cli.py +797 -0
- agent_evolve/contract.py +241 -0
- agent_evolve/core/__init__.py +91 -0
- agent_evolve/core/action_semantics.py +411 -0
- agent_evolve/core/authored.py +105 -0
- agent_evolve/core/formatting.py +286 -0
- agent_evolve/core/optimization_semantics.py +324 -0
- agent_evolve/core/problem.py +167 -0
- agent_evolve/core/results.py +323 -0
- agent_evolve/core/stats.py +70 -0
- agent_evolve/core/telemetry.py +100 -0
- agent_evolve/domain/__init__.py +89 -0
- agent_evolve/domain/artifact.py +162 -0
- agent_evolve/domain/durable_text.py +68 -0
- agent_evolve/domain/event.py +1454 -0
- agent_evolve/domain/finite_action_set.py +426 -0
- agent_evolve/domain/finite_variation.py +526 -0
- agent_evolve/domain/generative_emission.py +559 -0
- agent_evolve/domain/ids.py +163 -0
- agent_evolve/domain/inline_text.py +106 -0
- agent_evolve/domain/insight.py +27 -0
- agent_evolve/domain/lineage.py +737 -0
- agent_evolve/domain/llm_task_queue.py +960 -0
- agent_evolve/domain/outcome.py +96 -0
- agent_evolve/domain/patch.py +854 -0
- agent_evolve/domain/typed_json.py +542 -0
- agent_evolve/domain/variation_space.py +158 -0
- agent_evolve/driver.py +1014 -0
- agent_evolve/harness/__init__.py +29 -0
- agent_evolve/harness/base.py +242 -0
- agent_evolve/harness/directives.py +163 -0
- agent_evolve/harness/generative_seal.py +479 -0
- agent_evolve/harness/registry.py +41 -0
- agent_evolve/infrastructure/__init__.py +39 -0
- agent_evolve/infrastructure/artifacts/__init__.py +6 -0
- agent_evolve/infrastructure/artifacts/_verification.py +67 -0
- agent_evolve/infrastructure/artifacts/filesystem.py +343 -0
- agent_evolve/infrastructure/artifacts/in_memory.py +73 -0
- agent_evolve/infrastructure/asyncio_runtime.py +109 -0
- agent_evolve/infrastructure/authored_runtime.py +188 -0
- agent_evolve/infrastructure/authored_worker.py +171 -0
- agent_evolve/infrastructure/clock.py +53 -0
- agent_evolve/infrastructure/events/__init__.py +6 -0
- agent_evolve/infrastructure/events/_validation.py +89 -0
- agent_evolve/infrastructure/events/in_memory.py +56 -0
- agent_evolve/infrastructure/events/jsonl.py +193 -0
- agent_evolve/infrastructure/exception_provenance.py +215 -0
- agent_evolve/infrastructure/ids.py +118 -0
- agent_evolve/infrastructure/lineage_codec.py +1836 -0
- agent_evolve/infrastructure/outcome_adaptive_phase_journal.py +170 -0
- agent_evolve/infrastructure/residual_headroom_journal.py +221 -0
- agent_evolve/infrastructure/resource_lease.py +370 -0
- agent_evolve/infrastructure/sanitization/__init__.py +8 -0
- agent_evolve/infrastructure/sanitization/strict_json.py +484 -0
- agent_evolve/infrastructure/sequential_phase_journal.py +170 -0
- agent_evolve/infrastructure/stream_liveness.py +383 -0
- agent_evolve/infrastructure/subprocess_boundary.py +136 -0
- agent_evolve/integrations/__init__.py +1 -0
- agent_evolve/integrations/botorch/__init__.py +28 -0
- agent_evolve/integrations/botorch/finite_qlognehvi.py +190 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_batch.py +155 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_batch_identity.py +20 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_batch_worker.py +55 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_identity.py +22 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_worker.py +55 -0
- agent_evolve/integrations/botorch/subprocess_qlognehvi.py +261 -0
- agent_evolve/integrations/botorch/subprocess_qlognehvi_batch.py +273 -0
- agent_evolve/integrations/completion.py +242 -0
- agent_evolve/integrations/pydantic_ai/__init__.py +441 -0
- agent_evolve/integrations/pydantic_ai/action_forecast.py +1068 -0
- agent_evolve/integrations/pydantic_ai/agentic_generator.py +2308 -0
- agent_evolve/integrations/pydantic_ai/async_generator.py +1604 -0
- agent_evolve/integrations/pydantic_ai/boundary_codec.py +1526 -0
- agent_evolve/integrations/pydantic_ai/calibrated_portfolio_campaign.py +756 -0
- agent_evolve/integrations/pydantic_ai/calibrated_portfolio_selection.py +7537 -0
- agent_evolve/integrations/pydantic_ai/campaign_acquisition.py +609 -0
- agent_evolve/integrations/pydantic_ai/execution_binding.py +138 -0
- agent_evolve/integrations/pydantic_ai/forecast_geometry_action_committee.py +217 -0
- agent_evolve/integrations/pydantic_ai/harness.py +159 -0
- agent_evolve/integrations/pydantic_ai/heterogeneous_model_execution.py +306 -0
- agent_evolve/integrations/pydantic_ai/hierarchical_residual_adaptive_semantic_view.py +179 -0
- agent_evolve/integrations/pydantic_ai/json_schema_dialect.py +108 -0
- agent_evolve/integrations/pydantic_ai/materialized_hierarchical_residual_expert.py +952 -0
- agent_evolve/integrations/pydantic_ai/materialized_portfolio_judge.py +520 -0
- agent_evolve/integrations/pydantic_ai/model_execution_profile.py +659 -0
- agent_evolve/integrations/pydantic_ai/outbound_request_manifest.py +1170 -0
- agent_evolve/integrations/pydantic_ai/portable_residual_consequence_features.py +575 -0
- agent_evolve/integrations/pydantic_ai/portfolio_selection.py +422 -0
- agent_evolve/integrations/pydantic_ai/progress_aware_openrouter.py +416 -0
- agent_evolve/integrations/pydantic_ai/provider_attempt_join.py +1523 -0
- agent_evolve/integrations/pydantic_ai/provider_free_calibrated_runner.py +607 -0
- agent_evolve/integrations/pydantic_ai/queued_runner.py +2634 -0
- agent_evolve/integrations/pydantic_ai/reconciled_residual_reachability.py +1417 -0
- agent_evolve/integrations/pydantic_ai/residual_forecast_geometry.py +445 -0
- agent_evolve/integrations/pydantic_ai/residual_reachability.py +674 -0
- agent_evolve/integrations/pydantic_ai/residual_semantic_cells.py +239 -0
- agent_evolve/integrations/pydantic_ai/sealed_output_replay.py +1068 -0
- agent_evolve/integrations/pydantic_ai/semantic_coverage_residual_portfolio.py +770 -0
- agent_evolve/integrations/pydantic_ai/semantic_decision_replay.py +383 -0
- agent_evolve/integrations/pydantic_ai/support_adaptive_residual_portfolio.py +135 -0
- agent_evolve/integrations/pydantic_ai/trusted_residual_prompt_context.py +143 -0
- agent_evolve/integrations/pydantic_ai/validated_openrouter_model.py +107 -0
- agent_evolve/integrations/pymoo_adapter.py +242 -0
- agent_evolve/policies/__init__.py +17 -0
- agent_evolve/policies/check.py +469 -0
- agent_evolve/policies/emit_scaffold.py +451 -0
- agent_evolve/policies/feedback/__init__.py +37 -0
- agent_evolve/policies/feedback/held_out_asn.py +1325 -0
- agent_evolve/policies/genetic.py +607 -0
- agent_evolve/policies/llm_backoff.py +183 -0
- agent_evolve/policies/llm_chooser.py +226 -0
- agent_evolve/policies/llm_generator.py +1760 -0
- agent_evolve/policies/llm_init.py +267 -0
- agent_evolve/policies/llm_operator.py +109 -0
- agent_evolve/policies/llm_prior.py +194 -0
- agent_evolve/policies/llm_surrogate.py +334 -0
- agent_evolve/policies/measurement_evidence.py +704 -0
- agent_evolve/policies/memory/__init__.py +223 -0
- agent_evolve/policies/memory/balanced_subset_blocks.py +707 -0
- agent_evolve/policies/memory/compatibility_matching.py +593 -0
- agent_evolve/policies/memory/global_falsification.py +1841 -0
- agent_evolve/policies/memory/prompt_shape.py +503 -0
- agent_evolve/policies/memory/randomized_subset.py +714 -0
- agent_evolve/policies/memory/staged_causal.py +1270 -0
- agent_evolve/policies/memory/treatment_compliance.py +759 -0
- agent_evolve/policies/objective_resolution/__init__.py +17 -0
- agent_evolve/policies/objective_resolution/fixed_grid.py +364 -0
- agent_evolve/policies/operator_portfolio.py +407 -0
- agent_evolve/policies/reguidance.py +1133 -0
- agent_evolve/policies/reward/__init__.py +83 -0
- agent_evolve/policies/reward/affine_candidate_consequence.py +156 -0
- agent_evolve/policies/reward/affine_candidate_consequence_3d.py +159 -0
- agent_evolve/policies/reward/affine_hypervolume.py +490 -0
- agent_evolve/policies/reward/affine_hypervolume_3d.py +567 -0
- agent_evolve/policies/reward/contextual_marginal_utility.py +318 -0
- agent_evolve/policies/reward/frozen_archive.py +360 -0
- agent_evolve/policies/reward/frozen_wave_archive.py +368 -0
- agent_evolve/policies/search_state.py +208 -0
- agent_evolve/policies/selection/__init__.py +345 -0
- agent_evolve/policies/selection/acquisition_certified_slate.py +684 -0
- agent_evolve/policies/selection/affine_frontier_context.py +330 -0
- agent_evolve/policies/selection/affine_frontier_target.py +473 -0
- agent_evolve/policies/selection/archive_elite.py +1346 -0
- agent_evolve/policies/selection/calibrated_portfolio_binding.py +640 -0
- agent_evolve/policies/selection/calibrated_slate.py +1394 -0
- agent_evolve/policies/selection/calibrated_slate_codec.py +579 -0
- agent_evolve/policies/selection/common_candidate_pool.py +685 -0
- agent_evolve/policies/selection/diagnostic_sampling.py +319 -0
- agent_evolve/policies/selection/disjoint_pairs.py +479 -0
- agent_evolve/policies/selection/elite_explorer.py +719 -0
- agent_evolve/policies/selection/finite_action.py +187 -0
- agent_evolve/policies/selection/finite_option_prompt_projection.py +377 -0
- agent_evolve/policies/selection/finite_palette_evidence.py +247 -0
- agent_evolve/policies/selection/forecast_calibration.py +922 -0
- agent_evolve/policies/selection/frontier_probe_slate.py +814 -0
- agent_evolve/policies/selection/frozen_archive_pairs.py +762 -0
- agent_evolve/policies/selection/full_support_slate.py +91 -0
- agent_evolve/policies/selection/meaningful_direction.py +240 -0
- agent_evolve/policies/selection/memory_dose_feasibility.py +259 -0
- agent_evolve/policies/selection/model_anchored_slate.py +826 -0
- agent_evolve/policies/selection/phenotype_recourse.py +979 -0
- agent_evolve/policies/selection/proposal_support.py +368 -0
- agent_evolve/policies/selection/random_portfolio.py +254 -0
- agent_evolve/policies/selection/regret_bounded_slate.py +1084 -0
- agent_evolve/policies/selection/residual_frontier.py +463 -0
- agent_evolve/policies/selection/residual_frontier_target.py +605 -0
- agent_evolve/policies/selection/structural_posterior_slate.py +1571 -0
- agent_evolve/policies/selection/target_conditioned_allocator.py +648 -0
- agent_evolve/policies/selection/target_conditioned_features.py +812 -0
- agent_evolve/policies/selection/target_conditioned_prequential.py +1527 -0
- agent_evolve/policies/selection/task_keyed_palette.py +906 -0
- agent_evolve/policies/semantics.py +147 -0
- agent_evolve/policies/structure.py +362 -0
- agent_evolve/policies/structured_output_budget.py +62 -0
- agent_evolve/policies/surrogate.py +696 -0
- agent_evolve/policies/variation/__init__.py +1 -0
- agent_evolve/policies/variation/compositional_finite_catalog.py +426 -0
- agent_evolve/policies/variation/crossover_inheritance.py +575 -0
- agent_evolve/policies/variation/disjoint_recombination.py +611 -0
- agent_evolve/policies/variation/exact_composition_capacity.py +214 -0
- agent_evolve/policies/variation/exact_parent_crossover.py +950 -0
- agent_evolve/policies/variation/multiscale_restart_catalog.py +372 -0
- agent_evolve/policies/variation/source_union_finite_catalog.py +403 -0
- agent_evolve/policies/variation/typed_patch.py +1981 -0
- agent_evolve/policies/weighted_prior.py +394 -0
- agent_evolve/ports/__init__.py +383 -0
- agent_evolve/ports/action_allocation.py +733 -0
- agent_evolve/ports/action_allocation_frame.py +1153 -0
- agent_evolve/ports/action_allocation_frame_commit.py +294 -0
- agent_evolve/ports/action_allocation_frame_commit_v3.py +432 -0
- agent_evolve/ports/action_allocation_frame_v3.py +995 -0
- agent_evolve/ports/action_forecast.py +1568 -0
- agent_evolve/ports/action_metric_projection.py +165 -0
- agent_evolve/ports/agentic_generator.py +1561 -0
- agent_evolve/ports/archive_context.py +136 -0
- agent_evolve/ports/artifact_sanitizer.py +44 -0
- agent_evolve/ports/artifact_store.py +225 -0
- agent_evolve/ports/clock.py +13 -0
- agent_evolve/ports/contextual_search_allocation.py +827 -0
- agent_evolve/ports/decision_metric_projection.py +258 -0
- agent_evolve/ports/event_store.py +55 -0
- agent_evolve/ports/executable_hypothesis.py +557 -0
- agent_evolve/ports/finite_acquisition.py +377 -0
- agent_evolve/ports/finite_acquisition_batch.py +296 -0
- agent_evolve/ports/finite_acquisition_batch_json.py +164 -0
- agent_evolve/ports/finite_acquisition_json.py +247 -0
- agent_evolve/ports/finite_acquisition_space.py +168 -0
- agent_evolve/ports/finite_action_selection.py +348 -0
- agent_evolve/ports/finite_action_set.py +256 -0
- agent_evolve/ports/frontier_target.py +396 -0
- agent_evolve/ports/generation_failure.py +43 -0
- agent_evolve/ports/hard_feasibility.py +233 -0
- agent_evolve/ports/id_factory.py +34 -0
- agent_evolve/ports/llm_task_queue.py +93 -0
- agent_evolve/ports/objective_resolution.py +419 -0
- agent_evolve/ports/paired_allocation_comparison.py +401 -0
- agent_evolve/ports/paired_block_schedule.py +475 -0
- agent_evolve/ports/parent_measurement.py +336 -0
- agent_evolve/ports/portfolio_memory_dose.py +643 -0
- agent_evolve/ports/portfolio_selection.py +3169 -0
- agent_evolve/ports/postcommit_rank_authority.py +467 -0
- agent_evolve/ports/presented_action_evidence.py +794 -0
- agent_evolve/ports/resource_lease.py +162 -0
- agent_evolve/ports/structured_generator.py +734 -0
- agent_evolve/ports/structured_output_budget.py +120 -0
- agent_evolve/ports/subprocess_boundary.py +138 -0
- agent_evolve/ports/treatment_assignment.py +466 -0
- agent_evolve/ports/variation_catalog.py +76 -0
- agent_evolve/ports/variation_source.py +226 -0
- agent_evolve/proposal_mode.py +157 -0
- agent_evolve/proposers/__init__.py +10 -0
- agent_evolve/proposers/random_proposer.py +188 -0
- agent_evolve/provider_accounting.py +163 -0
- agent_evolve/py.typed +0 -0
- agent_evolve/reference_method.py +1570 -0
- agent_evolve/session/__init__.py +11 -0
- agent_evolve/session/authorship.py +864 -0
- agent_evolve/session/evaluate.py +236 -0
- agent_evolve/session/fidelity.py +237 -0
- agent_evolve/session/genetic_loop.py +742 -0
- agent_evolve/session/loop.py +803 -0
- agent_evolve/session/screening.py +671 -0
- agent_evolve/settings.py +376 -0
- agent_evolve/workload_kit.py +368 -0
- agent_evolve/workload_prompt.py +398 -0
- agentevolve_optimizer-0.5.0.dist-info/METADATA +599 -0
- agentevolve_optimizer-0.5.0.dist-info/RECORD +414 -0
- agentevolve_optimizer-0.5.0.dist-info/WHEEL +5 -0
- agentevolve_optimizer-0.5.0.dist-info/entry_points.txt +2 -0
- agentevolve_optimizer-0.5.0.dist-info/licenses/LICENSE +21 -0
- agentevolve_optimizer-0.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,910 @@
|
|
|
1
|
+
"""Offline reconstruction and exact replay of recorded provider exchanges.
|
|
2
|
+
|
|
3
|
+
This module has no provider, HTTP, model, or environment dependency. It reads
|
|
4
|
+
only validated events and sanitized canonical artifacts.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
from dataclasses import InitVar, dataclass, field
|
|
11
|
+
from decimal import Decimal
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from types import MappingProxyType
|
|
14
|
+
from typing import Dict, Iterable, Mapping, Optional, Tuple
|
|
15
|
+
|
|
16
|
+
from agent_evolve.application.artifact_replay import (
|
|
17
|
+
ArtifactRegistrationKey,
|
|
18
|
+
verify_artifact_journal,
|
|
19
|
+
)
|
|
20
|
+
from agent_evolve.domain.artifact import ArtifactRef, ArtifactRole, artifact_ref_for_bytes
|
|
21
|
+
from agent_evolve.domain.event import (
|
|
22
|
+
ArtifactRegistered,
|
|
23
|
+
EventEnvelope,
|
|
24
|
+
LLMCallCompleted,
|
|
25
|
+
LLMCallFailed,
|
|
26
|
+
LLMCallRequested,
|
|
27
|
+
LLMCallRetried,
|
|
28
|
+
LLMCallStarted,
|
|
29
|
+
LLMRequestArtifactLinked,
|
|
30
|
+
ProviderAttemptCompleted,
|
|
31
|
+
ProviderAttemptFailed,
|
|
32
|
+
ProviderAttemptStarted,
|
|
33
|
+
validated_event_snapshot,
|
|
34
|
+
)
|
|
35
|
+
from agent_evolve.domain.ids import EventId, LLMCallId, ProviderAttemptId, RunId
|
|
36
|
+
from agent_evolve.domain.inline_text import InlineTextPolicy, validate_inline_text
|
|
37
|
+
from agent_evolve.domain.outcome import (
|
|
38
|
+
FailureCategory,
|
|
39
|
+
FailureCode,
|
|
40
|
+
validate_failure_pair,
|
|
41
|
+
)
|
|
42
|
+
from agent_evolve.ports.artifact_store import (
|
|
43
|
+
JSON_MEDIA_TYPE,
|
|
44
|
+
ArtifactStore,
|
|
45
|
+
canonical_json_bytes,
|
|
46
|
+
decode_json_bytes,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
_READ_FAILED = object()
|
|
50
|
+
_STREAM_FAILED = object()
|
|
51
|
+
_CLONE_FAILED = object()
|
|
52
|
+
_BUILD_TOKEN = object()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class RecordedProviderReplayError(RuntimeError):
|
|
56
|
+
"""Recorded provider history is missing, inconsistent, or not replayable."""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class RecordedCallNotFoundError(RecordedProviderReplayError):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class RecordedRequestMismatchError(RecordedProviderReplayError):
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class RecordedCallUnavailableError(RecordedProviderReplayError):
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class RecordedCallStatus(str, Enum):
|
|
72
|
+
INCOMPLETE = "incomplete"
|
|
73
|
+
COMPLETED = "completed"
|
|
74
|
+
FAILED = "failed"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class RecordedAttemptStatus(str, Enum):
|
|
78
|
+
STARTED = "started"
|
|
79
|
+
COMPLETED = "completed"
|
|
80
|
+
FAILED = "failed"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(frozen=True, slots=True)
|
|
84
|
+
class _RegistrationOccurrence:
|
|
85
|
+
sequence_number: int
|
|
86
|
+
causation_event_id: Optional[EventId]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass(frozen=True, slots=True)
|
|
90
|
+
class RecordedProviderAttempt:
|
|
91
|
+
provider_attempt_id: ProviderAttemptId
|
|
92
|
+
attempt_number: int
|
|
93
|
+
status: RecordedAttemptStatus
|
|
94
|
+
resolved_provider: Optional[str] = None
|
|
95
|
+
resolved_model: Optional[str] = None
|
|
96
|
+
input_tokens: int = 0
|
|
97
|
+
output_tokens: int = 0
|
|
98
|
+
reasoning_tokens: int = 0
|
|
99
|
+
cache_read_tokens: int = 0
|
|
100
|
+
cache_write_tokens: int = 0
|
|
101
|
+
cost_usd: Decimal = Decimal("0")
|
|
102
|
+
latency_ns: int = 0
|
|
103
|
+
failure_category: Optional[FailureCategory] = None
|
|
104
|
+
failure_code: Optional[FailureCode] = None
|
|
105
|
+
retryable: Optional[bool] = None
|
|
106
|
+
failure_message: Optional[str] = None
|
|
107
|
+
_construction_token: InitVar[object] = None
|
|
108
|
+
|
|
109
|
+
def __post_init__(self, _construction_token: object) -> None:
|
|
110
|
+
if _construction_token is not _BUILD_TOKEN:
|
|
111
|
+
raise TypeError("recorded attempts are constructed only by verified replay")
|
|
112
|
+
_validate_recorded_attempt(self)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass(frozen=True, slots=True)
|
|
116
|
+
class RecordedLLMCall:
|
|
117
|
+
call_id: LLMCallId
|
|
118
|
+
operation: str
|
|
119
|
+
requested_model: str
|
|
120
|
+
request_ref: ArtifactRef
|
|
121
|
+
request_bytes: bytes = field(repr=False)
|
|
122
|
+
status: RecordedCallStatus
|
|
123
|
+
attempts: Tuple[RecordedProviderAttempt, ...]
|
|
124
|
+
response_ref: Optional[ArtifactRef] = None
|
|
125
|
+
response_bytes: Optional[bytes] = field(default=None, repr=False)
|
|
126
|
+
failure_category: Optional[FailureCategory] = None
|
|
127
|
+
failure_code: Optional[FailureCode] = None
|
|
128
|
+
failure_message: Optional[str] = None
|
|
129
|
+
_construction_token: InitVar[object] = None
|
|
130
|
+
|
|
131
|
+
def __post_init__(self, _construction_token: object) -> None:
|
|
132
|
+
if _construction_token is not _BUILD_TOKEN:
|
|
133
|
+
raise TypeError("recorded calls are constructed only by verified replay")
|
|
134
|
+
_validate_recorded_call(self)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _is_nonnegative_int(value: object) -> bool:
|
|
138
|
+
return type(value) is int and value >= 0
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _is_canonical_json_ref(ref: object, content: object) -> bool:
|
|
142
|
+
if type(ref) is not ArtifactRef or type(content) is not bytes:
|
|
143
|
+
return False
|
|
144
|
+
try:
|
|
145
|
+
reconstructed = artifact_ref_for_bytes(content, media_type=JSON_MEDIA_TYPE)
|
|
146
|
+
canonical = canonical_json_bytes(decode_json_bytes(content))
|
|
147
|
+
validated_ref = ArtifactRef(
|
|
148
|
+
artifact_id=ref.artifact_id,
|
|
149
|
+
sha256_hex=ref.sha256_hex,
|
|
150
|
+
size_bytes=ref.size_bytes,
|
|
151
|
+
media_type=ref.media_type,
|
|
152
|
+
)
|
|
153
|
+
except Exception:
|
|
154
|
+
return False
|
|
155
|
+
return canonical == content and validated_ref == reconstructed
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _is_safe_inline(value: object, policy: InlineTextPolicy) -> bool:
|
|
159
|
+
try:
|
|
160
|
+
validate_inline_text(value, field_name="recorded metadata", policy=policy)
|
|
161
|
+
except (TypeError, ValueError):
|
|
162
|
+
return False
|
|
163
|
+
return True
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _validate_recorded_attempt(attempt: RecordedProviderAttempt) -> None:
|
|
167
|
+
if (
|
|
168
|
+
type(attempt.provider_attempt_id) is not ProviderAttemptId
|
|
169
|
+
or type(attempt.attempt_number) is not int
|
|
170
|
+
or attempt.attempt_number < 1
|
|
171
|
+
or type(attempt.status) is not RecordedAttemptStatus
|
|
172
|
+
or any(
|
|
173
|
+
not _is_nonnegative_int(value)
|
|
174
|
+
for value in (
|
|
175
|
+
attempt.input_tokens,
|
|
176
|
+
attempt.output_tokens,
|
|
177
|
+
attempt.reasoning_tokens,
|
|
178
|
+
attempt.cache_read_tokens,
|
|
179
|
+
attempt.cache_write_tokens,
|
|
180
|
+
attempt.latency_ns,
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
or type(attempt.cost_usd) is not Decimal
|
|
184
|
+
or not attempt.cost_usd.is_finite()
|
|
185
|
+
or attempt.cost_usd < 0
|
|
186
|
+
or len(attempt.cost_usd.as_tuple().digits) > 64
|
|
187
|
+
or abs(attempt.cost_usd.as_tuple().exponent) > 64
|
|
188
|
+
):
|
|
189
|
+
raise TypeError("recorded attempt violates the immutable value contract")
|
|
190
|
+
|
|
191
|
+
if attempt.status is RecordedAttemptStatus.STARTED:
|
|
192
|
+
if (
|
|
193
|
+
attempt.resolved_provider is not None
|
|
194
|
+
or attempt.resolved_model is not None
|
|
195
|
+
or any(
|
|
196
|
+
value != 0
|
|
197
|
+
for value in (
|
|
198
|
+
attempt.input_tokens,
|
|
199
|
+
attempt.output_tokens,
|
|
200
|
+
attempt.reasoning_tokens,
|
|
201
|
+
attempt.cache_read_tokens,
|
|
202
|
+
attempt.cache_write_tokens,
|
|
203
|
+
attempt.cost_usd,
|
|
204
|
+
attempt.latency_ns,
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
or attempt.failure_category is not None
|
|
208
|
+
or attempt.failure_code is not None
|
|
209
|
+
or attempt.retryable is not None
|
|
210
|
+
or attempt.failure_message is not None
|
|
211
|
+
):
|
|
212
|
+
raise ValueError("started recorded attempt carries terminal fields")
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
if attempt.status is RecordedAttemptStatus.COMPLETED:
|
|
216
|
+
if (
|
|
217
|
+
not _is_safe_inline(
|
|
218
|
+
attempt.resolved_provider,
|
|
219
|
+
InlineTextPolicy.ROUTING_LABEL,
|
|
220
|
+
)
|
|
221
|
+
or not _is_safe_inline(
|
|
222
|
+
attempt.resolved_model,
|
|
223
|
+
InlineTextPolicy.ROUTING_LABEL,
|
|
224
|
+
)
|
|
225
|
+
or attempt.failure_category is not None
|
|
226
|
+
or attempt.failure_code is not None
|
|
227
|
+
or attempt.retryable is not None
|
|
228
|
+
or attempt.failure_message is not None
|
|
229
|
+
):
|
|
230
|
+
raise ValueError("completed recorded attempt has inconsistent fields")
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
if (
|
|
234
|
+
attempt.resolved_provider is not None
|
|
235
|
+
or attempt.resolved_model is not None
|
|
236
|
+
or any(
|
|
237
|
+
value != 0
|
|
238
|
+
for value in (
|
|
239
|
+
attempt.input_tokens,
|
|
240
|
+
attempt.output_tokens,
|
|
241
|
+
attempt.reasoning_tokens,
|
|
242
|
+
attempt.cache_read_tokens,
|
|
243
|
+
attempt.cache_write_tokens,
|
|
244
|
+
attempt.cost_usd,
|
|
245
|
+
)
|
|
246
|
+
)
|
|
247
|
+
or type(attempt.failure_category) is not FailureCategory
|
|
248
|
+
or type(attempt.failure_code) is not FailureCode
|
|
249
|
+
or type(attempt.retryable) is not bool
|
|
250
|
+
or not _is_safe_inline(
|
|
251
|
+
attempt.failure_message,
|
|
252
|
+
InlineTextPolicy.SAFE_SUMMARY,
|
|
253
|
+
)
|
|
254
|
+
):
|
|
255
|
+
raise ValueError("failed recorded attempt has inconsistent fields")
|
|
256
|
+
validate_failure_pair(attempt.failure_category, attempt.failure_code)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _validate_recorded_call(call: RecordedLLMCall) -> None:
|
|
260
|
+
if (
|
|
261
|
+
type(call.call_id) is not LLMCallId
|
|
262
|
+
or not _is_safe_inline(call.operation, InlineTextPolicy.METADATA_TOKEN)
|
|
263
|
+
or not _is_safe_inline(call.requested_model, InlineTextPolicy.ROUTING_LABEL)
|
|
264
|
+
or not _is_canonical_json_ref(call.request_ref, call.request_bytes)
|
|
265
|
+
or type(call.status) is not RecordedCallStatus
|
|
266
|
+
or type(call.attempts) is not tuple
|
|
267
|
+
or any(type(attempt) is not RecordedProviderAttempt for attempt in call.attempts)
|
|
268
|
+
or [attempt.attempt_number for attempt in call.attempts]
|
|
269
|
+
!= list(range(1, len(call.attempts) + 1))
|
|
270
|
+
or len({attempt.provider_attempt_id for attempt in call.attempts})
|
|
271
|
+
!= len(call.attempts)
|
|
272
|
+
):
|
|
273
|
+
raise TypeError("recorded call violates the immutable value contract")
|
|
274
|
+
|
|
275
|
+
if call.status is RecordedCallStatus.COMPLETED:
|
|
276
|
+
if (
|
|
277
|
+
not call.attempts
|
|
278
|
+
or call.attempts[-1].status is not RecordedAttemptStatus.COMPLETED
|
|
279
|
+
or not _is_canonical_json_ref(call.response_ref, call.response_bytes)
|
|
280
|
+
or call.failure_category is not None
|
|
281
|
+
or call.failure_code is not None
|
|
282
|
+
or call.failure_message is not None
|
|
283
|
+
):
|
|
284
|
+
raise ValueError("completed recorded call has inconsistent fields")
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
if call.response_ref is not None or call.response_bytes is not None:
|
|
288
|
+
raise ValueError("non-completed recorded call cannot carry a response")
|
|
289
|
+
if call.status is RecordedCallStatus.INCOMPLETE:
|
|
290
|
+
if (
|
|
291
|
+
call.failure_category is not None
|
|
292
|
+
or call.failure_code is not None
|
|
293
|
+
or call.failure_message is not None
|
|
294
|
+
):
|
|
295
|
+
raise ValueError("incomplete recorded call cannot carry failure fields")
|
|
296
|
+
return
|
|
297
|
+
if (
|
|
298
|
+
type(call.failure_category) is not FailureCategory
|
|
299
|
+
or type(call.failure_code) is not FailureCode
|
|
300
|
+
or not _is_safe_inline(call.failure_message, InlineTextPolicy.SAFE_SUMMARY)
|
|
301
|
+
):
|
|
302
|
+
raise ValueError("failed recorded call has inconsistent fields")
|
|
303
|
+
validate_failure_pair(call.failure_category, call.failure_code)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _clone_recorded_attempt(
|
|
307
|
+
attempt: RecordedProviderAttempt,
|
|
308
|
+
) -> RecordedProviderAttempt:
|
|
309
|
+
return RecordedProviderAttempt(
|
|
310
|
+
provider_attempt_id=ProviderAttemptId(attempt.provider_attempt_id.value),
|
|
311
|
+
attempt_number=attempt.attempt_number,
|
|
312
|
+
status=attempt.status,
|
|
313
|
+
resolved_provider=attempt.resolved_provider,
|
|
314
|
+
resolved_model=attempt.resolved_model,
|
|
315
|
+
input_tokens=attempt.input_tokens,
|
|
316
|
+
output_tokens=attempt.output_tokens,
|
|
317
|
+
reasoning_tokens=attempt.reasoning_tokens,
|
|
318
|
+
cache_read_tokens=attempt.cache_read_tokens,
|
|
319
|
+
cache_write_tokens=attempt.cache_write_tokens,
|
|
320
|
+
cost_usd=Decimal(attempt.cost_usd),
|
|
321
|
+
latency_ns=attempt.latency_ns,
|
|
322
|
+
failure_category=attempt.failure_category,
|
|
323
|
+
failure_code=attempt.failure_code,
|
|
324
|
+
retryable=attempt.retryable,
|
|
325
|
+
failure_message=attempt.failure_message,
|
|
326
|
+
_construction_token=_BUILD_TOKEN,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _clone_recorded_call(call: RecordedLLMCall) -> RecordedLLMCall:
|
|
331
|
+
request_bytes = bytes(call.request_bytes)
|
|
332
|
+
response_bytes = (
|
|
333
|
+
bytes(call.response_bytes) if call.response_bytes is not None else None
|
|
334
|
+
)
|
|
335
|
+
return RecordedLLMCall(
|
|
336
|
+
call_id=LLMCallId(call.call_id.value),
|
|
337
|
+
operation=call.operation,
|
|
338
|
+
requested_model=call.requested_model,
|
|
339
|
+
request_ref=artifact_ref_for_bytes(
|
|
340
|
+
request_bytes,
|
|
341
|
+
media_type=JSON_MEDIA_TYPE,
|
|
342
|
+
),
|
|
343
|
+
request_bytes=request_bytes,
|
|
344
|
+
status=call.status,
|
|
345
|
+
attempts=tuple(_clone_recorded_attempt(item) for item in call.attempts),
|
|
346
|
+
response_ref=(
|
|
347
|
+
artifact_ref_for_bytes(response_bytes, media_type=JSON_MEDIA_TYPE)
|
|
348
|
+
if response_bytes is not None
|
|
349
|
+
else None
|
|
350
|
+
),
|
|
351
|
+
response_bytes=response_bytes,
|
|
352
|
+
failure_category=call.failure_category,
|
|
353
|
+
failure_code=call.failure_code,
|
|
354
|
+
failure_message=call.failure_message,
|
|
355
|
+
_construction_token=_BUILD_TOKEN,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _try_clone_recorded_call(call: RecordedLLMCall) -> RecordedLLMCall | object:
|
|
360
|
+
try:
|
|
361
|
+
_validate_recorded_call(call)
|
|
362
|
+
return _clone_recorded_call(call)
|
|
363
|
+
except Exception:
|
|
364
|
+
return _CLONE_FAILED
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _recorded_call_fingerprint(call: RecordedLLMCall) -> str:
|
|
368
|
+
record = {
|
|
369
|
+
"call_id": call.call_id.value,
|
|
370
|
+
"operation": call.operation,
|
|
371
|
+
"requested_model": call.requested_model,
|
|
372
|
+
"request_sha256": hashlib.sha256(call.request_bytes).hexdigest(),
|
|
373
|
+
"status": call.status.value,
|
|
374
|
+
"attempts": [
|
|
375
|
+
{
|
|
376
|
+
"provider_attempt_id": attempt.provider_attempt_id.value,
|
|
377
|
+
"attempt_number": attempt.attempt_number,
|
|
378
|
+
"status": attempt.status.value,
|
|
379
|
+
"resolved_provider": attempt.resolved_provider,
|
|
380
|
+
"resolved_model": attempt.resolved_model,
|
|
381
|
+
"input_tokens": attempt.input_tokens,
|
|
382
|
+
"output_tokens": attempt.output_tokens,
|
|
383
|
+
"reasoning_tokens": attempt.reasoning_tokens,
|
|
384
|
+
"cache_read_tokens": attempt.cache_read_tokens,
|
|
385
|
+
"cache_write_tokens": attempt.cache_write_tokens,
|
|
386
|
+
"cost_usd": format(attempt.cost_usd, "f"),
|
|
387
|
+
"latency_ns": attempt.latency_ns,
|
|
388
|
+
"failure_category": (
|
|
389
|
+
attempt.failure_category.value
|
|
390
|
+
if attempt.failure_category is not None
|
|
391
|
+
else None
|
|
392
|
+
),
|
|
393
|
+
"failure_code": (
|
|
394
|
+
attempt.failure_code.value
|
|
395
|
+
if attempt.failure_code is not None
|
|
396
|
+
else None
|
|
397
|
+
),
|
|
398
|
+
"retryable": attempt.retryable,
|
|
399
|
+
"failure_message": attempt.failure_message,
|
|
400
|
+
}
|
|
401
|
+
for attempt in call.attempts
|
|
402
|
+
],
|
|
403
|
+
"response_sha256": (
|
|
404
|
+
hashlib.sha256(call.response_bytes).hexdigest()
|
|
405
|
+
if call.response_bytes is not None
|
|
406
|
+
else None
|
|
407
|
+
),
|
|
408
|
+
"failure_category": (
|
|
409
|
+
call.failure_category.value
|
|
410
|
+
if call.failure_category is not None
|
|
411
|
+
else None
|
|
412
|
+
),
|
|
413
|
+
"failure_code": (
|
|
414
|
+
call.failure_code.value if call.failure_code is not None else None
|
|
415
|
+
),
|
|
416
|
+
"failure_message": call.failure_message,
|
|
417
|
+
}
|
|
418
|
+
return hashlib.sha256(canonical_json_bytes(record)).hexdigest()
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
@dataclass(slots=True)
|
|
422
|
+
class _AttemptBuilder:
|
|
423
|
+
provider_attempt_id: ProviderAttemptId
|
|
424
|
+
attempt_number: int
|
|
425
|
+
status: RecordedAttemptStatus = RecordedAttemptStatus.STARTED
|
|
426
|
+
resolved_provider: Optional[str] = None
|
|
427
|
+
resolved_model: Optional[str] = None
|
|
428
|
+
input_tokens: int = 0
|
|
429
|
+
output_tokens: int = 0
|
|
430
|
+
reasoning_tokens: int = 0
|
|
431
|
+
cache_read_tokens: int = 0
|
|
432
|
+
cache_write_tokens: int = 0
|
|
433
|
+
cost_usd: Decimal = Decimal("0")
|
|
434
|
+
latency_ns: int = 0
|
|
435
|
+
failure_category: Optional[FailureCategory] = None
|
|
436
|
+
failure_code: Optional[FailureCode] = None
|
|
437
|
+
retryable: Optional[bool] = None
|
|
438
|
+
failure_message: Optional[str] = None
|
|
439
|
+
terminal_sequence: Optional[int] = None
|
|
440
|
+
terminal_event_id: Optional[EventId] = None
|
|
441
|
+
|
|
442
|
+
def freeze(self) -> RecordedProviderAttempt:
|
|
443
|
+
return RecordedProviderAttempt(
|
|
444
|
+
provider_attempt_id=self.provider_attempt_id,
|
|
445
|
+
attempt_number=self.attempt_number,
|
|
446
|
+
status=self.status,
|
|
447
|
+
resolved_provider=self.resolved_provider,
|
|
448
|
+
resolved_model=self.resolved_model,
|
|
449
|
+
input_tokens=self.input_tokens,
|
|
450
|
+
output_tokens=self.output_tokens,
|
|
451
|
+
reasoning_tokens=self.reasoning_tokens,
|
|
452
|
+
cache_read_tokens=self.cache_read_tokens,
|
|
453
|
+
cache_write_tokens=self.cache_write_tokens,
|
|
454
|
+
cost_usd=self.cost_usd,
|
|
455
|
+
latency_ns=self.latency_ns,
|
|
456
|
+
failure_category=self.failure_category,
|
|
457
|
+
failure_code=self.failure_code,
|
|
458
|
+
retryable=self.retryable,
|
|
459
|
+
failure_message=self.failure_message,
|
|
460
|
+
_construction_token=_BUILD_TOKEN,
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
@dataclass(slots=True)
|
|
465
|
+
class _CallBuilder:
|
|
466
|
+
call_id: LLMCallId
|
|
467
|
+
operation: str
|
|
468
|
+
requested_model: str
|
|
469
|
+
requested_sequence: int
|
|
470
|
+
request_ref: Optional[ArtifactRef] = None
|
|
471
|
+
request_bytes: Optional[bytes] = None
|
|
472
|
+
started: bool = False
|
|
473
|
+
status: RecordedCallStatus = RecordedCallStatus.INCOMPLETE
|
|
474
|
+
attempts: list[_AttemptBuilder] = field(default_factory=list)
|
|
475
|
+
open_attempt_id: Optional[ProviderAttemptId] = None
|
|
476
|
+
authorized_retry_number: Optional[int] = None
|
|
477
|
+
response_ref: Optional[ArtifactRef] = None
|
|
478
|
+
response_bytes: Optional[bytes] = None
|
|
479
|
+
failure_category: Optional[FailureCategory] = None
|
|
480
|
+
failure_code: Optional[FailureCode] = None
|
|
481
|
+
failure_message: Optional[str] = None
|
|
482
|
+
|
|
483
|
+
@property
|
|
484
|
+
def terminal(self) -> bool:
|
|
485
|
+
return self.status is not RecordedCallStatus.INCOMPLETE
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _fail() -> RecordedProviderReplayError:
|
|
489
|
+
return RecordedProviderReplayError(
|
|
490
|
+
"recorded provider history violates the offline replay contract"
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def _try_snapshot_event_stream(
|
|
495
|
+
events: Iterable[EventEnvelope],
|
|
496
|
+
) -> Tuple[EventEnvelope, ...] | object:
|
|
497
|
+
try:
|
|
498
|
+
return tuple(validated_event_snapshot(event) for event in events)
|
|
499
|
+
except Exception:
|
|
500
|
+
return _STREAM_FAILED
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
@dataclass(frozen=True, slots=True)
|
|
504
|
+
class RecordedProviderReplay:
|
|
505
|
+
"""Immutable offline provider boundary keyed by recorded logical call ID."""
|
|
506
|
+
|
|
507
|
+
run_id: Optional[RunId]
|
|
508
|
+
calls: Tuple[RecordedLLMCall, ...] = field(repr=False)
|
|
509
|
+
_by_call_id: Mapping[LLMCallId, RecordedLLMCall] = field(
|
|
510
|
+
init=False,
|
|
511
|
+
repr=False,
|
|
512
|
+
compare=False,
|
|
513
|
+
)
|
|
514
|
+
_fingerprints: Mapping[LLMCallId, str] = field(
|
|
515
|
+
init=False,
|
|
516
|
+
repr=False,
|
|
517
|
+
compare=False,
|
|
518
|
+
)
|
|
519
|
+
_construction_token: InitVar[object] = None
|
|
520
|
+
|
|
521
|
+
def __post_init__(self, _construction_token: object) -> None:
|
|
522
|
+
if _construction_token is not _BUILD_TOKEN:
|
|
523
|
+
raise TypeError("recorded replay is constructed only by verified replay")
|
|
524
|
+
if type(self.calls) is not tuple:
|
|
525
|
+
raise TypeError("calls must be an immutable tuple")
|
|
526
|
+
if self.run_id is not None and type(self.run_id) is not RunId:
|
|
527
|
+
raise TypeError("run_id must be a RunId or None")
|
|
528
|
+
if any(type(call) is not RecordedLLMCall for call in self.calls):
|
|
529
|
+
raise TypeError("calls must contain RecordedLLMCall values")
|
|
530
|
+
try:
|
|
531
|
+
public_calls = tuple(_try_clone_recorded_call(call) for call in self.calls)
|
|
532
|
+
internal_calls = tuple(_try_clone_recorded_call(call) for call in self.calls)
|
|
533
|
+
canonical_run_id = (
|
|
534
|
+
RunId(self.run_id.value) if self.run_id is not None else None
|
|
535
|
+
)
|
|
536
|
+
except Exception:
|
|
537
|
+
public_calls = None
|
|
538
|
+
internal_calls = None
|
|
539
|
+
canonical_run_id = None
|
|
540
|
+
if public_calls is None or internal_calls is None:
|
|
541
|
+
raise TypeError("recorded replay inputs failed canonical reconstruction")
|
|
542
|
+
if any(call is _CLONE_FAILED for call in public_calls + internal_calls):
|
|
543
|
+
raise TypeError("recorded replay inputs failed canonical reconstruction")
|
|
544
|
+
assert all(isinstance(call, RecordedLLMCall) for call in public_calls)
|
|
545
|
+
assert all(isinstance(call, RecordedLLMCall) for call in internal_calls)
|
|
546
|
+
by_call_id = {call.call_id: call for call in internal_calls}
|
|
547
|
+
if len(by_call_id) != len(internal_calls):
|
|
548
|
+
raise ValueError("recorded calls must have unique call IDs")
|
|
549
|
+
object.__setattr__(self, "run_id", canonical_run_id)
|
|
550
|
+
object.__setattr__(self, "calls", public_calls)
|
|
551
|
+
object.__setattr__(self, "_by_call_id", MappingProxyType(by_call_id))
|
|
552
|
+
object.__setattr__(
|
|
553
|
+
self,
|
|
554
|
+
"_fingerprints",
|
|
555
|
+
MappingProxyType(
|
|
556
|
+
{
|
|
557
|
+
call_id: _recorded_call_fingerprint(call)
|
|
558
|
+
for call_id, call in by_call_id.items()
|
|
559
|
+
}
|
|
560
|
+
),
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
def _internal_call(self, call_id: LLMCallId) -> RecordedLLMCall:
|
|
564
|
+
if type(call_id) is not LLMCallId:
|
|
565
|
+
raise TypeError("call_id must be an LLMCallId")
|
|
566
|
+
try:
|
|
567
|
+
canonical_call_id = LLMCallId(call_id.value)
|
|
568
|
+
call = self._by_call_id[canonical_call_id]
|
|
569
|
+
except (KeyError, TypeError, ValueError):
|
|
570
|
+
raise RecordedCallNotFoundError(
|
|
571
|
+
"recorded logical call was not found"
|
|
572
|
+
) from None
|
|
573
|
+
validated = _try_clone_recorded_call(call)
|
|
574
|
+
if (
|
|
575
|
+
validated is _CLONE_FAILED
|
|
576
|
+
or _recorded_call_fingerprint(call)
|
|
577
|
+
!= self._fingerprints.get(canonical_call_id)
|
|
578
|
+
):
|
|
579
|
+
raise RecordedProviderReplayError(
|
|
580
|
+
"recorded replay snapshot failed its immutable integrity check"
|
|
581
|
+
)
|
|
582
|
+
return call
|
|
583
|
+
|
|
584
|
+
def call(self, call_id: LLMCallId) -> RecordedLLMCall:
|
|
585
|
+
call = _try_clone_recorded_call(self._internal_call(call_id))
|
|
586
|
+
if call is _CLONE_FAILED: # pragma: no cover - guarded by _internal_call.
|
|
587
|
+
raise RecordedProviderReplayError(
|
|
588
|
+
"recorded replay snapshot failed its immutable integrity check"
|
|
589
|
+
)
|
|
590
|
+
assert isinstance(call, RecordedLLMCall)
|
|
591
|
+
return call
|
|
592
|
+
|
|
593
|
+
def replay_response(self, call_id: LLMCallId, *, request_bytes: bytes) -> bytes:
|
|
594
|
+
"""Return exact recorded response bytes only for the exact recorded request."""
|
|
595
|
+
|
|
596
|
+
if type(request_bytes) is not bytes:
|
|
597
|
+
raise TypeError("request_bytes must be immutable bytes")
|
|
598
|
+
call = self._internal_call(call_id)
|
|
599
|
+
if request_bytes != call.request_bytes:
|
|
600
|
+
raise RecordedRequestMismatchError(
|
|
601
|
+
"supplied request does not match the recorded sanitized request"
|
|
602
|
+
)
|
|
603
|
+
if (
|
|
604
|
+
call.status is not RecordedCallStatus.COMPLETED
|
|
605
|
+
or call.response_bytes is None
|
|
606
|
+
):
|
|
607
|
+
raise RecordedCallUnavailableError(
|
|
608
|
+
"recorded call has no completed response available for replay"
|
|
609
|
+
)
|
|
610
|
+
return call.response_bytes
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
def _read_registered_bytes(
|
|
614
|
+
store: ArtifactStore,
|
|
615
|
+
registrations: Dict[
|
|
616
|
+
ArtifactRegistrationKey,
|
|
617
|
+
tuple[ArtifactRef, list[_RegistrationOccurrence]],
|
|
618
|
+
],
|
|
619
|
+
artifact_id,
|
|
620
|
+
role: ArtifactRole,
|
|
621
|
+
) -> tuple[ArtifactRef, bytes, Tuple[_RegistrationOccurrence, ...]]:
|
|
622
|
+
key = ArtifactRegistrationKey(artifact_id, role)
|
|
623
|
+
registered = registrations.get(key)
|
|
624
|
+
if registered is None:
|
|
625
|
+
raise _fail()
|
|
626
|
+
ref, registration_occurrences = registered
|
|
627
|
+
content = _try_read_canonical_registered_bytes(store, ref)
|
|
628
|
+
if content is _READ_FAILED:
|
|
629
|
+
raise _fail()
|
|
630
|
+
assert isinstance(content, bytes)
|
|
631
|
+
return ref, content, tuple(registration_occurrences)
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
def _try_read_canonical_registered_bytes(
|
|
635
|
+
store: ArtifactStore,
|
|
636
|
+
ref: ArtifactRef,
|
|
637
|
+
) -> bytes | object:
|
|
638
|
+
try:
|
|
639
|
+
content = store.read_bytes(
|
|
640
|
+
ref.artifact_id,
|
|
641
|
+
expected_media_type=JSON_MEDIA_TYPE,
|
|
642
|
+
)
|
|
643
|
+
reread_ref = artifact_ref_for_bytes(content, media_type=JSON_MEDIA_TYPE)
|
|
644
|
+
canonical = canonical_json_bytes(decode_json_bytes(content))
|
|
645
|
+
if reread_ref != ref or canonical != content:
|
|
646
|
+
return _READ_FAILED
|
|
647
|
+
return content
|
|
648
|
+
except Exception:
|
|
649
|
+
# Return from the exception handler so the public replay error cannot
|
|
650
|
+
# retain secret-bearing store/codec exception context.
|
|
651
|
+
return _READ_FAILED
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def _require_call(
|
|
655
|
+
calls: Dict[LLMCallId, _CallBuilder],
|
|
656
|
+
call_id: LLMCallId,
|
|
657
|
+
) -> _CallBuilder:
|
|
658
|
+
call = calls.get(call_id)
|
|
659
|
+
if call is None:
|
|
660
|
+
raise _fail()
|
|
661
|
+
return call
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def build_recorded_provider_replay(
|
|
665
|
+
events: Iterable[EventEnvelope],
|
|
666
|
+
*,
|
|
667
|
+
artifact_store: ArtifactStore,
|
|
668
|
+
) -> RecordedProviderReplay:
|
|
669
|
+
"""Validate and reconstruct an offline replay boundary from one run stream."""
|
|
670
|
+
|
|
671
|
+
stream = _try_snapshot_event_stream(events)
|
|
672
|
+
if stream is _STREAM_FAILED:
|
|
673
|
+
raise _fail()
|
|
674
|
+
assert isinstance(stream, tuple)
|
|
675
|
+
report = verify_artifact_journal(stream, artifact_store=artifact_store)
|
|
676
|
+
registrations: Dict[
|
|
677
|
+
ArtifactRegistrationKey,
|
|
678
|
+
tuple[ArtifactRef, list[_RegistrationOccurrence]],
|
|
679
|
+
] = {}
|
|
680
|
+
calls: Dict[LLMCallId, _CallBuilder] = {}
|
|
681
|
+
call_order: list[LLMCallId] = []
|
|
682
|
+
attempts_by_id: Dict[ProviderAttemptId, tuple[LLMCallId, _AttemptBuilder]] = {}
|
|
683
|
+
seen_event_ids: set[EventId] = set()
|
|
684
|
+
|
|
685
|
+
for event in stream:
|
|
686
|
+
if event.event_id in seen_event_ids or (
|
|
687
|
+
event.causation_event_id is not None
|
|
688
|
+
and event.causation_event_id not in seen_event_ids
|
|
689
|
+
):
|
|
690
|
+
raise _fail()
|
|
691
|
+
seen_event_ids.add(event.event_id)
|
|
692
|
+
payload = event.payload
|
|
693
|
+
if isinstance(payload, ArtifactRegistered):
|
|
694
|
+
registration_key = ArtifactRegistrationKey(
|
|
695
|
+
payload.artifact_id,
|
|
696
|
+
payload.role,
|
|
697
|
+
)
|
|
698
|
+
registered = registrations.get(registration_key)
|
|
699
|
+
if registered is None:
|
|
700
|
+
registrations[registration_key] = (
|
|
701
|
+
payload.artifact_ref,
|
|
702
|
+
[
|
|
703
|
+
_RegistrationOccurrence(
|
|
704
|
+
sequence_number=event.sequence_number,
|
|
705
|
+
causation_event_id=event.causation_event_id,
|
|
706
|
+
)
|
|
707
|
+
],
|
|
708
|
+
)
|
|
709
|
+
else:
|
|
710
|
+
registered[1].append(
|
|
711
|
+
_RegistrationOccurrence(
|
|
712
|
+
sequence_number=event.sequence_number,
|
|
713
|
+
causation_event_id=event.causation_event_id,
|
|
714
|
+
)
|
|
715
|
+
)
|
|
716
|
+
continue
|
|
717
|
+
|
|
718
|
+
if isinstance(payload, LLMCallRequested):
|
|
719
|
+
if payload.call_id in calls:
|
|
720
|
+
raise _fail()
|
|
721
|
+
calls[payload.call_id] = _CallBuilder(
|
|
722
|
+
call_id=payload.call_id,
|
|
723
|
+
operation=payload.operation,
|
|
724
|
+
requested_model=payload.requested_model,
|
|
725
|
+
requested_sequence=event.sequence_number,
|
|
726
|
+
)
|
|
727
|
+
call_order.append(payload.call_id)
|
|
728
|
+
continue
|
|
729
|
+
|
|
730
|
+
if isinstance(payload, LLMRequestArtifactLinked):
|
|
731
|
+
call = _require_call(calls, payload.call_id)
|
|
732
|
+
if call.request_ref is not None or call.started or call.terminal:
|
|
733
|
+
raise _fail()
|
|
734
|
+
(
|
|
735
|
+
call.request_ref,
|
|
736
|
+
call.request_bytes,
|
|
737
|
+
registration_occurrences,
|
|
738
|
+
) = _read_registered_bytes(
|
|
739
|
+
artifact_store,
|
|
740
|
+
registrations,
|
|
741
|
+
payload.request_artifact_id,
|
|
742
|
+
ArtifactRole.LLM_REQUEST,
|
|
743
|
+
)
|
|
744
|
+
if not any(
|
|
745
|
+
occurrence.sequence_number < call.requested_sequence
|
|
746
|
+
for occurrence in registration_occurrences
|
|
747
|
+
):
|
|
748
|
+
raise _fail()
|
|
749
|
+
continue
|
|
750
|
+
|
|
751
|
+
if isinstance(payload, LLMCallStarted):
|
|
752
|
+
call = _require_call(calls, payload.call_id)
|
|
753
|
+
if call.started or call.terminal or call.request_ref is None:
|
|
754
|
+
raise _fail()
|
|
755
|
+
call.started = True
|
|
756
|
+
continue
|
|
757
|
+
|
|
758
|
+
if isinstance(payload, ProviderAttemptStarted):
|
|
759
|
+
call = _require_call(calls, payload.call_id)
|
|
760
|
+
expected_number = len(call.attempts) + 1
|
|
761
|
+
if (
|
|
762
|
+
not call.started
|
|
763
|
+
or call.terminal
|
|
764
|
+
or call.open_attempt_id is not None
|
|
765
|
+
or payload.provider_attempt_id in attempts_by_id
|
|
766
|
+
or payload.attempt_number != expected_number
|
|
767
|
+
or (
|
|
768
|
+
expected_number > 1
|
|
769
|
+
and call.authorized_retry_number != expected_number
|
|
770
|
+
)
|
|
771
|
+
):
|
|
772
|
+
raise _fail()
|
|
773
|
+
attempt = _AttemptBuilder(
|
|
774
|
+
provider_attempt_id=payload.provider_attempt_id,
|
|
775
|
+
attempt_number=payload.attempt_number,
|
|
776
|
+
)
|
|
777
|
+
call.attempts.append(attempt)
|
|
778
|
+
call.open_attempt_id = payload.provider_attempt_id
|
|
779
|
+
call.authorized_retry_number = None
|
|
780
|
+
attempts_by_id[payload.provider_attempt_id] = (payload.call_id, attempt)
|
|
781
|
+
continue
|
|
782
|
+
|
|
783
|
+
if isinstance(payload, (ProviderAttemptCompleted, ProviderAttemptFailed)):
|
|
784
|
+
call = _require_call(calls, payload.call_id)
|
|
785
|
+
found = attempts_by_id.get(payload.provider_attempt_id)
|
|
786
|
+
if (
|
|
787
|
+
found is None
|
|
788
|
+
or found[0] != payload.call_id
|
|
789
|
+
or call.open_attempt_id != payload.provider_attempt_id
|
|
790
|
+
or call.terminal
|
|
791
|
+
or found[1].status is not RecordedAttemptStatus.STARTED
|
|
792
|
+
):
|
|
793
|
+
raise _fail()
|
|
794
|
+
attempt = found[1]
|
|
795
|
+
if isinstance(payload, ProviderAttemptCompleted):
|
|
796
|
+
attempt.status = RecordedAttemptStatus.COMPLETED
|
|
797
|
+
attempt.resolved_provider = payload.resolved_provider
|
|
798
|
+
attempt.resolved_model = payload.resolved_model
|
|
799
|
+
attempt.input_tokens = payload.input_tokens
|
|
800
|
+
attempt.output_tokens = payload.output_tokens
|
|
801
|
+
attempt.reasoning_tokens = payload.reasoning_tokens
|
|
802
|
+
attempt.cache_read_tokens = payload.cache_read_tokens
|
|
803
|
+
attempt.cache_write_tokens = payload.cache_write_tokens
|
|
804
|
+
attempt.cost_usd = payload.cost_usd
|
|
805
|
+
attempt.latency_ns = payload.latency_ns
|
|
806
|
+
else:
|
|
807
|
+
attempt.status = RecordedAttemptStatus.FAILED
|
|
808
|
+
attempt.failure_category = payload.category
|
|
809
|
+
attempt.failure_code = payload.code
|
|
810
|
+
attempt.retryable = payload.retryable
|
|
811
|
+
attempt.failure_message = payload.message
|
|
812
|
+
attempt.latency_ns = payload.latency_ns
|
|
813
|
+
attempt.terminal_sequence = event.sequence_number
|
|
814
|
+
attempt.terminal_event_id = event.event_id
|
|
815
|
+
call.open_attempt_id = None
|
|
816
|
+
continue
|
|
817
|
+
|
|
818
|
+
if isinstance(payload, LLMCallRetried):
|
|
819
|
+
call = _require_call(calls, payload.call_id)
|
|
820
|
+
if (
|
|
821
|
+
not call.started
|
|
822
|
+
or call.terminal
|
|
823
|
+
or call.open_attempt_id is not None
|
|
824
|
+
or not call.attempts
|
|
825
|
+
or call.attempts[-1].status is not RecordedAttemptStatus.FAILED
|
|
826
|
+
or not call.attempts[-1].retryable
|
|
827
|
+
or call.authorized_retry_number is not None
|
|
828
|
+
or payload.next_attempt_number != len(call.attempts) + 1
|
|
829
|
+
):
|
|
830
|
+
raise _fail()
|
|
831
|
+
call.authorized_retry_number = payload.next_attempt_number
|
|
832
|
+
continue
|
|
833
|
+
|
|
834
|
+
if isinstance(payload, LLMCallCompleted):
|
|
835
|
+
call = _require_call(calls, payload.call_id)
|
|
836
|
+
if (
|
|
837
|
+
not call.started
|
|
838
|
+
or call.terminal
|
|
839
|
+
or call.open_attempt_id is not None
|
|
840
|
+
or not call.attempts
|
|
841
|
+
or call.attempts[-1].status is not RecordedAttemptStatus.COMPLETED
|
|
842
|
+
or payload.response_artifact_id is None
|
|
843
|
+
):
|
|
844
|
+
raise _fail()
|
|
845
|
+
(
|
|
846
|
+
call.response_ref,
|
|
847
|
+
call.response_bytes,
|
|
848
|
+
registration_occurrences,
|
|
849
|
+
) = _read_registered_bytes(
|
|
850
|
+
artifact_store,
|
|
851
|
+
registrations,
|
|
852
|
+
payload.response_artifact_id,
|
|
853
|
+
ArtifactRole.LLM_RESPONSE,
|
|
854
|
+
)
|
|
855
|
+
if (
|
|
856
|
+
call.attempts[-1].terminal_sequence is None
|
|
857
|
+
or call.attempts[-1].terminal_event_id is None
|
|
858
|
+
or not any(
|
|
859
|
+
occurrence.sequence_number
|
|
860
|
+
> call.attempts[-1].terminal_sequence
|
|
861
|
+
and occurrence.causation_event_id
|
|
862
|
+
== call.attempts[-1].terminal_event_id
|
|
863
|
+
for occurrence in registration_occurrences
|
|
864
|
+
)
|
|
865
|
+
):
|
|
866
|
+
raise _fail()
|
|
867
|
+
call.status = RecordedCallStatus.COMPLETED
|
|
868
|
+
continue
|
|
869
|
+
|
|
870
|
+
if isinstance(payload, LLMCallFailed):
|
|
871
|
+
call = _require_call(calls, payload.call_id)
|
|
872
|
+
if (
|
|
873
|
+
not call.started
|
|
874
|
+
or call.terminal
|
|
875
|
+
or call.open_attempt_id is not None
|
|
876
|
+
or call.authorized_retry_number is not None
|
|
877
|
+
):
|
|
878
|
+
raise _fail()
|
|
879
|
+
call.status = RecordedCallStatus.FAILED
|
|
880
|
+
call.failure_category = payload.category
|
|
881
|
+
call.failure_code = payload.code
|
|
882
|
+
call.failure_message = payload.message
|
|
883
|
+
|
|
884
|
+
frozen_calls: list[RecordedLLMCall] = []
|
|
885
|
+
for call_id in call_order:
|
|
886
|
+
call = calls[call_id]
|
|
887
|
+
if call.request_ref is None or call.request_bytes is None:
|
|
888
|
+
raise _fail()
|
|
889
|
+
frozen_calls.append(
|
|
890
|
+
RecordedLLMCall(
|
|
891
|
+
call_id=call.call_id,
|
|
892
|
+
operation=call.operation,
|
|
893
|
+
requested_model=call.requested_model,
|
|
894
|
+
request_ref=call.request_ref,
|
|
895
|
+
request_bytes=call.request_bytes,
|
|
896
|
+
status=call.status,
|
|
897
|
+
attempts=tuple(attempt.freeze() for attempt in call.attempts),
|
|
898
|
+
response_ref=call.response_ref,
|
|
899
|
+
response_bytes=call.response_bytes,
|
|
900
|
+
failure_category=call.failure_category,
|
|
901
|
+
failure_code=call.failure_code,
|
|
902
|
+
failure_message=call.failure_message,
|
|
903
|
+
_construction_token=_BUILD_TOKEN,
|
|
904
|
+
)
|
|
905
|
+
)
|
|
906
|
+
return RecordedProviderReplay(
|
|
907
|
+
run_id=report.run_id,
|
|
908
|
+
calls=tuple(frozen_calls),
|
|
909
|
+
_construction_token=_BUILD_TOKEN,
|
|
910
|
+
)
|