fiddler-langgraph 1.1.0__py3-none-any.whl → 1.3.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.
fiddler_langgraph/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.0
1
+ 1.3.0
@@ -34,6 +34,8 @@ class FiddlerSpanAttributes: # pylint: disable=too-few-public-methods
34
34
  LLM_TOKEN_COUNT_INPUT = 'gen_ai.usage.input_tokens'
35
35
  LLM_TOKEN_COUNT_OUTPUT = 'gen_ai.usage.output_tokens'
36
36
  LLM_TOKEN_COUNT_TOTAL = 'gen_ai.usage.total_tokens'
37
+ GEN_AI_INPUT_MESSAGES = 'gen_ai.input.messages'
38
+ GEN_AI_OUTPUT_MESSAGES = 'gen_ai.output.messages'
37
39
 
38
40
  # tool attributes
39
41
  TOOL_INPUT = 'gen_ai.tool.input'
@@ -1,6 +1,5 @@
1
1
  """Core client for Fiddler instrumentation."""
2
2
 
3
- import os
4
3
  import uuid
5
4
  from typing import Any
6
5
  from urllib.parse import urlparse
@@ -24,18 +23,6 @@ from fiddler_langgraph.core.attributes import FiddlerResourceAttributes
24
23
  from fiddler_langgraph.core.span_processor import FiddlerSpanProcessor
25
24
  from fiddler_langgraph.tracing.jsonl_capture import JSONLSpanExporter, initialize_jsonl_capture
26
25
 
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
26
 
40
27
  class FiddlerClient:
