by-framework-langgraph 0.0.3.dev1__tar.gz → 0.0.3.dev2__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_langgraph-0.0.3.dev1 → by_framework_langgraph-0.0.3.dev2}/.gitignore +0 -2
- {by_framework_langgraph-0.0.3.dev1 → by_framework_langgraph-0.0.3.dev2}/PKG-INFO +1 -1
- {by_framework_langgraph-0.0.3.dev1 → by_framework_langgraph-0.0.3.dev2}/pyproject.toml +1 -1
- {by_framework_langgraph-0.0.3.dev1 → by_framework_langgraph-0.0.3.dev2}/src/by_framework_langgraph/adapter.py +35 -57
- {by_framework_langgraph-0.0.3.dev1 → by_framework_langgraph-0.0.3.dev2}/tests/test_adapter.py +30 -61
- {by_framework_langgraph-0.0.3.dev1 → by_framework_langgraph-0.0.3.dev2}/README.md +0 -0
- {by_framework_langgraph-0.0.3.dev1 → by_framework_langgraph-0.0.3.dev2}/src/by_framework_langgraph/__init__.py +0 -0
- {by_framework_langgraph-0.0.3.dev1 → by_framework_langgraph-0.0.3.dev2}/src/by_framework_langgraph/_utils.py +0 -0
- {by_framework_langgraph-0.0.3.dev1 → by_framework_langgraph-0.0.3.dev2}/src/by_framework_langgraph/tools.py +0 -0
- {by_framework_langgraph-0.0.3.dev1 → by_framework_langgraph-0.0.3.dev2}/src/by_framework_langgraph/worker.py +0 -0
- {by_framework_langgraph-0.0.3.dev1 → by_framework_langgraph-0.0.3.dev2}/tests/test_tools.py +0 -0
- {by_framework_langgraph-0.0.3.dev1 → by_framework_langgraph-0.0.3.dev2}/tests/test_utils.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:
|
{by_framework_langgraph-0.0.3.dev1 → by_framework_langgraph-0.0.3.dev2}/tests/test_adapter.py
RENAMED
|
@@ -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
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|