fiddler-langgraph 1.3.0__tar.gz → 1.3.1__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.
- {fiddler_langgraph-1.3.0/fiddler_langgraph.egg-info → fiddler_langgraph-1.3.1}/PKG-INFO +1 -1
- fiddler_langgraph-1.3.1/fiddler_langgraph/VERSION +1 -0
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/core/client.py +107 -2
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/core/span_processor.py +4 -1
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/tracing/callback.py +1 -6
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/tracing/jsonl_capture.py +7 -4
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/tracing/util.py +2 -6
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1/fiddler_langgraph.egg-info}/PKG-INFO +1 -1
- fiddler_langgraph-1.3.0/fiddler_langgraph/VERSION +0 -1
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/MANIFEST.in +0 -0
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/PUBLIC.md +0 -0
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/README.md +0 -0
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/__init__.py +0 -0
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/core/__init__.py +0 -0
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/core/attributes.py +0 -0
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/tracing/__init__.py +0 -0
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/tracing/instrumentation.py +0 -0
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph.egg-info/SOURCES.txt +0 -0
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph.egg-info/dependency_links.txt +0 -0
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph.egg-info/requires.txt +0 -0
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph.egg-info/top_level.txt +0 -0
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/pyproject.toml +0 -0
- {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.3.1
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"""Core client for Fiddler instrumentation."""
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
4
|
+
import atexit
|
|
5
|
+
import logging
|
|
3
6
|
import uuid
|
|
4
7
|
from typing import Any
|
|
5
8
|
from urllib.parse import urlparse
|
|
@@ -23,6 +26,8 @@ from fiddler_langgraph.core.attributes import FiddlerResourceAttributes
|
|
|
23
26
|
from fiddler_langgraph.core.span_processor import FiddlerSpanProcessor
|
|
24
27
|
from fiddler_langgraph.tracing.jsonl_capture import JSONLSpanExporter, initialize_jsonl_capture
|
|
25
28
|
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
26
31
|
|
|
27
32
|
class FiddlerClient:
|
|
28
33
|
"""The main client for instrumenting Generative AI applications with Fiddler observability.
|
|
@@ -31,6 +36,20 @@ class FiddlerClient:
|
|
|
31
36
|
to the Fiddler platform for monitoring, analysis, and debugging of your AI agents
|
|
32
37
|
and workflows.
|
|
33
38
|
|
|
39
|
+
Flush on exit: A shutdown handler is registered via :func:`atexit` so that pending
|
|
40
|
+
spans are flushed and the tracer is shut down when the process exits. For short
|
|
41
|
+
scripts or critical workloads, call :meth:`force_flush` and :meth:`shutdown` explicitly
|
|
42
|
+
(e.g. in a ``try``/``finally`` or signal handler) since ``atexit`` may not run in all
|
|
43
|
+
environments (e.g. SIGKILL, fork).
|
|
44
|
+
|
|
45
|
+
Asyncio: Tracing works in asyncio (context vars propagate across ``await``). When
|
|
46
|
+
shutting down from async code, use :meth:`aflush` and :meth:`ashutdown` so the event
|
|
47
|
+
loop is not blocked; the sync :meth:`force_flush` and :meth:`shutdown` can block for
|
|
48
|
+
up to the flush timeout.
|
|
49
|
+
|
|
50
|
+
Context manager: Use ``with FiddlerClient(...) as client:`` to ensure
|
|
51
|
+
:meth:`shutdown` is called on exit (flush then shutdown; atexit is unregistered).
|
|
52
|
+
|
|
34
53
|
Attributes:
|
|
35
54
|
application_id (str): The UUID4 identifier for the application.
|
|
36
55
|
url (str): The Fiddler backend URL.
|
|
@@ -182,6 +201,92 @@ class FiddlerClient:
|
|
|
182
201
|
resource = Resource.create({FiddlerResourceAttributes.APPLICATION_ID: self.application_id})
|
|
183
202
|
self.resource = self._get_aggregated_resources_with_fallback(resource)
|
|
184
203
|
|
|
204
|
+
# Flush and shutdown tracer on process exit to reduce span loss (BatchSpanProcessor buffers in memory)
|
|
205
|
+
atexit.register(self._atexit_shutdown)
|
|
206
|
+
|
|
207
|
+
def __enter__(self) -> 'FiddlerClient':
|
|
208
|
+
"""Context manager entry. Returns this client."""
|
|
209
|
+
return self
|
|
210
|
+
|
|
211
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
212
|
+
"""Context manager exit. Flushes and shuts down the tracer provider."""
|
|
213
|
+
self.shutdown()
|
|
214
|
+
|
|
215
|
+
def _atexit_shutdown(self) -> None:
|
|
216
|
+
"""Called at process exit to flush and shutdown the tracer provider."""
|
|
217
|
+
self.shutdown()
|
|
218
|
+
|
|
219
|
+
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
|
220
|
+
"""Flushes pending spans to the exporter.
|
|
221
|
+
|
|
222
|
+
Call this before process exit (e.g. in signal handlers or atexit) to reduce
|
|
223
|
+
intermittent span loss. BatchSpanProcessor buffers spans in memory and exports
|
|
224
|
+
on a schedule; without flush, buffered spans can be lost on exit.
|
|
225
|
+
|
|
226
|
+
This method is blocking (up to ``timeout_millis``). From async code, use
|
|
227
|
+
:meth:`aflush` to avoid blocking the event loop.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
timeout_millis: Maximum time to wait for flush in milliseconds. Default 30000.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
True if flush completed within the timeout, False otherwise.
|
|
234
|
+
"""
|
|
235
|
+
if self._provider is None:
|
|
236
|
+
logger.debug('Tracer provider not initialized, skipping flush')
|
|
237
|
+
return True
|
|
238
|
+
logger.info('Flushing tracer provider (timeout_millis=%s)', timeout_millis)
|
|
239
|
+
return self._provider.force_flush(timeout_millis)
|
|
240
|
+
|
|
241
|
+
async def aflush(self, timeout_millis: int = 30000) -> bool:
|
|
242
|
+
"""Async version of :meth:`force_flush`.
|
|
243
|
+
|
|
244
|
+
Runs the flush in a thread pool so the event loop is not blocked.
|
|
245
|
+
Use this when shutting down an asyncio application.
|
|
246
|
+
"""
|
|
247
|
+
return await asyncio.to_thread(self.force_flush, timeout_millis)
|
|
248
|
+
|
|
249
|
+
def shutdown(self) -> None:
|
|
250
|
+
"""Shuts down the tracer provider after flushing pending spans.
|
|
251
|
+
|
|
252
|
+
Call this when the application is exiting to ensure all spans are exported.
|
|
253
|
+
Safe to call multiple times. Registered automatically via atexit when the
|
|
254
|
+
client is created. The atexit handler is unregistered so it will not run
|
|
255
|
+
again at process exit.
|
|
256
|
+
|
|
257
|
+
This method is blocking (flush can take up to 30 seconds). From async code,
|
|
258
|
+
use :meth:`ashutdown` to avoid blocking the event loop.
|
|
259
|
+
"""
|
|
260
|
+
if self._provider is None:
|
|
261
|
+
logger.debug('Tracer provider not initialized, skipping shutdown')
|
|
262
|
+
return
|
|
263
|
+
logger.info('Shutting down tracer provider')
|
|
264
|
+
try:
|
|
265
|
+
atexit.unregister(self._atexit_shutdown)
|
|
266
|
+
except Exception as e:
|
|
267
|
+
# Handler may already have been removed or not registered
|
|
268
|
+
logger.debug('Could not unregister atexit handler: %s', e)
|
|
269
|
+
try:
|
|
270
|
+
self._provider.force_flush(30000)
|
|
271
|
+
except Exception as e:
|
|
272
|
+
logger.warning('Error flushing tracer provider during shutdown: %s', e)
|
|
273
|
+
try:
|
|
274
|
+
self._provider.shutdown()
|
|
275
|
+
except Exception as e:
|
|
276
|
+
logger.warning('Error shutting down tracer provider: %s', e)
|
|
277
|
+
finally:
|
|
278
|
+
self._provider = None
|
|
279
|
+
self._tracer = None
|
|
280
|
+
|
|
281
|
+
async def ashutdown(self) -> None:
|
|
282
|
+
"""Async version of :meth:`shutdown`.
|
|
283
|
+
|
|
284
|
+
Runs flush and shutdown in a thread pool so the event loop is not blocked.
|
|
285
|
+
Use this when shutting down an asyncio application, e.g. in an
|
|
286
|
+
``async with`` cleanup or before closing the event loop.
|
|
287
|
+
"""
|
|
288
|
+
await asyncio.to_thread(self.shutdown)
|
|
289
|
+
|
|
185
290
|
def get_tracer_provider(self) -> TracerProvider:
|
|
186
291
|
"""Gets the OpenTelemetry TracerProvider instance.
|
|
187
292
|
|
|
@@ -225,8 +330,8 @@ class FiddlerClient:
|
|
|
225
330
|
|
|
226
331
|
try:
|
|
227
332
|
return get_aggregated_resources(detectors, initial_resource=initial_resource)
|
|
228
|
-
except Exception:
|
|
229
|
-
|
|
333
|
+
except Exception as e:
|
|
334
|
+
logger.debug('Resource aggregation failed, using initial resource: %s', e)
|
|
230
335
|
return initial_resource
|
|
231
336
|
|
|
232
337
|
def update_resource(self, attributes: dict[str, Any]) -> None:
|
{fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/core/span_processor.py
RENAMED
|
@@ -11,7 +11,10 @@ from fiddler_langgraph.core.attributes import (
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class FiddlerSpanProcessor(SpanProcessor):
|
|
14
|
-
|
|
14
|
+
"""OpenTelemetry SpanProcessor that injects conversation ID and session attributes into spans."""
|
|
15
|
+
|
|
16
|
+
def on_start(self, span: Span, parent_context: context.Context | None = None) -> None:
|
|
17
|
+
"""Injects custom session attributes and conversation ID from context vars into the span."""
|
|
15
18
|
# inject custom attributes
|
|
16
19
|
try:
|
|
17
20
|
custom_attributes = _CUSTOM_ATTRIBUTES.get().copy()
|
|
@@ -593,18 +593,13 @@ class _CallbackHandler(BaseCallbackHandler):
|
|
|
593
593
|
child_span = self._create_child_span(parent_span, kwargs.get('name', 'unknown'))
|
|
594
594
|
|
|
595
595
|
child_span.set_attribute(FiddlerSpanAttributes.TYPE, SpanType.TOOL)
|
|
596
|
+
child_span.set_attribute(FiddlerSpanAttributes.TOOL_NAME, kwargs.get('name', 'unknown'))
|
|
596
597
|
child_span.set_attribute(FiddlerSpanAttributes.TOOL_INPUT, query)
|
|
597
598
|
|
|
598
599
|
if metadata is not None:
|
|
599
600
|
_set_agent_name(child_span, metadata)
|
|
600
601
|
self._set_fiddler_attributes_from_metadata(child_span, metadata)
|
|
601
602
|
|
|
602
|
-
# semantic convention attributes
|
|
603
|
-
child_span.set_attribute(
|
|
604
|
-
FiddlerSpanAttributes.TYPE, SpanType.TOOL
|
|
605
|
-
) # document retrieval is a tool
|
|
606
|
-
child_span.set_attribute(FiddlerSpanAttributes.TOOL_NAME, kwargs.get('name', 'unknown'))
|
|
607
|
-
child_span.set_attribute(FiddlerSpanAttributes.TOOL_INPUT, str(query))
|
|
608
603
|
self._set_session_id(child_span)
|
|
609
604
|
self._add_span(child_span, run_id)
|
|
610
605
|
|
{fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/tracing/jsonl_capture.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""JSONL data capture module for simplified span data in structured format."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import logging
|
|
4
5
|
import os
|
|
5
6
|
import threading
|
|
6
7
|
from collections.abc import Sequence
|
|
@@ -13,6 +14,8 @@ from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
|
|
|
13
14
|
|
|
14
15
|
from fiddler_langgraph.core.attributes import FiddlerSpanAttributes, SpanType
|
|
15
16
|
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
16
19
|
|
|
17
20
|
class JSONLSpanCapture:
|
|
18
21
|
"""Captures OpenTelemetry span data and saves it to JSONL format with structured fields."""
|
|
@@ -37,7 +40,7 @@ class JSONLSpanCapture:
|
|
|
37
40
|
self.jsonl_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
38
41
|
self.jsonl_file_path.touch()
|
|
39
42
|
except Exception as e:
|
|
40
|
-
|
|
43
|
+
logger.warning('Could not create JSONL file %s: %s', self.jsonl_file_path, e)
|
|
41
44
|
|
|
42
45
|
def capture_span(self, span: ReadableSpan) -> None:
|
|
43
46
|
"""Capture a span and write it to JSONL file."""
|
|
@@ -45,7 +48,7 @@ class JSONLSpanCapture:
|
|
|
45
48
|
span_data = self._convert_span_to_structured_format(span)
|
|
46
49
|
self._write_span_to_jsonl(span_data)
|
|
47
50
|
except Exception as e:
|
|
48
|
-
|
|
51
|
+
logger.warning('Error capturing span to JSONL: %s', e)
|
|
49
52
|
|
|
50
53
|
def _convert_span_to_structured_format(self, span: ReadableSpan) -> dict[str, Any]:
|
|
51
54
|
"""Convert ReadableSpan to structured format for JSONL export."""
|
|
@@ -156,7 +159,7 @@ class JSONLSpanCapture:
|
|
|
156
159
|
json.dump(span_data, f, ensure_ascii=False)
|
|
157
160
|
f.write('\n')
|
|
158
161
|
except Exception as e:
|
|
159
|
-
|
|
162
|
+
logger.warning('Error writing to JSONL file %s: %s', self.jsonl_file_path, e)
|
|
160
163
|
|
|
161
164
|
|
|
162
165
|
class JSONLSpanExporter(SpanExporter):
|
|
@@ -177,7 +180,7 @@ class JSONLSpanExporter(SpanExporter):
|
|
|
177
180
|
self.jsonl_capture.capture_span(span)
|
|
178
181
|
return SpanExportResult.SUCCESS
|
|
179
182
|
except Exception as e:
|
|
180
|
-
|
|
183
|
+
logger.warning('Error exporting spans to JSONL: %s', e)
|
|
181
184
|
return SpanExportResult.FAILURE
|
|
182
185
|
|
|
183
186
|
|
|
@@ -42,8 +42,8 @@ class _LanggraphJSONEncoder(json.JSONEncoder):
|
|
|
42
42
|
if hasattr(o, 'to_json'):
|
|
43
43
|
return o.to_json()
|
|
44
44
|
|
|
45
|
-
if isinstance(o, BaseModel) and hasattr(o, '
|
|
46
|
-
return o.
|
|
45
|
+
if isinstance(o, BaseModel) and hasattr(o, 'model_dump'):
|
|
46
|
+
return o.model_dump()
|
|
47
47
|
|
|
48
48
|
if isinstance(o, datetime.datetime):
|
|
49
49
|
return o.isoformat()
|
|
@@ -68,10 +68,6 @@ def _check_langgraph_version(
|
|
|
68
68
|
langgraph_version: pkg_version.Version,
|
|
69
69
|
) -> None:
|
|
70
70
|
"""Check if the installed LangGraph version is compatible with the version of fiddler-langgraph."""
|
|
71
|
-
|
|
72
|
-
if langgraph_version is None:
|
|
73
|
-
raise ImportError('Either langgraph or langchain_core should be installed')
|
|
74
|
-
|
|
75
71
|
# check compatibility range
|
|
76
72
|
min_langgraph_version = pkg_version.parse('0.3.28')
|
|
77
73
|
max_langgraph_version = pkg_version.parse('1.1.0')
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.3.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/tracing/instrumentation.py
RENAMED
|
File without changes
|
|
File without changes
|
{fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
{fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|