flowra 0.0.26.dev37__tar.gz → 0.0.27.dev40__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 (271) hide show
  1. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/CHANGELOG.md +11 -0
  2. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/CLAUDE.md +1 -1
  3. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/PKG-INFO +1 -1
  4. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/context7.json +1 -1
  5. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/llm.md +14 -11
  6. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/llm_logging.py +2 -2
  7. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/ext/mlflow.py +2 -2
  8. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/__init__.py +8 -2
  9. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/blocks.py +12 -4
  10. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/providers/anthropic_vertex.py +4 -3
  11. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/providers/google_vertex.py +2 -2
  12. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/providers/openai.py +10 -8
  13. flowra-0.0.27.dev40/flowra/version.py +2 -0
  14. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/runtime/test_serialization.py +6 -6
  15. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/providers/test_anthropic_vertex.py +6 -6
  16. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/providers/test_google_vertex.py +4 -4
  17. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/providers/test_openai_provider.py +4 -4
  18. flowra-0.0.26.dev37/flowra/version.py +0 -2
  19. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/.claude/commands/update-pricing.md +0 -0
  20. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/.env.example +0 -0
  21. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/.github/workflows/master.yml +0 -0
  22. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/.github/workflows/publish.yml +0 -0
  23. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/.github/workflows/pull_request.yml +0 -0
  24. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/.github/workflows/pull_request_e2e.yml +0 -0
  25. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/.gitignore +0 -0
  26. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/.python-version +0 -0
  27. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/LICENSE +0 -0
  28. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/Makefile +0 -0
  29. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/README.md +0 -0
  30. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/agents.md +0 -0
  31. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/getting-started.md +0 -0
  32. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/agent.md +0 -0
  33. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/architecture.md +0 -0
  34. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/ext/mlflow.md +0 -0
  35. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/ext/otel.md +0 -0
  36. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/ext/tracing-guide.md +0 -0
  37. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/ext.md +0 -0
  38. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/lib/anthropic.md +0 -0
  39. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/lib.md +0 -0
  40. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/patterns.md +0 -0
  41. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/tools.md +0 -0
  42. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/llm.md +0 -0
  43. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/observability.md +0 -0
  44. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/patterns.md +0 -0
  45. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/flowing_context.md +0 -0
  46. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/hooks_redesign.md +0 -0
  47. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/llm_retry_backoff.md +0 -0
  48. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/mlflow_context_migration.md +0 -0
  49. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/model_fallback.md +0 -0
  50. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/otel_integration.md +0 -0
  51. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/pricing_complexity.md +0 -0
  52. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/provider_extensions.md +0 -0
  53. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/spawn_strategies.md +0 -0
  54. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/strands_comparison.md +0 -0
  55. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/tool_error_signals.md +0 -0
  56. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/tool_search_tool.md +0 -0
  57. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/voice_stt.md +0 -0
  58. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/review_prompts/step1_structure.md +0 -0
  59. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/review_prompts/step2_code_style.md +0 -0
  60. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/review_prompts/step3_documentation.md +0 -0
  61. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/review_prompts/step4_doc_readability.md +0 -0
  62. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/review_prompts/step5_doc_audit.md +0 -0
  63. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/review_prompts/step6_tests.md +0 -0
  64. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/todo.md +0 -0
  65. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/tools.md +0 -0
  66. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/TRACING_COMBINATIONS.md +0 -0
  67. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/__init__.py +0 -0
  68. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/agent_as_tool.py +0 -0
  69. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/app_agent.py +0 -0
  70. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/console_chat.py +0 -0
  71. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/docs/__init__.py +0 -0
  72. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/docs/agents_custom.py +0 -0
  73. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/docs/agents_parallel.py +0 -0
  74. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/docs/getting_started_chat.py +0 -0
  75. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/docs/getting_started_streaming.py +0 -0
  76. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/docs/getting_started_tools.py +0 -0
  77. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/docs/llm_streaming.py +0 -0
  78. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/docs/llm_structured_output.py +0 -0
  79. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/docs/tools_service_injection.py +0 -0
  80. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/escalation.py +0 -0
  81. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/llm_routing.py +0 -0
  82. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/menu_agent.py +0 -0
  83. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/menu_agent_class.py +0 -0
  84. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/mlflow_demo.py +0 -0
  85. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/mlflow_dual_export_demo.py +0 -0
  86. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/mlflow_nested_demo.py +0 -0
  87. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/mlflow_otel_both_demo.py +0 -0
  88. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/mlflow_otel_nested_demo.py +0 -0
  89. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/mlflow_parallel_demo.py +0 -0
  90. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/model_registry.py +0 -0
  91. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/otel_demo.py +0 -0
  92. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/otel_jaeger_demo.py +0 -0
  93. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/otel_nested_demo.py +0 -0
  94. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/otel_visualize.py +0 -0
  95. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/race.py +0 -0
  96. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/span_crash_demo.py +0 -0
  97. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/span_demo.py +0 -0
  98. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/system_prompt.txt +0 -0
  99. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/tools/__init__.py +0 -0
  100. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/tools/calculator.py +0 -0
  101. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/tools/random_numbers.py +0 -0
  102. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/tools/switch_model.py +0 -0
  103. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/tui_chat.py +0 -0
  104. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/__init__.py +0 -0
  105. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/_sentinel.py +0 -0
  106. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/__init__.py +0 -0
  107. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/__init__.py +0 -0
  108. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/agent.py +0 -0
  109. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/agent_arg.py +0 -0
  110. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/__init__.py +0 -0
  111. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/compiler.py +0 -0
  112. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/contract.py +0 -0
  113. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/init_params.py +0 -0
  114. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/instance.py +0 -0
  115. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/step_params.py +0 -0
  116. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/step_validation.py +0 -0
  117. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/steps.py +0 -0
  118. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/type_helpers.py +0 -0
  119. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/type_registry.py +0 -0
  120. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/model.py +0 -0
  121. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/registry.py +0 -0
  122. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/step.py +0 -0
  123. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/step_arg.py +0 -0
  124. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/step_helpers.py +0 -0
  125. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/flow/__init__.py +0 -0
  126. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/flow/actions.py +0 -0
  127. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/flow/context.py +0 -0
  128. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/flow/flowing_registry.py +0 -0
  129. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/flow/hooks.py +0 -0
  130. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/flow/interrupt.py +0 -0
  131. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/flow/interrupt_helpers.py +0 -0
  132. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/flow/spawn.py +0 -0
  133. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/flow/timeout.py +0 -0
  134. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/runtime/__init__.py +0 -0
  135. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/runtime/engine.py +0 -0
  136. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/runtime/execution.py +0 -0
  137. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/runtime/instance_factory.py +0 -0
  138. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/runtime/runtime.py +0 -0
  139. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/runtime/scope.py +0 -0
  140. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/runtime/serialization.py +0 -0
  141. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/runtime/spans.py +0 -0
  142. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/runtime/spawn_tree.py +0 -0
  143. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/services.py +0 -0
  144. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/state/__init__.py +0 -0
  145. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/state/markers.py +0 -0
  146. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/state/store.py +0 -0
  147. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/state/values.py +0 -0
  148. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/storage/__init__.py +0 -0
  149. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/storage/file.py +0 -0
  150. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/storage/in_memory.py +0 -0
  151. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/storage/session_storage.py +0 -0
  152. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/ext/__init__.py +0 -0
  153. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/ext/otel.py +0 -0
  154. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/__init__.py +0 -0
  155. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/anthropic/__init__.py +0 -0
  156. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/anthropic/cache.py +0 -0
  157. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/anthropic/presets.py +0 -0
  158. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/anthropic/tool_search.py +0 -0
  159. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/chat/__init__.py +0 -0
  160. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/chat/agent.py +0 -0
  161. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/chat/config.py +0 -0
  162. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/chat/hook_events.py +0 -0
  163. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/chat/spec.py +0 -0
  164. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/config_value.py +0 -0
  165. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/llm_call/__init__.py +0 -0
  166. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/llm_call/agent.py +0 -0
  167. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/llm_call/spec.py +0 -0
  168. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/llm_config.py +0 -0
  169. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/observability/__init__.py +0 -0
  170. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/observability/llm_hooks.py +0 -0
  171. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/__init__.py +0 -0
  172. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/agent.py +0 -0
  173. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/config.py +0 -0
  174. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/context.py +0 -0
  175. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/hook_events.py +0 -0
  176. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/spec.py +0 -0
  177. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/tool_call/__init__.py +0 -0
  178. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/tool_call/agent.py +0 -0
  179. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/tool_call/agent_tool.py +0 -0
  180. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/tool_call/context.py +0 -0
  181. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/base.py +0 -0
  182. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/messages.py +0 -0
  183. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/pricing/__init__.py +0 -0
  184. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/pricing/data/custom.json +0 -0
  185. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/pricing/data/generated.json +0 -0
  186. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/pricing/registry.py +0 -0
  187. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/provider.py +0 -0
  188. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/providers/__init__.py +0 -0
  189. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/request.py +0 -0
  190. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/response.py +0 -0
  191. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/schema_formatting.py +0 -0
  192. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/schema_validation.py +0 -0
  193. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/stream.py +0 -0
  194. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/tools.py +0 -0
  195. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/py.typed +0 -0
  196. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/tools/__init__.py +0 -0
  197. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/tools/local_tool.py +0 -0
  198. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/tools/mcp_connection.py +0 -0
  199. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/tools/tool_arg.py +0 -0
  200. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/tools/tool_group.py +0 -0
  201. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/tools/tool_registry.py +0 -0
  202. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/tools/types.py +0 -0
  203. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/pyproject.toml +0 -0
  204. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/__init__.py +0 -0
  205. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/__init__.py +0 -0
  206. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/definition/__init__.py +0 -0
  207. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/definition/compile/__init__.py +0 -0
  208. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/definition/compile/test_compile.py +0 -0
  209. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/definition/compile/test_type_helpers.py +0 -0
  210. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/definition/test_agent.py +0 -0
  211. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/definition/test_registry.py +0 -0
  212. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/definition/test_step_helpers.py +0 -0
  213. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/__init__.py +0 -0
  214. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_agent_def.py +0 -0
  215. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_context.py +0 -0
  216. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_flowing_registry.py +0 -0
  217. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_flowing_registry_tasks.py +0 -0
  218. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_flowing_sync.py +0 -0
  219. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_hooks.py +0 -0
  220. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_interrupt.py +0 -0
  221. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_spans.py +0 -0
  222. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_timeout.py +0 -0
  223. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_with_interrupt.py +0 -0
  224. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/runtime/__init__.py +0 -0
  225. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/runtime/test_engine.py +0 -0
  226. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/runtime/test_engine_spans.py +0 -0
  227. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/runtime/test_hook_context.py +0 -0
  228. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/runtime/test_persistence.py +0 -0
  229. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/runtime/test_runtime.py +0 -0
  230. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/runtime/test_scope.py +0 -0
  231. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/runtime/test_spec_in_constructor.py +0 -0
  232. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/state/__init__.py +0 -0
  233. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/state/test_values.py +0 -0
  234. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/storage/__init__.py +0 -0
  235. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/storage/test_file.py +0 -0
  236. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/storage/test_in_memory.py +0 -0
  237. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/test_missing_scenarios.py +0 -0
  238. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/ext/__init__.py +0 -0
  239. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/ext/test_mlflow.py +0 -0
  240. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/ext/test_otel.py +0 -0
  241. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/__init__.py +0 -0
  242. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/anthropic/__init__.py +0 -0
  243. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/anthropic/test_anthropic.py +0 -0
  244. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/test_chat_agent.py +0 -0
  245. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/test_config_value.py +0 -0
  246. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/test_llm_call_agent.py +0 -0
  247. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/test_matches_tool_filter.py +0 -0
  248. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/test_tool_call_agent.py +0 -0
  249. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/test_tool_call_agent_call_agent.py +0 -0
  250. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/test_tool_loop_agent.py +0 -0
  251. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/tool_loop/__init__.py +0 -0
  252. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/__init__.py +0 -0
  253. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/pricing/__init__.py +0 -0
  254. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/pricing/test_registry.py +0 -0
  255. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/providers/__init__.py +0 -0
  256. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/providers/test_anthropic_e2e.py +0 -0
  257. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/providers/test_google_vertex_e2e.py +0 -0
  258. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/providers/test_openai_e2e.py +0 -0
  259. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/test_cost_breakdown.py +0 -0
  260. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/test_metadata.py +0 -0
  261. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/test_response.py +0 -0
  262. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/test_schema_formatting.py +0 -0
  263. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/test_schema_validation.py +0 -0
  264. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/test_stream.py +0 -0
  265. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/tools/__init__.py +0 -0
  266. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/tools/test_local_tool.py +0 -0
  267. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/tools/test_mcp_connection.py +0 -0
  268. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/tools/test_tool_group.py +0 -0
  269. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/tools/test_tool_registry.py +0 -0
  270. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tools/sync_pricing.py +0 -0
  271. {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/uv.lock +0 -0
@@ -7,8 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+ - **Schema utilities exported** — `format_schema_for_llm`, `format_output_schema_for_description`,
12
+ `validate_json_schema`, `strip_markdown_code_block` now available from `flowra.llm` for custom providers.
13
+ - **`MediaBlock`** — replaces `ImageBlock`. Supports images, PDFs, and other documents via
14
+ `media_type` dispatch. Providers choose the right wire format automatically (e.g. `image` vs
15
+ `document` for Anthropic, `image_url` vs `file` for OpenAI). Optional `filename` field.
16
+
17
+ ## [0.0.26] - 2026-03-25
18
+
10
19
  ### Added
11
20
  - **`top_p`** parameter in `LLMRequest` and `LLMConfig` — nucleus sampling, supported by all providers.
21
+ - **`additional_config` in traces** — OTel span attributes (`gen_ai.request.<key>`) and MLflow
22
+ inputs now include provider-specific config (e.g. `thinking_budget_tokens`).
12
23
 
13
24
  ### Changed
14
25
  - **Universal pricing registry** — replaced three separate per-protocol pricing modules
@@ -45,7 +45,7 @@ Provider-agnostic interface for calling LLMs:
45
45
  - `StreamEvent` = `TextDelta | ThinkingDelta | ContentComplete` — stream events for real-time token delivery
46
46
  - `Usage` — input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens, cost_usd, cost (`CostBreakdown` with input/output/cache_read/cache_creation). Token contract: `input_tokens` excludes cached tokens
47
47
  - Messages: `SystemMessage`, `UserMessage`, `AssistantMessage` — system messages are separate in `LLMRequest.system`
48
- - Blocks: `TextBlock`, `ImageBlock`, `ToolUseBlock`, `ToolResultBlock`, `ThinkingBlock` — all inherit `LLMData` (metadata, extra, transient)
48
+ - Blocks: `TextBlock`, `MediaBlock`, `ToolUseBlock`, `ToolResultBlock`, `ThinkingBlock` — all inherit `LLMData` (metadata, extra, transient)
49
49
  - `Tool` — inherits from `LLMData` (has metadata, extra, transient). Fields: name, description, input_schema, output_schema
50
50
  - Span types (in `flowra.lib.observability`): `LLMCallSpan` (request + response), `ToolCallSpan` (tool_use + result + error_kind) — mutable, for observability via span-hooks
51
51
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowra
3
- Version: 0.0.26.dev37
3
+ Version: 0.0.27.dev40
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
@@ -18,7 +18,7 @@
18
18
  "LLMResponse contains: message (AssistantMessage), stop_reason (StopReason), stop_sequence (str | None), usage (Usage | None), extra (dict[str, Any] — provider-specific data like provider_stop_reason, id)",
19
19
  "Usage contains: input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens, cost_usd (total), cost (CostBreakdown with input/output/cache_read/cache_creation and total property). Token contract: input_tokens excludes cached tokens",
20
20
  "Messages: SystemMessage, UserMessage, AssistantMessage. System messages go in LLMRequest.system, conversation messages in LLMRequest.messages",
21
- "Blocks: TextBlock, ImageBlock, ToolUseBlock, ToolResultBlock, ThinkingBlock — all inherit LLMData (metadata, extra, transient)",
21
+ "Blocks: TextBlock, MediaBlock, ToolUseBlock, ToolResultBlock, ThinkingBlock — all inherit LLMData (metadata, extra, transient)",
22
22
  "LLMData base: metadata (dict, not sent to LLM), extra (dict, provider-specific passthrough), transient (bool, non-permanent content hint)",
23
23
  "ThinkingBlock holds reasoning/thinking text from models with extended thinking (Anthropic Claude, Google Gemini). Preserved in history by Anthropic (with signature in extra) and Google providers. OpenAI strips them",
24
24
  "Tool definition for LLM: Tool inherits LLMData, fields: name, description, input_schema, output_schema (plus metadata, extra, transient from LLMData)",
@@ -9,7 +9,7 @@ from flowra.llm import (
9
9
  # Messages
10
10
  SystemMessage, UserMessage, AssistantMessage, Message,
11
11
  # Content blocks
12
- TextBlock, ImageBlock, ToolUseBlock, ToolResultBlock, ThinkingBlock,
12
+ TextBlock, MediaBlock, ToolUseBlock, ToolResultBlock, ThinkingBlock,
13
13
  # Block type aliases
14
14
  UserBlock, AssistantBlock,
15
15
  # Tool schema
@@ -114,18 +114,21 @@ TextBlock(text="Injected context", transient=True) # mark as non-permanent
114
114
  |---------|--------|---------|---------------------|
115
115
  | `text` | `str` | — | Text content |
116
116
 
117
- ### `ImageBlock`
117
+ ### `MediaBlock`
118
118
 
119
- Binary image data. Valid in `UserMessage`.
119
+ Binary media content: images, PDFs, documents. Valid in `UserMessage`.
120
+ Providers dispatch on `media_type` to choose the right wire format.
120
121
 
121
122
  ```python
122
- ImageBlock(data=image_bytes, media_type="image/png")
123
+ MediaBlock(data=image_bytes, media_type="image/png")
124
+ MediaBlock(data=pdf_bytes, media_type="application/pdf", filename="invoice.pdf")
123
125
  ```
124
126
 
125
- | Field | Type | Default | Description |
126
- |--------------|---------|---------|-------------------------------------------|
127
- | `data` | `bytes` | — | Binary image data |
128
- | `media_type` | `str` | — | MIME type (`"image/png"`, `"image/jpeg"`) |
127
+ | Field | Type | Default | Description |
128
+ |--------------|-----------------|---------|------------------------------------------------------|
129
+ | `data` | `bytes` | — | Binary content |
130
+ | `media_type` | `str` | — | MIME type (`"image/png"`, `"application/pdf"`, etc.) |
131
+ | `filename` | `str \| None` | `None` | Optional filename (used by OpenAI for documents) |
129
132
 
130
133
  ### `ToolUseBlock`
131
134
 
@@ -179,7 +182,7 @@ ThinkingBlock(text="Let me reason about this...")
179
182
  ### Block type aliases
180
183
 
181
184
  ```python
182
- type UserBlock = TextBlock | ImageBlock | ToolResultBlock
185
+ type UserBlock = TextBlock | MediaBlock | ToolResultBlock
183
186
  type AssistantBlock = TextBlock | ToolUseBlock | ThinkingBlock
184
187
  ```
185
188
 
@@ -216,7 +219,7 @@ UserMessage(blocks=[TextBlock(text="What's the weather in Paris?")])
216
219
  # Text + image
217
220
  UserMessage(blocks=[
218
221
  TextBlock(text="What's in this picture?"),
219
- ImageBlock(data=png_bytes, media_type="image/png"),
222
+ MediaBlock(data=png_bytes, media_type="image/png"),
220
223
  ])
221
224
 
222
225
  # Tool result
@@ -787,7 +790,7 @@ if response.usage is not None:
787
790
  | `AssistantMessage` | dataclass | LLM response message |
788
791
  | `Message` | type alias | Union of all message types |
789
792
  | `TextBlock` | dataclass | Text content |
790
- | `ImageBlock` | dataclass | Binary image |
793
+ | `MediaBlock` | dataclass | Binary image |
791
794
  | `ToolUseBlock` | dataclass | Tool call from LLM |
792
795
  | `ToolResultBlock` | dataclass | Tool result for LLM |
793
796
  | `ThinkingBlock` | dataclass | Thinking/reasoning content |
@@ -17,7 +17,7 @@ from typing import Any
17
17
 
18
18
  from flowra.agent import HookSubscription
19
19
  from flowra.lib.observability import LLMCallSpan
20
- from flowra.llm import ImageBlock, Message, TextBlock, ThinkingBlock, ToolResultBlock, ToolUseBlock
20
+ from flowra.llm import MediaBlock, Message, TextBlock, ThinkingBlock, ToolResultBlock, ToolUseBlock
21
21
 
22
22
  _LOGS_DIR = ".logs"
23
23
 
@@ -82,6 +82,6 @@ def _format_messages(messages: list[Message]) -> str:
82
82
  parts.append(f" tool_result: (id={tid}){error_marker} {content}")
83
83
  case ThinkingBlock(text=text):
84
84
  parts.append(f" thinking: {text[:200]}...")
85
- case ImageBlock(media_type=media_type):
85
+ case MediaBlock(media_type=media_type):
86
86
  parts.append(f" image: {media_type}")
87
87
  return "\n".join(parts)
@@ -31,8 +31,8 @@ from ..agent import AgentRuntime, AgentSpan, FlowingVar, StepSpan
31
31
  from ..lib.observability import LLMCallSpan, ToolCallSpan
32
32
  from ..llm import (
33
33
  AssistantMessage,
34
- ImageBlock,
35
34
  LLMRequest,
35
+ MediaBlock,
36
36
  Message,
37
37
  SystemMessage,
38
38
  TextBlock,
@@ -452,7 +452,7 @@ def _user_message_to_openai(msg: UserMessage) -> list[dict[str, Any]]:
452
452
  "tool_call_id": tool_use_id,
453
453
  }
454
454
  )
455
- case ImageBlock():
455
+ case MediaBlock():
456
456
  text_parts.append("[image]")
457
457
  if text_parts:
458
458
  result.append({"role": "user", "content": "\n".join(text_parts)})
@@ -1,9 +1,11 @@
1
1
  from .base import LLMData
2
- from .blocks import AssistantBlock, ImageBlock, TextBlock, ThinkingBlock, ToolResultBlock, ToolUseBlock, UserBlock
2
+ from .blocks import AssistantBlock, MediaBlock, TextBlock, ThinkingBlock, ToolResultBlock, ToolUseBlock, UserBlock
3
3
  from .messages import AssistantMessage, Message, SystemMessage, UserMessage
4
4
  from .provider import LLMProvider
5
5
  from .request import LLMRequest
6
6
  from .response import CostBreakdown, LLMResponse, StopReason, Usage
7
+ from .schema_formatting import format_output_schema_for_description, format_schema_for_llm
8
+ from .schema_validation import strip_markdown_code_block, validate_json_schema
7
9
  from .stream import ContentComplete, StreamEvent, TextDelta, ThinkingDelta
8
10
  from .tools import Tool
9
11
 
@@ -12,11 +14,11 @@ __all__ = [
12
14
  "AssistantMessage",
13
15
  "ContentComplete",
14
16
  "CostBreakdown",
15
- "ImageBlock",
16
17
  "LLMData",
17
18
  "LLMProvider",
18
19
  "LLMRequest",
19
20
  "LLMResponse",
21
+ "MediaBlock",
20
22
  "Message",
21
23
  "StopReason",
22
24
  "StreamEvent",
@@ -31,4 +33,8 @@ __all__ = [
31
33
  "Usage",
32
34
  "UserBlock",
33
35
  "UserMessage",
36
+ "format_output_schema_for_description",
37
+ "format_schema_for_llm",
38
+ "strip_markdown_code_block",
39
+ "validate_json_schema",
34
40
  ]
@@ -5,7 +5,7 @@ from .base import LLMData
5
5
 
6
6
  __all__ = [
7
7
  "AssistantBlock",
8
- "ImageBlock",
8
+ "MediaBlock",
9
9
  "TextBlock",
10
10
  "ThinkingBlock",
11
11
  "ToolResultBlock",
@@ -21,10 +21,18 @@ class TextBlock(LLMData):
21
21
 
22
22
 
23
23
  @dataclasses.dataclass(frozen=True, slots=True, kw_only=True)
24
- class ImageBlock(LLMData):
25
- type: Literal["image"] = "image"
24
+ class MediaBlock(LLMData):
25
+ """Binary media content: images, PDFs, documents.
26
+
27
+ Providers dispatch on *media_type* to choose the right wire format
28
+ (e.g. ``image`` vs ``document`` for Anthropic, ``image_url`` vs ``file``
29
+ for OpenAI).
30
+ """
31
+
32
+ type: Literal["media"] = "media"
26
33
  data: bytes
27
34
  media_type: str
35
+ filename: str | None = None
28
36
 
29
37
 
30
38
  @dataclasses.dataclass(frozen=True, slots=True, kw_only=True)
@@ -49,5 +57,5 @@ class ThinkingBlock(LLMData):
49
57
  text: str
50
58
 
51
59
 
52
- type UserBlock = TextBlock | ImageBlock | ToolResultBlock
60
+ type UserBlock = TextBlock | MediaBlock | ToolResultBlock
53
61
  type AssistantBlock = TextBlock | ToolUseBlock | ThinkingBlock
@@ -10,7 +10,7 @@ from anthropic import AsyncAnthropicVertex, omit
10
10
  from anthropic.types import Message as AnthropicMessage, StopReason as AnthropicStopReason, Usage as AnthropicUsage
11
11
  from google.oauth2 import service_account
12
12
 
13
- from ..blocks import AssistantBlock, ImageBlock, TextBlock, ThinkingBlock, ToolResultBlock, ToolUseBlock
13
+ from ..blocks import AssistantBlock, MediaBlock, TextBlock, ThinkingBlock, ToolResultBlock, ToolUseBlock
14
14
  from ..messages import AssistantMessage, SystemMessage, UserMessage
15
15
  from ..pricing import estimate_cost as pricing_estimate_cost
16
16
  from ..provider import LLMProvider
@@ -241,10 +241,11 @@ class AnthropicVertexProvider(LLMProvider):
241
241
  match block:
242
242
  case TextBlock() as tb:
243
243
  content.append(cls.__convert_text_block(tb))
244
- case ImageBlock(data=data, media_type=media_type):
244
+ case MediaBlock(data=data, media_type=media_type):
245
+ block_type = "image" if media_type.startswith("image/") else "document"
245
246
  content.append(
246
247
  {
247
- "type": "image",
248
+ "type": block_type,
248
249
  "source": {
249
250
  "type": "base64",
250
251
  "media_type": media_type,
@@ -10,7 +10,7 @@ from google import genai
10
10
  from google.genai import types as genai_types
11
11
  from google.oauth2 import service_account
12
12
 
13
- from ..blocks import AssistantBlock, ImageBlock, TextBlock, ThinkingBlock, ToolResultBlock, ToolUseBlock
13
+ from ..blocks import AssistantBlock, MediaBlock, TextBlock, ThinkingBlock, ToolResultBlock, ToolUseBlock
14
14
  from ..messages import AssistantMessage, SystemMessage, UserMessage
15
15
  from ..pricing import estimate_cost as pricing_estimate_cost
16
16
  from ..provider import LLMProvider
@@ -211,7 +211,7 @@ class GoogleVertexProvider(LLMProvider):
211
211
  match block:
212
212
  case TextBlock(text=text):
213
213
  parts.append(genai_types.Part(text=text))
214
- case ImageBlock(data=data, media_type=media_type):
214
+ case MediaBlock(data=data, media_type=media_type):
215
215
  parts.append(
216
216
  genai_types.Part(
217
217
  inline_data=genai_types.Blob(mime_type=media_type, data=data),
@@ -8,7 +8,7 @@ from openai.types import CompletionUsage
8
8
  from openai.types.chat import ChatCompletion
9
9
  from openai.types.chat.chat_completion_message_function_tool_call import ChatCompletionMessageFunctionToolCall
10
10
 
11
- from ..blocks import AssistantBlock, ImageBlock, TextBlock, ThinkingBlock, ToolResultBlock, ToolUseBlock
11
+ from ..blocks import AssistantBlock, MediaBlock, TextBlock, ThinkingBlock, ToolResultBlock, ToolUseBlock
12
12
  from ..messages import AssistantMessage, Message, SystemMessage, UserMessage
13
13
  from ..pricing import estimate_cost as pricing_estimate_cost
14
14
  from ..provider import LLMProvider
@@ -130,14 +130,16 @@ class OpenAIProvider(LLMProvider):
130
130
  match block:
131
131
  case TextBlock(text=text):
132
132
  content_parts.append({"type": "text", "text": text})
133
- case ImageBlock(data=data, media_type=media_type):
133
+ case MediaBlock(data=data, media_type=media_type, filename=filename):
134
134
  b64 = base64.b64encode(data).decode()
135
- content_parts.append(
136
- {
137
- "type": "image_url",
138
- "image_url": {"url": f"data:{media_type};base64,{b64}"},
139
- }
140
- )
135
+ data_uri = f"data:{media_type};base64,{b64}"
136
+ if media_type.startswith("image/"):
137
+ content_parts.append({"type": "image_url", "image_url": {"url": data_uri}})
138
+ else:
139
+ file_data: dict[str, str] = {"file_data": data_uri}
140
+ if filename:
141
+ file_data["filename"] = filename
142
+ content_parts.append({"type": "file", "file": file_data})
141
143
  case ToolResultBlock(tool_use_id=tool_use_id, content=content, is_error=is_error):
142
144
  if content_parts:
143
145
  result.append({"role": "user", "content": cls.__simplify_content(content_parts)})
@@ -0,0 +1,2 @@
1
+ # Updated by CI/CD pipeline
2
+ __version__ = "0.0.27.dev40"
@@ -9,7 +9,7 @@ from flowra.agent import Interrupted
9
9
  from flowra.agent.runtime.serialization import deserialize_value, serialize_value
10
10
  from flowra.llm import (
11
11
  AssistantMessage,
12
- ImageBlock,
12
+ MediaBlock,
13
13
  SystemMessage,
14
14
  TextBlock,
15
15
  ToolResultBlock,
@@ -49,7 +49,7 @@ TYPE_REGISTRY: dict[str, type] = {
49
49
  "AssistantMessage": AssistantMessage,
50
50
  "Usage": Usage,
51
51
  "TextBlock": TextBlock,
52
- "ImageBlock": ImageBlock,
52
+ "MediaBlock": MediaBlock,
53
53
  "ToolUseBlock": ToolUseBlock,
54
54
  "ToolResultBlock": ToolResultBlock,
55
55
  "MySpec": MySpec,
@@ -392,10 +392,10 @@ class TestRoundTripBytes:
392
392
  assert restored == data
393
393
 
394
394
  def test_image_block_round_trip(self) -> None:
395
- block = ImageBlock(data=b"\x89PNG\r\n", media_type="image/png")
395
+ block = MediaBlock(data=b"\x89PNG\r\n", media_type="image/png")
396
396
  serialized = serialize_value(block, TYPE_TAGS)
397
397
  restored = deserialize_value(serialized, TYPE_REGISTRY)
398
- assert isinstance(restored, ImageBlock)
398
+ assert isinstance(restored, MediaBlock)
399
399
  assert restored.data == b"\x89PNG\r\n"
400
400
  assert restored.media_type == "image/png"
401
401
 
@@ -403,13 +403,13 @@ class TestRoundTripBytes:
403
403
  msg = UserMessage(
404
404
  blocks=[
405
405
  TextBlock(text="look at this"),
406
- ImageBlock(data=b"\xff\xd8\xff", media_type="image/jpeg"),
406
+ MediaBlock(data=b"\xff\xd8\xff", media_type="image/jpeg"),
407
407
  ]
408
408
  )
409
409
  serialized = serialize_value(msg, TYPE_TAGS)
410
410
  restored = deserialize_value(serialized, TYPE_REGISTRY)
411
411
  assert isinstance(restored, UserMessage)
412
- assert isinstance(restored.blocks[1], ImageBlock)
412
+ assert isinstance(restored.blocks[1], MediaBlock)
413
413
  assert restored.blocks[1].data == b"\xff\xd8\xff"
414
414
 
415
415
 
@@ -4,7 +4,7 @@ from unittest.mock import AsyncMock, MagicMock
4
4
 
5
5
  import pytest
6
6
 
7
- from flowra.llm import ContentComplete, ImageBlock, LLMRequest, StopReason, TextBlock, ThinkingBlock, Tool, UserMessage
7
+ from flowra.llm import ContentComplete, LLMRequest, MediaBlock, StopReason, TextBlock, ThinkingBlock, Tool, UserMessage
8
8
  from flowra.llm.providers.anthropic_vertex import AnthropicVertexProvider
9
9
 
10
10
 
@@ -282,18 +282,18 @@ class TestThinkingSupport:
282
282
  assert call_kwargs["temperature"] == 1 # forced by thinking
283
283
 
284
284
 
285
- class TestImageBlockConversion:
285
+ class TestMediaBlockConversion:
286
286
  async def test_image_block_converted_to_base64_source(
287
287
  self, provider: AnthropicVertexProvider, mock_client: AsyncMock
288
288
  ) -> None:
289
- """ImageBlock in UserMessage is converted to Anthropic base64 image format."""
289
+ """MediaBlock in UserMessage is converted to Anthropic base64 image format."""
290
290
  image_data = b"\x89PNG\r\n\x1a\n"
291
291
  request = LLMRequest(
292
292
  model="test",
293
293
  messages=[
294
294
  UserMessage(
295
295
  blocks=[
296
- ImageBlock(data=image_data, media_type="image/png"),
296
+ MediaBlock(data=image_data, media_type="image/png"),
297
297
  ]
298
298
  ),
299
299
  ],
@@ -313,14 +313,14 @@ class TestImageBlockConversion:
313
313
  assert "cache_control" not in img
314
314
 
315
315
  async def test_image_block_with_extra(self, provider: AnthropicVertexProvider, mock_client: AsyncMock) -> None:
316
- """ImageBlock with cache_control in extra includes cache_control."""
316
+ """MediaBlock with cache_control in extra includes cache_control."""
317
317
  image_data = b"\x89PNG\r\n\x1a\n"
318
318
  request = LLMRequest(
319
319
  model="test",
320
320
  messages=[
321
321
  UserMessage(
322
322
  blocks=[
323
- ImageBlock(
323
+ MediaBlock(
324
324
  data=image_data,
325
325
  media_type="image/png",
326
326
  extra={"cache_control": {"type": "ephemeral"}},
@@ -7,8 +7,8 @@ from google.genai import types as genai_types
7
7
 
8
8
  from flowra.llm import (
9
9
  AssistantMessage,
10
- ImageBlock,
11
10
  LLMRequest,
11
+ MediaBlock,
12
12
  StopReason,
13
13
  TextBlock,
14
14
  ThinkingBlock,
@@ -434,11 +434,11 @@ class TestAdditionalConfig:
434
434
  assert thinking_config.include_thoughts is True
435
435
 
436
436
 
437
- class TestImageBlockConversion:
437
+ class TestMediaBlockConversion:
438
438
  async def test_image_block_converted_to_inline_data(
439
439
  self, provider: GoogleVertexProvider, mock_client: MagicMock
440
440
  ) -> None:
441
- """ImageBlock in UserMessage is converted to Gemini inline_data Part."""
441
+ """MediaBlock in UserMessage is converted to Gemini inline_data Part."""
442
442
  image_data = b"\x89PNG\r\n\x1a\n"
443
443
 
444
444
  text_part = MagicMock()
@@ -456,7 +456,7 @@ class TestImageBlockConversion:
456
456
  messages=[
457
457
  UserMessage(
458
458
  blocks=[
459
- ImageBlock(data=image_data, media_type="image/png"),
459
+ MediaBlock(data=image_data, media_type="image/png"),
460
460
  ]
461
461
  ),
462
462
  ],
@@ -6,8 +6,8 @@ import pytest
6
6
 
7
7
  from flowra.llm import (
8
8
  AssistantMessage,
9
- ImageBlock,
10
9
  LLMRequest,
10
+ MediaBlock,
11
11
  SystemMessage,
12
12
  TextBlock,
13
13
  ToolResultBlock,
@@ -189,16 +189,16 @@ class TestSchemaStrictTransformation:
189
189
  assert "$ref" in item_prop
190
190
 
191
191
 
192
- class TestImageBlockConversion:
192
+ class TestMediaBlockConversion:
193
193
  async def test_image_block_converted_to_image_url(self, provider: OpenAIProvider, mock_client: AsyncMock) -> None:
194
- """ImageBlock in UserMessage is converted to OpenAI image_url format with data URI."""
194
+ """MediaBlock in UserMessage is converted to OpenAI image_url format with data URI."""
195
195
  image_data = b"\x89PNG\r\n\x1a\n"
196
196
  request = LLMRequest(
197
197
  model="gpt-4",
198
198
  messages=[
199
199
  UserMessage(
200
200
  blocks=[
201
- ImageBlock(data=image_data, media_type="image/png"),
201
+ MediaBlock(data=image_data, media_type="image/png"),
202
202
  ]
203
203
  ),
204
204
  ],
@@ -1,2 +0,0 @@
1
- # Updated by CI/CD pipeline
2
- __version__ = "0.0.26.dev37"
File without changes
File without changes
File without changes
File without changes
File without changes