by-framework-langgraph 0.0.3.dev1__py3-none-any.whl → 0.0.3.dev3__py3-none-any.whl

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.
@@ -525,6 +525,7 @@ class LangGraphAdapter:
525
525
  self._context, "parent_message_id", ""
526
526
  ),
527
527
  "by_framework_agent_id": getattr(self._context, "current_agent_id", ""),
528
+ "worker_id": getattr(self._context, "worker_id", ""),
528
529
  "langgraph_thread_id": self._thread_id,
529
530
  }
530
531
  return {
@@ -538,10 +539,31 @@ class LangGraphAdapter:
538
539
 
539
540
  with (
540
541
  self._phoenix_context_manager(),
542
+ self._langfuse_attribute_propagation_context_manager(),
541
543
  self._langfuse_callback_manager(callbacks),
542
544
  ):
543
545
  yield callbacks
544
546
 
547
+ @contextmanager
548
+ def _langfuse_attribute_propagation_context_manager(self) -> Iterator[None]:
549
+ """Propagate stable framework metadata to Langfuse child observations."""
550
+ worker_id = str(getattr(self._context, "worker_id", "") or "")
551
+ if not worker_id:
552
+ yield
553
+ return
554
+
555
+ try:
556
+ propagate_attributes = getattr(
557
+ import_module("langfuse"),
558
+ "propagate_attributes",
559
+ )
560
+ except (ImportError, AttributeError):
561
+ yield
562
+ return
563
+
564
+ with propagate_attributes(metadata={"worker_id": worker_id}):
565
+ yield
566
+
545
567
  @contextmanager
546
568
  def _phoenix_context_manager(self) -> Iterator[None]:
547
569
  """Prepare OpenTelemetry context for Phoenix tracing."""
@@ -590,69 +612,47 @@ class LangGraphAdapter:
590
612
  # Always inject token accumulator — works regardless of Langfuse config.
591
613
  callbacks.append(_TokenAccumulatingCallbackHandler(self._context))
592
614
 
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
615
  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
616
+ build_langchain_callback = getattr(
617
+ import_module("by_framework_trace_langfuse"),
618
+ "build_langchain_callback",
619
+ )
623
620
  except (ImportError, AttributeError):
624
621
  yield
625
622
  return
626
623
 
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
624
+ get_parent_observation_id = getattr(
625
+ self._context,
626
+ "get_trace_parent_observation_id",
627
+ None,
628
+ )
629
+ parent_observation_id = (
630
+ str(get_parent_observation_id() or "")
631
+ if callable(get_parent_observation_id)
632
+ else ""
633
+ )
634
+ if not parent_observation_id:
635
+ framework_observation = getattr(
636
+ self._context, LANGFUSE_OBSERVATION_ATTR, None
637
+ )
638
+ parent_observation_id = getattr(framework_observation, "id", "") or ""
639
+ if not parent_observation_id:
640
+ execution_id = getattr(self._context, "execution_id", "")
641
+ message_id = getattr(self._context, "message_id", "")
642
+ raw_parent_id = (
643
+ f"{execution_id}:worker.execute"
644
+ if execution_id
645
+ else f"{message_id}:worker.execute"
646
+ )
647
+ parent_observation_id = f"{str_to_uint64(raw_parent_id):016x}"
633
648
 
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
649
+ handler = build_langchain_callback(
650
+ trace_id=getattr(self._context, "trace_id", ""),
651
+ parent_observation_id=parent_observation_id,
652
+ )
653
+ if handler is not None:
654
+ callbacks.append(handler)
655
+ yield
656
656
 
657
657
  @staticmethod
658
658
  def _default_input_mapper(content: str) -> dict:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: by-framework-langgraph
3
- Version: 0.0.3.dev1
3
+ Version: 0.0.3.dev3
4
4
  Summary: LangGraph integration for by-framework
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: by-framework>=0.2.1
@@ -1,8 +1,8 @@
1
1
  by_framework_langgraph/__init__.py,sha256=x08wIaK4VRnUnUD_mbBxVgI2Zf7CvwAVbr3syP2NQps,1046
2
2
  by_framework_langgraph/_utils.py,sha256=A8eQ19YqyBiMKcPIlzmwggN_mK5iQvNJPTaTmsywWsw,1605
3
- by_framework_langgraph/adapter.py,sha256=HNDQuGdgR9JmPnt1qQ1xa8TgaEE-Fj79wlSf9jMEkSg,27817
3
+ by_framework_langgraph/adapter.py,sha256=7b-3veBqOPP2frpGWELiSCrfPg4EeCZOdfNLN_nsFB0,27468
4
4
  by_framework_langgraph/tools.py,sha256=l7LurtKKXA1Z4fyj15ffFWyWpt8drNCJ-mkGmlS7EVo,5561
5
5
  by_framework_langgraph/worker.py,sha256=wFalpBQLqFm-YzJugzAzkA3ciI9lUzxRTv4GmqVCv78,6156
6
- by_framework_langgraph-0.0.3.dev1.dist-info/METADATA,sha256=bzWo8izusq4vEWlBjSR_Izy8KXE2dkGnTNhxC5USX28,2334
7
- by_framework_langgraph-0.0.3.dev1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
8
- by_framework_langgraph-0.0.3.dev1.dist-info/RECORD,,
6
+ by_framework_langgraph-0.0.3.dev3.dist-info/METADATA,sha256=iN1sV32D2M5ydKW4ByHFnYiA3jYBVVSHNQQDG1Wcabo,2334
7
+ by_framework_langgraph-0.0.3.dev3.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
8
+ by_framework_langgraph-0.0.3.dev3.dist-info/RECORD,,