selectools 0.27.2__tar.gz → 0.29.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 (321) hide show
  1. {selectools-0.27.2/src/selectools.egg-info → selectools-0.29.0}/PKG-INFO +39 -7
  2. {selectools-0.27.2 → selectools-0.29.0}/README.md +33 -5
  3. {selectools-0.27.2 → selectools-0.29.0}/pyproject.toml +7 -2
  4. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/__init__.py +1 -1
  5. selectools-0.29.0/src/selectools/_ssrf.py +88 -0
  6. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/agent/_provider_caller.py +43 -21
  7. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/agent/_tool_executor.py +3 -2
  8. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/agent/config.py +24 -11
  9. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/agent/core.py +18 -8
  10. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/embeddings/anthropic.py +12 -1
  11. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/embeddings/cohere.py +12 -1
  12. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/embeddings/openai.py +8 -1
  13. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/evals/evaluators.py +1 -1
  14. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/mcp/multi.py +7 -1
  15. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/orchestration/__init__.py +1 -1
  16. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/orchestration/checkpoint.py +1 -1
  17. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/orchestration/graph.py +3 -5
  18. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/orchestration/node.py +1 -1
  19. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/orchestration/state.py +1 -1
  20. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/orchestration/supervisor.py +4 -2
  21. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/patterns/__init__.py +1 -1
  22. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/patterns/debate.py +1 -1
  23. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/patterns/plan_and_execute.py +1 -1
  24. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/patterns/reflective.py +1 -1
  25. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/patterns/team_lead.py +1 -1
  26. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/policy.py +1 -1
  27. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/providers/anthropic_provider.py +4 -4
  28. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/rag/loaders.py +1 -54
  29. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/rag/stores/chroma.py +2 -2
  30. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/rag/stores/memory.py +10 -11
  31. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/rag/vector_store.py +7 -5
  32. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/serve/api.py +7 -5
  33. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/serve/app.py +30 -10
  34. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/sessions.py +61 -35
  35. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/templates/__init__.py +2 -9
  36. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/browser_tools.py +7 -0
  37. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/calculator_tools.py +3 -3
  38. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/code_tools.py +56 -19
  39. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/db_tools.py +3 -3
  40. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/email_tools.py +3 -3
  41. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/github_tools.py +4 -4
  42. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/image_tools.py +3 -1
  43. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/linear_tools.py +4 -4
  44. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/notion_tools.py +4 -4
  45. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/pdf_tools.py +3 -3
  46. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/search_tools.py +4 -54
  47. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/slack_tools.py +4 -4
  48. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/types.py +8 -4
  49. {selectools-0.27.2 → selectools-0.29.0/src/selectools.egg-info}/PKG-INFO +39 -7
  50. {selectools-0.27.2 → selectools-0.29.0}/src/selectools.egg-info/SOURCES.txt +2 -0
  51. {selectools-0.27.2 → selectools-0.29.0}/src/selectools.egg-info/requires.txt +6 -1
  52. {selectools-0.27.2 → selectools-0.29.0}/tests/test_architecture.py +12 -12
  53. {selectools-0.27.2 → selectools-0.29.0}/tests/test_conversation_branching.py +40 -0
  54. {selectools-0.27.2 → selectools-0.29.0}/tests/test_guardrails_injection.py +18 -0
  55. {selectools-0.27.2 → selectools-0.29.0}/tests/test_scheduler.py +31 -6
  56. {selectools-0.27.2 → selectools-0.29.0}/tests/test_sessions_dynamodb.py +32 -0
  57. {selectools-0.27.2 → selectools-0.29.0}/tests/test_sessions_supabase.py +31 -0
  58. selectools-0.29.0/tests/test_ssrf.py +63 -0
  59. {selectools-0.27.2 → selectools-0.29.0}/tests/test_yaml_config.py +4 -2
  60. {selectools-0.27.2 → selectools-0.29.0}/LICENSE +0 -0
  61. {selectools-0.27.2 → selectools-0.29.0}/setup.cfg +0 -0
  62. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/_async_utils.py +0 -0
  63. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/_json_utils.py +0 -0
  64. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/a2a/__init__.py +0 -0
  65. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/a2a/client.py +0 -0
  66. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/a2a/server.py +0 -0
  67. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/a2a/types.py +0 -0
  68. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/agent/__init__.py +0 -0
  69. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/agent/_lifecycle.py +0 -0
  70. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/agent/_memory_manager.py +0 -0
  71. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/agent/_planning.py +0 -0
  72. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/agent/config_groups.py +0 -0
  73. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/analytics.py +0 -0
  74. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/audit.py +0 -0
  75. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/cache.py +0 -0
  76. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/cache_redis.py +0 -0
  77. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/cache_semantic.py +0 -0
  78. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/cancellation.py +0 -0
  79. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/checkpoint_postgres.py +0 -0
  80. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/coherence.py +0 -0
  81. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/compose.py +0 -0
  82. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/embeddings/__init__.py +0 -0
  83. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/embeddings/gemini.py +0 -0
  84. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/embeddings/provider.py +0 -0
  85. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/entity_memory.py +0 -0
  86. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/env.py +0 -0
  87. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/evals/__init__.py +0 -0
  88. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/evals/__main__.py +0 -0
  89. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/evals/badge.py +0 -0
  90. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/evals/dataset.py +0 -0
  91. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/evals/generator.py +0 -0
  92. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/evals/history.py +0 -0
  93. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/evals/html.py +0 -0
  94. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/evals/junit.py +0 -0
  95. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/evals/llm_evaluators.py +0 -0
  96. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/evals/pairwise.py +0 -0
  97. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/evals/regression.py +0 -0
  98. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/evals/report.py +0 -0
  99. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/evals/serve.py +0 -0
  100. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/evals/snapshot.py +0 -0
  101. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/evals/suite.py +0 -0
  102. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/evals/templates.py +0 -0
  103. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/evals/types.py +0 -0
  104. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/exceptions.py +0 -0
  105. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/guardrails/__init__.py +0 -0
  106. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/guardrails/base.py +0 -0
  107. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/guardrails/format.py +0 -0
  108. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/guardrails/injection.py +0 -0
  109. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/guardrails/length.py +0 -0
  110. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/guardrails/pii.py +0 -0
  111. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/guardrails/pipeline.py +0 -0
  112. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/guardrails/topic.py +0 -0
  113. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/guardrails/toxicity.py +0 -0
  114. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/knowledge.py +0 -0
  115. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/knowledge_backends.py +0 -0
  116. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/knowledge_graph.py +0 -0
  117. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/knowledge_sanitizers.py +0 -0
  118. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/knowledge_store_redis.py +0 -0
  119. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/knowledge_store_supabase.py +0 -0
  120. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/loop_detection.py +0 -0
  121. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/mcp/__init__.py +0 -0
  122. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/mcp/_loop.py +0 -0
  123. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/mcp/bridge.py +0 -0
  124. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/mcp/client.py +0 -0
  125. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/mcp/config.py +0 -0
  126. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/mcp/server.py +0 -0
  127. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/memory.py +0 -0
  128. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/models.py +0 -0
  129. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/observe/__init__.py +0 -0
  130. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/observe/langfuse.py +0 -0
  131. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/observe/otel.py +0 -0
  132. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/observe/trace_store.py +0 -0
  133. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/observer.py +0 -0
  134. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/parser.py +0 -0
  135. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/pending.py +0 -0
  136. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/pipeline.py +0 -0
  137. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/pricing.py +0 -0
  138. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/prompt.py +0 -0
  139. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/providers/__init__.py +0 -0
  140. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/providers/_openai_compat.py +0 -0
  141. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/providers/azure_openai_provider.py +0 -0
  142. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/providers/base.py +0 -0
  143. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/providers/fallback.py +0 -0
  144. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/providers/gemini_provider.py +0 -0
  145. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/providers/litellm_provider.py +0 -0
  146. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/providers/ollama_provider.py +0 -0
  147. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/providers/openai_provider.py +0 -0
  148. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/providers/router.py +0 -0
  149. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/providers/stubs.py +0 -0
  150. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/rag/__init__.py +0 -0
  151. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/rag/bm25.py +0 -0
  152. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/rag/chunking.py +0 -0
  153. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/rag/hybrid.py +0 -0
  154. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/rag/reranker.py +0 -0
  155. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/rag/stores/__init__.py +0 -0
  156. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/rag/stores/faiss.py +0 -0
  157. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/rag/stores/pgvector.py +0 -0
  158. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/rag/stores/pinecone.py +0 -0
  159. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/rag/stores/qdrant.py +0 -0
  160. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/rag/stores/sqlite.py +0 -0
  161. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/rag/tools.py +0 -0
  162. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/results.py +0 -0
  163. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/scheduler.py +0 -0
  164. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/security.py +0 -0
  165. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/serve/__init__.py +0 -0
  166. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/serve/_starlette_app.py +0 -0
  167. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/serve/_static/builder.css +0 -0
  168. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/serve/_static/builder.html +0 -0
  169. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/serve/_static/builder.js +0 -0
  170. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/serve/builder.py +0 -0
  171. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/serve/cli.py +0 -0
  172. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/serve/models.py +0 -0
  173. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/serve/playground.py +0 -0
  174. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/stability.py +0 -0
  175. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/structured.py +0 -0
  176. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/templates/code_reviewer.py +0 -0
  177. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/templates/customer_support.py +0 -0
  178. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/templates/data_analyst.py +0 -0
  179. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/templates/rag_chatbot.py +0 -0
  180. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/templates/research_assistant.py +0 -0
  181. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/token_estimation.py +0 -0
  182. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/__init__.py +0 -0
  183. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/data_tools.py +0 -0
  184. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/datetime_tools.py +0 -0
  185. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/discord_tools.py +0 -0
  186. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/file_tools.py +0 -0
  187. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/memory_tools.py +0 -0
  188. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/reasoning_tools.py +0 -0
  189. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/s3_tools.py +0 -0
  190. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/text_tools.py +0 -0
  191. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/toolbox/web_tools.py +0 -0
  192. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/tools/__init__.py +0 -0
  193. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/tools/base.py +0 -0
  194. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/tools/decorators.py +0 -0
  195. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/tools/loader.py +0 -0
  196. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/tools/registry.py +0 -0
  197. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/trace.py +0 -0
  198. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/unified_memory.py +0 -0
  199. {selectools-0.27.2 → selectools-0.29.0}/src/selectools/usage.py +0 -0
  200. {selectools-0.27.2 → selectools-0.29.0}/src/selectools.egg-info/dependency_links.txt +0 -0
  201. {selectools-0.27.2 → selectools-0.29.0}/src/selectools.egg-info/entry_points.txt +0 -0
  202. {selectools-0.27.2 → selectools-0.29.0}/src/selectools.egg-info/top_level.txt +0 -0
  203. {selectools-0.27.2 → selectools-0.29.0}/tests/test_a2a.py +0 -0
  204. {selectools-0.27.2 → selectools-0.29.0}/tests/test_agent_api.py +0 -0
  205. {selectools-0.27.2 → selectools-0.29.0}/tests/test_agent_hitl.py +0 -0
  206. {selectools-0.27.2 → selectools-0.29.0}/tests/test_approval_gate.py +0 -0
  207. {selectools-0.27.2 → selectools-0.29.0}/tests/test_async_observers.py +0 -0
  208. {selectools-0.27.2 → selectools-0.29.0}/tests/test_audit.py +0 -0
  209. {selectools-0.27.2 → selectools-0.29.0}/tests/test_bake_window_hunt.py +0 -0
  210. {selectools-0.27.2 → selectools-0.29.0}/tests/test_budget.py +0 -0
  211. {selectools-0.27.2 → selectools-0.29.0}/tests/test_bug_hunt_batch1_core.py +0 -0
  212. {selectools-0.27.2 → selectools-0.29.0}/tests/test_bug_hunt_batch1_security.py +0 -0
  213. {selectools-0.27.2 → selectools-0.29.0}/tests/test_bug_hunt_batch1_tools.py +0 -0
  214. {selectools-0.27.2 → selectools-0.29.0}/tests/test_bug_hunt_regression.py +0 -0
  215. {selectools-0.27.2 → selectools-0.29.0}/tests/test_cache.py +0 -0
  216. {selectools-0.27.2 → selectools-0.29.0}/tests/test_cache_redis.py +0 -0
  217. {selectools-0.27.2 → selectools-0.29.0}/tests/test_cancellation.py +0 -0
  218. {selectools-0.27.2 → selectools-0.29.0}/tests/test_checkpoint_postgres.py +0 -0
  219. {selectools-0.27.2 → selectools-0.29.0}/tests/test_coherence.py +0 -0
  220. {selectools-0.27.2 → selectools-0.29.0}/tests/test_concurrency_smoke.py +0 -0
  221. {selectools-0.27.2 → selectools-0.29.0}/tests/test_consolidation_regression.py +0 -0
  222. {selectools-0.27.2 → selectools-0.29.0}/tests/test_coverage_orchestration.py +0 -0
  223. {selectools-0.27.2 → selectools-0.29.0}/tests/test_document_loaders_extended.py +0 -0
  224. {selectools-0.27.2 → selectools-0.29.0}/tests/test_e2e_langfuse_observer.py +0 -0
  225. {selectools-0.27.2 → selectools-0.29.0}/tests/test_e2e_multimodal.py +0 -0
  226. {selectools-0.27.2 → selectools-0.29.0}/tests/test_e2e_otel_observer.py +0 -0
  227. {selectools-0.27.2 → selectools-0.29.0}/tests/test_e2e_v0_21_0_apps.py +0 -0
  228. {selectools-0.27.2 → selectools-0.29.0}/tests/test_e2e_v0_21_0_simulations.py +0 -0
  229. {selectools-0.27.2 → selectools-0.29.0}/tests/test_entity_memory.py +0 -0
  230. {selectools-0.27.2 → selectools-0.29.0}/tests/test_env.py +0 -0
  231. {selectools-0.27.2 → selectools-0.29.0}/tests/test_evals.py +0 -0
  232. {selectools-0.27.2 → selectools-0.29.0}/tests/test_evals_advanced.py +0 -0
  233. {selectools-0.27.2 → selectools-0.29.0}/tests/test_evals_cli.py +0 -0
  234. {selectools-0.27.2 → selectools-0.29.0}/tests/test_evals_e2e.py +0 -0
  235. {selectools-0.27.2 → selectools-0.29.0}/tests/test_evals_final.py +0 -0
  236. {selectools-0.27.2 → selectools-0.29.0}/tests/test_evals_hardening.py +0 -0
  237. {selectools-0.27.2 → selectools-0.29.0}/tests/test_evals_html_report.py +0 -0
  238. {selectools-0.27.2 → selectools-0.29.0}/tests/test_evals_new_evaluators.py +0 -0
  239. {selectools-0.27.2 → selectools-0.29.0}/tests/test_evals_ralph_bugs.py +0 -0
  240. {selectools-0.27.2 → selectools-0.29.0}/tests/test_evals_release.py +0 -0
  241. {selectools-0.27.2 → selectools-0.29.0}/tests/test_evals_serve.py +0 -0
  242. {selectools-0.27.2 → selectools-0.29.0}/tests/test_evals_v017_features.py +0 -0
  243. {selectools-0.27.2 → selectools-0.29.0}/tests/test_evals_v0191.py +0 -0
  244. {selectools-0.27.2 → selectools-0.29.0}/tests/test_features_in_graph.py +0 -0
  245. {selectools-0.27.2 → selectools-0.29.0}/tests/test_final_coverage_a.py +0 -0
  246. {selectools-0.27.2 → selectools-0.29.0}/tests/test_final_coverage_b.py +0 -0
  247. {selectools-0.27.2 → selectools-0.29.0}/tests/test_guardrails.py +0 -0
  248. {selectools-0.27.2 → selectools-0.29.0}/tests/test_hardening.py +0 -0
  249. {selectools-0.27.2 → selectools-0.29.0}/tests/test_knowledge.py +0 -0
  250. {selectools-0.27.2 → selectools-0.29.0}/tests/test_knowledge_backend.py +0 -0
  251. {selectools-0.27.2 → selectools-0.29.0}/tests/test_knowledge_backend_redis.py +0 -0
  252. {selectools-0.27.2 → selectools-0.29.0}/tests/test_knowledge_backend_supabase.py +0 -0
  253. {selectools-0.27.2 → selectools-0.29.0}/tests/test_knowledge_graph.py +0 -0
  254. {selectools-0.27.2 → selectools-0.29.0}/tests/test_knowledge_sanitizers.py +0 -0
  255. {selectools-0.27.2 → selectools-0.29.0}/tests/test_knowledge_store_redis.py +0 -0
  256. {selectools-0.27.2 → selectools-0.29.0}/tests/test_knowledge_store_supabase.py +0 -0
  257. {selectools-0.27.2 → selectools-0.29.0}/tests/test_knowledge_stores.py +0 -0
  258. {selectools-0.27.2 → selectools-0.29.0}/tests/test_knowledge_stores_error_handling.py +0 -0
  259. {selectools-0.27.2 → selectools-0.29.0}/tests/test_langfuse_observer.py +0 -0
  260. {selectools-0.27.2 → selectools-0.29.0}/tests/test_loop_detection.py +0 -0
  261. {selectools-0.27.2 → selectools-0.29.0}/tests/test_mcp.py +0 -0
  262. {selectools-0.27.2 → selectools-0.29.0}/tests/test_mcp_coverage.py +0 -0
  263. {selectools-0.27.2 → selectools-0.29.0}/tests/test_memory.py +0 -0
  264. {selectools-0.27.2 → selectools-0.29.0}/tests/test_memory_async.py +0 -0
  265. {selectools-0.27.2 → selectools-0.29.0}/tests/test_memory_boundary.py +0 -0
  266. {selectools-0.27.2 → selectools-0.29.0}/tests/test_memory_integration.py +0 -0
  267. {selectools-0.27.2 → selectools-0.29.0}/tests/test_model_switching.py +0 -0
  268. {selectools-0.27.2 → selectools-0.29.0}/tests/test_multi_agent_edge_cases.py +0 -0
  269. {selectools-0.27.2 → selectools-0.29.0}/tests/test_multimodal.py +0 -0
  270. {selectools-0.27.2 → selectools-0.29.0}/tests/test_orchestration_checkpoint.py +0 -0
  271. {selectools-0.27.2 → selectools-0.29.0}/tests/test_orchestration_e2e.py +0 -0
  272. {selectools-0.27.2 → selectools-0.29.0}/tests/test_orchestration_evals.py +0 -0
  273. {selectools-0.27.2 → selectools-0.29.0}/tests/test_orchestration_graph.py +0 -0
  274. {selectools-0.27.2 → selectools-0.29.0}/tests/test_orchestration_integration.py +0 -0
  275. {selectools-0.27.2 → selectools-0.29.0}/tests/test_orchestration_primitives.py +0 -0
  276. {selectools-0.27.2 → selectools-0.29.0}/tests/test_orchestration_supervisor.py +0 -0
  277. {selectools-0.27.2 → selectools-0.29.0}/tests/test_otel_observer.py +0 -0
  278. {selectools-0.27.2 → selectools-0.29.0}/tests/test_parser.py +0 -0
  279. {selectools-0.27.2 → selectools-0.29.0}/tests/test_patterns.py +0 -0
  280. {selectools-0.27.2 → selectools-0.29.0}/tests/test_pending.py +0 -0
  281. {selectools-0.27.2 → selectools-0.29.0}/tests/test_pending_redis_smoke.py +0 -0
  282. {selectools-0.27.2 → selectools-0.29.0}/tests/test_phase1_design_patterns.py +0 -0
  283. {selectools-0.27.2 → selectools-0.29.0}/tests/test_pipeline.py +0 -0
  284. {selectools-0.27.2 → selectools-0.29.0}/tests/test_pipeline_coverage.py +0 -0
  285. {selectools-0.27.2 → selectools-0.29.0}/tests/test_policy.py +0 -0
  286. {selectools-0.27.2 → selectools-0.29.0}/tests/test_prompt.py +0 -0
  287. {selectools-0.27.2 → selectools-0.29.0}/tests/test_prompt_compression.py +0 -0
  288. {selectools-0.27.2 → selectools-0.29.0}/tests/test_property_based.py +0 -0
  289. {selectools-0.27.2 → selectools-0.29.0}/tests/test_reasoning_strategy.py +0 -0
  290. {selectools-0.27.2 → selectools-0.29.0}/tests/test_remaining_coverage.py +0 -0
  291. {selectools-0.27.2 → selectools-0.29.0}/tests/test_results.py +0 -0
  292. {selectools-0.27.2 → selectools-0.29.0}/tests/test_routing_mode.py +0 -0
  293. {selectools-0.27.2 → selectools-0.29.0}/tests/test_security.py +0 -0
  294. {selectools-0.27.2 → selectools-0.29.0}/tests/test_semantic_cache.py +0 -0
  295. {selectools-0.27.2 → selectools-0.29.0}/tests/test_serve_app_coverage.py +0 -0
  296. {selectools-0.27.2 → selectools-0.29.0}/tests/test_serve_cli.py +0 -0
  297. {selectools-0.27.2 → selectools-0.29.0}/tests/test_sessions.py +0 -0
  298. {selectools-0.27.2 → selectools-0.29.0}/tests/test_sessions_edge_cases.py +0 -0
  299. {selectools-0.27.2 → selectools-0.29.0}/tests/test_sessions_mongo.py +0 -0
  300. {selectools-0.27.2 → selectools-0.29.0}/tests/test_sessions_redis.py +0 -0
  301. {selectools-0.27.2 → selectools-0.29.0}/tests/test_simple_observer.py +0 -0
  302. {selectools-0.27.2 → selectools-0.29.0}/tests/test_simulation_evals.py +0 -0
  303. {selectools-0.27.2 → selectools-0.29.0}/tests/test_stability.py +0 -0
  304. {selectools-0.27.2 → selectools-0.29.0}/tests/test_starlette_app.py +0 -0
  305. {selectools-0.27.2 → selectools-0.29.0}/tests/test_structured.py +0 -0
  306. {selectools-0.27.2 → selectools-0.29.0}/tests/test_structured_config.py +0 -0
  307. {selectools-0.27.2 → selectools-0.29.0}/tests/test_structured_tool_results.py +0 -0
  308. {selectools-0.27.2 → selectools-0.29.0}/tests/test_summarize_on_trim.py +0 -0
  309. {selectools-0.27.2 → selectools-0.29.0}/tests/test_templates_coverage.py +0 -0
  310. {selectools-0.27.2 → selectools-0.29.0}/tests/test_terminal_actions.py +0 -0
  311. {selectools-0.27.2 → selectools-0.29.0}/tests/test_token_estimation.py +0 -0
  312. {selectools-0.27.2 → selectools-0.29.0}/tests/test_tool_caching.py +0 -0
  313. {selectools-0.27.2 → selectools-0.29.0}/tests/test_tool_result_compression.py +0 -0
  314. {selectools-0.27.2 → selectools-0.29.0}/tests/test_trace.py +0 -0
  315. {selectools-0.27.2 → selectools-0.29.0}/tests/test_trace_html.py +0 -0
  316. {selectools-0.27.2 → selectools-0.29.0}/tests/test_trace_store.py +0 -0
  317. {selectools-0.27.2 → selectools-0.29.0}/tests/test_unified_memory.py +0 -0
  318. {selectools-0.27.2 → selectools-0.29.0}/tests/test_unified_memory_config.py +0 -0
  319. {selectools-0.27.2 → selectools-0.29.0}/tests/test_v016_regression.py +0 -0
  320. {selectools-0.27.2 → selectools-0.29.0}/tests/test_v019_features.py +0 -0
  321. {selectools-0.27.2 → selectools-0.29.0}/tests/test_visual_builder.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: selectools
