calfkit 0.3.0__tar.gz → 0.3.2__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.3.2/.release-please-manifest.json +3 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/CHANGELOG.md +25 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/PKG-INFO +52 -1
- {calfkit-0.3.0 → calfkit-0.3.2}/README.md +51 -0
- calfkit-0.3.2/ROADMAP.md +9 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/__init__.py +4 -1
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_protocol.py +1 -1
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/client/client.py +32 -1
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/client/deserialize.py +39 -15
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/client/invocation_handle.py +7 -1
- calfkit-0.3.2/calfkit/client/node_result.py +103 -0
- calfkit-0.3.2/calfkit/models/node_schema.py +32 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/models/state.py +2 -1
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/nodes/__init__.py +4 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/nodes/agent.py +12 -3
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/nodes/base.py +34 -2
- calfkit-0.3.2/calfkit/nodes/consumer.py +331 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/worker/worker.py +12 -2
- calfkit-0.3.2/docs/calfkit-v1-design.md +3439 -0
- calfkit-0.3.2/docs/calfkit-v1-dx-review.md +1432 -0
- calfkit-0.3.2/docs/hooks-design.md +1082 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/examples/quickstart/agent_service.py +1 -0
- calfkit-0.3.2/examples/quickstart/weather_sink.py +42 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/pyproject.toml +1 -1
- {calfkit-0.3.0 → calfkit-0.3.2}/tests/conftest.py +38 -1
- calfkit-0.3.2/tests/test_co_tenant_tool_isolation.py +458 -0
- calfkit-0.3.2/tests/test_consumer.py +1049 -0
- calfkit-0.3.2/tests/test_model_settings.py +219 -0
- calfkit-0.3.0/.release-please-manifest.json +0 -3
- calfkit-0.3.0/calfkit/client/node_result.py +0 -40
- calfkit-0.3.0/calfkit/models/node_schema.py +0 -21
- {calfkit-0.3.0 → calfkit-0.3.2}/.github/CODEOWNERS +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/.github/dependabot.yml +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/.github/workflows/build.yml +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/.github/workflows/code-checks.yml +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/.github/workflows/release.yml +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/.github/workflows/security.yml +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/.github/workflows/test.yml +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/.gitignore +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/LICENSE +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/Makefile +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_types.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/LICENSE +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/__main__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/_a2a.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/_agent_graph.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/_cli/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/_cli/web.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/_function_schema.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/_griffe.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/_instrumentation.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/_json_schema.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/_mcp.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/_otel_messages.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/_output.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/_parts_manager.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/_run_context.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/_system_prompt.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/_thinking_part.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/_tool_manager.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/_utils.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ag_ui.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/agent/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/agent/abstract.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/agent/wrapper.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/builtin_tools.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/common_tools/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/common_tools/duckduckgo.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/common_tools/exa.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/common_tools/tavily.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/direct.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_agent.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_fastmcp_toolset.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_mcp.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_mcp_server.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_model.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_utils.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_agent.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_cache_policies.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_function_toolset.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_mcp_server.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_model.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_toolset.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_types.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_agent.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_dynamic_toolset.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_fastmcp_toolset.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_function_toolset.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_logfire.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_mcp.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_mcp_server.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_model.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_run_context.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_toolset.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_workflow.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/embeddings/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/embeddings/base.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/embeddings/cohere.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/embeddings/google.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/embeddings/instrumented.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/embeddings/openai.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/embeddings/result.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/embeddings/sentence_transformers.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/embeddings/settings.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/embeddings/test.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/embeddings/voyageai.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/embeddings/wrapper.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/exceptions.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ext/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ext/aci.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ext/langchain.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/format_prompt.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/mcp.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/messages.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/anthropic.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/bedrock.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/cerebras.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/cohere.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/fallback.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/function.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/gemini.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/google.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/groq.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/huggingface.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/instrumented.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/mcp_sampling.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/mistral.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/openai.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/openrouter.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/outlines.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/test.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/wrapper.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/models/xai.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/output.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/profiles/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/profiles/amazon.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/profiles/anthropic.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/profiles/cohere.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/profiles/deepseek.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/profiles/google.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/profiles/grok.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/profiles/groq.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/profiles/harmony.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/profiles/meta.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/profiles/mistral.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/profiles/moonshotai.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/profiles/openai.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/profiles/qwen.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/profiles/zai.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/alibaba.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/anthropic.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/azure.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/bedrock.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/cerebras.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/cohere.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/deepseek.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/fireworks.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/gateway.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/github.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/google.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/google_gla.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/google_vertex.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/grok.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/groq.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/heroku.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/huggingface.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/litellm.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/mistral.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/moonshotai.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/nebius.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/ollama.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/openai.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/openrouter.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/outlines.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/ovhcloud.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/sambanova.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/sentence_transformers.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/together.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/vercel.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/voyageai.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/providers/xai.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/py.typed +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/result.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/retries.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/run.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/settings.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/tools.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/toolsets/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/toolsets/_dynamic.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/toolsets/abstract.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/toolsets/approval_required.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/toolsets/combined.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/toolsets/external.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/toolsets/fastmcp.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/toolsets/filtered.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/toolsets/function.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/toolsets/prefixed.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/toolsets/prepared.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/toolsets/renamed.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/toolsets/wrapper.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ui/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ui/_adapter.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ui/_event_stream.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ui/_messages_builder.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ui/_web/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ui/_web/api.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ui/_web/app.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ui/ag_ui/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ui/ag_ui/_adapter.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ui/ag_ui/_event_stream.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ui/ag_ui/app.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/_adapter.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/_event_stream.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/_models.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/_utils.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/request_types.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/response_types.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/pydantic_ai/usage.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/_vendor/vendor.txt +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/client/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/client/base.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/client/middleware.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/client/reply_dispatcher.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/exceptions.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/models/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/models/actions.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/models/envelope.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/models/payload.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/models/session_context.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/models/tool_context.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/nodes/node.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/nodes/tool.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/providers/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/providers/pydantic_ai/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/providers/pydantic_ai/anthropic.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/providers/pydantic_ai/model_client.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/providers/pydantic_ai/openai.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/worker/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/calfkit/worker/worker_config.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/codecov.yml +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/examples/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/examples/deprecated/agent_dispatcher.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/examples/deprecated/chat_node.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/examples/deprecated/chat_repl_cli.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/examples/deprecated/invoke_agent.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/examples/deprecated/router_node.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/examples/deprecated/tool_nodes.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/examples/quickstart/invoke.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/examples/quickstart/weather_tool.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/examples/rpc_worker.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/release-please-config.json +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/tests/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/tests/integration/__init__.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/tests/integration/test_agent_output_types.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/tests/integration/test_agent_workers.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/tests/providers.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/tests/test_concurrent_tool_calls.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/tests/test_gates.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/tests/test_headers.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/tests/test_instructions.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/tests/test_overrides.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/tests/test_serializable.py +0 -0
- {calfkit-0.3.0 → calfkit-0.3.2}/tests/utils.py +0 -0
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.2](https://github.com/calf-ai/calfkit-sdk/compare/v0.3.1...v0.3.2) (2026-05-21)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* route tool returns to private inbox to prevent co-tenant leak ([#142](https://github.com/calf-ai/calfkit-sdk/issues/142)) ([197463f](https://github.com/calf-ai/calfkit-sdk/commit/197463f2f7f338bae53bf144e3a86cf27373e350))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
* integrate DX review into v1 design and add reviewer artifact ([#138](https://github.com/calf-ai/calfkit-sdk/issues/138)) ([29a7961](https://github.com/calf-ai/calfkit-sdk/commit/29a7961d5c2d4e46fcb7e2e4a81f43116c34feb3))
|
|
14
|
+
|
|
15
|
+
## [0.3.1](https://github.com/calf-ai/calfkit-sdk/compare/v0.3.0...v0.3.1) (2026-05-19)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* add ConsumerNodeDef and expose State on NodeResult ([#137](https://github.com/calf-ai/calfkit-sdk/issues/137)) ([aa139c7](https://github.com/calf-ai/calfkit-sdk/commit/aa139c74fe7e0d1baa0a016b5db423ebdc6e65ad))
|
|
21
|
+
* add runtime model_settings to Agent and Client ([#134](https://github.com/calf-ai/calfkit-sdk/issues/134)) ([47d64b2](https://github.com/calf-ai/calfkit-sdk/commit/47d64b286b3ca151cc40022dfab22b4862d06b04))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Documentation
|
|
25
|
+
|
|
26
|
+
* add ROADMAP.md index pointing to design docs ([#135](https://github.com/calf-ai/calfkit-sdk/issues/135)) ([5fa2a1a](https://github.com/calf-ai/calfkit-sdk/commit/5fa2a1af731341e3d9bef71be42e30841451058a))
|
|
27
|
+
|
|
3
28
|
## [0.3.0](https://github.com/calf-ai/calfkit-sdk/compare/v0.2.6...v0.3.0) (2026-05-18)
|
|
4
29
|
|
|
5
30
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: calfkit
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
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
|
|
@@ -356,6 +356,57 @@ For tool-node gating, pass `gates=[...]` to `ToolNodeDef.create_tool_node(...)`
|
|
|
356
356
|
|
|
357
357
|
<br>
|
|
358
358
|
|
|
359
|
+
### Consumer Nodes (Optional)
|
|
360
|
+
|
|
361
|
+
A **consumer node** is a terminal sink — it subscribes to one or more topics and runs arbitrary Python logic against every event flowing through. Consumers receive the same `NodeResult` that `Client.execute_node()` returns, including the full session state (`tool_calls`, `tool_results`, `message_history`, `metadata`).
|
|
362
|
+
|
|
363
|
+
Deploy a consumer as its own service. Wire it to an agent's `publish_topic` (or any topic carrying calfkit envelopes) to observe outputs from agents, tools, and intermediate hops:
|
|
364
|
+
|
|
365
|
+
```python
|
|
366
|
+
# weather_sink.py
|
|
367
|
+
import asyncio
|
|
368
|
+
from calfkit.client import Client, NodeResult
|
|
369
|
+
from calfkit.nodes import consumer
|
|
370
|
+
from calfkit.worker import Worker
|
|
371
|
+
|
|
372
|
+
@consumer(subscribe_topics="weather_agent.output")
|
|
373
|
+
async def log_weather(result: NodeResult) -> None:
|
|
374
|
+
if result.output is None:
|
|
375
|
+
return # intermediate hop — no final output yet
|
|
376
|
+
print(f"[{result.correlation_id[:8]}] {result.output}")
|
|
377
|
+
|
|
378
|
+
async def main():
|
|
379
|
+
client = Client.connect("localhost:9092")
|
|
380
|
+
worker = Worker(client, nodes=[log_weather]) # Deploy the consumer node
|
|
381
|
+
await worker.run()
|
|
382
|
+
|
|
383
|
+
if __name__ == "__main__":
|
|
384
|
+
asyncio.run(main())
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Run alongside the agent service:
|
|
388
|
+
|
|
389
|
+
```shell
|
|
390
|
+
python weather_sink.py
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
An agent's `publish_topic` emits on **every** state transition — intermediate hops, tool completions, and terminals — so `result.output` is `None` on hops without final output parts. Filter via a gate if you only want agent terminals:
|
|
394
|
+
|
|
395
|
+
```python
|
|
396
|
+
@consumer(
|
|
397
|
+
subscribe_topics="weather_agent.output",
|
|
398
|
+
gates=[lambda ctx: bool(ctx.state.final_output_parts)],
|
|
399
|
+
)
|
|
400
|
+
async def save_final(result: NodeResult) -> None:
|
|
401
|
+
await db.save(result.output) # always populated here
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**Upstream requirement**: the upstream agent or tool must have a `publish_topic` set for consumers to tap (e.g. add `publish_topic="weather_agent.output"` to the agent in step 4).
|
|
405
|
+
|
|
406
|
+
**Error policy**: exceptions from the consumer function are logged and swallowed by default so a single bad event can't poison-pill the Kafka offset. Pass `re_raise=True` to fail loud during development.
|
|
407
|
+
|
|
408
|
+
<br>
|
|
409
|
+
|
|
359
410
|
## Documentation
|
|
360
411
|
|
|
361
412
|
Full documentation is coming soon. In the meantime, this README serves as the primary reference for getting started with Calfkit.
|
|
@@ -318,6 +318,57 @@ For tool-node gating, pass `gates=[...]` to `ToolNodeDef.create_tool_node(...)`
|
|
|
318
318
|
|
|
319
319
|
<br>
|
|
320
320
|
|
|
321
|
+
### Consumer Nodes (Optional)
|
|
322
|
+
|
|
323
|
+
A **consumer node** is a terminal sink — it subscribes to one or more topics and runs arbitrary Python logic against every event flowing through. Consumers receive the same `NodeResult` that `Client.execute_node()` returns, including the full session state (`tool_calls`, `tool_results`, `message_history`, `metadata`).
|
|
324
|
+
|
|
325
|
+
Deploy a consumer as its own service. Wire it to an agent's `publish_topic` (or any topic carrying calfkit envelopes) to observe outputs from agents, tools, and intermediate hops:
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
# weather_sink.py
|
|
329
|
+
import asyncio
|
|
330
|
+
from calfkit.client import Client, NodeResult
|
|
331
|
+
from calfkit.nodes import consumer
|
|
332
|
+
from calfkit.worker import Worker
|
|
333
|
+
|
|
334
|
+
@consumer(subscribe_topics="weather_agent.output")
|
|
335
|
+
async def log_weather(result: NodeResult) -> None:
|
|
336
|
+
if result.output is None:
|
|
337
|
+
return # intermediate hop — no final output yet
|
|
338
|
+
print(f"[{result.correlation_id[:8]}] {result.output}")
|
|
339
|
+
|
|
340
|
+
async def main():
|
|
341
|
+
client = Client.connect("localhost:9092")
|
|
342
|
+
worker = Worker(client, nodes=[log_weather]) # Deploy the consumer node
|
|
343
|
+
await worker.run()
|
|
344
|
+
|
|
345
|
+
if __name__ == "__main__":
|
|
346
|
+
asyncio.run(main())
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Run alongside the agent service:
|
|
350
|
+
|
|
351
|
+
```shell
|
|
352
|
+
python weather_sink.py
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
An agent's `publish_topic` emits on **every** state transition — intermediate hops, tool completions, and terminals — so `result.output` is `None` on hops without final output parts. Filter via a gate if you only want agent terminals:
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
@consumer(
|
|
359
|
+
subscribe_topics="weather_agent.output",
|
|
360
|
+
gates=[lambda ctx: bool(ctx.state.final_output_parts)],
|
|
361
|
+
)
|
|
362
|
+
async def save_final(result: NodeResult) -> None:
|
|
363
|
+
await db.save(result.output) # always populated here
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**Upstream requirement**: the upstream agent or tool must have a `publish_topic` set for consumers to tap (e.g. add `publish_topic="weather_agent.output"` to the agent in step 4).
|
|
367
|
+
|
|
368
|
+
**Error policy**: exceptions from the consumer function are logged and swallowed by default so a single bad event can't poison-pill the Kafka offset. Pass `re_raise=True` to fail loud during development.
|
|
369
|
+
|
|
370
|
+
<br>
|
|
371
|
+
|
|
321
372
|
## Documentation
|
|
322
373
|
|
|
323
374
|
Full documentation is coming soon. In the meantime, this README serves as the primary reference for getting started with Calfkit.
|
calfkit-0.3.2/ROADMAP.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
An index of potential features and changes under consideration for Calf SDK. Each entry links to a detailed design document in `docs/`. Inclusion here does not imply commitment — items may be reshaped, deferred, or dropped after review.
|
|
4
|
+
|
|
5
|
+
## Proposed
|
|
6
|
+
|
|
7
|
+
- [Calfkit 1.0](docs/calfkit-v1-design.md) — 1.0 rewrite proposal covering node, agent, and result shape changes
|
|
8
|
+
- [Hook System](docs/hooks-design.md) — two-layer middleware proposal for pre/post-run extensibility on nodes
|
|
9
|
+
- [Durable Fan-Out Aggregator](docs/durable-fanout-aggregator.md) — replace in-process `_pending_batches` with a Kafka-backed compacted-state aggregator for parallel tool calls
|
|
@@ -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, GateFunction, NodeDef, ToolNodeDef, agent_tool
|
|
5
|
+
from calfkit.nodes import Agent, BaseNodeDef, ConsumerFn, ConsumerNodeDef, GateFunction, NodeDef, ToolNodeDef, agent_tool, consumer
|
|
6
6
|
from calfkit.providers import AnthropicModelClient, OpenAIModelClient, OpenAIResponsesModelClient
|
|
7
7
|
from calfkit.worker import Worker
|
|
8
8
|
|
|
@@ -18,10 +18,13 @@ __all__ = [
|
|
|
18
18
|
# nodes
|
|
19
19
|
"Agent",
|
|
20
20
|
"BaseNodeDef",
|
|
21
|
+
"ConsumerFn",
|
|
22
|
+
"ConsumerNodeDef",
|
|
21
23
|
"GateFunction",
|
|
22
24
|
"NodeDef",
|
|
23
25
|
"ToolNodeDef",
|
|
24
26
|
"agent_tool",
|
|
27
|
+
"consumer",
|
|
25
28
|
# providers
|
|
26
29
|
"AnthropicModelClient",
|
|
27
30
|
"OpenAIModelClient",
|
|
@@ -8,7 +8,7 @@ Do not add imports from ``calfkit.*`` to this module.
|
|
|
8
8
|
|
|
9
9
|
from typing import Any, Literal
|
|
10
10
|
|
|
11
|
-
NodeKind = Literal["node", "agent", "tool", "client"]
|
|
11
|
+
NodeKind = Literal["node", "agent", "tool", "client", "consumer"]
|
|
12
12
|
"""Closed value space for the ``x-calf-emitter-kind`` Kafka header. Subclasses of
|
|
13
13
|
``BaseNodeDef`` declare their kind via ``_node_kind: ClassVar[NodeKind]``; the
|
|
14
14
|
client publishes with ``CLIENT_KIND`` directly.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from collections.abc import Sequence
|
|
4
5
|
from typing import Any, overload
|
|
5
6
|
|
|
@@ -7,6 +8,7 @@ import uuid_utils
|
|
|
7
8
|
|
|
8
9
|
from calfkit._types import OutputT
|
|
9
10
|
from calfkit._vendor.pydantic_ai.messages import ModelMessage, ModelRequest
|
|
11
|
+
from calfkit._vendor.pydantic_ai.settings import ModelSettings
|
|
10
12
|
from calfkit.client.base import BaseClient
|
|
11
13
|
from calfkit.client.deserialize import _UNSET
|
|
12
14
|
from calfkit.client.invocation_handle import InvocationHandle
|
|
@@ -39,6 +41,7 @@ class Client(BaseClient):
|
|
|
39
41
|
message_history: list[ModelMessage] | None = ...,
|
|
40
42
|
run_args: Sequence[Any] | None = ...,
|
|
41
43
|
deps: dict[str, Any] | None = ...,
|
|
44
|
+
model_settings: ModelSettings | dict[str, Any] | None = ...,
|
|
42
45
|
) -> InvocationHandle[OutputT]: ...
|
|
43
46
|
|
|
44
47
|
@overload
|
|
@@ -54,6 +57,7 @@ class Client(BaseClient):
|
|
|
54
57
|
message_history: list[ModelMessage] | None = ...,
|
|
55
58
|
run_args: Sequence[Any] | None = ...,
|
|
56
59
|
deps: dict[str, Any] | None = ...,
|
|
60
|
+
model_settings: ModelSettings | dict[str, Any] | None = ...,
|
|
57
61
|
) -> InvocationHandle[Any]: ...
|
|
58
62
|
|
|
59
63
|
async def invoke_node(
|
|
@@ -69,6 +73,7 @@ class Client(BaseClient):
|
|
|
69
73
|
message_history: list[ModelMessage] | None = None,
|
|
70
74
|
run_args: Sequence[Any] | None = None,
|
|
71
75
|
deps: dict[str, Any] | None = None,
|
|
76
|
+
model_settings: ModelSettings | dict[str, Any] | None = None,
|
|
72
77
|
) -> InvocationHandle[Any]:
|
|
73
78
|
"""Invoke an agent node asynchronously and return a handle for the reply.
|
|
74
79
|
|
|
@@ -95,11 +100,21 @@ class Client(BaseClient):
|
|
|
95
100
|
method.
|
|
96
101
|
deps: A mapping of dependency keys to values, made available to
|
|
97
102
|
the node's tools at runtime via the session context.
|
|
103
|
+
model_settings: Per-call model settings (e.g. ``{"temperature": 0.0}``)
|
|
104
|
+
that merge over the agent's constructor defaults, which in turn
|
|
105
|
+
merge over the model client's defaults. Must be JSON-serializable
|
|
106
|
+
since it travels over Kafka.
|
|
98
107
|
|
|
99
108
|
Returns:
|
|
100
109
|
An :class:`InvocationHandle` whose ``result()`` resolves to a
|
|
101
110
|
:class:`NodeResult`.
|
|
102
111
|
"""
|
|
112
|
+
if model_settings is not None:
|
|
113
|
+
try:
|
|
114
|
+
json.dumps(model_settings, allow_nan=False)
|
|
115
|
+
except (TypeError, ValueError) as exc:
|
|
116
|
+
raise ValueError(f"model_settings is not JSON-serializable: {exc}. Payload: {model_settings!r}") from exc
|
|
117
|
+
|
|
103
118
|
if correlation_id is None:
|
|
104
119
|
correlation_id = uuid_utils.uuid7().hex
|
|
105
120
|
if reply_topic is None:
|
|
@@ -107,13 +122,21 @@ class Client(BaseClient):
|
|
|
107
122
|
|
|
108
123
|
state = State(message_history=message_history or list(), temp_instructions=temp_instructions)
|
|
109
124
|
state.stage_message(ModelRequest.user_text_prompt(user_prompt))
|
|
125
|
+
overrides = (
|
|
126
|
+
OverridesState(
|
|
127
|
+
override_agent_tools=tool_overrides,
|
|
128
|
+
model_settings=dict(model_settings) if model_settings is not None else None,
|
|
129
|
+
)
|
|
130
|
+
if tool_overrides is not None or model_settings is not None
|
|
131
|
+
else None
|
|
132
|
+
)
|
|
110
133
|
return await self._invoke(
|
|
111
134
|
topic=topic,
|
|
112
135
|
reply_topic=reply_topic,
|
|
113
136
|
correlation_id=correlation_id,
|
|
114
137
|
run_args=run_args,
|
|
115
138
|
state=state,
|
|
116
|
-
overrides=
|
|
139
|
+
overrides=overrides,
|
|
117
140
|
deps=deps,
|
|
118
141
|
output_type=output_type,
|
|
119
142
|
)
|
|
@@ -132,6 +155,7 @@ class Client(BaseClient):
|
|
|
132
155
|
message_history: list[ModelMessage] | None = ...,
|
|
133
156
|
run_args: Sequence[Any] | None = ...,
|
|
134
157
|
deps: dict[str, Any] | None = ...,
|
|
158
|
+
model_settings: ModelSettings | dict[str, Any] | None = ...,
|
|
135
159
|
timeout: float | None = ...,
|
|
136
160
|
) -> NodeResult[OutputT]: ...
|
|
137
161
|
|
|
@@ -148,6 +172,7 @@ class Client(BaseClient):
|
|
|
148
172
|
message_history: list[ModelMessage] | None = ...,
|
|
149
173
|
run_args: Sequence[Any] | None = ...,
|
|
150
174
|
deps: dict[str, Any] | None = ...,
|
|
175
|
+
model_settings: ModelSettings | dict[str, Any] | None = ...,
|
|
151
176
|
timeout: float | None = ...,
|
|
152
177
|
) -> NodeResult[Any]: ...
|
|
153
178
|
|
|
@@ -164,6 +189,7 @@ class Client(BaseClient):
|
|
|
164
189
|
message_history: list[ModelMessage] | None = None,
|
|
165
190
|
run_args: Sequence[Any] | None = None,
|
|
166
191
|
deps: dict[str, Any] | None = None,
|
|
192
|
+
model_settings: ModelSettings | dict[str, Any] | None = None,
|
|
167
193
|
timeout: float | None = None,
|
|
168
194
|
) -> NodeResult[Any]:
|
|
169
195
|
"""Invoke an agent node and await the reply in a single call.
|
|
@@ -193,6 +219,10 @@ class Client(BaseClient):
|
|
|
193
219
|
method.
|
|
194
220
|
deps: A mapping of dependency keys to values, made available to
|
|
195
221
|
the node's tools at runtime via the session context.
|
|
222
|
+
model_settings: Per-call model settings (e.g. ``{"temperature": 0.0}``)
|
|
223
|
+
that merge over the agent's constructor defaults, which in turn
|
|
224
|
+
merge over the model client's defaults. Must be JSON-serializable
|
|
225
|
+
since it travels over Kafka.
|
|
196
226
|
timeout: Maximum seconds to wait for the reply. ``None`` means
|
|
197
227
|
wait indefinitely.
|
|
198
228
|
|
|
@@ -214,5 +244,6 @@ class Client(BaseClient):
|
|
|
214
244
|
message_history=message_history,
|
|
215
245
|
run_args=run_args,
|
|
216
246
|
deps=deps,
|
|
247
|
+
model_settings=model_settings,
|
|
217
248
|
)
|
|
218
249
|
return await handle.result(timeout=timeout)
|
|
@@ -15,6 +15,9 @@ _UNSET: Any = object()
|
|
|
15
15
|
def deserialize_to_node_result(
|
|
16
16
|
envelope: Envelope,
|
|
17
17
|
output_type: type[Any] = _UNSET,
|
|
18
|
+
*,
|
|
19
|
+
strict: bool = True,
|
|
20
|
+
type_adapter: TypeAdapter[Any] | None = None,
|
|
18
21
|
) -> NodeResult[Any]:
|
|
19
22
|
"""Project an ``Envelope`` into a client-facing ``NodeResult``.
|
|
20
23
|
|
|
@@ -26,41 +29,57 @@ def deserialize_to_node_result(
|
|
|
26
29
|
(returned as a raw dict), fall back to ``TextPart.text`` (str).
|
|
27
30
|
* ``str``: extract the first ``TextPart.text``.
|
|
28
31
|
* **anything else**: extract the first ``DataPart.data`` and validate
|
|
29
|
-
it through ``TypeAdapter(output_type)
|
|
32
|
+
it through ``TypeAdapter(output_type)`` (or *type_adapter* if
|
|
33
|
+
provided).
|
|
34
|
+
strict: When ``True`` (default — client semantics), raises
|
|
35
|
+
:class:`DeserializationError` if ``final_output_parts`` is empty or
|
|
36
|
+
doesn't contain the expected part type. When ``False`` (consumer
|
|
37
|
+
semantics), returns ``output=None`` for an empty
|
|
38
|
+
``final_output_parts`` (intermediate hop / tool completion);
|
|
39
|
+
validation errors on *present* parts still propagate.
|
|
40
|
+
type_adapter: An optional pre-built :class:`pydantic.TypeAdapter` to
|
|
41
|
+
use for validating ``DataPart.data`` against *output_type*. When
|
|
42
|
+
``None`` (default), a new adapter is constructed per call.
|
|
43
|
+
Consumers pre-build at wiring time so schema-generation errors
|
|
44
|
+
surface once at construction rather than per envelope.
|
|
30
45
|
|
|
31
46
|
Returns:
|
|
32
|
-
A ``NodeResult`` whose ``.output`` is typed according to *output_type
|
|
47
|
+
A ``NodeResult`` whose ``.output`` is typed according to *output_type*
|
|
48
|
+
(or ``None`` when ``strict=False`` and no parts are present).
|
|
33
49
|
|
|
34
50
|
Raises:
|
|
35
51
|
DeserializationError: If the expected content part is not found in
|
|
36
|
-
``final_output_parts
|
|
52
|
+
``final_output_parts`` (and either ``strict=True`` or the parts
|
|
53
|
+
list is non-empty but lacks the expected shape).
|
|
54
|
+
pydantic.ValidationError: If ``output_type`` is provided and the
|
|
55
|
+
matching ``DataPart.data`` doesn't validate against it.
|
|
56
|
+
pydantic.PydanticSchemaGenerationError: If ``type_adapter`` is ``None``
|
|
57
|
+
and ``output_type`` cannot be schematized by :class:`TypeAdapter`.
|
|
37
58
|
"""
|
|
38
59
|
state = envelope.context.state
|
|
39
|
-
output_parts = state.final_output_parts
|
|
40
|
-
message_history = state.message_history
|
|
41
|
-
metadata = state.metadata
|
|
42
60
|
correlation_id = envelope.context.deps.correlation_id
|
|
43
61
|
|
|
44
|
-
|
|
62
|
+
if not state.final_output_parts and not strict:
|
|
63
|
+
output: Any = None
|
|
64
|
+
else:
|
|
65
|
+
output = _extract_output(state.final_output_parts, output_type, type_adapter=type_adapter)
|
|
45
66
|
|
|
46
67
|
return NodeResult(
|
|
47
68
|
output=output,
|
|
48
|
-
|
|
49
|
-
message_history=message_history,
|
|
50
|
-
metadata=metadata,
|
|
69
|
+
state=state,
|
|
51
70
|
correlation_id=correlation_id,
|
|
52
71
|
emitter_node_id=envelope.context.emitter_node_id,
|
|
53
72
|
emitter_node_kind=envelope.context.emitter_node_kind,
|
|
54
73
|
)
|
|
55
74
|
|
|
56
75
|
|
|
57
|
-
def _extract_output(parts: list[Any], output_type: type[Any]) -> Any:
|
|
76
|
+
def _extract_output(parts: list[Any], output_type: type[Any], type_adapter: TypeAdapter[Any] | None = None) -> Any:
|
|
58
77
|
"""Extract and optionally deserialize the output from content parts."""
|
|
59
78
|
if output_type is _UNSET:
|
|
60
79
|
return _extract_auto(parts)
|
|
61
80
|
if output_type is str:
|
|
62
81
|
return _extract_text(parts)
|
|
63
|
-
return _extract_data(parts, output_type)
|
|
82
|
+
return _extract_data(parts, output_type, type_adapter=type_adapter)
|
|
64
83
|
|
|
65
84
|
|
|
66
85
|
def _extract_auto(parts: list[Any]) -> Any:
|
|
@@ -82,10 +101,15 @@ def _extract_text(parts: list[Any]) -> str:
|
|
|
82
101
|
raise DeserializationError("No TextPart found in final_output_parts; expected output_type=str.")
|
|
83
102
|
|
|
84
103
|
|
|
85
|
-
def _extract_data(parts: list[Any], output_type: type[Any]) -> Any:
|
|
86
|
-
"""Extract the first DataPart.data and validate via TypeAdapter.
|
|
104
|
+
def _extract_data(parts: list[Any], output_type: type[Any], type_adapter: TypeAdapter[Any] | None = None) -> Any:
|
|
105
|
+
"""Extract the first DataPart.data and validate via TypeAdapter.
|
|
106
|
+
|
|
107
|
+
Uses *type_adapter* if provided; otherwise constructs a new one (which may
|
|
108
|
+
raise :class:`pydantic.PydanticSchemaGenerationError` if *output_type* is
|
|
109
|
+
unschematizable).
|
|
110
|
+
"""
|
|
87
111
|
for part in parts:
|
|
88
112
|
if isinstance(part, DataPart):
|
|
89
|
-
adapter
|
|
113
|
+
adapter = type_adapter if type_adapter is not None else TypeAdapter(output_type)
|
|
90
114
|
return adapter.validate_python(part.data)
|
|
91
115
|
raise DeserializationError(f"No DataPart found in final_output_parts; expected output_type={getattr(output_type, '__name__', str(output_type))}.")
|
|
@@ -30,7 +30,13 @@ class InvocationHandle(Generic[OutputT]):
|
|
|
30
30
|
Raises:
|
|
31
31
|
asyncio.TimeoutError: If *timeout* elapses before a reply arrives.
|
|
32
32
|
RuntimeError: If this handle was created without a future (fire-and-forget).
|
|
33
|
-
DeserializationError: If the expected output part type is missing
|
|
33
|
+
DeserializationError: If the expected output part type is missing
|
|
34
|
+
from ``final_output_parts``.
|
|
35
|
+
pydantic.ValidationError: If ``output_type`` is provided and the
|
|
36
|
+
reply's ``DataPart.data`` doesn't validate against it.
|
|
37
|
+
pydantic.PydanticSchemaGenerationError: If ``output_type`` cannot
|
|
38
|
+
be schematized by :class:`pydantic.TypeAdapter` (e.g. an
|
|
39
|
+
unsupported generic or non-type value).
|
|
34
40
|
"""
|
|
35
41
|
if self._future is None:
|
|
36
42
|
raise RuntimeError("This handle has no associated future — was the client's reply dispatcher configured?")
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Generic
|
|
5
|
+
|
|
6
|
+
from calfkit._types import OutputT
|
|
7
|
+
from calfkit._vendor.pydantic_ai.messages import ModelMessage
|
|
8
|
+
from calfkit.models import ContentPart, State
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class NodeResult(Generic[OutputT]):
|
|
13
|
+
"""Client-facing projection of the session state after a node returns.
|
|
14
|
+
|
|
15
|
+
A ``NodeResult`` is what callers receive in two places:
|
|
16
|
+
|
|
17
|
+
* :meth:`Client.execute_node` / :meth:`InvocationHandle.result` — the
|
|
18
|
+
final reply from an agent invocation.
|
|
19
|
+
* The user function of a :class:`ConsumerNodeDef` — one per envelope on
|
|
20
|
+
every subscribed topic, including intermediate hops.
|
|
21
|
+
|
|
22
|
+
The ``state`` field is the full session :class:`~calfkit.models.State` at
|
|
23
|
+
the moment this envelope was published, exposing message history, in-flight
|
|
24
|
+
tool calls/results, application metadata, runtime overrides, and any other
|
|
25
|
+
state fields. ``message_history``, ``output_parts``, and ``metadata`` are
|
|
26
|
+
convenience properties that read through ``state``.
|
|
27
|
+
|
|
28
|
+
Treat ``NodeResult`` and its ``state`` as read-only. The dataclass is
|
|
29
|
+
frozen, but ``state`` is a mutable Pydantic model:
|
|
30
|
+
|
|
31
|
+
* **Consumer path**: the consumer never republishes (no ``publish_topic``),
|
|
32
|
+
so mutations have no observable downstream effect. They can still
|
|
33
|
+
surprise other code holding the same ``NodeResult`` instance.
|
|
34
|
+
* **Client path** (:meth:`Client.execute_node` / :meth:`InvocationHandle.result`):
|
|
35
|
+
the caller's lifetime owns the instance — mutations are caller-visible
|
|
36
|
+
and may corrupt any other code holding a parallel reference (caches,
|
|
37
|
+
retry layers, etc.).
|
|
38
|
+
|
|
39
|
+
``NodeResult`` is intentionally unhashable (``__hash__ = None``): the
|
|
40
|
+
underlying ``state`` field is a mutable Pydantic model and cannot be
|
|
41
|
+
placed in a set or used as a dict key safely. Use ``correlation_id`` if
|
|
42
|
+
you need a hashable identifier.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
output: OutputT | None
|
|
46
|
+
"""Deserialized final output (typed via ``output_type``).
|
|
47
|
+
|
|
48
|
+
``None`` on intermediate hops — envelopes whose ``state.final_output_parts``
|
|
49
|
+
is empty (e.g. agent hops mid-tool-call, tool completions). Populated when
|
|
50
|
+
the upstream node emitted a terminal envelope with final output parts.
|
|
51
|
+
Client-side strict-mode results (the default) always have ``output``
|
|
52
|
+
populated; consumer-side results may not.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
state: State
|
|
56
|
+
"""Full session state at this hop. Includes:
|
|
57
|
+
|
|
58
|
+
* ``message_history`` — cumulative conversation
|
|
59
|
+
* ``final_output_parts`` — agent's structured/text output (empty on
|
|
60
|
+
intermediate hops)
|
|
61
|
+
* ``tool_calls`` / ``tool_results`` — in-flight tool batch (keyed by
|
|
62
|
+
``tool_call_id``)
|
|
63
|
+
* ``metadata`` — application-level metadata
|
|
64
|
+
* ``overrides`` — agent tool overrides applied to this invocation (or
|
|
65
|
+
``None`` if unset)
|
|
66
|
+
* ``uncommitted_message`` / ``temp_instructions`` — agent-loop scratch;
|
|
67
|
+
usually ``None`` on terminal hops, may be populated mid-loop and is not
|
|
68
|
+
part of the public contract
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
correlation_id: str
|
|
72
|
+
"""The correlation ID that ties this result to its invocation."""
|
|
73
|
+
|
|
74
|
+
emitter_node_id: str | None = None
|
|
75
|
+
"""Node id of the node that emitted this reply (sourced from the
|
|
76
|
+
``x-calf-emitter`` Kafka header). May be ``None`` if the upstream
|
|
77
|
+
producer didn't stamp the header (e.g. a non-calfkit publisher)."""
|
|
78
|
+
|
|
79
|
+
emitter_node_kind: str | None = None
|
|
80
|
+
"""Coarse classification of the emitter (one of ``NodeKind``), sourced
|
|
81
|
+
from the ``x-calf-emitter-kind`` Kafka header. May be ``None`` if not
|
|
82
|
+
stamped."""
|
|
83
|
+
|
|
84
|
+
# NodeResult holds a mutable Pydantic model (state); the dataclass-
|
|
85
|
+
# synthesized __hash__ would recursively try to hash unhashable fields and
|
|
86
|
+
# raise at use-time. Declare unhashability explicitly so static type
|
|
87
|
+
# checkers and runtime introspection agree.
|
|
88
|
+
__hash__ = None # type: ignore[assignment]
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def output_parts(self) -> list[ContentPart]:
|
|
92
|
+
"""Convenience: ``state.final_output_parts``."""
|
|
93
|
+
return self.state.final_output_parts
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def message_history(self) -> list[ModelMessage]:
|
|
97
|
+
"""Convenience: ``state.message_history``."""
|
|
98
|
+
return self.state.message_history
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def metadata(self) -> Any:
|
|
102
|
+
"""Convenience: ``state.metadata``."""
|
|
103
|
+
return self.state.metadata
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
# Reject empty subscribe_topics for every node kind (Agent, Consumer,
|
|
17
|
+
# Tool, …). Lives here rather than in ``BaseNodeDef.__init__`` because
|
|
18
|
+
# ``@dataclass`` subclasses like ``BaseToolNodeDef`` get an
|
|
19
|
+
# auto-generated ``__init__`` that bypasses ``BaseNodeDef.__init__``
|
|
20
|
+
# entirely; ``__post_init__`` is the one hook every subclass runs.
|
|
21
|
+
# Without this guard, ``Worker.register_handlers`` would still add
|
|
22
|
+
# ``_return_topic`` to the subscriber set (issue #141 fix), so the
|
|
23
|
+
# node would "register" successfully but have no public inbox — a
|
|
24
|
+
# silent zombie consumer.
|
|
25
|
+
if not self.subscribe_topics:
|
|
26
|
+
raise ValueError(f"node {self.node_id!r} requires at least one subscribe_topic; got empty list")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class BaseToolNodeSchema(BaseNodeSchema):
|
|
31
|
+
_: KW_ONLY
|
|
32
|
+
tool_schema: ToolDefinition
|
|
@@ -23,7 +23,8 @@ class OverridesState(BaseAgentActivityState):
|
|
|
23
23
|
"""State for storing any override objects"""
|
|
24
24
|
|
|
25
25
|
model_config = ConfigDict(extra="ignore")
|
|
26
|
-
override_agent_tools: list[BaseToolNodeSchema] | None
|
|
26
|
+
override_agent_tools: list[BaseToolNodeSchema] | None = None
|
|
27
|
+
model_settings: dict[str, Any] | None = None
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
class CoreMessageState(BaseAgentActivityState):
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from calfkit.nodes.agent import Agent, BaseAgentNodeDef
|
|
2
2
|
from calfkit.nodes.base import BaseNodeDef, GateFunction
|
|
3
|
+
from calfkit.nodes.consumer import ConsumerFn, ConsumerNodeDef, consumer
|
|
3
4
|
from calfkit.nodes.node import NodeDef
|
|
4
5
|
from calfkit.nodes.tool import BaseToolNodeDef, ToolNodeDef, agent_tool
|
|
5
6
|
|
|
@@ -8,8 +9,11 @@ __all__ = [
|
|
|
8
9
|
"BaseAgentNodeDef",
|
|
9
10
|
"BaseNodeDef",
|
|
10
11
|
"BaseToolNodeDef",
|
|
12
|
+
"ConsumerFn",
|
|
13
|
+
"ConsumerNodeDef",
|
|
11
14
|
"GateFunction",
|
|
12
15
|
"NodeDef",
|
|
13
16
|
"ToolNodeDef",
|
|
14
17
|
"agent_tool",
|
|
18
|
+
"consumer",
|
|
15
19
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from collections.abc import Callable
|
|
3
|
-
from typing import Any, ClassVar, Generic
|
|
3
|
+
from typing import Any, ClassVar, Generic, cast
|
|
4
4
|
|
|
5
5
|
from calfkit._protocol import NodeKind
|
|
6
6
|
from calfkit._types import AgentOutputT
|
|
@@ -8,6 +8,7 @@ from calfkit._vendor.pydantic_ai import Agent as InternalAgentLoop
|
|
|
8
8
|
from calfkit._vendor.pydantic_ai import DeferredToolRequests
|
|
9
9
|
from calfkit._vendor.pydantic_ai.messages import RetryPromptPart
|
|
10
10
|
from calfkit._vendor.pydantic_ai.output import OutputSpec
|
|
11
|
+
from calfkit._vendor.pydantic_ai.settings import ModelSettings
|
|
11
12
|
from calfkit._vendor.pydantic_ai.tools import DeferredToolResults
|
|
12
13
|
from calfkit._vendor.pydantic_ai.toolsets.external import ExternalToolset
|
|
13
14
|
from calfkit.models import Call, DataPart, NodeResult, ReturnCall, State, TailCall, TextPart
|
|
@@ -42,6 +43,7 @@ class BaseAgentNodeDef(
|
|
|
42
43
|
model_client: PydanticModelClient,
|
|
43
44
|
final_output_type: OutputSpec[AgentOutputT] = str, # type: ignore[assignment]
|
|
44
45
|
sequential_only_mode: bool = False,
|
|
46
|
+
model_settings: ModelSettings | dict[str, Any] | None = None,
|
|
45
47
|
):
|
|
46
48
|
self.final_output_type = final_output_type
|
|
47
49
|
self.system_prompt = system_prompt
|
|
@@ -55,7 +57,12 @@ class BaseAgentNodeDef(
|
|
|
55
57
|
super().__init__(node_id=node_id, subscribe_topics=subscribe_topics, publish_topic=publish_topic, gates=gates)
|
|
56
58
|
|
|
57
59
|
self._agent_loop: InternalAgentLoop[dict[str, Any], AgentOutputT | DeferredToolRequests] = InternalAgentLoop(
|
|
58
|
-
model_client,
|
|
60
|
+
model_client,
|
|
61
|
+
name=self.name,
|
|
62
|
+
output_type=[final_output_type, DeferredToolRequests],
|
|
63
|
+
deps_type=dict,
|
|
64
|
+
instructions=system_prompt,
|
|
65
|
+
model_settings=cast(ModelSettings | None, model_settings),
|
|
59
66
|
)
|
|
60
67
|
|
|
61
68
|
def _parallel_state_aggregation(self, ctx: SessionRunContext) -> None:
|
|
@@ -124,12 +131,14 @@ class BaseAgentNodeDef(
|
|
|
124
131
|
if ctx.state.uncommitted_message is not None:
|
|
125
132
|
ctx.state.commit_message_to_history()
|
|
126
133
|
|
|
134
|
+
run_model_settings = cast(ModelSettings | None, ctx.state.overrides.model_settings) if ctx.state.overrides is not None else None
|
|
127
135
|
result = await self._agent_loop.run(
|
|
128
136
|
message_history=ctx.state.message_history,
|
|
129
137
|
instructions=ctx.state.temp_instructions,
|
|
130
138
|
toolsets=[ExternalToolset([tool.tool_schema for tool in tools_registry.values()])],
|
|
131
139
|
deps=ctx.deps.provided_deps, # None valid when AgentDepsT=NoneType
|
|
132
140
|
deferred_tool_results=tool_results,
|
|
141
|
+
model_settings=run_model_settings,
|
|
133
142
|
)
|
|
134
143
|
if isinstance(result.output, DeferredToolRequests):
|
|
135
144
|
# The LLM called one or more tools
|
|
@@ -175,7 +184,7 @@ class BaseAgentNodeDef(
|
|
|
175
184
|
# TODO: maybe consider a node retry return type that doesn't require round trip to itself.
|
|
176
185
|
# Tailcall to itself is a roundtrip.
|
|
177
186
|
logger.debug("[%s] all tool calls invalid, TailCall retry node=%s", ctx.deps.correlation_id[:8], self.name)
|
|
178
|
-
return TailCall[State](target_topic=self.
|
|
187
|
+
return TailCall[State](target_topic=self._return_topic, state=ctx.state)
|
|
179
188
|
|
|
180
189
|
pending_tool_calls = [tc for tc in latest_tool_calls if tc.tool_call_id not in ctx.state.tool_results]
|
|
181
190
|
|