41
28
  """The main client for instrumenting Generative AI applications with Fiddler observability.
@@ -62,7 +49,7 @@ class FiddlerClient:
62
49
  application_id: str,
63
50
  url: str,
64
51
  console_tracer: bool = False,
65
- span_limits: SpanLimits | None = _default_span_limits,
52
+ span_limits: SpanLimits | None = None,
66
53
  sampler: sampling.Sampler | None = None,
67
54
  compression: Compression = Compression.Gzip,
68
55
  jsonl_capture_enabled: bool = False,
@@ -83,8 +70,18 @@ class FiddlerClient:
83
70
  instead of being sent to the Fiddler backend. Useful for debugging.
84
71
  Defaults to `False`.
85
72
  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 (32 events/links/attributes, 2048 char limit).
73
+ maximum number of attributes or events. When `None` (default), OpenTelemetry
74
+ automatically applies its standard defaults:
75
+
76
+ - `max_attributes`: 128 (or `OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT` env var)
77
+ - `max_events`: 128 (or `OTEL_SPAN_EVENT_COUNT_LIMIT` env var)
78
+ - `max_links`: 128 (or `OTEL_SPAN_LINK_COUNT_LIMIT` env var)
79
+ - `max_event_attributes`: 128 (or `OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT` env var)
80
+ - `max_link_attributes`: 128 (or `OTEL_LINK_ATTRIBUTE_COUNT_LIMIT` env var)
81
+ - `max_span_attribute_length`: None/unlimited (or `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var)
82
+
83
+ You can override these by passing a custom `SpanLimits` object (see example below)
84
+ or by setting the environment variables.
88
85
  sampler (sampling.Sampler | None): The sampler for deciding which spans to record.
89
86
  Defaults to `None`, which uses the parent-based always-on OpenTelemetry sampler
90
87
  (100% sampling).
@@ -120,11 +117,15 @@ class FiddlerClient:
120
117
  from opentelemetry.sdk.trace import SpanLimits, sampling
121
118
  from opentelemetry.exporter.otlp.proto.http.trace_exporter import Compression
122
119
 
120
+ # Example: add custom limits
123
121
  client = FiddlerClient(
124
122
  api_key='YOUR_API_KEY',
125
123
  application_id='YOUR_APPLICATION_ID',
126
124
  url='https://your-instance.fiddler.ai',
127
- span_limits=SpanLimits(max_span_attributes=64),
125
+ span_limits=SpanLimits(
126
+ max_span_attributes=64, # Reduce from default 128
127
+ max_span_attribute_length=2048, # Limit from default None (unlimited)
128
+ ),
128
129
  sampler=sampling.TraceIdRatioBased(0.1), # Sample 10% of traces
129
130
  compression=Compression.Gzip,
130
131
  )
@@ -304,13 +305,9 @@ class FiddlerClient:
304
305
  },
305
306
  compression=self.compression,
306
307
  )
307
- span_processor = BatchSpanProcessor(
308
- otlp_exporter,
309
- max_queue_size=int(os.environ.get('OTEL_BSP_MAX_QUEUE_SIZE', '100')),
310
- schedule_delay_millis=int(os.environ.get('OTEL_BSP_SCHEDULE_DELAY_MILLIS', '1000')),
311
- max_export_batch_size=int(os.environ.get('OTEL_BSP_MAX_EXPORT_BATCH_SIZE', '10')),
312
- export_timeout_millis=int(os.environ.get('OTEL_BSP_EXPORT_TIMEOUT', '5000')),
313
- )
308
+ # OpenTelemetry automatically applies defaults
309
+ # (OTEL_BSP_MAX_QUEUE_SIZE, OTEL_BSP_SCHEDULE_DELAY, OTEL_BSP_MAX_EXPORT_BATCH_SIZE, etc.)
310
+ span_processor = BatchSpanProcessor(otlp_exporter)
314
311
 
315
312
  self._provider.add_span_processor(span_processor)
316
313
 
@@ -9,7 +9,7 @@ from uuid import UUID
9
9
 
10
10
  from langchain_core.callbacks import BaseCallbackHandler
11
11
  from langchain_core.documents import Document
12
- from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage
12
+ from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage, ToolMessage
13
13
  from langchain_core.outputs import ChatGeneration, LLMResult
14
14
  from opentelemetry import trace
15
15
  from opentelemetry.context.context import Context
@@ -164,6 +164,68 @@ def _set_tool_definitions(span: trace.Span, kwargs: dict[str, Any]) -> None:
164
164
  logger.warning('Failed to extract tool definitions: %s', e)
165
165
 
166
166
 
167
+ def _convert_message_to_otel_format(message: BaseMessage) -> dict[str, Any]:
168
+ """Convert a LangChain message to OpenTelemetry format.
169
+
170
+ Parameters
171
+ ----------
172
+ message : BaseMessage
173
+ The LangChain message to convert
174
+
175
+ Returns
176
+ -------
177
+ dict[str, Any]
178
+ Message in OpenTelemetry format.
179
+
180
+ """
181
+ result: dict[str, Any] = {}
182
+
183
+ # Add OpenTelemetry role mapping
184
+ role_mapping = {'ai': 'assistant', 'human': 'user'}
185
+ result['role'] = role_mapping.get(message.type, message.type)
186
+
187
+ parts = []
188
+ content = _stringify_message_content(message)
189
+
190
+ # Handle ToolMessage separately
191
+ if isinstance(message, ToolMessage):
192
+ tool_response_part: dict[str, Any] = {
193
+ 'type': 'tool_call_response',
194
+ 'response': content,
195
+ 'id': message.tool_call_id,
196
+ }
197
+ parts = [tool_response_part]
198
+ else:
199
+ # Add text content if present
200
+ if content:
201
+ parts.append({'type': 'text', 'content': content})
202
+
203
+ # Add tool calls if present (for AIMessage)
204
+ if isinstance(message, AIMessage) and hasattr(message, 'tool_calls') and message.tool_calls:
205
+ for tool_call in message.tool_calls:
206
+ tool_call_part: dict[str, Any] = {
207
+ 'type': 'tool_call',
208
+ 'name': tool_call.get('name', ''),
209
+ }
210
+ if 'id' in tool_call:
211
+ tool_call_part['id'] = tool_call['id']
212
+ if 'args' in tool_call:
213
+ tool_call_part['arguments'] = tool_call['args']
214
+ parts.append(tool_call_part)
215
+
216
+ result['parts'] = parts
217
+
218
+ # Extract finish_reason
219
+ if isinstance(message, AIMessage) and hasattr(message, 'response_metadata'):
220
+ response_metadata = message.response_metadata
221
+ if response_metadata:
222
+ finish_reason = response_metadata.get('finish_reason')
223
+ if finish_reason:
224
+ result['finish_reason'] = finish_reason
225
+
226
+ return result
227
+
228
+
167
229
  class _CallbackHandler(BaseCallbackHandler):
168
230
  """A LangChain callback handler that creates OpenTelemetry spans for Fiddler.
169
231
 
