by-framework 0.2.2.dev3__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.
Files changed (246) hide show
  1. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/.github/workflows/publish.yml +12 -0
  2. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/.gitignore +0 -2
  3. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/PKG-INFO +1 -1
  4. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/src/by_framework_langgraph/adapter.py +35 -57
  5. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/tests/test_adapter.py +30 -61
  6. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/__init__.py +2 -0
  7. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/langfuse.py +101 -29
  8. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-trace-query/tests/test_trace_query_sdk.py +91 -0
  9. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/pyproject.toml +1 -1
  10. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/client/client.py +117 -9
  11. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/common/config.py +2 -0
  12. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/common/constants.py +5 -1
  13. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/common/logger.py +13 -1
  14. by_framework-0.2.2.dev5/src/by_framework/core/__init__.py +95 -0
  15. by_framework-0.2.2.dev5/src/by_framework/core/availability.py +495 -0
  16. by_framework-0.2.2.dev5/src/by_framework/core/delivery_gate.py +60 -0
  17. by_framework-0.2.2.dev5/src/by_framework/core/discovery.py +359 -0
  18. by_framework-0.2.2.dev5/src/by_framework/core/extensions/__init__.py +35 -0
  19. by_framework-0.2.2.dev5/src/by_framework/core/extensions/agent_config.py +64 -0
  20. by_framework-0.2.2.dev5/src/by_framework/core/extensions/plugin.py +314 -0
  21. by_framework-0.2.2.dev5/src/by_framework/core/extensions/registry.py +711 -0
  22. by_framework-0.2.2.dev5/src/by_framework/core/extensions/trace_provider.py +20 -0
  23. by_framework-0.2.2.dev5/src/by_framework/core/protocol/__init__.py +133 -0
  24. by_framework-0.2.2.dev5/src/by_framework/core/protocol/action_type.py +33 -0
  25. by_framework-0.2.2.dev5/src/by_framework/core/protocol/agent_state.py +78 -0
  26. by_framework-0.2.2.dev5/src/by_framework/core/protocol/byai_codec.py +101 -0
  27. by_framework-0.2.2.dev5/src/by_framework/core/protocol/byai_command.py +53 -0
  28. by_framework-0.2.2.dev5/src/by_framework/core/protocol/byai_types.py +7 -0
  29. by_framework-0.2.2.dev5/src/by_framework/core/protocol/commands.py +285 -0
  30. by_framework-0.2.2.dev5/src/by_framework/core/protocol/content_codec.py +17 -0
  31. by_framework-0.2.2.dev5/src/by_framework/core/protocol/content_type.py +38 -0
  32. by_framework-0.2.2.dev5/src/by_framework/core/protocol/data_message.py +45 -0
  33. by_framework-0.2.2.dev5/src/by_framework/core/protocol/data_shapes.py +83 -0
  34. by_framework-0.2.2.dev5/src/by_framework/core/protocol/event_type.py +34 -0
  35. by_framework-0.2.2.dev5/src/by_framework/core/protocol/events.py +69 -0
  36. by_framework-0.2.2.dev5/src/by_framework/core/protocol/message.py +99 -0
  37. by_framework-0.2.2.dev5/src/by_framework/core/protocol/message_header.py +78 -0
  38. by_framework-0.2.2.dev5/src/by_framework/core/protocol/responses.py +94 -0
  39. by_framework-0.2.2.dev5/src/by_framework/core/protocol/results.py +149 -0
  40. by_framework-0.2.2.dev5/src/by_framework/core/registry.py +1254 -0
  41. by_framework-0.2.2.dev5/src/by_framework/core/runtime/__init__.py +29 -0
  42. by_framework-0.2.2.dev5/src/by_framework/core/runtime/agent_config_manager.py +283 -0
  43. by_framework-0.2.2.dev5/src/by_framework/core/runtime/agent_runtime_state.py +75 -0
  44. by_framework-0.2.2.dev5/src/by_framework/core/runtime/file_manager.py +437 -0
  45. by_framework-0.2.2.dev5/src/by_framework/core/runtime/file_paths.py +76 -0
  46. by_framework-0.2.2.dev5/src/by_framework/core/runtime/file_permissions.py +71 -0
  47. by_framework-0.2.2.dev5/src/by_framework/core/runtime/filestore/__init__.py +15 -0
  48. by_framework-0.2.2.dev5/src/by_framework/core/runtime/filestore/base.py +140 -0
  49. by_framework-0.2.2.dev5/src/by_framework/core/runtime/filestore/local.py +321 -0
  50. by_framework-0.2.2.dev5/src/by_framework/core/runtime/history/__init__.py +10 -0
  51. by_framework-0.2.2.dev5/src/by_framework/core/runtime/history/base.py +57 -0
  52. by_framework-0.2.2.dev5/src/by_framework/core/runtime/history/history_manager.py +55 -0
  53. by_framework-0.2.2.dev5/src/by_framework/core/runtime/history/in_memory.py +58 -0
  54. by_framework-0.2.2.dev5/src/by_framework/core/runtime/session_manager.py +118 -0
  55. by_framework-0.2.2.dev5/src/by_framework/core/wakeup_controller.py +151 -0
  56. by_framework-0.2.2.dev5/src/by_framework/core/workspace.py +126 -0
  57. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/metrics/__init__.py +2 -0
  58. by_framework-0.2.2.dev5/src/by_framework/metrics/collector.py +161 -0
  59. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/trace/__init__.py +2 -0
  60. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/trace/span_recorder.py +30 -0
  61. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/worker/context.py +1 -65
  62. by_framework-0.2.2.dev5/src/by_framework/worker/heartbeat.py +255 -0
  63. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/worker/runner.py +123 -6
  64. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/worker/worker.py +7 -0
  65. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/common/test_config.py +3 -1
  66. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/common/test_logger.py +15 -1
  67. by_framework-0.2.2.dev5/tests/core/protocol/test_byai_codec.py +105 -0
  68. by_framework-0.2.2.dev5/tests/core/protocol/test_command_wire.py +21 -0
  69. by_framework-0.2.2.dev5/tests/core/protocol/test_protocol.py +248 -0
  70. by_framework-0.2.2.dev5/tests/core/protocol/test_results.py +68 -0
  71. by_framework-0.2.2.dev5/tests/core/runtime/history/test_history_persistence.py +181 -0
  72. by_framework-0.2.2.dev5/tests/core/runtime/test_file_access_context.py +48 -0
  73. by_framework-0.2.2.dev5/tests/core/runtime/test_file_manager_default_storage.py +222 -0
  74. by_framework-0.2.2.dev5/tests/core/runtime/test_file_paths.py +28 -0
  75. by_framework-0.2.2.dev5/tests/core/runtime/test_file_permissions.py +85 -0
  76. by_framework-0.2.2.dev5/tests/core/runtime/test_filestore_local_collection.py +41 -0
  77. by_framework-0.2.2.dev5/tests/core/runtime/test_filestore_local_mutation.py +32 -0
  78. by_framework-0.2.2.dev5/tests/core/runtime/test_filestore_local_read.py +50 -0
  79. by_framework-0.2.2.dev5/tests/core/runtime/test_filestore_local_search.py +61 -0
  80. by_framework-0.2.2.dev5/tests/core/test_availability.py +606 -0
  81. by_framework-0.2.2.dev5/tests/core/test_discovery.py +204 -0
  82. by_framework-0.2.2.dev5/tests/core/test_registry.py +1067 -0
  83. by_framework-0.2.2.dev5/tests/integration/__init__.py +0 -0
  84. by_framework-0.2.2.dev5/tests/metrics/test_collector.py +173 -0
  85. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/metrics/test_snapshot.py +74 -0
  86. by_framework-0.2.2.dev5/tests/plugin/__init__.py +0 -0
  87. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/plugin/test_langfuse_plugin.py +48 -0
  88. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/trace/test_span_recorder.py +44 -0
  89. by_framework-0.2.2.dev5/tests/worker/__init__.py +0 -0
  90. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/worker/test_heartbeat.py +42 -7
  91. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/worker/test_runner.py +37 -3
  92. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/uv.lock +1 -1
  93. by_framework-0.2.2.dev3/src/by_framework/worker/heartbeat.py +0 -69
  94. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  95. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  96. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  97. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/.github/RELEASING.md +0 -0
  98. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/.github/workflows/ci.yml +0 -0
  99. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/.github/workflows/stale.yml +0 -0
  100. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/.pre-commit-config.yaml +0 -0
  101. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/AGENTS.md +0 -0
  102. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/CHANGELOG.md +0 -0
  103. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/CLAUDE.md +0 -0
  104. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/CODE_OF_CONDUCT.md +0 -0
  105. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/CONTRIBUTING.md +0 -0
  106. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/LICENSE +0 -0
  107. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/Makefile +0 -0
  108. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/README.md +0 -0
  109. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/README_zh.md +0 -0
  110. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/SECURITY.md +0 -0
  111. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/assets/img/architecture_en.png +0 -0
  112. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/assets/img/architecture_zh.png +0 -0
  113. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/autoformat.sh +0 -0
  114. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/docs/README.md +0 -0
  115. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/docs/by-framework-trace-change-usage.html +0 -0
  116. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/docs/by-framework-trace-complete-guide.html +0 -0
  117. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/docs/plans/2026-05-15-worker-task-state-stats.md +0 -0
  118. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/docs/plans/2026-06-10-trace-metrics-correlation.md +0 -0
  119. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-adk/README.md +0 -0
  120. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-adk/pyproject.toml +0 -0
  121. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-adk/src/by_framework_adk/__init__.py +0 -0
  122. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-adk/src/by_framework_adk/_utils.py +0 -0
  123. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-adk/src/by_framework_adk/adapter.py +0 -0
  124. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-adk/src/by_framework_adk/worker.py +0 -0
  125. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-adk/tests/test_worker.py +0 -0
  126. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/README.md +0 -0
  127. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/frontend/index.html +0 -0
  128. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/frontend/package-lock.json +0 -0
  129. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/frontend/package.json +0 -0
  130. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/frontend/src/main.jsx +0 -0
  131. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/frontend/src/styles.css +0 -0
  132. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/frontend/vite.config.js +0 -0
  133. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/pyproject.toml +0 -0
  134. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/src/by_framework_dashboard/__init__.py +0 -0
  135. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/src/by_framework_dashboard/adapters.py +0 -0
  136. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/src/by_framework_dashboard/dashboard.py +0 -0
  137. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/src/by_framework_dashboard/static/__init__.py +0 -0
  138. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/src/by_framework_dashboard/static/app.js +0 -0
  139. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/src/by_framework_dashboard/static/index.html +0 -0
  140. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/src/by_framework_dashboard/static/styles.css +0 -0
  141. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-dashboard/tests/test_dashboard_server.py +0 -0
  142. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-history-byclaw/README.md +0 -0
  143. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-history-byclaw/pyproject.toml +0 -0
  144. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/__init__.py +0 -0
  145. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/byclaw_history.py +0 -0
  146. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-history-byclaw/tests/test_byclaw_history.py +0 -0
  147. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-history-postgres/README.md +0 -0
  148. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-history-postgres/pyproject.toml +0 -0
  149. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-history-postgres/src/by_framework_history_postgres/__init__.py +0 -0
  150. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-history-postgres/src/by_framework_history_postgres/postgres.py +0 -0
  151. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-history-postgres/tests/test_postgres_history_storage.py +0 -0
  152. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/README.md +0 -0
  153. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/pyproject.toml +0 -0
  154. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/src/by_framework_langgraph/__init__.py +0 -0
  155. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/src/by_framework_langgraph/_utils.py +0 -0
  156. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/src/by_framework_langgraph/tools.py +0 -0
  157. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/src/by_framework_langgraph/worker.py +0 -0
  158. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/tests/test_tools.py +0 -0
  159. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-langgraph/tests/test_utils.py +0 -0
  160. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-trace-langfuse/README.md +0 -0
  161. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-trace-langfuse/pyproject.toml +0 -0
  162. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-trace-langfuse/tests/test_langfuse_import.py +0 -0
  163. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-trace-phoenix/README.md +0 -0
  164. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-trace-phoenix/pyproject.toml +0 -0
  165. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/__init__.py +0 -0
  166. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/phoenix.py +0 -0
  167. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-trace-phoenix/tests/test_phoenix_import.py +0 -0
  168. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-trace-phoenix/tests/test_phoenix_plugin.py +0 -0
  169. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-trace-query/pyproject.toml +0 -0
  170. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-trace-query/src/by_framework_trace_query/__init__.py +0 -0
  171. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-trace-query/src/by_framework_trace_query/client.py +0 -0
  172. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-trace-query/src/by_framework_trace_query/merger.py +0 -0
  173. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-trace-query/src/by_framework_trace_query/models.py +0 -0
  174. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/libs/by-framework-trace-query/src/by_framework_trace_query/redis_source.py +0 -0
  175. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/pylintrc +0 -0
  176. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/scripts/python_quality.sh +0 -0
  177. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/__init__.py +0 -0
  178. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/__main__.py +0 -0
  179. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/client/__init__.py +0 -0
  180. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/client/byai_client.py +0 -0
  181. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/common/__init__.py +0 -0
  182. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/common/emitter.py +0 -0
  183. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/common/exceptions.py +0 -0
  184. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/common/redis_client.py +0 -0
  185. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/errors/__init__.py +0 -0
  186. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/errors/base.py +0 -0
  187. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/errors/common.py +0 -0
  188. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/errors/execution.py +0 -0
  189. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/errors/http.py +0 -0
  190. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/errors/protocol.py +0 -0
  191. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/errors/registry.py +0 -0
  192. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/metrics/read_client.py +0 -0
  193. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/metrics/snapshot.py +0 -0
  194. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/trace/external_trace.py +0 -0
  195. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/trace/trace_schema.py +0 -0
  196. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/trace/trace_writer.py +0 -0
  197. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/util/__init__.py +0 -0
  198. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/util/discovery_http_client.py +0 -0
  199. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/util/generate_message_id.py +0 -0
  200. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/util/http_client.py +0 -0
  201. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/worker/__init__.py +0 -0
  202. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/worker/_control_handling.py +0 -0
  203. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/worker/_execution_tracking.py +0 -0
  204. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/worker/_message_processing.py +0 -0
  205. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/worker/app.py +0 -0
  206. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/worker/byai_context.py +0 -0
  207. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/worker/byai_worker.py +0 -0
  208. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/worker/processor.py +0 -0
  209. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/worker/sandbox/__init__.py +0 -0
  210. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/src/by_framework/worker/sandbox/hook_sandbox.py +0 -0
  211. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/client/__init__.py +0 -0
  212. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/client/test_client.py +0 -0
  213. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/common/__init__.py +0 -0
  214. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/common/test_exceptions.py +0 -0
  215. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/common/test_redis_client.py +0 -0
  216. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/conftest.py +0 -0
  217. {by_framework-0.2.2.dev3/tests/integration → by_framework-0.2.2.dev5/tests/core}/__init__.py +0 -0
  218. {by_framework-0.2.2.dev3/tests/plugin → by_framework-0.2.2.dev5/tests/core/protocol}/__init__.py +0 -0
  219. {by_framework-0.2.2.dev3/tests/worker → by_framework-0.2.2.dev5/tests/core/runtime/history}/__init__.py +0 -0
  220. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/integration/test_ask_user_flow.py +0 -0
  221. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/integration/test_callback_flow.py +0 -0
  222. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/integration/test_logger_integration.py +0 -0
  223. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/integration/test_scatter_gather.py +0 -0
  224. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/metrics/test_metrics.py +0 -0
  225. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/metrics/test_read_client.py +0 -0
  226. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/plugin/test_plugin_discovery.py +0 -0
  227. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/plugin/test_plugin_improvements.py +0 -0
  228. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/plugin/test_plugin_registry.py +0 -0
  229. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/plugin/test_plugin_system.py +0 -0
  230. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/trace/test_external_trace.py +0 -0
  231. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/trace/test_logger_correlation.py +0 -0
  232. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/trace/test_otel_exporter.py +0 -0
  233. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/util/test_discovery_http_client.py +0 -0
  234. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/util/test_discovery_http_client_download.py +0 -0
  235. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/util/test_discovery_http_client_upload.py +0 -0
  236. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/util/test_http_client.py +0 -0
  237. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/worker/test_app.py +0 -0
  238. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/worker/test_byai_worker.py +0 -0
  239. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/worker/test_context.py +0 -0
  240. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/worker/test_control_handling.py +0 -0
  241. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/worker/test_emitter.py +0 -0
  242. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/worker/test_gateway_worker.py +0 -0
  243. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/worker/test_message_processing.py +0 -0
  244. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/worker/test_processor.py +0 -0
  245. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/worker/test_sandbox.py +0 -0
  246. {by_framework-0.2.2.dev3 → by_framework-0.2.2.dev5}/tests/worker/test_workspace.py +0 -0
