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.
Files changed (270) hide show
  1. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/CHANGELOG.md +30 -0
  2. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/Makefile +22 -0
  3. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/PKG-INFO +1 -1
  4. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/context7.json +6 -5
  5. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/agents.md +17 -0
  6. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/agent.md +20 -0
  7. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/ext/mlflow.md +23 -2
  8. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/ext/otel.md +28 -2
  9. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/ext/tracing-guide.md +29 -0
  10. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/lib/anthropic.md +3 -2
  11. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/lib.md +2 -1
  12. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/llm.md +9 -8
  13. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/observability.md +49 -0
  14. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/mlflow_context_migration.md +6 -0
  15. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/otel_integration.md +5 -0
  16. flowra-0.0.23.dev31/examples/TRACING_COMBINATIONS.md +180 -0
  17. flowra-0.0.23.dev31/examples/mlflow_dual_export_demo.py +133 -0
  18. flowra-0.0.23.dev31/examples/mlflow_nested_demo.py +107 -0
  19. flowra-0.0.23.dev31/examples/mlflow_otel_both_demo.py +113 -0
  20. flowra-0.0.23.dev31/examples/mlflow_otel_nested_demo.py +150 -0
  21. flowra-0.0.23.dev31/examples/otel_jaeger_demo.py +135 -0
  22. flowra-0.0.23.dev31/examples/otel_nested_demo.py +115 -0
  23. flowra-0.0.23.dev31/examples/otel_visualize.py +144 -0
  24. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/__init__.py +2 -1
  25. flowra-0.0.23.dev31/flowra/agent/storage/__init__.py +5 -0
  26. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/storage/in_memory.py +25 -4
  27. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/ext/mlflow.py +16 -1
  28. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/ext/otel.py +11 -1
  29. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/anthropic/__init__.py +6 -0
  30. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/anthropic/cache.py +44 -10
  31. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/anthropic/presets.py +14 -2
  32. flowra-0.0.23.dev31/flowra/version.py +2 -0
  33. flowra-0.0.23.dev31/tests/agent/storage/test_in_memory.py +140 -0
  34. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/ext/test_mlflow.py +2 -0
  35. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/anthropic/test_anthropic.py +27 -1
  36. flowra-0.0.23.dev28/flowra/agent/storage/__init__.py +0 -5
  37. flowra-0.0.23.dev28/flowra/version.py +0 -2
  38. flowra-0.0.23.dev28/tests/agent/storage/test_in_memory.py +0 -70
  39. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/.claude/commands/update-pricing.md +0 -0
  40. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/.env.example +0 -0
  41. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/.github/workflows/master.yml +0 -0
  42. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/.github/workflows/publish.yml +0 -0
  43. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/.github/workflows/pull_request.yml +0 -0
  44. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/.github/workflows/pull_request_e2e.yml +0 -0
  45. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/.gitignore +0 -0
  46. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/.python-version +0 -0
  47. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/CLAUDE.md +0 -0
  48. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/LICENSE +0 -0
  49. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/README.md +0 -0
  50. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/getting-started.md +0 -0
  51. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/architecture.md +0 -0
  52. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/ext.md +0 -0
  53. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/llm.md +0 -0
  54. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/patterns.md +0 -0
  55. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/internal/tools.md +0 -0
  56. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/patterns.md +0 -0
  57. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/flowing_context.md +0 -0
  58. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/hooks_redesign.md +0 -0
  59. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/model_fallback.md +0 -0
  60. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/pricing_complexity.md +0 -0
  61. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/provider_extensions.md +0 -0
  62. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/spawn_strategies.md +0 -0
  63. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/strands_comparison.md +0 -0
  64. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/tool_error_signals.md +0 -0
  65. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/tool_search_tool.md +0 -0
  66. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/research/voice_stt.md +0 -0
  67. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/review_prompts/step1_structure.md +0 -0
  68. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/review_prompts/step2_code_style.md +0 -0
  69. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/review_prompts/step3_documentation.md +0 -0
  70. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/review_prompts/step4_doc_readability.md +0 -0
  71. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/review_prompts/step5_doc_audit.md +0 -0
  72. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/review_prompts/step6_tests.md +0 -0
  73. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/todo.md +0 -0
  74. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/docs/tools.md +0 -0
  75. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/__init__.py +0 -0
  76. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/agent_as_tool.py +0 -0
  77. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/app_agent.py +0 -0
  78. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/console_chat.py +0 -0
  79. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/docs/__init__.py +0 -0
  80. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/docs/agents_custom.py +0 -0
  81. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/docs/agents_parallel.py +0 -0
  82. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/docs/getting_started_chat.py +0 -0
  83. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/docs/getting_started_streaming.py +0 -0
  84. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/docs/getting_started_tools.py +0 -0
  85. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/docs/llm_streaming.py +0 -0
  86. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/docs/llm_structured_output.py +0 -0
  87. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/docs/tools_service_injection.py +0 -0
  88. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/escalation.py +0 -0
  89. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/llm_logging.py +0 -0
  90. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/llm_routing.py +0 -0
  91. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/menu_agent.py +0 -0
  92. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/menu_agent_class.py +0 -0
  93. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/mlflow_demo.py +0 -0
  94. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/model_registry.py +0 -0
  95. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/otel_demo.py +0 -0
  96. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/race.py +0 -0
  97. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/span_crash_demo.py +0 -0
  98. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/span_demo.py +0 -0
  99. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/system_prompt.txt +0 -0
  100. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/tools/__init__.py +0 -0
  101. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/tools/calculator.py +0 -0
  102. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/tools/random_numbers.py +0 -0
  103. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/tools/switch_model.py +0 -0
  104. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/examples/tui_chat.py +0 -0
  105. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/__init__.py +0 -0
  106. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/_sentinel.py +0 -0
  107. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/__init__.py +0 -0
  108. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/agent.py +0 -0
  109. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/agent_arg.py +0 -0
  110. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/__init__.py +0 -0
  111. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/compiler.py +0 -0
  112. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/contract.py +0 -0
  113. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/init_params.py +0 -0
  114. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/instance.py +0 -0
  115. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/step_params.py +0 -0
  116. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/step_validation.py +0 -0
  117. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/steps.py +0 -0
  118. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/type_helpers.py +0 -0
  119. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/compile/type_registry.py +0 -0
  120. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/model.py +0 -0
  121. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/registry.py +0 -0
  122. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/step.py +0 -0
  123. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/step_arg.py +0 -0
  124. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/definition/step_helpers.py +0 -0
  125. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/flow/__init__.py +0 -0
  126. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/flow/actions.py +0 -0
  127. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/flow/context.py +0 -0
  128. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/flow/flowing.py +0 -0
  129. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/flow/hooks.py +0 -0
  130. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/flow/interrupt.py +0 -0
  131. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/flow/interrupt_helpers.py +0 -0
  132. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/flow/spawn.py +0 -0
  133. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/flow/timeout.py +0 -0
  134. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/runtime/__init__.py +0 -0
  135. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/runtime/engine.py +0 -0
  136. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/runtime/execution.py +0 -0
  137. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/runtime/instance_factory.py +0 -0
  138. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/runtime/runtime.py +0 -0
  139. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/runtime/scope.py +0 -0
  140. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/runtime/serialization.py +0 -0
  141. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/runtime/spans.py +0 -0
  142. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/runtime/spawn_tree.py +0 -0
  143. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/services.py +0 -0
  144. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/state/__init__.py +0 -0
  145. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/state/markers.py +0 -0
  146. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/state/store.py +0 -0
  147. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/state/values.py +0 -0
  148. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/storage/file.py +0 -0
  149. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/agent/storage/session_storage.py +0 -0
  150. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/ext/__init__.py +0 -0
  151. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/__init__.py +0 -0
  152. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/anthropic/tool_search.py +0 -0
  153. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/chat/__init__.py +0 -0
  154. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/chat/agent.py +0 -0
  155. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/chat/config.py +0 -0
  156. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/chat/hook_events.py +0 -0
  157. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/chat/spec.py +0 -0
  158. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/config_value.py +0 -0
  159. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/llm_call/__init__.py +0 -0
  160. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/llm_call/agent.py +0 -0
  161. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/llm_call/spec.py +0 -0
  162. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/llm_config.py +0 -0
  163. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/observability/__init__.py +0 -0
  164. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/observability/llm_hooks.py +0 -0
  165. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/__init__.py +0 -0
  166. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/agent.py +0 -0
  167. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/config.py +0 -0
  168. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/context.py +0 -0
  169. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/hook_events.py +0 -0
  170. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/spec.py +0 -0
  171. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/tool_call/__init__.py +0 -0
  172. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/tool_call/agent.py +0 -0
  173. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/tool_call/agent_tool.py +0 -0
  174. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/lib/tool_loop/tool_call/context.py +0 -0
  175. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/__init__.py +0 -0
  176. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/base.py +0 -0
  177. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/blocks.py +0 -0
  178. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/messages.py +0 -0
  179. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/pricing/__init__.py +0 -0
  180. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/pricing/anthropic.py +0 -0
  181. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/pricing/google.py +0 -0
  182. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/pricing/openai.py +0 -0
  183. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/provider.py +0 -0
  184. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/providers/__init__.py +0 -0
  185. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/providers/anthropic_vertex.py +0 -0
  186. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/providers/google_vertex.py +0 -0
  187. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/providers/openai.py +0 -0
  188. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/request.py +0 -0
  189. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/response.py +0 -0
  190. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/schema_formatting.py +0 -0
  191. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/schema_validation.py +0 -0
  192. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/stream.py +0 -0
  193. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/llm/tools.py +0 -0
  194. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/py.typed +0 -0
  195. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/tools/__init__.py +0 -0
  196. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/tools/local_tool.py +0 -0
  197. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/tools/mcp_connection.py +0 -0
  198. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/tools/tool_arg.py +0 -0
  199. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/tools/tool_group.py +0 -0
  200. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/tools/tool_registry.py +0 -0
  201. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/flowra/tools/types.py +0 -0
  202. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/pyproject.toml +0 -0
  203. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/__init__.py +0 -0
  204. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/__init__.py +0 -0
  205. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/definition/__init__.py +0 -0
  206. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/definition/compile/__init__.py +0 -0
  207. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/definition/compile/test_compile.py +0 -0
  208. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/definition/compile/test_type_helpers.py +0 -0
  209. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/definition/test_agent.py +0 -0
  210. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/definition/test_registry.py +0 -0
  211. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/definition/test_step_helpers.py +0 -0
  212. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/flow/__init__.py +0 -0
  213. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/flow/test_agent_def.py +0 -0
  214. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/flow/test_context.py +0 -0
  215. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/flow/test_hooks.py +0 -0
  216. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/flow/test_interrupt.py +0 -0
  217. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/flow/test_spans.py +0 -0
  218. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/flow/test_timeout.py +0 -0
  219. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/flow/test_with_interrupt.py +0 -0
  220. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/runtime/__init__.py +0 -0
  221. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/runtime/test_engine.py +0 -0
  222. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/runtime/test_engine_spans.py +0 -0
  223. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/runtime/test_hook_context.py +0 -0
  224. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/runtime/test_persistence.py +0 -0
  225. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/runtime/test_runtime.py +0 -0
  226. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/runtime/test_scope.py +0 -0
  227. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/runtime/test_serialization.py +0 -0
  228. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/runtime/test_spec_in_constructor.py +0 -0
  229. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/state/__init__.py +0 -0
  230. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/state/test_values.py +0 -0
  231. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/storage/__init__.py +0 -0
  232. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/storage/test_file.py +0 -0
  233. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/agent/test_missing_scenarios.py +0 -0
  234. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/ext/__init__.py +0 -0
  235. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/ext/test_otel.py +0 -0
  236. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/__init__.py +0 -0
  237. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/anthropic/__init__.py +0 -0
  238. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/test_chat_agent.py +0 -0
  239. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/test_config_value.py +0 -0
  240. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/test_llm_call_agent.py +0 -0
  241. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/test_matches_tool_filter.py +0 -0
  242. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/test_tool_call_agent.py +0 -0
  243. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/test_tool_call_agent_call_agent.py +0 -0
  244. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/test_tool_loop_agent.py +0 -0
  245. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/lib/tool_loop/__init__.py +0 -0
  246. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/__init__.py +0 -0
  247. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/pricing/__init__.py +0 -0
  248. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/pricing/test_anthropic.py +0 -0
  249. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/pricing/test_google.py +0 -0
  250. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/pricing/test_openai.py +0 -0
  251. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/providers/__init__.py +0 -0
  252. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/providers/test_anthropic_e2e.py +0 -0
  253. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/providers/test_anthropic_vertex.py +0 -0
  254. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/providers/test_google_vertex.py +0 -0
  255. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/providers/test_google_vertex_e2e.py +0 -0
  256. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/providers/test_openai_e2e.py +0 -0
  257. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/providers/test_openai_provider.py +0 -0
  258. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/test_cost_breakdown.py +0 -0
  259. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/test_metadata.py +0 -0
  260. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/test_response.py +0 -0
  261. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/test_schema_formatting.py +0 -0
  262. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/test_schema_validation.py +0 -0
  263. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/llm/test_stream.py +0 -0
  264. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/tools/__init__.py +0 -0
  265. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/tools/test_local_tool.py +0 -0
  266. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/tools/test_mcp_connection.py +0 -0
  267. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/tools/test_tool_group.py +0 -0
  268. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tests/tools/test_tool_registry.py +0 -0
  269. {flowra-0.0.23.dev28 → flowra-0.0.23.dev31}/tools/sync_pricing.py +0 -0
  270. {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.dev28
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
- ## Example
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
- ## Example
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 of the last non-transient system message |
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 of the last non-transient message |
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` only caches the last non-transient message.
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
- | `ANTHROPIC_CACHE_ALL_MESSAGES` | Last block of the last message |
226
- | `ANTHROPIC_CACHE_NON_TRANSIENT_MESSAGES` | Last non-transient message block |
227
- | `ANTHROPIC_CACHE_HISTORY_MESSAGES` | Last history message (before current turn) |
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