flowra 0.0.91__tar.gz → 0.0.92.dev108__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.91 → flowra-0.0.92.dev108}/CHANGELOG.md +19 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/PKG-INFO +1 -1
- {flowra-0.0.91 → flowra-0.0.92.dev108}/context7.json +2 -2
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/agents.md +5 -2
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/observability.md +33 -2
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/__init__.py +2 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/agent.py +52 -5
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/hook_events.py +24 -2
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/version.py +1 -1
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_turn_agent.py +381 -2
- {flowra-0.0.91 → flowra-0.0.92.dev108}/.agents/skills/source-command-update-pricing/SKILL.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/.claude/commands/update-pricing.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/.env.example +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/.github/CODEOWNERS +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/.github/workflows/master.yml +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/.github/workflows/publish.yml +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/.github/workflows/pull_request.yml +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/.gitignore +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/.python-version +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/AGENTS.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/CLAUDE.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/LICENSE +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/Makefile +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/README.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/architecture.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/getting-started.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/llm.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/patterns.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/agent_async_init.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/agent_tool_errors.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/anthropic_web_search.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/e2e_security.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/enum_slot_values.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/final_tools.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/langchain_comparison.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/lifecycle_hooks_and_conversation.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/llm_retry_backoff.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/named_scope_lazy_hydration.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/named_scope_type_registry.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/named_scopes.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/pricing_complexity.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/pydantic_ai_comparison.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/strands_comparison.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/tool_error_signals.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/tool_errors_vs_failures.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/tool_registry_redesign.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/tool_search_tool.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/voice_stt.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/review_prompts/step1_structure.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/review_prompts/step2_code_style.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/review_prompts/step3_documentation.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/review_prompts/step4_doc_readability.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/review_prompts/step5_doc_audit.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/review_prompts/step6_tests.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/todo.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/tools.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/video_script.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/TRACING_COMBINATIONS.md +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/agent_as_tool.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/app_agent.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/console_chat.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/docs/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/docs/agents_custom.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/docs/agents_parallel.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/docs/getting_started_chat.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/docs/getting_started_streaming.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/docs/getting_started_tools.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/docs/llm_streaming.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/docs/llm_structured_output.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/docs/tools_service_injection.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/escalation.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/llm_logging.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/llm_routing.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/menu_agent.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/menu_agent_class.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/mlflow_demo.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/mlflow_dual_export_demo.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/mlflow_nested_demo.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/mlflow_otel_both_demo.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/mlflow_otel_nested_demo.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/mlflow_parallel_demo.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/model_registry.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/otel_demo.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/otel_jaeger_demo.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/otel_nested_demo.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/otel_visualize.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/race.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/span_crash_demo.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/span_demo.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/system_prompt.txt +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/tools/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/tools/calculator.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/tools/random_numbers.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/tools/switch_model.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/tui_chat.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/_sentinel.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/_typing.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/_path.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/actions.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/agent.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/agent_arg.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/compile/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/compile/compiler.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/compile/contract.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/compile/init_params.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/compile/instance.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/compile/step_params.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/compile/step_validation.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/compile/steps.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/compile/type_registry.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/model.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/registry.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/shared_persistent.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/spawn.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/step.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/step_helpers.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/flow/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/flow/context.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/flow/flowing_registry.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/flow/hooks.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/flow/interrupt.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/flow/interrupt_helpers.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/flow/timeout.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/engine.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/execution.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/instance_factory.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/key_locks.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/runtime.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/scope.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/serialization.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/spans.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/spawn_satisfaction.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/spawn_tree.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/visibility.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/services.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/session_state.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/state/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/state/markers.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/state/persistent.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/state/store.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/state/values.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/storage/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/storage/file.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/storage/in_memory.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/storage/session_storage.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/ext/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/ext/_session.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/ext/mlflow.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/ext/otel.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/chat/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/chat/agent.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/chat/config.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/chat/conversation.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/chat/hook_events.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/chat/spec.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/config_value.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/llm_call/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/llm_call/agent.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/llm_call/config.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/llm_call/spec.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/llm_caller.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/llm_config.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/observability/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/observability/llm_hooks.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/config.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/context.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/spec.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/tool_call/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/tool_call/agent.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/tool_call/agent_tool.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/tool_call/context.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/_schema_nodes.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/base.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/blocks.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/errors.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/ext/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/ext/anthropic/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/ext/anthropic/cache.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/ext/anthropic/presets.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/ext/anthropic/tool_search.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/ext/anthropic/web_search.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/ext/openai/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/ext/openai/tool_search.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/gemini_schema.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/messages.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/openai_schema.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/bundled.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/data/custom/anthropic.json +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/data/custom/vertex.json +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/data/generated/anthropic.json +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/data/generated/azure_openai.json +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/data/generated/bedrock.json +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/data/generated/gemini_api.json +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/data/generated/inception.json +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/data/generated/openai.json +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/data/generated/vertex.json +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/model_pricing.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/price_list.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/provider.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/_openai_common.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/anthropic.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/anthropic_bedrock.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/anthropic_foundry.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/anthropic_vertex.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/azure_openai.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/google_vertex.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/openai.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/openai_responses.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/request.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/response.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/schema_formatting.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/schema_validation.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/stream.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/tools.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/py.typed +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/_mcp_content.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/_mcp_headers.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/local_tool.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/mcp_connection.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/result_format.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/tool_arg.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/tool_filter.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/tool_registry.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/types.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/pyproject.toml +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/definition/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/definition/compile/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/definition/compile/test_compile.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/definition/test_actions.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/definition/test_agent.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/definition/test_registry.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/definition/test_spawn.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/definition/test_step_helpers.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_context.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_flowing_registry.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_flowing_registry_tasks.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_flowing_sync.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_hooks.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_interrupt.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_interruptible.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_spans.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_timeout.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_with_interrupt.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_engine.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_engine_spans.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_ephemeral_isolation.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_hook_context.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_named_scope_persistence.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_named_scope_registry.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_named_scopes.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_parallel_failure.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_persistence.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_primitive_join.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_runtime.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_scope.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_serialization.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_spec_in_constructor.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/state/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/state/test_values.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/storage/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/storage/test_file.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/storage/test_in_memory.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/test_missing_scenarios.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/test_session_state.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/examples/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/examples/test_menu_agent.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/ext/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/ext/_mlflow.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/ext/test_mlflow.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/ext/test_mlflow_cross_task.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/ext/test_otel.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/_helpers.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/ext/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/ext/anthropic/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/ext/anthropic/test_anthropic.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/ext/openai/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/ext/openai/test_openai_tool_search.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_chat_agent.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_chat_spec.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_config_value.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_conversation.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_llm_call_agent.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_llm_caller.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_matches_tool_filter.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_tool_call_agent.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_tool_call_agent_call_agent.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/turn/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/turn/test_per_call_headers.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/turn/test_render.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/turn/test_tool_errors.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/pricing/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/pricing/test_bundled.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/pricing/test_model_pricing.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/pricing/test_price_list.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/_mock_http.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/_raw_body.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/_tool_result_blocks.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_anthropic.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_anthropic_bedrock.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_anthropic_bedrock_e2e.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_anthropic_e2e.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_anthropic_foundry.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_anthropic_foundry_e2e.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_anthropic_vertex.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_anthropic_vertex_locations_e2e.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_azure_openai_e2e.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_cross_provider_e2e.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_errors.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_fallback_e2e.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_google_vertex.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_google_vertex_e2e.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_history_across_models.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_openai_e2e.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_openai_provider.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_openai_responses.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_openai_responses_e2e.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_refusals.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_schemas_e2e.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_blocks.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_cost_breakdown.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_gemini_schema.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_metadata.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_openai_schema.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_response.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_schema_formatting.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_schema_nodes.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_schema_validation.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_stream.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/scripts/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/scripts/test_sync_pricing.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/test_typing.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/tools/__init__.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/tools/test_local_tool.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/tools/test_mcp_connection.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/tools/test_mcp_headers.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/tools/test_result_format.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/tools/test_tool_registry.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/tools/test_types.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/tools/sync_pricing.py +0 -0
- {flowra-0.0.91 → flowra-0.0.92.dev108}/uv.lock +0 -0
|
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`BeforeToolCallsEvent`: answer a model message's tool calls before any
|
|
12
|
+
runs.** Emitted once per model message with tool calls, a single call
|
|
13
|
+
included, with every call of the message in `tool_uses`, a call the response cut off
|
|
14
|
+
included (an answer under its id is not used). A handler puts a
|
|
15
|
+
`ToolResult` into `results` under a call's id to answer that call itself:
|
|
16
|
+
it does not run, the other calls run as usual, and its result reaches the
|
|
17
|
+
model in the message's order. An answered call gets no
|
|
18
|
+
`BeforeToolCallEvent` and no `ToolCallSpan`; it goes through
|
|
19
|
+
`AfterToolCallEvent` and counts toward `max_consecutive_errors` like any
|
|
20
|
+
result. An error answered without an `error_kind` gets
|
|
21
|
+
`ToolErrorKind.EXECUTION`; a key that is not the id of a call in the message
|
|
22
|
+
raises `ValueError`. Answered calls are kept in the turn's state, so they
|
|
23
|
+
survive a resume before the results are collected.
|
|
24
|
+
- **`AfterToolCallEvent.prefilled`**: true for a call answered in
|
|
25
|
+
`BeforeToolCallsEvent`, so a handler can tell that the tool did not run.
|
|
26
|
+
|
|
8
27
|
## [0.0.91] - 2026-10-20
|
|
9
28
|
|
|
10
29
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: flowra
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.92.dev108
|
|
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
|
|
@@ -129,12 +129,12 @@
|
|
|
129
129
|
|
|
130
130
|
"PRE-BUILT AGENTS — TurnAgent: single-turn LLM tool loop with hooks and caching",
|
|
131
131
|
"TurnAgent sends messages to LLM, executes tool calls, feeds results back, repeats until done",
|
|
132
|
-
"TurnAgent stop handling: a response without tool calls ends the turn with TurnStatus.COMPLETED and TurnResult.stop_reason (END_TURN, MAX_TOKENS, STOP_SEQUENCE, OTHER, SCHEMA_VALIDATION_FAILED, REFUSAL). On REFUSAL TurnResult.refusal is set, result_message is None and messages keep the turn so far (tool calls included) with no empty assistant message. If a response stopped with anything but END_TURN/TOOL_USE ends on a tool call, that call was cut off: it is not run and gets an error ToolResultBlock (no
|
|
132
|
+
"TurnAgent stop handling: a response without tool calls ends the turn with TurnStatus.COMPLETED and TurnResult.stop_reason (END_TURN, MAX_TOKENS, STOP_SEQUENCE, OTHER, SCHEMA_VALIDATION_FAILED, REFUSAL). On REFUSAL TurnResult.refusal is set, result_message is None and messages keep the turn so far (tool calls included) with no empty assistant message. If a response stopped with anything but END_TURN/TOOL_USE ends on a tool call, that call was cut off: it is not run and gets an error ToolResultBlock (it appears in BeforeToolCallsEvent.tool_uses; no BeforeToolCallEvent/AfterToolCallEvent, no ToolCallSpan, not counted in max_consecutive_errors), the complete calls before it run, and the loop continues so the model can retry (bounded by max_llm_calls). History never holds an unanswered tool_use",
|
|
133
133
|
"TurnConfig configures TurnAgent: TurnConfig(llm_config=LLMConfig(model='...'), system=[...], history=[...])",
|
|
134
134
|
"RENDER: ChatConfig.render / TurnConfig.render (PromptRender = Callable[[Prompt], Prompt | Awaitable[Prompt]]; Prompt from flowra.lib.turn has system and messages) is called once per LLM call of the turn (not per fallback attempt), before LLMCaller, with the system messages and the conversation (history, then turn messages). What it returns is sent to every fallback link; each link's request_extensions apply after it, and each attempt's LLMCallSpan carries the request with them applied. Stored history and turn messages are not changed, so a marker block can be rendered from live state on each call; build new messages, never edit the given ones. Messages left with no blocks are not sent, none left at all is a ValueError before the call; a tool call must still be followed straight away by its results (Claude and OpenAI Chat Completions reject anything in between); an error in render is raised from the turn as is; an interrupt stops an async render and ends the turn INTERRUPTED",
|
|
135
135
|
"TurnAgent lifecycle: __post_init__ emits TurnReadyEvent(context, turn_messages) — fires on both creation and crash recovery. ChatAgent subscribes and emits ConversationReadyEvent(conversation, context) with a Conversation view combining history + turn messages",
|
|
136
136
|
"Conversation class (from flowra.lib.chat): read-only Sequence view over history + turn_messages. Supports len(), indexing, slicing, forward/reverse iteration. Live — reflects changes to underlying lists. Properties: history_len, turn_len. Method without_pending_tool_use() -> list[UserMessage | AssistantMessage] returns messages with ToolUseBlocks stripped from the last AssistantMessage (drops the message entirely if it becomes empty) — use it inside an agent-as-tool to feed the parent conversation to a child LLM call without the unresolved tool-use that spawned it",
|
|
137
|
-
"TurnAgent hook events: StartTurnEvent (messages, metadata — mutate messages to augment input), MessageAcceptedEvent, StartIterationEvent, TextReasoningEvent (each TextBlock with text in response), ThinkingEvent (each ThinkingBlock with text; not redacted or signature-only thoughts), ServerToolCallEvent (server-side tool call from intermediate messages, e.g. tool search), ServerToolResultEvent (server-side tool result), ResultMessageEvent, BeforeToolCallEvent (has tool_definition: ToolDefinition | None; mutable tool_use, headers, meta — see ToolCallInfo), AfterToolCallEvent (has error_kind; result is a single ToolResultBlock; append TextBlock/MediaBlock to additional_blocks to attach them to this call's result — they go at the end of its content, also when max_consecutive_errors replaces it, so they stay with their call and never land between the results of parallel calls; mark one transient to keep it out of saved history; being part of the result, a MediaBlock there takes the providers' rules for media in a tool result (Chat Completions raises, Gemini before 3 does not see it) and a transient block ends the non-transient cache prefix before the result). Delta events: TextDeltaEvent, ThinkingDeltaEvent (from flowra.lib.observability — shared with LLMCallAgent). UsageEvent emitted after each LLM call + subscribed to accumulate usage from child agents (e.g. agent_as_tool). Span-hooks: LLMCallSpan, ToolCallSpan. Request transforms (prompt caching, tool search) are NOT hooks — they live on LLMConfig.request_extensions. A conversation transform for every fallback link goes in ChatConfig.render / TurnConfig.render (see RENDER)",
|
|
137
|
+
"TurnAgent hook events: StartTurnEvent (messages, metadata — mutate messages to augment input), MessageAcceptedEvent, StartIterationEvent, TextReasoningEvent (each TextBlock with text in response), ThinkingEvent (each ThinkingBlock with text; not redacted or signature-only thoughts), ServerToolCallEvent (server-side tool call from intermediate messages, e.g. tool search), ServerToolResultEvent (server-side tool result), ResultMessageEvent, BeforeToolCallsEvent (once per model message with tool calls, before any runs; tool_uses is every call of the message, a cut-off call included (an answer under its id is not used); put a ToolResult into results under a call's id to answer it without running it — that call gets no BeforeToolCallEvent and no ToolCallSpan, its result goes through AfterToolCallEvent with prefilled=True and max_consecutive_errors in message order, an error without error_kind gets EXECUTION; a key not among the message's call ids raises ValueError), BeforeToolCallEvent (has tool_definition: ToolDefinition | None; mutable tool_use, headers, meta — see ToolCallInfo; not emitted for a call answered in BeforeToolCallsEvent), AfterToolCallEvent (fires after execution, and for a call answered in BeforeToolCallsEvent; has error_kind; prefilled is true for a call answered in BeforeToolCallsEvent; result is a single ToolResultBlock; append TextBlock/MediaBlock to additional_blocks to attach them to this call's result — they go at the end of its content, also when max_consecutive_errors replaces it, so they stay with their call and never land between the results of parallel calls; mark one transient to keep it out of saved history; being part of the result, a MediaBlock there takes the providers' rules for media in a tool result (Chat Completions raises, Gemini before 3 does not see it) and a transient block ends the non-transient cache prefix before the result). Delta events: TextDeltaEvent, ThinkingDeltaEvent (from flowra.lib.observability — shared with LLMCallAgent). UsageEvent emitted after each LLM call + subscribed to accumulate usage from child agents (e.g. agent_as_tool). Span-hooks: LLMCallSpan, ToolCallSpan. Request transforms (prompt caching, tool search) are NOT hooks — they live on LLMConfig.request_extensions. A conversation transform for every fallback link goes in ChatConfig.render / TurnConfig.render (see RENDER)",
|
|
138
138
|
"When TextDeltaEvent or ThinkingDeltaEvent handlers are registered, TurnAgent automatically uses provider.stream() instead of provider.call()",
|
|
139
139
|
|
|
140
140
|
"AGENTS AS TOOLS: Tools can delegate to sub-agents via ToolCallAgentContext.call_agent(). make_agent_tool(AgentClass, name, description) creates an ExecutableTool with input_schema from spec type and output_schema from result type (ToolResult excluded). @agent_tool decorator marks an agent class as a tool, get_agent_tool() extracts the ExecutableTool. ToolCallAgent spawns the sub-agent and serializes its result as a ToolResultBlock. Agent tools can return ToolResult directly to signal errors to the parent LLM or answer with blocks (e.g. return ToolResult(content='...', is_error=True)) — the agent completes normally; with is_error=True the parent LLM sees a tool error and can retry. Compile-time validation: step return types must be a subset of Agent's generic result type parameter. The sub-agent must be declared by the agent running the tool or one of its registry ancestors (top level works); it receives services of its own registry ancestors, not the calling chat's (unless the chat is one of them)",
|
|
@@ -802,11 +802,14 @@ Lifecycle events (from `flowra.lib.turn`):
|
|
|
802
802
|
| `ThinkingEvent` | Non-empty thinking block in LLM response | — |
|
|
803
803
|
| `ServerToolCallEvent` | Server-side tool call (e.g. tool search) | — |
|
|
804
804
|
| `ServerToolResultEvent` | Server-side tool result | — |
|
|
805
|
+
| `BeforeToolCallsEvent` | Message with tool calls, before any runs | `results` |
|
|
805
806
|
| `BeforeToolCallEvent` | Before tool execution | `tool_use`, `headers`, `meta` |
|
|
806
|
-
| `AfterToolCallEvent` | After tool execution
|
|
807
|
+
| `AfterToolCallEvent` | After tool execution or answer | `result`, `additional_blocks`, `additional_messages` |
|
|
807
808
|
| `ResultMessageEvent` | LLM finished (END_TURN) | `continue_messages` |
|
|
808
809
|
|
|
809
|
-
|
|
810
|
+
A tool call cut off by the output limit appears in `BeforeToolCallsEvent` but
|
|
811
|
+
fires no `BeforeToolCallEvent` or `AfterToolCallEvent`, and a call answered in
|
|
812
|
+
`BeforeToolCallsEvent` does not run; see
|
|
810
813
|
[Pre-built events](observability.md#pre-built-events) in Observability.
|
|
811
814
|
|
|
812
815
|
Streaming events (from `flowra.lib.observability`):
|
|
@@ -35,8 +35,9 @@ Events from `TurnAgent` (and `ChatAgent`, which uses it internally):
|
|
|
35
35
|
| `ThinkingEvent` | Non-empty thinking block in LLM response | — |
|
|
36
36
|
| `ServerToolCallEvent` | Server-side tool call (e.g. tool search) | — |
|
|
37
37
|
| `ServerToolResultEvent` | Server-side tool result | — |
|
|
38
|
+
| `BeforeToolCallsEvent` | Message with tool calls, before any runs | `results` |
|
|
38
39
|
| `BeforeToolCallEvent` | Before tool execution | `tool_use`, `headers`, `meta` |
|
|
39
|
-
| `AfterToolCallEvent` | After tool execution
|
|
40
|
+
| `AfterToolCallEvent` | After tool execution or answer | `result`, `additional_blocks`, `additional_messages` |
|
|
40
41
|
| `ResultMessageEvent` | LLM finished (END_TURN) | `continue_messages` |
|
|
41
42
|
|
|
42
43
|
`AfterToolCallEvent.additional_blocks` go at the end of that call's result, so a
|
|
@@ -71,7 +72,37 @@ runtime.hooks.on(AfterToolCallEvent, add_hint)
|
|
|
71
72
|
A tool call cut off by the output limit (see [TurnAgent](agents.md#turnagent)) is
|
|
72
73
|
never executed. It gets an error result directly, with no `BeforeToolCallEvent`,
|
|
73
74
|
`AfterToolCallEvent` or `ToolCallSpan`, so hooks that audit or rewrite tool
|
|
74
|
-
results don't see it.
|
|
75
|
+
results don't see it; it does appear in `BeforeToolCallsEvent.tool_uses`, and an
|
|
76
|
+
answer put under its id is not used.
|
|
77
|
+
|
|
78
|
+
`BeforeToolCallsEvent` fires once per model message with tool calls, a single
|
|
79
|
+
call included, before any of them runs. A handler answers a call itself by
|
|
80
|
+
putting a `ToolResult` into `results` under the call's id: that call does not
|
|
81
|
+
run, and the others run as usual. An answered call gets no `BeforeToolCallEvent`
|
|
82
|
+
and no `ToolCallSpan`; its result goes through `AfterToolCallEvent` (with
|
|
83
|
+
`prefilled=True`) and `max_consecutive_errors` like any other, in the message's
|
|
84
|
+
order. An error answered without an `error_kind` arrives with
|
|
85
|
+
`ToolErrorKind.EXECUTION`. A key that is not the id of a call in the message
|
|
86
|
+
fails the step with `ValueError`.
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from flowra.agent import Event
|
|
90
|
+
from flowra.lib.turn import BeforeToolCallsEvent
|
|
91
|
+
from flowra.tools import ToolResult
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def reply_alone(event: Event[BeforeToolCallsEvent]) -> None:
|
|
95
|
+
if len(event.data.tool_uses) < 2:
|
|
96
|
+
return
|
|
97
|
+
for tool_use in event.data.tool_uses:
|
|
98
|
+
if tool_use.name == "send_reply":
|
|
99
|
+
event.data.results[tool_use.id] = ToolResult(
|
|
100
|
+
content="Call send_reply on its own, in a message with no other tool calls.", is_error=True
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
runtime.hooks.on(BeforeToolCallsEvent, reply_alone)
|
|
105
|
+
```
|
|
75
106
|
|
|
76
107
|
A tool call that fails the run (an exception raised around a tool's executor,
|
|
77
108
|
see [Error handling](tools.md#what-fails-the-run)) has no result:
|
|
@@ -4,6 +4,7 @@ from .context import TurnAgentContext
|
|
|
4
4
|
from .hook_events import (
|
|
5
5
|
AfterToolCallEvent,
|
|
6
6
|
BeforeToolCallEvent,
|
|
7
|
+
BeforeToolCallsEvent,
|
|
7
8
|
MessageAcceptedEvent,
|
|
8
9
|
ResultMessageEvent,
|
|
9
10
|
ServerToolCallEvent,
|
|
@@ -20,6 +21,7 @@ from .tool_call import ToolCallAgentContext, agent_tool, get_agent_tool, make_ag
|
|
|
20
21
|
__all__ = [
|
|
21
22
|
"AfterToolCallEvent",
|
|
22
23
|
"BeforeToolCallEvent",
|
|
24
|
+
"BeforeToolCallsEvent",
|
|
23
25
|
"MessageAcceptedEvent",
|
|
24
26
|
"Prompt",
|
|
25
27
|
"PromptRender",
|
|
@@ -36,7 +36,7 @@ from ...llm import (
|
|
|
36
36
|
Usage,
|
|
37
37
|
UserMessage,
|
|
38
38
|
)
|
|
39
|
-
from ...tools import ToolRegistry
|
|
39
|
+
from ...tools import ToolErrorKind, 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
|
|
@@ -45,6 +45,7 @@ from .context import TurnAgentContext
|
|
|
45
45
|
from .hook_events import (
|
|
46
46
|
AfterToolCallEvent,
|
|
47
47
|
BeforeToolCallEvent,
|
|
48
|
+
BeforeToolCallsEvent,
|
|
48
49
|
MessageAcceptedEvent,
|
|
49
50
|
ResultMessageEvent,
|
|
50
51
|
ServerToolCallEvent,
|
|
@@ -65,6 +66,7 @@ logger = logging.getLogger(__name__)
|
|
|
65
66
|
|
|
66
67
|
class TurnAgent(Agent[TurnSpec, TurnResult]):
|
|
67
68
|
__slots__ = (
|
|
69
|
+
"__answered_tool_results",
|
|
68
70
|
"__caller",
|
|
69
71
|
"__config",
|
|
70
72
|
"__consecutive_errors",
|
|
@@ -97,6 +99,7 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
|
|
|
97
99
|
self.__usage = store.ephemeral.slot(Scalar[Usage | None], "usage")
|
|
98
100
|
self.__consecutive_errors = store.ephemeral.slot(Scalar[dict[str, int]], "consecutive_errors")
|
|
99
101
|
self.__incomplete_tool_result = store.ephemeral.slot(Scalar[ToolResultBlock | None], "incomplete_tool_result")
|
|
102
|
+
self.__answered_tool_results = store.ephemeral.slot(Scalar[list[ToolCallResult]], "answered_tool_results")
|
|
100
103
|
self.__interrupt = interrupt
|
|
101
104
|
self.__context = TurnAgentContext()
|
|
102
105
|
self.__config = config
|
|
@@ -246,7 +249,7 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
|
|
|
246
249
|
)
|
|
247
250
|
|
|
248
251
|
case StopReason.TOOL_USE:
|
|
249
|
-
return await self.__call_tools(response, tool_uses, registry, allowed_tools, denied_tools)
|
|
252
|
+
return await self.__call_tools(response, tool_uses, None, registry, allowed_tools, denied_tools)
|
|
250
253
|
|
|
251
254
|
case StopReason.REFUSAL:
|
|
252
255
|
# The provider declined: no output to keep, and an empty assistant message would make
|
|
@@ -268,7 +271,7 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
|
|
|
268
271
|
# A response cut off mid tool call ends on that call, and only that call is incomplete
|
|
269
272
|
cut_off = response.message.blocks[-1] is tool_uses[-1]
|
|
270
273
|
if not cut_off:
|
|
271
|
-
return await self.__call_tools(response, tool_uses, registry, allowed_tools, denied_tools)
|
|
274
|
+
return await self.__call_tools(response, tool_uses, None, registry, allowed_tools, denied_tools)
|
|
272
275
|
|
|
273
276
|
# Answer the cut-off call with an error so history stays valid and the model can retry
|
|
274
277
|
self.__incomplete_tool_result.set(
|
|
@@ -278,7 +281,9 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
|
|
|
278
281
|
is_error=True,
|
|
279
282
|
)
|
|
280
283
|
)
|
|
281
|
-
return await self.__call_tools(
|
|
284
|
+
return await self.__call_tools(
|
|
285
|
+
response, tool_uses[:-1], tool_uses[-1], registry, allowed_tools, denied_tools
|
|
286
|
+
)
|
|
282
287
|
|
|
283
288
|
@step("collect_results")
|
|
284
289
|
async def collect_results(
|
|
@@ -289,13 +294,17 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
|
|
|
289
294
|
consecutive_errors = dict(self.__consecutive_errors.get({}))
|
|
290
295
|
max_consecutive = await resolve_config_value(self.__config.max_consecutive_errors)
|
|
291
296
|
|
|
292
|
-
|
|
297
|
+
answered = self.__answered_tool_results.get([])
|
|
298
|
+
answered_ids = {tc_result.tool_use.id for tc_result in answered}
|
|
299
|
+
collected = self.__in_message_order([*answered, *results]) if answered else results
|
|
300
|
+
for tc_result in collected:
|
|
293
301
|
after_event = await self.__hooks.emit(
|
|
294
302
|
AfterToolCallEvent(
|
|
295
303
|
tool_use=tc_result.tool_use,
|
|
296
304
|
result=tc_result.result,
|
|
297
305
|
error_kind=tc_result.error_kind,
|
|
298
306
|
context=self.__context,
|
|
307
|
+
prefilled=tc_result.tool_use.id in answered_ids,
|
|
299
308
|
)
|
|
300
309
|
)
|
|
301
310
|
|
|
@@ -332,6 +341,8 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
|
|
|
332
341
|
if incomplete_result is not None:
|
|
333
342
|
result_blocks.append(incomplete_result)
|
|
334
343
|
self.__incomplete_tool_result.set(None)
|
|
344
|
+
if answered:
|
|
345
|
+
self.__answered_tool_results.set([])
|
|
335
346
|
|
|
336
347
|
self.__consecutive_errors.set(consecutive_errors)
|
|
337
348
|
user_message = UserMessage(blocks=list(result_blocks))
|
|
@@ -370,13 +381,32 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
|
|
|
370
381
|
self,
|
|
371
382
|
response: LLMResponse,
|
|
372
383
|
tool_uses: list[ToolUseBlock],
|
|
384
|
+
cut_off: ToolUseBlock | None,
|
|
373
385
|
registry: ToolRegistry | None,
|
|
374
386
|
allowed_tools: set[str] | None,
|
|
375
387
|
denied_tools: set[str] | None,
|
|
376
388
|
) -> Spawn:
|
|
389
|
+
message_calls = [*tool_uses, cut_off] if cut_off is not None else list(tool_uses)
|
|
390
|
+
# Taken before the emit, so a handler changing the event's list cannot widen what may be answered
|
|
391
|
+
call_ids = {tool_use.id for tool_use in message_calls}
|
|
392
|
+
calls_event = await self.__hooks.emit(BeforeToolCallsEvent(tool_uses=message_calls, context=self.__context))
|
|
393
|
+
for tool_use_id in calls_event.results:
|
|
394
|
+
if tool_use_id not in call_ids:
|
|
395
|
+
raise ValueError(f"No tool call {tool_use_id!r} in this message")
|
|
396
|
+
# Answers are taken from the calls that can run, so the cut-off call keeps its own error result
|
|
397
|
+
answered = [
|
|
398
|
+
self.__answered_result(tool_use, calls_event.results[tool_use.id])
|
|
399
|
+
for tool_use in tool_uses
|
|
400
|
+
if tool_use.id in calls_event.results
|
|
401
|
+
]
|
|
402
|
+
if answered:
|
|
403
|
+
self.__answered_tool_results.set(answered)
|
|
404
|
+
|
|
377
405
|
amendments: dict[str, ToolUseBlock] = {}
|
|
378
406
|
final_calls: list[BeforeToolCallEvent] = []
|
|
379
407
|
for tool_use in tool_uses:
|
|
408
|
+
if tool_use.id in calls_event.results:
|
|
409
|
+
continue
|
|
380
410
|
before_event = await self.__hooks.emit(
|
|
381
411
|
BeforeToolCallEvent(
|
|
382
412
|
tool_use=tool_use,
|
|
@@ -413,6 +443,23 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
|
|
|
413
443
|
]
|
|
414
444
|
return Spawn(children=children, then=self.collect_results)
|
|
415
445
|
|
|
446
|
+
def __in_message_order(self, results: list[ToolCallResult]) -> list[ToolCallResult]:
|
|
447
|
+
# Every collected call, answered or run, is one of the last assistant message in the turn
|
|
448
|
+
message = next(m for m in reversed(self.__turn_messages.get_all()) if isinstance(m, AssistantMessage))
|
|
449
|
+
position = {block.id: i for i, block in enumerate(message.blocks) if isinstance(block, ToolUseBlock)}
|
|
450
|
+
return sorted(results, key=lambda tc_result: position[tc_result.tool_use.id])
|
|
451
|
+
|
|
452
|
+
@staticmethod
|
|
453
|
+
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
|
+
return ToolCallResult(
|
|
458
|
+
tool_use=tool_use,
|
|
459
|
+
result=ToolResultBlock(tool_use_id=tool_use.id, content=result.blocks, is_error=result.is_error),
|
|
460
|
+
error_kind=error_kind,
|
|
461
|
+
)
|
|
462
|
+
|
|
416
463
|
@staticmethod
|
|
417
464
|
def __incomplete_tool_use_message(stop_reason: StopReason) -> str:
|
|
418
465
|
if stop_reason == StopReason.MAX_TOKENS:
|
|
@@ -14,12 +14,13 @@ from ...llm import (
|
|
|
14
14
|
ToolUseBlock,
|
|
15
15
|
UserMessage,
|
|
16
16
|
)
|
|
17
|
-
from ...tools import ToolDefinition, ToolErrorKind
|
|
17
|
+
from ...tools import ToolDefinition, ToolErrorKind, ToolResult
|
|
18
18
|
from .context import TurnAgentContext
|
|
19
19
|
|
|
20
20
|
__all__ = [
|
|
21
21
|
"AfterToolCallEvent",
|
|
22
22
|
"BeforeToolCallEvent",
|
|
23
|
+
"BeforeToolCallsEvent",
|
|
23
24
|
"MessageAcceptedEvent",
|
|
24
25
|
"ResultMessageEvent",
|
|
25
26
|
"ServerToolCallEvent",
|
|
@@ -96,6 +97,21 @@ class ResultMessageEvent:
|
|
|
96
97
|
continue_messages: list[UserMessage] = dataclasses.field(default_factory=list)
|
|
97
98
|
|
|
98
99
|
|
|
100
|
+
@dataclasses.dataclass(slots=True, kw_only=True)
|
|
101
|
+
class BeforeToolCallsEvent:
|
|
102
|
+
"""Emitted once per model message with tool calls, before any of them runs.
|
|
103
|
+
|
|
104
|
+
``tool_uses`` is every call of the message in its order, a call the response cut off included.
|
|
105
|
+
An answer under the cut-off call's id is not used: that call always gets its own error result.
|
|
106
|
+
Put a ``ToolResult`` into ``results`` under a call's id to answer it without running it: the
|
|
107
|
+
other calls run as usual, and the result reaches the model as that call's answer.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
tool_uses: list[ToolUseBlock]
|
|
111
|
+
context: TurnAgentContext
|
|
112
|
+
results: dict[str, ToolResult] = dataclasses.field(default_factory=dict)
|
|
113
|
+
|
|
114
|
+
|
|
99
115
|
@dataclasses.dataclass(slots=True, kw_only=True)
|
|
100
116
|
class BeforeToolCallEvent:
|
|
101
117
|
"""Emitted before each tool execution.
|
|
@@ -112,6 +128,9 @@ class BeforeToolCallEvent:
|
|
|
112
128
|
|
|
113
129
|
Neither this nor ``AfterToolCallEvent`` fires for a call the response cut
|
|
114
130
|
off (e.g. at ``MAX_TOKENS``): that call is not run.
|
|
131
|
+
|
|
132
|
+
Not emitted for a call answered in ``BeforeToolCallsEvent``; that call still gets
|
|
133
|
+
``AfterToolCallEvent``, with ``prefilled=True``.
|
|
115
134
|
"""
|
|
116
135
|
|
|
117
136
|
tool_use: ToolUseBlock
|
|
@@ -123,7 +142,7 @@ class BeforeToolCallEvent:
|
|
|
123
142
|
|
|
124
143
|
@dataclasses.dataclass(slots=True, kw_only=True)
|
|
125
144
|
class AfterToolCallEvent:
|
|
126
|
-
"""Emitted after each tool execution
|
|
145
|
+
"""Emitted after each tool execution, and for a call answered in ``BeforeToolCallsEvent``.
|
|
127
146
|
|
|
128
147
|
Replace ``result`` to modify tool output (e.g. sanitize errors, redact PII).
|
|
129
148
|
Append to ``additional_blocks`` to attach blocks (e.g. a hint as a ``TextBlock``)
|
|
@@ -136,12 +155,15 @@ class AfterToolCallEvent:
|
|
|
136
155
|
this result.
|
|
137
156
|
Append to ``additional_messages`` to inject standalone messages after the
|
|
138
157
|
message with the tool results.
|
|
158
|
+
|
|
159
|
+
``prefilled`` is true for a call answered in ``BeforeToolCallsEvent``: the tool did not run.
|
|
139
160
|
"""
|
|
140
161
|
|
|
141
162
|
tool_use: ToolUseBlock
|
|
142
163
|
result: ToolResultBlock
|
|
143
164
|
error_kind: ToolErrorKind | None
|
|
144
165
|
context: TurnAgentContext
|
|
166
|
+
prefilled: bool = False
|
|
145
167
|
additional_blocks: list[ToolResultContentBlock] = dataclasses.field(default_factory=list)
|
|
146
168
|
additional_messages: list[UserMessage] = dataclasses.field(default_factory=list)
|
|
147
169
|
|