fiddler-langgraph 0.1.0rc1__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.
- fiddler_langgraph/VERSION +1 -0
- fiddler_langgraph/__init__.py +11 -0
- fiddler_langgraph/core/__init__.py +1 -0
- fiddler_langgraph/core/attributes.py +87 -0
- fiddler_langgraph/core/client.py +318 -0
- fiddler_langgraph/core/span_processor.py +31 -0
- fiddler_langgraph/tracing/__init__.py +1 -0
- fiddler_langgraph/tracing/callback.py +795 -0
- fiddler_langgraph/tracing/instrumentation.py +264 -0
- fiddler_langgraph/tracing/jsonl_capture.py +185 -0
- fiddler_langgraph/tracing/util.py +83 -0
- fiddler_langgraph-0.1.0rc1.dist-info/METADATA +323 -0
- fiddler_langgraph-0.1.0rc1.dist-info/RECORD +15 -0
- fiddler_langgraph-0.1.0rc1.dist-info/WHEEL +5 -0
- fiddler_langgraph-0.1.0rc1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.1.0rc1
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Fiddler SDK for instrumenting GenAI Applications."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from fiddler_langgraph.core.client import FiddlerClient
|
|
6
|
+
|
|
7
|
+
# Read version from VERSION file
|
|
8
|
+
_version_file = Path(__file__).parent / 'VERSION'
|
|
9
|
+
__version__ = _version_file.read_text().strip()
|
|
10
|
+
|
|
11
|
+
__all__ = ['FiddlerClient', '__version__']
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core functionality for Fiddler SDK."""
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""OpenTelemetry span attributes for Fiddler instrumentation."""
|
|
2
|
+
|
|
3
|
+
import contextvars
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pydantic import ConfigDict, validate_call
|
|
7
|
+
|
|
8
|
+
# Key used for storing Fiddler-specific attributes in metadata dictionary
|
|
9
|
+
FIDDLER_METADATA_KEY = '_fiddler_attributes'
|
|
10
|
+
|
|
11
|
+
# Template strings for OpenTelemetry attribute key formatting
|
|
12
|
+
FIDDLER_USER_SPAN_ATTRIBUTE_TEMPLATE = 'fiddler.span.user.{key}'
|
|
13
|
+
FIDDLER_USER_SESSION_ATTRIBUTE_TEMPLATE = 'fiddler.session.user.{key}'
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FiddlerSpanAttributes: # pylint: disable=too-few-public-methods
|
|
17
|
+
"""Constants for Fiddler OpenTelemetry span attributes."""
|
|
18
|
+
|
|
19
|
+
# common attributes
|
|
20
|
+
AGENT_NAME = 'gen_ai.agent.name'
|
|
21
|
+
AGENT_ID = 'gen_ai.agent.id'
|
|
22
|
+
CONVERSATION_ID = 'gen_ai.conversation.id'
|
|
23
|
+
TYPE = 'fiddler.span.type'
|
|
24
|
+
|
|
25
|
+
# LLM attributes
|
|
26
|
+
LLM_INPUT_SYSTEM = 'gen_ai.llm.input.system'
|
|
27
|
+
LLM_INPUT_USER = 'gen_ai.llm.input.user'
|
|
28
|
+
LLM_OUTPUT = 'gen_ai.llm.output'
|
|
29
|
+
LLM_CONTEXT = 'gen_ai.llm.context'
|
|
30
|
+
|
|
31
|
+
# Model attributes - following OpenTelemetry semantic conventions
|
|
32
|
+
LLM_REQUEST_MODEL = 'gen_ai.request.model'
|
|
33
|
+
LLM_SYSTEM = 'gen_ai.system'
|
|
34
|
+
|
|
35
|
+
# Token usage attributes
|
|
36
|
+
LLM_TOKEN_COUNT_INPUT = 'gen_ai.usage.input_tokens'
|
|
37
|
+
LLM_TOKEN_COUNT_OUTPUT = 'gen_ai.usage.output_tokens'
|
|
38
|
+
LLM_TOKEN_COUNT_TOTAL = 'gen_ai.usage.total_tokens'
|
|
39
|
+
|
|
40
|
+
# tool attributes
|
|
41
|
+
TOOL_INPUT = 'gen_ai.tool.input'
|
|
42
|
+
TOOL_OUTPUT = 'gen_ai.tool.output'
|
|
43
|
+
TOOL_NAME = 'gen_ai.tool.name'
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class FiddlerResourceAttributes:
|
|
47
|
+
"""Constants for Fiddler OpenTelemetry resource attributes."""
|
|
48
|
+
|
|
49
|
+
APPLICATION_ID = 'application.id'
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class SpanType:
|
|
53
|
+
"""Constants for Fiddler OpenTelemetry span types."""
|
|
54
|
+
|
|
55
|
+
CHAIN = 'chain'
|
|
56
|
+
TOOL = 'tool'
|
|
57
|
+
LLM = 'llm'
|
|
58
|
+
OTHER = 'other'
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# context variable for conversation ID - used to store the conversation ID for the current thread/async coroutine
|
|
62
|
+
# note that contextvars are shallow copied, dictionaries/lists are not copied deeply and are shared between threads/coroutines
|
|
63
|
+
_CONVERSATION_ID: contextvars.ContextVar[str] = contextvars.ContextVar(
|
|
64
|
+
'_CONVERSATION_ID', default=''
|
|
65
|
+
)
|
|
66
|
+
_CUSTOM_ATTRIBUTES: contextvars.ContextVar[dict[str, Any]] = contextvars.ContextVar(
|
|
67
|
+
'_CUSTOM_ATTRIBUTES'
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@validate_call(config=ConfigDict(strict=True, arbitrary_types_allowed=True))
|
|
72
|
+
def add_session_attributes(key: str, value: str) -> None:
|
|
73
|
+
"""Adds Fiddler-specific attributes to a runnable's metadata.
|
|
74
|
+
|
|
75
|
+
This is used for various runnable types like Pregel nodes, LLM calls, tool
|
|
76
|
+
calls, and retriever calls.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
key (str): The attribute key to add or update.
|
|
80
|
+
value (str): The attribute value to set.
|
|
81
|
+
"""
|
|
82
|
+
try:
|
|
83
|
+
current_attributes = _CUSTOM_ATTRIBUTES.get().copy()
|
|
84
|
+
except LookupError:
|
|
85
|
+
current_attributes = {}
|
|
86
|
+
current_attributes[key] = value
|
|
87
|
+
_CUSTOM_ATTRIBUTES.set(current_attributes)
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"""Core client for Fiddler instrumentation."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import uuid
|
|
5
|
+
from typing import Any
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
from opentelemetry import trace
|
|
9
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import Compression, OTLPSpanExporter
|
|
10
|
+
from opentelemetry.sdk.resources import (
|
|
11
|
+
OTELResourceDetector,
|
|
12
|
+
ProcessResourceDetector,
|
|
13
|
+
Resource,
|
|
14
|
+
get_aggregated_resources,
|
|
15
|
+
)
|
|
16
|
+
from opentelemetry.sdk.trace import SpanLimits, TracerProvider, sampling
|
|
17
|
+
from opentelemetry.sdk.trace.export import (
|
|
18
|
+
BatchSpanProcessor,
|
|
19
|
+
ConsoleSpanExporter,
|
|
20
|
+
SimpleSpanProcessor,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from fiddler_langgraph.core.attributes import FiddlerResourceAttributes
|
|
24
|
+
from fiddler_langgraph.core.span_processor import FiddlerSpanProcessor
|
|
25
|
+
from fiddler_langgraph.tracing.jsonl_capture import JSONLSpanExporter, initialize_jsonl_capture
|
|
26
|
+
|
|
27
|
+
# Defaults are too permissive.
|
|
28
|
+
# Set restrictive defaults for span limits - can be overridden by the user
|
|
29
|
+
# See https://github.com/open-telemetry/opentelemetry-python/blob/main/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py
|
|
30
|
+
_default_span_limits = SpanLimits(
|
|
31
|
+
max_events=32,
|
|
32
|
+
max_links=32,
|
|
33
|
+
max_span_attributes=32,
|
|
34
|
+
max_event_attributes=32,
|
|
35
|
+
max_link_attributes=32,
|
|
36
|
+
max_span_attribute_length=2048,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class FiddlerClient:
|
|
41
|
+
"""The main client for instrumenting Generative AI applications with Fiddler observability.
|
|
42
|
+
|
|
43
|
+
This client configures and manages the OpenTelemetry tracer that sends telemetry data
|
|
44
|
+
to the Fiddler platform for monitoring, analysis, and debugging of your AI agents
|
|
45
|
+
and workflows.
|
|
46
|
+
|
|
47
|
+
Attributes:
|
|
48
|
+
application_id (str): The UUID4 identifier for the application.
|
|
49
|
+
url (str): The Fiddler backend URL.
|
|
50
|
+
api_key (str): The API key for Fiddler.
|
|
51
|
+
resource (Resource): The OpenTelemetry resource for the client.
|
|
52
|
+
span_limits (SpanLimits | None): OpenTelemetry span limits configuration.
|
|
53
|
+
sampler (sampling.Sampler | None): OpenTelemetry sampling configuration.
|
|
54
|
+
compression (Compression): OTLP export compression type.
|
|
55
|
+
jsonl_capture_enabled (bool): Whether JSONL capture is enabled.
|
|
56
|
+
jsonl_file_path (str): Path to the JSONL file for trace data capture.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
api_key: str,
|
|
62
|
+
application_id: str,
|
|
63
|
+
url: str = 'http://localhost:4318',
|
|
64
|
+
console_tracer: bool = False,
|
|
65
|
+
span_limits: SpanLimits | None = _default_span_limits,
|
|
66
|
+
sampler: sampling.Sampler | None = None,
|
|
67
|
+
compression: Compression = Compression.Gzip,
|
|
68
|
+
jsonl_capture_enabled: bool = False,
|
|
69
|
+
jsonl_file_path: str = 'fiddler_trace_data.jsonl',
|
|
70
|
+
):
|
|
71
|
+
"""Initializes the FiddlerClient.
|
|
72
|
+
|
|
73
|
+
This sets up the configuration for the OpenTelemetry tracer that will
|
|
74
|
+
be used to send data to Fiddler.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
api_key (str): The API key for authenticating with the Fiddler backend. **Required**.
|
|
78
|
+
application_id (str): The unique identifier (UUID4) for the application. **Required**.
|
|
79
|
+
url (str): The base URL for the Fiddler backend. While it defaults to
|
|
80
|
+
`http://localhost:4318` for local development, this **must** be set to your
|
|
81
|
+
Fiddler instance URL for any other use.
|
|
82
|
+
console_tracer (bool): If True, traces will be printed to the console
|
|
83
|
+
instead of being sent to the Fiddler backend. Useful for debugging.
|
|
84
|
+
Defaults to `False`.
|
|
85
|
+
span_limits (SpanLimits | None): Configuration for span limits, such as the
|
|
86
|
+
maximum number of attributes or events. Defaults to a restrictive
|
|
87
|
+
set of internal limits.
|
|
88
|
+
sampler (sampling.Sampler | None): The sampler for deciding which spans to record.
|
|
89
|
+
Defaults to `None`, which uses the parent-based OpenTelemetry sampler.
|
|
90
|
+
compression (Compression): The compression for exporting traces.
|
|
91
|
+
Can be `Compression.Gzip` or `Compression.NoCompression`.
|
|
92
|
+
Defaults to `Compression.Gzip`.
|
|
93
|
+
jsonl_capture_enabled (bool): Whether to enable JSONL capture of trace data.
|
|
94
|
+
When enabled, all span data will be captured and saved to a JSONL file
|
|
95
|
+
in OpenTelemetry format for analysis. Defaults to `False`.
|
|
96
|
+
jsonl_file_path (str): Path to the JSONL file where trace data will be saved.
|
|
97
|
+
Only used when `jsonl_capture_enabled` is `True`. Defaults to
|
|
98
|
+
"fiddler_trace_data.jsonl".
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
ValueError: If `application_id` is not a valid UUID4 or if the
|
|
102
|
+
`url` is not a valid HTTPS URL.
|
|
103
|
+
|
|
104
|
+
Examples:
|
|
105
|
+
>>> from opentelemetry.sdk.trace import SpanLimits
|
|
106
|
+
>>> from fiddler_langgraph import FiddlerClient
|
|
107
|
+
>>>
|
|
108
|
+
>>> client = FiddlerClient(
|
|
109
|
+
... api_key='YOUR_API_KEY',
|
|
110
|
+
... application_id='YOUR_APPLICATION_ID',
|
|
111
|
+
... url='https://your-fiddler-instance.fiddler.ai',
|
|
112
|
+
... span_limits=SpanLimits(max_span_attributes=64),
|
|
113
|
+
... )
|
|
114
|
+
"""
|
|
115
|
+
# Validate application_id is a valid UUID4
|
|
116
|
+
|
|
117
|
+
parsed_uuid = uuid.UUID(application_id)
|
|
118
|
+
if parsed_uuid.version != 4:
|
|
119
|
+
raise ValueError(
|
|
120
|
+
f'application_id must be a valid UUID4 (version 4), got version {parsed_uuid.version}'
|
|
121
|
+
)
|
|
122
|
+
# Store the validated UUID as a string
|
|
123
|
+
self.application_id = str(parsed_uuid)
|
|
124
|
+
|
|
125
|
+
# Validate URL is a valid URL format
|
|
126
|
+
parsed_url = urlparse(url)
|
|
127
|
+
if not parsed_url.scheme or not parsed_url.netloc:
|
|
128
|
+
raise ValueError('URL must have a valid scheme and netloc')
|
|
129
|
+
if parsed_url.scheme not in ('http', 'https'):
|
|
130
|
+
raise ValueError('URL scheme must be http or https')
|
|
131
|
+
self.url = url.rstrip('/')
|
|
132
|
+
|
|
133
|
+
self.api_key = api_key
|
|
134
|
+
|
|
135
|
+
# fiddler sdk must have its own tracer provider and tracer
|
|
136
|
+
# so we can have a separate configuration for the tracer provider than the global one.
|
|
137
|
+
# Additionally, other otel libraries maybe active who may override configs of the global tracer provider.
|
|
138
|
+
# we will initialize the provider and tracer when get_tracer is called
|
|
139
|
+
# we need to wait for any resources to be set before initializing the provider
|
|
140
|
+
# and tracer
|
|
141
|
+
self._provider: TracerProvider | None = None
|
|
142
|
+
self._tracer: trace.Tracer | None = None
|
|
143
|
+
self._console_tracer = console_tracer
|
|
144
|
+
|
|
145
|
+
self.span_limits = span_limits
|
|
146
|
+
self.sampler = sampler
|
|
147
|
+
self.compression = compression
|
|
148
|
+
self.jsonl_capture_enabled = jsonl_capture_enabled
|
|
149
|
+
self.jsonl_file_path = jsonl_file_path
|
|
150
|
+
|
|
151
|
+
# Create OpenTelemetry resource with service information
|
|
152
|
+
# we will update the resource with any additional attributes later
|
|
153
|
+
resource = Resource.create({FiddlerResourceAttributes.APPLICATION_ID: self.application_id})
|
|
154
|
+
self.resource = self._get_aggregated_resources_with_fallback(resource)
|
|
155
|
+
|
|
156
|
+
def get_tracer_provider(self) -> TracerProvider:
|
|
157
|
+
"""Gets the OpenTelemetry TracerProvider instance.
|
|
158
|
+
|
|
159
|
+
Initializes the provider on the first call.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
TracerProvider: The configured OpenTelemetry TracerProvider.
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
RuntimeError: If tracer provider initialization fails.
|
|
166
|
+
"""
|
|
167
|
+
if self._provider is None:
|
|
168
|
+
self._initialize_provider()
|
|
169
|
+
if self._provider is None:
|
|
170
|
+
raise RuntimeError('Failed to initialize tracer provider')
|
|
171
|
+
return self._provider
|
|
172
|
+
|
|
173
|
+
def _get_aggregated_resources_with_fallback(self, initial_resource: Resource) -> Resource:
|
|
174
|
+
"""Gets aggregated resources with a fallback for different OpenTelemetry versions.
|
|
175
|
+
|
|
176
|
+
This method tries to use `get_aggregated_resources` and dynamically imports
|
|
177
|
+
`OsResourceDetector` if available. It falls back to the initial resource if
|
|
178
|
+
aggregation fails.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
initial_resource (Resource): The initial resource to start with.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Resource: The aggregated resource.
|
|
185
|
+
"""
|
|
186
|
+
detectors = [OTELResourceDetector(), ProcessResourceDetector()]
|
|
187
|
+
|
|
188
|
+
# Try to add OsResourceDetector if available (OpenTelemetry >= 1.19)
|
|
189
|
+
try:
|
|
190
|
+
from opentelemetry.sdk.resources import OsResourceDetector
|
|
191
|
+
|
|
192
|
+
detectors.append(OsResourceDetector())
|
|
193
|
+
except ImportError:
|
|
194
|
+
# OsResourceDetector not available in this version, skip it
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
return get_aggregated_resources(detectors, initial_resource=initial_resource)
|
|
199
|
+
except Exception:
|
|
200
|
+
# Fallback to initial resource if aggregation fails
|
|
201
|
+
return initial_resource
|
|
202
|
+
|
|
203
|
+
def update_resource(self, attributes: dict[str, Any]) -> None:
|
|
204
|
+
"""Updates the OpenTelemetry resource with additional attributes.
|
|
205
|
+
|
|
206
|
+
Use this to add metadata that applies to all spans, such as version numbers
|
|
207
|
+
or environment names.
|
|
208
|
+
|
|
209
|
+
> [!IMPORTANT]
|
|
210
|
+
> Must be called before `get_tracer()` is invoked.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
attributes (dict[str, Any]): Key-value pairs to add to the resource. **Required**.
|
|
214
|
+
|
|
215
|
+
Raises:
|
|
216
|
+
ValueError: If the tracer has already been initialized.
|
|
217
|
+
|
|
218
|
+
Examples:
|
|
219
|
+
>>> from fiddler_langgraph import FiddlerClient
|
|
220
|
+
>>> client = FiddlerClient(api_key='...', application_id='...')
|
|
221
|
+
>>> client.update_resource({'service.version': '1.2.3'})
|
|
222
|
+
"""
|
|
223
|
+
if self._tracer is not None:
|
|
224
|
+
raise ValueError('Cannot update resource after tracer is initialized')
|
|
225
|
+
|
|
226
|
+
if (
|
|
227
|
+
self.resource.attributes.get('service.name', '') != 'unknown_service'
|
|
228
|
+
and attributes.get('service.name') is None
|
|
229
|
+
):
|
|
230
|
+
# service.name defaults to unknown_service in a new resource. When merging, the new resource will override the old one.
|
|
231
|
+
# so we need to keep the old service.name if it exists.
|
|
232
|
+
attributes['service.name'] = self.resource.attributes['service.name']
|
|
233
|
+
|
|
234
|
+
self.resource = self.resource.merge(Resource.create(attributes))
|
|
235
|
+
|
|
236
|
+
def _initialize_provider(self) -> None:
|
|
237
|
+
"""Initializes the tracer provider.
|
|
238
|
+
|
|
239
|
+
We are not using the default tracer provider because we want to have a
|
|
240
|
+
separate configuration for the tracer provider than the global one.
|
|
241
|
+
Additionally, other OTEL libraries may be active and override configs
|
|
242
|
+
of the global tracer provider.
|
|
243
|
+
"""
|
|
244
|
+
if self._provider is not None:
|
|
245
|
+
return
|
|
246
|
+
|
|
247
|
+
self._provider = TracerProvider(
|
|
248
|
+
resource=self.resource,
|
|
249
|
+
span_limits=self.span_limits,
|
|
250
|
+
sampler=self.sampler,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
def _initialize_tracer(self) -> None:
|
|
254
|
+
"""Initializes the OpenTelemetry tracer and registers span processors."""
|
|
255
|
+
if self._tracer is not None:
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
# Ensure provider is initialized
|
|
259
|
+
self._initialize_provider()
|
|
260
|
+
assert self._provider is not None # Type guard for mypy
|
|
261
|
+
|
|
262
|
+
# processors are executed in order, so we add the FiddlerSpanProcessor first
|
|
263
|
+
# so that it can inject the session ID and custom attributes into the spans
|
|
264
|
+
self._provider.add_span_processor(FiddlerSpanProcessor())
|
|
265
|
+
|
|
266
|
+
if self._console_tracer:
|
|
267
|
+
self._provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
|
|
268
|
+
|
|
269
|
+
otlp_exporter = OTLPSpanExporter(
|
|
270
|
+
endpoint=f'{self.url}/v1/traces',
|
|
271
|
+
headers={
|
|
272
|
+
'authorization': f'Bearer {self.api_key}',
|
|
273
|
+
'fiddler-application-id': self.application_id,
|
|
274
|
+
},
|
|
275
|
+
compression=self.compression,
|
|
276
|
+
)
|
|
277
|
+
span_processor = BatchSpanProcessor(
|
|
278
|
+
otlp_exporter,
|
|
279
|
+
max_queue_size=int(os.environ.get('OTEL_BSP_MAX_QUEUE_SIZE', '100')),
|
|
280
|
+
schedule_delay_millis=int(os.environ.get('OTEL_BSP_SCHEDULE_DELAY_MILLIS', '1000')),
|
|
281
|
+
max_export_batch_size=int(os.environ.get('OTEL_BSP_MAX_EXPORT_BATCH_SIZE', '10')),
|
|
282
|
+
export_timeout_millis=int(os.environ.get('OTEL_BSP_EXPORT_TIMEOUT', '5000')),
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
self._provider.add_span_processor(span_processor)
|
|
286
|
+
|
|
287
|
+
# Add JSONL capture if enabled
|
|
288
|
+
if self.jsonl_capture_enabled:
|
|
289
|
+
jsonl_capture = initialize_jsonl_capture(self.jsonl_file_path)
|
|
290
|
+
jsonl_exporter = JSONLSpanExporter(jsonl_capture)
|
|
291
|
+
self._provider.add_span_processor(SimpleSpanProcessor(jsonl_exporter))
|
|
292
|
+
|
|
293
|
+
self._tracer = trace.get_tracer('fiddler.langgraph.tracer', tracer_provider=self._provider)
|
|
294
|
+
|
|
295
|
+
def get_tracer(self) -> trace.Tracer:
|
|
296
|
+
"""Returns an OpenTelemetry tracer instance for creating spans.
|
|
297
|
+
|
|
298
|
+
Initializes the tracer on the first call. This is the primary method
|
|
299
|
+
for developers to get a tracer for custom instrumentation.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
trace.Tracer: OpenTelemetry tracer instance.
|
|
303
|
+
|
|
304
|
+
Raises:
|
|
305
|
+
RuntimeError: If tracer initialization fails.
|
|
306
|
+
|
|
307
|
+
Examples:
|
|
308
|
+
>>> from fiddler_langgraph import FiddlerClient
|
|
309
|
+
>>> client = FiddlerClient(api_key='...', application_id='...')
|
|
310
|
+
>>> tracer = client.get_tracer()
|
|
311
|
+
>>> with tracer.start_as_current_span('my-operation'):
|
|
312
|
+
... print('Doing some work...')
|
|
313
|
+
"""
|
|
314
|
+
if self._tracer is None:
|
|
315
|
+
self._initialize_tracer()
|
|
316
|
+
if self._tracer is None:
|
|
317
|
+
raise RuntimeError('Failed to initialize tracer')
|
|
318
|
+
return self._tracer
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from opentelemetry import context
|
|
2
|
+
from opentelemetry.sdk.trace import SpanProcessor
|
|
3
|
+
from opentelemetry.trace import Span
|
|
4
|
+
|
|
5
|
+
from fiddler_langgraph.core.attributes import (
|
|
6
|
+
_CONVERSATION_ID,
|
|
7
|
+
_CUSTOM_ATTRIBUTES,
|
|
8
|
+
FIDDLER_USER_SESSION_ATTRIBUTE_TEMPLATE,
|
|
9
|
+
FiddlerSpanAttributes,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FiddlerSpanProcessor(SpanProcessor):
|
|
14
|
+
def on_start(self, span: Span, parent_context: context.Context | None = None):
|
|
15
|
+
# inject custom attributes
|
|
16
|
+
try:
|
|
17
|
+
custom_attributes = _CUSTOM_ATTRIBUTES.get().copy()
|
|
18
|
+
except LookupError:
|
|
19
|
+
# LookupError is raised if the contextvar is not set
|
|
20
|
+
custom_attributes = {}
|
|
21
|
+
if custom_attributes:
|
|
22
|
+
for key, value in custom_attributes.items():
|
|
23
|
+
# prefix the key with fiddler.session.
|
|
24
|
+
# fdl_key = f'fiddler.session.{key}'
|
|
25
|
+
fdl_key = FIDDLER_USER_SESSION_ATTRIBUTE_TEMPLATE.format(key=key)
|
|
26
|
+
span.set_attribute(fdl_key, value)
|
|
27
|
+
|
|
28
|
+
# inject session id
|
|
29
|
+
session_id = _CONVERSATION_ID.get()
|
|
30
|
+
if session_id:
|
|
31
|
+
span.set_attribute(FiddlerSpanAttributes.CONVERSATION_ID, session_id)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""LangGraph instrumentation for Fiddler SDK."""
|