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,1526 @@
|
|
|
1
|
+
"""Provider-free codec for the pinned Pydantic AI ``Model.request`` seam.
|
|
2
|
+
|
|
3
|
+
The production route is deliberately exact: Pydantic AI's OpenAI chat adapter,
|
|
4
|
+
an OpenRouter provider, and the complete OpenAI profile dataclass at the pinned
|
|
5
|
+
dependency versions. A separate FunctionModel route exists only for offline
|
|
6
|
+
tests and retains framework timestamps and IDs rather than making a generic
|
|
7
|
+
claim that they are model-irrelevant.
|
|
8
|
+
|
|
9
|
+
This module constructs no provider/client and performs no I/O.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import hashlib
|
|
15
|
+
import math
|
|
16
|
+
import types
|
|
17
|
+
from dataclasses import InitVar, dataclass, field, fields, is_dataclass
|
|
18
|
+
from datetime import datetime, timezone
|
|
19
|
+
from decimal import Decimal
|
|
20
|
+
from enum import Enum
|
|
21
|
+
from typing import (
|
|
22
|
+
Annotated,
|
|
23
|
+
Any,
|
|
24
|
+
ForwardRef,
|
|
25
|
+
Literal,
|
|
26
|
+
Union,
|
|
27
|
+
get_args,
|
|
28
|
+
get_origin,
|
|
29
|
+
is_typeddict,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
import annotated_types
|
|
33
|
+
import pydantic
|
|
34
|
+
import pydantic_ai
|
|
35
|
+
import pydantic_core
|
|
36
|
+
import pydantic.types
|
|
37
|
+
from pydantic import BaseModel, TypeAdapter
|
|
38
|
+
from pydantic._internal._fields import _general_metadata_cls
|
|
39
|
+
from pydantic_ai.messages import ModelRequest, ModelResponse, UserPromptPart
|
|
40
|
+
from pydantic_ai._output import OutputSchema
|
|
41
|
+
from pydantic_ai.models import Model, ModelRequestParameters
|
|
42
|
+
from pydantic_ai.models.function import FunctionModel
|
|
43
|
+
from pydantic_ai.models.openai import OpenAIChatModel
|
|
44
|
+
from pydantic_ai.output import OutputObjectDefinition
|
|
45
|
+
from pydantic_ai.profiles import ModelProfile
|
|
46
|
+
from pydantic_ai.profiles.openai import (
|
|
47
|
+
OpenAIJsonSchemaTransformer,
|
|
48
|
+
OpenAIModelProfile,
|
|
49
|
+
)
|
|
50
|
+
from pydantic_ai.providers.openrouter import OpenRouterModelProfile, OpenRouterProvider
|
|
51
|
+
from pydantic_ai.tools import DeferredToolRequests, DeferredToolResults, ToolDefinition
|
|
52
|
+
|
|
53
|
+
from agent_evolve.ports.artifact_store import canonical_json_bytes, decode_json_bytes
|
|
54
|
+
|
|
55
|
+
REQUEST_FORMAT = "agent-evolve.pydantic-ai-request"
|
|
56
|
+
RESPONSE_FORMAT = "agent-evolve.pydantic-ai-response"
|
|
57
|
+
OUTPUT_CONTRACT_FORMAT = "agent-evolve.pydantic-ai-output-contract"
|
|
58
|
+
BOUNDARY_FORMAT_VERSION = 2
|
|
59
|
+
OUTPUT_CONTRACT_FORMAT_VERSION = 3
|
|
60
|
+
SUPPORTED_PYDANTIC_AI_VERSION = "1.107.1"
|
|
61
|
+
SUPPORTED_PYDANTIC_VERSION = "2.13.4"
|
|
62
|
+
SUPPORTED_PYDANTIC_CORE_VERSION = "2.46.4"
|
|
63
|
+
BOUNDARY_NAME = "model.request.pre-provider-prepare"
|
|
64
|
+
|
|
65
|
+
_PRODUCTION_ROUTE = "openrouter-openai-chat-v1"
|
|
66
|
+
_FIXTURE_ROUTE = "offline-function-fixture-v1"
|
|
67
|
+
_PRODUCTION_OMISSIONS = (
|
|
68
|
+
"messages[*].timestamp",
|
|
69
|
+
"messages[*].run_id",
|
|
70
|
+
"messages[*].conversation_id",
|
|
71
|
+
"messages[*].parts[*].timestamp",
|
|
72
|
+
)
|
|
73
|
+
_FAILED = object()
|
|
74
|
+
_OUTPUT_BUILD_TOKEN = object()
|
|
75
|
+
_SKIP_CONSTRUCTION_TOKEN_CHECK = object()
|
|
76
|
+
|
|
77
|
+
_MODEL_PROFILE_FIELDS = frozenset(
|
|
78
|
+
{
|
|
79
|
+
"supports_tools",
|
|
80
|
+
"supports_tool_return_schema",
|
|
81
|
+
"supports_json_schema_output",
|
|
82
|
+
"supports_json_object_output",
|
|
83
|
+
"supports_image_output",
|
|
84
|
+
"supports_inline_system_prompts",
|
|
85
|
+
"default_structured_output_mode",
|
|
86
|
+
"prompted_output_template",
|
|
87
|
+
"native_output_requires_schema_in_instructions",
|
|
88
|
+
"json_schema_transformer",
|
|
89
|
+
"supports_thinking",
|
|
90
|
+
"thinking_always_enabled",
|
|
91
|
+
"thinking_tags",
|
|
92
|
+
"ignore_streamed_leading_whitespace",
|
|
93
|
+
"supported_native_tools",
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
_OPENAI_PROFILE_EXTRA_FIELDS = frozenset(
|
|
97
|
+
{
|
|
98
|
+
"openai_chat_thinking_field",
|
|
99
|
+
"openai_chat_send_back_thinking_parts",
|
|
100
|
+
"openai_supports_strict_tool_definition",
|
|
101
|
+
"openai_supports_sampling_settings",
|
|
102
|
+
"openai_unsupported_model_settings",
|
|
103
|
+
"openai_supports_tool_choice_required",
|
|
104
|
+
"openai_system_prompt_role",
|
|
105
|
+
"openai_chat_supports_multiple_system_messages",
|
|
106
|
+
"openai_chat_supports_web_search",
|
|
107
|
+
"openai_chat_audio_input_encoding",
|
|
108
|
+
"openai_chat_supports_file_urls",
|
|
109
|
+
"openai_supports_encrypted_reasoning_content",
|
|
110
|
+
"openai_supports_reasoning",
|
|
111
|
+
"openai_supports_reasoning_effort_none",
|
|
112
|
+
"openai_responses_requires_function_call_status_none",
|
|
113
|
+
"openai_supports_phase",
|
|
114
|
+
"openai_chat_supports_document_input",
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
_OPENROUTER_PROFILE_EXTRA_FIELDS = frozenset(
|
|
118
|
+
{
|
|
119
|
+
"openrouter_supports_cache_control",
|
|
120
|
+
"openrouter_supports_cache_ttl",
|
|
121
|
+
"openrouter_supports_tool_cache",
|
|
122
|
+
"openrouter_supports_dynamic_instruction_cache",
|
|
123
|
+
"openrouter_max_cache_points",
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
_MODEL_REQUEST_FIELDS = frozenset(
|
|
127
|
+
{"parts", "timestamp", "instructions", "kind", "run_id", "conversation_id", "metadata"}
|
|
128
|
+
)
|
|
129
|
+
_USER_PROMPT_FIELDS = frozenset({"content", "timestamp", "part_kind"})
|
|
130
|
+
_REQUEST_PARAMETER_FIELDS = frozenset(
|
|
131
|
+
{
|
|
132
|
+
"function_tools",
|
|
133
|
+
"native_tools",
|
|
134
|
+
"output_mode",
|
|
135
|
+
"output_object",
|
|
136
|
+
"output_tools",
|
|
137
|
+
"prompted_output_template",
|
|
138
|
+
"allow_text_output",
|
|
139
|
+
"allow_image_output",
|
|
140
|
+
"instruction_parts",
|
|
141
|
+
"thinking",
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
_OUTPUT_OBJECT_FIELDS = frozenset({"json_schema", "name", "description", "strict"})
|
|
145
|
+
_TOOL_DEFINITION_FIELDS = frozenset(
|
|
146
|
+
{
|
|
147
|
+
"name",
|
|
148
|
+
"parameters_json_schema",
|
|
149
|
+
"description",
|
|
150
|
+
"outer_typed_dict_key",
|
|
151
|
+
"strict",
|
|
152
|
+
"sequential",
|
|
153
|
+
"kind",
|
|
154
|
+
"metadata",
|
|
155
|
+
"timeout",
|
|
156
|
+
"defer_loading",
|
|
157
|
+
"unless_native",
|
|
158
|
+
"with_native",
|
|
159
|
+
"tool_kind",
|
|
160
|
+
"return_schema",
|
|
161
|
+
"include_return_schema",
|
|
162
|
+
"capability_id",
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
_REQUEST_KEYS = frozenset(
|
|
166
|
+
{
|
|
167
|
+
"format",
|
|
168
|
+
"format_version",
|
|
169
|
+
"boundary",
|
|
170
|
+
"semantic_route",
|
|
171
|
+
"pydantic_ai_version",
|
|
172
|
+
"pydantic_version",
|
|
173
|
+
"pydantic_core_version",
|
|
174
|
+
"model_name",
|
|
175
|
+
"model_system",
|
|
176
|
+
"model_adapter_type",
|
|
177
|
+
"model_profile_type",
|
|
178
|
+
"provider_type",
|
|
179
|
+
"model_profile",
|
|
180
|
+
"model_profile_sha256",
|
|
181
|
+
"model_default_settings",
|
|
182
|
+
"messages",
|
|
183
|
+
"model_settings",
|
|
184
|
+
"model_request_parameters",
|
|
185
|
+
"output_type_contract",
|
|
186
|
+
"output_type_contract_sha256",
|
|
187
|
+
"omitted_framework_fields",
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
_RESPONSE_KEYS = frozenset(
|
|
191
|
+
{
|
|
192
|
+
"format",
|
|
193
|
+
"format_version",
|
|
194
|
+
"pydantic_ai_version",
|
|
195
|
+
"pydantic_version",
|
|
196
|
+
"pydantic_core_version",
|
|
197
|
+
"response",
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
_OUTPUT_CONTRACT_KEYS = frozenset(
|
|
201
|
+
{
|
|
202
|
+
"format",
|
|
203
|
+
"format_version",
|
|
204
|
+
"pydantic_ai_version",
|
|
205
|
+
"pydantic_version",
|
|
206
|
+
"pydantic_core_version",
|
|
207
|
+
"output_kind",
|
|
208
|
+
"type_graph",
|
|
209
|
+
"json_schema",
|
|
210
|
+
"request_shape",
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
_RESPONSE_ADAPTER = TypeAdapter(ModelResponse)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class PydanticAIBoundaryCodecError(RuntimeError):
|
|
217
|
+
"""The value is unsupported, unsafe to decode, or not replay-equivalent."""
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _error() -> PydanticAIBoundaryCodecError:
|
|
221
|
+
# Never retain caller/provider values or lower-level exception context.
|
|
222
|
+
return PydanticAIBoundaryCodecError(
|
|
223
|
+
"Pydantic AI boundary value does not satisfy the pinned replay contract"
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _qualified_type_name(value: type[Any]) -> str:
|
|
228
|
+
module = getattr(value, "__module__", None)
|
|
229
|
+
qualname = getattr(value, "__qualname__", None)
|
|
230
|
+
if type(module) is not str or not module or type(qualname) is not str or not qualname:
|
|
231
|
+
raise _error()
|
|
232
|
+
return f"{module}.{qualname}"
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _require_supported_versions() -> tuple[str, str, str]:
|
|
236
|
+
versions = (
|
|
237
|
+
pydantic_ai.__version__,
|
|
238
|
+
pydantic.__version__,
|
|
239
|
+
pydantic_core.__version__,
|
|
240
|
+
)
|
|
241
|
+
if versions != (
|
|
242
|
+
SUPPORTED_PYDANTIC_AI_VERSION,
|
|
243
|
+
SUPPORTED_PYDANTIC_VERSION,
|
|
244
|
+
SUPPORTED_PYDANTIC_CORE_VERSION,
|
|
245
|
+
):
|
|
246
|
+
raise _error()
|
|
247
|
+
return versions
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _strict_json_clone(value: Any, *, ancestors: set[int] | None = None) -> Any:
|
|
251
|
+
"""Clone the exact JSON runtime domain without Pydantic coercion."""
|
|
252
|
+
|
|
253
|
+
value_type = type(value)
|
|
254
|
+
if value is None or value_type is bool or value_type is int:
|
|
255
|
+
return value
|
|
256
|
+
if value_type is float:
|
|
257
|
+
if not math.isfinite(value):
|
|
258
|
+
raise _error()
|
|
259
|
+
return value
|
|
260
|
+
if value_type is str:
|
|
261
|
+
value.encode("utf-8", errors="strict")
|
|
262
|
+
return value
|
|
263
|
+
if value_type is list:
|
|
264
|
+
ancestors = set() if ancestors is None else ancestors
|
|
265
|
+
identity = id(value)
|
|
266
|
+
if identity in ancestors:
|
|
267
|
+
raise _error()
|
|
268
|
+
ancestors.add(identity)
|
|
269
|
+
try:
|
|
270
|
+
return [_strict_json_clone(item, ancestors=ancestors) for item in value]
|
|
271
|
+
finally:
|
|
272
|
+
ancestors.remove(identity)
|
|
273
|
+
if value_type is dict:
|
|
274
|
+
ancestors = set() if ancestors is None else ancestors
|
|
275
|
+
identity = id(value)
|
|
276
|
+
if identity in ancestors:
|
|
277
|
+
raise _error()
|
|
278
|
+
ancestors.add(identity)
|
|
279
|
+
try:
|
|
280
|
+
result: dict[str, Any] = {}
|
|
281
|
+
for key, item in value.items():
|
|
282
|
+
if type(key) is not str:
|
|
283
|
+
raise _error()
|
|
284
|
+
key.encode("utf-8", errors="strict")
|
|
285
|
+
result[key] = _strict_json_clone(item, ancestors=ancestors)
|
|
286
|
+
return result
|
|
287
|
+
finally:
|
|
288
|
+
ancestors.remove(identity)
|
|
289
|
+
raise _error()
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _json_mapping(value: Any) -> dict[str, Any] | None:
|
|
293
|
+
if value is None:
|
|
294
|
+
return None
|
|
295
|
+
if type(value) is not dict:
|
|
296
|
+
raise _error()
|
|
297
|
+
cloned = _strict_json_clone(value)
|
|
298
|
+
if type(cloned) is not dict:
|
|
299
|
+
raise _error()
|
|
300
|
+
canonical_json_bytes(cloned)
|
|
301
|
+
return cloned
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _exact_optional_string(value: Any, *, allow_empty: bool = True) -> str | None:
|
|
305
|
+
if value is None:
|
|
306
|
+
return None
|
|
307
|
+
if type(value) is not str or (not allow_empty and not value):
|
|
308
|
+
raise _error()
|
|
309
|
+
value.encode("utf-8", errors="strict")
|
|
310
|
+
return value
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _datetime_json(value: Any) -> str | None:
|
|
314
|
+
if value is None:
|
|
315
|
+
return None
|
|
316
|
+
if type(value) is not datetime or value.tzinfo is not timezone.utc:
|
|
317
|
+
raise _error()
|
|
318
|
+
rendered = value.isoformat()
|
|
319
|
+
if rendered.endswith("+00:00"):
|
|
320
|
+
rendered = f"{rendered[:-6]}Z"
|
|
321
|
+
return rendered
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _field_names(value: Any) -> frozenset[str]:
|
|
325
|
+
return frozenset(field.name for field in fields(value))
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _common_profile_record(profile: ModelProfile) -> dict[str, Any]:
|
|
329
|
+
boolean_fields = (
|
|
330
|
+
"supports_tools",
|
|
331
|
+
"supports_tool_return_schema",
|
|
332
|
+
"supports_json_schema_output",
|
|
333
|
+
"supports_json_object_output",
|
|
334
|
+
"supports_image_output",
|
|
335
|
+
"supports_inline_system_prompts",
|
|
336
|
+
"native_output_requires_schema_in_instructions",
|
|
337
|
+
"supports_thinking",
|
|
338
|
+
"thinking_always_enabled",
|
|
339
|
+
"ignore_streamed_leading_whitespace",
|
|
340
|
+
)
|
|
341
|
+
if any(type(getattr(profile, name)) is not bool for name in boolean_fields):
|
|
342
|
+
raise _error()
|
|
343
|
+
if (
|
|
344
|
+
type(profile.default_structured_output_mode) is not str
|
|
345
|
+
or profile.default_structured_output_mode not in ("tool", "native", "prompted")
|
|
346
|
+
or type(profile.prompted_output_template) is not str
|
|
347
|
+
or type(profile.thinking_tags) is not tuple
|
|
348
|
+
or len(profile.thinking_tags) != 2
|
|
349
|
+
or any(type(item) is not str for item in profile.thinking_tags)
|
|
350
|
+
or type(profile.supported_native_tools) is not frozenset
|
|
351
|
+
or any(not isinstance(item, type) for item in profile.supported_native_tools)
|
|
352
|
+
):
|
|
353
|
+
raise _error()
|
|
354
|
+
transformer = profile.json_schema_transformer
|
|
355
|
+
if transformer is not None and not isinstance(transformer, type):
|
|
356
|
+
raise _error()
|
|
357
|
+
return {
|
|
358
|
+
"supports_tools": profile.supports_tools,
|
|
359
|
+
"supports_tool_return_schema": profile.supports_tool_return_schema,
|
|
360
|
+
"supports_json_schema_output": profile.supports_json_schema_output,
|
|
361
|
+
"supports_json_object_output": profile.supports_json_object_output,
|
|
362
|
+
"supports_image_output": profile.supports_image_output,
|
|
363
|
+
"supports_inline_system_prompts": profile.supports_inline_system_prompts,
|
|
364
|
+
"default_structured_output_mode": profile.default_structured_output_mode,
|
|
365
|
+
"prompted_output_template": profile.prompted_output_template,
|
|
366
|
+
"native_output_requires_schema_in_instructions": (
|
|
367
|
+
profile.native_output_requires_schema_in_instructions
|
|
368
|
+
),
|
|
369
|
+
"json_schema_transformer": (
|
|
370
|
+
None if transformer is None else _qualified_type_name(transformer)
|
|
371
|
+
),
|
|
372
|
+
"supports_thinking": profile.supports_thinking,
|
|
373
|
+
"thinking_always_enabled": profile.thinking_always_enabled,
|
|
374
|
+
"thinking_tags": list(profile.thinking_tags),
|
|
375
|
+
"ignore_streamed_leading_whitespace": profile.ignore_streamed_leading_whitespace,
|
|
376
|
+
"supported_native_tools": sorted(
|
|
377
|
+
_qualified_type_name(item) for item in profile.supported_native_tools
|
|
378
|
+
),
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _openai_profile_record(profile: OpenRouterModelProfile) -> dict[str, Any]:
|
|
383
|
+
if type(profile) is not OpenRouterModelProfile:
|
|
384
|
+
raise _error()
|
|
385
|
+
if _field_names(profile) != (
|
|
386
|
+
_MODEL_PROFILE_FIELDS
|
|
387
|
+
| _OPENAI_PROFILE_EXTRA_FIELDS
|
|
388
|
+
| _OPENROUTER_PROFILE_EXTRA_FIELDS
|
|
389
|
+
):
|
|
390
|
+
raise _error()
|
|
391
|
+
if profile.json_schema_transformer is not OpenAIJsonSchemaTransformer:
|
|
392
|
+
raise _error()
|
|
393
|
+
boolean_fields = (
|
|
394
|
+
"openai_supports_strict_tool_definition",
|
|
395
|
+
"openai_supports_sampling_settings",
|
|
396
|
+
"openai_supports_tool_choice_required",
|
|
397
|
+
"openai_chat_supports_web_search",
|
|
398
|
+
"openai_chat_supports_multiple_system_messages",
|
|
399
|
+
"openai_chat_supports_file_urls",
|
|
400
|
+
"openai_supports_encrypted_reasoning_content",
|
|
401
|
+
"openai_supports_reasoning",
|
|
402
|
+
"openai_supports_reasoning_effort_none",
|
|
403
|
+
"openai_responses_requires_function_call_status_none",
|
|
404
|
+
"openai_supports_phase",
|
|
405
|
+
"openai_chat_supports_document_input",
|
|
406
|
+
)
|
|
407
|
+
if any(type(getattr(profile, name)) is not bool for name in boolean_fields):
|
|
408
|
+
raise _error()
|
|
409
|
+
send_back = profile.openai_chat_send_back_thinking_parts
|
|
410
|
+
if not (
|
|
411
|
+
(type(send_back) is str and send_back in ("auto", "tags", "field"))
|
|
412
|
+
or (type(send_back) is bool and send_back is False)
|
|
413
|
+
):
|
|
414
|
+
raise _error()
|
|
415
|
+
if (
|
|
416
|
+
profile.openai_chat_thinking_field is not None
|
|
417
|
+
and type(profile.openai_chat_thinking_field) is not str
|
|
418
|
+
):
|
|
419
|
+
raise _error()
|
|
420
|
+
if (
|
|
421
|
+
type(profile.openai_unsupported_model_settings) is not tuple
|
|
422
|
+
or any(type(item) is not str for item in profile.openai_unsupported_model_settings)
|
|
423
|
+
or (
|
|
424
|
+
profile.openai_system_prompt_role is not None
|
|
425
|
+
and (
|
|
426
|
+
type(profile.openai_system_prompt_role) is not str
|
|
427
|
+
or profile.openai_system_prompt_role not in ("system", "developer", "user")
|
|
428
|
+
)
|
|
429
|
+
)
|
|
430
|
+
or type(profile.openai_chat_audio_input_encoding) is not str
|
|
431
|
+
or profile.openai_chat_audio_input_encoding not in ("base64", "uri")
|
|
432
|
+
):
|
|
433
|
+
raise _error()
|
|
434
|
+
result = _common_profile_record(profile)
|
|
435
|
+
result.update(
|
|
436
|
+
{
|
|
437
|
+
"openai_chat_thinking_field": profile.openai_chat_thinking_field,
|
|
438
|
+
"openai_chat_send_back_thinking_parts": send_back,
|
|
439
|
+
"openai_supports_strict_tool_definition": (
|
|
440
|
+
profile.openai_supports_strict_tool_definition
|
|
441
|
+
),
|
|
442
|
+
"openai_supports_sampling_settings": profile.openai_supports_sampling_settings,
|
|
443
|
+
"openai_unsupported_model_settings": list(
|
|
444
|
+
profile.openai_unsupported_model_settings
|
|
445
|
+
),
|
|
446
|
+
"openai_supports_tool_choice_required": profile.openai_supports_tool_choice_required,
|
|
447
|
+
"openai_system_prompt_role": profile.openai_system_prompt_role,
|
|
448
|
+
"openai_chat_supports_multiple_system_messages": (
|
|
449
|
+
profile.openai_chat_supports_multiple_system_messages
|
|
450
|
+
),
|
|
451
|
+
"openai_chat_supports_web_search": profile.openai_chat_supports_web_search,
|
|
452
|
+
"openai_chat_audio_input_encoding": profile.openai_chat_audio_input_encoding,
|
|
453
|
+
"openai_chat_supports_file_urls": profile.openai_chat_supports_file_urls,
|
|
454
|
+
"openai_supports_encrypted_reasoning_content": (
|
|
455
|
+
profile.openai_supports_encrypted_reasoning_content
|
|
456
|
+
),
|
|
457
|
+
"openai_supports_reasoning": profile.openai_supports_reasoning,
|
|
458
|
+
"openai_supports_reasoning_effort_none": profile.openai_supports_reasoning_effort_none,
|
|
459
|
+
"openai_responses_requires_function_call_status_none": (
|
|
460
|
+
profile.openai_responses_requires_function_call_status_none
|
|
461
|
+
),
|
|
462
|
+
"openai_supports_phase": profile.openai_supports_phase,
|
|
463
|
+
"openai_chat_supports_document_input": profile.openai_chat_supports_document_input,
|
|
464
|
+
"openrouter_supports_cache_control": (
|
|
465
|
+
profile.openrouter_supports_cache_control
|
|
466
|
+
),
|
|
467
|
+
"openrouter_supports_cache_ttl": profile.openrouter_supports_cache_ttl,
|
|
468
|
+
"openrouter_supports_tool_cache": profile.openrouter_supports_tool_cache,
|
|
469
|
+
"openrouter_supports_dynamic_instruction_cache": (
|
|
470
|
+
profile.openrouter_supports_dynamic_instruction_cache
|
|
471
|
+
),
|
|
472
|
+
"openrouter_max_cache_points": profile.openrouter_max_cache_points,
|
|
473
|
+
}
|
|
474
|
+
)
|
|
475
|
+
openrouter_boolean_fields = (
|
|
476
|
+
"openrouter_supports_cache_control",
|
|
477
|
+
"openrouter_supports_cache_ttl",
|
|
478
|
+
"openrouter_supports_tool_cache",
|
|
479
|
+
"openrouter_supports_dynamic_instruction_cache",
|
|
480
|
+
)
|
|
481
|
+
if any(
|
|
482
|
+
type(getattr(profile, name)) is not bool
|
|
483
|
+
for name in openrouter_boolean_fields
|
|
484
|
+
):
|
|
485
|
+
raise _error()
|
|
486
|
+
if profile.openrouter_max_cache_points is not None and (
|
|
487
|
+
type(profile.openrouter_max_cache_points) is not int
|
|
488
|
+
or profile.openrouter_max_cache_points <= 0
|
|
489
|
+
):
|
|
490
|
+
raise _error()
|
|
491
|
+
return result
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def _fixture_profile_record(profile: ModelProfile) -> dict[str, Any]:
|
|
495
|
+
if type(profile) is not ModelProfile or _field_names(profile) != _MODEL_PROFILE_FIELDS:
|
|
496
|
+
raise _error()
|
|
497
|
+
# A custom transformer could inspect omitted/model-specific state. The
|
|
498
|
+
# fixture route is intentionally inert and closed to that behavior.
|
|
499
|
+
if profile.json_schema_transformer is not None:
|
|
500
|
+
raise _error()
|
|
501
|
+
return _common_profile_record(profile)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
@dataclass(frozen=True, slots=True)
|
|
505
|
+
class _ModelIdentity:
|
|
506
|
+
route: str
|
|
507
|
+
model_name: str
|
|
508
|
+
model_system: str
|
|
509
|
+
model_adapter_type: str
|
|
510
|
+
model_profile_type: str
|
|
511
|
+
provider_type: str | None
|
|
512
|
+
profile_record: dict[str, Any]
|
|
513
|
+
omissions: tuple[str, ...]
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def _model_identity(model: Model) -> _ModelIdentity:
|
|
517
|
+
if type(model) is OpenAIChatModel:
|
|
518
|
+
provider = model.provider
|
|
519
|
+
profile = model.profile
|
|
520
|
+
if (
|
|
521
|
+
type(provider) is not OpenRouterProvider
|
|
522
|
+
or type(profile) is not OpenRouterModelProfile
|
|
523
|
+
or type(model.model_name) is not str
|
|
524
|
+
or not model.model_name
|
|
525
|
+
or type(model.system) is not str
|
|
526
|
+
or model.system != "openrouter"
|
|
527
|
+
):
|
|
528
|
+
raise _error()
|
|
529
|
+
return _ModelIdentity(
|
|
530
|
+
route=_PRODUCTION_ROUTE,
|
|
531
|
+
model_name=model.model_name,
|
|
532
|
+
model_system=model.system,
|
|
533
|
+
model_adapter_type=_qualified_type_name(type(model)),
|
|
534
|
+
model_profile_type=_qualified_type_name(type(profile)),
|
|
535
|
+
provider_type=_qualified_type_name(type(provider)),
|
|
536
|
+
profile_record=_openai_profile_record(profile),
|
|
537
|
+
omissions=_PRODUCTION_OMISSIONS,
|
|
538
|
+
)
|
|
539
|
+
if type(model) is FunctionModel:
|
|
540
|
+
profile = model.profile
|
|
541
|
+
if (
|
|
542
|
+
model.provider is not None
|
|
543
|
+
or type(profile) is not ModelProfile
|
|
544
|
+
or type(model.model_name) is not str
|
|
545
|
+
or not model.model_name
|
|
546
|
+
or type(model.system) is not str
|
|
547
|
+
or model.system != "function"
|
|
548
|
+
):
|
|
549
|
+
raise _error()
|
|
550
|
+
return _ModelIdentity(
|
|
551
|
+
route=_FIXTURE_ROUTE,
|
|
552
|
+
model_name=model.model_name,
|
|
553
|
+
model_system=model.system,
|
|
554
|
+
model_adapter_type=_qualified_type_name(type(model)),
|
|
555
|
+
model_profile_type=_qualified_type_name(type(profile)),
|
|
556
|
+
provider_type=None,
|
|
557
|
+
profile_record=_fixture_profile_record(profile),
|
|
558
|
+
omissions=(),
|
|
559
|
+
)
|
|
560
|
+
raise _error()
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def model_profile_sha256(model: Model) -> str:
|
|
564
|
+
"""Hash the complete closed profile for one supported semantic model route."""
|
|
565
|
+
|
|
566
|
+
attempted = _attempt_model_profile_sha256(model)
|
|
567
|
+
if type(attempted) is not str:
|
|
568
|
+
raise _error() from None
|
|
569
|
+
return attempted
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def _attempt_model_profile_sha256(model: Model) -> str | object:
|
|
573
|
+
try:
|
|
574
|
+
_require_supported_versions()
|
|
575
|
+
identity = _model_identity(model)
|
|
576
|
+
return hashlib.sha256(canonical_json_bytes(identity.profile_record)).hexdigest()
|
|
577
|
+
except Exception:
|
|
578
|
+
return _FAILED
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
_TYPE_GRAPH_MAX_DEPTH = 64
|
|
582
|
+
_TYPE_GRAPH_MAX_NODES = 2048
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def _declared_dataclass_types(module: Any) -> tuple[type[Any], ...]:
|
|
586
|
+
"""Capture exact, package-owned metadata classes at module import time."""
|
|
587
|
+
|
|
588
|
+
result: list[type[Any]] = []
|
|
589
|
+
for name in dir(module):
|
|
590
|
+
value = getattr(module, name)
|
|
591
|
+
if (
|
|
592
|
+
isinstance(value, type)
|
|
593
|
+
and value.__module__ == module.__name__
|
|
594
|
+
and is_dataclass(value)
|
|
595
|
+
and not any(value is existing for existing in result)
|
|
596
|
+
):
|
|
597
|
+
result.append(value)
|
|
598
|
+
return tuple(result)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def _identity_unique_types(*groups: tuple[type[Any], ...]) -> tuple[type[Any], ...]:
|
|
602
|
+
result: list[type[Any]] = []
|
|
603
|
+
for group in groups:
|
|
604
|
+
for value in group:
|
|
605
|
+
if not any(value is existing for existing in result):
|
|
606
|
+
result.append(value)
|
|
607
|
+
return tuple(result)
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
_TRUSTED_METADATA_TYPES = _identity_unique_types(
|
|
611
|
+
_declared_dataclass_types(annotated_types),
|
|
612
|
+
_declared_dataclass_types(pydantic.types),
|
|
613
|
+
)
|
|
614
|
+
_PYDANTIC_GENERAL_METADATA_TYPE = _general_metadata_cls()
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
@dataclass(slots=True)
|
|
618
|
+
class _TypeGraphState:
|
|
619
|
+
active_model_ids: set[int]
|
|
620
|
+
active_annotations: set[int]
|
|
621
|
+
nodes: int = 0
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def _is_deferred_marker(value: Any) -> bool:
|
|
625
|
+
marker_types = (DeferredToolRequests, DeferredToolResults)
|
|
626
|
+
if isinstance(value, type):
|
|
627
|
+
try:
|
|
628
|
+
return issubclass(value, marker_types)
|
|
629
|
+
except TypeError:
|
|
630
|
+
return False
|
|
631
|
+
return isinstance(value, marker_types)
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
def _reject_nested_deferred(
|
|
635
|
+
value: Any,
|
|
636
|
+
*,
|
|
637
|
+
ancestors: set[int] | None = None,
|
|
638
|
+
depth: int = 0,
|
|
639
|
+
) -> None:
|
|
640
|
+
"""Reject deferred classes/instances inside exact built-in containers."""
|
|
641
|
+
|
|
642
|
+
if depth > _TYPE_GRAPH_MAX_DEPTH or _is_deferred_marker(value):
|
|
643
|
+
raise _error()
|
|
644
|
+
value_type = type(value)
|
|
645
|
+
if not any(
|
|
646
|
+
value_type is container_type
|
|
647
|
+
for container_type in (list, tuple, dict, set, frozenset)
|
|
648
|
+
):
|
|
649
|
+
return
|
|
650
|
+
ancestors = set() if ancestors is None else ancestors
|
|
651
|
+
identity = id(value)
|
|
652
|
+
if identity in ancestors:
|
|
653
|
+
raise _error()
|
|
654
|
+
ancestors.add(identity)
|
|
655
|
+
try:
|
|
656
|
+
items = value.items() if value_type is dict else enumerate(value)
|
|
657
|
+
for key, item in items:
|
|
658
|
+
if value_type is dict:
|
|
659
|
+
_reject_nested_deferred(key, ancestors=ancestors, depth=depth + 1)
|
|
660
|
+
_reject_nested_deferred(item, ancestors=ancestors, depth=depth + 1)
|
|
661
|
+
finally:
|
|
662
|
+
ancestors.remove(identity)
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
def _metadata_record(value: Any) -> dict[str, Any]:
|
|
666
|
+
"""Serialize only reviewed constraint metadata; reject opaque semantics."""
|
|
667
|
+
|
|
668
|
+
_reject_nested_deferred(value)
|
|
669
|
+
value_type = type(value)
|
|
670
|
+
if (
|
|
671
|
+
any(value_type is trusted for trusted in _TRUSTED_METADATA_TYPES)
|
|
672
|
+
and is_dataclass(value)
|
|
673
|
+
and not isinstance(value, type)
|
|
674
|
+
):
|
|
675
|
+
attributes: dict[str, Any] = {}
|
|
676
|
+
for item in fields(value):
|
|
677
|
+
attribute = getattr(value, item.name)
|
|
678
|
+
_reject_nested_deferred(attribute)
|
|
679
|
+
attributes[item.name] = _strict_json_clone(attribute)
|
|
680
|
+
return {
|
|
681
|
+
"type": _qualified_type_name(value_type),
|
|
682
|
+
"attributes": attributes,
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
# Pydantic creates this pinned private metadata carrier for constraints such
|
|
686
|
+
# as regex patterns. It is not a dataclass, but its exact JSON-valued state
|
|
687
|
+
# is closed and the Pydantic version is part of the contract.
|
|
688
|
+
if (
|
|
689
|
+
value_type is _PYDANTIC_GENERAL_METADATA_TYPE
|
|
690
|
+
and type(getattr(value, "__dict__", None)) is dict
|
|
691
|
+
):
|
|
692
|
+
attributes = _strict_json_clone(value.__dict__)
|
|
693
|
+
return {
|
|
694
|
+
"type": _qualified_type_name(value_type),
|
|
695
|
+
"attributes": attributes,
|
|
696
|
+
}
|
|
697
|
+
raise _error()
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
def _type_graph(
|
|
701
|
+
annotation: Any,
|
|
702
|
+
*,
|
|
703
|
+
state: _TypeGraphState | None = None,
|
|
704
|
+
depth: int = 0,
|
|
705
|
+
) -> dict[str, Any]:
|
|
706
|
+
state = state or _TypeGraphState(active_model_ids=set(), active_annotations=set())
|
|
707
|
+
state.nodes += 1
|
|
708
|
+
if depth > _TYPE_GRAPH_MAX_DEPTH or state.nodes > _TYPE_GRAPH_MAX_NODES:
|
|
709
|
+
raise _error()
|
|
710
|
+
if _is_deferred_marker(annotation):
|
|
711
|
+
raise _error()
|
|
712
|
+
if annotation is Any or isinstance(annotation, ForwardRef):
|
|
713
|
+
# Opaque values and unresolved forward references are outside M1e.
|
|
714
|
+
raise _error()
|
|
715
|
+
if annotation is None or annotation is type(None):
|
|
716
|
+
return {"kind": "type", "name": "builtins.NoneType"}
|
|
717
|
+
if any(annotation is scalar for scalar in (str, int, float, bool, Decimal)):
|
|
718
|
+
return {"kind": "type", "name": _qualified_type_name(annotation)}
|
|
719
|
+
|
|
720
|
+
if isinstance(annotation, type) and issubclass(annotation, BaseModel):
|
|
721
|
+
annotation_id = id(annotation)
|
|
722
|
+
if annotation_id in state.active_model_ids:
|
|
723
|
+
return {"kind": "model-ref", "name": _qualified_type_name(annotation)}
|
|
724
|
+
state.active_model_ids.add(annotation_id)
|
|
725
|
+
try:
|
|
726
|
+
if type(annotation.model_fields) is not dict:
|
|
727
|
+
raise _error()
|
|
728
|
+
model_fields: list[dict[str, Any]] = []
|
|
729
|
+
for name in sorted(annotation.model_fields):
|
|
730
|
+
if type(name) is not str:
|
|
731
|
+
raise _error()
|
|
732
|
+
field = annotation.model_fields[name]
|
|
733
|
+
metadata = getattr(field, "metadata", None)
|
|
734
|
+
if type(metadata) is not list:
|
|
735
|
+
raise _error()
|
|
736
|
+
default_factory = getattr(field, "default_factory", None)
|
|
737
|
+
if default_factory is list or default_factory is dict:
|
|
738
|
+
initialization = {
|
|
739
|
+
"kind": "factory",
|
|
740
|
+
"name": _qualified_type_name(default_factory),
|
|
741
|
+
}
|
|
742
|
+
elif default_factory is not None:
|
|
743
|
+
# Arbitrary factory code is behavioral state that neither
|
|
744
|
+
# the JSON schema nor a qualified name can bind safely.
|
|
745
|
+
raise _error()
|
|
746
|
+
elif field.is_required():
|
|
747
|
+
initialization = {"kind": "required"}
|
|
748
|
+
else:
|
|
749
|
+
default = getattr(field, "default", None)
|
|
750
|
+
_reject_nested_deferred(default)
|
|
751
|
+
initialization = {
|
|
752
|
+
"kind": "value",
|
|
753
|
+
"value": _strict_json_clone(default),
|
|
754
|
+
}
|
|
755
|
+
for auxiliary in (
|
|
756
|
+
getattr(field, "json_schema_extra", None),
|
|
757
|
+
getattr(field, "examples", None),
|
|
758
|
+
):
|
|
759
|
+
_reject_nested_deferred(auxiliary)
|
|
760
|
+
if auxiliary is not None:
|
|
761
|
+
_strict_json_clone(auxiliary)
|
|
762
|
+
model_fields.append(
|
|
763
|
+
{
|
|
764
|
+
"name": name,
|
|
765
|
+
"annotation": _type_graph(
|
|
766
|
+
field.annotation,
|
|
767
|
+
state=state,
|
|
768
|
+
depth=depth + 1,
|
|
769
|
+
),
|
|
770
|
+
"initialization": initialization,
|
|
771
|
+
"metadata": [_metadata_record(item) for item in metadata],
|
|
772
|
+
}
|
|
773
|
+
)
|
|
774
|
+
return {
|
|
775
|
+
"kind": "pydantic-model",
|
|
776
|
+
"name": _qualified_type_name(annotation),
|
|
777
|
+
"fields": model_fields,
|
|
778
|
+
}
|
|
779
|
+
finally:
|
|
780
|
+
state.active_model_ids.remove(annotation_id)
|
|
781
|
+
|
|
782
|
+
if is_typeddict(annotation):
|
|
783
|
+
raise _error()
|
|
784
|
+
if isinstance(annotation, type) and is_dataclass(annotation):
|
|
785
|
+
raise _error()
|
|
786
|
+
if isinstance(annotation, type) and issubclass(annotation, Enum):
|
|
787
|
+
members: list[dict[str, Any]] = []
|
|
788
|
+
for member in annotation:
|
|
789
|
+
_reject_nested_deferred(member.value)
|
|
790
|
+
members.append(
|
|
791
|
+
{"name": member.name, "value": _strict_json_clone(member.value)}
|
|
792
|
+
)
|
|
793
|
+
return {
|
|
794
|
+
"kind": "enum",
|
|
795
|
+
"name": _qualified_type_name(annotation),
|
|
796
|
+
"members": members,
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
origin = get_origin(annotation)
|
|
800
|
+
args = get_args(annotation)
|
|
801
|
+
if origin is None:
|
|
802
|
+
# Arbitrary classes and opaque typing constructs are not represented by
|
|
803
|
+
# a qualified name alone.
|
|
804
|
+
raise _error()
|
|
805
|
+
annotation_id = id(annotation)
|
|
806
|
+
if annotation_id in state.active_annotations:
|
|
807
|
+
raise _error()
|
|
808
|
+
state.active_annotations.add(annotation_id)
|
|
809
|
+
try:
|
|
810
|
+
if origin is Literal:
|
|
811
|
+
if not args:
|
|
812
|
+
raise _error()
|
|
813
|
+
values: list[Any] = []
|
|
814
|
+
for item in args:
|
|
815
|
+
_reject_nested_deferred(item)
|
|
816
|
+
values.append(_strict_json_clone(item))
|
|
817
|
+
return {"kind": "literal", "values": values}
|
|
818
|
+
if origin is Union or origin is types.UnionType:
|
|
819
|
+
if len(args) < 2:
|
|
820
|
+
raise _error()
|
|
821
|
+
return {
|
|
822
|
+
"kind": "union",
|
|
823
|
+
"args": [
|
|
824
|
+
_type_graph(item, state=state, depth=depth + 1) for item in args
|
|
825
|
+
],
|
|
826
|
+
}
|
|
827
|
+
if origin is Annotated:
|
|
828
|
+
if len(args) < 2:
|
|
829
|
+
raise _error()
|
|
830
|
+
base, *metadata = args
|
|
831
|
+
return {
|
|
832
|
+
"kind": "annotated",
|
|
833
|
+
"base": _type_graph(base, state=state, depth=depth + 1),
|
|
834
|
+
"metadata": [_metadata_record(item) for item in metadata],
|
|
835
|
+
}
|
|
836
|
+
if origin is list:
|
|
837
|
+
if len(args) != 1:
|
|
838
|
+
raise _error()
|
|
839
|
+
return {
|
|
840
|
+
"kind": "list",
|
|
841
|
+
"item": _type_graph(args[0], state=state, depth=depth + 1),
|
|
842
|
+
}
|
|
843
|
+
if origin is dict:
|
|
844
|
+
if len(args) != 2 or args[0] is not str:
|
|
845
|
+
raise _error()
|
|
846
|
+
return {
|
|
847
|
+
"kind": "dict",
|
|
848
|
+
"key": {"kind": "type", "name": "builtins.str"},
|
|
849
|
+
"value": _type_graph(args[1], state=state, depth=depth + 1),
|
|
850
|
+
}
|
|
851
|
+
if origin is tuple:
|
|
852
|
+
# Variable tuples carry Ellipsis as an argument. M1e deliberately
|
|
853
|
+
# rejects them rather than reaching Ellipsis incidentally.
|
|
854
|
+
if not args or any(item is Ellipsis for item in args):
|
|
855
|
+
raise _error()
|
|
856
|
+
return {
|
|
857
|
+
"kind": "tuple",
|
|
858
|
+
"items": [
|
|
859
|
+
_type_graph(item, state=state, depth=depth + 1) for item in args
|
|
860
|
+
],
|
|
861
|
+
}
|
|
862
|
+
raise _error()
|
|
863
|
+
finally:
|
|
864
|
+
state.active_annotations.remove(annotation_id)
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
@dataclass(frozen=True, slots=True)
|
|
868
|
+
class _OutputContractPayload:
|
|
869
|
+
canonical_bytes: bytes
|
|
870
|
+
sha256_hex: str
|
|
871
|
+
output_kind: str
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
875
|
+
class BoundOutputContract:
|
|
876
|
+
"""Canonical provider-request contract bound to one exact runtime output type.
|
|
877
|
+
|
|
878
|
+
The serialized bytes deliberately do not claim to identify user validator or
|
|
879
|
+
hook code across processes. Every consumer rebuilds them from ``output_type``;
|
|
880
|
+
replay additionally supplies its independently expected runtime type.
|
|
881
|
+
"""
|
|
882
|
+
|
|
883
|
+
output_type: Any = field(repr=False)
|
|
884
|
+
canonical_bytes: bytes
|
|
885
|
+
sha256_hex: str
|
|
886
|
+
output_kind: str
|
|
887
|
+
_construction_token: InitVar[object] = None
|
|
888
|
+
|
|
889
|
+
def __post_init__(self, _construction_token: object) -> None:
|
|
890
|
+
attempted = _attempt_validate_bound_output_contract(
|
|
891
|
+
self,
|
|
892
|
+
construction_token=_construction_token,
|
|
893
|
+
)
|
|
894
|
+
if attempted is not None:
|
|
895
|
+
# Raise only after the validation frame has discarded every
|
|
896
|
+
# lower-level exception, including its message and traceback.
|
|
897
|
+
raise _error() from None
|
|
898
|
+
|
|
899
|
+
def __eq__(self, other: object) -> bool:
|
|
900
|
+
return (
|
|
901
|
+
type(other) is BoundOutputContract
|
|
902
|
+
and self.output_type is other.output_type
|
|
903
|
+
and self.canonical_bytes == other.canonical_bytes
|
|
904
|
+
and self.sha256_hex == other.sha256_hex
|
|
905
|
+
and self.output_kind == other.output_kind
|
|
906
|
+
)
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
def _attempt_validate_bound_output_contract(
|
|
910
|
+
contract: BoundOutputContract,
|
|
911
|
+
*,
|
|
912
|
+
construction_token: object = _SKIP_CONSTRUCTION_TOKEN_CHECK,
|
|
913
|
+
) -> None | object:
|
|
914
|
+
try:
|
|
915
|
+
if (
|
|
916
|
+
type(contract) is not BoundOutputContract
|
|
917
|
+
or (
|
|
918
|
+
construction_token is not _SKIP_CONSTRUCTION_TOKEN_CHECK
|
|
919
|
+
and construction_token is not _OUTPUT_BUILD_TOKEN
|
|
920
|
+
)
|
|
921
|
+
or type(contract.canonical_bytes) is not bytes
|
|
922
|
+
or type(contract.sha256_hex) is not str
|
|
923
|
+
or type(contract.output_kind) is not str
|
|
924
|
+
):
|
|
925
|
+
raise _error()
|
|
926
|
+
expected = _attempt_build_output_contract_payload(contract.output_type)
|
|
927
|
+
if (
|
|
928
|
+
type(expected) is not _OutputContractPayload
|
|
929
|
+
or contract.canonical_bytes != expected.canonical_bytes
|
|
930
|
+
or contract.sha256_hex != expected.sha256_hex
|
|
931
|
+
or contract.output_kind != expected.output_kind
|
|
932
|
+
):
|
|
933
|
+
raise _error()
|
|
934
|
+
return None
|
|
935
|
+
except Exception:
|
|
936
|
+
return _FAILED
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
def build_output_type_contract(output_type: Any) -> BoundOutputContract:
|
|
940
|
+
"""Build and bind the provider-free contract for one exact runtime type."""
|
|
941
|
+
|
|
942
|
+
attempted = _attempt_build_output_contract_payload(output_type)
|
|
943
|
+
if type(attempted) is not _OutputContractPayload:
|
|
944
|
+
raise _error() from None
|
|
945
|
+
return BoundOutputContract(
|
|
946
|
+
output_type=output_type,
|
|
947
|
+
canonical_bytes=attempted.canonical_bytes,
|
|
948
|
+
sha256_hex=attempted.sha256_hex,
|
|
949
|
+
output_kind=attempted.output_kind,
|
|
950
|
+
_construction_token=_OUTPUT_BUILD_TOKEN,
|
|
951
|
+
)
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
def _attempt_build_output_contract_payload(
|
|
955
|
+
output_type: Any,
|
|
956
|
+
) -> _OutputContractPayload | object:
|
|
957
|
+
try:
|
|
958
|
+
pydantic_ai_version, pydantic_version, core_version = _require_supported_versions()
|
|
959
|
+
origin = get_origin(output_type)
|
|
960
|
+
output_args = get_args(output_type)
|
|
961
|
+
if output_type is str:
|
|
962
|
+
output_kind = "text"
|
|
963
|
+
elif origin is list and len(output_args) == 1 and output_args[0] is str:
|
|
964
|
+
output_kind = "structured"
|
|
965
|
+
elif isinstance(output_type, type) and issubclass(output_type, BaseModel):
|
|
966
|
+
output_kind = "structured"
|
|
967
|
+
else:
|
|
968
|
+
# This rejects list/union output specs and every marker workflow,
|
|
969
|
+
# including DeferredToolRequests, before Agent construction.
|
|
970
|
+
raise _error()
|
|
971
|
+
graph = _type_graph(output_type)
|
|
972
|
+
schema = _strict_json_clone(TypeAdapter(output_type).json_schema())
|
|
973
|
+
if type(schema) is not dict:
|
|
974
|
+
raise _error()
|
|
975
|
+
output_schema = OutputSchema.build(output_type)
|
|
976
|
+
if output_kind == "text":
|
|
977
|
+
if (
|
|
978
|
+
output_schema.mode != "text"
|
|
979
|
+
or output_schema.object_def is not None
|
|
980
|
+
or output_schema.toolset is not None
|
|
981
|
+
or output_schema.allows_text is not True
|
|
982
|
+
or output_schema.allows_image is not False
|
|
983
|
+
or output_schema.allows_deferred_tools is not False
|
|
984
|
+
):
|
|
985
|
+
raise _error()
|
|
986
|
+
request_shape = {
|
|
987
|
+
"output_mode": "text",
|
|
988
|
+
"output_object": None,
|
|
989
|
+
"output_tools": [],
|
|
990
|
+
"allow_text_output": True,
|
|
991
|
+
}
|
|
992
|
+
else:
|
|
993
|
+
toolset = output_schema.toolset
|
|
994
|
+
tool_defs = getattr(toolset, "_tool_defs", None)
|
|
995
|
+
if (
|
|
996
|
+
output_schema.mode != "auto"
|
|
997
|
+
or type(output_schema.object_def) is not OutputObjectDefinition
|
|
998
|
+
or toolset is None
|
|
999
|
+
or type(tool_defs) is not list
|
|
1000
|
+
or len(tool_defs) != 1
|
|
1001
|
+
or output_schema.allows_text is not True
|
|
1002
|
+
or output_schema.allows_image is not False
|
|
1003
|
+
or output_schema.allows_deferred_tools is not False
|
|
1004
|
+
):
|
|
1005
|
+
raise _error()
|
|
1006
|
+
request_shape = {
|
|
1007
|
+
"output_mode": "auto",
|
|
1008
|
+
"output_object": _project_output_object(output_schema.object_def),
|
|
1009
|
+
"output_tools": [_project_output_tool(tool_defs[0])],
|
|
1010
|
+
"allow_text_output": True,
|
|
1011
|
+
}
|
|
1012
|
+
value = {
|
|
1013
|
+
"format": OUTPUT_CONTRACT_FORMAT,
|
|
1014
|
+
"format_version": OUTPUT_CONTRACT_FORMAT_VERSION,
|
|
1015
|
+
"pydantic_ai_version": pydantic_ai_version,
|
|
1016
|
+
"pydantic_version": pydantic_version,
|
|
1017
|
+
"pydantic_core_version": core_version,
|
|
1018
|
+
"output_kind": output_kind,
|
|
1019
|
+
"type_graph": graph,
|
|
1020
|
+
"json_schema": schema,
|
|
1021
|
+
"request_shape": request_shape,
|
|
1022
|
+
}
|
|
1023
|
+
content = canonical_json_bytes(value)
|
|
1024
|
+
return _OutputContractPayload(
|
|
1025
|
+
canonical_bytes=content,
|
|
1026
|
+
sha256_hex=hashlib.sha256(content).hexdigest(),
|
|
1027
|
+
output_kind=output_kind,
|
|
1028
|
+
)
|
|
1029
|
+
except Exception:
|
|
1030
|
+
return _FAILED
|
|
1031
|
+
|
|
1032
|
+
|
|
1033
|
+
def validate_output_type_contract(
|
|
1034
|
+
contract: BoundOutputContract,
|
|
1035
|
+
*,
|
|
1036
|
+
compiled_json_schema: dict[str, Any] | None = None,
|
|
1037
|
+
) -> None:
|
|
1038
|
+
"""Rebuild and compare a contract, optionally binding Agent's compiled schema."""
|
|
1039
|
+
|
|
1040
|
+
attempted = _attempt_validate_output_type_contract(
|
|
1041
|
+
contract,
|
|
1042
|
+
compiled_json_schema=compiled_json_schema,
|
|
1043
|
+
)
|
|
1044
|
+
if attempted is not None:
|
|
1045
|
+
raise _error() from None
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
def _attempt_validate_output_type_contract(
|
|
1049
|
+
contract: BoundOutputContract,
|
|
1050
|
+
*,
|
|
1051
|
+
compiled_json_schema: dict[str, Any] | None = None,
|
|
1052
|
+
) -> None | object:
|
|
1053
|
+
try:
|
|
1054
|
+
if _attempt_validate_bound_output_contract(contract) is not None:
|
|
1055
|
+
raise _error()
|
|
1056
|
+
if compiled_json_schema is not None:
|
|
1057
|
+
compiled = _strict_json_clone(compiled_json_schema)
|
|
1058
|
+
value = decode_json_bytes(contract.canonical_bytes)
|
|
1059
|
+
if canonical_json_bytes(compiled) != canonical_json_bytes(
|
|
1060
|
+
value["json_schema"]
|
|
1061
|
+
):
|
|
1062
|
+
raise _error()
|
|
1063
|
+
return None
|
|
1064
|
+
except Exception:
|
|
1065
|
+
return _FAILED
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
def bind_recorded_output_type_contract(
|
|
1069
|
+
expected_output_type: Any,
|
|
1070
|
+
*,
|
|
1071
|
+
canonical_bytes: bytes,
|
|
1072
|
+
sha256_hex: str,
|
|
1073
|
+
) -> BoundOutputContract:
|
|
1074
|
+
"""Bind recorded bytes only after rebuilding them from the replay type."""
|
|
1075
|
+
|
|
1076
|
+
attempted = _attempt_bind_recorded_output_type_contract(
|
|
1077
|
+
expected_output_type,
|
|
1078
|
+
canonical_bytes=canonical_bytes,
|
|
1079
|
+
sha256_hex=sha256_hex,
|
|
1080
|
+
)
|
|
1081
|
+
if type(attempted) is not BoundOutputContract:
|
|
1082
|
+
raise _error() from None
|
|
1083
|
+
return attempted
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
def _attempt_bind_recorded_output_type_contract(
|
|
1087
|
+
expected_output_type: Any,
|
|
1088
|
+
*,
|
|
1089
|
+
canonical_bytes: bytes,
|
|
1090
|
+
sha256_hex: str,
|
|
1091
|
+
) -> BoundOutputContract | object:
|
|
1092
|
+
try:
|
|
1093
|
+
if type(canonical_bytes) is not bytes or type(sha256_hex) is not str:
|
|
1094
|
+
raise _error()
|
|
1095
|
+
expected = build_output_type_contract(expected_output_type)
|
|
1096
|
+
if (
|
|
1097
|
+
canonical_bytes != expected.canonical_bytes
|
|
1098
|
+
or sha256_hex != expected.sha256_hex
|
|
1099
|
+
or hashlib.sha256(canonical_bytes).hexdigest() != sha256_hex
|
|
1100
|
+
):
|
|
1101
|
+
raise _error()
|
|
1102
|
+
return expected
|
|
1103
|
+
except Exception:
|
|
1104
|
+
return _FAILED
|
|
1105
|
+
|
|
1106
|
+
|
|
1107
|
+
def _project_messages(
|
|
1108
|
+
messages: list[Any], *, route: str
|
|
1109
|
+
) -> list[dict[str, Any]]:
|
|
1110
|
+
if type(messages) is not list or len(messages) != 1:
|
|
1111
|
+
raise _error()
|
|
1112
|
+
message = messages[0]
|
|
1113
|
+
if (
|
|
1114
|
+
type(message) is not ModelRequest
|
|
1115
|
+
or _field_names(message) != _MODEL_REQUEST_FIELDS
|
|
1116
|
+
or type(message.parts) is not list
|
|
1117
|
+
or len(message.parts) != 1
|
|
1118
|
+
or type(message.kind) is not str
|
|
1119
|
+
or message.kind != "request"
|
|
1120
|
+
):
|
|
1121
|
+
raise _error()
|
|
1122
|
+
part = message.parts[0]
|
|
1123
|
+
if (
|
|
1124
|
+
type(part) is not UserPromptPart
|
|
1125
|
+
or _field_names(part) != _USER_PROMPT_FIELDS
|
|
1126
|
+
or type(part.part_kind) is not str
|
|
1127
|
+
or part.part_kind != "user-prompt"
|
|
1128
|
+
or type(part.content) is not str
|
|
1129
|
+
):
|
|
1130
|
+
raise _error()
|
|
1131
|
+
part.content.encode("utf-8", errors="strict")
|
|
1132
|
+
instructions = _exact_optional_string(message.instructions)
|
|
1133
|
+
metadata = _json_mapping(message.metadata)
|
|
1134
|
+
timestamp = _datetime_json(message.timestamp)
|
|
1135
|
+
part_timestamp = _datetime_json(part.timestamp)
|
|
1136
|
+
run_id = _exact_optional_string(message.run_id, allow_empty=False)
|
|
1137
|
+
conversation_id = _exact_optional_string(message.conversation_id, allow_empty=False)
|
|
1138
|
+
projected_message: dict[str, Any] = {
|
|
1139
|
+
"kind": "request",
|
|
1140
|
+
"parts": [{"part_kind": "user-prompt", "content": part.content}],
|
|
1141
|
+
"instructions": instructions,
|
|
1142
|
+
"metadata": metadata,
|
|
1143
|
+
}
|
|
1144
|
+
if route == _FIXTURE_ROUTE:
|
|
1145
|
+
projected_message.update(
|
|
1146
|
+
{
|
|
1147
|
+
"timestamp": timestamp,
|
|
1148
|
+
"run_id": run_id,
|
|
1149
|
+
"conversation_id": conversation_id,
|
|
1150
|
+
}
|
|
1151
|
+
)
|
|
1152
|
+
projected_message["parts"][0]["timestamp"] = part_timestamp
|
|
1153
|
+
elif route != _PRODUCTION_ROUTE:
|
|
1154
|
+
raise _error()
|
|
1155
|
+
return [projected_message]
|
|
1156
|
+
|
|
1157
|
+
|
|
1158
|
+
def _project_output_object(value: Any) -> dict[str, Any]:
|
|
1159
|
+
if type(value) is not OutputObjectDefinition or _field_names(value) != _OUTPUT_OBJECT_FIELDS:
|
|
1160
|
+
raise _error()
|
|
1161
|
+
name = _exact_optional_string(value.name, allow_empty=False)
|
|
1162
|
+
description = _exact_optional_string(value.description)
|
|
1163
|
+
if value.strict is not None and type(value.strict) is not bool:
|
|
1164
|
+
raise _error()
|
|
1165
|
+
schema = _json_mapping(value.json_schema)
|
|
1166
|
+
if schema is None:
|
|
1167
|
+
raise _error()
|
|
1168
|
+
return {
|
|
1169
|
+
"json_schema": schema,
|
|
1170
|
+
"name": name,
|
|
1171
|
+
"description": description,
|
|
1172
|
+
"strict": value.strict,
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
|
|
1176
|
+
def _project_output_tool(value: Any) -> dict[str, Any]:
|
|
1177
|
+
if type(value) is not ToolDefinition or _field_names(value) != _TOOL_DEFINITION_FIELDS:
|
|
1178
|
+
raise _error()
|
|
1179
|
+
if (
|
|
1180
|
+
type(value.name) is not str
|
|
1181
|
+
or value.name != "final_result"
|
|
1182
|
+
or type(value.description) is not str
|
|
1183
|
+
or value.description != "The final response which ends this conversation"
|
|
1184
|
+
or (value.outer_typed_dict_key is not None and type(value.outer_typed_dict_key) is not str)
|
|
1185
|
+
or (value.strict is not None and type(value.strict) is not bool)
|
|
1186
|
+
or type(value.sequential) is not bool
|
|
1187
|
+
or value.sequential is not False
|
|
1188
|
+
or type(value.kind) is not str
|
|
1189
|
+
or value.kind != "output"
|
|
1190
|
+
or value.metadata is not None
|
|
1191
|
+
or value.timeout is not None
|
|
1192
|
+
or type(value.defer_loading) is not bool
|
|
1193
|
+
or value.defer_loading is not False
|
|
1194
|
+
or value.unless_native is not None
|
|
1195
|
+
or value.with_native is not None
|
|
1196
|
+
or value.tool_kind is not None
|
|
1197
|
+
or value.return_schema is not None
|
|
1198
|
+
or value.include_return_schema is not None
|
|
1199
|
+
or value.capability_id is not None
|
|
1200
|
+
):
|
|
1201
|
+
raise _error()
|
|
1202
|
+
schema = _json_mapping(value.parameters_json_schema)
|
|
1203
|
+
if schema is None:
|
|
1204
|
+
raise _error()
|
|
1205
|
+
return {
|
|
1206
|
+
"name": value.name,
|
|
1207
|
+
"parameters_json_schema": schema,
|
|
1208
|
+
"description": value.description,
|
|
1209
|
+
"outer_typed_dict_key": value.outer_typed_dict_key,
|
|
1210
|
+
"strict": value.strict,
|
|
1211
|
+
"sequential": value.sequential,
|
|
1212
|
+
"kind": value.kind,
|
|
1213
|
+
"metadata": None,
|
|
1214
|
+
"timeout": None,
|
|
1215
|
+
"defer_loading": value.defer_loading,
|
|
1216
|
+
"unless_native": None,
|
|
1217
|
+
"with_native": None,
|
|
1218
|
+
"tool_kind": None,
|
|
1219
|
+
"return_schema": None,
|
|
1220
|
+
"include_return_schema": None,
|
|
1221
|
+
"capability_id": None,
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
|
|
1225
|
+
def _project_parameters(
|
|
1226
|
+
parameters: ModelRequestParameters,
|
|
1227
|
+
*,
|
|
1228
|
+
output_contract: BoundOutputContract,
|
|
1229
|
+
) -> dict[str, Any]:
|
|
1230
|
+
if _attempt_validate_bound_output_contract(output_contract) is _FAILED:
|
|
1231
|
+
raise _error()
|
|
1232
|
+
if (
|
|
1233
|
+
type(parameters) is not ModelRequestParameters
|
|
1234
|
+
or _field_names(parameters) != _REQUEST_PARAMETER_FIELDS
|
|
1235
|
+
or type(parameters.function_tools) is not list
|
|
1236
|
+
or len(parameters.function_tools) != 0
|
|
1237
|
+
or type(parameters.native_tools) is not list
|
|
1238
|
+
or len(parameters.native_tools) != 0
|
|
1239
|
+
or type(parameters.output_tools) is not list
|
|
1240
|
+
or type(parameters.allow_text_output) is not bool
|
|
1241
|
+
or type(parameters.allow_image_output) is not bool
|
|
1242
|
+
or parameters.allow_image_output is not False
|
|
1243
|
+
or parameters.prompted_output_template is not None
|
|
1244
|
+
or parameters.instruction_parts is not None
|
|
1245
|
+
or parameters.thinking is not None
|
|
1246
|
+
or type(parameters.output_mode) is not str
|
|
1247
|
+
):
|
|
1248
|
+
raise _error()
|
|
1249
|
+
|
|
1250
|
+
if output_contract.output_kind == "text":
|
|
1251
|
+
if (
|
|
1252
|
+
parameters.output_mode != "text"
|
|
1253
|
+
or parameters.allow_text_output is not True
|
|
1254
|
+
or parameters.output_object is not None
|
|
1255
|
+
or len(parameters.output_tools) != 0
|
|
1256
|
+
):
|
|
1257
|
+
raise _error()
|
|
1258
|
+
output_object = None
|
|
1259
|
+
output_tools: list[dict[str, Any]] = []
|
|
1260
|
+
elif output_contract.output_kind == "structured":
|
|
1261
|
+
if (
|
|
1262
|
+
parameters.output_mode != "auto"
|
|
1263
|
+
or parameters.allow_text_output is not True
|
|
1264
|
+
or type(parameters.output_object) is not OutputObjectDefinition
|
|
1265
|
+
or len(parameters.output_tools) != 1
|
|
1266
|
+
):
|
|
1267
|
+
raise _error()
|
|
1268
|
+
output_object = _project_output_object(parameters.output_object)
|
|
1269
|
+
output_tools = [_project_output_tool(parameters.output_tools[0])]
|
|
1270
|
+
else: # pragma: no cover - BoundOutputContract closes this.
|
|
1271
|
+
raise _error()
|
|
1272
|
+
|
|
1273
|
+
projected = {
|
|
1274
|
+
"function_tools": [],
|
|
1275
|
+
"native_tools": [],
|
|
1276
|
+
"output_mode": parameters.output_mode,
|
|
1277
|
+
"output_object": output_object,
|
|
1278
|
+
"output_tools": output_tools,
|
|
1279
|
+
"prompted_output_template": None,
|
|
1280
|
+
"allow_text_output": parameters.allow_text_output,
|
|
1281
|
+
"allow_image_output": False,
|
|
1282
|
+
"instruction_parts": None,
|
|
1283
|
+
"thinking": None,
|
|
1284
|
+
}
|
|
1285
|
+
contract_record = decode_json_bytes(output_contract.canonical_bytes)
|
|
1286
|
+
observed_shape = {
|
|
1287
|
+
"output_mode": projected["output_mode"],
|
|
1288
|
+
"output_object": projected["output_object"],
|
|
1289
|
+
"output_tools": projected["output_tools"],
|
|
1290
|
+
"allow_text_output": projected["allow_text_output"],
|
|
1291
|
+
}
|
|
1292
|
+
# Canonical comparison is type-sensitive, unlike Python equality where
|
|
1293
|
+
# True == 1 == 1.0 and hostile equality implementations can participate.
|
|
1294
|
+
if canonical_json_bytes(observed_shape) != canonical_json_bytes(
|
|
1295
|
+
contract_record["request_shape"]
|
|
1296
|
+
):
|
|
1297
|
+
raise _error()
|
|
1298
|
+
return projected
|
|
1299
|
+
|
|
1300
|
+
|
|
1301
|
+
def project_model_request(
|
|
1302
|
+
model: Model,
|
|
1303
|
+
messages: list[Any],
|
|
1304
|
+
model_settings: dict[str, Any] | None,
|
|
1305
|
+
model_request_parameters: ModelRequestParameters,
|
|
1306
|
+
*,
|
|
1307
|
+
output_contract: BoundOutputContract,
|
|
1308
|
+
) -> dict[str, Any]:
|
|
1309
|
+
"""Return the detached canonical projection for one supported request."""
|
|
1310
|
+
|
|
1311
|
+
attempted = _attempt_project_model_request(
|
|
1312
|
+
model,
|
|
1313
|
+
messages,
|
|
1314
|
+
model_settings,
|
|
1315
|
+
model_request_parameters,
|
|
1316
|
+
output_contract=output_contract,
|
|
1317
|
+
)
|
|
1318
|
+
if type(attempted) is not dict:
|
|
1319
|
+
raise _error() from None
|
|
1320
|
+
return attempted
|
|
1321
|
+
|
|
1322
|
+
|
|
1323
|
+
def _attempt_project_model_request(
|
|
1324
|
+
model: Model,
|
|
1325
|
+
messages: list[Any],
|
|
1326
|
+
model_settings: dict[str, Any] | None,
|
|
1327
|
+
model_request_parameters: ModelRequestParameters,
|
|
1328
|
+
*,
|
|
1329
|
+
output_contract: BoundOutputContract,
|
|
1330
|
+
) -> dict[str, Any] | object:
|
|
1331
|
+
try:
|
|
1332
|
+
pydantic_ai_version, pydantic_version, core_version = _require_supported_versions()
|
|
1333
|
+
if (
|
|
1334
|
+
not isinstance(model, Model)
|
|
1335
|
+
or _attempt_validate_bound_output_contract(output_contract) is _FAILED
|
|
1336
|
+
):
|
|
1337
|
+
raise _error()
|
|
1338
|
+
identity = _model_identity(model)
|
|
1339
|
+
profile_record = _strict_json_clone(identity.profile_record)
|
|
1340
|
+
contract_record = decode_json_bytes(output_contract.canonical_bytes)
|
|
1341
|
+
value = {
|
|
1342
|
+
"format": REQUEST_FORMAT,
|
|
1343
|
+
"format_version": BOUNDARY_FORMAT_VERSION,
|
|
1344
|
+
"boundary": BOUNDARY_NAME,
|
|
1345
|
+
"semantic_route": identity.route,
|
|
1346
|
+
"pydantic_ai_version": pydantic_ai_version,
|
|
1347
|
+
"pydantic_version": pydantic_version,
|
|
1348
|
+
"pydantic_core_version": core_version,
|
|
1349
|
+
"model_name": identity.model_name,
|
|
1350
|
+
"model_system": identity.model_system,
|
|
1351
|
+
"model_adapter_type": identity.model_adapter_type,
|
|
1352
|
+
"model_profile_type": identity.model_profile_type,
|
|
1353
|
+
"provider_type": identity.provider_type,
|
|
1354
|
+
"model_profile": profile_record,
|
|
1355
|
+
"model_profile_sha256": hashlib.sha256(
|
|
1356
|
+
canonical_json_bytes(profile_record)
|
|
1357
|
+
).hexdigest(),
|
|
1358
|
+
"model_default_settings": _json_mapping(model.settings),
|
|
1359
|
+
"messages": _project_messages(messages, route=identity.route),
|
|
1360
|
+
"model_settings": _json_mapping(model_settings),
|
|
1361
|
+
"model_request_parameters": _project_parameters(
|
|
1362
|
+
model_request_parameters,
|
|
1363
|
+
output_contract=output_contract,
|
|
1364
|
+
),
|
|
1365
|
+
"output_type_contract": contract_record,
|
|
1366
|
+
"output_type_contract_sha256": output_contract.sha256_hex,
|
|
1367
|
+
"omitted_framework_fields": list(identity.omissions),
|
|
1368
|
+
}
|
|
1369
|
+
if frozenset(value) != _REQUEST_KEYS:
|
|
1370
|
+
raise _error()
|
|
1371
|
+
detached = decode_json_bytes(canonical_json_bytes(value))
|
|
1372
|
+
if type(detached) is not dict or frozenset(detached) != _REQUEST_KEYS:
|
|
1373
|
+
raise _error()
|
|
1374
|
+
return detached
|
|
1375
|
+
except Exception:
|
|
1376
|
+
return _FAILED
|
|
1377
|
+
|
|
1378
|
+
|
|
1379
|
+
def encode_model_request(
|
|
1380
|
+
model: Model,
|
|
1381
|
+
messages: list[Any],
|
|
1382
|
+
model_settings: dict[str, Any] | None,
|
|
1383
|
+
model_request_parameters: ModelRequestParameters,
|
|
1384
|
+
*,
|
|
1385
|
+
output_contract: BoundOutputContract,
|
|
1386
|
+
) -> bytes:
|
|
1387
|
+
"""Encode one supported request to strict canonical JSON bytes."""
|
|
1388
|
+
|
|
1389
|
+
attempted = _attempt_encode_model_request(
|
|
1390
|
+
model,
|
|
1391
|
+
messages,
|
|
1392
|
+
model_settings,
|
|
1393
|
+
model_request_parameters,
|
|
1394
|
+
output_contract=output_contract,
|
|
1395
|
+
)
|
|
1396
|
+
if type(attempted) is not bytes:
|
|
1397
|
+
raise _error() from None
|
|
1398
|
+
return attempted
|
|
1399
|
+
|
|
1400
|
+
|
|
1401
|
+
def _attempt_encode_model_request(
|
|
1402
|
+
model: Model,
|
|
1403
|
+
messages: list[Any],
|
|
1404
|
+
model_settings: dict[str, Any] | None,
|
|
1405
|
+
model_request_parameters: ModelRequestParameters,
|
|
1406
|
+
*,
|
|
1407
|
+
output_contract: BoundOutputContract,
|
|
1408
|
+
) -> bytes | object:
|
|
1409
|
+
try:
|
|
1410
|
+
if _attempt_validate_bound_output_contract(output_contract) is _FAILED:
|
|
1411
|
+
raise _error()
|
|
1412
|
+
return canonical_json_bytes(
|
|
1413
|
+
project_model_request(
|
|
1414
|
+
model,
|
|
1415
|
+
messages,
|
|
1416
|
+
model_settings,
|
|
1417
|
+
model_request_parameters,
|
|
1418
|
+
output_contract=output_contract,
|
|
1419
|
+
)
|
|
1420
|
+
)
|
|
1421
|
+
except Exception:
|
|
1422
|
+
return _FAILED
|
|
1423
|
+
|
|
1424
|
+
|
|
1425
|
+
def encode_model_response(response: ModelResponse) -> bytes:
|
|
1426
|
+
"""Encode a typed response envelope for exact offline reconstruction."""
|
|
1427
|
+
|
|
1428
|
+
attempted = _attempt_encode_model_response(response)
|
|
1429
|
+
if type(attempted) is not bytes:
|
|
1430
|
+
raise _error() from None
|
|
1431
|
+
return attempted
|
|
1432
|
+
|
|
1433
|
+
|
|
1434
|
+
def _attempt_encode_model_response(response: ModelResponse) -> bytes | object:
|
|
1435
|
+
try:
|
|
1436
|
+
if type(response) is not ModelResponse:
|
|
1437
|
+
raise _error()
|
|
1438
|
+
pydantic_ai_version, pydantic_version, core_version = _require_supported_versions()
|
|
1439
|
+
value = {
|
|
1440
|
+
"format": RESPONSE_FORMAT,
|
|
1441
|
+
"format_version": BOUNDARY_FORMAT_VERSION,
|
|
1442
|
+
"pydantic_ai_version": pydantic_ai_version,
|
|
1443
|
+
"pydantic_version": pydantic_version,
|
|
1444
|
+
"pydantic_core_version": core_version,
|
|
1445
|
+
"response": _RESPONSE_ADAPTER.dump_python(
|
|
1446
|
+
response, mode="json", warnings="error"
|
|
1447
|
+
),
|
|
1448
|
+
}
|
|
1449
|
+
content = canonical_json_bytes(value)
|
|
1450
|
+
if _attempt_decode_model_response(content) is _FAILED:
|
|
1451
|
+
raise _error()
|
|
1452
|
+
return content
|
|
1453
|
+
except Exception:
|
|
1454
|
+
return _FAILED
|
|
1455
|
+
|
|
1456
|
+
|
|
1457
|
+
def decode_model_response(content: bytes) -> ModelResponse:
|
|
1458
|
+
"""Decode canonical response bytes, rejecting all coercion/version drift."""
|
|
1459
|
+
|
|
1460
|
+
attempted = _attempt_decode_model_response(content)
|
|
1461
|
+
if type(attempted) is not ModelResponse:
|
|
1462
|
+
raise _error() from None
|
|
1463
|
+
return attempted
|
|
1464
|
+
|
|
1465
|
+
|
|
1466
|
+
def _attempt_decode_model_response(content: bytes) -> ModelResponse | object:
|
|
1467
|
+
try:
|
|
1468
|
+
if type(content) is not bytes:
|
|
1469
|
+
raise _error()
|
|
1470
|
+
value = decode_json_bytes(content)
|
|
1471
|
+
versions = _require_supported_versions()
|
|
1472
|
+
if (
|
|
1473
|
+
type(value) is not dict
|
|
1474
|
+
or frozenset(value) != _RESPONSE_KEYS
|
|
1475
|
+
or canonical_json_bytes(value) != content
|
|
1476
|
+
or type(value["format"]) is not str
|
|
1477
|
+
or value["format"] != RESPONSE_FORMAT
|
|
1478
|
+
or type(value["format_version"]) is not int
|
|
1479
|
+
or value["format_version"] != BOUNDARY_FORMAT_VERSION
|
|
1480
|
+
or any(
|
|
1481
|
+
type(value[key]) is not str
|
|
1482
|
+
for key in ("pydantic_ai_version", "pydantic_version", "pydantic_core_version")
|
|
1483
|
+
)
|
|
1484
|
+
or tuple(
|
|
1485
|
+
value[key]
|
|
1486
|
+
for key in ("pydantic_ai_version", "pydantic_version", "pydantic_core_version")
|
|
1487
|
+
)
|
|
1488
|
+
!= versions
|
|
1489
|
+
or type(value["response"]) is not dict
|
|
1490
|
+
):
|
|
1491
|
+
raise _error()
|
|
1492
|
+
response = _RESPONSE_ADAPTER.validate_python(value["response"])
|
|
1493
|
+
normalized = dict(value)
|
|
1494
|
+
normalized["response"] = _RESPONSE_ADAPTER.dump_python(
|
|
1495
|
+
response, mode="json", warnings="error"
|
|
1496
|
+
)
|
|
1497
|
+
# Canonical byte comparison is type-sensitive (unlike Python equality,
|
|
1498
|
+
# where True == 1 == 1.0) and rejects aliases/extras/coercion.
|
|
1499
|
+
if canonical_json_bytes(normalized) != content:
|
|
1500
|
+
raise _error()
|
|
1501
|
+
return response
|
|
1502
|
+
except Exception:
|
|
1503
|
+
return _FAILED
|
|
1504
|
+
|
|
1505
|
+
|
|
1506
|
+
__all__ = [
|
|
1507
|
+
"BOUNDARY_FORMAT_VERSION",
|
|
1508
|
+
"BOUNDARY_NAME",
|
|
1509
|
+
"BoundOutputContract",
|
|
1510
|
+
"OUTPUT_CONTRACT_FORMAT",
|
|
1511
|
+
"OUTPUT_CONTRACT_FORMAT_VERSION",
|
|
1512
|
+
"PydanticAIBoundaryCodecError",
|
|
1513
|
+
"REQUEST_FORMAT",
|
|
1514
|
+
"RESPONSE_FORMAT",
|
|
1515
|
+
"SUPPORTED_PYDANTIC_AI_VERSION",
|
|
1516
|
+
"SUPPORTED_PYDANTIC_CORE_VERSION",
|
|
1517
|
+
"SUPPORTED_PYDANTIC_VERSION",
|
|
1518
|
+
"bind_recorded_output_type_contract",
|
|
1519
|
+
"build_output_type_contract",
|
|
1520
|
+
"decode_model_response",
|
|
1521
|
+
"encode_model_request",
|
|
1522
|
+
"encode_model_response",
|
|
1523
|
+
"model_profile_sha256",
|
|
1524
|
+
"project_model_request",
|
|
1525
|
+
"validate_output_type_contract",
|
|
1526
|
+
]
|