selectools 1.0.0__tar.gz → 1.1.0__tar.gz

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 (323) hide show
  1. {selectools-1.0.0/src/selectools.egg-info → selectools-1.1.0}/PKG-INFO +3 -1
  2. {selectools-1.0.0 → selectools-1.1.0}/README.md +2 -0
  3. {selectools-1.0.0 → selectools-1.1.0}/pyproject.toml +4 -3
  4. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/__init__.py +2 -1
  5. selectools-1.1.0/src/selectools/_time.py +42 -0
  6. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/agent/_lifecycle.py +1 -1
  7. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/agent/_memory_manager.py +0 -1
  8. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/agent/_provider_caller.py +76 -26
  9. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/agent/_tool_executor.py +2 -2
  10. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/agent/config.py +9 -1
  11. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/agent/config_groups.py +29 -5
  12. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/agent/core.py +316 -105
  13. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/audit.py +0 -1
  14. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/cache.py +4 -0
  15. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/cache_semantic.py +1 -1
  16. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/checkpoint_postgres.py +3 -5
  17. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/compose.py +1 -2
  18. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/embeddings/anthropic.py +0 -10
  19. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/embeddings/cohere.py +0 -10
  20. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/embeddings/gemini.py +0 -10
  21. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/embeddings/openai.py +0 -10
  22. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/embeddings/provider.py +6 -2
  23. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/evals/history.py +1 -1
  24. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/evals/pairwise.py +1 -5
  25. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/evals/serve.py +1 -5
  26. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/evals/templates.py +1 -2
  27. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/guardrails/pipeline.py +68 -2
  28. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/knowledge.py +7 -7
  29. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/knowledge_store_redis.py +3 -2
  30. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/knowledge_store_supabase.py +3 -2
  31. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/mcp/bridge.py +1 -1
  32. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/mcp/client.py +1 -2
  33. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/mcp/config.py +1 -1
  34. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/mcp/multi.py +1 -1
  35. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/mcp/server.py +1 -2
  36. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/observe/langfuse.py +1 -1
  37. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/observe/otel.py +1 -1
  38. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/observe/trace_store.py +4 -6
  39. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/observer.py +1 -1
  40. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/orchestration/checkpoint.py +4 -3
  41. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/orchestration/graph.py +2 -5
  42. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/orchestration/node.py +1 -3
  43. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/orchestration/state.py +3 -3
  44. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/orchestration/supervisor.py +3 -7
  45. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/patterns/debate.py +2 -3
  46. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/patterns/plan_and_execute.py +2 -3
  47. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/patterns/reflective.py +2 -3
  48. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/patterns/team_lead.py +2 -3
  49. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/providers/_openai_compat.py +46 -9
  50. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/providers/anthropic_provider.py +131 -109
  51. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/providers/azure_openai_provider.py +1 -1
  52. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/providers/fallback.py +1 -3
  53. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/providers/gemini_provider.py +103 -127
  54. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/providers/ollama_provider.py +1 -2
  55. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/providers/openai_provider.py +2 -0
  56. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/rag/bm25.py +0 -6
  57. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/rag/hybrid.py +0 -16
  58. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/rag/loaders.py +1 -1
  59. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/rag/stores/faiss.py +2 -2
  60. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/rag/stores/memory.py +0 -1
  61. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/rag/stores/pinecone.py +4 -1
  62. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/rag/stores/qdrant.py +1 -3
  63. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/scheduler.py +1 -1
  64. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/serve/app.py +1 -3
  65. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/serve/cli.py +1 -1
  66. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/serve/models.py +0 -25
  67. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/sessions.py +39 -110
  68. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/structured.py +19 -1
  69. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/datetime_tools.py +0 -1
  70. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/tools/base.py +0 -1
  71. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/trace.py +1 -1
  72. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/unified_memory.py +2 -3
  73. {selectools-1.0.0 → selectools-1.1.0/src/selectools.egg-info}/PKG-INFO +3 -1
  74. {selectools-1.0.0 → selectools-1.1.0}/src/selectools.egg-info/SOURCES.txt +2 -0
  75. {selectools-1.0.0 → selectools-1.1.0}/tests/test_bug_hunt_batch1_core.py +0 -3
  76. {selectools-1.0.0 → selectools-1.1.0}/tests/test_bug_hunt_batch1_security.py +0 -3
  77. {selectools-1.0.0 → selectools-1.1.0}/tests/test_bug_hunt_batch1_tools.py +1 -5
  78. {selectools-1.0.0 → selectools-1.1.0}/tests/test_bug_hunt_regression.py +1 -10
  79. {selectools-1.0.0 → selectools-1.1.0}/tests/test_checkpoint_postgres.py +3 -4
  80. {selectools-1.0.0 → selectools-1.1.0}/tests/test_coherence.py +1 -2
  81. {selectools-1.0.0 → selectools-1.1.0}/tests/test_concurrency_smoke.py +1 -4
  82. {selectools-1.0.0 → selectools-1.1.0}/tests/test_consolidation_regression.py +2 -2
  83. {selectools-1.0.0 → selectools-1.1.0}/tests/test_coverage_orchestration.py +7 -14
  84. {selectools-1.0.0 → selectools-1.1.0}/tests/test_document_loaders_extended.py +0 -3
  85. {selectools-1.0.0 → selectools-1.1.0}/tests/test_e2e_multimodal.py +0 -1
  86. {selectools-1.0.0 → selectools-1.1.0}/tests/test_entity_memory.py +1 -3
  87. {selectools-1.0.0 → selectools-1.1.0}/tests/test_evals.py +0 -4
  88. {selectools-1.0.0 → selectools-1.1.0}/tests/test_evals_advanced.py +2 -4
  89. {selectools-1.0.0 → selectools-1.1.0}/tests/test_evals_cli.py +5 -7
  90. {selectools-1.0.0 → selectools-1.1.0}/tests/test_evals_e2e.py +3 -11
  91. {selectools-1.0.0 → selectools-1.1.0}/tests/test_evals_final.py +1 -1
  92. {selectools-1.0.0 → selectools-1.1.0}/tests/test_evals_hardening.py +0 -8
  93. {selectools-1.0.0 → selectools-1.1.0}/tests/test_evals_html_report.py +0 -6
  94. {selectools-1.0.0 → selectools-1.1.0}/tests/test_evals_new_evaluators.py +2 -4
  95. {selectools-1.0.0 → selectools-1.1.0}/tests/test_evals_ralph_bugs.py +2 -7
  96. {selectools-1.0.0 → selectools-1.1.0}/tests/test_evals_release.py +1 -4
  97. {selectools-1.0.0 → selectools-1.1.0}/tests/test_evals_serve.py +0 -5
  98. {selectools-1.0.0 → selectools-1.1.0}/tests/test_evals_v017_features.py +1 -2
  99. {selectools-1.0.0 → selectools-1.1.0}/tests/test_evals_v0191.py +1 -2
  100. {selectools-1.0.0 → selectools-1.1.0}/tests/test_features_in_graph.py +7 -12
  101. {selectools-1.0.0 → selectools-1.1.0}/tests/test_final_coverage_a.py +1 -3
  102. {selectools-1.0.0 → selectools-1.1.0}/tests/test_final_coverage_b.py +10 -14
  103. {selectools-1.0.0 → selectools-1.1.0}/tests/test_guardrails.py +100 -2
  104. {selectools-1.0.0 → selectools-1.1.0}/tests/test_hardening.py +1 -7
  105. {selectools-1.0.0 → selectools-1.1.0}/tests/test_knowledge.py +1 -4
  106. {selectools-1.0.0 → selectools-1.1.0}/tests/test_knowledge_backend.py +0 -1
  107. {selectools-1.0.0 → selectools-1.1.0}/tests/test_knowledge_backend_redis.py +1 -1
  108. {selectools-1.0.0 → selectools-1.1.0}/tests/test_knowledge_backend_supabase.py +1 -1
  109. {selectools-1.0.0 → selectools-1.1.0}/tests/test_knowledge_graph.py +1 -3
  110. {selectools-1.0.0 → selectools-1.1.0}/tests/test_knowledge_stores.py +0 -1
  111. {selectools-1.0.0 → selectools-1.1.0}/tests/test_knowledge_stores_error_handling.py +0 -2
  112. {selectools-1.0.0 → selectools-1.1.0}/tests/test_mcp.py +2 -5
  113. {selectools-1.0.0 → selectools-1.1.0}/tests/test_mcp_coverage.py +1 -2
  114. {selectools-1.0.0 → selectools-1.1.0}/tests/test_memory_boundary.py +0 -2
  115. {selectools-1.0.0 → selectools-1.1.0}/tests/test_memory_integration.py +0 -4
  116. {selectools-1.0.0 → selectools-1.1.0}/tests/test_multi_agent_edge_cases.py +2 -3
  117. {selectools-1.0.0 → selectools-1.1.0}/tests/test_multimodal.py +0 -4
  118. {selectools-1.0.0 → selectools-1.1.0}/tests/test_orchestration_checkpoint.py +4 -5
  119. {selectools-1.0.0 → selectools-1.1.0}/tests/test_orchestration_evals.py +0 -1
  120. {selectools-1.0.0 → selectools-1.1.0}/tests/test_orchestration_graph.py +3 -9
  121. {selectools-1.0.0 → selectools-1.1.0}/tests/test_orchestration_integration.py +0 -9
  122. {selectools-1.0.0 → selectools-1.1.0}/tests/test_orchestration_supervisor.py +5 -6
  123. {selectools-1.0.0 → selectools-1.1.0}/tests/test_patterns.py +5 -11
  124. {selectools-1.0.0 → selectools-1.1.0}/tests/test_phase1_design_patterns.py +0 -1
  125. {selectools-1.0.0 → selectools-1.1.0}/tests/test_pipeline.py +0 -2
  126. {selectools-1.0.0 → selectools-1.1.0}/tests/test_pipeline_coverage.py +1 -3
  127. {selectools-1.0.0 → selectools-1.1.0}/tests/test_prompt_compression.py +0 -2
  128. {selectools-1.0.0 → selectools-1.1.0}/tests/test_property_based.py +0 -1
  129. {selectools-1.0.0 → selectools-1.1.0}/tests/test_remaining_coverage.py +4 -9
  130. {selectools-1.0.0 → selectools-1.1.0}/tests/test_results.py +1 -1
  131. {selectools-1.0.0 → selectools-1.1.0}/tests/test_security.py +1 -3
  132. {selectools-1.0.0 → selectools-1.1.0}/tests/test_semantic_cache.py +0 -1
  133. {selectools-1.0.0 → selectools-1.1.0}/tests/test_serve_app_coverage.py +7 -11
  134. {selectools-1.0.0 → selectools-1.1.0}/tests/test_serve_cli.py +0 -1
  135. {selectools-1.0.0 → selectools-1.1.0}/tests/test_sessions_dynamodb.py +1 -1
  136. {selectools-1.0.0 → selectools-1.1.0}/tests/test_sessions_edge_cases.py +1 -3
  137. {selectools-1.0.0 → selectools-1.1.0}/tests/test_sessions_redis.py +1 -1
  138. {selectools-1.0.0 → selectools-1.1.0}/tests/test_sessions_supabase.py +4 -5
  139. {selectools-1.0.0 → selectools-1.1.0}/tests/test_starlette_app.py +0 -2
  140. {selectools-1.0.0 → selectools-1.1.0}/tests/test_structured_config.py +0 -10
  141. {selectools-1.0.0 → selectools-1.1.0}/tests/test_structured_tool_results.py +1 -1
  142. {selectools-1.0.0 → selectools-1.1.0}/tests/test_summarize_on_trim.py +2 -4
  143. {selectools-1.0.0 → selectools-1.1.0}/tests/test_terminal_actions.py +1 -2
  144. selectools-1.1.0/tests/test_time.py +66 -0
  145. {selectools-1.0.0 → selectools-1.1.0}/tests/test_token_estimation.py +0 -2
  146. {selectools-1.0.0 → selectools-1.1.0}/tests/test_trace.py +0 -3
  147. {selectools-1.0.0 → selectools-1.1.0}/tests/test_trace_html.py +0 -4
  148. {selectools-1.0.0 → selectools-1.1.0}/tests/test_trace_store.py +1 -5
  149. {selectools-1.0.0 → selectools-1.1.0}/tests/test_v016_regression.py +0 -2
  150. {selectools-1.0.0 → selectools-1.1.0}/tests/test_v019_features.py +2 -3
  151. {selectools-1.0.0 → selectools-1.1.0}/tests/test_visual_builder.py +3 -17
  152. {selectools-1.0.0 → selectools-1.1.0}/LICENSE +0 -0
  153. {selectools-1.0.0 → selectools-1.1.0}/setup.cfg +0 -0
  154. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/_async_utils.py +0 -0
  155. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/_json_utils.py +0 -0
  156. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/_ssrf.py +0 -0
  157. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/a2a/__init__.py +0 -0
  158. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/a2a/client.py +0 -0
  159. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/a2a/server.py +0 -0
  160. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/a2a/types.py +0 -0
  161. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/agent/__init__.py +0 -0
  162. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/agent/_planning.py +0 -0
  163. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/analytics.py +0 -0
  164. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/cache_redis.py +0 -0
  165. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/cancellation.py +0 -0
  166. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/coherence.py +0 -0
  167. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/embeddings/__init__.py +0 -0
  168. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/entity_memory.py +0 -0
  169. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/env.py +0 -0
  170. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/evals/__init__.py +0 -0
  171. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/evals/__main__.py +0 -0
  172. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/evals/badge.py +0 -0
  173. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/evals/dataset.py +0 -0
  174. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/evals/evaluators.py +0 -0
  175. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/evals/generator.py +0 -0
  176. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/evals/html.py +0 -0
  177. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/evals/junit.py +0 -0
  178. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/evals/llm_evaluators.py +0 -0
  179. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/evals/regression.py +0 -0
  180. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/evals/report.py +0 -0
  181. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/evals/snapshot.py +0 -0
  182. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/evals/suite.py +0 -0
  183. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/evals/types.py +0 -0
  184. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/exceptions.py +0 -0
  185. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/guardrails/__init__.py +0 -0
  186. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/guardrails/base.py +0 -0
  187. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/guardrails/format.py +0 -0
  188. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/guardrails/injection.py +0 -0
  189. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/guardrails/length.py +0 -0
  190. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/guardrails/pii.py +0 -0
  191. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/guardrails/topic.py +0 -0
  192. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/guardrails/toxicity.py +0 -0
  193. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/knowledge_backends.py +0 -0
  194. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/knowledge_graph.py +0 -0
  195. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/knowledge_sanitizers.py +0 -0
  196. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/loop_detection.py +0 -0
  197. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/mcp/__init__.py +0 -0
  198. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/mcp/_loop.py +0 -0
  199. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/memory.py +0 -0
  200. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/models.py +0 -0
  201. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/observe/__init__.py +0 -0
  202. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/orchestration/__init__.py +0 -0
  203. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/parser.py +0 -0
  204. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/patterns/__init__.py +0 -0
  205. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/pending.py +0 -0
  206. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/pipeline.py +0 -0
  207. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/policy.py +0 -0
  208. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/pricing.py +0 -0
  209. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/prompt.py +0 -0
  210. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/providers/__init__.py +0 -0
  211. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/providers/base.py +0 -0
  212. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/providers/litellm_provider.py +0 -0
  213. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/providers/router.py +0 -0
  214. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/providers/stubs.py +0 -0
  215. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/rag/__init__.py +0 -0
  216. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/rag/chunking.py +0 -0
  217. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/rag/reranker.py +0 -0
  218. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/rag/stores/__init__.py +0 -0
  219. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/rag/stores/chroma.py +0 -0
  220. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/rag/stores/pgvector.py +0 -0
  221. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/rag/stores/sqlite.py +0 -0
  222. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/rag/tools.py +0 -0
  223. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/rag/vector_store.py +0 -0
  224. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/results.py +0 -0
  225. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/security.py +0 -0
  226. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/serve/__init__.py +0 -0
  227. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/serve/_starlette_app.py +0 -0
  228. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/serve/_static/builder.css +0 -0
  229. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/serve/_static/builder.html +0 -0
  230. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/serve/_static/builder.js +0 -0
  231. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/serve/api.py +0 -0
  232. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/serve/builder.py +0 -0
  233. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/serve/playground.py +0 -0
  234. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/stability.py +0 -0
  235. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/templates/__init__.py +0 -0
  236. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/templates/code_reviewer.py +0 -0
  237. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/templates/customer_support.py +0 -0
  238. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/templates/data_analyst.py +0 -0
  239. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/templates/rag_chatbot.py +0 -0
  240. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/templates/research_assistant.py +0 -0
  241. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/token_estimation.py +0 -0
  242. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/__init__.py +0 -0
  243. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/browser_tools.py +0 -0
  244. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/calculator_tools.py +0 -0
  245. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/code_tools.py +0 -0
  246. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/data_tools.py +0 -0
  247. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/db_tools.py +0 -0
  248. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/discord_tools.py +0 -0
  249. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/email_tools.py +0 -0
  250. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/file_tools.py +0 -0
  251. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/github_tools.py +0 -0
  252. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/image_tools.py +0 -0
  253. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/linear_tools.py +0 -0
  254. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/memory_tools.py +0 -0
  255. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/notion_tools.py +0 -0
  256. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/pdf_tools.py +0 -0
  257. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/reasoning_tools.py +0 -0
  258. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/s3_tools.py +0 -0
  259. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/search_tools.py +0 -0
  260. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/slack_tools.py +0 -0
  261. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/text_tools.py +0 -0
  262. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/toolbox/web_tools.py +0 -0
  263. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/tools/__init__.py +0 -0
  264. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/tools/decorators.py +0 -0
  265. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/tools/loader.py +0 -0
  266. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/tools/registry.py +0 -0
  267. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/types.py +0 -0
  268. {selectools-1.0.0 → selectools-1.1.0}/src/selectools/usage.py +0 -0
  269. {selectools-1.0.0 → selectools-1.1.0}/src/selectools.egg-info/dependency_links.txt +0 -0
  270. {selectools-1.0.0 → selectools-1.1.0}/src/selectools.egg-info/entry_points.txt +0 -0
  271. {selectools-1.0.0 → selectools-1.1.0}/src/selectools.egg-info/requires.txt +0 -0
  272. {selectools-1.0.0 → selectools-1.1.0}/src/selectools.egg-info/top_level.txt +0 -0
  273. {selectools-1.0.0 → selectools-1.1.0}/tests/test_a2a.py +0 -0
  274. {selectools-1.0.0 → selectools-1.1.0}/tests/test_agent_api.py +0 -0
  275. {selectools-1.0.0 → selectools-1.1.0}/tests/test_agent_hitl.py +0 -0
  276. {selectools-1.0.0 → selectools-1.1.0}/tests/test_approval_gate.py +0 -0
  277. {selectools-1.0.0 → selectools-1.1.0}/tests/test_architecture.py +0 -0
  278. {selectools-1.0.0 → selectools-1.1.0}/tests/test_async_observers.py +0 -0
  279. {selectools-1.0.0 → selectools-1.1.0}/tests/test_audit.py +0 -0
  280. {selectools-1.0.0 → selectools-1.1.0}/tests/test_bake_window_hunt.py +0 -0
  281. {selectools-1.0.0 → selectools-1.1.0}/tests/test_budget.py +0 -0
  282. {selectools-1.0.0 → selectools-1.1.0}/tests/test_cache.py +0 -0
  283. {selectools-1.0.0 → selectools-1.1.0}/tests/test_cache_redis.py +0 -0
  284. {selectools-1.0.0 → selectools-1.1.0}/tests/test_cancellation.py +0 -0
  285. {selectools-1.0.0 → selectools-1.1.0}/tests/test_conversation_branching.py +0 -0
  286. {selectools-1.0.0 → selectools-1.1.0}/tests/test_e2e_langfuse_observer.py +0 -0
  287. {selectools-1.0.0 → selectools-1.1.0}/tests/test_e2e_otel_observer.py +0 -0
  288. {selectools-1.0.0 → selectools-1.1.0}/tests/test_e2e_v0_21_0_apps.py +0 -0
  289. {selectools-1.0.0 → selectools-1.1.0}/tests/test_e2e_v0_21_0_simulations.py +0 -0
  290. {selectools-1.0.0 → selectools-1.1.0}/tests/test_env.py +0 -0
  291. {selectools-1.0.0 → selectools-1.1.0}/tests/test_guardrails_injection.py +0 -0
  292. {selectools-1.0.0 → selectools-1.1.0}/tests/test_knowledge_sanitizers.py +0 -0
  293. {selectools-1.0.0 → selectools-1.1.0}/tests/test_knowledge_store_redis.py +0 -0
  294. {selectools-1.0.0 → selectools-1.1.0}/tests/test_knowledge_store_supabase.py +0 -0
  295. {selectools-1.0.0 → selectools-1.1.0}/tests/test_langfuse_observer.py +0 -0
  296. {selectools-1.0.0 → selectools-1.1.0}/tests/test_loop_detection.py +0 -0
  297. {selectools-1.0.0 → selectools-1.1.0}/tests/test_memory.py +0 -0
  298. {selectools-1.0.0 → selectools-1.1.0}/tests/test_memory_async.py +0 -0
  299. {selectools-1.0.0 → selectools-1.1.0}/tests/test_model_switching.py +0 -0
  300. {selectools-1.0.0 → selectools-1.1.0}/tests/test_orchestration_e2e.py +0 -0
  301. {selectools-1.0.0 → selectools-1.1.0}/tests/test_orchestration_primitives.py +0 -0
  302. {selectools-1.0.0 → selectools-1.1.0}/tests/test_otel_observer.py +0 -0
  303. {selectools-1.0.0 → selectools-1.1.0}/tests/test_parser.py +0 -0
  304. {selectools-1.0.0 → selectools-1.1.0}/tests/test_pending.py +0 -0
  305. {selectools-1.0.0 → selectools-1.1.0}/tests/test_pending_redis_smoke.py +0 -0
  306. {selectools-1.0.0 → selectools-1.1.0}/tests/test_policy.py +0 -0
  307. {selectools-1.0.0 → selectools-1.1.0}/tests/test_prompt.py +0 -0
  308. {selectools-1.0.0 → selectools-1.1.0}/tests/test_reasoning_strategy.py +0 -0
  309. {selectools-1.0.0 → selectools-1.1.0}/tests/test_routing_mode.py +0 -0
  310. {selectools-1.0.0 → selectools-1.1.0}/tests/test_scheduler.py +0 -0
  311. {selectools-1.0.0 → selectools-1.1.0}/tests/test_sessions.py +0 -0
  312. {selectools-1.0.0 → selectools-1.1.0}/tests/test_sessions_mongo.py +0 -0
  313. {selectools-1.0.0 → selectools-1.1.0}/tests/test_simple_observer.py +0 -0
  314. {selectools-1.0.0 → selectools-1.1.0}/tests/test_simulation_evals.py +0 -0
  315. {selectools-1.0.0 → selectools-1.1.0}/tests/test_ssrf.py +0 -0
  316. {selectools-1.0.0 → selectools-1.1.0}/tests/test_stability.py +0 -0
  317. {selectools-1.0.0 → selectools-1.1.0}/tests/test_structured.py +0 -0
  318. {selectools-1.0.0 → selectools-1.1.0}/tests/test_templates_coverage.py +0 -0
  319. {selectools-1.0.0 → selectools-1.1.0}/tests/test_tool_caching.py +0 -0
  320. {selectools-1.0.0 → selectools-1.1.0}/tests/test_tool_result_compression.py +0 -0
  321. {selectools-1.0.0 → selectools-1.1.0}/tests/test_unified_memory.py +0 -0
  322. {selectools-1.0.0 → selectools-1.1.0}/tests/test_unified_memory_config.py +0 -0
  323. {selectools-1.0.0 → selectools-1.1.0}/tests/test_yaml_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: selectools
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: Production-ready Python framework for AI agents with multi-agent graphs, hybrid RAG, guardrails, audit logging, 50 evaluators, and a visual builder. Supports OpenAI, Anthropic, Gemini, Ollama. By NichevLabs.
5
5
  Author-email: John Nichev <johnnichev@gmail.com>
