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.
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/CHANGELOG.md +11 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/CLAUDE.md +1 -1
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/PKG-INFO +1 -1
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/context7.json +1 -1
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/llm.md +14 -11
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/llm_logging.py +2 -2
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/ext/mlflow.py +2 -2
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/__init__.py +8 -2
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/blocks.py +12 -4
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/providers/anthropic_vertex.py +4 -3
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/providers/google_vertex.py +2 -2
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/providers/openai.py +10 -8
- flowra-0.0.27.dev40/flowra/version.py +2 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/runtime/test_serialization.py +6 -6
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/providers/test_anthropic_vertex.py +6 -6
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/providers/test_google_vertex.py +4 -4
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/providers/test_openai_provider.py +4 -4
- flowra-0.0.26.dev37/flowra/version.py +0 -2
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/.claude/commands/update-pricing.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/.env.example +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/.github/workflows/master.yml +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/.github/workflows/publish.yml +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/.github/workflows/pull_request.yml +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/.github/workflows/pull_request_e2e.yml +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/.gitignore +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/.python-version +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/LICENSE +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/Makefile +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/README.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/agents.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/getting-started.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/agent.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/architecture.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/ext/mlflow.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/ext/otel.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/ext/tracing-guide.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/ext.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/lib/anthropic.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/lib.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/patterns.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/internal/tools.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/llm.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/observability.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/patterns.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/flowing_context.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/hooks_redesign.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/llm_retry_backoff.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/mlflow_context_migration.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/model_fallback.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/otel_integration.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/pricing_complexity.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/provider_extensions.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/spawn_strategies.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/strands_comparison.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/tool_error_signals.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/tool_search_tool.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/research/voice_stt.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/review_prompts/step1_structure.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/review_prompts/step2_code_style.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/review_prompts/step3_documentation.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/review_prompts/step4_doc_readability.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/review_prompts/step5_doc_audit.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/review_prompts/step6_tests.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/todo.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/docs/tools.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/TRACING_COMBINATIONS.md +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/agent_as_tool.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/app_agent.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/console_chat.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/docs/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/docs/agents_custom.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/docs/agents_parallel.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/docs/getting_started_chat.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/docs/getting_started_streaming.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/docs/getting_started_tools.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/docs/llm_streaming.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/docs/llm_structured_output.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/docs/tools_service_injection.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/escalation.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/llm_routing.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/menu_agent.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/menu_agent_class.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/mlflow_demo.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/mlflow_dual_export_demo.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/mlflow_nested_demo.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/mlflow_otel_both_demo.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/mlflow_otel_nested_demo.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/mlflow_parallel_demo.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/model_registry.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/otel_demo.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/otel_jaeger_demo.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/otel_nested_demo.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/otel_visualize.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/race.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/span_crash_demo.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/span_demo.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/system_prompt.txt +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/tools/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/tools/calculator.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/tools/random_numbers.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/tools/switch_model.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/examples/tui_chat.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/_sentinel.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/agent.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/agent_arg.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/compiler.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/contract.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/init_params.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/instance.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/step_params.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/step_validation.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/steps.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/type_helpers.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/compile/type_registry.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/model.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/registry.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/step.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/step_arg.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/definition/step_helpers.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/flow/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/flow/actions.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/flow/context.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/flow/flowing_registry.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/flow/hooks.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/flow/interrupt.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/flow/interrupt_helpers.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/flow/spawn.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/flow/timeout.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/runtime/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/runtime/engine.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/runtime/execution.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/runtime/instance_factory.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/runtime/runtime.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/runtime/scope.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/runtime/serialization.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/runtime/spans.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/runtime/spawn_tree.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/services.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/state/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/state/markers.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/state/store.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/state/values.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/storage/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/storage/file.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/storage/in_memory.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/agent/storage/session_storage.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/ext/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/ext/otel.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/anthropic/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/anthropic/cache.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/anthropic/presets.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/anthropic/tool_search.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/chat/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/chat/agent.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/chat/config.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/chat/hook_events.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/chat/spec.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/config_value.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/llm_call/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/llm_call/agent.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/llm_call/spec.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/llm_config.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/observability/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/observability/llm_hooks.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/agent.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/config.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/context.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/hook_events.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/spec.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/tool_call/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/tool_call/agent.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/tool_call/agent_tool.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/lib/tool_loop/tool_call/context.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/base.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/messages.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/pricing/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/pricing/data/custom.json +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/pricing/data/generated.json +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/pricing/registry.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/provider.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/providers/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/request.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/response.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/schema_formatting.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/schema_validation.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/stream.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/llm/tools.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/py.typed +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/tools/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/tools/local_tool.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/tools/mcp_connection.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/tools/tool_arg.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/tools/tool_group.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/tools/tool_registry.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/flowra/tools/types.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/pyproject.toml +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/definition/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/definition/compile/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/definition/compile/test_compile.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/definition/compile/test_type_helpers.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/definition/test_agent.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/definition/test_registry.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/definition/test_step_helpers.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_agent_def.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_context.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_flowing_registry.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_flowing_registry_tasks.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_flowing_sync.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_hooks.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_interrupt.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_spans.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_timeout.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/flow/test_with_interrupt.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/runtime/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/runtime/test_engine.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/runtime/test_engine_spans.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/runtime/test_hook_context.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/runtime/test_persistence.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/runtime/test_runtime.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/runtime/test_scope.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/runtime/test_spec_in_constructor.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/state/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/state/test_values.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/storage/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/storage/test_file.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/storage/test_in_memory.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/agent/test_missing_scenarios.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/ext/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/ext/test_mlflow.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/ext/test_otel.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/anthropic/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/anthropic/test_anthropic.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/test_chat_agent.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/test_config_value.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/test_llm_call_agent.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/test_matches_tool_filter.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/test_tool_call_agent.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/test_tool_call_agent_call_agent.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/test_tool_loop_agent.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/lib/tool_loop/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/pricing/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/pricing/test_registry.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/providers/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/providers/test_anthropic_e2e.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/providers/test_google_vertex_e2e.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/providers/test_openai_e2e.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/test_cost_breakdown.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/test_metadata.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/test_response.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/test_schema_formatting.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/test_schema_validation.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/llm/test_stream.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/tools/__init__.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/tools/test_local_tool.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/tools/test_mcp_connection.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/tools/test_tool_group.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tests/tools/test_tool_registry.py +0 -0
- {flowra-0.0.26.dev37 → flowra-0.0.27.dev40}/tools/sync_pricing.py +0 -0
- {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`, `
|
|
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.
|
|
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,
|
|
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,
|
|
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
|
-
### `
|
|
117
|
+
### `MediaBlock`
|
|
118
118
|
|
|
119
|
-
Binary
|
|
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
|
-
|
|
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
|
|
126
|
-
|
|
127
|
-
| `data` | `bytes`
|
|
128
|
-
| `media_type` | `str`
|
|
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 |
|
|
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
|
-
|
|
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
|
-
| `
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
"
|
|
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
|
|
25
|
-
|
|
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 |
|
|
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,
|
|
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
|
|
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":
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
133
|
+
case MediaBlock(data=data, media_type=media_type, filename=filename):
|
|
134
134
|
b64 = base64.b64encode(data).decode()
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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)})
|
|
@@ -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
|
-
|
|
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
|
-
"
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
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],
|
|
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,
|
|
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
|
|
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
|
-
"""
|
|
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
|
-
|
|
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
|
-
"""
|
|
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
|
-
|
|
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
|
|
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
|
-
"""
|
|
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
|
-
|
|
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
|
|
192
|
+
class TestMediaBlockConversion:
|
|
193
193
|
async def test_image_block_converted_to_image_url(self, provider: OpenAIProvider, mock_client: AsyncMock) -> None:
|
|
194
|
-
"""
|
|
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
|
-
|
|
201
|
+
MediaBlock(data=image_data, media_type="image/png"),
|
|
202
202
|
]
|
|
203
203
|
),
|
|
204
204
|
],
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|