3
- Version: 0.27.2
3
+ Version: 0.29.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>
@@ -59,14 +59,18 @@ Provides-Extra: toolbox
59
59
  Requires-Dist: requests>=2.28.0; extra == "toolbox"
60
60
  Requires-Dist: slack-sdk>=3.27.0; extra == "toolbox"
61
61
  Requires-Dist: pdfplumber>=0.11.0; extra == "toolbox"
62
+ Requires-Dist: pytz>=2023.3; extra == "toolbox"
62
63
  Provides-Extra: aws
63
64
  Requires-Dist: boto3>=1.34.0; extra == "aws"
64
65
  Provides-Extra: browser
65
66
  Requires-Dist: playwright>=1.40.0; extra == "browser"
66
67
  Provides-Extra: mongo
67
68
  Requires-Dist: pymongo>=4.0.0; extra == "mongo"
69
+ Provides-Extra: cache
70
+ Requires-Dist: redis>=4.0.0; extra == "cache"
68
71
  Provides-Extra: evals
69
72
  Requires-Dist: pyyaml>=6.0.0; extra == "evals"
73
+ Requires-Dist: jsonschema>=4.0.0; extra == "evals"
70
74
  Provides-Extra: mcp
71
75
  Requires-Dist: mcp<2.0.0,>=1.0.0; extra == "mcp"
