by-framework-langgraph 0.0.3.dev2__tar.gz → 0.0.3.dev3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: by-framework-langgraph
3
- Version: 0.0.3.dev2
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,6 +1,6 @@
1
1
  [project]
2
2
  name = "by-framework-langgraph"
3
- version = "0.0.3.dev2"
3
+ version = "0.0.3.dev3"
4
4
  description = "LangGraph integration for by-framework"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -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."""
@@ -23,6 +23,7 @@ def _make_mock_context(session_id: str = "test-session"):
23
23
  ctx.message_id = "msg-ctx"
24
24
  ctx.parent_message_id = "parent-ctx"
25
25
  ctx.current_agent_id = "planner"
26
+ ctx.worker_id = "worker-langgraph-1"
26
27
  ctx.redis = AsyncMock()
27
28
  ctx.emit_chunk = AsyncMock()
28
29
  ctx.ask_user = AsyncMock()
@@ -225,6 +226,7 @@ class TestAdapterRun:
225
226
  assert config["metadata"]["langfuse_session_id"] == "test-session"
226
227
  assert config["metadata"]["langfuse_user_id"] == "user-1"
227
228
  assert config["metadata"]["by_framework_message_id"] == "msg-ctx"
229
+ assert config["metadata"]["worker_id"] == "worker-langgraph-1"
228
230
  assert config["metadata"]["langgraph_thread_id"] == "test-session"
229
231
  # Callbacks list includes the Langfuse handler(s) plus the token accumulator.
230
232
  assert callback_handler in config["callbacks"]
@@ -239,6 +241,63 @@ class TestAdapterRun:
239
241
  }
240
242
  ]
241
243
 
244
+ @pytest.mark.asyncio
245
+ async def test_propagates_worker_id_to_langfuse_child_observations(
246
+ self, monkeypatch
247
+ ):
248
+ """Langfuse attribute propagation covers nested LangGraph observations."""
249
+ ctx = _make_mock_context()
250
+ graph = MagicMock()
251
+ snapshot = MagicMock()
252
+ snapshot.next = ()
253
+ graph.get_state.return_value = snapshot
254
+
255
+ propagation_events: list[tuple[str, dict[str, str]]] = []
256
+ propagation_active = False
257
+
258
+ class FakePropagation:
259
+
260
+ def __init__(self, metadata):
261
+ self.metadata = metadata
262
+
263
+ def __enter__(self):
264
+ nonlocal propagation_active
265
+ propagation_active = True
266
+ propagation_events.append(("enter", self.metadata))
267
+
268
+ def __exit__(self, exc_type, exc_val, exc_tb):
269
+ nonlocal propagation_active
270
+ propagation_events.append(("exit", self.metadata))
271
+ propagation_active = False
272
+
273
+ def fake_propagate_attributes(**kwargs):
274
+ return FakePropagation(metadata=kwargs["metadata"])
275
+
276
+ async def fake_ainvoke(input_data, *, config):
277
+ del input_data
278
+ assert config["metadata"]["worker_id"] == "worker-langgraph-1"
279
+ assert propagation_active is True
280
+ return {"messages": [MagicMock(content="hello")]}
281
+
282
+ graph.ainvoke = fake_ainvoke
283
+ monkeypatch.setitem(
284
+ sys.modules,
285
+ "langfuse",
286
+ SimpleNamespace(propagate_attributes=fake_propagate_attributes),
287
+ )
288
+
289
+ adapter = LangGraphAdapter(graph, ctx, stream=False)
290
+
291
+ result = await adapter.run(
292
+ AskAgentCommand(header=_make_header(), content="write a poem")
293
+ )
294
+
295
+ assert result == "hello"
296
+ assert propagation_events == [
297
+ ("enter", {"worker_id": "worker-langgraph-1"}),
298
+ ("exit", {"worker_id": "worker-langgraph-1"}),
299
+ ]
300
+
242
301
  @pytest.mark.asyncio
243
302
  async def test_skips_langfuse_callback_when_provider_package_missing(
244
303
  self, monkeypatch