flowra 0.0.23.dev28__tar.gz → 0.0.23.dev31__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.23.dev28 → flowra-0.0.23.dev31}/CHANGELOG.md +30 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/Makefile +22 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/PKG-INFO +1 -1
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/context7.json +6 -5
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/agents.md +17 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/agent.md +20 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/ext/mlflow.md +23 -2
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/ext/otel.md +28 -2
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/ext/tracing-guide.md +29 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/lib/anthropic.md +3 -2
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/lib.md +2 -1
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/llm.md +9 -8
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/observability.md +49 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/mlflow_context_migration.md +6 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/otel_integration.md +5 -0
- flowra-0.0.23.dev31/examples/TRACING_COMBINATIONS.md +180 -0
- flowra-0.0.23.dev31/examples/mlflow_dual_export_demo.py +133 -0
- flowra-0.0.23.dev31/examples/mlflow_nested_demo.py +107 -0
- flowra-0.0.23.dev31/examples/mlflow_otel_both_demo.py +113 -0
- flowra-0.0.23.dev31/examples/mlflow_otel_nested_demo.py +150 -0
- flowra-0.0.23.dev31/examples/otel_jaeger_demo.py +135 -0
- flowra-0.0.23.dev31/examples/otel_nested_demo.py +115 -0
- flowra-0.0.23.dev31/examples/otel_visualize.py +144 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/__init__.py +2 -1
- flowra-0.0.23.dev31/flowra/agent/storage/__init__.py +5 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/storage/in_memory.py +25 -4
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/ext/mlflow.py +16 -1
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/ext/otel.py +11 -1
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/anthropic/__init__.py +6 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/anthropic/cache.py +44 -10
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/anthropic/presets.py +14 -2
- flowra-0.0.23.dev31/flowra/version.py +2 -0
- flowra-0.0.23.dev31/tests/agent/storage/test_in_memory.py +140 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/ext/test_mlflow.py +2 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/anthropic/test_anthropic.py +27 -1
- flowra-0.0.23.dev28/flowra/agent/storage/__init__.py +0 -5
- flowra-0.0.23.dev28/flowra/version.py +0 -2
- flowra-0.0.23.dev28/tests/agent/storage/test_in_memory.py +0 -70
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/.claude/commands/update-pricing.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/.env.example +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/.github/workflows/master.yml +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/.github/workflows/publish.yml +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/.github/workflows/pull_request.yml +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/.github/workflows/pull_request_e2e.yml +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/.gitignore +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/.python-version +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/CLAUDE.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/LICENSE +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/README.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/getting-started.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/architecture.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/ext.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/llm.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/patterns.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/tools.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/patterns.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/flowing_context.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/hooks_redesign.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/model_fallback.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/pricing_complexity.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/provider_extensions.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/spawn_strategies.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/strands_comparison.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/tool_error_signals.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/tool_search_tool.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/voice_stt.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/review_prompts/step1_structure.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/review_prompts/step2_code_style.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/review_prompts/step3_documentation.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/review_prompts/step4_doc_readability.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/review_prompts/step5_doc_audit.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/review_prompts/step6_tests.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/todo.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/tools.md +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/agent_as_tool.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/app_agent.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/console_chat.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/docs/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/docs/agents_custom.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/docs/agents_parallel.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/docs/getting_started_chat.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/docs/getting_started_streaming.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/docs/getting_started_tools.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/docs/llm_streaming.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/docs/llm_structured_output.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/docs/tools_service_injection.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/escalation.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/llm_logging.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/llm_routing.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/menu_agent.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/menu_agent_class.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/mlflow_demo.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/model_registry.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/otel_demo.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/race.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/span_crash_demo.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/span_demo.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/system_prompt.txt +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/tools/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/tools/calculator.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/tools/random_numbers.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/tools/switch_model.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/tui_chat.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/_sentinel.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/agent.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/agent_arg.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/compiler.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/contract.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/init_params.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/instance.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/step_params.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/step_validation.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/steps.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/type_helpers.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/type_registry.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/model.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/registry.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/step.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/step_arg.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/step_helpers.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/flow/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/flow/actions.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/flow/context.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/flow/flowing.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/flow/hooks.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/flow/interrupt.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/flow/interrupt_helpers.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/flow/spawn.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/flow/timeout.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/runtime/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/runtime/engine.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/runtime/execution.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/runtime/instance_factory.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/runtime/runtime.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/runtime/scope.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/runtime/serialization.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/runtime/spans.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/runtime/spawn_tree.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/services.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/state/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/state/markers.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/state/store.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/state/values.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/storage/file.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/storage/session_storage.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/ext/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/anthropic/tool_search.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/chat/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/chat/agent.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/chat/config.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/chat/hook_events.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/chat/spec.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/config_value.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/llm_call/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/llm_call/agent.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/llm_call/spec.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/llm_config.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/observability/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/observability/llm_hooks.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/agent.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/config.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/context.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/hook_events.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/spec.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/tool_call/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/tool_call/agent.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/tool_call/agent_tool.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/tool_call/context.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/base.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/blocks.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/messages.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/pricing/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/pricing/anthropic.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/pricing/google.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/pricing/openai.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/provider.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/providers/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/providers/anthropic_vertex.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/providers/google_vertex.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/providers/openai.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/request.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/response.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/schema_formatting.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/schema_validation.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/stream.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/tools.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/py.typed +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/tools/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/tools/local_tool.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/tools/mcp_connection.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/tools/tool_arg.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/tools/tool_group.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/tools/tool_registry.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/tools/types.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/pyproject.toml +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/definition/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/definition/compile/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/definition/compile/test_compile.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/definition/compile/test_type_helpers.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/definition/test_agent.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/definition/test_registry.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/definition/test_step_helpers.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/flow/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/flow/test_agent_def.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/flow/test_context.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/flow/test_hooks.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/flow/test_interrupt.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/flow/test_spans.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/flow/test_timeout.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/flow/test_with_interrupt.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/runtime/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/runtime/test_engine.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/runtime/test_engine_spans.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/runtime/test_hook_context.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/runtime/test_persistence.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/runtime/test_runtime.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/runtime/test_scope.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/runtime/test_serialization.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/runtime/test_spec_in_constructor.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/state/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/state/test_values.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/storage/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/storage/test_file.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/test_missing_scenarios.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/ext/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/ext/test_otel.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/anthropic/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/test_chat_agent.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/test_config_value.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/test_llm_call_agent.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/test_matches_tool_filter.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/test_tool_call_agent.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/test_tool_call_agent_call_agent.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/test_tool_loop_agent.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/tool_loop/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/pricing/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/pricing/test_anthropic.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/pricing/test_google.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/pricing/test_openai.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/providers/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/providers/test_anthropic_e2e.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/providers/test_anthropic_vertex.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/providers/test_google_vertex.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/providers/test_google_vertex_e2e.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/providers/test_openai_e2e.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/providers/test_openai_provider.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/test_cost_breakdown.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/test_metadata.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/test_response.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/test_schema_formatting.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/test_schema_validation.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/test_stream.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/tools/__init__.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/tools/test_local_tool.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/tools/test_mcp_connection.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/tools/test_tool_group.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/tools/test_tool_registry.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tools/sync_pricing.py +0 -0
- {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/uv.lock +0 -0
|
@@ -7,6 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Added
|
|
11
|
+
- **`AnthropicCacheNonTransientTools`** — caching bundle for the last non-transient
|
|
12
|
+
tool definition. Mirrors existing `NonTransient` bundles for system messages and
|
|
13
|
+
messages.
|
|
14
|
+
- **`ANTHROPIC_CACHE_NON_TRANSIENT`** preset — composite of
|
|
15
|
+
`ANTHROPIC_CACHE_NON_TRANSIENT_SYSTEM_MESSAGES` +
|
|
16
|
+
`ANTHROPIC_CACHE_NON_TRANSIENT_TOOLS` + `ANTHROPIC_CACHE_NON_TRANSIENT_MESSAGES`.
|
|
17
|
+
- **Pre-instantiated constants** for all individual caching bundles:
|
|
18
|
+
`ANTHROPIC_CACHE_ALL_MESSAGES`, `ANTHROPIC_CACHE_ALL_SYSTEM_MESSAGES`,
|
|
19
|
+
`ANTHROPIC_CACHE_ALL_TOOLS`, `ANTHROPIC_CACHE_HISTORY_MESSAGES`,
|
|
20
|
+
`ANTHROPIC_CACHE_NON_TRANSIENT_MESSAGES`, `ANTHROPIC_CACHE_NON_TRANSIENT_SYSTEM_MESSAGES`,
|
|
21
|
+
`ANTHROPIC_CACHE_NON_TRANSIENT_TOOLS`. No need to instantiate classes manually.
|
|
22
|
+
- **`InMemorySessionStorage` snapshot** — `storage.snapshot()` returns an
|
|
23
|
+
`InMemorySessionSnapshot` (frozen dataclass) with a deep copy of all data.
|
|
24
|
+
`InMemorySessionStorage(snapshot)` creates a new storage from a snapshot.
|
|
25
|
+
Enables cloning: `InMemorySessionStorage(storage.snapshot())`.
|
|
26
|
+
- **External tracing context integration** — both MLflow and OTel integrations now
|
|
27
|
+
detect and attach to existing external spans. If flowra is called inside
|
|
28
|
+
`with mlflow.start_span()` or `tracer.start_as_current_span()`, flowra's spans
|
|
29
|
+
become children of the external span.
|
|
30
|
+
- **Tracing examples** — 7 new examples demonstrating all MLflow/OTel tracing
|
|
31
|
+
combinations: nested external spans, both independently, dual export, and mixed
|
|
32
|
+
setups. Console OTel span tree visualizer (`otel_visualize.py`).
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- **NonTransient caching logic**: `NonTransient` bundles now cache the last
|
|
36
|
+
non-transient element in the non-transient prefix — walk forward and stop at
|
|
37
|
+
the first transient element. Previously they searched for the last non-transient
|
|
38
|
+
element overall, which could skip over transient content in the middle.
|
|
39
|
+
|
|
10
40
|
## [0.0.22] - 2026-03-23
|
|
11
41
|
|
|
12
42
|
### Changed
|
|
@@ -69,9 +69,31 @@ race:
|
|
|
69
69
|
mlflow-demo:
|
|
70
70
|
uv run python examples/mlflow_demo.py $(args)
|
|
71
71
|
|
|
72
|
+
mlflow-nested-demo:
|
|
73
|
+
uv run python examples/mlflow_nested_demo.py $(args)
|
|
74
|
+
|
|
72
75
|
otel-demo:
|
|
73
76
|
uv run python examples/otel_demo.py $(args)
|
|
74
77
|
|
|
78
|
+
otel-nested-demo:
|
|
79
|
+
uv run python examples/otel_nested_demo.py $(args)
|
|
80
|
+
|
|
81
|
+
otel-nested-visualize:
|
|
82
|
+
uv run python examples/otel_nested_demo.py 2>&1 | uv run python examples/otel_visualize.py
|
|
83
|
+
|
|
84
|
+
otel-jaeger-demo:
|
|
85
|
+
uv run python examples/otel_jaeger_demo.py $(args)
|
|
86
|
+
|
|
87
|
+
# ── MLflow + OTel combinations ──────────────────────────────────────
|
|
88
|
+
mlflow-otel-both:
|
|
89
|
+
uv run python examples/mlflow_otel_both_demo.py $(args)
|
|
90
|
+
|
|
91
|
+
mlflow-dual-export:
|
|
92
|
+
uv run python examples/mlflow_dual_export_demo.py $(args)
|
|
93
|
+
|
|
94
|
+
mlflow-otel-nested:
|
|
95
|
+
uv run python examples/mlflow_otel_nested_demo.py $(args)
|
|
96
|
+
|
|
75
97
|
escalation:
|
|
76
98
|
uv run python examples/escalation.py $(if $(fast_model),--fast-model $(fast_model)) $(if $(smart_model),--smart-model $(smart_model)) $(if $(message),--message "$(message)") $(args)
|
|
77
99
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flowra
|
|
3
|
-
Version: 0.0.23.
|
|
3
|
+
Version: 0.0.23.dev31
|
|
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
|
|
@@ -103,8 +103,8 @@
|
|
|
103
103
|
|
|
104
104
|
"PROMPT CACHING: handled via composable hook bundles that subscribe to PrepareLLMRequestEvent and set provider-specific cache hints via extra on blocks/tools",
|
|
105
105
|
"PrepareLLMRequestEvent: hook event emitted after LLMRequest is built but before the LLM call. Mutable field: request (LLMRequest). Use to modify messages, tools, or add cache control",
|
|
106
|
-
"flowra.lib.anthropic: Anthropic provider extensions (caching + tool search). Caching: pre-instantiated constants ANTHROPIC_CACHE_ALL_SYSTEM_MESSAGES, ANTHROPIC_CACHE_NON_TRANSIENT_SYSTEM_MESSAGES, ANTHROPIC_CACHE_ALL_TOOLS, ANTHROPIC_CACHE_ALL_MESSAGES, ANTHROPIC_CACHE_NON_TRANSIENT_MESSAGES, ANTHROPIC_CACHE_HISTORY_MESSAGES. Composite presets: ANTHROPIC_CACHE_ALL, ANTHROPIC_CACHE_SESSION. Tool search: AnthropicToolSearch(variant='bm25'|'regex', predicate=...) — defers tool loading, adds server-side search tool. Install via ext.install(hooks)",
|
|
107
|
-
"transient hint: blocks/messages with transient=True are (1) skipped by NonTransient caching bundles and (2) auto-filtered from ChatAgent session history",
|
|
106
|
+
"flowra.lib.anthropic: Anthropic provider extensions (caching + tool search). Caching: pre-instantiated constants ANTHROPIC_CACHE_ALL_SYSTEM_MESSAGES, ANTHROPIC_CACHE_NON_TRANSIENT_SYSTEM_MESSAGES, ANTHROPIC_CACHE_ALL_TOOLS, ANTHROPIC_CACHE_NON_TRANSIENT_TOOLS, ANTHROPIC_CACHE_ALL_MESSAGES, ANTHROPIC_CACHE_NON_TRANSIENT_MESSAGES, ANTHROPIC_CACHE_HISTORY_MESSAGES. Composite presets: ANTHROPIC_CACHE_ALL, ANTHROPIC_CACHE_SESSION, ANTHROPIC_CACHE_NON_TRANSIENT. Tool search: AnthropicToolSearch(variant='bm25'|'regex', predicate=...) — defers tool loading, adds server-side search tool. Install via ext.install(hooks)",
|
|
107
|
+
"transient hint: blocks/messages/tools with transient=True are (1) skipped by NonTransient caching bundles (which cache the non-transient prefix — stop at first transient) and (2) auto-filtered from ChatAgent session history",
|
|
108
108
|
"Anthropic extra passthrough: AnthropicVertexProvider merges block.extra into output dicts (**block.extra), so cache_control and other Anthropic-specific fields pass through directly",
|
|
109
109
|
|
|
110
110
|
"CONFIG: LLMConfig(model, temperature, max_tokens, stop_sequences, additional_config) configures LLM calls",
|
|
@@ -114,14 +114,15 @@
|
|
|
114
114
|
"Import ChatAgent: from flowra.lib.chat import ChatAgent, ChatConfig, ChatResult, ChatSpec, SaveHistoryEvent",
|
|
115
115
|
"Import LLMConfig: from flowra.lib import LLMConfig",
|
|
116
116
|
"Import LLM types: from flowra.llm import LLMProvider, SystemMessage, TextBlock, Usage, TextDelta, ThinkingDelta, ContentComplete",
|
|
117
|
-
"Import agent runtime: from flowra.agent import AgentRuntime, FileSessionStorage, InMemorySessionStorage",
|
|
117
|
+
"Import agent runtime: from flowra.agent import AgentRuntime, FileSessionStorage, InMemorySessionSnapshot, InMemorySessionStorage",
|
|
118
|
+
"InMemorySessionStorage.snapshot() returns InMemorySessionSnapshot (frozen dataclass with execution and node_states). InMemorySessionStorage(snapshot) constructs a new storage from snapshot (deep copy). Clone: InMemorySessionStorage(storage.snapshot())",
|
|
118
119
|
"Import tools: from flowra.tools import ToolRegistry, get_local_tool, tool, ToolErrorKind",
|
|
119
120
|
|
|
120
121
|
"FlowingContextVar: from flowra.agent import FlowingContextVar. Typed variables that flow with agent execution. Engine manages lifecycle (fork/restore for sibling isolation, snapshot/apply for task inheritance). Used by tracing integrations for parent span tracking. Usage: my_var = FlowingContextVar[str | None]('my.var', default=None); my_var.get(); my_var.set('value')",
|
|
121
122
|
|
|
122
123
|
"EXTENSIONS (flowra/ext/): optional integrations with external services. Each requires its own extra dependency",
|
|
123
|
-
"MLflow tracing: from flowra.ext.mlflow import install_mlflow_tracing. Requires flowra[mlflow]. Uses FlowingContextVar for parent tracking, MLflow set_span_in_context for W3C propagation. Creates MLflow traces with structured chat UI (CHAT_MODEL spans with OpenAI-format messages). Session support via session_id parameter. Usage: hooks = HookSubscription(); install_mlflow_tracing(hooks, experiment_name='my-exp', session_id='chat-123')",
|
|
124
|
-
"OpenTelemetry tracing: from flowra.ext.otel import install_otel_tracing. Requires flowra[otel]. Uses FlowingContextVar for parent tracking. Creates OTel spans with GenAI semantic conventions (gen_ai.operation.name, gen_ai.provider.name, gen_ai.usage.*, etc.). Users configure their own TracerProvider. Usage: install_otel_tracing(hooks, capture_content=False, session_id='chat-123')",
|
|
124
|
+
"MLflow tracing: from flowra.ext.mlflow import install_mlflow_tracing. Requires flowra[mlflow]. Uses FlowingContextVar for parent tracking, MLflow set_span_in_context for W3C propagation. Creates MLflow traces with structured chat UI (CHAT_MODEL spans with OpenAI-format messages). Session support via session_id parameter. Automatically integrates with external MLflow context (mlflow.get_current_active_span). Usage: hooks = HookSubscription(); install_mlflow_tracing(hooks, experiment_name='my-exp', session_id='chat-123')",
|
|
125
|
+
"OpenTelemetry tracing: from flowra.ext.otel import install_otel_tracing. Requires flowra[otel]. Uses FlowingContextVar for parent tracking. Creates OTel spans with GenAI semantic conventions (gen_ai.operation.name, gen_ai.provider.name, gen_ai.usage.*, etc.). Users configure their own TracerProvider. Automatically integrates with external OTel context (trace.get_current_span). Usage: install_otel_tracing(hooks, capture_content=False, session_id='chat-123')",
|
|
125
126
|
"__preview__() protocol: objects implementing __preview__() -> str get clean text in MLflow trace list/session view instead of str(). Built-in: ChatSpec, ChatResult, ToolLoopSpec, ToolLoopResult. Custom specs/results can implement it for readable summaries in any tracing backend"
|
|
126
127
|
],
|
|
127
128
|
"previousVersions": []
|
|
@@ -135,6 +135,23 @@ if await runtime.has_pending_execution():
|
|
|
135
135
|
result = await runtime.resume()
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
+
### Cloning in-memory storage
|
|
139
|
+
|
|
140
|
+
`InMemorySessionStorage` supports snapshotting — take a deep copy of all stored
|
|
141
|
+
data and use it to create a new, independent storage:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from flowra.agent import InMemorySessionStorage
|
|
145
|
+
|
|
146
|
+
# After some agent runs have populated the storage...
|
|
147
|
+
snapshot = storage.snapshot() # deep-copy all data out
|
|
148
|
+
cloned = InMemorySessionStorage(snapshot) # deep-copy into new storage
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
This is useful for forking a session (e.g. branching a conversation) or
|
|
152
|
+
inspecting storage contents. The snapshot is a frozen dataclass with `execution`
|
|
153
|
+
and `node_states` fields.
|
|
154
|
+
|
|
138
155
|
## Dependency injection
|
|
139
156
|
|
|
140
157
|
Agent constructors receive services by type matching. Some services are always
|
|
@@ -594,6 +594,26 @@ result = await runtime.run(agent=MyAgent, spec=spec)
|
|
|
594
594
|
result = await runtime.resume()
|
|
595
595
|
```
|
|
596
596
|
|
|
597
|
+
#### InMemorySessionStorage snapshot
|
|
598
|
+
|
|
599
|
+
`InMemorySessionStorage` supports snapshotting for cloning and data inspection:
|
|
600
|
+
|
|
601
|
+
```python
|
|
602
|
+
from flowra.agent import InMemorySessionSnapshot, InMemorySessionStorage
|
|
603
|
+
|
|
604
|
+
# Take a deep-copy snapshot of all stored data
|
|
605
|
+
snapshot: InMemorySessionSnapshot = storage.snapshot()
|
|
606
|
+
|
|
607
|
+
# Create a new storage from snapshot (deep copy)
|
|
608
|
+
cloned = InMemorySessionStorage(snapshot)
|
|
609
|
+
|
|
610
|
+
# Clone in one line
|
|
611
|
+
cloned = InMemorySessionStorage(storage.snapshot())
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
`InMemorySessionSnapshot` is a frozen dataclass with `execution` and `node_states`
|
|
615
|
+
fields — the complete contents of the storage.
|
|
616
|
+
|
|
597
617
|
---
|
|
598
618
|
|
|
599
619
|
## Internal modules (not public)
|
|
@@ -39,6 +39,26 @@ MLflow's `set_span_in_context()` / `detach_span_from_context()` populate
|
|
|
39
39
|
the MLflow context stack, enabling `update_current_trace()` for trace
|
|
40
40
|
previews and W3C `traceparent` propagation for distributed tracing.
|
|
41
41
|
|
|
42
|
+
## External context integration
|
|
43
|
+
|
|
44
|
+
When no flowra parent exists (root of execution), handlers check for an
|
|
45
|
+
external MLflow span via `mlflow.get_current_active_span()`. If found,
|
|
46
|
+
flowra's root span becomes a child of the external span.
|
|
47
|
+
|
|
48
|
+
This allows flowra to nest inside existing MLflow traces — e.g. when called
|
|
49
|
+
from a `with mlflow.start_span()` block or a `@mlflow.trace`-decorated function:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import mlflow
|
|
53
|
+
|
|
54
|
+
with mlflow.start_span(name="my_business_logic"):
|
|
55
|
+
# flowra spans become children of "my_business_logic"
|
|
56
|
+
result = await runtime.run(agent=MyAgent, spec=MySpec(...))
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Priority: internal flowra context (`FlowingContextVar`) > external MLflow context
|
|
60
|
+
(`get_current_active_span()`). External context is only used at the root.
|
|
61
|
+
|
|
42
62
|
## Parameters
|
|
43
63
|
|
|
44
64
|
```python
|
|
@@ -110,8 +130,9 @@ uv run mlflow ui --port 5050
|
|
|
110
130
|
# Open http://localhost:5050
|
|
111
131
|
```
|
|
112
132
|
|
|
113
|
-
##
|
|
133
|
+
## Examples
|
|
114
134
|
|
|
115
135
|
```bash
|
|
116
|
-
make mlflow-demo
|
|
136
|
+
make mlflow-demo # basic MLflow tracing
|
|
137
|
+
make mlflow-nested-demo # external MLflow span wrapping flowra
|
|
117
138
|
```
|
|
@@ -93,6 +93,29 @@ provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
|
|
|
93
93
|
trace.set_tracer_provider(provider)
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
+
## External context integration
|
|
97
|
+
|
|
98
|
+
When no flowra parent exists (root of execution), `_start_span` checks for an
|
|
99
|
+
external OTel span via `trace.get_current_span()`. If a recording span is found,
|
|
100
|
+
flowra's root span becomes a child of the external span.
|
|
101
|
+
|
|
102
|
+
This allows flowra to nest inside existing OTel traces — e.g. when called
|
|
103
|
+
from a `tracer.start_as_current_span()` block:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from opentelemetry import trace
|
|
107
|
+
|
|
108
|
+
app_tracer = trace.get_tracer("my_app")
|
|
109
|
+
|
|
110
|
+
with app_tracer.start_as_current_span("my_business_logic"):
|
|
111
|
+
# flowra spans become children of "my_business_logic"
|
|
112
|
+
result = await runtime.run(agent=MyAgent, spec=MySpec(...))
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Priority: internal flowra context (`FlowingContextVar`) > external OTel context
|
|
116
|
+
(`trace.get_current_span()`). External context is only used at the root.
|
|
117
|
+
Non-recording spans (e.g. `INVALID_SPAN`) are ignored.
|
|
118
|
+
|
|
96
119
|
## Using with MLflow
|
|
97
120
|
|
|
98
121
|
Both integrations can run side by side — they use independent `FlowingContextVar`
|
|
@@ -111,8 +134,11 @@ Alternatively, MLflow can export to OTel backends via dual export
|
|
|
111
134
|
(`MLFLOW_TRACE_ENABLE_OTLP_DUAL_EXPORT=true`) — in that case you only need
|
|
112
135
|
the MLflow integration.
|
|
113
136
|
|
|
114
|
-
##
|
|
137
|
+
## Examples
|
|
115
138
|
|
|
116
139
|
```bash
|
|
117
|
-
make otel-demo
|
|
140
|
+
make otel-demo # basic OTel tracing (console)
|
|
141
|
+
make otel-nested-demo # external OTel span wrapping flowra (console)
|
|
142
|
+
make otel-nested-visualize # same, with tree visualization
|
|
143
|
+
make otel-jaeger-demo # external OTel span, exported to Jaeger
|
|
118
144
|
```
|
|
@@ -148,6 +148,35 @@ This tells MLflow to use the global OTel `TracerProvider` instead of its own
|
|
|
148
148
|
isolated one. All spans — from your web framework, from flowra, from any other
|
|
149
149
|
OTel-instrumented library — appear in a single unified trace.
|
|
150
150
|
|
|
151
|
+
## Integration with external tracing context
|
|
152
|
+
|
|
153
|
+
Both MLflow and OTel integrations automatically detect and attach to an existing
|
|
154
|
+
tracing context. If your application already creates spans before calling flowra,
|
|
155
|
+
flowra's spans become children of the external span.
|
|
156
|
+
|
|
157
|
+
- **MLflow:** detects via `mlflow.get_current_active_span()`
|
|
158
|
+
- **OTel:** detects via `trace.get_current_span()` (ignores non-recording spans)
|
|
159
|
+
|
|
160
|
+
This works in all setups:
|
|
161
|
+
|
|
162
|
+
| Setup | External MLflow span | External OTel span |
|
|
163
|
+
|---------------------|----------------------|----------------------------|
|
|
164
|
+
| MLflow only | Nested inside | Not detected |
|
|
165
|
+
| OTel only | Not detected | Nested inside |
|
|
166
|
+
| Both independently | MLflow: nested | OTel: nested (independent) |
|
|
167
|
+
| MLflow + OTel export| Nested inside | Via MLflow's OTel layer |
|
|
168
|
+
|
|
169
|
+
Priority: internal flowra context > external context. External context is only
|
|
170
|
+
used when there is no flowra parent (i.e. at the root of flowra's execution).
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
# Example: external OTel span wrapping flowra with both tracing systems
|
|
174
|
+
with app_tracer.start_as_current_span("business_workflow"):
|
|
175
|
+
# OTel integration: nests inside "business_workflow"
|
|
176
|
+
# MLflow integration: creates independent trace (no OTel awareness)
|
|
177
|
+
result = await runtime.run(agent=MyAgent, spec=MySpec(...))
|
|
178
|
+
```
|
|
179
|
+
|
|
151
180
|
## Choosing the right setup
|
|
152
181
|
|
|
153
182
|
```
|
|
@@ -28,10 +28,11 @@ All bundles are pre-instantiated constants (no need to call constructors):
|
|
|
28
28
|
| Constant | What it caches |
|
|
29
29
|
|-------------------------------------------------|------------------------------------------------------------------------------------------|
|
|
30
30
|
| `ANTHROPIC_CACHE_ALL_SYSTEM_MESSAGES` | Last block of the last system message |
|
|
31
|
-
| `ANTHROPIC_CACHE_NON_TRANSIENT_SYSTEM_MESSAGES` | Last non-transient block
|
|
31
|
+
| `ANTHROPIC_CACHE_NON_TRANSIENT_SYSTEM_MESSAGES` | Last non-transient system block in the non-transient prefix (stops at first transient) |
|
|
32
32
|
| `ANTHROPIC_CACHE_ALL_TOOLS` | Last tool definition |
|
|
33
|
+
| `ANTHROPIC_CACHE_NON_TRANSIENT_TOOLS` | Last non-transient tool in the non-transient prefix (stops at first transient) |
|
|
33
34
|
| `ANTHROPIC_CACHE_ALL_MESSAGES` | Last block of the last message |
|
|
34
|
-
| `ANTHROPIC_CACHE_NON_TRANSIENT_MESSAGES` | Last non-transient block
|
|
35
|
+
| `ANTHROPIC_CACHE_NON_TRANSIENT_MESSAGES` | Last non-transient message block in the non-transient prefix (stops at first transient) |
|
|
35
36
|
| `ANTHROPIC_CACHE_HISTORY_MESSAGES` | Last message that was in history before the current turn (via `SaveHistoryEvent` marker) |
|
|
36
37
|
|
|
37
38
|
```python
|
|
@@ -271,7 +271,8 @@ and conversation messages. Individual bundles are also available — see
|
|
|
271
271
|
|
|
272
272
|
The `transient` hint on blocks and messages lets caching bundles skip non-permanent
|
|
273
273
|
content when placing cache breakpoints. For example,
|
|
274
|
-
`AnthropicCacheNonTransientMessages`
|
|
274
|
+
`AnthropicCacheNonTransientMessages` caches the last non-transient message block
|
|
275
|
+
in the non-transient prefix (stops at the first transient message).
|
|
275
276
|
|
|
276
277
|
To implement custom caching logic for other providers, subscribe to
|
|
277
278
|
`PrepareLLMRequestEvent` and modify `event.data.request` directly.
|
|
@@ -217,14 +217,15 @@ message — covers the most common case.
|
|
|
217
217
|
|
|
218
218
|
For fine-grained control, install individual bundles (all are pre-instantiated constants):
|
|
219
219
|
|
|
220
|
-
| Constant | What it caches
|
|
221
|
-
|
|
222
|
-
| `ANTHROPIC_CACHE_ALL_SYSTEM_MESSAGES` | Last block of the last system message
|
|
223
|
-
| `ANTHROPIC_CACHE_NON_TRANSIENT_SYSTEM_MESSAGES` | Last non-transient system block
|
|
224
|
-
| `ANTHROPIC_CACHE_ALL_TOOLS` | Last tool definition
|
|
225
|
-
| `
|
|
226
|
-
| `
|
|
227
|
-
| `
|
|
220
|
+
| Constant | What it caches |
|
|
221
|
+
|-------------------------------------------------|-------------------------------------------------------------------------|
|
|
222
|
+
| `ANTHROPIC_CACHE_ALL_SYSTEM_MESSAGES` | Last block of the last system message |
|
|
223
|
+
| `ANTHROPIC_CACHE_NON_TRANSIENT_SYSTEM_MESSAGES` | Last non-transient system block (stops at first transient) |
|
|
224
|
+
| `ANTHROPIC_CACHE_ALL_TOOLS` | Last tool definition |
|
|
225
|
+
| `ANTHROPIC_CACHE_NON_TRANSIENT_TOOLS` | Last non-transient tool (stops at first transient) |
|
|
226
|
+
| `ANTHROPIC_CACHE_ALL_MESSAGES` | Last block of the last message |
|
|
227
|
+
| `ANTHROPIC_CACHE_NON_TRANSIENT_MESSAGES` | Last non-transient message block (stops at first transient) |
|
|
228
|
+
| `ANTHROPIC_CACHE_HISTORY_MESSAGES` | Last history message (before current turn) |
|
|
228
229
|
|
|
229
230
|
```python
|
|
230
231
|
from flowra.lib.anthropic import (
|
|
@@ -260,6 +260,55 @@ install_otel_tracing(
|
|
|
260
260
|
)
|
|
261
261
|
```
|
|
262
262
|
|
|
263
|
+
## Integration with external tracing context
|
|
264
|
+
|
|
265
|
+
Both integrations automatically detect and attach to an existing tracing context.
|
|
266
|
+
If your application already creates spans (e.g. a FastAPI middleware, a gRPC
|
|
267
|
+
interceptor, or business-logic code), flowra's spans become children of the
|
|
268
|
+
external span.
|
|
269
|
+
|
|
270
|
+
### MLflow
|
|
271
|
+
|
|
272
|
+
If a span was created with `with mlflow.start_span()` or `@mlflow.trace`, flowra
|
|
273
|
+
detects it via `mlflow.get_current_active_span()` and nests inside:
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
import mlflow
|
|
277
|
+
from flowra.ext.mlflow import install_mlflow_tracing
|
|
278
|
+
|
|
279
|
+
hooks = HookSubscription()
|
|
280
|
+
install_mlflow_tracing(hooks, experiment_name="my-experiment")
|
|
281
|
+
|
|
282
|
+
# External MLflow span — flowra spans become children
|
|
283
|
+
with mlflow.start_span(name="my_business_logic"):
|
|
284
|
+
result = await runtime.run(agent=MyAgent, spec=MySpec(...))
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### OpenTelemetry
|
|
288
|
+
|
|
289
|
+
If a span was created with `tracer.start_as_current_span()`, flowra detects it
|
|
290
|
+
via `trace.get_current_span()` and nests inside:
|
|
291
|
+
|
|
292
|
+
```python
|
|
293
|
+
from opentelemetry import trace
|
|
294
|
+
from flowra.ext.otel import install_otel_tracing
|
|
295
|
+
|
|
296
|
+
hooks = HookSubscription()
|
|
297
|
+
install_otel_tracing(hooks)
|
|
298
|
+
|
|
299
|
+
app_tracer = trace.get_tracer("my_app")
|
|
300
|
+
|
|
301
|
+
# External OTel span — flowra spans become children
|
|
302
|
+
with app_tracer.start_as_current_span("my_business_logic"):
|
|
303
|
+
result = await runtime.run(agent=MyAgent, spec=MySpec(...))
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Priority
|
|
307
|
+
|
|
308
|
+
Internal flowra context (from parent agent/step spans) takes priority over
|
|
309
|
+
external context. External context is only used when there is no flowra parent —
|
|
310
|
+
i.e. at the root of the flowra execution.
|
|
311
|
+
|
|
263
312
|
## Choosing the right tracing setup
|
|
264
313
|
|
|
265
314
|
| Setup | Best for | What you get |
|
|
@@ -9,6 +9,12 @@ Implemented via `FlowingContextVar` (see `flowing_context.md`). The MLflow
|
|
|
9
9
|
handler uses `_mlflow_parent: FlowingContextVar[LiveSpan | None]` for parent
|
|
10
10
|
tracking. The engine's fork/restore mechanism handles sibling isolation.
|
|
11
11
|
|
|
12
|
+
**Update (2026-03-24):** Added external context integration. When no flowra
|
|
13
|
+
parent exists, handlers check `mlflow.get_current_active_span()` to detect
|
|
14
|
+
external MLflow spans. This allows flowra to nest inside existing MLflow traces
|
|
15
|
+
(e.g. `with mlflow.start_span()` blocks). Same approach for OTel via
|
|
16
|
+
`trace.get_current_span()`.
|
|
17
|
+
|
|
12
18
|
## Problem
|
|
13
19
|
|
|
14
20
|
`flowra/ext/mlflow.py` manually managed span parent-child relationships via a
|
|
@@ -9,6 +9,11 @@ Phase 1 implemented as `flowra/ext/otel.py`. Span-hook integration with GenAI
|
|
|
9
9
|
semantic conventions. Uses `FlowingContextVar` for parent tracking (same as MLflow).
|
|
10
10
|
Optional dependency: `flowra[otel]` (`opentelemetry-api`, `opentelemetry-sdk`).
|
|
11
11
|
|
|
12
|
+
**Update (2026-03-24):** Added external context integration. When no flowra
|
|
13
|
+
parent exists, `_start_span` checks `trace.get_current_span()` to detect
|
|
14
|
+
external OTel spans (ignoring non-recording spans). This allows flowra to nest
|
|
15
|
+
inside existing OTel traces (e.g. `tracer.start_as_current_span()` blocks).
|
|
16
|
+
|
|
12
17
|
## 1. MLflow + OTel Relationship
|
|
13
18
|
|
|
14
19
|
### Architecture
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# MLflow + OpenTelemetry Tracing Combinations
|
|
2
|
+
|
|
3
|
+
This directory contains examples demonstrating different ways to use MLflow and OpenTelemetry tracing together.
|
|
4
|
+
|
|
5
|
+
## Quick Reference
|
|
6
|
+
|
|
7
|
+
| Example | MLflow | OTel | Trace IDs | Best for |
|
|
8
|
+
|---------|--------|------|-----------|----------|
|
|
9
|
+
| `mlflow_otel_both_demo.py` | ✓ Independent | ✓ Independent | Different | Full picture, two UIs |
|
|
10
|
+
| `mlflow_dual_export_demo.py` | ✓ Dual export | Via MLflow | **Same** | Single instrumentation |
|
|
11
|
+
| `mlflow_otel_nested_demo.py` | ✓ Independent | ✓ Nested | Different | External OTel context |
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
### 1. Both Independently (`mlflow_otel_both_demo.py`)
|
|
16
|
+
|
|
17
|
+
**What it does:**
|
|
18
|
+
- Enables both MLflow and OTel tracing
|
|
19
|
+
- Creates two independent trace trees
|
|
20
|
+
- Different trace IDs
|
|
21
|
+
|
|
22
|
+
**Run:**
|
|
23
|
+
```bash
|
|
24
|
+
make mlflow-otel-both
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**View traces:**
|
|
28
|
+
- MLflow: http://localhost:5050 (experiment: `mlflow-otel-both`)
|
|
29
|
+
- Jaeger: http://localhost:16686 (service: `flowra`)
|
|
30
|
+
|
|
31
|
+
**Use case:**
|
|
32
|
+
You want the full picture with both UIs:
|
|
33
|
+
- MLflow for LLM-specific details (cost, tokens, chat preview)
|
|
34
|
+
- OTel for distributed tracing with standard GenAI semantic conventions
|
|
35
|
+
|
|
36
|
+
**Trade-off:** Two separate trace IDs — no direct link between traces.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
### 2. MLflow Dual Export (`mlflow_dual_export_demo.py`)
|
|
41
|
+
|
|
42
|
+
**What it does:**
|
|
43
|
+
- Enables only MLflow tracing
|
|
44
|
+
- MLflow automatically exports spans to OTel collector
|
|
45
|
+
- Same trace ID in both systems
|
|
46
|
+
|
|
47
|
+
**Setup:**
|
|
48
|
+
Requires environment variables:
|
|
49
|
+
```bash
|
|
50
|
+
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="http://localhost:4317"
|
|
51
|
+
export MLFLOW_TRACE_ENABLE_OTLP_DUAL_EXPORT="true"
|
|
52
|
+
export OTEL_SERVICE_NAME="flowra-mlflow-dual"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Run:**
|
|
56
|
+
```bash
|
|
57
|
+
make mlflow-dual-export
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**View traces:**
|
|
61
|
+
- MLflow: http://localhost:5050 (experiment: `mlflow-dual-export`)
|
|
62
|
+
- Jaeger: http://localhost:16686 (service: `flowra-mlflow-dual`)
|
|
63
|
+
|
|
64
|
+
**Check:** Trace IDs should be **identical** in both UIs!
|
|
65
|
+
|
|
66
|
+
**Use case:**
|
|
67
|
+
Single instrumentation with dual output. MLflow is the source of truth, OTel gets a copy.
|
|
68
|
+
|
|
69
|
+
**Trade-off:** MLflow uses its own attribute names (`mlflow.chat.tokenUsage`) instead of
|
|
70
|
+
standard GenAI semantic conventions (`gen_ai.usage.input_tokens`), so OTel backends
|
|
71
|
+
won't recognize them as standard GenAI spans.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### 3. External OTel Span Wrapping Flowra (`mlflow_otel_nested_demo.py`)
|
|
76
|
+
|
|
77
|
+
**What it does:**
|
|
78
|
+
- External OTel span created by business application
|
|
79
|
+
- Flowra runs inside with BOTH MLflow and OTel tracing
|
|
80
|
+
- OTel spans nest into external context
|
|
81
|
+
- MLflow creates independent trace tree
|
|
82
|
+
|
|
83
|
+
**Run:**
|
|
84
|
+
```bash
|
|
85
|
+
make mlflow-otel-nested
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**View traces:**
|
|
89
|
+
- Jaeger: http://localhost:16686 (service: `business_app`)
|
|
90
|
+
- Shows: `business_workflow` span with flowra spans nested inside
|
|
91
|
+
- MLflow: http://localhost:5050 (experiment: `mlflow-otel-nested`)
|
|
92
|
+
- Shows: independent MLflow trace tree
|
|
93
|
+
|
|
94
|
+
**Use case:**
|
|
95
|
+
Flowra is part of a larger service that already uses OTel tracing. You want:
|
|
96
|
+
- OTel: full distributed trace including external business logic
|
|
97
|
+
- MLflow: LLM-specific view with cost/token details
|
|
98
|
+
|
|
99
|
+
**Key insight:**
|
|
100
|
+
- OTel integrates with external context (same trace tree)
|
|
101
|
+
- MLflow creates independent tree (its own trace ID)
|
|
102
|
+
- Both capture the SAME execution from different perspectives
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Other Examples
|
|
107
|
+
|
|
108
|
+
### 4. MLflow Only with External Span (`mlflow_nested_demo.py`)
|
|
109
|
+
|
|
110
|
+
External MLflow span wrapping flowra (only MLflow tracing).
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
make mlflow-nested-demo
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 5. OTel Only with External Span (`otel_nested_demo.py`)
|
|
117
|
+
|
|
118
|
+
External OTel span wrapping flowra (only OTel tracing).
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
make otel-nested-demo
|
|
122
|
+
# Visualize:
|
|
123
|
+
make otel-nested-visualize
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 6. OTel with Jaeger (`otel_jaeger_demo.py`)
|
|
127
|
+
|
|
128
|
+
OTel tracing with external span, exported to Jaeger.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
make otel-jaeger-demo
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Decision Tree
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
Do you use OTel in your service already?
|
|
140
|
+
├── No → MLflow only (simplest, best LLM UI)
|
|
141
|
+
└── Yes
|
|
142
|
+
├── Do you need MLflow's LLM-specific UI?
|
|
143
|
+
│ ├── No → OTel only (standard, lightweight)
|
|
144
|
+
│ └── Yes
|
|
145
|
+
│ ├── Do you want one trace ID across both?
|
|
146
|
+
│ │ ├── Yes → MLflow with OTel export (dual export)
|
|
147
|
+
│ │ └── No → Both independently
|
|
148
|
+
│ └── Do you need standard GenAI semconv attributes?
|
|
149
|
+
│ ├── Yes → Both independently (OTel uses gen_ai.*)
|
|
150
|
+
│ └── No → MLflow with OTel export (simpler)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Quick Start
|
|
156
|
+
|
|
157
|
+
1. **Start Jaeger** (for OTel examples):
|
|
158
|
+
```bash
|
|
159
|
+
docker run -d --name jaeger \
|
|
160
|
+
-p 16686:16686 \
|
|
161
|
+
-p 4317:4317 \
|
|
162
|
+
jaegertracing/all-in-one:latest
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
2. **Run an example**:
|
|
166
|
+
```bash
|
|
167
|
+
make mlflow-otel-both
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
3. **View traces**:
|
|
171
|
+
- MLflow UI: `uv run mlflow ui --port 5050` → http://localhost:5050
|
|
172
|
+
- Jaeger UI: http://localhost:16686
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## See Also
|
|
177
|
+
|
|
178
|
+
- `docs/observability.md` — full documentation on tracing options
|
|
179
|
+
- `flowra/ext/mlflow.py` — MLflow integration implementation
|
|
180
|
+
- `flowra/ext/otel.py` — OpenTelemetry integration implementation
|