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.
Files changed (414) hide show
  1. agent_evolve/__init__.py +722 -0
  2. agent_evolve/agentic.py +2800 -0
  3. agent_evolve/api.py +767 -0
  4. agent_evolve/application/__init__.py +1580 -0
  5. agent_evolve/application/action_allocation.py +744 -0
  6. agent_evolve/application/action_allocation_frame.py +347 -0
  7. agent_evolve/application/action_allocation_frame_commit.py +185 -0
  8. agent_evolve/application/action_allocation_frame_commit_v3.py +184 -0
  9. agent_evolve/application/action_allocation_frame_v3.py +338 -0
  10. agent_evolve/application/action_archive_value.py +497 -0
  11. agent_evolve/application/action_evidence_consistency.py +455 -0
  12. agent_evolve/application/action_forecast_partitioning.py +1471 -0
  13. agent_evolve/application/action_metric_projection.py +211 -0
  14. agent_evolve/application/action_role_value.py +680 -0
  15. agent_evolve/application/action_score_authorities.py +363 -0
  16. agent_evolve/application/action_structural_signature.py +116 -0
  17. agent_evolve/application/action_target_realization.py +402 -0
  18. agent_evolve/application/agentic_evolution.py +7734 -0
  19. agent_evolve/application/agentic_portfolio_residual_expert.py +835 -0
  20. agent_evolve/application/anchor_residual_identification.py +463 -0
  21. agent_evolve/application/archive_conditioned_action_target.py +208 -0
  22. agent_evolve/application/artifact_journal.py +246 -0
  23. agent_evolve/application/artifact_replay.py +347 -0
  24. agent_evolve/application/budgeted_optimizer.py +1828 -0
  25. agent_evolve/application/calibrated_campaign.py +485 -0
  26. agent_evolve/application/calibrated_current_prefix_forecast_opportunity.py +322 -0
  27. agent_evolve/application/calibrated_positive_gain_opportunity.py +1581 -0
  28. agent_evolve/application/campaign_capacity_recourse.py +254 -0
  29. agent_evolve/application/campaign_contextual_outcomes.py +119 -0
  30. agent_evolve/application/campaign_diagnostic_blocks.py +930 -0
  31. agent_evolve/application/campaign_evidence_registry.py +262 -0
  32. agent_evolve/application/campaign_execution.py +2537 -0
  33. agent_evolve/application/campaign_generation_audit.py +942 -0
  34. agent_evolve/application/campaign_learning.py +1812 -0
  35. agent_evolve/application/campaign_learning_runtime.py +1977 -0
  36. agent_evolve/application/campaign_search_phase.py +227 -0
  37. agent_evolve/application/campaign_selector_context_extension.py +220 -0
  38. agent_evolve/application/campaign_variation_envelope.py +649 -0
  39. agent_evolve/application/campaign_variation_trace.py +451 -0
  40. agent_evolve/application/candidate_archive_consequence.py +128 -0
  41. agent_evolve/application/causal_opportunity_portfolio_gate.py +385 -0
  42. agent_evolve/application/composite_outcome_updater.py +145 -0
  43. agent_evolve/application/composition_portfolio_selection.py +363 -0
  44. agent_evolve/application/concurrent_stage.py +144 -0
  45. agent_evolve/application/contextual_action_allocation.py +181 -0
  46. agent_evolve/application/contextual_campaign_outcomes.py +267 -0
  47. agent_evolve/application/contextual_campaign_planning.py +1366 -0
  48. agent_evolve/application/contextual_delayed_credit.py +651 -0
  49. agent_evolve/application/contextual_search_controller.py +2374 -0
  50. agent_evolve/application/current_prefix_forecast_opportunity.py +714 -0
  51. agent_evolve/application/decision_metric_projection.py +112 -0
  52. agent_evolve/application/derived_action_semantics.py +129 -0
  53. agent_evolve/application/detailed_evaluation.py +449 -0
  54. agent_evolve/application/earned_lineage.py +1011 -0
  55. agent_evolve/application/effective_choice_audit.py +484 -0
  56. agent_evolve/application/empirical_consequence_calibration.py +908 -0
  57. agent_evolve/application/evaluation_accounting.py +325 -0
  58. agent_evolve/application/evaluation_cache.py +199 -0
  59. agent_evolve/application/evaluation_escrow.py +547 -0
  60. agent_evolve/application/evaluation_recourse.py +253 -0
  61. agent_evolve/application/event_recorder.py +151 -0
  62. agent_evolve/application/evolution_campaign.py +1840 -0
  63. agent_evolve/application/executable_hypothesis.py +323 -0
  64. agent_evolve/application/factorial_branch_pilot.py +772 -0
  65. agent_evolve/application/finite_acquisition_capacity_recourse.py +672 -0
  66. agent_evolve/application/finite_acquisition_residual_expert.py +373 -0
  67. agent_evolve/application/finite_acquisition_variation_envelope.py +802 -0
  68. agent_evolve/application/finite_action_hypothesis_semantics.py +446 -0
  69. agent_evolve/application/finite_action_selection.py +188 -0
  70. agent_evolve/application/finite_action_set.py +306 -0
  71. agent_evolve/application/finite_action_transition.py +537 -0
  72. agent_evolve/application/finite_variation_eligibility.py +296 -0
  73. agent_evolve/application/forecast_geometry_portfolio.py +799 -0
  74. agent_evolve/application/forecast_opportunity_shadow_calibration.py +316 -0
  75. agent_evolve/application/front_proximity_admission.py +311 -0
  76. agent_evolve/application/front_proximity_parent_basis.py +458 -0
  77. agent_evolve/application/frozen_hurdle_score.py +659 -0
  78. agent_evolve/application/g3_causal_screen.py +2257 -0
  79. agent_evolve/application/g3_causal_validation.py +1046 -0
  80. agent_evolve/application/g3_postseal_curation.py +818 -0
  81. agent_evolve/application/gated_agentic_generator.py +205 -0
  82. agent_evolve/application/generation_feedback.py +293 -0
  83. agent_evolve/application/generative_proposal_journal.py +185 -0
  84. agent_evolve/application/geometry_conditional_elasticity.py +453 -0
  85. agent_evolve/application/global_wave_action_allocation.py +1151 -0
  86. agent_evolve/application/head_mass_conditional_seat.py +268 -0
  87. agent_evolve/application/identifiable_reflection_evidence.py +1147 -0
  88. agent_evolve/application/identifiable_reflection_learning.py +395 -0
  89. agent_evolve/application/identifiable_reflection_request.py +364 -0
  90. agent_evolve/application/in_memory_residual_archive.py +341 -0
  91. agent_evolve/application/insight_memory.py +1804 -0
  92. agent_evolve/application/live_runtime_manifest.py +758 -0
  93. agent_evolve/application/llm_task_queue.py +769 -0
  94. agent_evolve/application/matched_finite_action_block.py +409 -0
  95. agent_evolve/application/materialized_action_broker.py +2328 -0
  96. agent_evolve/application/materialized_action_constraints.py +83 -0
  97. agent_evolve/application/materialized_variation.py +211 -0
  98. agent_evolve/application/multi_option_evolution.py +1536 -0
  99. agent_evolve/application/outcome_adaptive_action_racing.py +2827 -0
  100. agent_evolve/application/outcome_adaptive_residual_campaign_runtime.py +580 -0
  101. agent_evolve/application/outcome_adaptive_residual_portfolio_evolution.py +3671 -0
  102. agent_evolve/application/outcome_conditioned_portfolio_selection.py +1374 -0
  103. agent_evolve/application/outcome_relation.py +193 -0
  104. agent_evolve/application/paired_allocation_comparison.py +241 -0
  105. agent_evolve/application/paired_block_schedule.py +127 -0
  106. agent_evolve/application/parent_measurement.py +226 -0
  107. agent_evolve/application/pareto_archive.py +811 -0
  108. agent_evolve/application/portfolio_campaign_runtime.py +4739 -0
  109. agent_evolve/application/portfolio_evolution.py +2950 -0
  110. agent_evolve/application/portfolio_hypothesis_observations.py +814 -0
  111. agent_evolve/application/portfolio_memory_attribution.py +581 -0
  112. agent_evolve/application/portfolio_memory_dose.py +788 -0
  113. agent_evolve/application/portfolio_memory_matched_control.py +938 -0
  114. agent_evolve/application/portfolio_memory_transfer.py +297 -0
  115. agent_evolve/application/portfolio_optimization_memory.py +363 -0
  116. agent_evolve/application/portfolio_outcome_feedback.py +1613 -0
  117. agent_evolve/application/portfolio_projection.py +335 -0
  118. agent_evolve/application/portfolio_recombination.py +2032 -0
  119. agent_evolve/application/post_evolution_reflection.py +834 -0
  120. agent_evolve/application/postcommit_rank_authority.py +245 -0
  121. agent_evolve/application/precommitted_portfolio_racing.py +2762 -0
  122. agent_evolve/application/prequential_archive_opportunity_calibration.py +1154 -0
  123. agent_evolve/application/prequential_residual_exploration.py +343 -0
  124. agent_evolve/application/prequential_score_portfolio.py +954 -0
  125. agent_evolve/application/projections.py +292 -0
  126. agent_evolve/application/protected_action_committee.py +1027 -0
  127. agent_evolve/application/protected_branch_pilot.py +376 -0
  128. agent_evolve/application/protected_current_prefix_forecast_opportunity.py +552 -0
  129. agent_evolve/application/provider_replay.py +910 -0
  130. agent_evolve/application/rank_balanced_causal_pilot.py +1372 -0
  131. agent_evolve/application/recombination_residual_expert.py +403 -0
  132. agent_evolve/application/reflection_workflow.py +571 -0
  133. agent_evolve/application/region_conditional_credit.py +911 -0
  134. agent_evolve/application/residual_campaign_runtime.py +531 -0
  135. agent_evolve/application/residual_headroom_campaign_runtime.py +459 -0
  136. agent_evolve/application/residual_headroom_ledger.py +1544 -0
  137. agent_evolve/application/residual_learning_transaction.py +396 -0
  138. agent_evolve/application/residual_portfolio_evolution.py +1228 -0
  139. agent_evolve/application/residual_reachability.py +749 -0
  140. agent_evolve/application/residual_stage_credit.py +499 -0
  141. agent_evolve/application/same_prefix_paired_audit.py +1580 -0
  142. agent_evolve/application/semantic_coverage_score_portfolio.py +838 -0
  143. agent_evolve/application/sequential_lineage_allocation.py +1017 -0
  144. agent_evolve/application/sequential_market_replay.py +1395 -0
  145. agent_evolve/application/sequential_residual_campaign_runtime.py +305 -0
  146. agent_evolve/application/sequential_residual_portfolio_evolution.py +940 -0
  147. agent_evolve/application/single_score_action_allocation.py +299 -0
  148. agent_evolve/application/source_exposure_allocation.py +906 -0
  149. agent_evolve/application/staged_memory.py +210 -0
  150. agent_evolve/application/stratified_cold_start_allocation.py +732 -0
  151. agent_evolve/application/support_guarded_hurdle_score.py +549 -0
  152. agent_evolve/application/target_conditioned_action_forecast.py +595 -0
  153. agent_evolve/application/target_conditioned_campaign.py +566 -0
  154. agent_evolve/application/treatment_assignment.py +201 -0
  155. agent_evolve/application/trusted_objective_evidence.py +217 -0
  156. agent_evolve/application/two_stage_action_evolution.py +1131 -0
  157. agent_evolve/application/v8lite_allocation_policy.py +1083 -0
  158. agent_evolve/application/v9_candidate_policy.py +1303 -0
  159. agent_evolve/bootstrap.py +108 -0
  160. agent_evolve/campaign_presets.py +517 -0
  161. agent_evolve/campaign_profiles.py +452 -0
  162. agent_evolve/campaign_variation_topology.py +288 -0
  163. agent_evolve/campaign_workload.py +950 -0
  164. agent_evolve/cli.py +797 -0
  165. agent_evolve/contract.py +241 -0
  166. agent_evolve/core/__init__.py +91 -0
  167. agent_evolve/core/action_semantics.py +411 -0
  168. agent_evolve/core/authored.py +105 -0
  169. agent_evolve/core/formatting.py +286 -0
  170. agent_evolve/core/optimization_semantics.py +324 -0
  171. agent_evolve/core/problem.py +167 -0
  172. agent_evolve/core/results.py +323 -0
  173. agent_evolve/core/stats.py +70 -0
  174. agent_evolve/core/telemetry.py +100 -0
  175. agent_evolve/domain/__init__.py +89 -0
  176. agent_evolve/domain/artifact.py +162 -0
  177. agent_evolve/domain/durable_text.py +68 -0
  178. agent_evolve/domain/event.py +1454 -0
  179. agent_evolve/domain/finite_action_set.py +426 -0
  180. agent_evolve/domain/finite_variation.py +526 -0
  181. agent_evolve/domain/generative_emission.py +559 -0
  182. agent_evolve/domain/ids.py +163 -0
  183. agent_evolve/domain/inline_text.py +106 -0
  184. agent_evolve/domain/insight.py +27 -0
  185. agent_evolve/domain/lineage.py +737 -0
  186. agent_evolve/domain/llm_task_queue.py +960 -0
  187. agent_evolve/domain/outcome.py +96 -0
  188. agent_evolve/domain/patch.py +854 -0
  189. agent_evolve/domain/typed_json.py +542 -0
  190. agent_evolve/domain/variation_space.py +158 -0
  191. agent_evolve/driver.py +1014 -0
  192. agent_evolve/harness/__init__.py +29 -0
  193. agent_evolve/harness/base.py +242 -0
  194. agent_evolve/harness/directives.py +163 -0
  195. agent_evolve/harness/generative_seal.py +479 -0
  196. agent_evolve/harness/registry.py +41 -0
  197. agent_evolve/infrastructure/__init__.py +39 -0
  198. agent_evolve/infrastructure/artifacts/__init__.py +6 -0
  199. agent_evolve/infrastructure/artifacts/_verification.py +67 -0
  200. agent_evolve/infrastructure/artifacts/filesystem.py +343 -0
  201. agent_evolve/infrastructure/artifacts/in_memory.py +73 -0
  202. agent_evolve/infrastructure/asyncio_runtime.py +109 -0
  203. agent_evolve/infrastructure/authored_runtime.py +188 -0
  204. agent_evolve/infrastructure/authored_worker.py +171 -0
  205. agent_evolve/infrastructure/clock.py +53 -0
  206. agent_evolve/infrastructure/events/__init__.py +6 -0
  207. agent_evolve/infrastructure/events/_validation.py +89 -0
  208. agent_evolve/infrastructure/events/in_memory.py +56 -0
  209. agent_evolve/infrastructure/events/jsonl.py +193 -0
  210. agent_evolve/infrastructure/exception_provenance.py +215 -0
  211. agent_evolve/infrastructure/ids.py +118 -0
  212. agent_evolve/infrastructure/lineage_codec.py +1836 -0
  213. agent_evolve/infrastructure/outcome_adaptive_phase_journal.py +170 -0
  214. agent_evolve/infrastructure/residual_headroom_journal.py +221 -0
  215. agent_evolve/infrastructure/resource_lease.py +370 -0
  216. agent_evolve/infrastructure/sanitization/__init__.py +8 -0
  217. agent_evolve/infrastructure/sanitization/strict_json.py +484 -0
  218. agent_evolve/infrastructure/sequential_phase_journal.py +170 -0
  219. agent_evolve/infrastructure/stream_liveness.py +383 -0
  220. agent_evolve/infrastructure/subprocess_boundary.py +136 -0
  221. agent_evolve/integrations/__init__.py +1 -0
  222. agent_evolve/integrations/botorch/__init__.py +28 -0
  223. agent_evolve/integrations/botorch/finite_qlognehvi.py +190 -0
  224. agent_evolve/integrations/botorch/finite_qlognehvi_batch.py +155 -0
  225. agent_evolve/integrations/botorch/finite_qlognehvi_batch_identity.py +20 -0
  226. agent_evolve/integrations/botorch/finite_qlognehvi_batch_worker.py +55 -0
  227. agent_evolve/integrations/botorch/finite_qlognehvi_identity.py +22 -0
  228. agent_evolve/integrations/botorch/finite_qlognehvi_worker.py +55 -0
  229. agent_evolve/integrations/botorch/subprocess_qlognehvi.py +261 -0
  230. agent_evolve/integrations/botorch/subprocess_qlognehvi_batch.py +273 -0
  231. agent_evolve/integrations/completion.py +242 -0
  232. agent_evolve/integrations/pydantic_ai/__init__.py +441 -0
  233. agent_evolve/integrations/pydantic_ai/action_forecast.py +1068 -0
  234. agent_evolve/integrations/pydantic_ai/agentic_generator.py +2308 -0
  235. agent_evolve/integrations/pydantic_ai/async_generator.py +1604 -0
  236. agent_evolve/integrations/pydantic_ai/boundary_codec.py +1526 -0
  237. agent_evolve/integrations/pydantic_ai/calibrated_portfolio_campaign.py +756 -0
  238. agent_evolve/integrations/pydantic_ai/calibrated_portfolio_selection.py +7537 -0
  239. agent_evolve/integrations/pydantic_ai/campaign_acquisition.py +609 -0
  240. agent_evolve/integrations/pydantic_ai/execution_binding.py +138 -0
  241. agent_evolve/integrations/pydantic_ai/forecast_geometry_action_committee.py +217 -0
  242. agent_evolve/integrations/pydantic_ai/harness.py +159 -0
  243. agent_evolve/integrations/pydantic_ai/heterogeneous_model_execution.py +306 -0
  244. agent_evolve/integrations/pydantic_ai/hierarchical_residual_adaptive_semantic_view.py +179 -0
  245. agent_evolve/integrations/pydantic_ai/json_schema_dialect.py +108 -0
  246. agent_evolve/integrations/pydantic_ai/materialized_hierarchical_residual_expert.py +952 -0
  247. agent_evolve/integrations/pydantic_ai/materialized_portfolio_judge.py +520 -0
  248. agent_evolve/integrations/pydantic_ai/model_execution_profile.py +659 -0
  249. agent_evolve/integrations/pydantic_ai/outbound_request_manifest.py +1170 -0
  250. agent_evolve/integrations/pydantic_ai/portable_residual_consequence_features.py +575 -0
  251. agent_evolve/integrations/pydantic_ai/portfolio_selection.py +422 -0
  252. agent_evolve/integrations/pydantic_ai/progress_aware_openrouter.py +416 -0
  253. agent_evolve/integrations/pydantic_ai/provider_attempt_join.py +1523 -0
  254. agent_evolve/integrations/pydantic_ai/provider_free_calibrated_runner.py +607 -0
  255. agent_evolve/integrations/pydantic_ai/queued_runner.py +2634 -0
  256. agent_evolve/integrations/pydantic_ai/reconciled_residual_reachability.py +1417 -0
  257. agent_evolve/integrations/pydantic_ai/residual_forecast_geometry.py +445 -0
  258. agent_evolve/integrations/pydantic_ai/residual_reachability.py +674 -0
  259. agent_evolve/integrations/pydantic_ai/residual_semantic_cells.py +239 -0
  260. agent_evolve/integrations/pydantic_ai/sealed_output_replay.py +1068 -0
  261. agent_evolve/integrations/pydantic_ai/semantic_coverage_residual_portfolio.py +770 -0
  262. agent_evolve/integrations/pydantic_ai/semantic_decision_replay.py +383 -0
  263. agent_evolve/integrations/pydantic_ai/support_adaptive_residual_portfolio.py +135 -0
  264. agent_evolve/integrations/pydantic_ai/trusted_residual_prompt_context.py +143 -0
  265. agent_evolve/integrations/pydantic_ai/validated_openrouter_model.py +107 -0
  266. agent_evolve/integrations/pymoo_adapter.py +242 -0
  267. agent_evolve/policies/__init__.py +17 -0
  268. agent_evolve/policies/check.py +469 -0
  269. agent_evolve/policies/emit_scaffold.py +451 -0
  270. agent_evolve/policies/feedback/__init__.py +37 -0
  271. agent_evolve/policies/feedback/held_out_asn.py +1325 -0
  272. agent_evolve/policies/genetic.py +607 -0
  273. agent_evolve/policies/llm_backoff.py +183 -0
  274. agent_evolve/policies/llm_chooser.py +226 -0
  275. agent_evolve/policies/llm_generator.py +1760 -0
  276. agent_evolve/policies/llm_init.py +267 -0
  277. agent_evolve/policies/llm_operator.py +109 -0
  278. agent_evolve/policies/llm_prior.py +194 -0
  279. agent_evolve/policies/llm_surrogate.py +334 -0
  280. agent_evolve/policies/measurement_evidence.py +704 -0
  281. agent_evolve/policies/memory/__init__.py +223 -0
  282. agent_evolve/policies/memory/balanced_subset_blocks.py +707 -0
  283. agent_evolve/policies/memory/compatibility_matching.py +593 -0
  284. agent_evolve/policies/memory/global_falsification.py +1841 -0
  285. agent_evolve/policies/memory/prompt_shape.py +503 -0
  286. agent_evolve/policies/memory/randomized_subset.py +714 -0
  287. agent_evolve/policies/memory/staged_causal.py +1270 -0
  288. agent_evolve/policies/memory/treatment_compliance.py +759 -0
  289. agent_evolve/policies/objective_resolution/__init__.py +17 -0
  290. agent_evolve/policies/objective_resolution/fixed_grid.py +364 -0
  291. agent_evolve/policies/operator_portfolio.py +407 -0
  292. agent_evolve/policies/reguidance.py +1133 -0
  293. agent_evolve/policies/reward/__init__.py +83 -0
  294. agent_evolve/policies/reward/affine_candidate_consequence.py +156 -0
  295. agent_evolve/policies/reward/affine_candidate_consequence_3d.py +159 -0
  296. agent_evolve/policies/reward/affine_hypervolume.py +490 -0
  297. agent_evolve/policies/reward/affine_hypervolume_3d.py +567 -0
  298. agent_evolve/policies/reward/contextual_marginal_utility.py +318 -0
  299. agent_evolve/policies/reward/frozen_archive.py +360 -0
  300. agent_evolve/policies/reward/frozen_wave_archive.py +368 -0
  301. agent_evolve/policies/search_state.py +208 -0
  302. agent_evolve/policies/selection/__init__.py +345 -0
  303. agent_evolve/policies/selection/acquisition_certified_slate.py +684 -0
  304. agent_evolve/policies/selection/affine_frontier_context.py +330 -0
  305. agent_evolve/policies/selection/affine_frontier_target.py +473 -0
  306. agent_evolve/policies/selection/archive_elite.py +1346 -0
  307. agent_evolve/policies/selection/calibrated_portfolio_binding.py +640 -0
  308. agent_evolve/policies/selection/calibrated_slate.py +1394 -0
  309. agent_evolve/policies/selection/calibrated_slate_codec.py +579 -0
  310. agent_evolve/policies/selection/common_candidate_pool.py +685 -0
  311. agent_evolve/policies/selection/diagnostic_sampling.py +319 -0
  312. agent_evolve/policies/selection/disjoint_pairs.py +479 -0
  313. agent_evolve/policies/selection/elite_explorer.py +719 -0
  314. agent_evolve/policies/selection/finite_action.py +187 -0
  315. agent_evolve/policies/selection/finite_option_prompt_projection.py +377 -0
  316. agent_evolve/policies/selection/finite_palette_evidence.py +247 -0
  317. agent_evolve/policies/selection/forecast_calibration.py +922 -0
  318. agent_evolve/policies/selection/frontier_probe_slate.py +814 -0
  319. agent_evolve/policies/selection/frozen_archive_pairs.py +762 -0
  320. agent_evolve/policies/selection/full_support_slate.py +91 -0
  321. agent_evolve/policies/selection/meaningful_direction.py +240 -0
  322. agent_evolve/policies/selection/memory_dose_feasibility.py +259 -0
  323. agent_evolve/policies/selection/model_anchored_slate.py +826 -0
  324. agent_evolve/policies/selection/phenotype_recourse.py +979 -0
  325. agent_evolve/policies/selection/proposal_support.py +368 -0
  326. agent_evolve/policies/selection/random_portfolio.py +254 -0
  327. agent_evolve/policies/selection/regret_bounded_slate.py +1084 -0
  328. agent_evolve/policies/selection/residual_frontier.py +463 -0
  329. agent_evolve/policies/selection/residual_frontier_target.py +605 -0
  330. agent_evolve/policies/selection/structural_posterior_slate.py +1571 -0
  331. agent_evolve/policies/selection/target_conditioned_allocator.py +648 -0
  332. agent_evolve/policies/selection/target_conditioned_features.py +812 -0
  333. agent_evolve/policies/selection/target_conditioned_prequential.py +1527 -0
  334. agent_evolve/policies/selection/task_keyed_palette.py +906 -0
  335. agent_evolve/policies/semantics.py +147 -0
  336. agent_evolve/policies/structure.py +362 -0
  337. agent_evolve/policies/structured_output_budget.py +62 -0
  338. agent_evolve/policies/surrogate.py +696 -0
  339. agent_evolve/policies/variation/__init__.py +1 -0
  340. agent_evolve/policies/variation/compositional_finite_catalog.py +426 -0
  341. agent_evolve/policies/variation/crossover_inheritance.py +575 -0
  342. agent_evolve/policies/variation/disjoint_recombination.py +611 -0
  343. agent_evolve/policies/variation/exact_composition_capacity.py +214 -0
  344. agent_evolve/policies/variation/exact_parent_crossover.py +950 -0
  345. agent_evolve/policies/variation/multiscale_restart_catalog.py +372 -0
  346. agent_evolve/policies/variation/source_union_finite_catalog.py +403 -0
  347. agent_evolve/policies/variation/typed_patch.py +1981 -0
  348. agent_evolve/policies/weighted_prior.py +394 -0
  349. agent_evolve/ports/__init__.py +383 -0
  350. agent_evolve/ports/action_allocation.py +733 -0
  351. agent_evolve/ports/action_allocation_frame.py +1153 -0
  352. agent_evolve/ports/action_allocation_frame_commit.py +294 -0
  353. agent_evolve/ports/action_allocation_frame_commit_v3.py +432 -0
  354. agent_evolve/ports/action_allocation_frame_v3.py +995 -0
  355. agent_evolve/ports/action_forecast.py +1568 -0
  356. agent_evolve/ports/action_metric_projection.py +165 -0
  357. agent_evolve/ports/agentic_generator.py +1561 -0
  358. agent_evolve/ports/archive_context.py +136 -0
  359. agent_evolve/ports/artifact_sanitizer.py +44 -0
  360. agent_evolve/ports/artifact_store.py +225 -0
  361. agent_evolve/ports/clock.py +13 -0
  362. agent_evolve/ports/contextual_search_allocation.py +827 -0
  363. agent_evolve/ports/decision_metric_projection.py +258 -0
  364. agent_evolve/ports/event_store.py +55 -0
  365. agent_evolve/ports/executable_hypothesis.py +557 -0
  366. agent_evolve/ports/finite_acquisition.py +377 -0
  367. agent_evolve/ports/finite_acquisition_batch.py +296 -0
  368. agent_evolve/ports/finite_acquisition_batch_json.py +164 -0
  369. agent_evolve/ports/finite_acquisition_json.py +247 -0
  370. agent_evolve/ports/finite_acquisition_space.py +168 -0
  371. agent_evolve/ports/finite_action_selection.py +348 -0
  372. agent_evolve/ports/finite_action_set.py +256 -0
  373. agent_evolve/ports/frontier_target.py +396 -0
  374. agent_evolve/ports/generation_failure.py +43 -0
  375. agent_evolve/ports/hard_feasibility.py +233 -0
  376. agent_evolve/ports/id_factory.py +34 -0
  377. agent_evolve/ports/llm_task_queue.py +93 -0
  378. agent_evolve/ports/objective_resolution.py +419 -0
  379. agent_evolve/ports/paired_allocation_comparison.py +401 -0
  380. agent_evolve/ports/paired_block_schedule.py +475 -0
  381. agent_evolve/ports/parent_measurement.py +336 -0
  382. agent_evolve/ports/portfolio_memory_dose.py +643 -0
  383. agent_evolve/ports/portfolio_selection.py +3169 -0
  384. agent_evolve/ports/postcommit_rank_authority.py +467 -0
  385. agent_evolve/ports/presented_action_evidence.py +794 -0
  386. agent_evolve/ports/resource_lease.py +162 -0
  387. agent_evolve/ports/structured_generator.py +734 -0
  388. agent_evolve/ports/structured_output_budget.py +120 -0
  389. agent_evolve/ports/subprocess_boundary.py +138 -0
  390. agent_evolve/ports/treatment_assignment.py +466 -0
  391. agent_evolve/ports/variation_catalog.py +76 -0
  392. agent_evolve/ports/variation_source.py +226 -0
  393. agent_evolve/proposal_mode.py +157 -0
  394. agent_evolve/proposers/__init__.py +10 -0
  395. agent_evolve/proposers/random_proposer.py +188 -0
  396. agent_evolve/provider_accounting.py +163 -0
  397. agent_evolve/py.typed +0 -0
  398. agent_evolve/reference_method.py +1570 -0
  399. agent_evolve/session/__init__.py +11 -0
  400. agent_evolve/session/authorship.py +864 -0
  401. agent_evolve/session/evaluate.py +236 -0
  402. agent_evolve/session/fidelity.py +237 -0
  403. agent_evolve/session/genetic_loop.py +742 -0
  404. agent_evolve/session/loop.py +803 -0
  405. agent_evolve/session/screening.py +671 -0
  406. agent_evolve/settings.py +376 -0
  407. agent_evolve/workload_kit.py +368 -0
  408. agent_evolve/workload_prompt.py +398 -0
  409. agentevolve_optimizer-0.5.0.dist-info/METADATA +599 -0
  410. agentevolve_optimizer-0.5.0.dist-info/RECORD +414 -0
  411. agentevolve_optimizer-0.5.0.dist-info/WHEEL +5 -0
  412. agentevolve_optimizer-0.5.0.dist-info/entry_points.txt +2 -0
  413. agentevolve_optimizer-0.5.0.dist-info/licenses/LICENSE +21 -0
  414. 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
+ ]