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,1170 @@
|
|
|
1
|
+
"""Content-free evidence for exact OpenRouter requests at the HTTP boundary.
|
|
2
|
+
|
|
3
|
+
Logical request evidence is not enough to diagnose provider rejections: both
|
|
4
|
+
Pydantic-AI and the OpenAI SDK transform model settings, messages, and tool
|
|
5
|
+
schemas before HTTP transport. This module observes the fully serialized JSON
|
|
6
|
+
body in an HTTPX request hook, verifies that it still represents the bound
|
|
7
|
+
``StructuredGenerationRequest``, and synchronously publishes a redacted record
|
|
8
|
+
before the transport is allowed to send any bytes.
|
|
9
|
+
|
|
10
|
+
The record deliberately contains no headers, query text, prompt, tool
|
|
11
|
+
description, JSON schema, or raw body. Exact byte and canonical semantic
|
|
12
|
+
commitments make those omitted values auditable without retaining them.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import hashlib
|
|
18
|
+
import json
|
|
19
|
+
import math
|
|
20
|
+
import re
|
|
21
|
+
import threading
|
|
22
|
+
from collections.abc import Callable, Iterator, Mapping
|
|
23
|
+
from contextlib import contextmanager
|
|
24
|
+
from contextvars import ContextVar
|
|
25
|
+
from copy import deepcopy
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
from importlib.metadata import version
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
from agent_evolve.domain.ids import LLMCallId, ProviderAttemptId
|
|
31
|
+
from agent_evolve.ports.structured_generator import (
|
|
32
|
+
MAX_OUTPUT_TOKENS,
|
|
33
|
+
MAX_PROMPT_UTF8_BYTES,
|
|
34
|
+
StructuredGenerationRequest,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
OPENROUTER_OUTBOUND_REQUEST_MANIFEST_SCHEMA_VERSION = 3
|
|
39
|
+
OPENROUTER_OUTBOUND_REQUEST_BOUNDARY_ID = (
|
|
40
|
+
"openrouter_chat_completions_httpx_pre_transport_v3"
|
|
41
|
+
)
|
|
42
|
+
OPENROUTER_OUTBOUND_REQUEST_MANIFEST_HASH_ALGORITHM = (
|
|
43
|
+
"sha256_domain_and_canonical_redacted_projection_v3"
|
|
44
|
+
)
|
|
45
|
+
OPENROUTER_OUTBOUND_PUBLIC_PROJECTION_HASH_ALGORITHM = (
|
|
46
|
+
"sha256_domain_and_canonical_public_projection_v3"
|
|
47
|
+
)
|
|
48
|
+
OPENROUTER_OUTBOUND_TRANSPORT_CONTRACT_HASH_ALGORITHM = (
|
|
49
|
+
"sha256_domain_and_canonical_transport_contract_v3"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
_FRAMEWORK_PACKAGES = ("httpx", "openai", "pydantic", "pydantic-ai")
|
|
53
|
+
_TRANSPORT_CONTRACT = {
|
|
54
|
+
"contract_version": 3,
|
|
55
|
+
"hook_phase": "httpx_request_event_before_transport",
|
|
56
|
+
"method": "POST",
|
|
57
|
+
"scheme": "https",
|
|
58
|
+
"host": "openrouter.ai",
|
|
59
|
+
"port": None,
|
|
60
|
+
"path": "/api/v1/chat/completions",
|
|
61
|
+
"query_required_absent": True,
|
|
62
|
+
"openai_sdk_retries": 0,
|
|
63
|
+
"pydantic_ai_agent_retries": 0,
|
|
64
|
+
"pydantic_ai_output_retries": 0,
|
|
65
|
+
"pydantic_ai_request_limit": 1,
|
|
66
|
+
"output_mode": "profile_selected_tool_or_native_json_schema",
|
|
67
|
+
"sink_timing": "synchronous_before_transport_send",
|
|
68
|
+
"raw_sensitive_values_persisted": False,
|
|
69
|
+
}
|
|
70
|
+
_FORBIDDEN_TOP_LEVEL_FIELDS = (
|
|
71
|
+
"extra_body",
|
|
72
|
+
"mode",
|
|
73
|
+
"models",
|
|
74
|
+
"plugins",
|
|
75
|
+
"preset",
|
|
76
|
+
"pro",
|
|
77
|
+
"reasoning_effort",
|
|
78
|
+
"reasoning_mode",
|
|
79
|
+
"transforms",
|
|
80
|
+
"web_search_options",
|
|
81
|
+
)
|
|
82
|
+
_FORBIDDEN_REASONING_FIELDS = (
|
|
83
|
+
"enabled",
|
|
84
|
+
"exclude",
|
|
85
|
+
"mode",
|
|
86
|
+
"pro",
|
|
87
|
+
)
|
|
88
|
+
_BASE_BODY_FIELDS = frozenset(
|
|
89
|
+
{
|
|
90
|
+
"max_completion_tokens",
|
|
91
|
+
"messages",
|
|
92
|
+
"model",
|
|
93
|
+
"provider",
|
|
94
|
+
"stream",
|
|
95
|
+
"usage",
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
_STRUCTURED_OUTPUT_MODES = frozenset({"tool", "native_json_schema"})
|
|
99
|
+
_LOWER_SHA256 = re.compile(r"^[0-9a-f]{64}$")
|
|
100
|
+
_OPERATION_TOKEN = re.compile(r"^[a-z][a-z0-9_.-]{0,95}$")
|
|
101
|
+
_TOOL_TOKEN = re.compile(r"^[A-Za-z][A-Za-z0-9_-]{0,63}$")
|
|
102
|
+
_PROVIDER_SLUG = re.compile(r"^[a-z0-9][a-z0-9_-]{0,63}$")
|
|
103
|
+
_MODEL_SLUG = re.compile(
|
|
104
|
+
r"^[a-z0-9][a-z0-9_.-]{0,95}/[A-Za-z0-9][A-Za-z0-9_.:-]{0,191}$"
|
|
105
|
+
)
|
|
106
|
+
_VERSION_TOKEN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9.+_-]{0,63}$")
|
|
107
|
+
_REASONING_EFFORTS = frozenset(
|
|
108
|
+
{"xhigh", "high", "medium", "low", "minimal", "none"}
|
|
109
|
+
)
|
|
110
|
+
_MAX_WIRE_BODY_BYTES = 8 * 1_048_576
|
|
111
|
+
_MANIFEST_DOMAIN = b"agent-evolve:openrouter-outbound-manifest:v3\x00"
|
|
112
|
+
_PUBLIC_PROJECTION_DOMAIN = (
|
|
113
|
+
b"agent-evolve:openrouter-outbound-public-projection:v3\x00"
|
|
114
|
+
)
|
|
115
|
+
_TRANSPORT_CONTRACT_DOMAIN = (
|
|
116
|
+
b"agent-evolve:openrouter-outbound-transport-contract:v3\x00"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
OpenRouterOutboundRequestManifestSink = Callable[[dict[str, object]], None]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class OpenRouterOutboundRequestManifestError(RuntimeError):
|
|
124
|
+
"""The exact outbound request could not be safely authenticated/published."""
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _canonical_bytes(value: object) -> bytes:
|
|
128
|
+
return json.dumps(
|
|
129
|
+
value,
|
|
130
|
+
ensure_ascii=True,
|
|
131
|
+
allow_nan=False,
|
|
132
|
+
separators=(",", ":"),
|
|
133
|
+
sort_keys=True,
|
|
134
|
+
).encode("ascii")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _sha256(value: bytes) -> str:
|
|
138
|
+
return hashlib.sha256(value).hexdigest()
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _domain_sha256(domain: bytes, value: object) -> str:
|
|
142
|
+
return hashlib.sha256(domain + _canonical_bytes(value)).hexdigest()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _canonical_json_copy(value: object, *, label: str) -> object:
|
|
146
|
+
try:
|
|
147
|
+
encoded = _canonical_bytes(value)
|
|
148
|
+
return json.loads(encoded)
|
|
149
|
+
except (TypeError, ValueError) as exc:
|
|
150
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
151
|
+
f"{label} is not finite canonical JSON"
|
|
152
|
+
) from exc
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _reject_duplicate_json_keys(
|
|
156
|
+
pairs: list[tuple[str, object]],
|
|
157
|
+
) -> dict[str, object]:
|
|
158
|
+
result: dict[str, object] = {}
|
|
159
|
+
for key, value in pairs:
|
|
160
|
+
if key in result:
|
|
161
|
+
raise ValueError("duplicate JSON object key")
|
|
162
|
+
result[key] = value
|
|
163
|
+
return result
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _framework_versions() -> dict[str, str]:
|
|
167
|
+
"""Project the closed dependency set; launch qualification binds exact values."""
|
|
168
|
+
|
|
169
|
+
observed = {name: version(name) for name in _FRAMEWORK_PACKAGES}
|
|
170
|
+
if any(
|
|
171
|
+
type(value) is not str or _VERSION_TOKEN.fullmatch(value) is None
|
|
172
|
+
for value in observed.values()
|
|
173
|
+
):
|
|
174
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
175
|
+
"outbound framework version is outside the closed token grammar"
|
|
176
|
+
)
|
|
177
|
+
return observed
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _closed_provider(value: object) -> dict[str, object]:
|
|
181
|
+
if type(value) is not dict:
|
|
182
|
+
raise ValueError("provider routing must be an exact object")
|
|
183
|
+
fields = frozenset(value)
|
|
184
|
+
if fields not in (
|
|
185
|
+
frozenset({"only", "allow_fallbacks"}),
|
|
186
|
+
frozenset({"only", "allow_fallbacks", "require_parameters"}),
|
|
187
|
+
):
|
|
188
|
+
raise ValueError("provider routing has unexpected fields")
|
|
189
|
+
only = value["only"]
|
|
190
|
+
if (
|
|
191
|
+
type(only) is not list
|
|
192
|
+
or not 1 <= len(only) <= 16
|
|
193
|
+
or any(
|
|
194
|
+
type(item) is not str or _PROVIDER_SLUG.fullmatch(item) is None
|
|
195
|
+
for item in only
|
|
196
|
+
)
|
|
197
|
+
or len(set(only)) != len(only)
|
|
198
|
+
or value["allow_fallbacks"] is not False
|
|
199
|
+
or (
|
|
200
|
+
"require_parameters" in value
|
|
201
|
+
and value["require_parameters"] is not True
|
|
202
|
+
)
|
|
203
|
+
):
|
|
204
|
+
raise ValueError("provider routing violates the closed no-fallback policy")
|
|
205
|
+
return dict(value)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _closed_reasoning(value: object) -> dict[str, object] | None:
|
|
209
|
+
if value is None:
|
|
210
|
+
return None
|
|
211
|
+
if type(value) is not dict or len(value) != 1:
|
|
212
|
+
raise ValueError("reasoning must contain exactly one closed control")
|
|
213
|
+
if frozenset(value) == {"effort"}:
|
|
214
|
+
effort = value["effort"]
|
|
215
|
+
if type(effort) is not str or effort not in _REASONING_EFFORTS:
|
|
216
|
+
raise ValueError("reasoning effort is outside the closed enum")
|
|
217
|
+
elif frozenset(value) == {"max_tokens"}:
|
|
218
|
+
max_tokens = value["max_tokens"]
|
|
219
|
+
if (
|
|
220
|
+
type(max_tokens) is not int
|
|
221
|
+
or not 1 <= max_tokens <= MAX_OUTPUT_TOKENS
|
|
222
|
+
):
|
|
223
|
+
raise ValueError("reasoning max_tokens is outside the generic bound")
|
|
224
|
+
else:
|
|
225
|
+
raise ValueError("reasoning contains an unsupported control")
|
|
226
|
+
return dict(value)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _transport_contract_sha256() -> str:
|
|
230
|
+
return _domain_sha256(_TRANSPORT_CONTRACT_DOMAIN, _TRANSPORT_CONTRACT)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _wire_output_schema(
|
|
234
|
+
output_type: type[Any],
|
|
235
|
+
*,
|
|
236
|
+
json_schema_transformer: type[Any] | None = None,
|
|
237
|
+
strict: bool = False,
|
|
238
|
+
) -> tuple[int, str, int, str]:
|
|
239
|
+
"""Return logical and pinned OpenAI-wire schema commitments.
|
|
240
|
+
|
|
241
|
+
``OpenRouterModel`` delegates schema projection to its resolved model
|
|
242
|
+
profile. Different model families can therefore serialize the same
|
|
243
|
+
logical Pydantic type differently (for example, OpenAI-style references
|
|
244
|
+
versus inline definitions). Invoking the exact profile transformer is
|
|
245
|
+
intentional: a dependency/source/model-profile change must be qualified
|
|
246
|
+
rather than approximated by an ad-hoc title stripper.
|
|
247
|
+
|
|
248
|
+
The default preserves the historical helper contract for callers that do
|
|
249
|
+
not own a resolved model profile. Production publishers always inject the
|
|
250
|
+
selected OpenRouter profile's transformer.
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
from pydantic_ai.profiles.openai import OpenAIJsonSchemaTransformer
|
|
255
|
+
from pydantic_ai.tools import GenerateToolJsonSchema
|
|
256
|
+
|
|
257
|
+
transformer = (
|
|
258
|
+
OpenAIJsonSchemaTransformer
|
|
259
|
+
if json_schema_transformer is None
|
|
260
|
+
else json_schema_transformer
|
|
261
|
+
)
|
|
262
|
+
if not isinstance(transformer, type):
|
|
263
|
+
raise TypeError("json schema transformer must be a class")
|
|
264
|
+
# The logical commitment must be byte-identical to the prequeue
|
|
265
|
+
# request-evidence boundary. That boundary deliberately records
|
|
266
|
+
# Pydantic's ordinary validation schema, before any transport-specific
|
|
267
|
+
# projection.
|
|
268
|
+
logical = output_type.model_json_schema(
|
|
269
|
+
mode="validation",
|
|
270
|
+
)
|
|
271
|
+
if type(logical) is not dict:
|
|
272
|
+
raise TypeError("logical schema is not an exact object")
|
|
273
|
+
logical_bytes = _canonical_bytes(logical)
|
|
274
|
+
# ``ObjectOutputProcessor`` independently uses Pydantic-AI's
|
|
275
|
+
# tool-specific generator before the model-profile transformer. In
|
|
276
|
+
# particular it removes property titles. Reproduce that maintained
|
|
277
|
+
# path for the wire commitment without conflating it with the logical
|
|
278
|
+
# request schema above.
|
|
279
|
+
transport_logical = output_type.model_json_schema(
|
|
280
|
+
mode="validation",
|
|
281
|
+
schema_generator=GenerateToolJsonSchema,
|
|
282
|
+
)
|
|
283
|
+
if type(transport_logical) is not dict:
|
|
284
|
+
raise TypeError("transport logical schema is not an exact object")
|
|
285
|
+
# Detach because schema transformers are allowed to mutate their input.
|
|
286
|
+
# Preserve insertion order: strict transformers derive ordered
|
|
287
|
+
# ``required`` arrays from property order, whereas replaying canonical
|
|
288
|
+
# (key-sorted) JSON would authenticate a different wire schema.
|
|
289
|
+
detached = deepcopy(transport_logical)
|
|
290
|
+
# ObjectOutputProcessor promotes the root schema description (normally
|
|
291
|
+
# the model docstring) to the function-tool description before the
|
|
292
|
+
# model-specific schema transformer runs.
|
|
293
|
+
detached.pop("description", None)
|
|
294
|
+
if type(strict) is not bool:
|
|
295
|
+
raise TypeError("strict must be an exact bool")
|
|
296
|
+
wire = transformer(detached, strict=strict).walk()
|
|
297
|
+
if type(wire) is not dict:
|
|
298
|
+
raise TypeError("wire schema is not an exact object")
|
|
299
|
+
wire_bytes = _canonical_bytes(wire)
|
|
300
|
+
except Exception as exc:
|
|
301
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
302
|
+
"structured output type cannot produce the pinned wire schema"
|
|
303
|
+
) from exc
|
|
304
|
+
return (
|
|
305
|
+
len(logical_bytes),
|
|
306
|
+
_sha256(logical_bytes),
|
|
307
|
+
len(wire_bytes),
|
|
308
|
+
_sha256(wire_bytes),
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@dataclass(frozen=True, slots=True)
|
|
313
|
+
class _OutboundExpectation:
|
|
314
|
+
publisher_identity: object
|
|
315
|
+
call_id: str
|
|
316
|
+
operation: str
|
|
317
|
+
provider_attempt_id: str
|
|
318
|
+
requested_model: str
|
|
319
|
+
prompt_utf8_bytes: int
|
|
320
|
+
prompt_sha256: str
|
|
321
|
+
output_tool_name: str
|
|
322
|
+
logical_schema_utf8_bytes: int
|
|
323
|
+
logical_schema_sha256: str
|
|
324
|
+
wire_schema_utf8_bytes: int
|
|
325
|
+
wire_schema_sha256: str
|
|
326
|
+
max_completion_tokens: int
|
|
327
|
+
requested_temperature_hex: str | None
|
|
328
|
+
provider: dict[str, object]
|
|
329
|
+
reasoning: dict[str, object] | None
|
|
330
|
+
stream: bool
|
|
331
|
+
output_mode: str
|
|
332
|
+
output_strict: bool
|
|
333
|
+
expected_tool_choice: str
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
_BOUND_OUTBOUND_EXPECTATION: ContextVar[_OutboundExpectation | None] = ContextVar(
|
|
337
|
+
"agent_evolve_openrouter_outbound_expectation",
|
|
338
|
+
default=None,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class OpenRouterOutboundRequestManifestPublisher:
|
|
343
|
+
"""Concurrency-safe HTTPX hook and task-local request expectation owner."""
|
|
344
|
+
|
|
345
|
+
def __init__(
|
|
346
|
+
self,
|
|
347
|
+
sink: OpenRouterOutboundRequestManifestSink,
|
|
348
|
+
*,
|
|
349
|
+
json_schema_transformer: type[Any] | None = None,
|
|
350
|
+
) -> None:
|
|
351
|
+
if not callable(sink):
|
|
352
|
+
raise TypeError("outbound request manifest sink must be callable")
|
|
353
|
+
if json_schema_transformer is not None and not isinstance(
|
|
354
|
+
json_schema_transformer, type
|
|
355
|
+
):
|
|
356
|
+
raise TypeError("json_schema_transformer must be a class or None")
|
|
357
|
+
self._sink = sink
|
|
358
|
+
self._json_schema_transformer = json_schema_transformer
|
|
359
|
+
self._identity = object()
|
|
360
|
+
self._publication_lock = threading.Lock()
|
|
361
|
+
self._published_provider_attempt_ids: set[str] = set()
|
|
362
|
+
|
|
363
|
+
@contextmanager
|
|
364
|
+
def bind(
|
|
365
|
+
self,
|
|
366
|
+
request: StructuredGenerationRequest[Any],
|
|
367
|
+
*,
|
|
368
|
+
requested_model: str,
|
|
369
|
+
provider: Mapping[str, object],
|
|
370
|
+
reasoning: Mapping[str, object] | None,
|
|
371
|
+
stream: bool,
|
|
372
|
+
output_mode: str = "tool",
|
|
373
|
+
output_strict: bool = False,
|
|
374
|
+
expected_tool_choice: str = "required",
|
|
375
|
+
) -> Iterator[None]:
|
|
376
|
+
"""Bind one exact physical-attempt identity to the current async task."""
|
|
377
|
+
|
|
378
|
+
if type(request) is not StructuredGenerationRequest:
|
|
379
|
+
raise TypeError("request must be an exact StructuredGenerationRequest")
|
|
380
|
+
StructuredGenerationRequest.__post_init__(request)
|
|
381
|
+
if request.provider_attempt_id is None:
|
|
382
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
383
|
+
"outbound evidence requires a physical provider_attempt_id"
|
|
384
|
+
)
|
|
385
|
+
if (
|
|
386
|
+
type(requested_model) is not str
|
|
387
|
+
or _MODEL_SLUG.fullmatch(requested_model) is None
|
|
388
|
+
):
|
|
389
|
+
raise ValueError("requested_model must be an OpenRouter model slug")
|
|
390
|
+
if not isinstance(provider, Mapping):
|
|
391
|
+
raise TypeError("provider must be a mapping")
|
|
392
|
+
if reasoning is not None and not isinstance(reasoning, Mapping):
|
|
393
|
+
raise TypeError("reasoning must be a mapping or None")
|
|
394
|
+
if type(stream) is not bool:
|
|
395
|
+
raise TypeError("stream must be an exact bool")
|
|
396
|
+
if type(output_strict) is not bool:
|
|
397
|
+
raise TypeError("output_strict must be an exact bool")
|
|
398
|
+
if (
|
|
399
|
+
type(expected_tool_choice) is not str
|
|
400
|
+
or expected_tool_choice not in {"required", "auto"}
|
|
401
|
+
):
|
|
402
|
+
raise ValueError("expected_tool_choice must be required or auto")
|
|
403
|
+
if (
|
|
404
|
+
type(output_mode) is not str
|
|
405
|
+
or output_mode not in _STRUCTURED_OUTPUT_MODES
|
|
406
|
+
):
|
|
407
|
+
raise ValueError("output_mode is outside the closed transport modes")
|
|
408
|
+
if _BOUND_OUTBOUND_EXPECTATION.get() is not None:
|
|
409
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
410
|
+
"nested outbound request evidence contexts are forbidden"
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
try:
|
|
414
|
+
provider_json = _canonical_json_copy(dict(provider), label="provider")
|
|
415
|
+
reasoning_json = (
|
|
416
|
+
None
|
|
417
|
+
if reasoning is None
|
|
418
|
+
else _canonical_json_copy(dict(reasoning), label="reasoning")
|
|
419
|
+
)
|
|
420
|
+
provider_copy = _closed_provider(provider_json)
|
|
421
|
+
reasoning_copy = _closed_reasoning(reasoning_json)
|
|
422
|
+
except (TypeError, ValueError) as exc:
|
|
423
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
424
|
+
"provider/reasoning settings violate the closed safe projection"
|
|
425
|
+
) from exc
|
|
426
|
+
prompt_bytes = request.prompt.encode("utf-8", errors="strict")
|
|
427
|
+
(
|
|
428
|
+
logical_schema_utf8_bytes,
|
|
429
|
+
logical_schema_sha256,
|
|
430
|
+
wire_schema_utf8_bytes,
|
|
431
|
+
wire_schema_sha256,
|
|
432
|
+
) = _wire_output_schema(
|
|
433
|
+
request.output_type,
|
|
434
|
+
json_schema_transformer=self._json_schema_transformer,
|
|
435
|
+
strict=output_strict,
|
|
436
|
+
)
|
|
437
|
+
expectation = _OutboundExpectation(
|
|
438
|
+
publisher_identity=self._identity,
|
|
439
|
+
call_id=request.call_id.value,
|
|
440
|
+
operation=request.operation,
|
|
441
|
+
provider_attempt_id=request.provider_attempt_id.value,
|
|
442
|
+
requested_model=requested_model,
|
|
443
|
+
prompt_utf8_bytes=len(prompt_bytes),
|
|
444
|
+
prompt_sha256=_sha256(prompt_bytes),
|
|
445
|
+
output_tool_name=request.output_tool_name,
|
|
446
|
+
logical_schema_utf8_bytes=logical_schema_utf8_bytes,
|
|
447
|
+
logical_schema_sha256=logical_schema_sha256,
|
|
448
|
+
wire_schema_utf8_bytes=wire_schema_utf8_bytes,
|
|
449
|
+
wire_schema_sha256=wire_schema_sha256,
|
|
450
|
+
max_completion_tokens=request.max_output_tokens,
|
|
451
|
+
requested_temperature_hex=(
|
|
452
|
+
None
|
|
453
|
+
if request.temperature is None
|
|
454
|
+
else float(request.temperature).hex()
|
|
455
|
+
),
|
|
456
|
+
provider=provider_copy,
|
|
457
|
+
reasoning=reasoning_copy,
|
|
458
|
+
stream=stream,
|
|
459
|
+
output_mode=output_mode,
|
|
460
|
+
output_strict=output_strict,
|
|
461
|
+
expected_tool_choice=expected_tool_choice,
|
|
462
|
+
)
|
|
463
|
+
token = _BOUND_OUTBOUND_EXPECTATION.set(expectation)
|
|
464
|
+
try:
|
|
465
|
+
yield
|
|
466
|
+
finally:
|
|
467
|
+
_BOUND_OUTBOUND_EXPECTATION.reset(token)
|
|
468
|
+
|
|
469
|
+
async def httpx_request_hook(self, request: Any) -> None:
|
|
470
|
+
"""Validate and publish the final serialized body before HTTP transport."""
|
|
471
|
+
|
|
472
|
+
expectation = _BOUND_OUTBOUND_EXPECTATION.get()
|
|
473
|
+
if expectation is None or expectation.publisher_identity is not self._identity:
|
|
474
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
475
|
+
"OpenRouter dispatch has no matching task-local attempt context"
|
|
476
|
+
)
|
|
477
|
+
record = await _manifest_record_from_httpx_request(request, expectation)
|
|
478
|
+
provider_attempt_id = expectation.provider_attempt_id
|
|
479
|
+
with self._publication_lock:
|
|
480
|
+
if provider_attempt_id in self._published_provider_attempt_ids:
|
|
481
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
482
|
+
"duplicate transport dispatch for one provider_attempt_id"
|
|
483
|
+
)
|
|
484
|
+
# Reserve before publication. A sink that writes and then raises must
|
|
485
|
+
# never cause the same physical identity to be published/sent again.
|
|
486
|
+
self._published_provider_attempt_ids.add(provider_attempt_id)
|
|
487
|
+
self._sink(record)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
async def _manifest_record_from_httpx_request(
|
|
491
|
+
request: Any,
|
|
492
|
+
expectation: _OutboundExpectation,
|
|
493
|
+
) -> dict[str, object]:
|
|
494
|
+
if getattr(request, "method", None) != _TRANSPORT_CONTRACT["method"]:
|
|
495
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
496
|
+
"outbound request method violates the transport contract"
|
|
497
|
+
)
|
|
498
|
+
url = getattr(request, "url", None)
|
|
499
|
+
if (
|
|
500
|
+
url is None
|
|
501
|
+
or getattr(url, "scheme", None) != _TRANSPORT_CONTRACT["scheme"]
|
|
502
|
+
or getattr(url, "host", None) != _TRANSPORT_CONTRACT["host"]
|
|
503
|
+
or getattr(url, "port", None) is not None
|
|
504
|
+
or getattr(url, "path", None) != _TRANSPORT_CONTRACT["path"]
|
|
505
|
+
or getattr(url, "query", b"") not in (b"", "")
|
|
506
|
+
):
|
|
507
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
508
|
+
"outbound URL violates the closed OpenRouter transport contract"
|
|
509
|
+
)
|
|
510
|
+
try:
|
|
511
|
+
wire_body = await request.aread()
|
|
512
|
+
except Exception as exc:
|
|
513
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
514
|
+
"outbound JSON body could not be read before transport"
|
|
515
|
+
) from exc
|
|
516
|
+
if (
|
|
517
|
+
type(wire_body) is not bytes
|
|
518
|
+
or not wire_body
|
|
519
|
+
or len(wire_body) > _MAX_WIRE_BODY_BYTES
|
|
520
|
+
):
|
|
521
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
522
|
+
"outbound JSON body is empty or outside its evidence bound"
|
|
523
|
+
)
|
|
524
|
+
try:
|
|
525
|
+
body = json.loads(
|
|
526
|
+
wire_body,
|
|
527
|
+
object_pairs_hook=_reject_duplicate_json_keys,
|
|
528
|
+
)
|
|
529
|
+
except (UnicodeDecodeError, json.JSONDecodeError, ValueError) as exc:
|
|
530
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
531
|
+
"outbound body is not one UTF-8 JSON object"
|
|
532
|
+
) from exc
|
|
533
|
+
if type(body) is not dict:
|
|
534
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
535
|
+
"outbound body must be one exact JSON object"
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
expected_fields = set(_BASE_BODY_FIELDS)
|
|
539
|
+
if expectation.output_mode == "tool":
|
|
540
|
+
expected_fields.update(("tools", "tool_choice"))
|
|
541
|
+
else:
|
|
542
|
+
expected_fields.add("response_format")
|
|
543
|
+
if expectation.reasoning is not None:
|
|
544
|
+
expected_fields.add("reasoning")
|
|
545
|
+
if expectation.requested_temperature_hex is not None:
|
|
546
|
+
# Pydantic-AI intentionally drops sampling parameters for model
|
|
547
|
+
# profiles that identify an active reasoning model. Absence is therefore
|
|
548
|
+
# admitted and recorded; any present value must still match exactly.
|
|
549
|
+
if "temperature" in body:
|
|
550
|
+
expected_fields.add("temperature")
|
|
551
|
+
if expectation.stream:
|
|
552
|
+
expected_fields.add("stream_options")
|
|
553
|
+
if frozenset(body) != frozenset(expected_fields):
|
|
554
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
555
|
+
"outbound body has missing or unexpected top-level fields"
|
|
556
|
+
)
|
|
557
|
+
if any(name in body for name in _FORBIDDEN_TOP_LEVEL_FIELDS):
|
|
558
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
559
|
+
"outbound body contains a forbidden routing/behavior field"
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
model = body["model"]
|
|
563
|
+
if type(model) is not str or model != expectation.requested_model:
|
|
564
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
565
|
+
"outbound model does not match the bound request"
|
|
566
|
+
)
|
|
567
|
+
max_completion_tokens = body["max_completion_tokens"]
|
|
568
|
+
if (
|
|
569
|
+
type(max_completion_tokens) is not int
|
|
570
|
+
or max_completion_tokens != expectation.max_completion_tokens
|
|
571
|
+
):
|
|
572
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
573
|
+
"outbound completion budget does not match the bound request"
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
messages = body["messages"]
|
|
577
|
+
if (
|
|
578
|
+
type(messages) is not list
|
|
579
|
+
or len(messages) != 1
|
|
580
|
+
or type(messages[0]) is not dict
|
|
581
|
+
or frozenset(messages[0]) != {"content", "role"}
|
|
582
|
+
or messages[0]["role"] != "user"
|
|
583
|
+
or type(messages[0]["content"]) is not str
|
|
584
|
+
):
|
|
585
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
586
|
+
"outbound messages violate the single-prompt contract"
|
|
587
|
+
)
|
|
588
|
+
prompt_bytes = messages[0]["content"].encode("utf-8", errors="strict")
|
|
589
|
+
if (
|
|
590
|
+
len(prompt_bytes) != expectation.prompt_utf8_bytes
|
|
591
|
+
or _sha256(prompt_bytes) != expectation.prompt_sha256
|
|
592
|
+
):
|
|
593
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
594
|
+
"outbound prompt does not match the bound request"
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
output_count: int
|
|
598
|
+
output_kind: str
|
|
599
|
+
output_name: str
|
|
600
|
+
output_description: str
|
|
601
|
+
wire_strict: bool | None
|
|
602
|
+
tool_choice: str | None
|
|
603
|
+
response_format_kind: str | None
|
|
604
|
+
if expectation.output_mode == "tool":
|
|
605
|
+
tools = body["tools"]
|
|
606
|
+
if (
|
|
607
|
+
type(tools) is not list
|
|
608
|
+
or len(tools) != 1
|
|
609
|
+
or type(tools[0]) is not dict
|
|
610
|
+
or frozenset(tools[0]) != {"function", "type"}
|
|
611
|
+
or tools[0]["type"] != "function"
|
|
612
|
+
or type(tools[0]["function"]) is not dict
|
|
613
|
+
):
|
|
614
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
615
|
+
"outbound tools violate the single-function contract"
|
|
616
|
+
)
|
|
617
|
+
function = tools[0]["function"]
|
|
618
|
+
expected_function_fields = {"description", "name", "parameters"}
|
|
619
|
+
if expectation.output_strict:
|
|
620
|
+
expected_function_fields.add("strict")
|
|
621
|
+
if frozenset(function) != expected_function_fields:
|
|
622
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
623
|
+
"outbound function tool has unexpected fields"
|
|
624
|
+
)
|
|
625
|
+
if (
|
|
626
|
+
type(function["name"]) is not str
|
|
627
|
+
or function["name"] != expectation.output_tool_name
|
|
628
|
+
or type(function["description"]) is not str
|
|
629
|
+
or type(function["parameters"]) is not dict
|
|
630
|
+
):
|
|
631
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
632
|
+
"outbound function tool does not match the bound contract"
|
|
633
|
+
)
|
|
634
|
+
output_count = 1
|
|
635
|
+
output_kind = "function"
|
|
636
|
+
output_name = function["name"]
|
|
637
|
+
output_description = function["description"]
|
|
638
|
+
schema = function["parameters"]
|
|
639
|
+
wire_strict = function.get("strict")
|
|
640
|
+
tool_choice = body["tool_choice"]
|
|
641
|
+
response_format_kind = None
|
|
642
|
+
if tool_choice != expectation.expected_tool_choice:
|
|
643
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
644
|
+
"outbound tool choice differs from the bound route capability"
|
|
645
|
+
)
|
|
646
|
+
if wire_strict != (True if expectation.output_strict else None):
|
|
647
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
648
|
+
"outbound function strictness differs from the bound contract"
|
|
649
|
+
)
|
|
650
|
+
else:
|
|
651
|
+
response_format = body["response_format"]
|
|
652
|
+
if (
|
|
653
|
+
type(response_format) is not dict
|
|
654
|
+
or frozenset(response_format) != {"json_schema", "type"}
|
|
655
|
+
or response_format["type"] != "json_schema"
|
|
656
|
+
or type(response_format["json_schema"]) is not dict
|
|
657
|
+
):
|
|
658
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
659
|
+
"outbound native response format violates the JSON-schema contract"
|
|
660
|
+
)
|
|
661
|
+
native = response_format["json_schema"]
|
|
662
|
+
if frozenset(native) not in (
|
|
663
|
+
frozenset({"name", "schema", "strict"}),
|
|
664
|
+
frozenset({"description", "name", "schema", "strict"}),
|
|
665
|
+
):
|
|
666
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
667
|
+
"outbound native JSON schema has unexpected fields"
|
|
668
|
+
)
|
|
669
|
+
if (
|
|
670
|
+
type(native["name"]) is not str
|
|
671
|
+
or native["name"] != expectation.output_tool_name
|
|
672
|
+
or type(native["schema"]) is not dict
|
|
673
|
+
or native["strict"] is not expectation.output_strict
|
|
674
|
+
or (
|
|
675
|
+
"description" in native
|
|
676
|
+
and type(native["description"]) is not str
|
|
677
|
+
)
|
|
678
|
+
):
|
|
679
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
680
|
+
"outbound native JSON schema does not match the bound contract"
|
|
681
|
+
)
|
|
682
|
+
output_count = 0
|
|
683
|
+
output_kind = "native_json_schema"
|
|
684
|
+
output_name = native["name"]
|
|
685
|
+
output_description = native.get("description", "")
|
|
686
|
+
schema = native["schema"]
|
|
687
|
+
wire_strict = expectation.output_strict
|
|
688
|
+
tool_choice = None
|
|
689
|
+
response_format_kind = "json_schema"
|
|
690
|
+
|
|
691
|
+
description_bytes = output_description.encode("utf-8", errors="strict")
|
|
692
|
+
schema_bytes = _canonical_bytes(schema)
|
|
693
|
+
if (
|
|
694
|
+
len(schema_bytes) != expectation.wire_schema_utf8_bytes
|
|
695
|
+
or _sha256(schema_bytes) != expectation.wire_schema_sha256
|
|
696
|
+
):
|
|
697
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
698
|
+
"outbound structured schema does not match the bound output type: "
|
|
699
|
+
f"expected={expectation.wire_schema_sha256}/"
|
|
700
|
+
f"{expectation.wire_schema_utf8_bytes}, "
|
|
701
|
+
f"observed={_sha256(schema_bytes)}/{len(schema_bytes)}"
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
if type(body["provider"]) is not dict or body["provider"] != expectation.provider:
|
|
705
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
706
|
+
"outbound provider routing differs from the bound route"
|
|
707
|
+
)
|
|
708
|
+
reasoning = body.get("reasoning")
|
|
709
|
+
if reasoning != expectation.reasoning or (
|
|
710
|
+
reasoning is not None and type(reasoning) is not dict
|
|
711
|
+
):
|
|
712
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
713
|
+
"outbound reasoning differs from the bound configuration"
|
|
714
|
+
)
|
|
715
|
+
if reasoning is not None:
|
|
716
|
+
expected_reasoning_fields = (
|
|
717
|
+
{"effort"} if "effort" in reasoning else {"max_tokens"}
|
|
718
|
+
)
|
|
719
|
+
if (
|
|
720
|
+
frozenset(reasoning) != expected_reasoning_fields
|
|
721
|
+
or any(name in reasoning for name in _FORBIDDEN_REASONING_FIELDS)
|
|
722
|
+
):
|
|
723
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
724
|
+
"outbound reasoning has unexpected or forbidden fields"
|
|
725
|
+
)
|
|
726
|
+
if body["usage"] != {"include": True}:
|
|
727
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
728
|
+
"outbound usage accounting is not explicitly enabled"
|
|
729
|
+
)
|
|
730
|
+
if type(body["stream"]) is not bool or body["stream"] is not expectation.stream:
|
|
731
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
732
|
+
"outbound stream mode differs from the bound execution path"
|
|
733
|
+
)
|
|
734
|
+
stream_options = body.get("stream_options")
|
|
735
|
+
if stream_options != ({"include_usage": True} if expectation.stream else None):
|
|
736
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
737
|
+
"outbound stream usage options violate the closed contract"
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
temperature = body.get("temperature")
|
|
741
|
+
temperature_hex: str | None = None
|
|
742
|
+
if temperature is not None:
|
|
743
|
+
if (
|
|
744
|
+
isinstance(temperature, bool)
|
|
745
|
+
or not isinstance(temperature, (int, float))
|
|
746
|
+
or not math.isfinite(float(temperature))
|
|
747
|
+
):
|
|
748
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
749
|
+
"outbound temperature is not a finite number"
|
|
750
|
+
)
|
|
751
|
+
temperature_hex = float(temperature).hex()
|
|
752
|
+
if temperature_hex != expectation.requested_temperature_hex:
|
|
753
|
+
raise OpenRouterOutboundRequestManifestError(
|
|
754
|
+
"outbound temperature differs from the bound request"
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
canonical_body = _canonical_bytes(body)
|
|
758
|
+
record: dict[str, object] = {
|
|
759
|
+
"schema_version": OPENROUTER_OUTBOUND_REQUEST_MANIFEST_SCHEMA_VERSION,
|
|
760
|
+
"boundary_id": OPENROUTER_OUTBOUND_REQUEST_BOUNDARY_ID,
|
|
761
|
+
"call_id": expectation.call_id,
|
|
762
|
+
"operation": expectation.operation,
|
|
763
|
+
"provider_attempt_id": expectation.provider_attempt_id,
|
|
764
|
+
"transport": dict(_TRANSPORT_CONTRACT),
|
|
765
|
+
"transport_contract_sha256": _transport_contract_sha256(),
|
|
766
|
+
"framework_versions": _framework_versions(),
|
|
767
|
+
"body": {
|
|
768
|
+
"wire_utf8_bytes": len(wire_body),
|
|
769
|
+
"wire_sha256": _sha256(wire_body),
|
|
770
|
+
"canonical_utf8_bytes": len(canonical_body),
|
|
771
|
+
"canonical_sha256": _sha256(canonical_body),
|
|
772
|
+
"top_level_keys": sorted(body),
|
|
773
|
+
},
|
|
774
|
+
"message": {
|
|
775
|
+
"count": 1,
|
|
776
|
+
"role": "user",
|
|
777
|
+
"content_utf8_bytes": len(prompt_bytes),
|
|
778
|
+
"content_sha256": _sha256(prompt_bytes),
|
|
779
|
+
},
|
|
780
|
+
"tool": {
|
|
781
|
+
"count": output_count,
|
|
782
|
+
"type": output_kind,
|
|
783
|
+
"name": output_name,
|
|
784
|
+
"description_utf8_bytes": len(description_bytes),
|
|
785
|
+
"description_sha256": _sha256(description_bytes),
|
|
786
|
+
"schema_utf8_bytes": len(schema_bytes),
|
|
787
|
+
"schema_sha256": _sha256(schema_bytes),
|
|
788
|
+
"requested_strict": expectation.output_strict,
|
|
789
|
+
"wire_strict": wire_strict,
|
|
790
|
+
},
|
|
791
|
+
"request_contract": {
|
|
792
|
+
"logical_output_schema_utf8_bytes": (
|
|
793
|
+
expectation.logical_schema_utf8_bytes
|
|
794
|
+
),
|
|
795
|
+
"logical_output_schema_sha256": expectation.logical_schema_sha256,
|
|
796
|
+
"wire_output_schema_utf8_bytes": expectation.wire_schema_utf8_bytes,
|
|
797
|
+
"wire_output_schema_sha256": expectation.wire_schema_sha256,
|
|
798
|
+
"requested_temperature_hex": expectation.requested_temperature_hex,
|
|
799
|
+
},
|
|
800
|
+
"settings": {
|
|
801
|
+
"model": model,
|
|
802
|
+
"provider": body["provider"],
|
|
803
|
+
"reasoning": reasoning,
|
|
804
|
+
"usage": body["usage"],
|
|
805
|
+
"stream": body["stream"],
|
|
806
|
+
"stream_options": stream_options,
|
|
807
|
+
"output_mode": expectation.output_mode,
|
|
808
|
+
"tool_choice": tool_choice,
|
|
809
|
+
"max_completion_tokens": max_completion_tokens,
|
|
810
|
+
"temperature_hex": temperature_hex,
|
|
811
|
+
"response_format": response_format_kind,
|
|
812
|
+
},
|
|
813
|
+
"forbidden_fields_absent": {
|
|
814
|
+
**{name: name not in body for name in _FORBIDDEN_TOP_LEVEL_FIELDS},
|
|
815
|
+
**{
|
|
816
|
+
f"reasoning.{name}": reasoning is None or name not in reasoning
|
|
817
|
+
for name in _FORBIDDEN_REASONING_FIELDS
|
|
818
|
+
},
|
|
819
|
+
},
|
|
820
|
+
"hash_algorithms": {
|
|
821
|
+
"manifest": OPENROUTER_OUTBOUND_REQUEST_MANIFEST_HASH_ALGORITHM,
|
|
822
|
+
"public_projection": (
|
|
823
|
+
OPENROUTER_OUTBOUND_PUBLIC_PROJECTION_HASH_ALGORITHM
|
|
824
|
+
),
|
|
825
|
+
"transport_contract": (
|
|
826
|
+
OPENROUTER_OUTBOUND_TRANSPORT_CONTRACT_HASH_ALGORITHM
|
|
827
|
+
),
|
|
828
|
+
"content": "sha256_exact_bytes_or_canonical_json_v1",
|
|
829
|
+
},
|
|
830
|
+
}
|
|
831
|
+
record["public_projection_sha256"] = _domain_sha256(
|
|
832
|
+
_PUBLIC_PROJECTION_DOMAIN,
|
|
833
|
+
record,
|
|
834
|
+
)
|
|
835
|
+
record["outbound_request_manifest_sha256"] = _domain_sha256(
|
|
836
|
+
_MANIFEST_DOMAIN,
|
|
837
|
+
record,
|
|
838
|
+
)
|
|
839
|
+
return validate_openrouter_outbound_request_manifest_record(record)
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
_MANIFEST_FIELDS = frozenset(
|
|
843
|
+
{
|
|
844
|
+
"schema_version",
|
|
845
|
+
"boundary_id",
|
|
846
|
+
"call_id",
|
|
847
|
+
"operation",
|
|
848
|
+
"provider_attempt_id",
|
|
849
|
+
"transport",
|
|
850
|
+
"transport_contract_sha256",
|
|
851
|
+
"framework_versions",
|
|
852
|
+
"body",
|
|
853
|
+
"message",
|
|
854
|
+
"tool",
|
|
855
|
+
"request_contract",
|
|
856
|
+
"settings",
|
|
857
|
+
"forbidden_fields_absent",
|
|
858
|
+
"hash_algorithms",
|
|
859
|
+
"public_projection_sha256",
|
|
860
|
+
"outbound_request_manifest_sha256",
|
|
861
|
+
}
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
def _require_sha256(value: object, *, label: str) -> str:
|
|
866
|
+
if type(value) is not str or _LOWER_SHA256.fullmatch(value) is None:
|
|
867
|
+
raise ValueError(f"{label} must be a lowercase SHA-256 digest")
|
|
868
|
+
return value
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
def _validated_temperature_hex(value: object, *, label: str) -> str | None:
|
|
872
|
+
if value is None:
|
|
873
|
+
return None
|
|
874
|
+
if type(value) is not str:
|
|
875
|
+
raise ValueError(f"{label} must be a hexadecimal float or None")
|
|
876
|
+
try:
|
|
877
|
+
decoded = float.fromhex(value)
|
|
878
|
+
except ValueError as exc:
|
|
879
|
+
raise ValueError(f"{label} is not a hexadecimal float") from exc
|
|
880
|
+
if (
|
|
881
|
+
not math.isfinite(decoded)
|
|
882
|
+
or not 0 <= decoded <= 2
|
|
883
|
+
or decoded.hex() != value
|
|
884
|
+
):
|
|
885
|
+
raise ValueError(f"{label} is outside the canonical temperature range")
|
|
886
|
+
return value
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
def validate_openrouter_outbound_request_manifest_record(
|
|
890
|
+
record: Mapping[str, object],
|
|
891
|
+
) -> dict[str, object]:
|
|
892
|
+
"""Strictly validate, authenticate, and detach one persisted manifest row."""
|
|
893
|
+
|
|
894
|
+
if not isinstance(record, Mapping):
|
|
895
|
+
raise TypeError("outbound request manifest must be a mapping")
|
|
896
|
+
try:
|
|
897
|
+
canonical = json.loads(_canonical_bytes(dict(record)))
|
|
898
|
+
except (TypeError, ValueError) as exc:
|
|
899
|
+
raise ValueError("outbound request manifest is not canonical JSON") from exc
|
|
900
|
+
if type(canonical) is not dict or frozenset(canonical) != _MANIFEST_FIELDS:
|
|
901
|
+
raise ValueError("outbound request manifest has unexpected fields")
|
|
902
|
+
if (
|
|
903
|
+
type(canonical["schema_version"]) is not int
|
|
904
|
+
or canonical["schema_version"]
|
|
905
|
+
!= OPENROUTER_OUTBOUND_REQUEST_MANIFEST_SCHEMA_VERSION
|
|
906
|
+
or canonical["boundary_id"] != OPENROUTER_OUTBOUND_REQUEST_BOUNDARY_ID
|
|
907
|
+
):
|
|
908
|
+
raise ValueError("unsupported outbound request manifest contract")
|
|
909
|
+
LLMCallId(canonical["call_id"])
|
|
910
|
+
ProviderAttemptId(canonical["provider_attempt_id"])
|
|
911
|
+
if (
|
|
912
|
+
type(canonical["operation"]) is not str
|
|
913
|
+
or _OPERATION_TOKEN.fullmatch(canonical["operation"]) is None
|
|
914
|
+
):
|
|
915
|
+
raise ValueError("operation is outside the closed token grammar")
|
|
916
|
+
if canonical["transport"] != _TRANSPORT_CONTRACT:
|
|
917
|
+
raise ValueError("transport contract projection drifted")
|
|
918
|
+
frameworks = canonical["framework_versions"]
|
|
919
|
+
if (
|
|
920
|
+
type(frameworks) is not dict
|
|
921
|
+
or tuple(sorted(frameworks)) != tuple(sorted(_FRAMEWORK_PACKAGES))
|
|
922
|
+
or any(
|
|
923
|
+
type(value) is not str or _VERSION_TOKEN.fullmatch(value) is None
|
|
924
|
+
for value in frameworks.values()
|
|
925
|
+
)
|
|
926
|
+
):
|
|
927
|
+
raise ValueError("framework version projection is outside its closed schema")
|
|
928
|
+
if (
|
|
929
|
+
_require_sha256(
|
|
930
|
+
canonical["transport_contract_sha256"],
|
|
931
|
+
label="transport_contract_sha256",
|
|
932
|
+
)
|
|
933
|
+
!= _transport_contract_sha256()
|
|
934
|
+
):
|
|
935
|
+
raise ValueError("transport_contract_sha256 does not authenticate contract")
|
|
936
|
+
|
|
937
|
+
exact_nested_fields = {
|
|
938
|
+
"body": {
|
|
939
|
+
"wire_utf8_bytes",
|
|
940
|
+
"wire_sha256",
|
|
941
|
+
"canonical_utf8_bytes",
|
|
942
|
+
"canonical_sha256",
|
|
943
|
+
"top_level_keys",
|
|
944
|
+
},
|
|
945
|
+
"message": {
|
|
946
|
+
"count",
|
|
947
|
+
"role",
|
|
948
|
+
"content_utf8_bytes",
|
|
949
|
+
"content_sha256",
|
|
950
|
+
},
|
|
951
|
+
"tool": {
|
|
952
|
+
"count",
|
|
953
|
+
"type",
|
|
954
|
+
"name",
|
|
955
|
+
"description_utf8_bytes",
|
|
956
|
+
"description_sha256",
|
|
957
|
+
"schema_utf8_bytes",
|
|
958
|
+
"schema_sha256",
|
|
959
|
+
"requested_strict",
|
|
960
|
+
"wire_strict",
|
|
961
|
+
},
|
|
962
|
+
"request_contract": {
|
|
963
|
+
"logical_output_schema_utf8_bytes",
|
|
964
|
+
"logical_output_schema_sha256",
|
|
965
|
+
"wire_output_schema_utf8_bytes",
|
|
966
|
+
"wire_output_schema_sha256",
|
|
967
|
+
"requested_temperature_hex",
|
|
968
|
+
},
|
|
969
|
+
"settings": {
|
|
970
|
+
"model",
|
|
971
|
+
"provider",
|
|
972
|
+
"reasoning",
|
|
973
|
+
"usage",
|
|
974
|
+
"stream",
|
|
975
|
+
"stream_options",
|
|
976
|
+
"output_mode",
|
|
977
|
+
"tool_choice",
|
|
978
|
+
"max_completion_tokens",
|
|
979
|
+
"temperature_hex",
|
|
980
|
+
"response_format",
|
|
981
|
+
},
|
|
982
|
+
"hash_algorithms": {
|
|
983
|
+
"manifest",
|
|
984
|
+
"public_projection",
|
|
985
|
+
"transport_contract",
|
|
986
|
+
"content",
|
|
987
|
+
},
|
|
988
|
+
}
|
|
989
|
+
for name, fields in exact_nested_fields.items():
|
|
990
|
+
value = canonical[name]
|
|
991
|
+
if type(value) is not dict or frozenset(value) != fields:
|
|
992
|
+
raise ValueError(f"{name} projection has unexpected fields")
|
|
993
|
+
for container, names in (
|
|
994
|
+
(canonical["body"], ("wire_sha256", "canonical_sha256")),
|
|
995
|
+
(canonical["message"], ("content_sha256",)),
|
|
996
|
+
(canonical["tool"], ("description_sha256", "schema_sha256")),
|
|
997
|
+
(
|
|
998
|
+
canonical["request_contract"],
|
|
999
|
+
("logical_output_schema_sha256", "wire_output_schema_sha256"),
|
|
1000
|
+
),
|
|
1001
|
+
):
|
|
1002
|
+
for name in names:
|
|
1003
|
+
_require_sha256(container[name], label=name)
|
|
1004
|
+
for container, names in (
|
|
1005
|
+
(canonical["body"], ("wire_utf8_bytes", "canonical_utf8_bytes")),
|
|
1006
|
+
(canonical["message"], ("content_utf8_bytes",)),
|
|
1007
|
+
(
|
|
1008
|
+
canonical["tool"],
|
|
1009
|
+
("description_utf8_bytes", "schema_utf8_bytes"),
|
|
1010
|
+
),
|
|
1011
|
+
(
|
|
1012
|
+
canonical["request_contract"],
|
|
1013
|
+
("logical_output_schema_utf8_bytes", "wire_output_schema_utf8_bytes"),
|
|
1014
|
+
),
|
|
1015
|
+
):
|
|
1016
|
+
if any(type(container[name]) is not int or container[name] < 0 for name in names):
|
|
1017
|
+
raise ValueError("manifest byte lengths must be non-negative integers")
|
|
1018
|
+
body = canonical["body"]
|
|
1019
|
+
message = canonical["message"]
|
|
1020
|
+
tool = canonical["tool"]
|
|
1021
|
+
request_contract = canonical["request_contract"]
|
|
1022
|
+
settings = canonical["settings"]
|
|
1023
|
+
if (
|
|
1024
|
+
not 1 <= body["wire_utf8_bytes"] <= _MAX_WIRE_BODY_BYTES
|
|
1025
|
+
or not 1 <= body["canonical_utf8_bytes"] <= _MAX_WIRE_BODY_BYTES
|
|
1026
|
+
or not 1 <= message["content_utf8_bytes"] <= MAX_PROMPT_UTF8_BYTES
|
|
1027
|
+
or tool["description_utf8_bytes"] > MAX_PROMPT_UTF8_BYTES
|
|
1028
|
+
or not 1 <= tool["schema_utf8_bytes"] <= 1_048_576
|
|
1029
|
+
or not 1 <= request_contract["logical_output_schema_utf8_bytes"] <= 1_048_576
|
|
1030
|
+
or not 1 <= request_contract["wire_output_schema_utf8_bytes"] <= 1_048_576
|
|
1031
|
+
):
|
|
1032
|
+
raise ValueError("manifest byte length exceeds its generic bound")
|
|
1033
|
+
output_mode = settings["output_mode"]
|
|
1034
|
+
if type(output_mode) is not str or output_mode not in _STRUCTURED_OUTPUT_MODES:
|
|
1035
|
+
raise ValueError("manifest output mode is outside the closed transport modes")
|
|
1036
|
+
tool_mode_valid = (
|
|
1037
|
+
output_mode == "tool"
|
|
1038
|
+
and tool["count"] == 1
|
|
1039
|
+
and tool["type"] == "function"
|
|
1040
|
+
and tool["wire_strict"]
|
|
1041
|
+
== (True if tool["requested_strict"] else None)
|
|
1042
|
+
and settings["tool_choice"] in {"required", "auto"}
|
|
1043
|
+
and settings["response_format"] is None
|
|
1044
|
+
)
|
|
1045
|
+
native_mode_valid = (
|
|
1046
|
+
output_mode == "native_json_schema"
|
|
1047
|
+
and tool["count"] == 0
|
|
1048
|
+
and tool["type"] == "native_json_schema"
|
|
1049
|
+
and tool["wire_strict"] is tool["requested_strict"]
|
|
1050
|
+
and settings["tool_choice"] is None
|
|
1051
|
+
and settings["response_format"] == "json_schema"
|
|
1052
|
+
)
|
|
1053
|
+
if (
|
|
1054
|
+
message["count"] != 1
|
|
1055
|
+
or message["role"] != "user"
|
|
1056
|
+
or not (tool_mode_valid or native_mode_valid)
|
|
1057
|
+
or type(tool["name"]) is not str
|
|
1058
|
+
or _TOOL_TOKEN.fullmatch(tool["name"]) is None
|
|
1059
|
+
or type(tool["requested_strict"]) is not bool
|
|
1060
|
+
or settings["usage"] != {"include": True}
|
|
1061
|
+
):
|
|
1062
|
+
raise ValueError("manifest projections violate the structured request contract")
|
|
1063
|
+
if (
|
|
1064
|
+
tool["schema_sha256"] != request_contract["wire_output_schema_sha256"]
|
|
1065
|
+
or tool["schema_utf8_bytes"]
|
|
1066
|
+
!= request_contract["wire_output_schema_utf8_bytes"]
|
|
1067
|
+
):
|
|
1068
|
+
raise ValueError("tool schema projection does not join its request contract")
|
|
1069
|
+
|
|
1070
|
+
model = settings["model"]
|
|
1071
|
+
if type(model) is not str or _MODEL_SLUG.fullmatch(model) is None:
|
|
1072
|
+
raise ValueError("model is outside the closed OpenRouter slug grammar")
|
|
1073
|
+
try:
|
|
1074
|
+
provider = _closed_provider(settings["provider"])
|
|
1075
|
+
reasoning = _closed_reasoning(settings["reasoning"])
|
|
1076
|
+
except ValueError as exc:
|
|
1077
|
+
raise ValueError("settings contain unsafe provider/reasoning values") from exc
|
|
1078
|
+
if provider != settings["provider"] or reasoning != settings["reasoning"]:
|
|
1079
|
+
raise ValueError("provider/reasoning settings are not canonical")
|
|
1080
|
+
if (
|
|
1081
|
+
type(settings["stream"]) is not bool
|
|
1082
|
+
or settings["stream_options"]
|
|
1083
|
+
!= ({"include_usage": True} if settings["stream"] else None)
|
|
1084
|
+
or type(settings["max_completion_tokens"]) is not int
|
|
1085
|
+
or not 1 <= settings["max_completion_tokens"] <= MAX_OUTPUT_TOKENS
|
|
1086
|
+
):
|
|
1087
|
+
raise ValueError("stream or completion settings are outside the closed contract")
|
|
1088
|
+
requested_temperature = _validated_temperature_hex(
|
|
1089
|
+
request_contract["requested_temperature_hex"],
|
|
1090
|
+
label="requested_temperature_hex",
|
|
1091
|
+
)
|
|
1092
|
+
wire_temperature = _validated_temperature_hex(
|
|
1093
|
+
settings["temperature_hex"],
|
|
1094
|
+
label="temperature_hex",
|
|
1095
|
+
)
|
|
1096
|
+
if wire_temperature is not None and wire_temperature != requested_temperature:
|
|
1097
|
+
raise ValueError("wire temperature differs from requested temperature")
|
|
1098
|
+
|
|
1099
|
+
expected_top_level_fields = set(_BASE_BODY_FIELDS)
|
|
1100
|
+
if output_mode == "tool":
|
|
1101
|
+
expected_top_level_fields.update(("tools", "tool_choice"))
|
|
1102
|
+
else:
|
|
1103
|
+
expected_top_level_fields.add("response_format")
|
|
1104
|
+
if reasoning is not None:
|
|
1105
|
+
expected_top_level_fields.add("reasoning")
|
|
1106
|
+
if wire_temperature is not None:
|
|
1107
|
+
expected_top_level_fields.add("temperature")
|
|
1108
|
+
if settings["stream"]:
|
|
1109
|
+
expected_top_level_fields.add("stream_options")
|
|
1110
|
+
top_level_keys = body["top_level_keys"]
|
|
1111
|
+
if (
|
|
1112
|
+
type(top_level_keys) is not list
|
|
1113
|
+
or any(type(name) is not str for name in top_level_keys)
|
|
1114
|
+
or top_level_keys != sorted(expected_top_level_fields)
|
|
1115
|
+
):
|
|
1116
|
+
raise ValueError("top-level key projection disagrees with settings")
|
|
1117
|
+
forbidden = canonical["forbidden_fields_absent"]
|
|
1118
|
+
expected_forbidden_keys = {
|
|
1119
|
+
*_FORBIDDEN_TOP_LEVEL_FIELDS,
|
|
1120
|
+
*(f"reasoning.{name}" for name in _FORBIDDEN_REASONING_FIELDS),
|
|
1121
|
+
}
|
|
1122
|
+
if (
|
|
1123
|
+
type(forbidden) is not dict
|
|
1124
|
+
or frozenset(forbidden) != expected_forbidden_keys
|
|
1125
|
+
or any(value is not True for value in forbidden.values())
|
|
1126
|
+
):
|
|
1127
|
+
raise ValueError("forbidden-field absence projection is invalid")
|
|
1128
|
+
if canonical["hash_algorithms"] != {
|
|
1129
|
+
"manifest": OPENROUTER_OUTBOUND_REQUEST_MANIFEST_HASH_ALGORITHM,
|
|
1130
|
+
"public_projection": OPENROUTER_OUTBOUND_PUBLIC_PROJECTION_HASH_ALGORITHM,
|
|
1131
|
+
"transport_contract": (
|
|
1132
|
+
OPENROUTER_OUTBOUND_TRANSPORT_CONTRACT_HASH_ALGORITHM
|
|
1133
|
+
),
|
|
1134
|
+
"content": "sha256_exact_bytes_or_canonical_json_v1",
|
|
1135
|
+
}:
|
|
1136
|
+
raise ValueError("manifest hash algorithm projection drifted")
|
|
1137
|
+
|
|
1138
|
+
supplied_manifest = _require_sha256(
|
|
1139
|
+
canonical["outbound_request_manifest_sha256"],
|
|
1140
|
+
label="outbound_request_manifest_sha256",
|
|
1141
|
+
)
|
|
1142
|
+
without_manifest = dict(canonical)
|
|
1143
|
+
del without_manifest["outbound_request_manifest_sha256"]
|
|
1144
|
+
if supplied_manifest != _domain_sha256(_MANIFEST_DOMAIN, without_manifest):
|
|
1145
|
+
raise ValueError("outbound manifest self hash does not authenticate record")
|
|
1146
|
+
supplied_public = _require_sha256(
|
|
1147
|
+
canonical["public_projection_sha256"],
|
|
1148
|
+
label="public_projection_sha256",
|
|
1149
|
+
)
|
|
1150
|
+
public_projection = dict(without_manifest)
|
|
1151
|
+
del public_projection["public_projection_sha256"]
|
|
1152
|
+
if supplied_public != _domain_sha256(
|
|
1153
|
+
_PUBLIC_PROJECTION_DOMAIN,
|
|
1154
|
+
public_projection,
|
|
1155
|
+
):
|
|
1156
|
+
raise ValueError("public projection hash does not authenticate record")
|
|
1157
|
+
return canonical
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
__all__ = [
|
|
1161
|
+
"OPENROUTER_OUTBOUND_PUBLIC_PROJECTION_HASH_ALGORITHM",
|
|
1162
|
+
"OPENROUTER_OUTBOUND_REQUEST_BOUNDARY_ID",
|
|
1163
|
+
"OPENROUTER_OUTBOUND_REQUEST_MANIFEST_HASH_ALGORITHM",
|
|
1164
|
+
"OPENROUTER_OUTBOUND_REQUEST_MANIFEST_SCHEMA_VERSION",
|
|
1165
|
+
"OPENROUTER_OUTBOUND_TRANSPORT_CONTRACT_HASH_ALGORITHM",
|
|
1166
|
+
"OpenRouterOutboundRequestManifestError",
|
|
1167
|
+
"OpenRouterOutboundRequestManifestPublisher",
|
|
1168
|
+
"OpenRouterOutboundRequestManifestSink",
|
|
1169
|
+
"validate_openrouter_outbound_request_manifest_record",
|
|
1170
|
+
]
|