by-framework-langgraph 0.0.2__tar.gz → 0.0.3.dev0__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.
@@ -3,6 +3,7 @@ __pycache__/
3
3
  *.py[cod]
4
4
  *$py.class
5
5
  *.so
6
+ .ruff_cache/
6
7
  .Python
7
8
  build/
8
9
  develop-eggs/
@@ -66,3 +67,7 @@ Thumbs.db
66
67
  *gateway-sdk.log*
67
68
  .claude/settings.local.json
68
69
  .claude/settings.json
70
+
71
+ core
72
+
73
+ .coderfleet-uploads/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: by-framework-langgraph
3
- Version: 0.0.2
3
+ Version: 0.0.3.dev0
4
4
  Summary: LangGraph integration for by-framework
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: by-framework>=0.2.1
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "by-framework-langgraph"
3
- version = "0.0.2"
3
+ version = "0.0.3.dev0"
4
4
  description = "LangGraph integration for by-framework"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -2,7 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import hashlib
6
5
  from typing import Any
7
6
 
8
7
  from by_framework.core.protocol.commands import ResumeCommand
@@ -52,13 +51,3 @@ def extract_resume_data(command: ResumeCommand) -> str:
52
51
  return extract_content_text(command.content)
53
52
 
54
53
  return ""
55
-
56
-
57
- def str_to_uint128(s: str) -> int:
58
- """Convert a string to a 128-bit integer (for OTEL TraceId)."""
59
- return int(hashlib.md5(s.encode()).hexdigest(), 16)
60
-
61
-
62
- def str_to_uint64(s: str) -> int:
63
- """Convert a string to a 64-bit integer (for OTEL SpanId)."""
64
- return int(hashlib.md5(s.encode()).hexdigest()[:16], 16)
@@ -16,15 +16,11 @@ from by_framework.common.logger import logger
16
16
  from by_framework.core.protocol.agent_state import AgentState
17
17
  from by_framework.core.protocol.commands import ResumeCommand
18
18
  from by_framework.core.protocol.events import StreamChunkEvent
19
+ from by_framework.observability.span_recorder import (str_to_uint64, str_to_uint128)
19
20
  from langchain_core.messages import HumanMessage
20
21
  from langgraph.types import Command
21
22
 
22
- from ._utils import (
23
- extract_content_text,
24
- extract_resume_data,
25
- str_to_uint64,
26
- str_to_uint128,
27
- )
23
+ from ._utils import extract_content_text, extract_resume_data
28
24
 
29
25
  if TYPE_CHECKING:
30
26
  from by_framework.core.protocol.commands import GatewayCommand
@@ -383,6 +379,26 @@ class LangGraphAdapter:
383
379
  @contextmanager
384
380
  def _langfuse_callback_manager(self, callbacks: list[Any]) -> Iterator[None]:
385
381
  """Prepare Langfuse callback and observation for LangChain."""
382
+ # Prefer AgentContext's callback factory so trace and parent ids align.
383
+ # Filter out auto-generated MagicMock attributes when tests use a mock
384
+ # context — real callback objects always come from a non-test module.
385
+ langfuse_callback_value = getattr(self._context, "langfuse_callback", None)
386
+ is_real_callback = langfuse_callback_value is not None and type(
387
+ langfuse_callback_value
388
+ ).__module__ not in ("unittest.mock",)
389
+
390
+ if is_real_callback:
391
+ handler = (
392
+ langfuse_callback_value()
393
+ if callable(langfuse_callback_value)
394
+ else langfuse_callback_value
395
+ )
396
+ if handler is not None:
397
+ callbacks.append(handler)
398
+ yield
399
+ return
400
+
401
+ # Fallback to local import if context method is missing
386
402
  # pylint: disable=import-outside-toplevel
387
403
  try:
388
404
  langfuse_config = import_module(
@@ -414,6 +430,17 @@ class LangGraphAdapter:
414
430
  },
415
431
  metadata=self._default_metadata(),
416
432
  ):
433
+ # Prevent the generated OTel span from being promoted to a trace root.
434
+ # The native LangfusePlugin sets the same attribute on its own path
435
+ # (via _SdkLangfuseTracer); this covers the LangGraph fallback path.
436
+ try:
437
+ from opentelemetry import trace
438
+
439
+ current_span = trace.get_current_span()
440
+ if current_span and hasattr(current_span, "set_attribute"):
441
+ current_span.set_attribute("langfuse.internal.as_root", False)
442
+ except Exception: # pylint: disable=broad-exception-caught
443
+ pass
417
444
  yield
418
445
 
419
446
  @staticmethod
@@ -259,6 +259,50 @@ class TestAdapterRun:
259
259
  }
260
260
  assert observation_calls[0]["name"] == "planner:langgraph"
261
261
 
262
+ @pytest.mark.asyncio
263
+ async def test_uses_context_langfuse_callback_property(self):
264
+ """Verify AgentContext.langfuse_callback property value is used directly as a handler."""
265
+ handler = object()
266
+
267
+ # pylint: disable=too-few-public-methods,missing-class-docstring,missing-function-docstring
268
+ class ContextWithCallbackProperty:
269
+ session_id = "test-session"
270
+ trace_id = "trace-ctx"
271
+ message_id = "msg-ctx"
272
+ parent_message_id = ""
273
+ current_agent_id = "planner"
274
+ current_command = SimpleNamespace(
275
+ header=MessageHeader(
276
+ message_id="msg-ctx",
277
+ session_id="test-session",
278
+ trace_id="trace-ctx",
279
+ target_agent_type="planner",
280
+ )
281
+ )
282
+
283
+ def __init__(self):
284
+ self.emit_chunk = AsyncMock()
285
+
286
+ @property
287
+ def langfuse_callback(self):
288
+ return handler
289
+
290
+ ctx = ContextWithCallbackProperty()
291
+ graph = MagicMock()
292
+ graph.ainvoke = AsyncMock(
293
+ return_value={"messages": [MagicMock(content="hello")]}
294
+ )
295
+ snapshot = MagicMock()
296
+ snapshot.next = ()
297
+ graph.get_state.return_value = snapshot
298
+
299
+ adapter = LangGraphAdapter(graph, ctx, stream=False)
300
+ result = await adapter.run(AskAgentCommand(header=_make_header(), content="hi"))
301
+
302
+ assert result == "hello"
303
+ _, kwargs = graph.ainvoke.call_args
304
+ assert kwargs["config"]["callbacks"] == [handler]
305
+
262
306
  @pytest.mark.asyncio
263
307
  async def test_skips_langfuse_tracing_silently_when_not_configured(
264
308
  self, monkeypatch, caplog