72
76
  Provides-Extra: litellm
@@ -77,7 +81,7 @@ Provides-Extra: supabase
77
81
  Requires-Dist: supabase>=2.0.0; extra == "supabase"
78
82
  Provides-Extra: serve
79
83
  Requires-Dist: pyyaml>=6.0.0; extra == "serve"
80
- Requires-Dist: starlette>=0.27.0; extra == "serve"
84
+ Requires-Dist: starlette<1.0,>=0.27.0; extra == "serve"
81
85
  Requires-Dist: uvicorn[standard]>=0.24.0; extra == "serve"
82
86
  Requires-Dist: httpx>=0.24.0; extra == "serve"
83
87
  Dynamic: license-file
@@ -114,6 +118,34 @@ result = AgentGraph.chain(planner, writer, reviewer).run("Write a blog post")
114
118
  # selectools serve agent.yaml
115
119
  ```
116
120
 
121
+ ## What's New in v0.29
122
+
123
+ ### v0.29.0 — Stability Promotions (v1.0 Freeze Prep)
124
+
125
+ The first beta → stable promotion pass toward the 1.0 API freeze. Additive only — promotions strengthen guarantees, nothing is removed or renamed.
126
+
127
+ - **24 mature toolbox tools promoted to `@stable`** — calculator, code (incl. `execute_shell`), db, email, GitHub, Linear, Notion, PDF, web search, and Slack tools. They're 4–15 releases old with frozen signatures.
128
+ - **`orchestration`, `patterns`, and `policy` are now stable modules** — their public surfaces (AgentGraph, the checkpoint stores, all five pattern agents, ToolPolicy) were already frozen in v0.25; this aligns the module-level promise. Also promoted `SessionSearchResult` and `PlanningConfig`.
129
+ - **Held `@beta` on purpose** — the v0.27.0 additions (recall tool, injection guardrail, cache-rate cost, Mongo/Dynamo session stores) get one more cycle, and `evals` stays a beta module while its 50 evaluator classes keep growing (its 7 core types are `@stable`).
130
+ - **New `scripts/stability_audit.py`** reports any symbol whose marker disagrees with its module promise, so promotion passes are reproducible.
131
+
132
+ See `CHANGELOG.md` for the full entry.
133
+
134
+ ## What's New in v0.28
135
+
136
+ ### v0.28.0 — Hardening Sweep: Shell Safety, Session Namespaces & Typed Public API
137
+
138
+ A full audit-driven tech-debt sweep — mostly additive and bugfix, with one called-out behavior change.
139
+
140
+ - **`execute_shell` is now a real boundary, not a best-effort filter** — it parses with `shlex` and runs with `shell=False`, so pipes, chaining (`;`, `&&`), redirection, subshells, globbing, and backgrounding can never be interpreted (closes the previously-bypassable `\n` and bare-`&` holes). **Behavior change:** commands relying on shell features now fail fast instead of running through `/bin/sh`.
141
+ - **SSRF guard extended to the headless-browser tools and the serve webhook** — `browser_scrape_page`/`browser_screenshot` and the eval-alert webhook now reject loopback/private/link-local targets; SSRF logic is consolidated into one shared validator.
142
+ - **Session namespaces fixed across all backends** — `SessionStore.branch()` gained an optional `namespace` parameter, and `list()` now returns a round-trippable storage key consistently (JSON/Redis/Mongo/DynamoDB previously returned a bare id that couldn't be reloaded for namespaced sessions).
143
+ - **Embedding providers accept `timeout` + `max_retries`** (OpenAI/Voyage/Cohere, default 60s/2) so a hung or rate-limited call can't block ingestion.
144
+ - **Typed public API** — `AgentResult.trace`/`.usage` and the ten `AgentConfig` nested-group fields are now their concrete types instead of `Any` (full autocomplete + type-checking); tightening surfaced and fixed two real bugs the `Any` had masked.
145
+ - **Packaging** — added the missing `[cache]` extra (`redis`), `pytz` to `[toolbox]`, and `jsonschema` to `[evals]`.
146
+
147
+ See `CHANGELOG.md` for the full entry (7,796 tests, 115 examples, 115 models).
148
+
117
149
  ## What's New in v0.27
118
150
 
119
151
  ### v0.27.2 — Provider Model Fix & Tool-less Agents
@@ -298,7 +330,7 @@ The first AI agent framework to ship a visual graph builder in a single `pip ins
298
330
 
