calfkit 0.2.4__tar.gz → 0.2.6__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.
- {calfkit-0.2.4 → calfkit-0.2.6}/.github/workflows/release.yml +1 -1
- {calfkit-0.2.4 → calfkit-0.2.6}/.github/workflows/security.yml +1 -1
- calfkit-0.2.6/.release-please-manifest.json +3 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/CHANGELOG.md +14 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/PKG-INFO +43 -1
- {calfkit-0.2.4 → calfkit-0.2.6}/README.md +42 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/__init__.py +2 -1
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/client/base.py +3 -1
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/client/client.py +14 -2
- calfkit-0.2.6/calfkit/models/node_schema.py +21 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/models/session_context.py +2 -1
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/models/state.py +9 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/nodes/__init__.py +2 -1
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/nodes/agent.py +12 -3
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/nodes/base.py +92 -19
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/nodes/tool.py +27 -16
- {calfkit-0.2.4 → calfkit-0.2.6}/pyproject.toml +1 -1
- calfkit-0.2.6/tests/conftest.py +394 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/tests/providers.py +12 -1
- calfkit-0.2.6/tests/test_gates.py +391 -0
- calfkit-0.2.6/tests/test_overrides.py +55 -0
- calfkit-0.2.6/tests/test_serializable.py +31 -0
- calfkit-0.2.4/.release-please-manifest.json +0 -3
- calfkit-0.2.4/tests/conftest.py +0 -157
- {calfkit-0.2.4 → calfkit-0.2.6}/.github/CODEOWNERS +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/.github/dependabot.yml +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/.github/workflows/build.yml +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/.github/workflows/code-checks.yml +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/.github/workflows/test.yml +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/.gitignore +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/LICENSE +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/Makefile +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_types.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/LICENSE +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/__main__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_a2a.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_agent_graph.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_cli/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_cli/web.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_function_schema.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_griffe.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_instrumentation.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_json_schema.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_mcp.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_otel_messages.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_output.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_parts_manager.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_run_context.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_system_prompt.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_thinking_part.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_tool_manager.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_utils.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ag_ui.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/agent/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/agent/abstract.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/agent/wrapper.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/builtin_tools.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/common_tools/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/common_tools/duckduckgo.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/common_tools/exa.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/common_tools/tavily.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/direct.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_agent.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_fastmcp_toolset.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_mcp.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_mcp_server.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_model.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_utils.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_agent.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_cache_policies.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_function_toolset.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_mcp_server.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_model.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_toolset.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_types.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_agent.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_dynamic_toolset.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_fastmcp_toolset.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_function_toolset.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_logfire.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_mcp.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_mcp_server.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_model.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_run_context.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_toolset.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_workflow.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/base.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/cohere.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/google.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/instrumented.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/openai.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/result.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/sentence_transformers.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/settings.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/test.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/voyageai.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/wrapper.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/exceptions.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ext/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ext/aci.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ext/langchain.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/format_prompt.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/mcp.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/messages.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/anthropic.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/bedrock.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/cerebras.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/cohere.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/fallback.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/function.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/gemini.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/google.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/groq.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/huggingface.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/instrumented.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/mcp_sampling.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/mistral.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/openai.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/openrouter.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/outlines.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/test.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/wrapper.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/xai.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/output.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/amazon.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/anthropic.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/cohere.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/deepseek.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/google.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/grok.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/groq.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/harmony.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/meta.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/mistral.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/moonshotai.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/openai.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/qwen.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/zai.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/alibaba.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/anthropic.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/azure.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/bedrock.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/cerebras.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/cohere.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/deepseek.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/fireworks.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/gateway.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/github.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/google.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/google_gla.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/google_vertex.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/grok.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/groq.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/heroku.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/huggingface.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/litellm.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/mistral.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/moonshotai.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/nebius.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/ollama.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/openai.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/openrouter.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/outlines.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/ovhcloud.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/sambanova.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/sentence_transformers.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/together.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/vercel.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/voyageai.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/xai.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/py.typed +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/result.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/retries.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/run.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/settings.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/tools.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/_dynamic.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/abstract.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/approval_required.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/combined.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/external.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/fastmcp.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/filtered.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/function.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/prefixed.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/prepared.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/renamed.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/wrapper.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/_adapter.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/_event_stream.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/_messages_builder.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/_web/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/_web/api.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/_web/app.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/ag_ui/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/ag_ui/_adapter.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/ag_ui/_event_stream.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/ag_ui/app.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/_adapter.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/_event_stream.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/_models.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/_utils.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/request_types.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/response_types.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/usage.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/vendor.txt +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/client/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/client/deserialize.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/client/invocation_handle.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/client/middleware.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/client/node_result.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/client/reply_dispatcher.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/exceptions.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/models/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/models/actions.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/models/envelope.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/models/payload.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/models/tool_context.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/nodes/node.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/providers/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/providers/pydantic_ai/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/providers/pydantic_ai/anthropic.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/providers/pydantic_ai/model_client.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/providers/pydantic_ai/openai.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/worker/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/worker/worker.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/worker/worker_config.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/codecov.yml +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/examples/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/examples/deprecated/agent_dispatcher.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/examples/deprecated/chat_node.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/examples/deprecated/chat_repl_cli.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/examples/deprecated/invoke_agent.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/examples/deprecated/router_node.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/examples/deprecated/tool_nodes.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/examples/quickstart/agent_service.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/examples/quickstart/invoke.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/examples/quickstart/weather_tool.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/examples/rpc_worker.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/release-please-config.json +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/tests/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/tests/integration/__init__.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/tests/integration/test_agent_output_types.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/tests/integration/test_agent_workers.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/tests/test_concurrent_tool_calls.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/tests/test_instructions.py +0 -0
- {calfkit-0.2.4 → calfkit-0.2.6}/tests/utils.py +0 -0
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.6](https://github.com/calf-ai/calfkit-sdk/compare/v0.2.5...v0.2.6) (2026-05-17)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add gates to BaseNodeDef for pre-run event filtering ([#127](https://github.com/calf-ai/calfkit-sdk/issues/127)) ([9fbdbd0](https://github.com/calf-ai/calfkit-sdk/commit/9fbdbd072a4de4335c3774efa8775c0e8b43b2bd))
|
|
9
|
+
|
|
10
|
+
## [0.2.5](https://github.com/calf-ai/calfkit-sdk/compare/v0.2.4...v0.2.5) (2026-04-11)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* add serializable node schemas and runtime tool overrides ([#121](https://github.com/calf-ai/calfkit-sdk/issues/121)) ([e464c08](https://github.com/calf-ai/calfkit-sdk/commit/e464c08e4f179e0cd622789a067718fdbd043c1f))
|
|
16
|
+
|
|
3
17
|
## [0.2.4](https://github.com/calf-ai/calfkit-sdk/compare/v0.2.3...v0.2.4) (2026-04-08)
|
|
4
18
|
|
|
5
19
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: calfkit
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: Build AI workflows and agents as fully-distributed and event-driven microservices.
|
|
5
5
|
Project-URL: Homepage, https://github.com/calf-ai/calf-sdk
|
|
6
6
|
Project-URL: Repository, https://github.com/calf-ai/calf-sdk
|
|
@@ -316,6 +316,48 @@ result = await client.execute_node(
|
|
|
316
316
|
|
|
317
317
|
<br>
|
|
318
318
|
|
|
319
|
+
### Gating Node Invocations (Optional)
|
|
320
|
+
|
|
321
|
+
When multiple agents share an input topic (each with its own consumer group), every agent receives every message published to that topic. A **gate stack** lets a node decide whether to handle an inbound event *before* `run()` runs — avoiding wasted LLM tokens on messages addressed elsewhere.
|
|
322
|
+
|
|
323
|
+
Gates are predicates: `Callable[[SessionRunContext], bool | Awaitable[bool]]`. They stack with **AND semantics** in registration order and short-circuit on the first `False`, exception, or non-bool return. When any gate rejects, `run()` is skipped and the envelope is returned unchanged — the Kafka offset still commits.
|
|
324
|
+
|
|
325
|
+
**Constructor form** — good for shared, cross-cutting predicates passed in as values:
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
def is_scheduler_target(ctx) -> bool:
|
|
329
|
+
discord = ctx.deps.provided_deps.get("discord", {})
|
|
330
|
+
return discord.get("slash_target") == "scheduler"
|
|
331
|
+
|
|
332
|
+
scheduler = Agent(
|
|
333
|
+
"scheduler",
|
|
334
|
+
subscribe_topics="discord.thread.123",
|
|
335
|
+
model_client=OpenAIResponsesModelClient(model_name="gpt-5.4-nano"),
|
|
336
|
+
gates=[is_scheduler_target],
|
|
337
|
+
)
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Decorator form** — good for node-specific gates defined inline:
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
scheduler = Agent("scheduler", subscribe_topics="discord.thread.123", model_client=...)
|
|
344
|
+
|
|
345
|
+
@scheduler.gate
|
|
346
|
+
def is_scheduler_target(ctx) -> bool:
|
|
347
|
+
discord = ctx.deps.provided_deps.get("discord", {})
|
|
348
|
+
return discord.get("slash_target") == "scheduler"
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Constructor and decorator forms can be combined; constructor gates run first.
|
|
352
|
+
|
|
353
|
+
**Idempotency requirement**: Kafka may redeliver an event before its offset commits, so gates may run more than once for the same logical message. Keep gate functions deterministic and side-effect-free.
|
|
354
|
+
|
|
355
|
+
**Failure behavior**: If a gate raises or returns a non-bool, the framework logs the failure and rejects the message (fail-safe). Place cheap fast-reject gates first to maximize short-circuit efficiency.
|
|
356
|
+
|
|
357
|
+
For tool-node gating, pass `gates=[...]` to `ToolNodeDef.create_tool_node(...)` directly; the `@agent_tool` decorator doesn't expose `gates=` because tool topics are typically 1:1.
|
|
358
|
+
|
|
359
|
+
<br>
|
|
360
|
+
|
|
319
361
|
## Documentation
|
|
320
362
|
|
|
321
363
|
Full documentation is coming soon. In the meantime, this README serves as the primary reference for getting started with Calfkit.
|
|
@@ -278,6 +278,48 @@ result = await client.execute_node(
|
|
|
278
278
|
|
|
279
279
|
<br>
|
|
280
280
|
|
|
281
|
+
### Gating Node Invocations (Optional)
|
|
282
|
+
|
|
283
|
+
When multiple agents share an input topic (each with its own consumer group), every agent receives every message published to that topic. A **gate stack** lets a node decide whether to handle an inbound event *before* `run()` runs — avoiding wasted LLM tokens on messages addressed elsewhere.
|
|
284
|
+
|
|
285
|
+
Gates are predicates: `Callable[[SessionRunContext], bool | Awaitable[bool]]`. They stack with **AND semantics** in registration order and short-circuit on the first `False`, exception, or non-bool return. When any gate rejects, `run()` is skipped and the envelope is returned unchanged — the Kafka offset still commits.
|
|
286
|
+
|
|
287
|
+
**Constructor form** — good for shared, cross-cutting predicates passed in as values:
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
def is_scheduler_target(ctx) -> bool:
|
|
291
|
+
discord = ctx.deps.provided_deps.get("discord", {})
|
|
292
|
+
return discord.get("slash_target") == "scheduler"
|
|
293
|
+
|
|
294
|
+
scheduler = Agent(
|
|
295
|
+
"scheduler",
|
|
296
|
+
subscribe_topics="discord.thread.123",
|
|
297
|
+
model_client=OpenAIResponsesModelClient(model_name="gpt-5.4-nano"),
|
|
298
|
+
gates=[is_scheduler_target],
|
|
299
|
+
)
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Decorator form** — good for node-specific gates defined inline:
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
scheduler = Agent("scheduler", subscribe_topics="discord.thread.123", model_client=...)
|
|
306
|
+
|
|
307
|
+
@scheduler.gate
|
|
308
|
+
def is_scheduler_target(ctx) -> bool:
|
|
309
|
+
discord = ctx.deps.provided_deps.get("discord", {})
|
|
310
|
+
return discord.get("slash_target") == "scheduler"
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Constructor and decorator forms can be combined; constructor gates run first.
|
|
314
|
+
|
|
315
|
+
**Idempotency requirement**: Kafka may redeliver an event before its offset commits, so gates may run more than once for the same logical message. Keep gate functions deterministic and side-effect-free.
|
|
316
|
+
|
|
317
|
+
**Failure behavior**: If a gate raises or returns a non-bool, the framework logs the failure and rejects the message (fail-safe). Place cheap fast-reject gates first to maximize short-circuit efficiency.
|
|
318
|
+
|
|
319
|
+
For tool-node gating, pass `gates=[...]` to `ToolNodeDef.create_tool_node(...)` directly; the `@agent_tool` decorator doesn't expose `gates=` because tool topics are typically 1:1.
|
|
320
|
+
|
|
321
|
+
<br>
|
|
322
|
+
|
|
281
323
|
## Documentation
|
|
282
324
|
|
|
283
325
|
Full documentation is coming soon. In the meantime, this README serves as the primary reference for getting started with Calfkit.
|
|
@@ -2,7 +2,7 @@ from importlib.metadata import version
|
|
|
2
2
|
|
|
3
3
|
from calfkit.client import Client, InvocationHandle, NodeResult
|
|
4
4
|
from calfkit.models import ToolContext
|
|
5
|
-
from calfkit.nodes import Agent, BaseNodeDef, NodeDef, ToolNodeDef, agent_tool
|
|
5
|
+
from calfkit.nodes import Agent, BaseNodeDef, GateFunction, NodeDef, ToolNodeDef, agent_tool
|
|
6
6
|
from calfkit.providers import AnthropicModelClient, OpenAIModelClient, OpenAIResponsesModelClient
|
|
7
7
|
from calfkit.worker import Worker
|
|
8
8
|
|
|
@@ -18,6 +18,7 @@ __all__ = [
|
|
|
18
18
|
# nodes
|
|
19
19
|
"Agent",
|
|
20
20
|
"BaseNodeDef",
|
|
21
|
+
"GateFunction",
|
|
21
22
|
"NodeDef",
|
|
22
23
|
"ToolNodeDef",
|
|
23
24
|
"agent_tool",
|
|
@@ -20,6 +20,7 @@ from calfkit.models.session_context import (
|
|
|
20
20
|
SessionRunContext,
|
|
21
21
|
WorkflowState,
|
|
22
22
|
)
|
|
23
|
+
from calfkit.models.state import OverridesState
|
|
23
24
|
|
|
24
25
|
logger = logging.getLogger(__name__)
|
|
25
26
|
|
|
@@ -111,6 +112,7 @@ class BaseClient:
|
|
|
111
112
|
reply_topic: str,
|
|
112
113
|
correlation_id: str,
|
|
113
114
|
state: State,
|
|
115
|
+
overrides: OverridesState | None = None,
|
|
114
116
|
run_args: Sequence[Any] | None = None,
|
|
115
117
|
deps: dict[str, Any] | None = None,
|
|
116
118
|
output_type: type[Any] = _UNSET,
|
|
@@ -136,7 +138,7 @@ class BaseClient:
|
|
|
136
138
|
await self._connection.start()
|
|
137
139
|
|
|
138
140
|
call_stack = CallFrameStack()
|
|
139
|
-
call_stack.push(CallFrame(target_topic=topic, callback_topic=reply_topic, input_args=run_args))
|
|
141
|
+
call_stack.push(CallFrame(target_topic=topic, callback_topic=reply_topic, input_args=run_args, overrides=overrides))
|
|
140
142
|
|
|
141
143
|
envelope = Envelope(
|
|
142
144
|
internal_workflow_state=WorkflowState(call_stack=call_stack),
|
|
@@ -12,6 +12,8 @@ from calfkit.client.deserialize import _UNSET
|
|
|
12
12
|
from calfkit.client.invocation_handle import InvocationHandle
|
|
13
13
|
from calfkit.client.node_result import NodeResult
|
|
14
14
|
from calfkit.models import State
|
|
15
|
+
from calfkit.models.node_schema import BaseToolNodeSchema
|
|
16
|
+
from calfkit.models.state import OverridesState
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
class Client(BaseClient):
|
|
@@ -30,6 +32,7 @@ class Client(BaseClient):
|
|
|
30
32
|
topic: str,
|
|
31
33
|
*,
|
|
32
34
|
output_type: type[OutputT],
|
|
35
|
+
tool_overrides: list[BaseToolNodeSchema] | None = ...,
|
|
33
36
|
reply_topic: str | None = ...,
|
|
34
37
|
correlation_id: str | None = ...,
|
|
35
38
|
temp_instructions: str | None = ...,
|
|
@@ -44,6 +47,7 @@ class Client(BaseClient):
|
|
|
44
47
|
user_prompt: str,
|
|
45
48
|
topic: str,
|
|
46
49
|
*,
|
|
50
|
+
tool_overrides: list[BaseToolNodeSchema] | None = ...,
|
|
47
51
|
reply_topic: str | None = ...,
|
|
48
52
|
correlation_id: str | None = ...,
|
|
49
53
|
temp_instructions: str | None = ...,
|
|
@@ -57,6 +61,7 @@ class Client(BaseClient):
|
|
|
57
61
|
user_prompt: str,
|
|
58
62
|
topic: str,
|
|
59
63
|
*,
|
|
64
|
+
tool_overrides: list[BaseToolNodeSchema] | None = None,
|
|
60
65
|
output_type: type[Any] = _UNSET,
|
|
61
66
|
reply_topic: str | None = None,
|
|
62
67
|
correlation_id: str | None = None,
|
|
@@ -74,6 +79,7 @@ class Client(BaseClient):
|
|
|
74
79
|
Args:
|
|
75
80
|
user_prompt: The user message to send to the agent node.
|
|
76
81
|
topic: The Kafka topic the target node subscribes to.
|
|
82
|
+
tool_overrides: Runtime agent tool overrides.
|
|
77
83
|
output_type: The expected Python type for deserializing the agent's
|
|
78
84
|
output. When omitted, auto-detection is used (``DataPart`` →
|
|
79
85
|
``TextPart`` fallback).
|
|
@@ -82,7 +88,7 @@ class Client(BaseClient):
|
|
|
82
88
|
correlation_id: Unique identifier to correlate this request with its
|
|
83
89
|
reply. Auto-generated (uuid7) when ``None``.
|
|
84
90
|
temp_instructions: Optional system-level instructions injected into
|
|
85
|
-
the user prompt as a
|
|
91
|
+
the user prompt as a temporary prompt addition.
|
|
86
92
|
message_history: Prior conversation messages to include for
|
|
87
93
|
multi-turn context. Defaults to an empty history.
|
|
88
94
|
run_args: Positional arguments forwarded to the node's ``run()``
|
|
@@ -107,6 +113,7 @@ class Client(BaseClient):
|
|
|
107
113
|
correlation_id=correlation_id,
|
|
108
114
|
run_args=run_args,
|
|
109
115
|
state=state,
|
|
116
|
+
overrides=OverridesState(override_agent_tools=tool_overrides) if tool_overrides is not None else None,
|
|
110
117
|
deps=deps,
|
|
111
118
|
output_type=output_type,
|
|
112
119
|
)
|
|
@@ -118,6 +125,7 @@ class Client(BaseClient):
|
|
|
118
125
|
topic: str,
|
|
119
126
|
*,
|
|
120
127
|
output_type: type[OutputT],
|
|
128
|
+
tool_overrides: list[BaseToolNodeSchema] | None = ...,
|
|
121
129
|
reply_topic: str | None = ...,
|
|
122
130
|
correlation_id: str | None = ...,
|
|
123
131
|
temp_instructions: str | None = ...,
|
|
@@ -133,6 +141,7 @@ class Client(BaseClient):
|
|
|
133
141
|
user_prompt: str,
|
|
134
142
|
topic: str,
|
|
135
143
|
*,
|
|
144
|
+
tool_overrides: list[BaseToolNodeSchema] | None = ...,
|
|
136
145
|
reply_topic: str | None = ...,
|
|
137
146
|
correlation_id: str | None = ...,
|
|
138
147
|
temp_instructions: str | None = ...,
|
|
@@ -147,6 +156,7 @@ class Client(BaseClient):
|
|
|
147
156
|
user_prompt: str,
|
|
148
157
|
topic: str,
|
|
149
158
|
*,
|
|
159
|
+
tool_overrides: list[BaseToolNodeSchema] | None = None,
|
|
150
160
|
output_type: type[Any] = _UNSET,
|
|
151
161
|
reply_topic: str | None = None,
|
|
152
162
|
correlation_id: str | None = None,
|
|
@@ -168,6 +178,7 @@ class Client(BaseClient):
|
|
|
168
178
|
Args:
|
|
169
179
|
user_prompt: The user message to send to the agent node.
|
|
170
180
|
topic: The Kafka topic the target node subscribes to.
|
|
181
|
+
tool_overrides: Runtime agent tool overrides.
|
|
171
182
|
output_type: The expected Python type for deserializing the agent's
|
|
172
183
|
output. When omitted, auto-detection is used.
|
|
173
184
|
reply_topic: Topic the node should publish its reply to.
|
|
@@ -175,7 +186,7 @@ class Client(BaseClient):
|
|
|
175
186
|
correlation_id: Unique identifier to correlate this request with its
|
|
176
187
|
reply. Auto-generated (uuid7) when ``None``.
|
|
177
188
|
temp_instructions: Optional system-level instructions injected into
|
|
178
|
-
the user prompt as a
|
|
189
|
+
the user prompt as a temporary prompt addition.
|
|
179
190
|
message_history: Prior conversation messages to include for
|
|
180
191
|
multi-turn context. Defaults to an empty history.
|
|
181
192
|
run_args: Positional arguments forwarded to the node's ``run()``
|
|
@@ -195,6 +206,7 @@ class Client(BaseClient):
|
|
|
195
206
|
handle = await self.invoke_node(
|
|
196
207
|
user_prompt,
|
|
197
208
|
topic,
|
|
209
|
+
tool_overrides=tool_overrides,
|
|
198
210
|
output_type=output_type,
|
|
199
211
|
reply_topic=reply_topic,
|
|
200
212
|
correlation_id=correlation_id,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from dataclasses import KW_ONLY, dataclass
|
|
2
|
+
|
|
3
|
+
from calfkit._vendor.pydantic_ai.tools import ToolDefinition
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class BaseNodeSchema:
|
|
8
|
+
_: KW_ONLY
|
|
9
|
+
node_id: str
|
|
10
|
+
subscribe_topics: list[str]
|
|
11
|
+
publish_topic: str | None
|
|
12
|
+
|
|
13
|
+
def __post_init__(self) -> None:
|
|
14
|
+
if not isinstance(self.subscribe_topics, (list, tuple)):
|
|
15
|
+
self.subscribe_topics = [self.subscribe_topics]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class BaseToolNodeSchema(BaseNodeSchema):
|
|
20
|
+
_: KW_ONLY
|
|
21
|
+
tool_schema: ToolDefinition
|
|
@@ -7,7 +7,7 @@ from pydantic import BaseModel, ConfigDict, Field
|
|
|
7
7
|
|
|
8
8
|
from calfkit._types import DepsT, StackItemT, StateT
|
|
9
9
|
from calfkit.models.actions import _Call
|
|
10
|
-
from calfkit.models.state import State
|
|
10
|
+
from calfkit.models.state import OverridesState, State
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
@dataclass
|
|
@@ -36,6 +36,7 @@ class CallFrame:
|
|
|
36
36
|
callback_topic: str # return address
|
|
37
37
|
input_args: Sequence[Any] | None = field(default=None)
|
|
38
38
|
frame_id: str = field(default_factory=lambda: uuid_utils.uuid7().hex)
|
|
39
|
+
overrides: OverridesState | None = field(default=None)
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
CallFrameStack = Stack[CallFrame]
|
|
@@ -11,6 +11,7 @@ from calfkit._vendor.pydantic_ai.messages import (
|
|
|
11
11
|
ToolCallPart,
|
|
12
12
|
)
|
|
13
13
|
from calfkit._vendor.pydantic_ai.tools import DeferredToolCallResult as ToolCallResult
|
|
14
|
+
from calfkit.models.node_schema import BaseToolNodeSchema
|
|
14
15
|
from calfkit.models.payload import ContentPart
|
|
15
16
|
|
|
16
17
|
|
|
@@ -18,6 +19,13 @@ class BaseAgentActivityState(BaseModel):
|
|
|
18
19
|
model_config = ConfigDict(extra="ignore")
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
class OverridesState(BaseAgentActivityState):
|
|
23
|
+
"""State for storing any override objects"""
|
|
24
|
+
|
|
25
|
+
model_config = ConfigDict(extra="ignore")
|
|
26
|
+
override_agent_tools: list[BaseToolNodeSchema] | None
|
|
27
|
+
|
|
28
|
+
|
|
21
29
|
class CoreMessageState(BaseAgentActivityState):
|
|
22
30
|
"""The state for committed messages and structured objects"""
|
|
23
31
|
|
|
@@ -90,6 +98,7 @@ class State(CoreMessageState, InFlightToolsState):
|
|
|
90
98
|
default=None,
|
|
91
99
|
description="Additional data that can be accessed programmatically by the application but is not sent to the LLM.", # noqa: E501
|
|
92
100
|
)
|
|
101
|
+
overrides: OverridesState | None = None
|
|
93
102
|
|
|
94
103
|
|
|
95
104
|
# class State(BaseModel):
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from calfkit.nodes.agent import Agent, BaseAgentNodeDef
|
|
2
|
-
from calfkit.nodes.base import BaseNodeDef
|
|
2
|
+
from calfkit.nodes.base import BaseNodeDef, GateFunction
|
|
3
3
|
from calfkit.nodes.node import NodeDef
|
|
4
4
|
from calfkit.nodes.tool import BaseToolNodeDef, ToolNodeDef, agent_tool
|
|
5
5
|
|
|
@@ -8,6 +8,7 @@ __all__ = [
|
|
|
8
8
|
"BaseAgentNodeDef",
|
|
9
9
|
"BaseNodeDef",
|
|
10
10
|
"BaseToolNodeDef",
|
|
11
|
+
"GateFunction",
|
|
11
12
|
"NodeDef",
|
|
12
13
|
"ToolNodeDef",
|
|
13
14
|
"agent_tool",
|
|
@@ -11,9 +11,10 @@ from calfkit._vendor.pydantic_ai.tools import DeferredToolResults
|
|
|
11
11
|
from calfkit._vendor.pydantic_ai.toolsets.external import ExternalToolset
|
|
12
12
|
from calfkit.models import Call, DataPart, NodeResult, ReturnCall, State, TailCall, TextPart
|
|
13
13
|
from calfkit.models.actions import Silent
|
|
14
|
+
from calfkit.models.node_schema import BaseToolNodeSchema
|
|
14
15
|
from calfkit.models.session_context import SessionRunContext
|
|
15
16
|
from calfkit.models.state import PendingToolBatch
|
|
16
|
-
from calfkit.nodes.base import BaseNodeDef
|
|
17
|
+
from calfkit.nodes.base import BaseNodeDef, GateFunction
|
|
17
18
|
from calfkit.nodes.tool import ToolNodeDef
|
|
18
19
|
from calfkit.providers.pydantic_ai.model_client import PydanticModelClient
|
|
19
20
|
|
|
@@ -33,6 +34,7 @@ class BaseAgentNodeDef(
|
|
|
33
34
|
system_prompt: str = "You are a helpful AI assistant.",
|
|
34
35
|
subscribe_topics: str | list[str],
|
|
35
36
|
publish_topic: str | None = None,
|
|
37
|
+
gates: list[GateFunction] | None = None,
|
|
36
38
|
tools: list[ToolNodeDef] | None = None,
|
|
37
39
|
model_client: PydanticModelClient,
|
|
38
40
|
final_output_type: OutputSpec[AgentOutputT] = str, # type: ignore[assignment]
|
|
@@ -44,7 +46,10 @@ class BaseAgentNodeDef(
|
|
|
44
46
|
self.sequential_only_mode = sequential_only_mode
|
|
45
47
|
self._pending_batches: dict[str, PendingToolBatch] = dict()
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
if not isinstance(subscribe_topics, (list, tuple)):
|
|
50
|
+
subscribe_topics = [subscribe_topics]
|
|
51
|
+
|
|
52
|
+
super().__init__(node_id=node_id, subscribe_topics=subscribe_topics, publish_topic=publish_topic, gates=gates)
|
|
48
53
|
|
|
49
54
|
self._agent_loop: InternalAgentLoop[dict[str, Any], AgentOutputT | DeferredToolRequests] = InternalAgentLoop(
|
|
50
55
|
model_client, name=self.name, output_type=[final_output_type, DeferredToolRequests], deps_type=dict, instructions=system_prompt
|
|
@@ -64,7 +69,11 @@ class BaseAgentNodeDef(
|
|
|
64
69
|
del self._pending_batches[ctx.deps.correlation_id]
|
|
65
70
|
|
|
66
71
|
async def run(self, ctx: SessionRunContext) -> NodeResult[State]:
|
|
67
|
-
tools_registry =
|
|
72
|
+
tools_registry = dict[str, BaseToolNodeSchema]()
|
|
73
|
+
if ctx.state.overrides is not None and ctx.state.overrides.override_agent_tools is not None:
|
|
74
|
+
tools_registry = {tool.tool_schema.name: tool for tool in ctx.state.overrides.override_agent_tools}
|
|
75
|
+
elif self.tools:
|
|
76
|
+
tools_registry = {tool.tool_schema.name: tool for tool in self.tools}
|
|
68
77
|
|
|
69
78
|
logger.debug(
|
|
70
79
|
"[%s] agent run entered node=%s pending_tool_calls=%d history_len=%d",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import logging
|
|
3
3
|
from abc import abstractmethod
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
4
5
|
from typing import Annotated, Any
|
|
5
6
|
|
|
6
7
|
from faststream import Context
|
|
@@ -17,17 +18,27 @@ from calfkit.models import (
|
|
|
17
18
|
TailCall,
|
|
18
19
|
)
|
|
19
20
|
from calfkit.models.envelope import Envelope
|
|
21
|
+
from calfkit.models.node_schema import BaseNodeSchema
|
|
20
22
|
from calfkit.models.session_context import SessionRunContext
|
|
21
23
|
|
|
22
24
|
logger = logging.getLogger(__name__)
|
|
23
25
|
|
|
24
26
|
|
|
27
|
+
GateFunction = Callable[[SessionRunContext], bool | Awaitable[bool]]
|
|
28
|
+
"""A predicate evaluated in ``handler()`` before ``run()``. Sync or async; must return ``bool``.
|
|
29
|
+
|
|
30
|
+
Returning ``False`` (or raising, or returning a non-bool) skips ``run()`` and returns the
|
|
31
|
+
envelope unchanged. Gates stack with AND semantics in registration order and short-circuit
|
|
32
|
+
on the first rejection.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
|
|
25
36
|
# ---------------------------------------------------------------------------
|
|
26
37
|
# Base node definition
|
|
27
38
|
# ---------------------------------------------------------------------------
|
|
28
39
|
|
|
29
40
|
|
|
30
|
-
class BaseNodeDef:
|
|
41
|
+
class BaseNodeDef(BaseNodeSchema):
|
|
31
42
|
_run_accepts_input: bool
|
|
32
43
|
|
|
33
44
|
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
@@ -39,18 +50,70 @@ class BaseNodeDef:
|
|
|
39
50
|
|
|
40
51
|
def __init__(
|
|
41
52
|
self,
|
|
42
|
-
node_id: str,
|
|
43
53
|
*,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
node_id: str,
|
|
55
|
+
subscribe_topics: list[str],
|
|
56
|
+
publish_topic: str | None = None,
|
|
57
|
+
gates: list[GateFunction] | None = None,
|
|
58
|
+
) -> None:
|
|
59
|
+
"""Initialize a node definition.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
node_id: Unique identifier for the node.
|
|
63
|
+
subscribe_topics: One or more topics the node consumes from.
|
|
64
|
+
publish_topic: Optional default topic to publish results to.
|
|
65
|
+
gates: Optional list of predicates evaluated in ``handler()`` before
|
|
66
|
+
``run()``. Stack with AND semantics in registration order;
|
|
67
|
+
short-circuits on the first ``False``, exception, or non-bool.
|
|
68
|
+
Returning anything other than ``True`` rejects the message:
|
|
69
|
+
``run()`` is skipped and the envelope is returned unchanged.
|
|
70
|
+
"""
|
|
71
|
+
super().__init__(
|
|
72
|
+
node_id=node_id,
|
|
73
|
+
subscribe_topics=subscribe_topics,
|
|
74
|
+
publish_topic=publish_topic,
|
|
75
|
+
)
|
|
76
|
+
self.gates: list[GateFunction] = list(gates) if gates else []
|
|
77
|
+
|
|
78
|
+
def gate(self, fn: GateFunction) -> GateFunction:
|
|
79
|
+
"""Register a gate predicate. Usable as a decorator. Repeatable.
|
|
80
|
+
|
|
81
|
+
Multiple gates evaluate in registration order with AND semantics
|
|
82
|
+
(short-circuit on the first ``False``, exception, or non-bool return).
|
|
83
|
+
Returns ``fn`` unchanged so it remains callable.
|
|
84
|
+
"""
|
|
85
|
+
self.gates.append(fn)
|
|
86
|
+
return fn
|
|
87
|
+
|
|
88
|
+
async def _evaluate_gates(self, ctx: SessionRunContext, correlation_id: str) -> bool:
|
|
89
|
+
for i, gate in enumerate(self.gates):
|
|
90
|
+
try:
|
|
91
|
+
result = gate(ctx)
|
|
92
|
+
if inspect.isawaitable(result):
|
|
93
|
+
result = await result
|
|
94
|
+
if not isinstance(result, bool):
|
|
95
|
+
raise TypeError(
|
|
96
|
+
f"gate[{i}] {getattr(gate, '__name__', repr(gate))} for node={self.node_id} returned {type(result).__name__}; expected bool"
|
|
97
|
+
)
|
|
98
|
+
if not result:
|
|
99
|
+
logger.debug(
|
|
100
|
+
"[%s] gate[%d]=%s rejected node=%s",
|
|
101
|
+
correlation_id[:8],
|
|
102
|
+
i,
|
|
103
|
+
getattr(gate, "__name__", "?"),
|
|
104
|
+
self.node_id,
|
|
105
|
+
)
|
|
106
|
+
return False
|
|
107
|
+
except Exception:
|
|
108
|
+
logger.exception(
|
|
109
|
+
"[%s] gate[%d]=%s raised for node=%s; treating as reject",
|
|
110
|
+
correlation_id[:8],
|
|
111
|
+
i,
|
|
112
|
+
getattr(gate, "__name__", "?"),
|
|
113
|
+
self.node_id,
|
|
114
|
+
)
|
|
115
|
+
return False
|
|
116
|
+
return True
|
|
54
117
|
|
|
55
118
|
# TODO: consider multiple abstract methods per node based on the incoming communication pattern,
|
|
56
119
|
# like a delgation or emit. So the communication-specific handler can properly handle it
|
|
@@ -77,6 +140,8 @@ class BaseNodeDef:
|
|
|
77
140
|
|
|
78
141
|
async def prepare_context(self, envelope: Envelope) -> SessionRunContext:
|
|
79
142
|
ctx = envelope.context.model_copy(deep=True)
|
|
143
|
+
if envelope.internal_workflow_state.current_frame.overrides:
|
|
144
|
+
ctx.state.overrides = envelope.internal_workflow_state.current_frame.overrides
|
|
80
145
|
return ctx
|
|
81
146
|
|
|
82
147
|
async def _publish_action(self, output: NodeResult[State], envelope: Envelope, correlation_id: str, broker: BrokerAnnotation) -> Envelope:
|
|
@@ -107,7 +172,7 @@ class BaseNodeDef:
|
|
|
107
172
|
internal_workflow_state=envelope.internal_workflow_state,
|
|
108
173
|
)
|
|
109
174
|
target_topic = envelope.internal_workflow_state.current_frame.target_topic
|
|
110
|
-
logger.debug("[%s] Call target=%s frame_pushed node=%s", correlation_id[:8], target_topic, self.
|
|
175
|
+
logger.debug("[%s] Call target=%s frame_pushed node=%s", correlation_id[:8], target_topic, self.node_id)
|
|
111
176
|
await broker.publish(
|
|
112
177
|
publish_envelope,
|
|
113
178
|
topic=target_topic,
|
|
@@ -121,7 +186,7 @@ class BaseNodeDef:
|
|
|
121
186
|
context=SessionRunContext(state=output.state, deps=envelope.context.deps),
|
|
122
187
|
internal_workflow_state=envelope.internal_workflow_state,
|
|
123
188
|
)
|
|
124
|
-
logger.debug("[%s] ReturnCall callback=%s node=%s", correlation_id[:8], frame.callback_topic, self.
|
|
189
|
+
logger.debug("[%s] ReturnCall callback=%s node=%s", correlation_id[:8], frame.callback_topic, self.node_id)
|
|
125
190
|
await broker.publish(
|
|
126
191
|
publish_envelope,
|
|
127
192
|
topic=frame.callback_topic,
|
|
@@ -138,7 +203,7 @@ class BaseNodeDef:
|
|
|
138
203
|
internal_workflow_state=envelope.internal_workflow_state,
|
|
139
204
|
)
|
|
140
205
|
target_topic = envelope.internal_workflow_state.current_frame.target_topic
|
|
141
|
-
logger.debug("[%s] TailCall target=%s node=%s", correlation_id[:8], target_topic, self.
|
|
206
|
+
logger.debug("[%s] TailCall target=%s node=%s", correlation_id[:8], target_topic, self.node_id)
|
|
142
207
|
await broker.publish(
|
|
143
208
|
publish_envelope,
|
|
144
209
|
topic=target_topic,
|
|
@@ -164,21 +229,29 @@ class BaseNodeDef:
|
|
|
164
229
|
correlation_id: Annotated[str, Context()],
|
|
165
230
|
broker: BrokerAnnotation,
|
|
166
231
|
) -> Envelope:
|
|
167
|
-
logger.debug("[%s] handler entered node=%s", correlation_id[:8], self.
|
|
232
|
+
logger.debug("[%s] handler entered node=%s", correlation_id[:8], self.node_id)
|
|
168
233
|
ctx = await self.prepare_context(envelope)
|
|
234
|
+
|
|
235
|
+
if not await self._evaluate_gates(ctx, correlation_id):
|
|
236
|
+
return envelope
|
|
237
|
+
|
|
169
238
|
if self._run_accepts_input and envelope.internal_workflow_state.current_frame.input_args is not None:
|
|
170
239
|
output = await self.run(ctx, *envelope.internal_workflow_state.current_frame.input_args)
|
|
171
240
|
else:
|
|
172
241
|
output = await self.run(ctx)
|
|
173
242
|
|
|
174
|
-
logger.debug("[%s] run() returned action=%s node=%s", correlation_id[:8], type(output).__name__, self.
|
|
243
|
+
logger.debug("[%s] run() returned action=%s node=%s", correlation_id[:8], type(output).__name__, self.node_id)
|
|
175
244
|
|
|
176
245
|
return await self._publish_action(output, envelope, correlation_id, broker)
|
|
177
246
|
|
|
178
247
|
@property
|
|
179
248
|
def id(self) -> str:
|
|
180
|
-
return self.
|
|
249
|
+
return self.node_id
|
|
181
250
|
|
|
182
251
|
@property
|
|
183
252
|
def name(self) -> str:
|
|
184
|
-
return self.
|
|
253
|
+
return self.node_id
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def _return_topic(self) -> str:
|
|
257
|
+
return f"{self.node_id}.private.return"
|