by-framework 0.2.2.dev4__tar.gz → 0.2.2.dev5__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.
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/PKG-INFO +1 -1
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/src/by_framework_langgraph/adapter.py +35 -57
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/tests/test_adapter.py +30 -61
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/__init__.py +2 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/langfuse.py +101 -29
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-trace-query/tests/test_trace_query_sdk.py +91 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/pyproject.toml +1 -1
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/client/client.py +102 -2
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/common/config.py +2 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/common/constants.py +5 -1
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/common/logger.py +13 -1
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/__init__.py +40 -14
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/extensions/__init__.py +9 -3
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/extensions/plugin.py +5 -3
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/extensions/registry.py +12 -5
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/protocol/__init__.py +53 -19
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/protocol/byai_codec.py +7 -2
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/registry.py +222 -70
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/runtime/__init__.py +4 -2
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/runtime/file_manager.py +15 -12
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/runtime/filestore/local.py +12 -4
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/wakeup_controller.py +5 -3
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/metrics/__init__.py +2 -0
- by_framework-0.2.2.dev5/src/by_framework/metrics/collector.py +161 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/trace/__init__.py +2 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/trace/span_recorder.py +30 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/worker/context.py +1 -65
- by_framework-0.2.2.dev5/src/by_framework/worker/heartbeat.py +255 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/worker/runner.py +123 -6
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/worker/worker.py +7 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/common/test_config.py +3 -1
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/common/test_logger.py +15 -1
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/core/protocol/test_byai_codec.py +11 -6
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/core/protocol/test_protocol.py +9 -6
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/core/runtime/history/test_history_persistence.py +1 -2
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/core/runtime/test_file_access_context.py +1 -2
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/core/runtime/test_file_paths.py +1 -2
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/core/runtime/test_file_permissions.py +4 -2
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/core/test_availability.py +9 -12
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/core/test_registry.py +134 -0
- by_framework-0.2.2.dev5/tests/metrics/test_collector.py +173 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/metrics/test_snapshot.py +74 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/plugin/test_langfuse_plugin.py +48 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/trace/test_span_recorder.py +44 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/worker/test_heartbeat.py +42 -7
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/worker/test_runner.py +37 -3
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/uv.lock +1 -1
- by_framework-0.2.2.dev4/src/by_framework/worker/heartbeat.py +0 -69
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/.github/RELEASING.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/.github/workflows/ci.yml +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/.github/workflows/publish.yml +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/.github/workflows/stale.yml +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/.gitignore +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/.pre-commit-config.yaml +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/AGENTS.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/CHANGELOG.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/CLAUDE.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/CODE_OF_CONDUCT.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/CONTRIBUTING.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/LICENSE +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/Makefile +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/README.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/README_zh.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/SECURITY.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/assets/img/architecture_en.png +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/assets/img/architecture_zh.png +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/autoformat.sh +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/docs/README.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/docs/by-framework-trace-change-usage.html +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/docs/by-framework-trace-complete-guide.html +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/docs/plans/2026-05-15-worker-task-state-stats.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/docs/plans/2026-06-10-trace-metrics-correlation.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-adk/README.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-adk/pyproject.toml +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-adk/src/by_framework_adk/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-adk/src/by_framework_adk/_utils.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-adk/src/by_framework_adk/adapter.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-adk/src/by_framework_adk/worker.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-adk/tests/test_worker.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/README.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/frontend/index.html +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/frontend/package-lock.json +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/frontend/package.json +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/frontend/src/main.jsx +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/frontend/src/styles.css +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/frontend/vite.config.js +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/pyproject.toml +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/src/by_framework_dashboard/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/src/by_framework_dashboard/adapters.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/src/by_framework_dashboard/dashboard.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/src/by_framework_dashboard/static/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/src/by_framework_dashboard/static/app.js +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/src/by_framework_dashboard/static/index.html +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/src/by_framework_dashboard/static/styles.css +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/tests/test_dashboard_server.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-history-byclaw/README.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-history-byclaw/pyproject.toml +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/byclaw_history.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-history-byclaw/tests/test_byclaw_history.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-history-postgres/README.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-history-postgres/pyproject.toml +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-history-postgres/src/by_framework_history_postgres/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-history-postgres/src/by_framework_history_postgres/postgres.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-history-postgres/tests/test_postgres_history_storage.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/README.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/pyproject.toml +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/src/by_framework_langgraph/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/src/by_framework_langgraph/_utils.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/src/by_framework_langgraph/tools.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/src/by_framework_langgraph/worker.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/tests/test_tools.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/tests/test_utils.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-trace-langfuse/README.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-trace-langfuse/pyproject.toml +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-trace-langfuse/tests/test_langfuse_import.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-trace-phoenix/README.md +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-trace-phoenix/pyproject.toml +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/phoenix.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-trace-phoenix/tests/test_phoenix_import.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-trace-phoenix/tests/test_phoenix_plugin.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-trace-query/pyproject.toml +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-trace-query/src/by_framework_trace_query/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-trace-query/src/by_framework_trace_query/client.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-trace-query/src/by_framework_trace_query/merger.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-trace-query/src/by_framework_trace_query/models.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/libs/by-framework-trace-query/src/by_framework_trace_query/redis_source.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/pylintrc +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/scripts/python_quality.sh +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/__main__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/client/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/client/byai_client.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/common/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/common/emitter.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/common/exceptions.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/common/redis_client.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/availability.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/delivery_gate.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/discovery.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/extensions/agent_config.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/extensions/trace_provider.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/protocol/action_type.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/protocol/agent_state.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/protocol/byai_command.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/protocol/byai_types.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/protocol/commands.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/protocol/content_codec.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/protocol/content_type.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/protocol/data_message.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/protocol/data_shapes.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/protocol/event_type.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/protocol/events.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/protocol/message.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/protocol/message_header.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/protocol/responses.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/protocol/results.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/runtime/agent_config_manager.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/runtime/agent_runtime_state.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/runtime/file_paths.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/runtime/file_permissions.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/runtime/filestore/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/runtime/filestore/base.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/runtime/history/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/runtime/history/base.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/runtime/history/history_manager.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/runtime/history/in_memory.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/runtime/session_manager.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/core/workspace.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/errors/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/errors/base.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/errors/common.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/errors/execution.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/errors/http.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/errors/protocol.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/errors/registry.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/metrics/read_client.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/metrics/snapshot.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/trace/external_trace.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/trace/trace_schema.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/trace/trace_writer.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/util/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/util/discovery_http_client.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/util/generate_message_id.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/util/http_client.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/worker/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/worker/_control_handling.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/worker/_execution_tracking.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/worker/_message_processing.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/worker/app.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/worker/byai_context.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/worker/byai_worker.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/worker/processor.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/worker/sandbox/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/src/by_framework/worker/sandbox/hook_sandbox.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/client/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/client/test_client.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/common/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/common/test_exceptions.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/common/test_redis_client.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/conftest.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/core/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/core/protocol/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/core/protocol/test_command_wire.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/core/protocol/test_results.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/core/runtime/history/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/core/runtime/test_file_manager_default_storage.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/core/runtime/test_filestore_local_collection.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/core/runtime/test_filestore_local_mutation.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/core/runtime/test_filestore_local_read.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/core/runtime/test_filestore_local_search.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/core/test_discovery.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/integration/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/integration/test_ask_user_flow.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/integration/test_callback_flow.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/integration/test_logger_integration.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/integration/test_scatter_gather.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/metrics/test_metrics.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/metrics/test_read_client.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/plugin/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/plugin/test_plugin_discovery.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/plugin/test_plugin_improvements.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/plugin/test_plugin_registry.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/plugin/test_plugin_system.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/trace/test_external_trace.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/trace/test_logger_correlation.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/trace/test_otel_exporter.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/util/test_discovery_http_client.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/util/test_discovery_http_client_download.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/util/test_discovery_http_client_upload.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/util/test_http_client.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/worker/__init__.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/worker/test_app.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/worker/test_byai_worker.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/worker/test_context.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/worker/test_control_handling.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/worker/test_emitter.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/worker/test_gateway_worker.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/worker/test_message_processing.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/worker/test_processor.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/worker/test_sandbox.py +0 -0
- {by_framework-0.2.2.dev4 → by_framework-0.2.2.dev5}/tests/worker/test_workspace.py +0 -0
|
@@ -590,69 +590,47 @@ class LangGraphAdapter:
|
|
|
590
590
|
# Always inject token accumulator — works regardless of Langfuse config.
|
|
591
591
|
callbacks.append(_TokenAccumulatingCallbackHandler(self._context))
|
|
592
592
|
|
|
593
|
-
# Prefer AgentContext's callback factory so trace and parent ids align.
|
|
594
|
-
# Filter out auto-generated MagicMock attributes when tests use a mock
|
|
595
|
-
# context — real callback objects always come from a non-test module.
|
|
596
|
-
langfuse_callback_value = getattr(self._context, "langfuse_callback", None)
|
|
597
|
-
is_real_callback = langfuse_callback_value is not None and type(
|
|
598
|
-
langfuse_callback_value
|
|
599
|
-
).__module__ not in ("unittest.mock",)
|
|
600
|
-
|
|
601
|
-
if is_real_callback:
|
|
602
|
-
handler = (
|
|
603
|
-
langfuse_callback_value()
|
|
604
|
-
if callable(langfuse_callback_value)
|
|
605
|
-
else langfuse_callback_value
|
|
606
|
-
)
|
|
607
|
-
if handler is not None:
|
|
608
|
-
callbacks.append(handler)
|
|
609
|
-
yield
|
|
610
|
-
return
|
|
611
|
-
|
|
612
|
-
# Fallback to local import if context method is missing
|
|
613
|
-
# pylint: disable=import-outside-toplevel
|
|
614
593
|
try:
|
|
615
|
-
|
|
616
|
-
"by_framework_trace_langfuse"
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
raise ImportError("Langfuse not configured")
|
|
620
|
-
|
|
621
|
-
callback_handler = import_module("langfuse.langchain").CallbackHandler
|
|
622
|
-
get_client = import_module("langfuse").get_client
|
|
594
|
+
build_langchain_callback = getattr(
|
|
595
|
+
import_module("by_framework_trace_langfuse"),
|
|
596
|
+
"build_langchain_callback",
|
|
597
|
+
)
|
|
623
598
|
except (ImportError, AttributeError):
|
|
624
599
|
yield
|
|
625
600
|
return
|
|
626
601
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
602
|
+
get_parent_observation_id = getattr(
|
|
603
|
+
self._context,
|
|
604
|
+
"get_trace_parent_observation_id",
|
|
605
|
+
None,
|
|
606
|
+
)
|
|
607
|
+
parent_observation_id = (
|
|
608
|
+
str(get_parent_observation_id() or "")
|
|
609
|
+
if callable(get_parent_observation_id)
|
|
610
|
+
else ""
|
|
611
|
+
)
|
|
612
|
+
if not parent_observation_id:
|
|
613
|
+
framework_observation = getattr(
|
|
614
|
+
self._context, LANGFUSE_OBSERVATION_ATTR, None
|
|
615
|
+
)
|
|
616
|
+
parent_observation_id = getattr(framework_observation, "id", "") or ""
|
|
617
|
+
if not parent_observation_id:
|
|
618
|
+
execution_id = getattr(self._context, "execution_id", "")
|
|
619
|
+
message_id = getattr(self._context, "message_id", "")
|
|
620
|
+
raw_parent_id = (
|
|
621
|
+
f"{execution_id}:worker.execute"
|
|
622
|
+
if execution_id
|
|
623
|
+
else f"{message_id}:worker.execute"
|
|
624
|
+
)
|
|
625
|
+
parent_observation_id = f"{str_to_uint64(raw_parent_id):016x}"
|
|
633
626
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
},
|
|
642
|
-
metadata=self._default_metadata(),
|
|
643
|
-
):
|
|
644
|
-
# Prevent the generated OTel span from being promoted to a trace root.
|
|
645
|
-
# The native LangfusePlugin sets the same attribute on its own path
|
|
646
|
-
# (via _SdkLangfuseTracer); this covers the LangGraph fallback path.
|
|
647
|
-
try:
|
|
648
|
-
from opentelemetry import trace
|
|
649
|
-
|
|
650
|
-
current_span = trace.get_current_span()
|
|
651
|
-
if current_span and hasattr(current_span, "set_attribute"):
|
|
652
|
-
current_span.set_attribute("langfuse.internal.as_root", False)
|
|
653
|
-
except Exception: # pylint: disable=broad-exception-caught
|
|
654
|
-
pass
|
|
655
|
-
yield
|
|
627
|
+
handler = build_langchain_callback(
|
|
628
|
+
trace_id=getattr(self._context, "trace_id", ""),
|
|
629
|
+
parent_observation_id=parent_observation_id,
|
|
630
|
+
)
|
|
631
|
+
if handler is not None:
|
|
632
|
+
callbacks.append(handler)
|
|
633
|
+
yield
|
|
656
634
|
|
|
657
635
|
@staticmethod
|
|
658
636
|
def _default_input_mapper(content: str) -> dict:
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Tests for adapter and worker modules."""
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
|
-
from contextlib import contextmanager
|
|
5
4
|
from types import SimpleNamespace
|
|
6
5
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
7
6
|
|
|
@@ -40,7 +39,7 @@ def _make_mock_context(session_id: str = "test-session"):
|
|
|
40
39
|
metadata={"source": "test"},
|
|
41
40
|
)
|
|
42
41
|
)
|
|
43
|
-
ctx.
|
|
42
|
+
ctx.get_trace_parent_observation_id.return_value = "obs-framework"
|
|
44
43
|
return ctx
|
|
45
44
|
|
|
46
45
|
|
|
@@ -187,7 +186,7 @@ class TestAdapterRun:
|
|
|
187
186
|
async def test_includes_langfuse_callbacks_and_parent_trace_context(
|
|
188
187
|
self, monkeypatch
|
|
189
188
|
):
|
|
190
|
-
"""Verify adapter
|
|
189
|
+
"""Verify adapter gets Langfuse callback from the trace provider package."""
|
|
191
190
|
ctx = _make_mock_context()
|
|
192
191
|
graph = MagicMock()
|
|
193
192
|
graph.ainvoke = AsyncMock(
|
|
@@ -197,45 +196,18 @@ class TestAdapterRun:
|
|
|
197
196
|
snapshot.next = ()
|
|
198
197
|
graph.get_state.return_value = snapshot
|
|
199
198
|
|
|
200
|
-
|
|
201
|
-
|
|
199
|
+
callback_handler = object()
|
|
200
|
+
callback_calls: list[dict[str, str]] = []
|
|
202
201
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
yield SimpleNamespace(id="obs-langgraph")
|
|
202
|
+
def fake_build_langchain_callback(**kwargs):
|
|
203
|
+
callback_calls.append(kwargs)
|
|
204
|
+
return callback_handler
|
|
207
205
|
|
|
208
|
-
class FakeCallbackHandler: # pylint: disable=too-few-public-methods
|
|
209
|
-
"""Minimal callback handler stub for adapter config assertions."""
|
|
210
|
-
|
|
211
|
-
def __init__(self):
|
|
212
|
-
callback_instances.append(self)
|
|
213
|
-
|
|
214
|
-
fake_langfuse_client = SimpleNamespace(
|
|
215
|
-
start_as_current_observation=fake_observation_scope
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
monkeypatch.setitem(
|
|
219
|
-
sys.modules,
|
|
220
|
-
"langfuse",
|
|
221
|
-
SimpleNamespace(get_client=MagicMock(return_value=fake_langfuse_client)),
|
|
222
|
-
)
|
|
223
|
-
monkeypatch.setitem(
|
|
224
|
-
sys.modules,
|
|
225
|
-
"langfuse.langchain",
|
|
226
|
-
SimpleNamespace(CallbackHandler=FakeCallbackHandler),
|
|
227
|
-
)
|
|
228
206
|
monkeypatch.setitem(
|
|
229
207
|
sys.modules,
|
|
230
208
|
"by_framework_trace_langfuse",
|
|
231
|
-
SimpleNamespace(
|
|
209
|
+
SimpleNamespace(build_langchain_callback=fake_build_langchain_callback),
|
|
232
210
|
)
|
|
233
|
-
sys.modules[
|
|
234
|
-
"by_framework_trace_langfuse"
|
|
235
|
-
].LangfuseConfig.from_env.return_value = object()
|
|
236
|
-
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "sk-test")
|
|
237
|
-
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "pk-test")
|
|
238
|
-
monkeypatch.setenv("LANGFUSE_BASE_URL", "http://localhost:3000")
|
|
239
211
|
|
|
240
212
|
adapter = LangGraphAdapter(graph, ctx, stream=False)
|
|
241
213
|
|
|
@@ -255,25 +227,26 @@ class TestAdapterRun:
|
|
|
255
227
|
assert config["metadata"]["by_framework_message_id"] == "msg-ctx"
|
|
256
228
|
assert config["metadata"]["langgraph_thread_id"] == "test-session"
|
|
257
229
|
# Callbacks list includes the Langfuse handler(s) plus the token accumulator.
|
|
258
|
-
assert
|
|
230
|
+
assert callback_handler in config["callbacks"]
|
|
259
231
|
assert any(
|
|
260
232
|
type(cb).__name__ == "_TokenAccumulatingCallbackHandler"
|
|
261
233
|
for cb in config["callbacks"]
|
|
262
234
|
)
|
|
263
|
-
assert
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
235
|
+
assert callback_calls == [
|
|
236
|
+
{
|
|
237
|
+
"trace_id": "trace-ctx",
|
|
238
|
+
"parent_observation_id": "obs-framework",
|
|
239
|
+
}
|
|
240
|
+
]
|
|
269
241
|
|
|
270
242
|
@pytest.mark.asyncio
|
|
271
|
-
async def
|
|
272
|
-
|
|
273
|
-
|
|
243
|
+
async def test_skips_langfuse_callback_when_provider_package_missing(
|
|
244
|
+
self, monkeypatch
|
|
245
|
+
):
|
|
246
|
+
"""LangGraph stays provider-agnostic when trace-langfuse is not installed."""
|
|
274
247
|
|
|
275
248
|
# pylint: disable=too-few-public-methods,missing-class-docstring,missing-function-docstring
|
|
276
|
-
class
|
|
249
|
+
class ContextWithoutProvider:
|
|
277
250
|
session_id = "test-session"
|
|
278
251
|
trace_id = "trace-ctx"
|
|
279
252
|
message_id = "msg-ctx"
|
|
@@ -291,11 +264,15 @@ class TestAdapterRun:
|
|
|
291
264
|
def __init__(self):
|
|
292
265
|
self.emit_chunk = AsyncMock()
|
|
293
266
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
267
|
+
def fake_import_module(name):
|
|
268
|
+
if name == "by_framework_trace_langfuse":
|
|
269
|
+
raise ImportError(name)
|
|
270
|
+
raise AssertionError(f"unexpected import: {name}")
|
|
297
271
|
|
|
298
|
-
|
|
272
|
+
monkeypatch.setattr(
|
|
273
|
+
"by_framework_langgraph.adapter.import_module", fake_import_module
|
|
274
|
+
)
|
|
275
|
+
ctx = ContextWithoutProvider()
|
|
299
276
|
graph = MagicMock()
|
|
300
277
|
graph.ainvoke = AsyncMock(
|
|
301
278
|
return_value={"messages": [MagicMock(content="hello")]}
|
|
@@ -310,7 +287,6 @@ class TestAdapterRun:
|
|
|
310
287
|
assert result == "hello"
|
|
311
288
|
_, kwargs = graph.ainvoke.call_args
|
|
312
289
|
callbacks = kwargs["config"]["callbacks"]
|
|
313
|
-
assert handler in callbacks
|
|
314
290
|
assert any(
|
|
315
291
|
type(cb).__name__ == "_TokenAccumulatingCallbackHandler" for cb in callbacks
|
|
316
292
|
)
|
|
@@ -335,11 +311,8 @@ class TestAdapterRun:
|
|
|
335
311
|
monkeypatch.setitem(
|
|
336
312
|
sys.modules,
|
|
337
313
|
"by_framework_trace_langfuse",
|
|
338
|
-
SimpleNamespace(
|
|
314
|
+
SimpleNamespace(build_langchain_callback=MagicMock(return_value=None)),
|
|
339
315
|
)
|
|
340
|
-
sys.modules[
|
|
341
|
-
"by_framework_trace_langfuse"
|
|
342
|
-
].LangfuseConfig.from_env.return_value = None
|
|
343
316
|
|
|
344
317
|
adapter = LangGraphAdapter(graph, ctx, stream=False)
|
|
345
318
|
|
|
@@ -382,11 +355,8 @@ class TestAdapterRun:
|
|
|
382
355
|
monkeypatch.setitem(
|
|
383
356
|
sys.modules,
|
|
384
357
|
"by_framework_trace_langfuse",
|
|
385
|
-
SimpleNamespace(
|
|
358
|
+
SimpleNamespace(build_langchain_callback=MagicMock(return_value=None)),
|
|
386
359
|
)
|
|
387
|
-
sys.modules[
|
|
388
|
-
"by_framework_trace_langfuse"
|
|
389
|
-
].LangfuseConfig.from_env.return_value = None
|
|
390
360
|
|
|
391
361
|
adapter = LangGraphAdapter(graph, ctx, stream=False)
|
|
392
362
|
|
|
@@ -530,7 +500,6 @@ class TestTokenAccumulatingCallbackHandler:
|
|
|
530
500
|
def test_callback_injected_in_tracing_scope(self):
|
|
531
501
|
"""_langfuse_callback_manager injects _TokenAccumulatingCallbackHandler."""
|
|
532
502
|
ctx = MagicMock()
|
|
533
|
-
ctx.langfuse_callback = None
|
|
534
503
|
graph = MagicMock()
|
|
535
504
|
adapter = LangGraphAdapter(graph=graph, context=ctx)
|
|
536
505
|
|
|
@@ -5,6 +5,7 @@ from .langfuse import (
|
|
|
5
5
|
LangfuseConfig,
|
|
6
6
|
LangfusePlugin,
|
|
7
7
|
LangfuseTraceProviderFactory,
|
|
8
|
+
build_langchain_callback,
|
|
8
9
|
start_client_dispatch_observation,
|
|
9
10
|
)
|
|
10
11
|
|
|
@@ -13,5 +14,6 @@ __all__ = [
|
|
|
13
14
|
"LangfuseConfig",
|
|
14
15
|
"LangfusePlugin",
|
|
15
16
|
"LangfuseTraceProviderFactory",
|
|
17
|
+
"build_langchain_callback",
|
|
16
18
|
"start_client_dispatch_observation",
|
|
17
19
|
]
|
|
@@ -13,6 +13,7 @@ from datetime import datetime, timezone
|
|
|
13
13
|
from importlib import import_module
|
|
14
14
|
from typing import Any, Optional, Protocol, runtime_checkable
|
|
15
15
|
|
|
16
|
+
from by_framework.common.logger import get_logger
|
|
16
17
|
from by_framework.core.extensions import (
|
|
17
18
|
AgentConfig,
|
|
18
19
|
Plugin,
|
|
@@ -28,6 +29,8 @@ from by_framework.trace.span_recorder import (
|
|
|
28
29
|
str_to_uint128,
|
|
29
30
|
)
|
|
30
31
|
|
|
32
|
+
logger = get_logger(__name__)
|
|
33
|
+
|
|
31
34
|
LANGFUSE_OBSERVATION_ATTR = "_langfuse_observation"
|
|
32
35
|
LANGFUSE_CALL_PARENT_OBSERVATION_ATTR = "_langfuse_call_parent_observation"
|
|
33
36
|
LANGFUSE_WORKFLOW_OBSERVATION_ATTR = "_langfuse_workflow_observation"
|
|
@@ -71,6 +74,82 @@ class LangfuseConfig:
|
|
|
71
74
|
return value.strip().strip(_QUOTES_TO_STRIP)
|
|
72
75
|
|
|
73
76
|
|
|
77
|
+
def build_langchain_callback(
|
|
78
|
+
*,
|
|
79
|
+
trace_id: str,
|
|
80
|
+
parent_observation_id: str,
|
|
81
|
+
) -> Optional[Any]:
|
|
82
|
+
"""Build a Langfuse LangChain CallbackHandler for an existing trace."""
|
|
83
|
+
config = LangfuseConfig.from_env()
|
|
84
|
+
if config is None:
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
callback_handler_cls = getattr(
|
|
89
|
+
import_module("langfuse.langchain"),
|
|
90
|
+
"CallbackHandler",
|
|
91
|
+
)
|
|
92
|
+
callback_handler_cls = _without_langchain_root_promotion(callback_handler_cls)
|
|
93
|
+
trace_id_hex = f"{str_to_uint128(trace_id):032x}"
|
|
94
|
+
trace_context = {
|
|
95
|
+
"trace_id": trace_id_hex,
|
|
96
|
+
"parent_span_id": parent_observation_id,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return callback_handler_cls(
|
|
100
|
+
public_key=config.public_key,
|
|
101
|
+
trace_context=trace_context,
|
|
102
|
+
)
|
|
103
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _without_langchain_root_promotion(callback_handler_cls: Any) -> Any:
|
|
108
|
+
"""Return a CallbackHandler class that keeps LangChain runs as child spans."""
|
|
109
|
+
|
|
110
|
+
# pylint: disable=too-few-public-methods
|
|
111
|
+
class ByFrameworkCallbackHandler(callback_handler_cls):
|
|
112
|
+
"""Langfuse LangChain handler that does not rename the existing trace root."""
|
|
113
|
+
|
|
114
|
+
def on_chain_start(
|
|
115
|
+
self,
|
|
116
|
+
serialized: Any,
|
|
117
|
+
inputs: dict[str, Any],
|
|
118
|
+
*,
|
|
119
|
+
run_id: Any,
|
|
120
|
+
parent_run_id: Any = None,
|
|
121
|
+
**kwargs: Any,
|
|
122
|
+
) -> Any:
|
|
123
|
+
result = super().on_chain_start(
|
|
124
|
+
serialized,
|
|
125
|
+
inputs,
|
|
126
|
+
run_id=run_id,
|
|
127
|
+
parent_run_id=parent_run_id,
|
|
128
|
+
**kwargs,
|
|
129
|
+
)
|
|
130
|
+
if parent_run_id is None:
|
|
131
|
+
runs = getattr(self, "_runs", {})
|
|
132
|
+
observation = runs.get(run_id) if isinstance(runs, dict) else None
|
|
133
|
+
_mark_observation_as_non_root(observation)
|
|
134
|
+
return result
|
|
135
|
+
|
|
136
|
+
ByFrameworkCallbackHandler.__name__ = callback_handler_cls.__name__
|
|
137
|
+
ByFrameworkCallbackHandler.__qualname__ = callback_handler_cls.__qualname__
|
|
138
|
+
ByFrameworkCallbackHandler.__module__ = callback_handler_cls.__module__
|
|
139
|
+
return ByFrameworkCallbackHandler
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _mark_observation_as_non_root(observation: Any) -> None:
|
|
143
|
+
"""Prevent Langfuse from using a child LangChain run as the trace root."""
|
|
144
|
+
otel_span = getattr(observation, "_otel_span", None)
|
|
145
|
+
if otel_span is None or not hasattr(otel_span, "set_attribute"):
|
|
146
|
+
return
|
|
147
|
+
try:
|
|
148
|
+
otel_span.set_attribute("langfuse.internal.as_root", False)
|
|
149
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
|
|
74
153
|
@runtime_checkable
|
|
75
154
|
class ObservationHandle(Protocol):
|
|
76
155
|
"""Minimal observation interface needed by the plugin."""
|
|
@@ -371,6 +450,8 @@ class LangfusePlugin(Plugin):
|
|
|
371
450
|
|
|
372
451
|
async def on_task_start(self, context: Any) -> None: # pylint: disable=too-many-locals
|
|
373
452
|
tracer = self._get_tracer()
|
|
453
|
+
if tracer is None:
|
|
454
|
+
return
|
|
374
455
|
observation_store = self._get_observation_store(context)
|
|
375
456
|
identity = self._build_task_identity(context)
|
|
376
457
|
|
|
@@ -532,6 +613,8 @@ class LangfusePlugin(Plugin):
|
|
|
532
613
|
async def on_call_agent_start(self, context: Any, command: Any) -> None:
|
|
533
614
|
"""Create a call observation and pass it as the child task parent."""
|
|
534
615
|
tracer = self._get_tracer()
|
|
616
|
+
if tracer is None:
|
|
617
|
+
return
|
|
535
618
|
header = getattr(command, "header", None)
|
|
536
619
|
if header is None:
|
|
537
620
|
return
|
|
@@ -618,6 +701,8 @@ class LangfusePlugin(Plugin):
|
|
|
618
701
|
"""Create a return observation and pass it as the resume parent."""
|
|
619
702
|
del context, command
|
|
620
703
|
tracer = self._get_tracer()
|
|
704
|
+
if tracer is None:
|
|
705
|
+
return
|
|
621
706
|
header = getattr(callback_command, "header", None)
|
|
622
707
|
if header is None:
|
|
623
708
|
return
|
|
@@ -730,7 +815,7 @@ class LangfusePlugin(Plugin):
|
|
|
730
815
|
return parent_id
|
|
731
816
|
return ""
|
|
732
817
|
|
|
733
|
-
def _get_tracer(self) -> LangfuseTracer:
|
|
818
|
+
def _get_tracer(self) -> Optional[LangfuseTracer]:
|
|
734
819
|
tracer = self._tracer or self._build_default_tracer()
|
|
735
820
|
self._tracer = tracer
|
|
736
821
|
return tracer
|
|
@@ -744,13 +829,14 @@ class LangfusePlugin(Plugin):
|
|
|
744
829
|
)
|
|
745
830
|
return self._observation_store
|
|
746
831
|
|
|
747
|
-
def _build_default_tracer(self) -> LangfuseTracer:
|
|
832
|
+
def _build_default_tracer(self) -> Optional[LangfuseTracer]:
|
|
748
833
|
config = LangfuseConfig.from_env()
|
|
749
834
|
if config is None:
|
|
750
|
-
|
|
751
|
-
"LangfusePlugin
|
|
752
|
-
"
|
|
835
|
+
logger.error(
|
|
836
|
+
"LangfusePlugin: LANGFUSE_SECRET_KEY, LANGFUSE_PUBLIC_KEY, and "
|
|
837
|
+
"LANGFUSE_BASE_URL are not set — Langfuse tracing disabled."
|
|
753
838
|
)
|
|
839
|
+
return None
|
|
754
840
|
|
|
755
841
|
try:
|
|
756
842
|
langfuse_module = import_module("langfuse")
|
|
@@ -761,18 +847,11 @@ class LangfusePlugin(Plugin):
|
|
|
761
847
|
) from err
|
|
762
848
|
langfuse_client_cls = getattr(langfuse_module, "Langfuse")
|
|
763
849
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
)
|
|
770
|
-
except TypeError:
|
|
771
|
-
client = langfuse_client_cls(
|
|
772
|
-
public_key=config.public_key,
|
|
773
|
-
secret_key=config.secret_key,
|
|
774
|
-
host=config.base_url,
|
|
775
|
-
)
|
|
850
|
+
client = langfuse_client_cls(
|
|
851
|
+
public_key=config.public_key,
|
|
852
|
+
secret_key=config.secret_key,
|
|
853
|
+
base_url=config.base_url,
|
|
854
|
+
)
|
|
776
855
|
|
|
777
856
|
return _SdkLangfuseTracer(client, config)
|
|
778
857
|
|
|
@@ -1037,18 +1116,11 @@ def start_client_dispatch_observation(
|
|
|
1037
1116
|
tracer = _CLIENT_DISPATCH_TRACER_CACHE.get(config)
|
|
1038
1117
|
if tracer is None:
|
|
1039
1118
|
langfuse_client_cls = getattr(langfuse_module, "Langfuse")
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
)
|
|
1046
|
-
except TypeError:
|
|
1047
|
-
client = langfuse_client_cls(
|
|
1048
|
-
public_key=config.public_key,
|
|
1049
|
-
secret_key=config.secret_key,
|
|
1050
|
-
host=config.base_url,
|
|
1051
|
-
)
|
|
1119
|
+
client = langfuse_client_cls(
|
|
1120
|
+
public_key=config.public_key,
|
|
1121
|
+
secret_key=config.secret_key,
|
|
1122
|
+
base_url=config.base_url,
|
|
1123
|
+
)
|
|
1052
1124
|
tracer = _SdkLangfuseTracer(client, config)
|
|
1053
1125
|
_CLIENT_DISPATCH_TRACER_CACHE[config] = tracer
|
|
1054
1126
|
|
|
@@ -891,3 +891,94 @@ async def test_dashboard_trace_endpoints_use_trace_read_sdk_shape():
|
|
|
891
891
|
assert traces_response.status == 200
|
|
892
892
|
assert traces_payload["traces"][0]["trace_id"] == "trace-dashboard"
|
|
893
893
|
assert traces_payload["traces"][0]["span_count"] == trace_payload["span_count"]
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
@pytest.mark.asyncio
|
|
897
|
+
async def test_trace_write_client_records_trace_root_start():
|
|
898
|
+
"""TraceWriteClient persists start_ts and QUEUED status for trace root."""
|
|
899
|
+
redis = QueryRedis()
|
|
900
|
+
writer = TraceWriteClient(redis)
|
|
901
|
+
await writer.record_trace(
|
|
902
|
+
TraceRecord(
|
|
903
|
+
trace_id="trace-root-start",
|
|
904
|
+
name="planner",
|
|
905
|
+
session_id="session-x",
|
|
906
|
+
root_message_id="msg-1",
|
|
907
|
+
root_agent_type="planner",
|
|
908
|
+
input={"text": "hello"},
|
|
909
|
+
status="QUEUED",
|
|
910
|
+
start_ts=1000,
|
|
911
|
+
)
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
meta = {
|
|
915
|
+
k.decode() if isinstance(k, bytes) else k: decode_redis_value(v)
|
|
916
|
+
for k, v in (
|
|
917
|
+
await redis.hgetall(RedisKeys.trace_meta("trace-root-start"))
|
|
918
|
+
).items()
|
|
919
|
+
}
|
|
920
|
+
assert meta["trace_id"] == "trace-root-start"
|
|
921
|
+
assert meta["status"] == "QUEUED"
|
|
922
|
+
assert int(meta["start_ts"]) == 1000
|
|
923
|
+
assert meta["root_agent_type"] == "planner"
|
|
924
|
+
assert meta["root_message_id"] == "msg-1"
|
|
925
|
+
assert meta.get("input") == {"text": "hello"}
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
@pytest.mark.asyncio
|
|
929
|
+
async def test_trace_write_client_records_trace_root_end():
|
|
930
|
+
"""TraceWriteClient persists end_ts, status and output when root finishes."""
|
|
931
|
+
redis = QueryRedis()
|
|
932
|
+
writer = TraceWriteClient(redis)
|
|
933
|
+
# Simulate the start record written by the client
|
|
934
|
+
await writer.record_trace(
|
|
935
|
+
TraceRecord(
|
|
936
|
+
trace_id="trace-root-end",
|
|
937
|
+
name="planner",
|
|
938
|
+
root_agent_type="planner",
|
|
939
|
+
status="QUEUED",
|
|
940
|
+
start_ts=1000,
|
|
941
|
+
)
|
|
942
|
+
)
|
|
943
|
+
# Simulate the end record written by the worker
|
|
944
|
+
await writer.record_trace(
|
|
945
|
+
TraceRecord(
|
|
946
|
+
trace_id="trace-root-end",
|
|
947
|
+
status="COMPLETED",
|
|
948
|
+
end_ts=5000,
|
|
949
|
+
output={"answer": "42"},
|
|
950
|
+
)
|
|
951
|
+
)
|
|
952
|
+
|
|
953
|
+
meta = {
|
|
954
|
+
k.decode() if isinstance(k, bytes) else k: decode_redis_value(v)
|
|
955
|
+
for k, v in (
|
|
956
|
+
await redis.hgetall(RedisKeys.trace_meta("trace-root-end"))
|
|
957
|
+
).items()
|
|
958
|
+
}
|
|
959
|
+
assert meta["status"] == "COMPLETED"
|
|
960
|
+
assert int(meta["end_ts"]) == 5000
|
|
961
|
+
assert meta.get("output") == {"answer": "42"}
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
@pytest.mark.asyncio
|
|
965
|
+
async def test_trace_read_client_returns_start_ts_from_trace_record():
|
|
966
|
+
"""TraceReadClient propagates start_ts written by the client into the result."""
|
|
967
|
+
redis = QueryRedis()
|
|
968
|
+
writer = TraceWriteClient(redis)
|
|
969
|
+
await writer.record_trace(
|
|
970
|
+
TraceRecord(
|
|
971
|
+
trace_id="trace-ts-check",
|
|
972
|
+
name="scheduler",
|
|
973
|
+
session_id="sess-ts",
|
|
974
|
+
root_agent_type="scheduler",
|
|
975
|
+
status="QUEUED",
|
|
976
|
+
start_ts=2000,
|
|
977
|
+
)
|
|
978
|
+
)
|
|
979
|
+
from by_framework_trace_query.client import TraceReadClient as TRC
|
|
980
|
+
|
|
981
|
+
client = TRC(redis_client=redis)
|
|
982
|
+
result = await client.get_trace("trace-ts-check", session_id="sess-ts")
|
|
983
|
+
assert result.trace.start_ts == 2000
|
|
984
|
+
assert result.trace.root_agent_type == "scheduler"
|