selectools 0.27.1__tar.gz → 0.28.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.
- {selectools-0.27.1/src/selectools.egg-info → selectools-0.28.0}/PKG-INFO +30 -7
- {selectools-0.27.1 → selectools-0.28.0}/README.md +24 -5
- {selectools-0.27.1 → selectools-0.28.0}/pyproject.toml +7 -2
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/__init__.py +1 -1
- selectools-0.28.0/src/selectools/_ssrf.py +88 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/agent/_provider_caller.py +43 -21
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/agent/_tool_executor.py +3 -2
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/agent/config.py +27 -11
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/agent/core.py +46 -21
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/embeddings/anthropic.py +12 -1
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/embeddings/cohere.py +12 -1
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/embeddings/openai.py +8 -1
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/evaluators.py +1 -1
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/mcp/multi.py +7 -1
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/orchestration/graph.py +2 -4
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/orchestration/supervisor.py +3 -1
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/anthropic_provider.py +4 -4
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/loaders.py +1 -54
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/stores/chroma.py +2 -2
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/stores/memory.py +10 -11
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/vector_store.py +7 -5
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/api.py +7 -5
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/app.py +34 -12
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/sessions.py +60 -34
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/templates/__init__.py +2 -9
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/browser_tools.py +7 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/code_tools.py +53 -16
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/image_tools.py +3 -1
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/search_tools.py +1 -51
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/types.py +17 -4
- {selectools-0.27.1 → selectools-0.28.0/src/selectools.egg-info}/PKG-INFO +30 -7
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools.egg-info/SOURCES.txt +2 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools.egg-info/requires.txt +6 -1
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_conversation_branching.py +40 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_guardrails_injection.py +18 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_model_switching.py +46 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_scheduler.py +31 -6
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_sessions_dynamodb.py +32 -0
- selectools-0.28.0/tests/test_ssrf.py +63 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_yaml_config.py +4 -2
- {selectools-0.27.1 → selectools-0.28.0}/LICENSE +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/setup.cfg +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/_async_utils.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/_json_utils.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/a2a/__init__.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/a2a/client.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/a2a/server.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/a2a/types.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/agent/__init__.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/agent/_lifecycle.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/agent/_memory_manager.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/agent/_planning.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/agent/config_groups.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/analytics.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/audit.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/cache.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/cache_redis.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/cache_semantic.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/cancellation.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/checkpoint_postgres.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/coherence.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/compose.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/embeddings/__init__.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/embeddings/gemini.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/embeddings/provider.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/entity_memory.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/env.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/__init__.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/__main__.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/badge.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/dataset.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/generator.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/history.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/html.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/junit.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/llm_evaluators.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/pairwise.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/regression.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/report.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/serve.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/snapshot.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/suite.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/templates.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/types.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/exceptions.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/guardrails/__init__.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/guardrails/base.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/guardrails/format.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/guardrails/injection.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/guardrails/length.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/guardrails/pii.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/guardrails/pipeline.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/guardrails/topic.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/guardrails/toxicity.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/knowledge.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/knowledge_backends.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/knowledge_graph.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/knowledge_sanitizers.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/knowledge_store_redis.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/knowledge_store_supabase.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/loop_detection.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/mcp/__init__.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/mcp/_loop.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/mcp/bridge.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/mcp/client.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/mcp/config.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/mcp/server.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/memory.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/models.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/observe/__init__.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/observe/langfuse.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/observe/otel.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/observe/trace_store.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/observer.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/orchestration/__init__.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/orchestration/checkpoint.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/orchestration/node.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/orchestration/state.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/parser.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/patterns/__init__.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/patterns/debate.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/patterns/plan_and_execute.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/patterns/reflective.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/patterns/team_lead.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/pending.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/pipeline.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/policy.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/pricing.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/prompt.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/__init__.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/_openai_compat.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/azure_openai_provider.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/base.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/fallback.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/gemini_provider.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/litellm_provider.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/ollama_provider.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/openai_provider.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/router.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/stubs.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/__init__.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/bm25.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/chunking.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/hybrid.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/reranker.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/stores/__init__.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/stores/faiss.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/stores/pgvector.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/stores/pinecone.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/stores/qdrant.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/stores/sqlite.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/tools.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/results.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/scheduler.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/security.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/__init__.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/_starlette_app.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/_static/builder.css +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/_static/builder.html +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/_static/builder.js +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/builder.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/cli.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/models.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/playground.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/stability.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/structured.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/templates/code_reviewer.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/templates/customer_support.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/templates/data_analyst.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/templates/rag_chatbot.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/templates/research_assistant.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/token_estimation.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/__init__.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/calculator_tools.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/data_tools.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/datetime_tools.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/db_tools.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/discord_tools.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/email_tools.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/file_tools.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/github_tools.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/linear_tools.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/memory_tools.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/notion_tools.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/pdf_tools.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/reasoning_tools.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/s3_tools.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/slack_tools.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/text_tools.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/web_tools.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/tools/__init__.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/tools/base.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/tools/decorators.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/tools/loader.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/tools/registry.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/trace.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/unified_memory.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools/usage.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools.egg-info/dependency_links.txt +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools.egg-info/entry_points.txt +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/src/selectools.egg-info/top_level.txt +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_a2a.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_agent_api.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_agent_hitl.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_approval_gate.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_architecture.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_async_observers.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_audit.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_bake_window_hunt.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_budget.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_bug_hunt_batch1_core.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_bug_hunt_batch1_security.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_bug_hunt_batch1_tools.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_bug_hunt_regression.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_cache.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_cache_redis.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_cancellation.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_checkpoint_postgres.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_coherence.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_concurrency_smoke.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_consolidation_regression.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_coverage_orchestration.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_document_loaders_extended.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_e2e_langfuse_observer.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_e2e_multimodal.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_e2e_otel_observer.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_e2e_v0_21_0_apps.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_e2e_v0_21_0_simulations.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_entity_memory.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_env.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_advanced.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_cli.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_e2e.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_final.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_hardening.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_html_report.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_new_evaluators.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_ralph_bugs.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_release.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_serve.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_v017_features.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_v0191.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_features_in_graph.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_final_coverage_a.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_final_coverage_b.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_guardrails.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_hardening.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge_backend.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge_backend_redis.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge_backend_supabase.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge_graph.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge_sanitizers.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge_store_redis.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge_store_supabase.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge_stores.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge_stores_error_handling.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_langfuse_observer.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_loop_detection.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_mcp.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_mcp_coverage.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_memory.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_memory_async.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_memory_boundary.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_memory_integration.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_multi_agent_edge_cases.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_multimodal.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_orchestration_checkpoint.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_orchestration_e2e.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_orchestration_evals.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_orchestration_graph.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_orchestration_integration.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_orchestration_primitives.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_orchestration_supervisor.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_otel_observer.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_parser.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_patterns.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_pending.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_pending_redis_smoke.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_phase1_design_patterns.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_pipeline.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_pipeline_coverage.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_policy.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_prompt.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_prompt_compression.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_property_based.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_reasoning_strategy.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_remaining_coverage.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_results.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_routing_mode.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_security.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_semantic_cache.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_serve_app_coverage.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_serve_cli.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_sessions.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_sessions_edge_cases.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_sessions_mongo.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_sessions_redis.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_sessions_supabase.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_simple_observer.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_simulation_evals.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_stability.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_starlette_app.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_structured.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_structured_config.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_structured_tool_results.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_summarize_on_trim.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_templates_coverage.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_terminal_actions.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_token_estimation.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_tool_caching.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_tool_result_compression.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_trace.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_trace_html.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_trace_store.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_unified_memory.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_unified_memory_config.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_v016_regression.py +0 -0
- {selectools-0.27.1 → selectools-0.28.0}/tests/test_v019_features.py +0 -0
- {selectools-0.27.1 → selectools-0.28.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.
|
|
3
|
+
Version: 0.28.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
|
|
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,8 +118,27 @@ 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.28
|
|
122
|
+
|
|
123
|
+
### v0.28.0 — Hardening Sweep: Shell Safety, Session Namespaces & Typed Public API
|
|
124
|
+
|
|
125
|
+
A full audit-driven tech-debt sweep — mostly additive and bugfix, with one called-out behavior change.
|
|
126
|
+
|
|
127
|
+
- **`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`.
|
|
128
|
+
- **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.
|
|
129
|
+
- **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).
|
|
130
|
+
- **Embedding providers accept `timeout` + `max_retries`** (OpenAI/Voyage/Cohere, default 60s/2) so a hung or rate-limited call can't block ingestion.
|
|
131
|
+
- **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.
|
|
132
|
+
- **Packaging** — added the missing `[cache]` extra (`redis`), `pytz` to `[toolbox]`, and `jsonschema` to `[evals]`.
|
|
133
|
+
|
|
134
|
+
See `CHANGELOG.md` for the full entry (7,796 tests, 115 examples, 115 models).
|
|
135
|
+
|
|
117
136
|
## What's New in v0.27
|
|
118
137
|
|
|
138
|
+
### v0.27.2 — Provider Model Fix & Tool-less Agents
|
|
139
|
+
|
|
140
|
+
Fixes a P0: the agent ignored every provider's default model and sent `gpt-5-mini` to all of them, so **any non-OpenAI provider (Anthropic, Gemini, Ollama, LiteLLM) 404'd on every request**. `AgentConfig.model` now defaults to `None` and resolves to the provider's own default. Also: `Agent(tools=[])` is now valid (pure conversational agents), and the structured-output (`result.parsed` vs `.content`) and streaming (accumulate `StreamChunk` deltas, not the terminal `AgentResult`) sharp edges are documented.
|
|
141
|
+
|
|
119
142
|
### v0.27.1 — Bug-Hunt Patch
|
|
120
143
|
|
|
121
144
|
An adversarial bug hunt of the v0.27.0 surface fixed 10 confirmed bugs (no API changes, no breaking changes). Highlights: scheduled agents recorded the `AgentResult` repr instead of the answer text; `PromptInjectionGuardrail` blocked benign requests ("pretend you are a pirate", "show the rules"); `s3_get_object` leaked its HTTP connection; and unified memory persisted un-redacted input in async mode. See `CHANGELOG.md` for the full list.
|
|
@@ -294,7 +317,7 @@ The first AI agent framework to ship a visual graph builder in a single `pip ins
|
|
|
294
317
|
|
|
295
318
|
**[Try the builder in your browser →](https://selectools.dev/builder/)** — no install required.
|
|
296
319
|
|
|
297
|
-
[](https://colab.research.google.com/github/johnnichev/selectools/blob/main/notebooks/getting_started.ipynb) [](https://colab.research.google.com/github/johnnichev/selectools/blob/main/notebooks/getting_started.ipynb) [](https://selectools.dev/examples/)
|
|
298
321
|
|
|
299
322
|
```bash
|
|
300
323
|
pip install selectools
|
|
@@ -596,7 +619,7 @@ report = suite.run()
|
|
|
596
619
|
report.to_html("report.html")
|
|
597
620
|
```
|
|
598
621
|
|
|
599
|
-
- **50 Evaluators** —
|
|
622
|
+
- **50 Evaluators** — 29 deterministic + 21 LLM-as-judge
|
|
600
623
|
- **A/B Testing**, regression detection, snapshot testing
|
|
601
624
|
- **HTML reports**, JUnit XML, CLI, GitHub Action integration
|
|
602
625
|
|
|
@@ -678,7 +701,7 @@ report.to_html("report.html")
|
|
|
678
701
|
| **Knowledge Graph** | Relationship triple extraction with in-memory and SQLite storage and keyword-based querying. |
|
|
679
702
|
| **Cross-Session Knowledge** | Daily logs + persistent facts with auto-registered `remember` + `recall` tools. |
|
|
680
703
|
| **MCP Integration** | Connect to any MCP tool server (stdio + HTTP). MCPClient, MultiMCPClient, MCPServer. Circuit breaker, retry, graceful degradation. |
|
|
681
|
-
| **Eval Framework** | 50 built-in evaluators (
|
|
704
|
+
| **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. |
|
|
682
705
|
| **Multi-Agent Orchestration** | `AgentGraph` for directed agent graphs, `SupervisorAgent` with 4 strategies, HITL via generator nodes, parallel execution, checkpointing, subgraph composition. |
|
|
683
706
|
| **Composable Pipelines** | `Pipeline` + `@step` + `|` operator + `parallel()` + `branch()` — chain agents, tools, and transforms with plain Python. |
|
|
684
707
|
| **AgentObserver Protocol** | 46-event lifecycle observer with `run_id`/`call_id` correlation. Built-in `LoggingObserver` + `SimpleStepObserver`. |
|
|
@@ -725,7 +748,7 @@ report.to_html("report.html")
|
|
|
725
748
|
- **Multi-Agent Orchestration**: `AgentGraph` with routing, parallel execution, HITL, checkpointing; `SupervisorAgent` with 4 strategies (plan_and_execute, round_robin, dynamic, magentic)
|
|
726
749
|
- **Composable Pipelines**: `Pipeline` + `@step` + `|` operator + `parallel()` + `branch()` — chain agents, tools, and transforms
|
|
727
750
|
- **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
|
|
728
|
-
- **Built-in Eval Framework**: 50 evaluators (
|
|
751
|
+
- **Built-in Eval Framework**: 50 evaluators (29 deterministic + 21 LLM-as-judge), A/B testing, regression detection, HTML reports, JUnit XML, snapshot testing
|
|
729
752
|
- **AgentObserver Protocol**: 46 lifecycle events with `run_id` correlation, `LoggingObserver`, `SimpleStepObserver`, OTel export
|
|
730
753
|
- **7700+ Tests**: Unit, integration, regression, and E2E with real API calls
|
|
731
754
|
|
|
@@ -1353,7 +1376,7 @@ pytest tests/ -x -q # All tests
|
|
|
1353
1376
|
pytest tests/ -k "not e2e" # Skip E2E (no API keys needed)
|
|
1354
1377
|
```
|
|
1355
1378
|
|
|
1356
|
-
|
|
1379
|
+
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.
|
|
1357
1380
|
|
|
1358
1381
|
## License
|
|
1359
1382
|
|
|
@@ -30,8 +30,27 @@ 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.28
|
|
34
|
+
|
|
35
|
+
### v0.28.0 — Hardening Sweep: Shell Safety, Session Namespaces & Typed Public API
|
|
36
|
+
|
|
37
|
+
A full audit-driven tech-debt sweep — mostly additive and bugfix, with one called-out behavior change.
|
|
38
|
+
|
|
39
|
+
- **`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`.
|
|
40
|
+
- **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.
|
|
41
|
+
- **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).
|
|
42
|
+
- **Embedding providers accept `timeout` + `max_retries`** (OpenAI/Voyage/Cohere, default 60s/2) so a hung or rate-limited call can't block ingestion.
|
|
43
|
+
- **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.
|
|
44
|
+
- **Packaging** — added the missing `[cache]` extra (`redis`), `pytz` to `[toolbox]`, and `jsonschema` to `[evals]`.
|
|
45
|
+
|
|
46
|
+
See `CHANGELOG.md` for the full entry (7,796 tests, 115 examples, 115 models).
|
|
47
|
+
|
|
33
48
|
## What's New in v0.27
|
|
34
49
|
|
|
50
|
+
### v0.27.2 — Provider Model Fix & Tool-less Agents
|
|
51
|
+
|
|
52
|
+
Fixes a P0: the agent ignored every provider's default model and sent `gpt-5-mini` to all of them, so **any non-OpenAI provider (Anthropic, Gemini, Ollama, LiteLLM) 404'd on every request**. `AgentConfig.model` now defaults to `None` and resolves to the provider's own default. Also: `Agent(tools=[])` is now valid (pure conversational agents), and the structured-output (`result.parsed` vs `.content`) and streaming (accumulate `StreamChunk` deltas, not the terminal `AgentResult`) sharp edges are documented.
|
|
53
|
+
|
|
35
54
|
### v0.27.1 — Bug-Hunt Patch
|
|
36
55
|
|
|
37
56
|
An adversarial bug hunt of the v0.27.0 surface fixed 10 confirmed bugs (no API changes, no breaking changes). Highlights: scheduled agents recorded the `AgentResult` repr instead of the answer text; `PromptInjectionGuardrail` blocked benign requests ("pretend you are a pirate", "show the rules"); `s3_get_object` leaked its HTTP connection; and unified memory persisted un-redacted input in async mode. See `CHANGELOG.md` for the full list.
|
|
@@ -210,7 +229,7 @@ The first AI agent framework to ship a visual graph builder in a single `pip ins
|
|
|
210
229
|
|
|
211
230
|
**[Try the builder in your browser →](https://selectools.dev/builder/)** — no install required.
|
|
212
231
|
|
|
213
|
-
[](https://colab.research.google.com/github/johnnichev/selectools/blob/main/notebooks/getting_started.ipynb) [](https://colab.research.google.com/github/johnnichev/selectools/blob/main/notebooks/getting_started.ipynb) [](https://selectools.dev/examples/)
|
|
214
233
|
|
|
215
234
|
```bash
|
|
216
235
|
pip install selectools
|
|
@@ -512,7 +531,7 @@ report = suite.run()
|
|
|
512
531
|
report.to_html("report.html")
|
|
513
532
|
```
|
|
514
533
|
|
|
515
|
-
- **50 Evaluators** —
|
|
534
|
+
- **50 Evaluators** — 29 deterministic + 21 LLM-as-judge
|
|
516
535
|
- **A/B Testing**, regression detection, snapshot testing
|
|
517
536
|
- **HTML reports**, JUnit XML, CLI, GitHub Action integration
|
|
518
537
|
|
|
@@ -594,7 +613,7 @@ report.to_html("report.html")
|
|
|
594
613
|
| **Knowledge Graph** | Relationship triple extraction with in-memory and SQLite storage and keyword-based querying. |
|
|
595
614
|
| **Cross-Session Knowledge** | Daily logs + persistent facts with auto-registered `remember` + `recall` tools. |
|
|
596
615
|
| **MCP Integration** | Connect to any MCP tool server (stdio + HTTP). MCPClient, MultiMCPClient, MCPServer. Circuit breaker, retry, graceful degradation. |
|
|
597
|
-
| **Eval Framework** | 50 built-in evaluators (
|
|
616
|
+
| **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. |
|
|
598
617
|
| **Multi-Agent Orchestration** | `AgentGraph` for directed agent graphs, `SupervisorAgent` with 4 strategies, HITL via generator nodes, parallel execution, checkpointing, subgraph composition. |
|
|
599
618
|
| **Composable Pipelines** | `Pipeline` + `@step` + `|` operator + `parallel()` + `branch()` — chain agents, tools, and transforms with plain Python. |
|
|
600
619
|
| **AgentObserver Protocol** | 46-event lifecycle observer with `run_id`/`call_id` correlation. Built-in `LoggingObserver` + `SimpleStepObserver`. |
|
|
@@ -641,7 +660,7 @@ report.to_html("report.html")
|
|
|
641
660
|
- **Multi-Agent Orchestration**: `AgentGraph` with routing, parallel execution, HITL, checkpointing; `SupervisorAgent` with 4 strategies (plan_and_execute, round_robin, dynamic, magentic)
|
|
642
661
|
- **Composable Pipelines**: `Pipeline` + `@step` + `|` operator + `parallel()` + `branch()` — chain agents, tools, and transforms
|
|
643
662
|
- **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
|
|
644
|
-
- **Built-in Eval Framework**: 50 evaluators (
|
|
663
|
+
- **Built-in Eval Framework**: 50 evaluators (29 deterministic + 21 LLM-as-judge), A/B testing, regression detection, HTML reports, JUnit XML, snapshot testing
|
|
645
664
|
- **AgentObserver Protocol**: 46 lifecycle events with `run_id` correlation, `LoggingObserver`, `SimpleStepObserver`, OTel export
|
|
646
665
|
- **7700+ Tests**: Unit, integration, regression, and E2E with real API calls
|
|
647
666
|
|
|
@@ -1269,7 +1288,7 @@ pytest tests/ -x -q # All tests
|
|
|
1269
1288
|
pytest tests/ -k "not e2e" # Skip E2E (no API keys needed)
|
|
1270
1289
|
```
|
|
1271
1290
|
|
|
1272
|
-
|
|
1291
|
+
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.
|
|
1273
1292
|
|
|
1274
1293
|
## License
|
|
1275
1294
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "selectools"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.28.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
|
]
|
|
@@ -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
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
277
|
-
|
|
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
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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],
|
|
@@ -123,7 +135,10 @@ class AgentConfig:
|
|
|
123
135
|
"""
|
|
124
136
|
|
|
125
137
|
name: str = "agent"
|
|
126
|
-
|
|
138
|
+
# None means "use the provider's own default_model". A hardcoded default
|
|
139
|
+
# here would be sent to EVERY provider — e.g. an OpenAI model id reaching
|
|
140
|
+
# the Anthropic API and 404-ing on every call.
|
|
141
|
+
model: Optional[str] = None
|
|
127
142
|
temperature: float = 0.0
|
|
128
143
|
max_tokens: int = 1000
|
|
129
144
|
max_iterations: int = 6
|
|
@@ -176,16 +191,17 @@ class AgentConfig:
|
|
|
176
191
|
|
|
177
192
|
# -- Nested config groups (optional, for YAML / clean APIs) --
|
|
178
193
|
# When provided, nested values take precedence over flat fields.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
|
189
205
|
|
|
190
206
|
# -- Planning-as-config (ROADMAP P2). See PlanningConfig below and
|
|
191
207
|
# agent/_planning.py for the adapter. Self-contained: default None means
|
|
@@ -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
|
|
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],
|
|
@@ -160,11 +158,10 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
|
|
|
160
158
|
... memory=ConversationMemory(max_messages=20)
|
|
161
159
|
... )
|
|
162
160
|
"""
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
self.
|
|
167
|
-
self._tools_by_name = {tool.name: tool for tool in tools}
|
|
161
|
+
# An empty tool list is valid: a pure conversational agent (no tools)
|
|
162
|
+
# is a normal use case — the provider is simply called with no tools.
|
|
163
|
+
self.tools = tools or []
|
|
164
|
+
self._tools_by_name = {tool.name: tool for tool in self.tools}
|
|
168
165
|
self.provider = provider or OpenAIProvider()
|
|
169
166
|
strategy = config.reasoning_strategy if config else None
|
|
170
167
|
if prompt_builder:
|
|
@@ -248,10 +245,16 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
|
|
|
248
245
|
def _effective_model(self) -> str:
|
|
249
246
|
"""The model to use for the current iteration.
|
|
250
247
|
|
|
251
|
-
|
|
252
|
-
|
|
248
|
+
Resolution order: a ``model_selector`` override, then an explicit
|
|
249
|
+
``config.model``, then the provider's own ``default_model``. Without the
|
|
250
|
+
provider fallback a non-OpenAI provider would receive the OpenAI default
|
|
251
|
+
and 404. The final ``"gpt-5-mini"`` only applies to providers that expose
|
|
252
|
+
no default (e.g. the LocalProvider stub, which ignores the model).
|
|
253
253
|
"""
|
|
254
|
-
|
|
254
|
+
override = getattr(self, "_current_model", None) or self.config.model
|
|
255
|
+
if override:
|
|
256
|
+
return override
|
|
257
|
+
return getattr(self.provider, "default_model", None) or "gpt-5-mini"
|
|
255
258
|
|
|
256
259
|
def __call__(
|
|
257
260
|
self,
|
|
@@ -316,14 +319,11 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
|
|
|
316
319
|
|
|
317
320
|
Raises:
|
|
318
321
|
KeyError: If no tool with that name exists.
|
|
319
|
-
ValueError: If removing would leave the agent with zero tools.
|
|
320
322
|
"""
|
|
321
323
|
if tool_name not in self._tools_by_name:
|
|
322
324
|
raise KeyError(
|
|
323
325
|
f"Tool '{tool_name}' not found. Available: {', '.join(self._tools_by_name.keys())}"
|
|
324
326
|
)
|
|
325
|
-
if len(self.tools) <= 1:
|
|
326
|
-
raise ValueError("Agent requires at least one tool.")
|
|
327
327
|
|
|
328
328
|
removed = self._tools_by_name.pop(tool_name)
|
|
329
329
|
self.tools = [t for t in self.tools if t.name != tool_name]
|
|
@@ -1076,6 +1076,10 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
|
|
|
1076
1076
|
result = AgentResult(
|
|
1077
1077
|
message=Message(role=Role.ASSISTANT, content=f"Batch error: {exc}"),
|
|
1078
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.
|
|
1079
1083
|
self.usage.merge(clone.usage)
|
|
1080
1084
|
completed += 1
|
|
1081
1085
|
if on_progress:
|
|
@@ -1121,6 +1125,14 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
|
|
|
1121
1125
|
Returns:
|
|
1122
1126
|
AgentResult with the final response and tool call metadata.
|
|
1123
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
|
+
|
|
1124
1136
|
Examples:
|
|
1125
1137
|
>>> result = agent.run("What is Python?")
|
|
1126
1138
|
>>> result = agent.run([Message(role=Role.USER, content="Hello")])
|
|
@@ -1212,7 +1224,7 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
|
|
|
1212
1224
|
# agent with a tight tool-iteration budget should
|
|
1213
1225
|
# still allow the full RetryConfig.max_retries
|
|
1214
1226
|
# worth of structured-validation retries.
|
|
1215
|
-
retry_budget = self.config.
|
|
1227
|
+
retry_budget = self.config.max_retries
|
|
1216
1228
|
if ctx.structured_retries < retry_budget:
|
|
1217
1229
|
ctx.structured_retries += 1
|
|
1218
1230
|
ctx.trace.add(
|
|
@@ -1370,7 +1382,7 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
|
|
|
1370
1382
|
clone.analytics = None
|
|
1371
1383
|
return clone
|
|
1372
1384
|
|
|
1373
|
-
@deprecated(since="
|
|
1385
|
+
@deprecated(since="0.25.0", replacement="clone_for_isolation")
|
|
1374
1386
|
def _clone_for_isolation(self) -> "Agent":
|
|
1375
1387
|
"""Deprecated alias for :meth:`clone_for_isolation`."""
|
|
1376
1388
|
return self.clone_for_isolation()
|
|
@@ -1391,8 +1403,21 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
|
|
|
1391
1403
|
parent_run_id: Optional run-ID of a parent agent for trace linking.
|
|
1392
1404
|
|
|
1393
1405
|
Yields:
|
|
1394
|
-
StreamChunk:
|
|
1395
|
-
AgentResult:
|
|
1406
|
+
StreamChunk: incremental content deltas (and tool-call chunks).
|
|
1407
|
+
AgentResult: the final, complete result — yielded once at the very
|
|
1408
|
+
end. Its ``.content`` is the WHOLE answer, not a delta.
|
|
1409
|
+
|
|
1410
|
+
Consuming the stream — accumulate the ``StreamChunk`` deltas and treat
|
|
1411
|
+
the terminal ``AgentResult`` separately. Do NOT add the final
|
|
1412
|
+
``AgentResult.content`` to the accumulated deltas, or you will get the
|
|
1413
|
+
answer twice::
|
|
1414
|
+
|
|
1415
|
+
text = ""
|
|
1416
|
+
async for chunk in agent.astream("..."):
|
|
1417
|
+
if isinstance(chunk, StreamChunk):
|
|
1418
|
+
text += chunk.content or "" # deltas only
|
|
1419
|
+
else: # AgentResult (terminal)
|
|
1420
|
+
result = chunk # full answer + metadata
|
|
1396
1421
|
"""
|
|
1397
1422
|
messages = self._normalize_messages(messages)
|
|
1398
1423
|
|
|
@@ -1636,7 +1661,7 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
|
|
|
1636
1661
|
# agent with a tight tool-iteration budget should
|
|
1637
1662
|
# still allow the full RetryConfig.max_retries
|
|
1638
1663
|
# worth of structured-validation retries.
|
|
1639
|
-
retry_budget = self.config.
|
|
1664
|
+
retry_budget = self.config.max_retries
|
|
1640
1665
|
if ctx.structured_retries < retry_budget:
|
|
1641
1666
|
ctx.structured_retries += 1
|
|
1642
1667
|
ctx.trace.add(
|
|
@@ -1955,7 +1980,7 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
|
|
|
1955
1980
|
# agent with a tight tool-iteration budget should
|
|
1956
1981
|
# still allow the full RetryConfig.max_retries
|
|
1957
1982
|
# worth of structured-validation retries.
|
|
1958
|
-
retry_budget = self.config.
|
|
1983
|
+
retry_budget = self.config.max_retries
|
|
1959
1984
|
if ctx.structured_retries < retry_budget:
|
|
1960
1985
|
ctx.structured_retries += 1
|
|
1961
1986
|
ctx.trace.add(
|