6
6
  Maintainer-email: NichevLabs <support@nichevlabs.com>
@@ -103,6 +103,8 @@ An open-source project from **[NichevLabs](https://nichevlabs.com)**.
103
103
 
104
104
  **Multi-agent orchestration in plain Python.** Build agent graphs, compose pipelines with `|`, deploy with one command. No DSL, no compile step, no paid debugger. Works with OpenAI, Anthropic, Gemini, and Ollama.
105
105
 
106
+ > 🎉 **selectools is 1.0 — stable.** The public API is frozen; `@stable` symbols carry a 2-minor compatibility promise. Python 3.10+. See [What's New in v1.0](#whats-new-in-v10).
107
+
106
108
  ### 3 Ways to Build
107
109
 
108
110
  ```python
@@ -16,6 +16,8 @@ An open-source project from **[NichevLabs](https://nichevlabs.com)**.
16
16
 
17
17
  **Multi-agent orchestration in plain Python.** Build agent graphs, compose pipelines with `|`, deploy with one command. No DSL, no compile step, no paid debugger. Works with OpenAI, Anthropic, Gemini, and Ollama.
18
18
 
19
+ > 🎉 **selectools is 1.0 — stable.** The public API is frozen; `@stable` symbols carry a 2-minor compatibility promise. Python 3.10+. See [What's New in v1.0](#whats-new-in-v10).
20
+
19
21
  ### 3 Ways to Build
20
22
 
21
23
  ```python
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "selectools"
7
- version = "1.0.0"
7
+ version = "1.1.0"
8
8
  description = "Production-ready Python framework for AI agents with multi-agent graphs, hybrid RAG, guardrails, audit logging, 50 evaluators, and a visual builder. Supports OpenAI, Anthropic, Gemini, Ollama. By NichevLabs."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -154,15 +154,16 @@ ignore = [
154
154
  "E501", # line too long (handled by formatter)
155
155
  "E402", # module-level import not at top
156
156
  "E721", # type comparison with == (allowed; legacy test pattern)
157
- "F401", # unused imports (common in __init__.py re-exports)
158
157
  "F541", # f-string without placeholder
159
- "F841", # local variable assigned but never used
160
158
  "B011", # do not call assert False
161
159
  "B023", # function definition does not bind loop variable
162
160
  "B904", # raise without `from` in except (TODO: fix pre-existing violations)
163
161
  ]
164
162
 
165
163
  [tool.ruff.lint.per-file-ignores]
164
+ # F401 unused-import is expected in __init__.py re-export hubs (the public-API
165
+ # surface). Everywhere else, unused imports and unused locals are real findings.
166
+ "**/__init__.py" = ["F401"]
166
167
  "tests/*" = ["B"]
167
168
  "examples/*" = ["B", "F"]
168
169
  "scripts/*" = ["B", "F"]
@@ -1,6 +1,6 @@
1
1
  """Public exports for the selectools package."""
2
2
 
3
- __version__ = "1.0.0"
3
+ __version__ = "1.1.0"
4
4
 
5
5
  # Import submodules (lazy loading for optional dependencies)
6
6
  from . import embeddings, evals, guardrails, models, observe, patterns, rag, toolbox
@@ -13,6 +13,7 @@ from .agent.config_groups import (
13
13
  MemoryConfig,
14
14
  RetryConfig,
15
15
  SessionConfig,
16
+ StructuredOutputConfig,
16
17
  SummarizeConfig,
17
18
  ToolConfig,
18
19
  TraceConfig,
@@ -0,0 +1,42 @@
1
+ """Internal time helpers.
2
+
3
+ Centralizes ISO-8601 timestamp parsing so every persistence backend handles a
4
+ trailing ``Z`` (UTC designator) the same way.
5
+
6
+ ``datetime.fromisoformat`` did not accept the ``Z`` suffix until Python 3.11.
7
+ On Python 3.10 (the project's minimum) a ``Z``-suffixed timestamp such as
8
+ ``"2026-06-15T12:00:00Z"`` raises ``ValueError``. Postgres/Supabase/Redis and
9
+ other stores routinely return that form, so parsing it inline crashed the
10
+ knowledge and checkpoint loaders on 3.10. ``parse_iso`` normalizes the suffix
11
+ before delegating, so the same code works on every supported interpreter.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from datetime import datetime, timezone
17
+ from typing import Union
18
+
19
+
20
+ def parse_iso(value: Union[str, datetime]) -> datetime:
21
+ """Parse an ISO-8601 timestamp, tolerating a trailing ``Z`` (UTC).
22
+
23
+ Accepts either a string or an already-parsed ``datetime`` (returned as-is,
24
+ so callers reading columns that may already be typed do not need their own
25
+ ``isinstance`` guard). Timezone awareness is preserved exactly as encoded:
26
+ an offset-bearing string (including ``Z``) yields an aware datetime; a naive
27
+ string yields a naive datetime. Use :func:`ensure_aware` when a guaranteed
28
+ aware value is required.
29
+ """
30
+ if isinstance(value, datetime):
31
+ return value
32
+ s = str(value)
33
+ if s.endswith("Z"):
34
+ s = s[:-1] + "+00:00"
35
+ return datetime.fromisoformat(s)
36
+
37
+
38
+ def ensure_aware(dt: datetime, *, tz: timezone = timezone.utc) -> datetime:
39
+ """Attach ``tz`` (default UTC) to a naive datetime; pass aware ones through."""
40
+ if dt.tzinfo is None:
41
+ return dt.replace(tzinfo=tz)
42
+ return dt
@@ -8,7 +8,7 @@ import threading
8
8
  from typing import TYPE_CHECKING, Any, Optional, cast
9
9
 
10
10
  if TYPE_CHECKING:
11
- from ..memory import ConversationMemory
11
+ pass
12
12
 
13
13
  logger = logging.getLogger(__name__)
14
14
 
@@ -9,7 +9,6 @@ from ..trace import AgentTrace, StepType, TraceStep
9
9
  from ..types import Message, Role
10
10
 
11
11
  if TYPE_CHECKING:
12
- from ..memory import ConversationMemory
13
12
  from .core import _RunContext
14
13
 
15
14
 
@@ -50,8 +50,18 @@ class _ProviderCallerMixin:
50
50
  stream_handler: Optional[Callable[[str], None]] = None,
51
51
  trace: Optional[AgentTrace] = None,
52
52
  run_id: Optional[str] = None,
53
+ response_format: Optional[Dict[str, Any]] = None,
54
+ tools_override: Optional[List[Any]] = None,
55
+ system_prompt_override: Optional[str] = None,
53
56
  ) -> Message:
54
57
  call_start = time.time()
58
+ effective_tools = self.tools if tools_override is None else tools_override
59
+ effective_system_prompt = (
60
+ system_prompt_override if system_prompt_override is not None else self._system_prompt
61
+ )
62
+ provider_extra: Dict[str, Any] = {}
63
+ if response_format is not None:
64
+ provider_extra["response_format"] = response_format
55
65
 
56
66
  cache_key: Optional[str] = None
57
67
  if self.config.cache and not (
@@ -59,10 +69,11 @@ class _ProviderCallerMixin:
59
69
  ):
60
70
  cache_key = CacheKeyBuilder.build(
61
71
  model=self._effective_model,
62
- system_prompt=self._system_prompt,
72
+ system_prompt=effective_system_prompt,
63
73
  messages=self._history,
64
- tools=self.tools,
74
+ tools=effective_tools,
65
75
  temperature=self.config.temperature,
76
+ response_format=response_format,
66
77
  )
67
78
  cached = self.config.cache.get(cache_key)
68
79
  # Only treat a well-formed (Message, usage) pair as a cache hit;
@@ -82,7 +93,7 @@ class _ProviderCallerMixin:
82
93
  run_id,
83
94
  self._history,
84
95
  self._effective_model,
85
- self._system_prompt,
96
+ effective_system_prompt,
86
97
  )
87
98
  self._notify_observers(
88
99
  "on_llm_end",
@@ -122,12 +133,15 @@ class _ProviderCallerMixin:
122
133
  run_id,
123
134
  self._history,
124
135
  self._effective_model,
125
- self._system_prompt,
136
+ effective_system_prompt,
126
137
  )
127
138
 
128
139
  if self.config.stream and getattr(self.provider, "supports_streaming", False):
129
140
  response_text, streamed_tool_calls = self._streaming_call(
130
- stream_handler=stream_handler
141
+ stream_handler=stream_handler,
142
+ response_format=response_format,
143
+ tools=effective_tools,
144
+ system_prompt=effective_system_prompt,
131
145
  )
132
146
  if run_id:
133
147
  self._notify_observers("on_llm_end", run_id, response_text, None)
@@ -139,12 +153,13 @@ class _ProviderCallerMixin:
139
153
 
140
154
  response_msg, usage_stats = self.provider.complete(
141
155
  model=self._effective_model,
142
- system_prompt=self._system_prompt,
156
+ system_prompt=effective_system_prompt,
143
157
  messages=self._history,
144
- tools=self.tools,
158
+ tools=effective_tools,
145
159
  temperature=self.config.temperature,
146
160
  max_tokens=self.config.max_tokens,
147
161
  timeout=self.config.request_timeout,
162
+ **provider_extra,
148
163
  )
149
164
  response_text = response_msg.content or ""
150
165
 
@@ -229,21 +244,29 @@ class _ProviderCallerMixin:
229
244
  )
230
245
 
231
246
  def _streaming_call(
232
- self, stream_handler: Optional[Callable[[str], None]] = None
247
+ self,
248
+ stream_handler: Optional[Callable[[str], None]] = None,
249
+ response_format: Optional[Dict[str, Any]] = None,
250
+ tools: Optional[List[Any]] = None,
251
+ system_prompt: Optional[str] = None,
233
252
  ) -> Tuple[str, List[ToolCall]]:
234
253
  if not getattr(self.provider, "supports_streaming", False):
235
254
  raise ProviderError(f"Provider {self.provider.name} does not support streaming.")
236
255
 
256
+ provider_extra: Dict[str, Any] = {}
257
+ if response_format is not None:
258
+ provider_extra["response_format"] = response_format
237
259
  aggregated: List[str] = []
238
260
  tool_calls: List[ToolCall] = []
239
261
  for chunk in self.provider.stream(
240
262
  model=self._effective_model,
241
- system_prompt=self._system_prompt,
263
+ system_prompt=self._system_prompt if system_prompt is None else system_prompt,
242
264
  messages=self._history,
243
- tools=self.tools,
265
+ tools=self.tools if tools is None else tools,
244
266
  temperature=self.config.temperature,
245
267
  max_tokens=self.config.max_tokens,
246
268
  timeout=self.config.request_timeout,
269
+ **provider_extra,
247
270
  ):
248
271
  if isinstance(chunk, str):
249
272
  if chunk:
@@ -264,9 +287,19 @@ class _ProviderCallerMixin:
264
287
  stream_handler: Optional[Callable[[str], None]] = None,
265
288
  trace: Optional[AgentTrace] = None,
266
289
  run_id: Optional[str] = None,
290
+ response_format: Optional[Dict[str, Any]] = None,
291
+ tools_override: Optional[List[Any]] = None,
292
+ system_prompt_override: Optional[str] = None,
267
293
  ) -> Message:
268
294
  """Async version of _call_provider with retry logic."""
269
295
  call_start = time.time()
296
+ effective_tools = self.tools if tools_override is None else tools_override
297
+ effective_system_prompt = (
298
+ system_prompt_override if system_prompt_override is not None else self._system_prompt
299
+ )
300
+ provider_extra: Dict[str, Any] = {}
301
+ if response_format is not None:
302
+ provider_extra["response_format"] = response_format
270
303
 
271
304
  cache_key: Optional[str] = None
272
305
  if self.config.cache and not (
@@ -274,10 +307,11 @@ class _ProviderCallerMixin:
274
307
  ):
275
308
  cache_key = CacheKeyBuilder.build(
276
309
  model=self._effective_model,
277
- system_prompt=self._system_prompt,
310
+ system_prompt=effective_system_prompt,
278
311
  messages=self._history,
279
- tools=self.tools,
312
+ tools=effective_tools,
280
313
  temperature=self.config.temperature,
314
+ response_format=response_format,
281
315
  )
282
316
  cached = self.config.cache.get(cache_key)
283
317
  # Only treat a well-formed (Message, usage) pair as a cache hit;
@@ -297,14 +331,14 @@ class _ProviderCallerMixin:
297
331
  run_id,
298
332
  self._history,
299
333
  self._effective_model,
300
- self._system_prompt,
334
+ effective_system_prompt,
301
335
  )
302
336
  await self._anotify_observers(
303
337
  "on_llm_start",
304
338
  run_id,
305
339
  self._history,
306
340
  self._effective_model,
307
- self._system_prompt,
341
+ effective_system_prompt,
308
342
  )
309
343
  self._notify_observers(
310
344
  "on_llm_end",
@@ -357,19 +391,22 @@ class _ProviderCallerMixin:
357
391
  run_id,
358
392
  self._history,
359
393
  self._effective_model,
360
- self._system_prompt,
394
+ effective_system_prompt,
361
395
  )
362
396
  await self._anotify_observers(
363
397
  "on_llm_start",
364
398
  run_id,
365
399
  self._history,
366
400
  self._effective_model,
367
- self._system_prompt,
401
+ effective_system_prompt,
368
402
  )
369
403
 
370
404
  if self.config.stream and getattr(self.provider, "supports_streaming", False):
371
405
  response_text, streamed_tool_calls = await self._astreaming_call(
372
- stream_handler=stream_handler
406
+ stream_handler=stream_handler,
407
+ response_format=response_format,
408
+ tools=effective_tools,
409
+ system_prompt=effective_system_prompt,
373
410
  )
374
411
  if run_id:
375
412
  self._notify_observers("on_llm_end", run_id, response_text, None)
@@ -386,12 +423,13 @@ class _ProviderCallerMixin:
386
423
  ):
387
424
  response_msg, usage_stats = await self.provider.acomplete(
388
425
  model=self._effective_model,
389
- system_prompt=self._system_prompt,
426
+ system_prompt=effective_system_prompt,
390
427
  messages=self._history,
391
- tools=self.tools,
428
+ tools=effective_tools,
392
429
  temperature=self.config.temperature,
393
430
  max_tokens=self.config.max_tokens,
394
431
  timeout=self.config.request_timeout,
432
+ **provider_extra,
395
433
  )
396
434
  response_text = response_msg.content or ""
397
435
  else:
@@ -405,12 +443,13 @@ class _ProviderCallerMixin:
405
443
  _get_async_provider_executor(),
406
444
  lambda: self.provider.complete(
407
445
  model=self._effective_model,
408
- system_prompt=self._system_prompt,
446
+ system_prompt=effective_system_prompt,
409
447
  messages=self._history,
410
- tools=self.tools,
448
+ tools=effective_tools,
411
449
  temperature=self.config.temperature,
412
450
  max_tokens=self.config.max_tokens,
413
451
  timeout=self.config.request_timeout,
452
+ **provider_extra,
414
453
  ),
415
454
  )
416
455
  response_text = response_msg.content or ""
@@ -506,12 +545,21 @@ class _ProviderCallerMixin:
506
545
  )
507
546
 
508
547
  async def _astreaming_call(
509
- self, stream_handler: Optional[Callable[[str], None]] = None
548
+ self,
549
+ stream_handler: Optional[Callable[[str], None]] = None,
550
+ response_format: Optional[Dict[str, Any]] = None,
551
+ tools: Optional[List[Any]] = None,
552
+ system_prompt: Optional[str] = None,
510
553
  ) -> Tuple[str, List[ToolCall]]:
511
554
  """Async version of _streaming_call."""
512
555
  if not getattr(self.provider, "supports_streaming", False):
513
556
  raise ProviderError(f"Provider {self.provider.name} does not support streaming.")
514
557
 
558
+ provider_extra: Dict[str, Any] = {}
559
+ if response_format is not None:
560
+ provider_extra["response_format"] = response_format
561
+ effective_tools = self.tools if tools is None else tools
562
+ effective_system_prompt = self._system_prompt if system_prompt is None else system_prompt
515
563
  aggregated: List[str] = []
516
564
  tool_calls: List[ToolCall] = []
517
565
 
@@ -521,12 +569,13 @@ class _ProviderCallerMixin:
521
569
  async with aclosing(
522
570
  self.provider.astream( # type: ignore[attr-defined]
523
571
  model=self._effective_model,
524
- system_prompt=self._system_prompt,
572
+ system_prompt=effective_system_prompt,
525
573
  messages=self._history,
526
- tools=self.tools,
574
+ tools=effective_tools,
527
575
  temperature=self.config.temperature,
528
576
  max_tokens=self.config.max_tokens,
529
577
  timeout=self.config.request_timeout,
578
+ **provider_extra,
530
579
  )
531
580
  ) as stream:
532
581
  async for chunk in stream:
@@ -546,12 +595,13 @@ class _ProviderCallerMixin:
546
595
  with closing(
547
596
  self.provider.stream(
548
597
  model=self._effective_model,
549
- system_prompt=self._system_prompt,
598
+ system_prompt=effective_system_prompt,
550
599
  messages=self._history,
551
- tools=self.tools,
600
+ tools=effective_tools,
552
601
  temperature=self.config.temperature,
553
602
  max_tokens=self.config.max_tokens,
554
603
  timeout=self.config.request_timeout,
604
+ **provider_extra,
555
605
  )
556
606
  ) as stream:
557
607
  for chunk in stream:
@@ -57,8 +57,8 @@ def _get_parallel_dispatch_executor() -> ThreadPoolExecutor:
57
57
 
58
58
 
59
59
  from .._async_utils import run_in_executor_copyctx
60
- from ..coherence import CoherenceResult, acheck_coherence, check_coherence
61
- from ..policy import ApprovalRequest, PolicyDecision, PolicyResult, ToolPolicy
60
+ from ..coherence import acheck_coherence, check_coherence
61
+ from ..policy import ApprovalRequest, PolicyDecision, PolicyResult
62
62
  from ..security import screen_output as screen_tool_output
63
63
  from ..trace import StepType, TraceStep
64
64
  from ..types import Message, Role
@@ -7,7 +7,7 @@ from __future__ import annotations
7
7
  from dataclasses import dataclass, field
8
8
  from typing import TYPE_CHECKING, Any, Callable, Coroutine, Dict, List, Optional, Union
9
9
 
10
- from ..stability import beta, stable
10
+ from ..stability import stable
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from ..cache import Cache
@@ -30,6 +30,7 @@ if TYPE_CHECKING:
30
30
  MemoryConfig,
31
31
  RetryConfig,
32
32
  SessionConfig,
33
+ StructuredOutputConfig,
33
34
  SummarizeConfig,
34
35
  ToolConfig,
35
36
  TraceConfig,
@@ -202,6 +203,8 @@ class AgentConfig:
202
203
  budget: Optional["BudgetConfig"] = None
203
204
  trace: Optional["TraceConfig"] = None
204
205
  compress: Optional["CompressConfig"] = None
206
+ # Structured-output behavior (beta, v1.1). Group-only — no flat mirrors.
207
+ structured_output: Optional["StructuredOutputConfig"] = None
205
208
 
206
209
  # -- Planning-as-config (ROADMAP P2). See PlanningConfig below and
207
210
  # agent/_planning.py for the adapter. Self-contained: default None means
@@ -217,6 +220,7 @@ class AgentConfig:
217
220
  MemoryConfig,
218
221
  RetryConfig,
219
222
  SessionConfig,
223
+ StructuredOutputConfig,
220
224
  SummarizeConfig,
221
225
  ToolConfig,
222
226
  TraceConfig,
@@ -382,6 +386,10 @@ class AgentConfig:
382
386
  keep_recent=self.compress_keep_recent,
383
387
  )
384
388
 
389
+ self.structured_output = _unpack(self.structured_output, StructuredOutputConfig)
390
+ if self.structured_output is None:
391
+ self.structured_output = StructuredOutputConfig()
392
+
385
393
  # -- Planning-as-config: auto-unpack dicts (for YAML / dict configs) --
386
394
  if isinstance(self.planning, dict):
387
395
  self.planning = PlanningConfig(**self.planning)
@@ -24,23 +24,20 @@ Usage::
24
24
  from __future__ import annotations
25
25
 
26
26
  from dataclasses import dataclass, field
27
- from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
27
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
28
28
 
29
- from ..stability import stable
29
+ from ..stability import beta, stable
30
30
 
31
31
  if TYPE_CHECKING:
32
- from ..cache import Cache
33
32
  from ..cancellation import CancellationToken
34
33
  from ..entity_memory import EntityMemory
35
34
  from ..guardrails import GuardrailsPipeline
36
35
  from ..knowledge import KnowledgeMemory
37
36
  from ..knowledge_graph import KnowledgeGraphMemory
38
- from ..observer import AgentObserver
39
37
  from ..policy import ToolPolicy
40
38
  from ..providers.base import Provider
41
39
  from ..sessions import SessionStore
42
40
  from ..unified_memory import UnifiedMemory
43
- from ..usage import AgentUsage
44
41
 
45
42
 
46
43
  @stable
@@ -177,6 +174,32 @@ class GuardrailsConfig:
177
174
  output_screening_patterns: Optional[List[str]] = None
178
175
 
179
176
 
177
+ @beta
178
+ @dataclass
179
+ class StructuredOutputConfig:
180
+ """Structured-output (``response_format``) behavior settings (beta, v1.1).
181
+
182
+ Attributes:
183
+ native: Use the provider's native structured-output mode (OpenAI
184
+ ``json_schema``, Gemini ``response_json_schema``) when the provider
185
+ advertises support, instead of injecting a schema instruction into
186
+ the system prompt. Providers without native support keep the
187
+ prompt-injection + parse behavior unchanged. Default: True.
188
+ final_turn_only: Keep the schema out of tool-loop turns entirely.
189
+ The loop runs to convergence with no schema pressure (so tool
190
+ calling and the text ``ToolCallParser`` work normally), then one
191
+ extra synthesis call — with the schema applied and no tools —
192
+ produces the validated structured final answer. In ``astream()``
193
+ the synthesis JSON is delivered only via the terminal
194
+ ``AgentResult`` (``.content`` / ``.parsed``), never leaked as
195
+ content chunks. Costs one extra provider call per run.
196
+ Default: False.
197
+ """
198
+
199
+ native: bool = True
200
+ final_turn_only: bool = False
201
+
202
+
180
203
  @stable
181
204
  @dataclass
182
205
  class SessionConfig:
@@ -326,6 +349,7 @@ __stability__ = "stable"
326
349
 
327
350
  __all__ = [
328
351
  "RetryConfig",
352
+ "StructuredOutputConfig",
329
353
  "ToolConfig",
330
354
  "CoherenceConfig",
331
355
  "GuardrailsConfig",