299
331
  **[Try the builder in your browser →](https://selectools.dev/builder/)** — no install required.
300
332
 
301
- [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/johnnichev/selectools/blob/main/notebooks/getting_started.ipynb) [![Examples Gallery](https://img.shields.io/badge/examples-96_scripts-06b6d4)](https://selectools.dev/examples/)
333
+ [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/johnnichev/selectools/blob/main/notebooks/getting_started.ipynb) [![Examples Gallery](https://img.shields.io/badge/examples-115_scripts-06b6d4)](https://selectools.dev/examples/)
302
334
 
303
335
  ```bash
304
336
  pip install selectools
@@ -600,7 +632,7 @@ report = suite.run()
600
632
  report.to_html("report.html")
601
633
  ```
602
634
 
603
- - **50 Evaluators** — 30 deterministic + 21 LLM-as-judge
635
+ - **50 Evaluators** — 29 deterministic + 21 LLM-as-judge
604
636
  - **A/B Testing**, regression detection, snapshot testing
605
637
  - **HTML reports**, JUnit XML, CLI, GitHub Action integration
606
638
 
@@ -682,7 +714,7 @@ report.to_html("report.html")
682
714
  | **Knowledge Graph** | Relationship triple extraction with in-memory and SQLite storage and keyword-based querying. |
683
715
  | **Cross-Session Knowledge** | Daily logs + persistent facts with auto-registered `remember` + `recall` tools. |
684
716
  | **MCP Integration** | Connect to any MCP tool server (stdio + HTTP). MCPClient, MultiMCPClient, MCPServer. Circuit breaker, retry, graceful degradation. |
685
- | **Eval Framework** | 50 built-in evaluators (30 deterministic + 21 LLM-as-judge). A/B testing, regression detection, snapshot testing, HTML reports, JUnit XML, CI integration. |
717
+ | **Eval Framework** | 50 built-in evaluators (29 deterministic + 21 LLM-as-judge). A/B testing, regression detection, snapshot testing, HTML reports, JUnit XML, CI integration. |
686
718
  | **Multi-Agent Orchestration** | `AgentGraph` for directed agent graphs, `SupervisorAgent` with 4 strategies, HITL via generator nodes, parallel execution, checkpointing, subgraph composition. |
687
719
  | **Composable Pipelines** | `Pipeline` + `@step` + `|` operator + `parallel()` + `branch()` — chain agents, tools, and transforms with plain Python. |
688
720
  | **AgentObserver Protocol** | 46-event lifecycle observer with `run_id`/`call_id` correlation. Built-in `LoggingObserver` + `SimpleStepObserver`. |
@@ -729,7 +761,7 @@ report.to_html("report.html")
729
761
  - **Multi-Agent Orchestration**: `AgentGraph` with routing, parallel execution, HITL, checkpointing; `SupervisorAgent` with 4 strategies (plan_and_execute, round_robin, dynamic, magentic)
730
762
  - **Composable Pipelines**: `Pipeline` + `@step` + `|` operator + `parallel()` + `branch()` — chain agents, tools, and transforms
731
763
  - **115 Examples**: Multi-agent graphs, RAG, hybrid search, streaming, structured output, traces, batch, policy, observer, guardrails, audit, sessions (incl. Supabase), entity memory, knowledge graph, eval framework, advanced agent patterns, stability markers, HTML trace viewer, agent-as-API, A2A, routing, unified memory, scheduled agents, reasoning tools, and more
732
- - **Built-in Eval Framework**: 50 evaluators (30 deterministic + 21 LLM-as-judge), A/B testing, regression detection, HTML reports, JUnit XML, snapshot testing
764
+ - **Built-in Eval Framework**: 50 evaluators (29 deterministic + 21 LLM-as-judge), A/B testing, regression detection, HTML reports, JUnit XML, snapshot testing
733
765
  - **AgentObserver Protocol**: 46 lifecycle events with `run_id` correlation, `LoggingObserver`, `SimpleStepObserver`, OTel export
734
766
  - **7700+ Tests**: Unit, integration, regression, and E2E with real API calls
735
767
 
@@ -1357,7 +1389,7 @@ pytest tests/ -x -q # All tests
1357
1389
  pytest tests/ -k "not e2e" # Skip E2E (no API keys needed)
1358
1390
  ```
1359
1391
 
1360
- 7268 tests covering parsing, agent loop, providers, RAG pipeline, hybrid search, advanced chunking, dynamic tools, caching, streaming, guardrails, sessions, memory, eval framework, budget/cancellation, knowledge stores, orchestration, pipelines, agent patterns, stability markers, trace viewer, serve API, A2A, routing, and E2E integration with real API calls.
1392
+ 7,796 tests covering parsing, agent loop, providers, RAG pipeline, hybrid search, advanced chunking, dynamic tools, caching, streaming, guardrails, sessions, memory, eval framework, budget/cancellation, knowledge stores, orchestration, pipelines, agent patterns, stability markers, trace viewer, serve API, A2A, routing, and E2E integration with real API calls.
1361
1393
 
1362
1394
  ## License
1363
1395
 
@@ -30,6 +30,34 @@ result = AgentGraph.chain(planner, writer, reviewer).run("Write a blog post")
30
30
  # selectools serve agent.yaml
31
31
  ```
32
32
 
33
+ ## What's New in v0.29
34
+
35
+ ### v0.29.0 — Stability Promotions (v1.0 Freeze Prep)
36
+
37
+ The first beta → stable promotion pass toward the 1.0 API freeze. Additive only — promotions strengthen guarantees, nothing is removed or renamed.
38
+
39
+ - **24 mature toolbox tools promoted to `@stable`** — calculator, code (incl. `execute_shell`), db, email, GitHub, Linear, Notion, PDF, web search, and Slack tools. They're 4–15 releases old with frozen signatures.
40
+ - **`orchestration`, `patterns`, and `policy` are now stable modules** — their public surfaces (AgentGraph, the checkpoint stores, all five pattern agents, ToolPolicy) were already frozen in v0.25; this aligns the module-level promise. Also promoted `SessionSearchResult` and `PlanningConfig`.
41
+ - **Held `@beta` on purpose** — the v0.27.0 additions (recall tool, injection guardrail, cache-rate cost, Mongo/Dynamo session stores) get one more cycle, and `evals` stays a beta module while its 50 evaluator classes keep growing (its 7 core types are `@stable`).
42
+ - **New `scripts/stability_audit.py`** reports any symbol whose marker disagrees with its module promise, so promotion passes are reproducible.
43
+
44
+ See `CHANGELOG.md` for the full entry.
45
+
46
+ ## What's New in v0.28
47
+
48
+ ### v0.28.0 — Hardening Sweep: Shell Safety, Session Namespaces & Typed Public API
49
+
50
+ A full audit-driven tech-debt sweep — mostly additive and bugfix, with one called-out behavior change.
51
+
52
+ - **`execute_shell` is now a real boundary, not a best-effort filter** — it parses with `shlex` and runs with `shell=False`, so pipes, chaining (`;`, `&&`), redirection, subshells, globbing, and backgrounding can never be interpreted (closes the previously-bypassable `\n` and bare-`&` holes). **Behavior change:** commands relying on shell features now fail fast instead of running through `/bin/sh`.
53
+ - **SSRF guard extended to the headless-browser tools and the serve webhook** — `browser_scrape_page`/`browser_screenshot` and the eval-alert webhook now reject loopback/private/link-local targets; SSRF logic is consolidated into one shared validator.
54
+ - **Session namespaces fixed across all backends** — `SessionStore.branch()` gained an optional `namespace` parameter, and `list()` now returns a round-trippable storage key consistently (JSON/Redis/Mongo/DynamoDB previously returned a bare id that couldn't be reloaded for namespaced sessions).
55
+ - **Embedding providers accept `timeout` + `max_retries`** (OpenAI/Voyage/Cohere, default 60s/2) so a hung or rate-limited call can't block ingestion.
56
+ - **Typed public API** — `AgentResult.trace`/`.usage` and the ten `AgentConfig` nested-group fields are now their concrete types instead of `Any` (full autocomplete + type-checking); tightening surfaced and fixed two real bugs the `Any` had masked.
57
+ - **Packaging** — added the missing `[cache]` extra (`redis`), `pytz` to `[toolbox]`, and `jsonschema` to `[evals]`.
58
+
59
+ See `CHANGELOG.md` for the full entry (7,796 tests, 115 examples, 115 models).
60
+
33
61
  ## What's New in v0.27
34
62
 
35
63
  ### v0.27.2 — Provider Model Fix & Tool-less Agents
@@ -214,7 +242,7 @@ The first AI agent framework to ship a visual graph builder in a single `pip ins
214
242
 
215
243
  **[Try the builder in your browser →](https://selectools.dev/builder/)** — no install required.
216
244
 
217
- [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/johnnichev/selectools/blob/main/notebooks/getting_started.ipynb) [![Examples Gallery](https://img.shields.io/badge/examples-96_scripts-06b6d4)](https://selectools.dev/examples/)
245
+ [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/johnnichev/selectools/blob/main/notebooks/getting_started.ipynb) [![Examples Gallery](https://img.shields.io/badge/examples-115_scripts-06b6d4)](https://selectools.dev/examples/)
218
246
 
219
247
  ```bash
220
248
  pip install selectools
@@ -516,7 +544,7 @@ report = suite.run()
516
544
  report.to_html("report.html")
517
545
  ```
518
546
 
519
- - **50 Evaluators** — 30 deterministic + 21 LLM-as-judge
547
+ - **50 Evaluators** — 29 deterministic + 21 LLM-as-judge
520
548
  - **A/B Testing**, regression detection, snapshot testing
521
549
  - **HTML reports**, JUnit XML, CLI, GitHub Action integration
522
550
 
@@ -598,7 +626,7 @@ report.to_html("report.html")
598
626
  | **Knowledge Graph** | Relationship triple extraction with in-memory and SQLite storage and keyword-based querying. |
599
627
  | **Cross-Session Knowledge** | Daily logs + persistent facts with auto-registered `remember` + `recall` tools. |
600
628
  | **MCP Integration** | Connect to any MCP tool server (stdio + HTTP). MCPClient, MultiMCPClient, MCPServer. Circuit breaker, retry, graceful degradation. |
601
- | **Eval Framework** | 50 built-in evaluators (30 deterministic + 21 LLM-as-judge). A/B testing, regression detection, snapshot testing, HTML reports, JUnit XML, CI integration. |
629
+ | **Eval Framework** | 50 built-in evaluators (29 deterministic + 21 LLM-as-judge). A/B testing, regression detection, snapshot testing, HTML reports, JUnit XML, CI integration. |
602
630
  | **Multi-Agent Orchestration** | `AgentGraph` for directed agent graphs, `SupervisorAgent` with 4 strategies, HITL via generator nodes, parallel execution, checkpointing, subgraph composition. |
603
631
  | **Composable Pipelines** | `Pipeline` + `@step` + `|` operator + `parallel()` + `branch()` — chain agents, tools, and transforms with plain Python. |
604
632
  | **AgentObserver Protocol** | 46-event lifecycle observer with `run_id`/`call_id` correlation. Built-in `LoggingObserver` + `SimpleStepObserver`. |
@@ -645,7 +673,7 @@ report.to_html("report.html")
645
673
  - **Multi-Agent Orchestration**: `AgentGraph` with routing, parallel execution, HITL, checkpointing; `SupervisorAgent` with 4 strategies (plan_and_execute, round_robin, dynamic, magentic)
646
674
  - **Composable Pipelines**: `Pipeline` + `@step` + `|` operator + `parallel()` + `branch()` — chain agents, tools, and transforms
647
675
  - **115 Examples**: Multi-agent graphs, RAG, hybrid search, streaming, structured output, traces, batch, policy, observer, guardrails, audit, sessions (incl. Supabase), entity memory, knowledge graph, eval framework, advanced agent patterns, stability markers, HTML trace viewer, agent-as-API, A2A, routing, unified memory, scheduled agents, reasoning tools, and more
648
- - **Built-in Eval Framework**: 50 evaluators (30 deterministic + 21 LLM-as-judge), A/B testing, regression detection, HTML reports, JUnit XML, snapshot testing
676
+ - **Built-in Eval Framework**: 50 evaluators (29 deterministic + 21 LLM-as-judge), A/B testing, regression detection, HTML reports, JUnit XML, snapshot testing
649
677
  - **AgentObserver Protocol**: 46 lifecycle events with `run_id` correlation, `LoggingObserver`, `SimpleStepObserver`, OTel export
650
678
  - **7700+ Tests**: Unit, integration, regression, and E2E with real API calls
651
679
 
@@ -1273,7 +1301,7 @@ pytest tests/ -x -q # All tests
1273
1301
  pytest tests/ -k "not e2e" # Skip E2E (no API keys needed)
1274
1302
  ```
1275
1303
 
1276
- 7268 tests covering parsing, agent loop, providers, RAG pipeline, hybrid search, advanced chunking, dynamic tools, caching, streaming, guardrails, sessions, memory, eval framework, budget/cancellation, knowledge stores, orchestration, pipelines, agent patterns, stability markers, trace viewer, serve API, A2A, routing, and E2E integration with real API calls.
1304
+ 7,796 tests covering parsing, agent loop, providers, RAG pipeline, hybrid search, advanced chunking, dynamic tools, caching, streaming, guardrails, sessions, memory, eval framework, budget/cancellation, knowledge stores, orchestration, pipelines, agent patterns, stability markers, trace viewer, serve API, A2A, routing, and E2E integration with real API calls.
1277
1305
 
1278
1306
  ## License
1279
1307
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "selectools"
7
- version = "0.27.2"
7
+ version = "0.29.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.9"
@@ -79,6 +79,7 @@ toolbox = [
79
79
  "requests>=2.28.0",
80
80
  "slack-sdk>=3.27.0",
81
81
  "pdfplumber>=0.11.0",
82
+ "pytz>=2023.3",
82
83
  ]
83
84
  aws = [
84
85
  "boto3>=1.34.0",
@@ -89,8 +90,12 @@ browser = [
89
90
  mongo = [
90
91
  "pymongo>=4.0.0",
91
92
  ]
93
+ cache = [
94
+ "redis>=4.0.0",
95
+ ]
92
96
  evals = [
93
97
  "pyyaml>=6.0.0",
98
+ "jsonschema>=4.0.0",
94
99
  ]
95
100
  mcp = [
96
101
  "mcp>=1.0.0,<2.0.0",
@@ -106,7 +111,7 @@ supabase = [
106
111
  ]
107
112
  serve = [
108
113
  "pyyaml>=6.0.0",
109
- "starlette>=0.27.0",
114
+ "starlette>=0.27.0,<1.0",
110
115
  "uvicorn[standard]>=0.24.0",
111
116
  "httpx>=0.24.0",
112
117
  ]
@@ -1,6 +1,6 @@
1
1
  """Public exports for the selectools package."""
2
2
 
3
- __version__ = "0.27.2"
3
+ __version__ = "0.29.0"
4
4
 
5
5
  # Import submodules (lazy loading for optional dependencies)
6
6
  from . import embeddings, evals, guardrails, models, observe, patterns, rag, toolbox
@@ -0,0 +1,88 @@
1
+ """
2
+ Shared SSRF (server-side request forgery) URL validation.
3
+
4
+ A single source of truth for the blocked-network list and the
5
+ hostname-resolution check used by every outbound-request surface in the
6
+ library (web search/scrape tools, headless-browser tools, RAG document
7
+ loaders, and the serve webhook dispatcher).
8
+
9
+ Two entry points expose the same logic under the two contracts callers
10
+ need:
11
+
12
+ - :func:`validate_url` returns an error string (or ``None`` when safe), for
13
+ tool functions that report errors as their return value.
14
+ - :func:`check_url` raises :class:`ValueError`, for internal callers that
15
+ prefer exceptions.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import ipaddress
21
+ import socket
22
+ from typing import List, Optional, Union
23
+ from urllib.parse import urlparse
24
+
25
+ # Private / loopback / link-local networks that must never be reached from a
26
+ # user- or model-supplied URL.
27
+ _BLOCKED_NETWORKS: List[Union[ipaddress.IPv4Network, ipaddress.IPv6Network]] = [
28
+ ipaddress.ip_network("127.0.0.0/8"),
29
+ ipaddress.ip_network("10.0.0.0/8"),
30
+ ipaddress.ip_network("172.16.0.0/12"),
31
+ ipaddress.ip_network("192.168.0.0/16"),
32
+ ipaddress.ip_network("169.254.0.0/16"),
33
+ ipaddress.ip_network("::1/128"),
34
+ ipaddress.ip_network("fc00::/7"),
35
+ ipaddress.ip_network("fe80::/10"),
36
+ ]
37
+
38
+
39
+ def validate_url(url: str) -> Optional[str]:
40
+ """Validate *url* against SSRF rules.
41
+
42
+ Returns a human-readable error message if the URL uses a non-HTTP
43
+ scheme, has no hostname, targets a loopback/internal name, or resolves
44
+ to a private/reserved IP range. Returns ``None`` when the URL is safe to
45
+ request.
46
+ """
47
+ parsed = urlparse(url)
48
+
49
+ if parsed.scheme not in ("http", "https"):
50
+ return f"Error: URL scheme {parsed.scheme!r} is not allowed."
51
+
52
+ hostname = parsed.hostname
53
+ if not hostname:
54
+ return "Error: URL has no hostname."
55
+
56
+ lower_host = hostname.lower()
57
+ if lower_host in ("localhost", "0.0.0.0"): # nosec B104 — comparison, not a bind
58
+ return f"Error: Requests to {hostname!r} are blocked (loopback/internal address)."
59
+
60
+ try:
61
+ addr_infos = socket.getaddrinfo(hostname, None)
62
+ except socket.gaierror as e:
63
+ return f"Error: Could not resolve hostname {hostname!r}: {e}"
64
+
65
+ for _family, _type, _proto, _canonname, sockaddr in addr_infos:
66
+ ip = ipaddress.ip_address(sockaddr[0])
67
+ for network in _BLOCKED_NETWORKS:
68
+ if ip in network:
69
+ return (
70
+ f"Error: URL resolves to private/reserved address {ip} "
71
+ f"(network {network}). Requests to internal networks are blocked."
72
+ )
73
+
74
+ return None
75
+
76
+
77
+ def check_url(url: str) -> None:
78
+ """Raise :class:`ValueError` if *url* fails SSRF validation.
79
+
80
+ Equivalent to :func:`validate_url` but raises instead of returning the
81
+ message (with the ``"Error: "`` prefix stripped).
82
+ """
83
+ message = validate_url(url)
84
+ if message is not None:
85
+ raise ValueError(message.removeprefix("Error: "))
86
+
87
+
88
+ __all__ = ["validate_url", "check_url"]
@@ -6,7 +6,7 @@ import asyncio
6
6
  import threading
7
7
  import time
8
8
  from concurrent.futures import ThreadPoolExecutor
9
- from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, cast
9
+ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple
10
10
 
11
11
  # Module-level singleton for running sync provider calls in an async context.
12
12
  # Creating a new ThreadPoolExecutor per call (inside a retry loop) wastes
@@ -65,8 +65,15 @@ class _ProviderCallerMixin:
65
65
  temperature=self.config.temperature,
66
66
  )
67
67
  cached = self.config.cache.get(cache_key)
68
- if cached is not None:
69
- cached_msg = cast(Message, cached[0])
68
+ # Only treat a well-formed (Message, usage) pair as a cache hit;
69
+ # anything else (key collision, schema drift) falls through to a
70
+ # real provider call instead of crashing on a bad cast.
71
+ if (
72
+ isinstance(cached, (tuple, list))
73
+ and len(cached) >= 2
74
+ and isinstance(cached[0], Message)
75
+ ):
76
+ cached_msg = cached[0]
70
77
  cached_usage = cached[1]
71
78
  self.usage.add_usage(cached_usage, tool_name=None)
72
79
  if run_id:
@@ -273,8 +280,15 @@ class _ProviderCallerMixin:
273
280
  temperature=self.config.temperature,
274
281
  )
275
282
  cached = self.config.cache.get(cache_key)
276
- if cached is not None:
277
- cached_msg = cast(Message, cached[0])
283
+ # Only treat a well-formed (Message, usage) pair as a cache hit;
284
+ # anything else (key collision, schema drift) falls through to a
285
+ # real provider call instead of crashing on a bad cast.
286
+ if (
287
+ isinstance(cached, (tuple, list))
288
+ and len(cached) >= 2
289
+ and isinstance(cached[0], Message)
290
+ ):
291
+ cached_msg = cached[0]
278
292
  cached_usage = cached[1]
279
293
  self.usage.add_usage(cached_usage, tool_name=None)
280
294
  if run_id:
@@ -524,21 +538,29 @@ class _ProviderCallerMixin:
524
538
  elif isinstance(chunk, ToolCall):
525
539
  tool_calls.append(chunk)
526
540
  else:
527
- for chunk in self.provider.stream(
528
- model=self._effective_model,
529
- system_prompt=self._system_prompt,
530
- messages=self._history,
531
- tools=self.tools,
532
- temperature=self.config.temperature,
533
- max_tokens=self.config.max_tokens,
534
- timeout=self.config.request_timeout,
535
- ):
536
- if isinstance(chunk, str):
537
- if chunk:
538
- aggregated.append(chunk)
539
- if stream_handler:
540
- stream_handler(chunk)
541
- elif isinstance(chunk, ToolCall):
542
- tool_calls.append(chunk)
541
+ # Sync-generator fallback: wrap in closing() so a stream_handler
542
+ # exception or caller disconnect deterministically closes the
543
+ # provider generator (the sync analog of the aclosing path above).
544
+ from contextlib import closing
545
+
546
+ with closing(
547
+ self.provider.stream(
548
+ model=self._effective_model,
549
+ system_prompt=self._system_prompt,
550
+ messages=self._history,
551
+ tools=self.tools,
552
+ temperature=self.config.temperature,
553
+ max_tokens=self.config.max_tokens,
554
+ timeout=self.config.request_timeout,
555
+ )
556
+ ) as stream:
557
+ for chunk in stream:
558
+ if isinstance(chunk, str):
559
+ if chunk:
560
+ aggregated.append(chunk)
561
+ if stream_handler:
562
+ stream_handler(chunk)
563
+ elif isinstance(chunk, ToolCall):
564
+ tool_calls.append(chunk)
543
565
 
544
566
  return "".join(aggregated), tool_calls
@@ -107,7 +107,7 @@ class _ToolExecutorMixin:
107
107
  return False
108
108
  if tool is None or getattr(tool, "terminal", False):
109
109
  return False
110
- return len(result) > tcfg.compress_threshold
110
+ return bool(len(result) > tcfg.compress_threshold)
111
111
 
112
112
  def _stop_condition_hit(self, tool_name: str, result: str) -> bool:
113
113
  """True if config.stop_condition fires for this result (terminal path)."""
@@ -138,6 +138,7 @@ class _ToolExecutorMixin:
138
138
  tool_name,
139
139
  type(exc).__name__,
140
140
  exc,
141
+ exc_info=exc,
141
142
  )
142
143
 
143
144
  def _build_compression_request(self, tool_name: str, result: str) -> Message:
@@ -203,7 +204,7 @@ class _ToolExecutorMixin:
203
204
  def _compression_max_tokens(self) -> int:
204
205
  """Token budget for the summary: comfortably under the char threshold."""
205
206
  threshold = self.config.tool.compress_threshold
206
- return max(128, min(1000, threshold // 4))
207
+ return int(max(128, min(1000, threshold // 4)))
207
208
 
208
209
  def _compress_tool_result(
209
210
  self,
@@ -22,6 +22,18 @@ if TYPE_CHECKING:
22
22
  from ..providers.base import Provider
23
23
  from ..sessions import SessionStore
24
24
  from ..usage import AgentUsage
25
+ from .config_groups import (
26
+ BudgetConfig,
27
+ CoherenceConfig,
28
+ CompressConfig,
29
+ GuardrailsConfig,
30
+ MemoryConfig,
31
+ RetryConfig,
32
+ SessionConfig,
33
+ SummarizeConfig,
34
+ ToolConfig,
35
+ TraceConfig,
36
+ )
25
37
 
26
38
  ConfirmAction = Union[
27
39
  Callable[[str, Dict[str, Any], str], bool],
@@ -179,16 +191,17 @@ class AgentConfig:
179
191
 
180
192
  # -- Nested config groups (optional, for YAML / clean APIs) --
181
193
  # When provided, nested values take precedence over flat fields.
182
- retry: Optional[Any] = None # RetryConfig
183
- tool: Optional[Any] = None # ToolConfig
184
- coherence: Optional[Any] = None # CoherenceConfig (name collision with coherence_check)
185
- guardrail: Optional[Any] = None # GuardrailsConfig
186
- session: Optional[Any] = None # SessionConfig
187
- summarize: Optional[Any] = None # SummarizeConfig
188
- memory: Optional[Any] = None # MemoryConfig
189
- budget: Optional[Any] = None # BudgetConfig
190
- trace: Optional[Any] = None # TraceConfig
191
- compress: Optional[Any] = None # CompressConfig
194
+ # (CoherenceConfig's attribute name collides with the flat coherence_check.)
195
+ retry: Optional["RetryConfig"] = None
196
+ tool: Optional["ToolConfig"] = None
197
+ coherence: Optional["CoherenceConfig"] = None
198
+ guardrail: Optional["GuardrailsConfig"] = None
199
+ session: Optional["SessionConfig"] = None
200
+ summarize: Optional["SummarizeConfig"] = None
201
+ memory: Optional["MemoryConfig"] = None
202
+ budget: Optional["BudgetConfig"] = None
203
+ trace: Optional["TraceConfig"] = None
204
+ compress: Optional["CompressConfig"] = None
192
205
 
193
206
  # -- Planning-as-config (ROADMAP P2). See PlanningConfig below and
194
207
  # agent/_planning.py for the adapter. Self-contained: default None means
@@ -391,7 +404,7 @@ step executes and must return one of:
391
404
  """
392
405
 
393
406
 
394
- @beta
407
+ @stable
395
408
  @dataclass
396
409
  class PlanningConfig:
397
410
  """Opt-in planning for any Agent: plan -> (approve) -> execute -> synthesize.
@@ -142,16 +142,14 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
142
142
  Initialize a new Agent instance.
143
143
 
144
144
  Args:
145
- tools: List of Tool instances the agent can use (minimum 1 required).
145
+ tools: List of Tool instances the agent can use. May be empty for a
146
+ pure conversational agent (the provider is called with no tools).
146
147
  provider: LLM provider adapter. Defaults to OpenAIProvider().
147
148
  prompt_builder: Custom prompt builder. Defaults to PromptBuilder().
148
149
  parser: Custom tool call parser. Defaults to ToolCallParser().
149
150
  config: Agent configuration options. Defaults to AgentConfig().
150
151
  memory: Optional conversation memory for maintaining history across turns.
151
152
 
152
- Raises:
153
- ValueError: If tools list is empty.
154
-
155
153
  Example:
156
154
  >>> agent = Agent(
157
155
  ... tools=[search, calculator],
@@ -1078,6 +1076,10 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
1078
1076
  result = AgentResult(
1079
1077
  message=Message(role=Role.ASSISTANT, content=f"Batch error: {exc}"),
1080
1078
  )
1079
+ # No lock needed here (unlike sync batch(), which uses real OS
1080
+ # threads): merge() and the increment are synchronous with no
1081
+ # await, so under asyncio's single-threaded event loop they run
1082
+ # atomically relative to the other gathered coroutines.
1081
1083
  self.usage.merge(clone.usage)
1082
1084
  completed += 1
1083
1085
  if on_progress:
@@ -1123,6 +1125,14 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
1123
1125
  Returns:
1124
1126
  AgentResult with the final response and tool call metadata.
1125
1127
 
1128
+ Error handling:
1129
+ If the provider keeps failing after ``config.max_retries`` attempts,
1130
+ the agent does NOT raise — it returns an ``AgentResult`` whose
1131
+ ``content`` is ``"Provider error: <detail>"`` (graceful degradation),
1132
+ and the failure is recorded on ``result.trace`` with the error.
1133
+ Check ``result.trace`` (or test for the ``"Provider error:"`` prefix)
1134
+ if you need to distinguish a provider failure from a real answer.
1135
+
1126
1136
  Examples:
1127
1137
  >>> result = agent.run("What is Python?")
1128
1138
  >>> result = agent.run([Message(role=Role.USER, content="Hello")])
@@ -1214,7 +1224,7 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
1214
1224
  # agent with a tight tool-iteration budget should
1215
1225
  # still allow the full RetryConfig.max_retries
1216
1226
  # worth of structured-validation retries.
1217
- retry_budget = self.config.retry.max_retries
1227
+ retry_budget = self.config.max_retries
1218
1228
  if ctx.structured_retries < retry_budget:
1219
1229
  ctx.structured_retries += 1
1220
1230
  ctx.trace.add(
@@ -1372,7 +1382,7 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
1372
1382
  clone.analytics = None
1373
1383
  return clone
1374
1384
 
1375
- @deprecated(since="1.0.0", replacement="clone_for_isolation")
1385
+ @deprecated(since="0.25.0", replacement="clone_for_isolation")
1376
1386
  def _clone_for_isolation(self) -> "Agent":
1377
1387
  """Deprecated alias for :meth:`clone_for_isolation`."""
1378
1388
  return self.clone_for_isolation()
@@ -1651,7 +1661,7 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
1651
1661
  # agent with a tight tool-iteration budget should
1652
1662
  # still allow the full RetryConfig.max_retries
1653
1663
  # worth of structured-validation retries.
1654
- retry_budget = self.config.retry.max_retries
1664
+ retry_budget = self.config.max_retries
1655
1665
  if ctx.structured_retries < retry_budget:
1656
1666
  ctx.structured_retries += 1
1657
1667
  ctx.trace.add(
@@ -1970,7 +1980,7 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
1970
1980
  # agent with a tight tool-iteration budget should
1971
1981
  # still allow the full RetryConfig.max_retries
1972
1982
  # worth of structured-validation retries.
1973
- retry_budget = self.config.retry.max_retries
1983
+ retry_budget = self.config.max_retries
1974
1984
  if ctx.structured_retries < retry_budget:
1975
1985
  ctx.structured_retries += 1
1976
1986
  ctx.trace.add(
@@ -35,6 +35,8 @@ class AnthropicEmbeddingProvider(EmbeddingProvider):
35
35
  model: str = "voyage-3-lite",
36
36
  api_key: Optional[str] = None,
37
37
  truncate: bool = True,
38
+ timeout: float = 60.0,
39
+ max_retries: int = 2,
38
40
  ):
39
41
  """
40
42
  Initialize Anthropic/Voyage embedding provider.
@@ -43,6 +45,10 @@ class AnthropicEmbeddingProvider(EmbeddingProvider):
43
45
  model: Model name (default: voyage-3-lite)
44
46
  api_key: Voyage API key (defaults to VOYAGE_API_KEY env var)
45
47
  truncate: Whether to truncate inputs longer than context window
48
+ timeout: Per-request timeout in seconds (default: 60). Prevents a
49
+ hung embeddings call from blocking ingestion indefinitely.
50
+ max_retries: Number of automatic retries on transient errors,
51
+ handled by the Voyage SDK (default: 2).
46
52
  """
47
53
  try:
48
54
  import voyageai
@@ -52,7 +58,12 @@ class AnthropicEmbeddingProvider(EmbeddingProvider):
52
58
  "Install with: pip install voyageai"
53
59
  ) from e
54
60
 
55
- self.client = voyageai.Client(api_key=api_key)
61
+ try:
62
+ self.client = voyageai.Client(api_key=api_key, max_retries=max_retries, timeout=timeout)
63
+ except TypeError:
64
+ # Older voyageai versions lack the timeout/max_retries kwargs.
65
+ logger.debug("voyageai.Client does not accept timeout/max_retries; using defaults")
66
+ self.client = voyageai.Client(api_key=api_key)
56
67
  self.model = model
57
68
  self.truncate = truncate
58
69
  self._dimension = self._get_model_dimension()