agento11y-langgraph 0.10.0__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.
- agento11y_langgraph/__init__.py +51 -0
- agento11y_langgraph/handler.py +349 -0
- agento11y_langgraph/py.typed +0 -0
- agento11y_langgraph-0.10.0.dist-info/METADATA +194 -0
- agento11y_langgraph-0.10.0.dist-info/RECORD +8 -0
- agento11y_langgraph-0.10.0.dist-info/WHEEL +5 -0
- agento11y_langgraph-0.10.0.dist-info/licenses/LICENSE +3 -0
- agento11y_langgraph-0.10.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Public exports for Sigil LangGraph callback handlers."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from agento11y import Client
|
|
6
|
+
|
|
7
|
+
from .handler import Agento11yAsyncLangGraphHandler, Agento11yLangGraphHandler
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def create_agento11y_langgraph_handler(
|
|
11
|
+
*,
|
|
12
|
+
client: Client,
|
|
13
|
+
async_handler: bool = False,
|
|
14
|
+
**handler_kwargs: Any,
|
|
15
|
+
) -> Agento11yLangGraphHandler | Agento11yAsyncLangGraphHandler:
|
|
16
|
+
"""Create a LangGraph Sigil callback handler for sync or async flows."""
|
|
17
|
+
if async_handler:
|
|
18
|
+
return Agento11yAsyncLangGraphHandler(client=client, **handler_kwargs)
|
|
19
|
+
return Agento11yLangGraphHandler(client=client, **handler_kwargs)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def with_agento11y_langgraph_callbacks(
|
|
23
|
+
config: dict[str, Any] | None,
|
|
24
|
+
*,
|
|
25
|
+
client: Client,
|
|
26
|
+
async_handler: bool = False,
|
|
27
|
+
**handler_kwargs: Any,
|
|
28
|
+
) -> dict[str, Any]:
|
|
29
|
+
"""Append a Sigil callback handler to a LangGraph invocation config."""
|
|
30
|
+
merged = dict(config or {})
|
|
31
|
+
existing = merged.get("callbacks")
|
|
32
|
+
if isinstance(existing, list):
|
|
33
|
+
callbacks = list(existing)
|
|
34
|
+
elif existing is None:
|
|
35
|
+
callbacks = []
|
|
36
|
+
else:
|
|
37
|
+
callbacks = [existing]
|
|
38
|
+
if not any(isinstance(item, (Agento11yLangGraphHandler, Agento11yAsyncLangGraphHandler)) for item in callbacks):
|
|
39
|
+
callbacks.append(
|
|
40
|
+
create_agento11y_langgraph_handler(client=client, async_handler=async_handler, **handler_kwargs)
|
|
41
|
+
)
|
|
42
|
+
merged["callbacks"] = callbacks
|
|
43
|
+
return merged
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
"Agento11yLangGraphHandler",
|
|
48
|
+
"Agento11yAsyncLangGraphHandler",
|
|
49
|
+
"create_agento11y_langgraph_handler",
|
|
50
|
+
"with_agento11y_langgraph_callbacks",
|
|
51
|
+
]
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"""LangGraph callback handlers for Sigil generation recording."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from agento11y import Client
|
|
9
|
+
from agento11y.framework_handler import Agento11yFrameworkHandlerBase, ProviderResolver
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from langchain_core.callbacks import AsyncCallbackHandler, BaseCallbackHandler
|
|
13
|
+
except ModuleNotFoundError: # pragma: no cover - handled by package dependency in normal installs
|
|
14
|
+
|
|
15
|
+
class BaseCallbackHandler: # type: ignore[no-redef]
|
|
16
|
+
"""Fallback base class when langchain-core is unavailable."""
|
|
17
|
+
|
|
18
|
+
class AsyncCallbackHandler: # type: ignore[no-redef]
|
|
19
|
+
"""Fallback async base class when langchain-core is unavailable."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_framework_name = "langgraph"
|
|
23
|
+
_framework_source = "handler"
|
|
24
|
+
_framework_language = "python"
|
|
25
|
+
_framework_instrumentation_name = "github.com/grafana/sigil/sdks/python-frameworks/langgraph"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _merge_callback_kwargs(
|
|
29
|
+
kwargs: dict[str, Any],
|
|
30
|
+
*,
|
|
31
|
+
tags: list[str] | None = None,
|
|
32
|
+
metadata: dict[str, Any] | None = None,
|
|
33
|
+
run_name: str | None = None,
|
|
34
|
+
) -> dict[str, Any]:
|
|
35
|
+
callback_kwargs = dict(kwargs)
|
|
36
|
+
if tags is not None:
|
|
37
|
+
callback_kwargs["tags"] = tags
|
|
38
|
+
if metadata is not None:
|
|
39
|
+
callback_kwargs["metadata"] = metadata
|
|
40
|
+
if run_name is not None and run_name.strip() != "":
|
|
41
|
+
callback_kwargs["run_name"] = run_name
|
|
42
|
+
return callback_kwargs
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class _Agento11yLangGraphBase(Agento11yFrameworkHandlerBase):
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
*,
|
|
49
|
+
client: Client,
|
|
50
|
+
agent_name: str = "",
|
|
51
|
+
agent_version: str = "",
|
|
52
|
+
provider_resolver: ProviderResolver = "auto",
|
|
53
|
+
provider: str = "",
|
|
54
|
+
capture_inputs: bool = True,
|
|
55
|
+
capture_outputs: bool = True,
|
|
56
|
+
capture_workflow_steps: bool = False,
|
|
57
|
+
conversation_title: str = "",
|
|
58
|
+
extra_tags: dict[str, str] | None = None,
|
|
59
|
+
extra_metadata: dict[str, Any] | None = None,
|
|
60
|
+
) -> None:
|
|
61
|
+
super().__init__(
|
|
62
|
+
client=client,
|
|
63
|
+
framework_name=_framework_name,
|
|
64
|
+
framework_source=_framework_source,
|
|
65
|
+
framework_language=_framework_language,
|
|
66
|
+
framework_instrumentation_name=_framework_instrumentation_name,
|
|
67
|
+
agent_name=agent_name,
|
|
68
|
+
agent_version=agent_version,
|
|
69
|
+
provider_resolver=provider_resolver,
|
|
70
|
+
provider=provider,
|
|
71
|
+
capture_inputs=capture_inputs,
|
|
72
|
+
capture_outputs=capture_outputs,
|
|
73
|
+
capture_workflow_steps=capture_workflow_steps,
|
|
74
|
+
conversation_title=conversation_title,
|
|
75
|
+
extra_tags=extra_tags,
|
|
76
|
+
extra_metadata=extra_metadata,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class Agento11yLangGraphHandler(_Agento11yLangGraphBase, BaseCallbackHandler):
|
|
81
|
+
"""Sync LangGraph callback handler that records Sigil generations."""
|
|
82
|
+
|
|
83
|
+
def on_llm_start(
|
|
84
|
+
self,
|
|
85
|
+
serialized: dict[str, Any] | None,
|
|
86
|
+
prompts: list[str],
|
|
87
|
+
*,
|
|
88
|
+
run_id: UUID,
|
|
89
|
+
parent_run_id: UUID | None = None,
|
|
90
|
+
tags: list[str] | None = None,
|
|
91
|
+
metadata: dict[str, Any] | None = None,
|
|
92
|
+
invocation_params: dict[str, Any] | None = None,
|
|
93
|
+
run_name: str | None = None,
|
|
94
|
+
**kwargs: Any,
|
|
95
|
+
) -> None:
|
|
96
|
+
self._on_llm_start(
|
|
97
|
+
serialized=serialized,
|
|
98
|
+
prompts=prompts,
|
|
99
|
+
run_id=run_id,
|
|
100
|
+
parent_run_id=parent_run_id,
|
|
101
|
+
invocation_params=invocation_params,
|
|
102
|
+
callback_kwargs=_merge_callback_kwargs(kwargs, tags=tags, metadata=metadata, run_name=run_name),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def on_chat_model_start(
|
|
106
|
+
self,
|
|
107
|
+
serialized: dict[str, Any] | None,
|
|
108
|
+
messages: list[list[Any]],
|
|
109
|
+
*,
|
|
110
|
+
run_id: UUID,
|
|
111
|
+
parent_run_id: UUID | None = None,
|
|
112
|
+
tags: list[str] | None = None,
|
|
113
|
+
metadata: dict[str, Any] | None = None,
|
|
114
|
+
invocation_params: dict[str, Any] | None = None,
|
|
115
|
+
run_name: str | None = None,
|
|
116
|
+
**kwargs: Any,
|
|
117
|
+
) -> None:
|
|
118
|
+
self._on_chat_model_start(
|
|
119
|
+
serialized=serialized,
|
|
120
|
+
messages=messages,
|
|
121
|
+
run_id=run_id,
|
|
122
|
+
parent_run_id=parent_run_id,
|
|
123
|
+
invocation_params=invocation_params,
|
|
124
|
+
callback_kwargs=_merge_callback_kwargs(kwargs, tags=tags, metadata=metadata, run_name=run_name),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def on_llm_new_token(self, token: str, *, run_id: UUID, **_kwargs: Any) -> None:
|
|
128
|
+
self._on_llm_new_token(token=token, run_id=run_id)
|
|
129
|
+
|
|
130
|
+
def on_llm_end(self, response: Any, *, run_id: UUID, **_kwargs: Any) -> None:
|
|
131
|
+
self._on_llm_end(response=response, run_id=run_id)
|
|
132
|
+
|
|
133
|
+
def on_llm_error(self, error: BaseException, *, run_id: UUID, **_kwargs: Any) -> None:
|
|
134
|
+
self._on_llm_error(error=error, run_id=run_id)
|
|
135
|
+
|
|
136
|
+
def on_tool_start(
|
|
137
|
+
self,
|
|
138
|
+
serialized: dict[str, Any] | None,
|
|
139
|
+
input_str: str,
|
|
140
|
+
*,
|
|
141
|
+
run_id: UUID,
|
|
142
|
+
parent_run_id: UUID | None = None,
|
|
143
|
+
tags: list[str] | None = None,
|
|
144
|
+
metadata: dict[str, Any] | None = None,
|
|
145
|
+
run_name: str | None = None,
|
|
146
|
+
**kwargs: Any,
|
|
147
|
+
) -> None:
|
|
148
|
+
self._on_tool_start(
|
|
149
|
+
serialized=serialized,
|
|
150
|
+
input_str=input_str,
|
|
151
|
+
run_id=run_id,
|
|
152
|
+
parent_run_id=parent_run_id,
|
|
153
|
+
callback_kwargs=_merge_callback_kwargs(kwargs, tags=tags, metadata=metadata, run_name=run_name),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def on_tool_end(self, output: Any, *, run_id: UUID, **_kwargs: Any) -> None:
|
|
157
|
+
self._on_tool_end(output=output, run_id=run_id)
|
|
158
|
+
|
|
159
|
+
def on_tool_error(self, error: BaseException, *, run_id: UUID, **_kwargs: Any) -> None:
|
|
160
|
+
self._on_tool_error(error=error, run_id=run_id)
|
|
161
|
+
|
|
162
|
+
def on_chain_start(
|
|
163
|
+
self,
|
|
164
|
+
serialized: dict[str, Any] | None,
|
|
165
|
+
inputs: dict[str, Any] | None,
|
|
166
|
+
*,
|
|
167
|
+
run_id: UUID,
|
|
168
|
+
parent_run_id: UUID | None = None,
|
|
169
|
+
tags: list[str] | None = None,
|
|
170
|
+
metadata: dict[str, Any] | None = None,
|
|
171
|
+
run_type: str | None = None,
|
|
172
|
+
run_name: str | None = None,
|
|
173
|
+
**kwargs: Any,
|
|
174
|
+
) -> None:
|
|
175
|
+
self._on_chain_start(
|
|
176
|
+
serialized=serialized,
|
|
177
|
+
run_id=run_id,
|
|
178
|
+
parent_run_id=parent_run_id,
|
|
179
|
+
run_type=run_type or "chain",
|
|
180
|
+
callback_kwargs=_merge_callback_kwargs(kwargs, tags=tags, metadata=metadata, run_name=run_name),
|
|
181
|
+
inputs=inputs,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def on_chain_end(self, outputs: dict[str, Any] | None, *, run_id: UUID, **_kwargs: Any) -> None:
|
|
185
|
+
self._on_chain_end(run_id=run_id, outputs=outputs)
|
|
186
|
+
|
|
187
|
+
def on_chain_error(self, error: BaseException, *, run_id: UUID, **_kwargs: Any) -> None:
|
|
188
|
+
self._on_chain_error(error=error, run_id=run_id)
|
|
189
|
+
|
|
190
|
+
def on_retriever_start(
|
|
191
|
+
self,
|
|
192
|
+
serialized: dict[str, Any] | None,
|
|
193
|
+
_query: str,
|
|
194
|
+
*,
|
|
195
|
+
run_id: UUID,
|
|
196
|
+
parent_run_id: UUID | None = None,
|
|
197
|
+
tags: list[str] | None = None,
|
|
198
|
+
metadata: dict[str, Any] | None = None,
|
|
199
|
+
run_name: str | None = None,
|
|
200
|
+
**kwargs: Any,
|
|
201
|
+
) -> None:
|
|
202
|
+
self._on_retriever_start(
|
|
203
|
+
serialized=serialized,
|
|
204
|
+
run_id=run_id,
|
|
205
|
+
parent_run_id=parent_run_id,
|
|
206
|
+
callback_kwargs=_merge_callback_kwargs(kwargs, tags=tags, metadata=metadata, run_name=run_name),
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def on_retriever_end(self, _documents: list[Any], *, run_id: UUID, **_kwargs: Any) -> None:
|
|
210
|
+
self._on_retriever_end(run_id=run_id)
|
|
211
|
+
|
|
212
|
+
def on_retriever_error(self, error: BaseException, *, run_id: UUID, **_kwargs: Any) -> None:
|
|
213
|
+
self._on_retriever_error(error=error, run_id=run_id)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class Agento11yAsyncLangGraphHandler(_Agento11yLangGraphBase, AsyncCallbackHandler):
|
|
217
|
+
"""Async LangGraph callback handler that records Sigil generations."""
|
|
218
|
+
|
|
219
|
+
async def on_llm_start(
|
|
220
|
+
self,
|
|
221
|
+
serialized: dict[str, Any] | None,
|
|
222
|
+
prompts: list[str],
|
|
223
|
+
*,
|
|
224
|
+
run_id: UUID,
|
|
225
|
+
parent_run_id: UUID | None = None,
|
|
226
|
+
tags: list[str] | None = None,
|
|
227
|
+
metadata: dict[str, Any] | None = None,
|
|
228
|
+
invocation_params: dict[str, Any] | None = None,
|
|
229
|
+
run_name: str | None = None,
|
|
230
|
+
**kwargs: Any,
|
|
231
|
+
) -> None:
|
|
232
|
+
self._on_llm_start(
|
|
233
|
+
serialized=serialized,
|
|
234
|
+
prompts=prompts,
|
|
235
|
+
run_id=run_id,
|
|
236
|
+
parent_run_id=parent_run_id,
|
|
237
|
+
invocation_params=invocation_params,
|
|
238
|
+
callback_kwargs=_merge_callback_kwargs(kwargs, tags=tags, metadata=metadata, run_name=run_name),
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
async def on_chat_model_start(
|
|
242
|
+
self,
|
|
243
|
+
serialized: dict[str, Any] | None,
|
|
244
|
+
messages: list[list[Any]],
|
|
245
|
+
*,
|
|
246
|
+
run_id: UUID,
|
|
247
|
+
parent_run_id: UUID | None = None,
|
|
248
|
+
tags: list[str] | None = None,
|
|
249
|
+
metadata: dict[str, Any] | None = None,
|
|
250
|
+
invocation_params: dict[str, Any] | None = None,
|
|
251
|
+
run_name: str | None = None,
|
|
252
|
+
**kwargs: Any,
|
|
253
|
+
) -> None:
|
|
254
|
+
self._on_chat_model_start(
|
|
255
|
+
serialized=serialized,
|
|
256
|
+
messages=messages,
|
|
257
|
+
run_id=run_id,
|
|
258
|
+
parent_run_id=parent_run_id,
|
|
259
|
+
invocation_params=invocation_params,
|
|
260
|
+
callback_kwargs=_merge_callback_kwargs(kwargs, tags=tags, metadata=metadata, run_name=run_name),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
async def on_llm_new_token(self, token: str, *, run_id: UUID, **_kwargs: Any) -> None:
|
|
264
|
+
self._on_llm_new_token(token=token, run_id=run_id)
|
|
265
|
+
|
|
266
|
+
async def on_llm_end(self, response: Any, *, run_id: UUID, **_kwargs: Any) -> None:
|
|
267
|
+
self._on_llm_end(response=response, run_id=run_id)
|
|
268
|
+
|
|
269
|
+
async def on_llm_error(self, error: BaseException, *, run_id: UUID, **_kwargs: Any) -> None:
|
|
270
|
+
self._on_llm_error(error=error, run_id=run_id)
|
|
271
|
+
|
|
272
|
+
async def on_tool_start(
|
|
273
|
+
self,
|
|
274
|
+
serialized: dict[str, Any] | None,
|
|
275
|
+
input_str: str,
|
|
276
|
+
*,
|
|
277
|
+
run_id: UUID,
|
|
278
|
+
parent_run_id: UUID | None = None,
|
|
279
|
+
tags: list[str] | None = None,
|
|
280
|
+
metadata: dict[str, Any] | None = None,
|
|
281
|
+
run_name: str | None = None,
|
|
282
|
+
**kwargs: Any,
|
|
283
|
+
) -> None:
|
|
284
|
+
self._on_tool_start(
|
|
285
|
+
serialized=serialized,
|
|
286
|
+
input_str=input_str,
|
|
287
|
+
run_id=run_id,
|
|
288
|
+
parent_run_id=parent_run_id,
|
|
289
|
+
callback_kwargs=_merge_callback_kwargs(kwargs, tags=tags, metadata=metadata, run_name=run_name),
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
async def on_tool_end(self, output: Any, *, run_id: UUID, **_kwargs: Any) -> None:
|
|
293
|
+
self._on_tool_end(output=output, run_id=run_id)
|
|
294
|
+
|
|
295
|
+
async def on_tool_error(self, error: BaseException, *, run_id: UUID, **_kwargs: Any) -> None:
|
|
296
|
+
self._on_tool_error(error=error, run_id=run_id)
|
|
297
|
+
|
|
298
|
+
async def on_chain_start(
|
|
299
|
+
self,
|
|
300
|
+
serialized: dict[str, Any] | None,
|
|
301
|
+
inputs: dict[str, Any] | None,
|
|
302
|
+
*,
|
|
303
|
+
run_id: UUID,
|
|
304
|
+
parent_run_id: UUID | None = None,
|
|
305
|
+
tags: list[str] | None = None,
|
|
306
|
+
metadata: dict[str, Any] | None = None,
|
|
307
|
+
run_type: str | None = None,
|
|
308
|
+
run_name: str | None = None,
|
|
309
|
+
**kwargs: Any,
|
|
310
|
+
) -> None:
|
|
311
|
+
self._on_chain_start(
|
|
312
|
+
serialized=serialized,
|
|
313
|
+
run_id=run_id,
|
|
314
|
+
parent_run_id=parent_run_id,
|
|
315
|
+
run_type=run_type or "chain",
|
|
316
|
+
callback_kwargs=_merge_callback_kwargs(kwargs, tags=tags, metadata=metadata, run_name=run_name),
|
|
317
|
+
inputs=inputs,
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
async def on_chain_end(self, outputs: dict[str, Any] | None, *, run_id: UUID, **_kwargs: Any) -> None:
|
|
321
|
+
self._on_chain_end(run_id=run_id, outputs=outputs)
|
|
322
|
+
|
|
323
|
+
async def on_chain_error(self, error: BaseException, *, run_id: UUID, **_kwargs: Any) -> None:
|
|
324
|
+
self._on_chain_error(error=error, run_id=run_id)
|
|
325
|
+
|
|
326
|
+
async def on_retriever_start(
|
|
327
|
+
self,
|
|
328
|
+
serialized: dict[str, Any] | None,
|
|
329
|
+
_query: str,
|
|
330
|
+
*,
|
|
331
|
+
run_id: UUID,
|
|
332
|
+
parent_run_id: UUID | None = None,
|
|
333
|
+
tags: list[str] | None = None,
|
|
334
|
+
metadata: dict[str, Any] | None = None,
|
|
335
|
+
run_name: str | None = None,
|
|
336
|
+
**kwargs: Any,
|
|
337
|
+
) -> None:
|
|
338
|
+
self._on_retriever_start(
|
|
339
|
+
serialized=serialized,
|
|
340
|
+
run_id=run_id,
|
|
341
|
+
parent_run_id=parent_run_id,
|
|
342
|
+
callback_kwargs=_merge_callback_kwargs(kwargs, tags=tags, metadata=metadata, run_name=run_name),
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
async def on_retriever_end(self, _documents: list[Any], *, run_id: UUID, **_kwargs: Any) -> None:
|
|
346
|
+
self._on_retriever_end(run_id=run_id)
|
|
347
|
+
|
|
348
|
+
async def on_retriever_error(self, error: BaseException, *, run_id: UUID, **_kwargs: Any) -> None:
|
|
349
|
+
self._on_retriever_error(error=error, run_id=run_id)
|
|
File without changes
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agento11y-langgraph
|
|
3
|
+
Version: 0.10.0
|
|
4
|
+
Summary: LangGraph callback handlers for the Grafana Agent Observability Python SDK
|
|
5
|
+
License: SPDX-License-Identifier: Apache-2.0
|
|
6
|
+
|
|
7
|
+
See /LICENSE at repository root for full license text.
|
|
8
|
+
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: agento11y>=0.10.0
|
|
13
|
+
Requires-Dist: langchain-core>=0.3.0
|
|
14
|
+
Requires-Dist: langgraph>=0.2.0
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=9.0.3; extra == "dev"
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# Sigil Python Framework Module: LangGraph
|
|
20
|
+
|
|
21
|
+
`agento11y-langgraph` provides callback handlers that map LangGraph lifecycle events into Sigil generation recorder lifecycles.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install agento11y agento11y-langgraph
|
|
27
|
+
pip install langgraph langchain-openai
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from agento11y import Client
|
|
34
|
+
from agento11y_langgraph import with_agento11y_langgraph_callbacks
|
|
35
|
+
|
|
36
|
+
client = Client()
|
|
37
|
+
config = with_agento11y_langgraph_callbacks(None, client=client, provider_resolver="auto")
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## End-to-end example (graph invoke + stream)
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from typing import TypedDict
|
|
44
|
+
|
|
45
|
+
from langchain_core.runnables import RunnableConfig
|
|
46
|
+
from langchain_openai import ChatOpenAI
|
|
47
|
+
from langgraph.graph import END, StateGraph
|
|
48
|
+
from agento11y import Client
|
|
49
|
+
from agento11y_langgraph import with_agento11y_langgraph_callbacks
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class GraphState(TypedDict):
|
|
53
|
+
prompt: str
|
|
54
|
+
answer: str
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
client = Client()
|
|
58
|
+
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def run_model(state: GraphState, config: RunnableConfig) -> GraphState:
|
|
62
|
+
response = llm.invoke(
|
|
63
|
+
state["prompt"],
|
|
64
|
+
config=config,
|
|
65
|
+
)
|
|
66
|
+
return {"prompt": state["prompt"], "answer": str(response.content).strip()}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
workflow = StateGraph(GraphState)
|
|
70
|
+
workflow.add_node("model", run_model)
|
|
71
|
+
workflow.set_entry_point("model")
|
|
72
|
+
workflow.add_edge("model", END)
|
|
73
|
+
graph = workflow.compile()
|
|
74
|
+
|
|
75
|
+
agento11y_config = with_agento11y_langgraph_callbacks(
|
|
76
|
+
None,
|
|
77
|
+
client=client,
|
|
78
|
+
provider_resolver="auto",
|
|
79
|
+
agent_name="langgraph-example",
|
|
80
|
+
agent_version="1.0.0",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Non-stream graph invocation.
|
|
84
|
+
out = graph.invoke(
|
|
85
|
+
{"prompt": "Explain SLO burn rate in one paragraph.", "answer": ""},
|
|
86
|
+
config=agento11y_config,
|
|
87
|
+
)
|
|
88
|
+
print(out["answer"])
|
|
89
|
+
|
|
90
|
+
# Streamed graph events.
|
|
91
|
+
for _event in graph.stream(
|
|
92
|
+
{"prompt": "List three practical alerting tips.", "answer": ""},
|
|
93
|
+
config=agento11y_config,
|
|
94
|
+
):
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
client.shutdown()
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Workflow step capture
|
|
101
|
+
|
|
102
|
+
Enable `capture_workflow_steps=True` to record each graph node as a Sigil workflow step.
|
|
103
|
+
This enables the **Workflow** tab in the conversation detail view, showing node execution order,
|
|
104
|
+
duration, input/output state, and which LLM generations ran inside each node. The **Dependencies**
|
|
105
|
+
tab remains available for the generation-level DAG built from `parent_generation_ids`.
|
|
106
|
+
|
|
107
|
+
Always set `conversation_title` to a short human-readable label — it appears as the conversation
|
|
108
|
+
name in the Sigil UI. Without it, the title falls back to an opaque auto-generated ID.
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from agento11y import Client
|
|
112
|
+
from agento11y_langgraph import Agento11yLangGraphHandler
|
|
113
|
+
|
|
114
|
+
client = Client()
|
|
115
|
+
handler = Agento11yLangGraphHandler(
|
|
116
|
+
client=client,
|
|
117
|
+
agent_name="my-pipeline",
|
|
118
|
+
conversation_title="My Pipeline Run",
|
|
119
|
+
capture_workflow_steps=True,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Reuse the `graph` from the end-to-end example above. The node must pass its
|
|
123
|
+
# received `config` into `llm.invoke(...)` so generations link to the workflow step.
|
|
124
|
+
result = graph.invoke(
|
|
125
|
+
{"prompt": "Explain why my dashboard is slow.", "answer": ""},
|
|
126
|
+
config={"callbacks": [handler]},
|
|
127
|
+
)
|
|
128
|
+
client.shutdown()
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The handler automatically:
|
|
132
|
+
- Detects graph root and direct-child nodes
|
|
133
|
+
- Creates a workflow step per node with `input_state`, `output_state`, and timestamps
|
|
134
|
+
- Links LLM generation IDs to their parent step via `linked_generation_ids`
|
|
135
|
+
- Tracks sequential `parent_step_ids` so the DAG edges are correct
|
|
136
|
+
|
|
137
|
+
## Persistent thread example (LangGraph checkpointer)
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from langgraph.checkpoint.memory import MemorySaver
|
|
141
|
+
|
|
142
|
+
checkpointer = MemorySaver()
|
|
143
|
+
graph = workflow.compile(checkpointer=checkpointer)
|
|
144
|
+
|
|
145
|
+
thread_config = {
|
|
146
|
+
**with_agento11y_langgraph_callbacks(None, client=client, provider_resolver="auto"),
|
|
147
|
+
"configurable": {"thread_id": "customer-42"},
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
graph.invoke({"prompt": "Remember that my timezone is UTC+1.", "answer": ""}, config=thread_config)
|
|
151
|
+
graph.invoke({"prompt": "What timezone did I just give you?", "answer": ""}, config=thread_config)
|
|
152
|
+
|
|
153
|
+
# Advanced usage: explicit handler wiring remains supported.
|
|
154
|
+
_ = graph.invoke(
|
|
155
|
+
{"prompt": "manual handler wiring", "answer": ""},
|
|
156
|
+
config={"callbacks": [handler]},
|
|
157
|
+
)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
When `thread_id` is present, the handler records:
|
|
161
|
+
|
|
162
|
+
- `conversation_id=<thread_id>`
|
|
163
|
+
- `metadata["agento11y.framework.run_id"]=<run id>`
|
|
164
|
+
- `metadata["agento11y.framework.thread_id"]=<thread id>`
|
|
165
|
+
- generation span attributes `agento11y.framework.run_id` and `agento11y.framework.thread_id`
|
|
166
|
+
|
|
167
|
+
## Behavior
|
|
168
|
+
|
|
169
|
+
- Lifecycle mapping:
|
|
170
|
+
- `on_llm_start` / `on_chat_model_start` -> generation recorder
|
|
171
|
+
- `on_tool_start` / `on_tool_end` / `on_tool_error` -> `start_tool_execution`
|
|
172
|
+
- `on_chain_start` / `on_chain_end` / `on_chain_error` -> framework chain spans
|
|
173
|
+
- `on_retriever_start` / `on_retriever_end` / `on_retriever_error` -> framework retriever spans
|
|
174
|
+
- `on_llm_new_token` -> first-token timestamp for stream mode
|
|
175
|
+
- Mode mapping: non-stream -> `SYNC`, stream -> `STREAM`.
|
|
176
|
+
- Provider resolver parity:
|
|
177
|
+
- explicit provider metadata when available
|
|
178
|
+
- model-name inference (`gpt-`/`o1`/`o3`/`o4` -> `openai`, `claude-` -> `anthropic`, `gemini-` -> `gemini`)
|
|
179
|
+
- fallback -> `custom`
|
|
180
|
+
- Framework tags/metadata are always set:
|
|
181
|
+
- `agento11y.framework.name=langgraph`
|
|
182
|
+
- `agento11y.framework.source=handler`
|
|
183
|
+
- `agento11y.framework.language=python`
|
|
184
|
+
- `metadata["agento11y.framework.run_id"]=<run id>`
|
|
185
|
+
- `metadata["agento11y.framework.thread_id"]=<thread id>` (when present in callback metadata/config)
|
|
186
|
+
- `metadata["agento11y.framework.parent_run_id"]` (when available)
|
|
187
|
+
- `metadata["agento11y.framework.component_name"]` (serialized component identity)
|
|
188
|
+
- `metadata["agento11y.framework.run_type"]` (`llm`, `chat`, `tool`, `chain`, `retriever`)
|
|
189
|
+
- `metadata["agento11y.framework.tags"]` (normalized callback tags)
|
|
190
|
+
- `metadata["agento11y.framework.retry_attempt"]` (when available)
|
|
191
|
+
- `metadata["agento11y.framework.langgraph.node"]` (when callback context exposes node identity)
|
|
192
|
+
- generation span attributes mirror low-cardinality framework metadata keys
|
|
193
|
+
|
|
194
|
+
Call `client.shutdown()` during teardown to flush buffered telemetry.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
agento11y_langgraph/__init__.py,sha256=pF2bmP71RisDZiFZ01gC7EDiqcrSYMZocrUR1ka-d9s,1616
|
|
2
|
+
agento11y_langgraph/handler.py,sha256=uDaGg2kPbKZBMfSeeZh_QeXjNzx2sfontjOYXlZ1Xj0,12607
|
|
3
|
+
agento11y_langgraph/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
agento11y_langgraph-0.10.0.dist-info/licenses/LICENSE,sha256=L8b58whU_6MH2fW8ptr80ew_FRBkaPhSi82_uYAOglI,92
|
|
5
|
+
agento11y_langgraph-0.10.0.dist-info/METADATA,sha256=eotS5ybfAT4yPGnWpf1lU87YvdgDLDGvcgWh7Vur8Yc,6623
|
|
6
|
+
agento11y_langgraph-0.10.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
7
|
+
agento11y_langgraph-0.10.0.dist-info/top_level.txt,sha256=oIseh5WhMiqWd5zCXUTjnmBStExewwMQrtFbPsMfM6o,20
|
|
8
|
+
agento11y_langgraph-0.10.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
agento11y_langgraph
|