flowra 0.0.92.dev108__tar.gz → 0.0.92.dev109__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.
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/CHANGELOG.md +9 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/PKG-INFO +1 -1
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/context7.json +1 -1
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/observability.md +1 -1
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/tools.md +3 -2
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/turn/agent.py +11 -10
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/turn/tool_call/agent.py +11 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/version.py +1 -1
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/test_tool_call_agent.py +69 -2
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/.agents/skills/source-command-update-pricing/SKILL.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/.claude/commands/update-pricing.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/.env.example +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/.github/CODEOWNERS +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/.github/workflows/master.yml +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/.github/workflows/publish.yml +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/.github/workflows/pull_request.yml +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/.gitignore +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/.python-version +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/AGENTS.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/CLAUDE.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/LICENSE +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/Makefile +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/README.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/agents.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/architecture.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/getting-started.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/llm.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/patterns.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/agent_async_init.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/agent_tool_errors.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/anthropic_web_search.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/e2e_security.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/enum_slot_values.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/final_tools.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/langchain_comparison.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/lifecycle_hooks_and_conversation.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/llm_retry_backoff.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/named_scope_lazy_hydration.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/named_scope_type_registry.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/named_scopes.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/pricing_complexity.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/pydantic_ai_comparison.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/strands_comparison.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/tool_error_signals.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/tool_errors_vs_failures.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/tool_registry_redesign.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/tool_search_tool.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/voice_stt.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/review_prompts/step1_structure.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/review_prompts/step2_code_style.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/review_prompts/step3_documentation.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/review_prompts/step4_doc_readability.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/review_prompts/step5_doc_audit.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/review_prompts/step6_tests.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/todo.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/video_script.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/TRACING_COMBINATIONS.md +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/agent_as_tool.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/app_agent.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/console_chat.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/docs/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/docs/agents_custom.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/docs/agents_parallel.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/docs/getting_started_chat.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/docs/getting_started_streaming.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/docs/getting_started_tools.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/docs/llm_streaming.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/docs/llm_structured_output.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/docs/tools_service_injection.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/escalation.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/llm_logging.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/llm_routing.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/menu_agent.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/menu_agent_class.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/mlflow_demo.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/mlflow_dual_export_demo.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/mlflow_nested_demo.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/mlflow_otel_both_demo.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/mlflow_otel_nested_demo.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/mlflow_parallel_demo.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/model_registry.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/otel_demo.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/otel_jaeger_demo.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/otel_nested_demo.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/otel_visualize.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/race.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/span_crash_demo.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/span_demo.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/system_prompt.txt +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/tools/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/tools/calculator.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/tools/random_numbers.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/tools/switch_model.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/examples/tui_chat.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/_sentinel.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/_typing.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/_path.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/actions.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/agent.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/agent_arg.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/compile/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/compile/compiler.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/compile/contract.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/compile/init_params.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/compile/instance.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/compile/step_params.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/compile/step_validation.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/compile/steps.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/compile/type_registry.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/model.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/registry.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/shared_persistent.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/spawn.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/step.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/step_helpers.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/flow/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/flow/context.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/flow/flowing_registry.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/flow/hooks.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/flow/interrupt.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/flow/interrupt_helpers.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/flow/timeout.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/runtime/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/runtime/engine.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/runtime/execution.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/runtime/instance_factory.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/runtime/key_locks.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/runtime/runtime.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/runtime/scope.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/runtime/serialization.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/runtime/spans.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/runtime/spawn_satisfaction.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/runtime/spawn_tree.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/runtime/visibility.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/services.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/session_state.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/state/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/state/markers.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/state/persistent.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/state/store.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/state/values.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/storage/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/storage/file.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/storage/in_memory.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/storage/session_storage.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/ext/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/ext/_session.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/ext/mlflow.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/ext/otel.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/chat/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/chat/agent.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/chat/config.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/chat/conversation.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/chat/hook_events.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/chat/spec.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/config_value.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/llm_call/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/llm_call/agent.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/llm_call/config.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/llm_call/spec.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/llm_caller.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/llm_config.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/observability/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/observability/llm_hooks.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/turn/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/turn/config.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/turn/context.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/turn/hook_events.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/turn/spec.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/turn/tool_call/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/turn/tool_call/agent_tool.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/lib/turn/tool_call/context.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/_schema_nodes.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/base.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/blocks.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/errors.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/ext/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/ext/anthropic/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/ext/anthropic/cache.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/ext/anthropic/presets.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/ext/anthropic/tool_search.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/ext/anthropic/web_search.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/ext/openai/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/ext/openai/tool_search.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/gemini_schema.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/messages.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/openai_schema.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/pricing/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/pricing/bundled.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/pricing/data/custom/anthropic.json +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/pricing/data/custom/vertex.json +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/pricing/data/generated/anthropic.json +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/pricing/data/generated/azure_openai.json +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/pricing/data/generated/bedrock.json +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/pricing/data/generated/gemini_api.json +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/pricing/data/generated/inception.json +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/pricing/data/generated/openai.json +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/pricing/data/generated/vertex.json +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/pricing/model_pricing.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/pricing/price_list.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/provider.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/providers/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/providers/_openai_common.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/providers/anthropic.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/providers/anthropic_bedrock.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/providers/anthropic_foundry.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/providers/anthropic_vertex.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/providers/azure_openai.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/providers/google_vertex.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/providers/openai.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/providers/openai_responses.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/request.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/response.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/schema_formatting.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/schema_validation.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/stream.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/llm/tools.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/py.typed +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/tools/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/tools/_mcp_content.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/tools/_mcp_headers.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/tools/local_tool.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/tools/mcp_connection.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/tools/result_format.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/tools/tool_arg.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/tools/tool_filter.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/tools/tool_registry.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/tools/types.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/pyproject.toml +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/definition/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/definition/compile/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/definition/compile/test_compile.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/definition/test_actions.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/definition/test_agent.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/definition/test_registry.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/definition/test_spawn.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/definition/test_step_helpers.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/flow/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/flow/test_context.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/flow/test_flowing_registry.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/flow/test_flowing_registry_tasks.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/flow/test_flowing_sync.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/flow/test_hooks.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/flow/test_interrupt.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/flow/test_interruptible.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/flow/test_spans.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/flow/test_timeout.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/flow/test_with_interrupt.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/runtime/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/runtime/test_engine.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/runtime/test_engine_spans.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/runtime/test_ephemeral_isolation.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/runtime/test_hook_context.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/runtime/test_named_scope_persistence.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/runtime/test_named_scope_registry.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/runtime/test_named_scopes.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/runtime/test_parallel_failure.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/runtime/test_persistence.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/runtime/test_primitive_join.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/runtime/test_runtime.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/runtime/test_scope.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/runtime/test_serialization.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/runtime/test_spec_in_constructor.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/state/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/state/test_values.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/storage/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/storage/test_file.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/storage/test_in_memory.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/test_missing_scenarios.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/agent/test_session_state.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/examples/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/examples/test_menu_agent.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/ext/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/ext/_mlflow.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/ext/test_mlflow.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/ext/test_mlflow_cross_task.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/ext/test_otel.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/_helpers.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/ext/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/ext/anthropic/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/ext/anthropic/test_anthropic.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/ext/openai/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/ext/openai/test_openai_tool_search.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/test_chat_agent.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/test_chat_spec.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/test_config_value.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/test_conversation.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/test_llm_call_agent.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/test_llm_caller.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/test_matches_tool_filter.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/test_tool_call_agent_call_agent.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/test_turn_agent.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/turn/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/turn/test_per_call_headers.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/turn/test_render.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/lib/turn/test_tool_errors.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/pricing/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/pricing/test_bundled.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/pricing/test_model_pricing.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/pricing/test_price_list.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/_mock_http.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/_raw_body.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/_tool_result_blocks.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_anthropic.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_anthropic_bedrock.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_anthropic_bedrock_e2e.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_anthropic_e2e.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_anthropic_foundry.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_anthropic_foundry_e2e.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_anthropic_vertex.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_anthropic_vertex_locations_e2e.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_azure_openai_e2e.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_cross_provider_e2e.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_errors.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_fallback_e2e.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_google_vertex.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_google_vertex_e2e.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_history_across_models.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_openai_e2e.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_openai_provider.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_openai_responses.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_openai_responses_e2e.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_refusals.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/providers/test_schemas_e2e.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/test_blocks.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/test_cost_breakdown.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/test_gemini_schema.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/test_metadata.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/test_openai_schema.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/test_response.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/test_schema_formatting.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/test_schema_nodes.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/test_schema_validation.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/llm/test_stream.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/scripts/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/scripts/test_sync_pricing.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/test_typing.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/tools/__init__.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/tools/test_local_tool.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/tools/test_mcp_connection.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/tools/test_mcp_headers.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/tools/test_result_format.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/tools/test_tool_registry.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tests/tools/test_types.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/tools/sync_pricing.py +0 -0
- {flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/uv.lock +0 -0
|
@@ -24,6 +24,15 @@ and this project adheres to [Semantic Versioning](https://semver.org).
|
|
|
24
24
|
- **`AfterToolCallEvent.prefilled`**: true for a call answered in
|
|
25
25
|
`BeforeToolCallsEvent`, so a handler can tell that the tool did not run.
|
|
26
26
|
|
|
27
|
+
### Changed
|
|
28
|
+
- **A call's answer carries an `error_kind` exactly when it is an error.**
|
|
29
|
+
`ToolCallResult` sets `ToolErrorKind.EXECUTION` for an error that came
|
|
30
|
+
without a kind — a `ToolResult(is_error=True)` a tool or an agent tool
|
|
31
|
+
returns, "Tool execution interrupted" — and drops a kind from an answer that
|
|
32
|
+
is not an error. `AfterToolCallEvent.error_kind` and `ToolCallSpan.error_kind`
|
|
33
|
+
follow, so a consumer telling an error by its kind no longer reads such an
|
|
34
|
+
error as a success.
|
|
35
|
+
|
|
27
36
|
## [0.0.91] - 2026-10-20
|
|
28
37
|
|
|
29
38
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: flowra
|
|
3
|
-
Version: 0.0.92.
|
|
3
|
+
Version: 0.0.92.dev109
|
|
4
4
|
Summary: Flowra — flow infrastructure for building stateful LLM agents
|
|
5
5
|
Project-URL: Repository, https://github.com/anna-money/flowra
|
|
6
6
|
Project-URL: Changelog, https://github.com/anna-money/flowra/blob/master/CHANGELOG.md
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"McpConnection connects to remote MCP servers for tool discovery over Streamable HTTP. McpConnection.snapshot() returns a frozen ToolRegistry; the tool list is refreshed in the background (server ttlMs, 60s default) with add_on_tools_changed(callback) for changes, and tools with invalid x-mcp-header annotations are dropped. Speaks both MCP protocol eras and detects on connect: stateless 2026-07-28 (no handshake/session; x-mcp-header args mirrored into Mcp-Param-* headers) and handshake-based 2025-03-26 to 2025-11-25 (any other version fails connect()). McpConnection.session (McpSession) exposes stateless: bool, protocol_version and session_id. A tool call needing client input (MRTR) returns a tool error; subscriptions/listen and MRTR are not implemented. McpConnection.call_tool(name, args, headers=..., meta=...) adds per-call HTTP headers and params._meta (kept on the reconnect retry; Content-Type/Accept/Mcp-* headers and the protocol's io.modelcontextprotocol/* _meta keys raise ValueError)",
|
|
49
49
|
"ToolCallInfo (flowra.tools) carries per-call headers and meta, empty by default — flowra sets none itself. Fill them in a BeforeToolCallEvent handler (event.data.headers / event.data.meta); they are stored in ToolCallSpec, so a resumed call gets the same values (generate idempotency keys there, not in the tool). ToolCallAgent passes ToolCallInfo in services: MCP snapshot tools send them with tools/call, a @tool reads them by declaring an info: ToolCallInfo parameter",
|
|
50
50
|
"ToolRegistry is immutable, accepts @tool-decorated functions or ExecutableTools: ToolRegistry([my_func, other_func])",
|
|
51
|
-
"TOOL ERRORS: the executor built by @tool catches the decorated function's exceptions (and argument validation/service binding errors) and returns ToolResult(content='Error: {e}', is_error=True, error_kind=EXECUTION, or SCHEMA_VALIDATION for invalid arguments) — so registry.execute and a direct get_tool(f).executor(...) call both return the error result. A ToolResult the function returns is the answer as it is (not serialized); ToolResult.content is str | list[TextBlock | MediaBlock], and ToolCallAgent puts its blocks into the ToolResultBlock as they are (metadata and transient kept; a str is one TextBlock; ToolResult.blocks / ToolResult.text). A '-> ToolResult' annotation gives no output_schema; '-> Answer | ToolResult' gives Answer's. raise ToolError(content, error_kind=EXECUTION) (str or blocks) to answer with an error the tool chose: the executor returns exc.result (is_error=True, content as written, no 'Error: ' prefix, no log); only the @tool executor answers it — an agent behind an agent tool returns ToolResult(is_error=True) instead, and a ToolError raised in an agent fails the run like any exception. InterruptedError is the exception: the executor raises it, and ToolCallAgent answers 'Tool execution interrupted'. ToolRegistry.execute does not catch: an exception from code around an executor (a wrapper via dataclasses.replace(tool, executor=...), a custom ExecutableTool executor) propagates through ToolCallAgent to AgentRuntime.run/resume as itself (session stays resumable); return ToolResult(is_error=True) from such code to answer the model. ToolCallAgent answers InterruptedError raised around a tool with 'Tool execution interrupted'. McpConnection never raises for a call, except ValueError for invalid per-call headers/_meta (set by a BeforeToolCallEvent handler, it fails the run and a resume repeats it, since the values are stored with the call): unknown/removed tools → UNKNOWN_TOOL; a result's MCP content comes back as blocks (text and resource text → TextBlock; image, audio and resource blob → MediaBlock; resource_link and unknown types → TextBlock with the block as JSON), with what the flowra block does not carry in metadata['mcp'] (text/image/audio: every field but type, content (text/data) and, for media, mimeType (annotations, _meta, any other), no metadata when none, so plain text is a plain TextBlock; resource: the whole block but the resource's text/blob, uri and mimeType under metadata['mcp']['resource']; resource_link and unknown types: type, annotations, _meta); a malformed block makes the response invalid; isError → the server's blocks, JSON-RPC errors → the server's text; transport failures → 'temporarily unavailable' (retried once if the reconnect succeeds); invalid responses → 'returned an invalid response', not retried. When one of several parallel steps (e.g. parallel tool calls) raises, the engine cancels and awaits its siblings, then re-raises the original exception (no ExceptionGroup). Agent tool executors (make_agent_tool/@agent_tool) answer invalid arguments with SCHEMA_VALIDATION/EXECUTION results like @tool; with valid arguments, one called outside ToolCallAgent raises RuntimeError",
|
|
51
|
+
"TOOL ERRORS: the executor built by @tool catches the decorated function's exceptions (and argument validation/service binding errors) and returns ToolResult(content='Error: {e}', is_error=True, error_kind=EXECUTION, or SCHEMA_VALIDATION for invalid arguments) — so registry.execute and a direct get_tool(f).executor(...) call both return the error result. A ToolResult the function returns is the answer as it is (not serialized), except that what hooks and spans see (ToolCallResult, AfterToolCallEvent, ToolCallSpan) carries an error_kind exactly when it is an error — EXECUTION for an error given without one, None for a non-error; ToolResult.content is str | list[TextBlock | MediaBlock], and ToolCallAgent puts its blocks into the ToolResultBlock as they are (metadata and transient kept; a str is one TextBlock; ToolResult.blocks / ToolResult.text). A '-> ToolResult' annotation gives no output_schema; '-> Answer | ToolResult' gives Answer's. raise ToolError(content, error_kind=EXECUTION) (str or blocks) to answer with an error the tool chose: the executor returns exc.result (is_error=True, content as written, no 'Error: ' prefix, no log); only the @tool executor answers it — an agent behind an agent tool returns ToolResult(is_error=True) instead, and a ToolError raised in an agent fails the run like any exception. InterruptedError is the exception: the executor raises it, and ToolCallAgent answers 'Tool execution interrupted'. ToolRegistry.execute does not catch: an exception from code around an executor (a wrapper via dataclasses.replace(tool, executor=...), a custom ExecutableTool executor) propagates through ToolCallAgent to AgentRuntime.run/resume as itself (session stays resumable); return ToolResult(is_error=True) from such code to answer the model. ToolCallAgent answers InterruptedError raised around a tool with 'Tool execution interrupted'. McpConnection never raises for a call, except ValueError for invalid per-call headers/_meta (set by a BeforeToolCallEvent handler, it fails the run and a resume repeats it, since the values are stored with the call): unknown/removed tools → UNKNOWN_TOOL; a result's MCP content comes back as blocks (text and resource text → TextBlock; image, audio and resource blob → MediaBlock; resource_link and unknown types → TextBlock with the block as JSON), with what the flowra block does not carry in metadata['mcp'] (text/image/audio: every field but type, content (text/data) and, for media, mimeType (annotations, _meta, any other), no metadata when none, so plain text is a plain TextBlock; resource: the whole block but the resource's text/blob, uri and mimeType under metadata['mcp']['resource']; resource_link and unknown types: type, annotations, _meta); a malformed block makes the response invalid; isError → the server's blocks, JSON-RPC errors → the server's text; transport failures → 'temporarily unavailable' (retried once if the reconnect succeeds); invalid responses → 'returned an invalid response', not retried. When one of several parallel steps (e.g. parallel tool calls) raises, the engine cancels and awaits its siblings, then re-raises the original exception (no ExceptionGroup). Agent tool executors (make_agent_tool/@agent_tool) answer invalid arguments with SCHEMA_VALIDATION/EXECUTION results like @tool; with valid arguments, one called outside ToolCallAgent raises RuntimeError",
|
|
52
52
|
"ToolRegistry.builder() returns ToolRegistryBuilder with .add(source, prefix=..., filter=..., on_duplicate='error'|'skip') and .build() for composing registries from multiple sources. filter is a single Callable[[ExecutableTool], bool]; use the `tool_filter` module for common cases: tool_filter.INCLUDE_SUFFIX(*names) whitelists, tool_filter.EXCLUDE_SUFFIX(*names) blacklists (both match exact name or __name suffix), tool_filter.ALL_OF(*filters)/tool_filter.ANY_OF(*filters) compose. Import: from flowra.tools import tool_filter",
|
|
53
53
|
"Duplicate tool names are detected eagerly at the ToolRegistryBuilder.add() call where the collision is introduced. Default on_duplicate='error' raises ValueError; on_duplicate='skip' silently drops repeats (first occurrence wins). ToolRegistry(...) constructor also raises on duplicate names. OnDuplicate type alias is exported from flowra.tools",
|
|
54
54
|
"ToolRegistry is a ConfigValue[ToolRegistry | None] in ChatConfig and TurnConfig (field name: tools)",
|
|
@@ -82,7 +82,7 @@ run, and the others run as usual. An answered call gets no `BeforeToolCallEvent`
|
|
|
82
82
|
and no `ToolCallSpan`; its result goes through `AfterToolCallEvent` (with
|
|
83
83
|
`prefilled=True`) and `max_consecutive_errors` like any other, in the message's
|
|
84
84
|
order. An error answered without an `error_kind` arrives with
|
|
85
|
-
`ToolErrorKind.EXECUTION
|
|
85
|
+
`ToolErrorKind.EXECUTION`, as any error a tool gives without one does. A key that is not the id of a call in the message
|
|
86
86
|
fails the step with `ValueError`.
|
|
87
87
|
|
|
88
88
|
```python
|
|
@@ -684,8 +684,9 @@ async def respond(params: RespondParams) -> Sent:
|
|
|
684
684
|
`ToolError(content, error_kind=ToolErrorKind.EXECUTION)` takes a string or a
|
|
685
685
|
list of blocks; `exc.result` is the `ToolResult` it stands for. Returning
|
|
686
686
|
`ToolResult(content=..., is_error=True, error_kind=ToolErrorKind.EXECUTION)`
|
|
687
|
-
gives the same answer
|
|
688
|
-
|
|
687
|
+
gives the same answer. Without `error_kind`, an error still reaches hooks and
|
|
688
|
+
spans as `EXECUTION`: a call's answer carries a kind exactly when it is an
|
|
689
|
+
error, so `error_kind is not None` and `is_error` never disagree.
|
|
689
690
|
|
|
690
691
|
`ToolError` is for `@tool` functions: only their executor answers it. An agent
|
|
691
692
|
behind an [agent tool](#agents-as-tools) returns its error as a `ToolResult`
|
|
@@ -36,7 +36,7 @@ from ...llm import (
|
|
|
36
36
|
Usage,
|
|
37
37
|
UserMessage,
|
|
38
38
|
)
|
|
39
|
-
from ...tools import
|
|
39
|
+
from ...tools import ToolRegistry, ToolResult
|
|
40
40
|
from ..config_value import resolve_config_value
|
|
41
41
|
from ..llm_caller import DEFAULT_LLM_CALLER, LLMCaller
|
|
42
42
|
from ..observability import UsageEvent
|
|
@@ -67,6 +67,7 @@ logger = logging.getLogger(__name__)
|
|
|
67
67
|
class TurnAgent(Agent[TurnSpec, TurnResult]):
|
|
68
68
|
__slots__ = (
|
|
69
69
|
"__answered_tool_results",
|
|
70
|
+
"__call_order",
|
|
70
71
|
"__caller",
|
|
71
72
|
"__config",
|
|
72
73
|
"__consecutive_errors",
|
|
@@ -100,6 +101,7 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
|
|
|
100
101
|
self.__consecutive_errors = store.ephemeral.slot(Scalar[dict[str, int]], "consecutive_errors")
|
|
101
102
|
self.__incomplete_tool_result = store.ephemeral.slot(Scalar[ToolResultBlock | None], "incomplete_tool_result")
|
|
102
103
|
self.__answered_tool_results = store.ephemeral.slot(Scalar[list[ToolCallResult]], "answered_tool_results")
|
|
104
|
+
self.__call_order = store.ephemeral.slot(Scalar[list[str]], "call_order")
|
|
103
105
|
self.__interrupt = interrupt
|
|
104
106
|
self.__context = TurnAgentContext()
|
|
105
107
|
self.__config = config
|
|
@@ -296,7 +298,7 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
|
|
|
296
298
|
|
|
297
299
|
answered = self.__answered_tool_results.get([])
|
|
298
300
|
answered_ids = {tc_result.tool_use.id for tc_result in answered}
|
|
299
|
-
collected = self.
|
|
301
|
+
collected = self.__in_call_order([*answered, *results]) if answered else results
|
|
300
302
|
for tc_result in collected:
|
|
301
303
|
after_event = await self.__hooks.emit(
|
|
302
304
|
AfterToolCallEvent(
|
|
@@ -343,6 +345,7 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
|
|
|
343
345
|
self.__incomplete_tool_result.set(None)
|
|
344
346
|
if answered:
|
|
345
347
|
self.__answered_tool_results.set([])
|
|
348
|
+
self.__call_order.set([])
|
|
346
349
|
|
|
347
350
|
self.__consecutive_errors.set(consecutive_errors)
|
|
348
351
|
user_message = UserMessage(blocks=list(result_blocks))
|
|
@@ -401,6 +404,7 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
|
|
|
401
404
|
]
|
|
402
405
|
if answered:
|
|
403
406
|
self.__answered_tool_results.set(answered)
|
|
407
|
+
self.__call_order.set([tool_use.id for tool_use in tool_uses])
|
|
404
408
|
|
|
405
409
|
amendments: dict[str, ToolUseBlock] = {}
|
|
406
410
|
final_calls: list[BeforeToolCallEvent] = []
|
|
@@ -443,21 +447,18 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
|
|
|
443
447
|
]
|
|
444
448
|
return Spawn(children=children, then=self.collect_results)
|
|
445
449
|
|
|
446
|
-
def
|
|
447
|
-
#
|
|
448
|
-
|
|
449
|
-
position = {
|
|
450
|
+
def __in_call_order(self, results: list[ToolCallResult]) -> list[ToolCallResult]:
|
|
451
|
+
# By the order the message's calls had when they were made, kept beside the answers: it holds
|
|
452
|
+
# whatever the history looks like by the time the results are collected
|
|
453
|
+
position = {tool_use_id: i for i, tool_use_id in enumerate(self.__call_order.get([]))}
|
|
450
454
|
return sorted(results, key=lambda tc_result: position[tc_result.tool_use.id])
|
|
451
455
|
|
|
452
456
|
@staticmethod
|
|
453
457
|
def __answered_result(tool_use: ToolUseBlock, result: ToolResult) -> ToolCallResult:
|
|
454
|
-
# Consumers tell an error by its kind: an error answered without one gets the default ToolError uses,
|
|
455
|
-
# and an answer that is not an error carries none
|
|
456
|
-
error_kind = (result.error_kind or ToolErrorKind.EXECUTION) if result.is_error else None
|
|
457
458
|
return ToolCallResult(
|
|
458
459
|
tool_use=tool_use,
|
|
459
460
|
result=ToolResultBlock(tool_use_id=tool_use.id, content=result.blocks, is_error=result.is_error),
|
|
460
|
-
error_kind=error_kind,
|
|
461
|
+
error_kind=result.error_kind,
|
|
461
462
|
)
|
|
462
463
|
|
|
463
464
|
@staticmethod
|
|
@@ -43,10 +43,21 @@ class ToolCallSpec:
|
|
|
43
43
|
|
|
44
44
|
@dataclasses.dataclass(frozen=True, slots=True, kw_only=True)
|
|
45
45
|
class ToolCallResult:
|
|
46
|
+
"""A call's answer as the turn reads it.
|
|
47
|
+
|
|
48
|
+
``error_kind`` is set exactly when ``result.is_error``: an error the tool gave no kind is ``EXECUTION``,
|
|
49
|
+
the kind ``ToolError`` defaults to, and an answer that is not an error carries none. Hooks and spans tell
|
|
50
|
+
an error by its kind, so the two never disagree.
|
|
51
|
+
"""
|
|
52
|
+
|
|
46
53
|
tool_use: ToolUseBlock
|
|
47
54
|
result: ToolResultBlock
|
|
48
55
|
error_kind: ToolErrorKind | None = None
|
|
49
56
|
|
|
57
|
+
def __post_init__(self) -> None:
|
|
58
|
+
error_kind = (self.error_kind or ToolErrorKind.EXECUTION) if self.result.is_error else None
|
|
59
|
+
object.__setattr__(self, "error_kind", error_kind)
|
|
60
|
+
|
|
50
61
|
|
|
51
62
|
class ToolCallAgent(Agent[ToolCallSpec, ToolCallResult]):
|
|
52
63
|
"""Executes a single tool call and returns the result.
|
|
@@ -7,8 +7,8 @@ from flowra.agent import AgentRuntime, InterruptedError
|
|
|
7
7
|
from flowra.lib import LLMConfig
|
|
8
8
|
from flowra.lib.turn import TurnConfig
|
|
9
9
|
from flowra.lib.turn.tool_call import ToolCallAgent, ToolCallResult, ToolCallSpec
|
|
10
|
-
from flowra.llm import LLMProvider, LLMRequest, LLMResponse, ToolUseBlock
|
|
11
|
-
from flowra.tools import ToolRegistry, tool
|
|
10
|
+
from flowra.llm import LLMProvider, LLMRequest, LLMResponse, ToolResultBlock, ToolUseBlock
|
|
11
|
+
from flowra.tools import ToolErrorKind, ToolRegistry, ToolResult, tool
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class _UnusedProvider(LLMProvider):
|
|
@@ -37,6 +37,18 @@ def stopped_tool() -> str:
|
|
|
37
37
|
raise InterruptedError
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
@tool("refusing")
|
|
41
|
+
def refusing_tool() -> ToolResult:
|
|
42
|
+
"""Answer with an error of its own, naming no kind."""
|
|
43
|
+
return ToolResult(content="no", is_error=True)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@tool("kind_without_error")
|
|
47
|
+
def kind_without_error_tool() -> ToolResult:
|
|
48
|
+
"""Answer successfully while naming a kind."""
|
|
49
|
+
return ToolResult(content="fine", error_kind=ToolErrorKind.EXECUTION)
|
|
50
|
+
|
|
51
|
+
|
|
40
52
|
class TestToolCallAgent:
|
|
41
53
|
async def test_execute_tool_call(self) -> None:
|
|
42
54
|
registry = ToolRegistry([add_tool])
|
|
@@ -217,6 +229,7 @@ class TestToolCallAgent:
|
|
|
217
229
|
assert result.result.tool_use_id == "call_2"
|
|
218
230
|
assert result.result.text == "Tool execution interrupted"
|
|
219
231
|
assert result.result.is_error is True
|
|
232
|
+
assert result.error_kind == ToolErrorKind.EXECUTION
|
|
220
233
|
|
|
221
234
|
async def test_a_tool_that_raises_interrupted_is_interrupted_not_failed(
|
|
222
235
|
self, caplog: pytest.LogCaptureFixture
|
|
@@ -242,3 +255,57 @@ class TestToolCallAgent:
|
|
|
242
255
|
assert result.result.text == "Tool execution interrupted"
|
|
243
256
|
assert result.result.is_error is True
|
|
244
257
|
assert caplog.records == []
|
|
258
|
+
|
|
259
|
+
@pytest.mark.parametrize(
|
|
260
|
+
("tool_name", "error_kind"),
|
|
261
|
+
[("refusing", ToolErrorKind.EXECUTION), ("kind_without_error", None)],
|
|
262
|
+
)
|
|
263
|
+
async def test_a_tool_answer_carries_a_kind_exactly_when_it_is_an_error(
|
|
264
|
+
self, tool_name: str, error_kind: ToolErrorKind | None
|
|
265
|
+
) -> None:
|
|
266
|
+
runtime = AgentRuntime(
|
|
267
|
+
agents={"tool_call": ToolCallAgent},
|
|
268
|
+
services={
|
|
269
|
+
TurnConfig: TurnConfig(
|
|
270
|
+
tools=ToolRegistry([refusing_tool, kind_without_error_tool]),
|
|
271
|
+
llm_config=LLMConfig(provider=_UNUSED_PROVIDER, model="test"),
|
|
272
|
+
system=[],
|
|
273
|
+
history=[],
|
|
274
|
+
)
|
|
275
|
+
},
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
result = await runtime.run(
|
|
279
|
+
agent=ToolCallAgent, spec=ToolCallSpec(tool_use=ToolUseBlock(id="call_4", name=tool_name, input={}))
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
assert isinstance(result, ToolCallResult)
|
|
283
|
+
assert result.error_kind == error_kind
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class TestToolCallResult:
|
|
287
|
+
def test_an_error_without_a_kind_gets_execution(self) -> None:
|
|
288
|
+
result = ToolCallResult(
|
|
289
|
+
tool_use=ToolUseBlock(id="c", name="t", input={}),
|
|
290
|
+
result=ToolResultBlock(tool_use_id="c", content=[], is_error=True),
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
assert result.error_kind == ToolErrorKind.EXECUTION
|
|
294
|
+
|
|
295
|
+
def test_an_error_keeps_the_kind_it_has(self) -> None:
|
|
296
|
+
result = ToolCallResult(
|
|
297
|
+
tool_use=ToolUseBlock(id="c", name="t", input={}),
|
|
298
|
+
result=ToolResultBlock(tool_use_id="c", content=[], is_error=True),
|
|
299
|
+
error_kind=ToolErrorKind.SCHEMA_VALIDATION,
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
assert result.error_kind == ToolErrorKind.SCHEMA_VALIDATION
|
|
303
|
+
|
|
304
|
+
def test_an_answer_that_is_not_an_error_carries_no_kind(self) -> None:
|
|
305
|
+
result = ToolCallResult(
|
|
306
|
+
tool_use=ToolUseBlock(id="c", name="t", input={}),
|
|
307
|
+
result=ToolResultBlock(tool_use_id="c", content=[]),
|
|
308
|
+
error_kind=ToolErrorKind.EXECUTION,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
assert result.error_kind is None
|
{flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/.agents/skills/source-command-update-pricing/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/docs/research/lifecycle_hooks_and_conversation.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/compile/init_params.py
RENAMED
|
File without changes
|
|
File without changes
|
{flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/compile/step_params.py
RENAMED
|
File without changes
|
{flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/compile/step_validation.py
RENAMED
|
File without changes
|
|
File without changes
|
{flowra-0.0.92.dev108 → flowra-0.0.92.dev109}/flowra/agent/definition/compile/type_registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|