@@ -423,6 +485,7 @@ class _CallbackHandler(BaseCallbackHandler):
423
485
  return
424
486
  child_span = self._create_child_span(parent_span, serialized.get('name', 'unknown'))
425
487
  span_input = json.dumps(inputs, cls=_LanggraphJSONEncoder) if inputs else input_str
488
+
426
489
  child_span.set_attribute(FiddlerSpanAttributes.TYPE, SpanType.TOOL)
427
490
  child_span.set_attribute(FiddlerSpanAttributes.TOOL_NAME, serialized.get('name', 'unknown'))
428
491
  child_span.set_attribute(FiddlerSpanAttributes.TOOL_INPUT, span_input)
@@ -454,7 +517,6 @@ class _CallbackHandler(BaseCallbackHandler):
454
517
  """
455
518
  span = self._get_span(run_id)
456
519
  if span:
457
- # limit the output to 100 characters for now - add formal limits later
458
520
  span.set_attribute(
459
521
  FiddlerSpanAttributes.TOOL_OUTPUT,
460
522
  json.dumps(output, cls=_LanggraphJSONEncoder),
@@ -649,14 +711,16 @@ class _CallbackHandler(BaseCallbackHandler):
649
711
 
650
712
  # chat models are a special case of LLMs with Structure Inputs (messages)
651
713
  # the ordering of messages is preserved over the lifecycle of an agent's invocation
652
- # we are ignoring AIMessage, ToolMessage, FunctionMessage & ChatMessage
653
- # see https://python.langchain.com/api_reference/core/messages.html#module-langchain_core.messages
654
714
  system_message = []
655
715
  user_message = []
716
+ message_history = []
717
+
656
718
  if messages and messages[0]:
657
719
  system_message = [m for m in messages[0] if isinstance(m, SystemMessage)]
658
720
  user_message = [m for m in messages[0] if isinstance(m, HumanMessage)]
659
721
 
722
+ message_history = list(messages[0])
723
+
660
724
  if metadata is not None:
661
725
  _set_agent_name(child_span, metadata)
662
726
 
@@ -684,6 +748,16 @@ class _CallbackHandler(BaseCallbackHandler):
684
748
  FiddlerSpanAttributes.LLM_INPUT_USER,
685
749
  user_content,
686
750
  )
751
+
752
+ # Add complete message history as a span attribute (GenAI semantic convention)
753
+ if message_history:
754
+ # Convert messages to OpenTelemetry format
755
+ otel_messages = [_convert_message_to_otel_format(msg) for msg in message_history]
756
+ child_span.set_attribute(
757
+ FiddlerSpanAttributes.GEN_AI_INPUT_MESSAGES,
758
+ json.dumps(otel_messages, cls=_LanggraphJSONEncoder),
759
+ )
760
+
687
761
  self._set_session_id(child_span)
688
762
  self._add_span(child_span, run_id)
689
763
 
@@ -771,6 +845,8 @@ class _CallbackHandler(BaseCallbackHandler):
771
845
  # we always get only one element in the list - even with batch mode
772
846
  # Add safety checks to prevent index errors
773
847
  output = ''
848
+ output_message_dict = None
849
+
774
850
  if (
775
851
  response.generations
776
852
  and len(response.generations) > 0
@@ -778,22 +854,41 @@ class _CallbackHandler(BaseCallbackHandler):
778
854
  and len(response.generations[0]) > 0
779
855
  ):
780
856
  generation = response.generations[0][0]
857
+
781
858
  output = generation.text
782
- if (
783
- output == ''
784
- and isinstance(generation, ChatGeneration)
785
- and isinstance(generation.message, AIMessage)
786
- and hasattr(generation.message, 'tool_calls')
859
+
860
+ # Check if this is a ChatGeneration with an AIMessage
861
+ if isinstance(generation, ChatGeneration) and isinstance(
862
+ generation.message, AIMessage
787
863
  ):
788
- # if llm returns an empty string, it means it used a tool
789
- # we are using the tool calls to get the output
790
- output = json.dumps(generation.message.tool_calls, cls=_LanggraphJSONEncoder)
864
+ # Use the complete output message
865
+ output_message_dict = generation.message
866
+
867
+ if (
868
+ output == ''
869
+ and hasattr(generation.message, 'tool_calls')
870
+ and generation.message.tool_calls
871
+ ):
872
+ # if llm returns an empty string, it means it used a tool
873
+ # we are using the tool calls to get the output
874
+ output = json.dumps(
875
+ generation.message.tool_calls, cls=_LanggraphJSONEncoder
876
+ )
791
877
 
792
878
  span.set_attribute(FiddlerSpanAttributes.LLM_OUTPUT, output)
793
879
 
794
880
  # Extract and set token usage information
795
881
  _set_token_usage_attributes(span, response)
796
882
 
883
+ # Add output message as a span attribute
884
+ if output_message_dict:
885
+ # Convert message to OpenTelemetry format
886
+ otel_message = _convert_message_to_otel_format(output_message_dict)
887
+ span.set_attribute(
888
+ FiddlerSpanAttributes.GEN_AI_OUTPUT_MESSAGES,
889
+ json.dumps([otel_message], cls=_LanggraphJSONEncoder),
890
+ )
891
+
797
892
  span.end()
798
893
  self._remove_span(run_id)
799
894
  else:
@@ -98,6 +98,12 @@ class JSONLSpanCapture:
98
98
  span_data['llm_input_user'] = attributes.get(FiddlerSpanAttributes.LLM_INPUT_USER, '')
99
99
  span_data['llm_output'] = attributes.get(FiddlerSpanAttributes.LLM_OUTPUT, '')
100
100
  span_data['llm_context'] = attributes.get(FiddlerSpanAttributes.LLM_CONTEXT, '')
101
+ span_data['gen_ai_input_messages'] = attributes.get(
102
+ FiddlerSpanAttributes.GEN_AI_INPUT_MESSAGES, ''
103
+ )
104
+ span_data['gen_ai_output_messages'] = attributes.get(
105
+ FiddlerSpanAttributes.GEN_AI_OUTPUT_MESSAGES, ''
106
+ )
101
107
 
102
108
  # Tool information
103
109
  span_data['tool_name'] = attributes.get(FiddlerSpanAttributes.TOOL_NAME, '')
@@ -125,6 +131,7 @@ class JSONLSpanCapture:
125
131
 
126
132
  # Exception information
127
133
  exception_info = []
134
+
128
135
  if hasattr(span, 'events') and span.events:
129
136
  for event in span.events:
130
137
  if event.name == 'exception':
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fiddler-langgraph
3
- Version: 1.1.0
3
+ Version: 1.3.0
4
4
  Summary: Python SDK for instrumenting GenAI Applications with Fiddler
5
5
  Author-email: Fiddler AI <support@fiddler.ai>
6
6
  License-Expression: Apache-2.0
@@ -23,10 +23,10 @@ Classifier: Operating System :: OS Independent
23
23
  Requires-Python: >=3.10
24
24
  Description-Content-Type: text/markdown
25
25
  Requires-Dist: pip>=21.0
26
- Requires-Dist: opentelemetry-api<=1.35.0,>=1.19.0
27
- Requires-Dist: opentelemetry-sdk<=1.35.0,>=1.19.0
28
- Requires-Dist: opentelemetry-instrumentation<=0.56b0,>=0.40b0
29
- Requires-Dist: opentelemetry-exporter-otlp-proto-http<=1.35.0,>=1.19.0
26
+ Requires-Dist: opentelemetry-api<=1.39.1,>=1.19.0
27
+ Requires-Dist: opentelemetry-sdk<=1.39.1,>=1.19.0
28
+ Requires-Dist: opentelemetry-instrumentation<=0.60b1,>=0.40b0
29
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http<=1.39.1,>=1.19.0
30
30
  Requires-Dist: pydantic>=2.0
31
31
  Provides-Extra: dev
32
32
  Requires-Dist: pytest>=8.3.5; extra == "dev"
@@ -0,0 +1,15 @@
1
+ fiddler_langgraph/VERSION,sha256=ZNI_hY71Gw-Zbklm1OJ8A3G0N-LSeHiQsfetItTsVmM,6
2
+ fiddler_langgraph/__init__.py,sha256=cqomWAmuY-2KvwJTvo7c7cecCPoe31pv4vgyBk_E8oQ,315
3
+ fiddler_langgraph/core/__init__.py,sha256=HXPZt8YpmVrvwEEukoWR78LufMKtl7lVjLtcl9UNSoc,42
4
+ fiddler_langgraph/core/attributes.py,sha256=Syfj75dJI3ra0xkeEEw9zRQr4MVHiSxfjI2kMe-9FxY,2324
5
+ fiddler_langgraph/core/client.py,sha256=NrkigvlyN21VFBUB9KvWLDE-GM9N9sNG86gygDeob2w,15155
6
+ fiddler_langgraph/core/span_processor.py,sha256=ODYmdo0FUYEFbIWS_VaR9L6qHUVvpnuk-RSIhgRxyb0,1164
7
+ fiddler_langgraph/tracing/__init__.py,sha256=Kw8VUB7RDffBq4ss0v6vNQYi4KDQOM0J1elbMrqJpsU,49
8
+ fiddler_langgraph/tracing/callback.py,sha256=uUs2hKnPmIZJaUbjT49wiD8RaWyDII3UT6gy43uSlYA,35529
9
+ fiddler_langgraph/tracing/instrumentation.py,sha256=AlCM9GWp3qN_Fa9cl-USJjrUhuvKmrLokzkLMgah-CY,21142
10
+ fiddler_langgraph/tracing/jsonl_capture.py,sha256=dJl09h9J7zGpASwr6CzjVVFuBQs2DDF2yc4Hu7aDlxQ,8538
11
+ fiddler_langgraph/tracing/util.py,sha256=RKMrrmdCXSRJrTIHngdhRsotPLEY_LR1SKnUXAJC40Y,2678
12
+ fiddler_langgraph-1.3.0.dist-info/METADATA,sha256=5zyyUOVUTlB5TzBPJo--S0mdOhOG3C53-ss3vH_xpf8,10424
13
+ fiddler_langgraph-1.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ fiddler_langgraph-1.3.0.dist-info/top_level.txt,sha256=hOKdR6_3AkS4dS6EfE9Ii7YrS_hApnyGfY-0v0DV0s4,18
15
+ fiddler_langgraph-1.3.0.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- fiddler_langgraph/VERSION,sha256=FXXhr0qV8S9wtO5qatzoFglT2T6hfcJhG5CIPMw607g,6
2
- fiddler_langgraph/__init__.py,sha256=cqomWAmuY-2KvwJTvo7c7cecCPoe31pv4vgyBk_E8oQ,315
3
- fiddler_langgraph/core/__init__.py,sha256=HXPZt8YpmVrvwEEukoWR78LufMKtl7lVjLtcl9UNSoc,42
4
- fiddler_langgraph/core/attributes.py,sha256=kVu2F7XcdOA6216owWUrb49tps0DXAgM-DhNNgkk0E8,2218
5
- fiddler_langgraph/core/client.py,sha256=Fn4sPiTfAF1i375Qg_9DYwD0wAT65K6j3M9luyTVKPE,14948
6
- fiddler_langgraph/core/span_processor.py,sha256=ODYmdo0FUYEFbIWS_VaR9L6qHUVvpnuk-RSIhgRxyb0,1164
7
- fiddler_langgraph/tracing/__init__.py,sha256=Kw8VUB7RDffBq4ss0v6vNQYi4KDQOM0J1elbMrqJpsU,49
8
- fiddler_langgraph/tracing/callback.py,sha256=h75rSCl4XLNAScDt0Bh6JmluYxw7_-GQbF6FWok5Xxc,32428
9
- fiddler_langgraph/tracing/instrumentation.py,sha256=AlCM9GWp3qN_Fa9cl-USJjrUhuvKmrLokzkLMgah-CY,21142
10
- fiddler_langgraph/tracing/jsonl_capture.py,sha256=ju5JHGZKjUsRduAjlJMjHj_jQyLP1Tr9jzRR0j_Yatc,8273
11
- fiddler_langgraph/tracing/util.py,sha256=RKMrrmdCXSRJrTIHngdhRsotPLEY_LR1SKnUXAJC40Y,2678
12
- fiddler_langgraph-1.1.0.dist-info/METADATA,sha256=b1ad_wYbBcEugKM3c1fkYEGtLI-WHqxA0diq4j66QwM,10424
13
- fiddler_langgraph-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- fiddler_langgraph-1.1.0.dist-info/top_level.txt,sha256=hOKdR6_3AkS4dS6EfE9Ii7YrS_hApnyGfY-0v0DV0s4,18
15
- fiddler_langgraph-1.1.0.dist-info/RECORD,,