@@ -8,6 +8,8 @@ on:
8
8
  - "by-framework-history-byclaw-v*"
9
9
  - "by-framework-trace-langfuse-v*"
10
10
  - "by-framework-trace-phoenix-v*"
11
+ - "by-framework-trace-query-v*"
12
+ - "by-framework-dashboard-v*"
11
13
  - "by-framework-langgraph-v*"
12
14
  - "by-framework-adk-v*"
13
15
 
@@ -54,6 +56,16 @@ jobs:
54
56
  echo "package_dir=libs/by-framework-trace-phoenix" >> "$GITHUB_OUTPUT"
55
57
  echo "version=${REF_NAME#by-framework-trace-phoenix-v}" >> "$GITHUB_OUTPUT"
56
58
  ;;
59
+ by-framework-trace-query-v*)
60
+ echo "package_name=by-framework-trace-query" >> "$GITHUB_OUTPUT"
61
+ echo "package_dir=libs/by-framework-trace-query" >> "$GITHUB_OUTPUT"
62
+ echo "version=${REF_NAME#by-framework-trace-query-v}" >> "$GITHUB_OUTPUT"
63
+ ;;
64
+ by-framework-dashboard-v*)
65
+ echo "package_name=by-framework-dashboard" >> "$GITHUB_OUTPUT"
66
+ echo "package_dir=libs/by-framework-dashboard" >> "$GITHUB_OUTPUT"
67
+ echo "version=${REF_NAME#by-framework-dashboard-v}" >> "$GITHUB_OUTPUT"
68
+ ;;
57
69
  by-framework-langgraph-v*)
