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.
- {by_framework_langgraph-0.0.2 → by_framework_langgraph-0.0.3.dev0}/.gitignore +5 -0
- {by_framework_langgraph-0.0.2 → by_framework_langgraph-0.0.3.dev0}/PKG-INFO +1 -1
- {by_framework_langgraph-0.0.2 → by_framework_langgraph-0.0.3.dev0}/pyproject.toml +1 -1
- {by_framework_langgraph-0.0.2 → by_framework_langgraph-0.0.3.dev0}/src/by_framework_langgraph/_utils.py +0 -11
- {by_framework_langgraph-0.0.2 → by_framework_langgraph-0.0.3.dev0}/src/by_framework_langgraph/adapter.py +33 -6
- {by_framework_langgraph-0.0.2 → by_framework_langgraph-0.0.3.dev0}/tests/test_adapter.py +44 -0
- {by_framework_langgraph-0.0.2 → by_framework_langgraph-0.0.3.dev0}/README.md +0 -0
- {by_framework_langgraph-0.0.2 → by_framework_langgraph-0.0.3.dev0}/src/by_framework_langgraph/__init__.py +0 -0
- {by_framework_langgraph-0.0.2 → by_framework_langgraph-0.0.3.dev0}/src/by_framework_langgraph/tools.py +0 -0
- {by_framework_langgraph-0.0.2 → by_framework_langgraph-0.0.3.dev0}/src/by_framework_langgraph/worker.py +0 -0
- {by_framework_langgraph-0.0.2 → by_framework_langgraph-0.0.3.dev0}/tests/test_tools.py +0 -0
- {by_framework_langgraph-0.0.2 → by_framework_langgraph-0.0.3.dev0}/tests/test_utils.py +0 -0
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|