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.
Files changed (23) hide show
  1. {fiddler_langgraph-1.3.0/fiddler_langgraph.egg-info → fiddler_langgraph-1.3.1}/PKG-INFO +1 -1
  2. fiddler_langgraph-1.3.1/fiddler_langgraph/VERSION +1 -0
  3. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/core/client.py +107 -2
  4. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/core/span_processor.py +4 -1
  5. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/tracing/callback.py +1 -6
  6. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/tracing/jsonl_capture.py +7 -4
  7. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/tracing/util.py +2 -6
  8. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1/fiddler_langgraph.egg-info}/PKG-INFO +1 -1
  9. fiddler_langgraph-1.3.0/fiddler_langgraph/VERSION +0 -1
  10. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/MANIFEST.in +0 -0
  11. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/PUBLIC.md +0 -0
  12. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/README.md +0 -0
  13. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/__init__.py +0 -0
  14. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/core/__init__.py +0 -0
  15. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/core/attributes.py +0 -0
  16. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/tracing/__init__.py +0 -0
  17. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph/tracing/instrumentation.py +0 -0
  18. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph.egg-info/SOURCES.txt +0 -0
  19. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph.egg-info/dependency_links.txt +0 -0
  20. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph.egg-info/requires.txt +0 -0
  21. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/fiddler_langgraph.egg-info/top_level.txt +0 -0
  22. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/pyproject.toml +0 -0
  23. {fiddler_langgraph-1.3.0 → fiddler_langgraph-1.3.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fiddler-langgraph
3
- Version: 1.3.0
3
+ Version: 1.3.1
4
4
  Summary: Python SDK for instrumenting GenAI Applications with Fiddler
5
5
  Home-page: https://fiddler.ai
6
6
  Author: Fiddler AI
@@ -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
- # Fallback to initial resource if aggregation fails
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:
@@ -11,7 +11,10 @@ from fiddler_langgraph.core.attributes import (
11
11
 
12
12
 
13
13
  class FiddlerSpanProcessor(SpanProcessor):
14
- def on_start(self, span: Span, parent_context: context.Context | None = None):
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
 
@@ -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
- print(f'Warning: Could not create JSONL file {self.jsonl_file_path}: {e}')
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
- print(f'Error capturing span to JSONL: {e}')
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
- print(f'Error writing to JSONL file {self.jsonl_file_path}: {e}')
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
- print(f'Error exporting spans to JSONL: {e}')
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, 'model_dump_json'):
46
- return o.model_dump_json()
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fiddler-langgraph
3
- Version: 1.3.0
3
+ Version: 1.3.1
4
4
  Summary: Python SDK for instrumenting GenAI Applications with Fiddler
5
5
  Home-page: https://fiddler.ai
6
6
  Author: Fiddler AI
@@ -1 +0,0 @@
1
- 1.3.0