58
70
  echo "package_name=by-framework-langgraph" >> "$GITHUB_OUTPUT"
59
71
  echo "package_dir=libs/by-framework-langgraph" >> "$GITHUB_OUTPUT"
@@ -68,8 +68,6 @@ Thumbs.db
68
68
  .claude/settings.local.json
69
69
  .claude/settings.json
70
70
 
71
- core
72
-
73
71
  .coderfleet-uploads/
74
72
 
75
73
  .claude/skills
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: by-framework
3
- Version: 0.2.2.dev3
3
+ Version: 0.2.2.dev5
4
4
  Summary: 分布式 Agent 调度框架
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -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
- langfuse_config = import_module(
616
- "by_framework_trace_langfuse"
617
- ).LangfuseConfig
618
- if langfuse_config.from_env() is None:
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
- callbacks.append(callback_handler())
628
-
629
- framework_observation = getattr(self._context, LANGFUSE_OBSERVATION_ATTR, None)
630
- if framework_observation is None:
631
- yield
632
- return
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
- langfuse = get_client()
635
- with langfuse.start_as_current_observation(
636
- as_type="span",
637
- name=self._tracing.run_name,
638
- trace_context={
639
- "trace_id": getattr(self._context, "trace_id", ""),
640
- "parent_span_id": framework_observation.id,
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._langfuse_observation = SimpleNamespace(id="obs-framework") # pylint: disable=protected-access
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 wires Langfuse callback handler into LangGraph config."""
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
- observation_calls: list[dict] = []
201
- callback_instances: list[object] = []
199
+ callback_handler = object()
200
+ callback_calls: list[dict[str, str]] = []
202
201
 
203
- @contextmanager
204
- def fake_observation_scope(**kwargs):
205
- observation_calls.append(kwargs)
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(LangfuseConfig=MagicMock()),
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 all(cb in config["callbacks"] for cb in callback_instances)
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 len(observation_calls) == 1
264
- assert observation_calls[0]["trace_context"] == {
265
- "trace_id": "trace-ctx",
266
- "parent_span_id": "obs-framework",
267
- }
268
- assert observation_calls[0]["name"] == "planner:langgraph"
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 test_uses_context_langfuse_callback_property(self):
272
- """Verify AgentContext.langfuse_callback property is used directly."""
273
- handler = object()
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 ContextWithCallbackProperty:
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
- @property
295
- def langfuse_callback(self):
296
- return handler
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
- ctx = ContextWithCallbackProperty()
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(LangfuseConfig=MagicMock()),
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(LangfuseConfig=MagicMock()),
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
- raise RuntimeError(
751
- "LangfusePlugin requires LANGFUSE_SECRET_KEY, "
752
- "LANGFUSE_PUBLIC_KEY, and LANGFUSE_BASE_URL to be set."
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
- try:
765
- client = langfuse_client_cls(
766
- public_key=config.public_key,
767
- secret_key=config.secret_key,
768
- base_url=config.base_url,
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
- try:
1041
- client = langfuse_client_cls(
1042
- public_key=config.public_key,
1043
- secret_key=config.secret_key,
1044
- base_url=config.base_url,
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"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "by-framework"
3
- version = "0.2.2.dev3"
3
+ version = "0.2.2.dev5"
4
4
  description = "分布式 Agent 调度框架"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"