openlit 1.34.26__py3-none-any.whl → 1.34.28__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.
Files changed (37) hide show
  1. openlit/__helpers.py +38 -0
  2. openlit/__init__.py +22 -155
  3. openlit/_instrumentors.py +144 -0
  4. openlit/guard/all.py +3 -3
  5. openlit/instrumentation/astra/__init__.py +71 -159
  6. openlit/instrumentation/astra/astra.py +32 -22
  7. openlit/instrumentation/astra/async_astra.py +32 -22
  8. openlit/instrumentation/astra/utils.py +263 -88
  9. openlit/instrumentation/chroma/utils.py +2 -2
  10. openlit/instrumentation/controlflow/controlflow.py +2 -2
  11. openlit/instrumentation/embedchain/embedchain.py +4 -4
  12. openlit/instrumentation/groq/__init__.py +4 -4
  13. openlit/instrumentation/haystack/__init__.py +57 -28
  14. openlit/instrumentation/haystack/async_haystack.py +54 -0
  15. openlit/instrumentation/haystack/haystack.py +35 -65
  16. openlit/instrumentation/haystack/utils.py +377 -0
  17. openlit/instrumentation/julep/async_julep.py +2 -2
  18. openlit/instrumentation/julep/julep.py +2 -2
  19. openlit/instrumentation/langchain_community/utils.py +2 -2
  20. openlit/instrumentation/llamaindex/__init__.py +165 -37
  21. openlit/instrumentation/llamaindex/async_llamaindex.py +53 -0
  22. openlit/instrumentation/llamaindex/llamaindex.py +32 -64
  23. openlit/instrumentation/llamaindex/utils.py +412 -0
  24. openlit/instrumentation/mem0/mem0.py +2 -2
  25. openlit/instrumentation/milvus/__init__.py +30 -68
  26. openlit/instrumentation/milvus/milvus.py +34 -161
  27. openlit/instrumentation/milvus/utils.py +276 -0
  28. openlit/instrumentation/openai/__init__.py +24 -24
  29. openlit/instrumentation/openai/utils.py +10 -4
  30. openlit/instrumentation/pinecone/utils.py +2 -2
  31. openlit/instrumentation/qdrant/utils.py +2 -2
  32. openlit/instrumentation/together/__init__.py +8 -8
  33. openlit/semcov/__init__.py +79 -0
  34. {openlit-1.34.26.dist-info → openlit-1.34.28.dist-info}/METADATA +1 -1
  35. {openlit-1.34.26.dist-info → openlit-1.34.28.dist-info}/RECORD +37 -31
  36. {openlit-1.34.26.dist-info → openlit-1.34.28.dist-info}/LICENSE +0 -0
  37. {openlit-1.34.26.dist-info → openlit-1.34.28.dist-info}/WHEEL +0 -0
@@ -1,84 +1,54 @@
1
- # pylint: disable=duplicate-code, broad-exception-caught, too-many-statements, unused-argument
2
1
  """
3
- Module for monitoring Haystack applications.
2
+ Haystack sync wrapper
4
3
  """
5
4
 
6
- import logging
7
- from opentelemetry.trace import SpanKind, Status, StatusCode
8
- from opentelemetry.sdk.resources import SERVICE_NAME, TELEMETRY_SDK_NAME, DEPLOYMENT_ENVIRONMENT
5
+ import time
6
+ from opentelemetry.trace import SpanKind
7
+ from opentelemetry import context as context_api
9
8
  from openlit.__helpers import handle_exception
10
- from openlit.semcov import SemanticConvention
9
+ from openlit.instrumentation.haystack.utils import (
10
+ process_haystack_response,
11
+ OPERATION_MAP,
12
+ set_server_address_and_port,
13
+ )
11
14
 
12
- # Initialize logger for logging potential issues and operations
13
- logger = logging.getLogger(__name__)
15
+ def general_wrap(endpoint, version, environment, application_name,
16
+ tracer, pricing_info, capture_message_content, metrics, disable_metrics):
17
+ """Optimized wrapper for Haystack operations"""
14
18
 
15
- def join_data(gen_ai_endpoint, version, environment, application_name,
16
- tracer, pricing_info, capture_message_content):
17
- """
18
- Creates a wrapper around a function call to trace and log its execution metrics.
19
+ def wrapper(wrapped, instance, args, kwargs):
20
+ """Fast wrapper with minimal overhead"""
19
21
 
20
- This function wraps any given function to measure its execution time,
21
- log its operation, and trace its execution using OpenTelemetry.
22
-
23
- Parameters:
24
- - gen_ai_endpoint (str): A descriptor or name for the endpoint being traced.
25
- - version (str): The version of the Haystack application.
26
- - environment (str): The deployment environment (e.g., 'production', 'development').
27
- - application_name (str): Name of the Haystack application.
28
- - tracer (opentelemetry.trace.Tracer): The tracer object used for OpenTelemetry tracing.
29
- - pricing_info (dict): Information about the pricing for internal metrics (currently not used).
30
- - capture_message_content (bool): Flag indicating whether to trace the content of the response.
22
+ # CRITICAL: Suppression check
23
+ if context_api.get_value(context_api._SUPPRESS_INSTRUMENTATION_KEY):
24
+ return wrapped(*args, **kwargs)
31
25
 
32
- Returns:
33
- - function: A higher-order function that takes a function 'wrapped' and returns
34
- a new function that wraps 'wrapped' with additional tracing and logging.
35
- """
26
+ # Fast operation mapping
27
+ operation_type = OPERATION_MAP.get(endpoint, "framework")
36
28
 
37
- def wrapper(wrapped, instance, args, kwargs):
38
- """
39
- An inner wrapper function that executes the wrapped function, measures execution
40
- time, and records trace data using OpenTelemetry.
29
+ # Optimized span naming
30
+ if endpoint == "pipeline":
31
+ span_name = f"{operation_type} pipeline"
32
+ else:
33
+ span_name = f"{operation_type} {endpoint}"
41
34
 
42
- Parameters:
43
- - wrapped (Callable): The original function that this wrapper will execute.
44
- - instance (object): The instance to which the wrapped function belongs. This
45
- is used for instance methods. For static and classmethods,
46
- this may be None.
47
- - args (tuple): Positional arguments passed to the wrapped function.
48
- - kwargs (dict): Keyword arguments passed to the wrapped function.
35
+ # Fast server address
36
+ server_address, server_port = set_server_address_and_port(instance)
49
37
 
50
- Returns:
51
- - The result of the wrapped function call.
52
-
53
- The wrapper initiates a span with the provided tracer, sets various attributes
54
- on the span based on the function's execution and response, and ensures
55
- errors are handled and logged appropriately.
56
- """
57
- with tracer.start_as_current_span(gen_ai_endpoint, kind= SpanKind.CLIENT) as span:
38
+ with tracer.start_as_current_span(span_name, kind=SpanKind.CLIENT) as span:
39
+ start_time = time.time()
58
40
  response = wrapped(*args, **kwargs)
59
41
 
60
42
  try:
61
- span.set_attribute(TELEMETRY_SDK_NAME, "openlit")
62
- span.set_attribute(SemanticConvention.GEN_AI_SYSTEM,
63
- SemanticConvention.GEN_AI_SYSTEM_HAYSTACK)
64
- span.set_attribute(SemanticConvention.GEN_AI_ENDPOINT,
65
- gen_ai_endpoint)
66
- span.set_attribute(DEPLOYMENT_ENVIRONMENT,
67
- environment)
68
- span.set_attribute(SemanticConvention.GEN_AI_OPERATION,
69
- SemanticConvention.GEN_AI_OPERATION_TYPE_FRAMEWORK)
70
- span.set_attribute(SERVICE_NAME,
71
- application_name)
72
- span.set_status(Status(StatusCode.OK))
73
-
74
- # Return original response
75
- return response
76
-
43
+ response = process_haystack_response(
44
+ response, operation_type, server_address, server_port,
45
+ environment, application_name, metrics, start_time, span,
46
+ capture_message_content, disable_metrics, version,
47
+ instance, args, endpoint=endpoint, **kwargs
48
+ )
77
49
  except Exception as e:
78
50
  handle_exception(span, e)
79
- logger.error("Error in trace creation: %s", e)
80
51
 
81
- # Return original response
82
- return response
52
+ return response
83
53
 
84
54
  return wrapper
@@ -0,0 +1,377 @@
1
+ """
2
+ Haystack utilities
3
+ """
4
+ import time
5
+ import json
6
+ from typing import Dict, Any
7
+ from opentelemetry.trace import Status, StatusCode
8
+ from openlit.__helpers import common_framework_span_attributes, record_framework_metrics
9
+ from openlit.semcov import SemanticConvention
10
+
11
+ # Optimized operation mapping - minimal and fast
12
+ OPERATION_MAP = {
13
+ "pipeline": SemanticConvention.GEN_AI_OPERATION_TYPE_FRAMEWORK,
14
+ "bm25_retriever": SemanticConvention.GEN_AI_OPERATION_TYPE_RETRIEVE,
15
+ "prompt_builder": SemanticConvention.GEN_AI_OPERATION_TYPE_FRAMEWORK,
16
+ "openai_generator": SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT,
17
+ "openai_chat_generator": SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT,
18
+ "text_embedder": SemanticConvention.GEN_AI_OPERATION_TYPE_EMBEDDING,
19
+ "document_embedder": SemanticConvention.GEN_AI_OPERATION_TYPE_EMBEDDING,
20
+ }
21
+
22
+ def set_server_address_and_port(instance):
23
+ """Fast server address extraction"""
24
+ return "localhost", 8080
25
+
26
+ def object_count(obj):
27
+ """Fast object counting"""
28
+ try:
29
+ return len(obj) if hasattr(obj, "__len__") else 1
30
+ except:
31
+ return 1
32
+
33
+ def extract_component_technical_details(instance, args, kwargs, endpoint) -> Dict[str, Any]:
34
+ """Extract comprehensive component technical details with performance optimization"""
35
+ details = {}
36
+
37
+ try:
38
+ # Component class information
39
+ if hasattr(instance, "__class__"):
40
+ details["class_name"] = instance.__class__.__name__
41
+ details["module_name"] = instance.__class__.__module__
42
+
43
+ # Component input type extraction (optimized)
44
+ if hasattr(instance, "_component_config") and hasattr(instance._component_config, "input_types"):
45
+ input_types = {}
46
+ for name, type_info in instance._component_config.input_types.items():
47
+ input_types[name] = str(type_info) if type_info else "Any"
48
+ details["input_types"] = input_types
49
+ elif hasattr(instance, "run") and hasattr(instance.run, "__annotations__"):
50
+ # Fallback: extract from method annotations
51
+ annotations = instance.run.__annotations__
52
+ input_types = {k: str(v) for k, v in annotations.items() if k != "return"}
53
+ details["input_types"] = input_types
54
+
55
+ # Component output type extraction (optimized)
56
+ if hasattr(instance, "_component_config") and hasattr(instance._component_config, "output_types"):
57
+ output_types = {}
58
+ for name, type_info in instance._component_config.output_types.items():
59
+ output_types[name] = str(type_info) if type_info else "Any"
60
+ details["output_types"] = output_types
61
+
62
+ # Enhanced input/output specifications with connections
63
+ if hasattr(instance, "_component_config"):
64
+ config = instance._component_config
65
+
66
+ # Input specifications with data flow
67
+ if hasattr(config, "input_sockets"):
68
+ input_spec = {}
69
+ for socket_name, socket in config.input_sockets.items():
70
+ spec_info = {
71
+ "type": str(getattr(socket, "type", "Any")),
72
+ "default_value": str(getattr(socket, "default_value", None)),
73
+ "is_optional": getattr(socket, "is_optional", False)
74
+ }
75
+ input_spec[socket_name] = spec_info
76
+ details["input_spec"] = input_spec
77
+
78
+ # Output specifications with receivers
79
+ if hasattr(config, "output_sockets"):
80
+ output_spec = {}
81
+ for socket_name, socket in config.output_sockets.items():
82
+ spec_info = {
83
+ "type": str(getattr(socket, "type", "Any")),
84
+ "is_list": getattr(socket, "is_list", False)
85
+ }
86
+ output_spec[socket_name] = spec_info
87
+ details["output_spec"] = output_spec
88
+
89
+ # Runtime input data analysis (for actual values)
90
+ if args or kwargs:
91
+ runtime_inputs = {}
92
+ if args:
93
+ for i, arg in enumerate(args):
94
+ runtime_inputs[f"arg_{i}"] = type(arg).__name__
95
+ if kwargs:
96
+ for key, value in kwargs.items():
97
+ runtime_inputs[key] = type(value).__name__
98
+ details["runtime_input_types"] = runtime_inputs
99
+
100
+ except Exception:
101
+ # Silently continue if introspection fails - maintain performance
102
+ pass
103
+
104
+ return details
105
+
106
+ def extract_pipeline_metadata(instance, args, kwargs) -> Dict[str, Any]:
107
+ """Extract pipeline-level metadata and configuration"""
108
+ metadata = {}
109
+
110
+ try:
111
+ # Pipeline configuration
112
+ if hasattr(instance, "graph"):
113
+ graph = instance.graph
114
+ elif hasattr(instance, "_graph"):
115
+ graph = instance._graph
116
+
117
+ # Component count and connections
118
+ if hasattr(graph, "nodes"):
119
+ metadata["component_count"] = len(graph.nodes())
120
+
121
+ # Extract component connections and data flow
122
+ connections = []
123
+ if hasattr(graph, "edges"):
124
+ for edge in graph.edges(data=True):
125
+ source, target, data = edge
126
+ connection_info = {
127
+ "source": source,
128
+ "target": target,
129
+ "data": str(data) if data else None
130
+ }
131
+ connections.append(connection_info)
132
+ metadata["connections"] = connections
133
+
134
+ # Component list with types
135
+ components = []
136
+ for node in graph.nodes():
137
+ node_data = graph.nodes[node] if hasattr(graph.nodes[node], "get") else {}
138
+ component_info = {
139
+ "name": node,
140
+ "type": str(type(node_data.get("instance", ""))) if node_data.get("instance") else "unknown"
141
+ }
142
+ components.append(component_info)
143
+ metadata["components"] = components
144
+
145
+ # Pipeline configuration parameters
146
+ if hasattr(instance, "max_runs_per_component"):
147
+ metadata["max_runs_per_component"] = instance.max_runs_per_component
148
+
149
+ # Input/output data (if provided)
150
+ if args and len(args) > 0:
151
+ # Pipeline input data
152
+ input_data = args[0] if args else {}
153
+ if isinstance(input_data, dict):
154
+ # Sanitize large data for telemetry
155
+ sanitized_input = {}
156
+ for key, value in input_data.items():
157
+ if isinstance(value, (str, int, float, bool)):
158
+ sanitized_input[key] = value
159
+ elif isinstance(value, dict):
160
+ sanitized_input[key] = {k: str(v)[:100] for k, v in value.items()}
161
+ else:
162
+ sanitized_input[key] = str(type(value)).__name__
163
+ metadata["input_data"] = sanitized_input
164
+
165
+ except Exception:
166
+ # Silently continue if metadata extraction fails
167
+ pass
168
+
169
+ return metadata
170
+
171
+ def extract_component_connections(instance) -> Dict[str, Any]:
172
+ """Extract component connection and data flow information"""
173
+ connections = {}
174
+
175
+ try:
176
+ # Extract senders (components that send data to this component)
177
+ if hasattr(instance, "_component_config") and hasattr(instance._component_config, "input_sockets"):
178
+ senders = []
179
+ for socket_name, socket in instance._component_config.input_sockets.items():
180
+ if hasattr(socket, "_senders") and socket._senders:
181
+ for sender in socket._senders:
182
+ sender_info = {
183
+ "component": str(sender),
184
+ "socket": socket_name
185
+ }
186
+ senders.append(sender_info)
187
+ connections["senders"] = senders
188
+
189
+ # Extract receivers (components that receive data from this component)
190
+ if hasattr(instance, "_component_config") and hasattr(instance._component_config, "output_sockets"):
191
+ receivers = []
192
+ for socket_name, socket in instance._component_config.output_sockets.items():
193
+ if hasattr(socket, "_receivers") and socket._receivers:
194
+ for receiver in socket._receivers:
195
+ receiver_info = {
196
+ "component": str(receiver),
197
+ "socket": socket_name
198
+ }
199
+ receivers.append(receiver_info)
200
+ connections["receivers"] = receivers
201
+
202
+ except Exception:
203
+ # Silently continue if connection extraction fails
204
+ pass
205
+
206
+ return connections
207
+
208
+ def process_haystack_response(response, operation_type, server_address, server_port,
209
+ environment, application_name, metrics, start_time, span,
210
+ capture_message_content, disable_metrics, version, instance=None,
211
+ args=None, endpoint=None, **kwargs):
212
+ """Enhanced response processing with comprehensive technical details and optimized performance"""
213
+
214
+ end_time = time.time()
215
+
216
+ # Essential attributes
217
+ common_framework_span_attributes(
218
+ type("Scope", (), {
219
+ "_span": span,
220
+ "_server_address": server_address,
221
+ "_server_port": server_port,
222
+ "_start_time": start_time,
223
+ "_end_time": end_time
224
+ })(),
225
+ SemanticConvention.GEN_AI_SYSTEM_HAYSTACK,
226
+ server_address, server_port, environment, application_name,
227
+ version, endpoint, instance
228
+ )
229
+
230
+ # Core operation attributes
231
+ span.set_attribute(SemanticConvention.GEN_AI_OPERATION, operation_type)
232
+
233
+ # Enhanced technical details collection
234
+ if instance:
235
+ # Extract comprehensive component technical details
236
+ tech_details = extract_component_technical_details(instance, args, kwargs, endpoint)
237
+
238
+ # Apply component technical attributes using new semantic conventions
239
+ if tech_details.get("class_name"):
240
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_COMPONENT_CLASS_NAME, tech_details["class_name"])
241
+
242
+ if tech_details.get("input_types"):
243
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_COMPONENT_INPUT_TYPES,
244
+ json.dumps(tech_details["input_types"]))
245
+
246
+ if tech_details.get("output_types"):
247
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_COMPONENT_OUTPUT_TYPES,
248
+ json.dumps(tech_details["output_types"]))
249
+
250
+ if tech_details.get("input_spec"):
251
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_COMPONENT_INPUT_SPEC,
252
+ json.dumps(tech_details["input_spec"]))
253
+
254
+ if tech_details.get("output_spec"):
255
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_COMPONENT_OUTPUT_SPEC,
256
+ json.dumps(tech_details["output_spec"]))
257
+
258
+ # Component connections and data flow
259
+ connections = extract_component_connections(instance)
260
+ if connections.get("senders"):
261
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_COMPONENT_SENDERS,
262
+ json.dumps(connections["senders"]))
263
+
264
+ if connections.get("receivers"):
265
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_COMPONENT_RECEIVERS,
266
+ json.dumps(connections["receivers"]))
267
+
268
+ # Enhanced telemetry - pipeline level
269
+ if endpoint == "pipeline" and isinstance(response, dict):
270
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_CONTEXT_COUNT, len(response))
271
+
272
+ # Enhanced pipeline metadata collection
273
+ if instance:
274
+ pipeline_metadata = extract_pipeline_metadata(instance, args, kwargs)
275
+
276
+ # Apply pipeline metadata using new semantic conventions
277
+ if pipeline_metadata.get("component_count"):
278
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_PIPELINE_COMPONENT_COUNT,
279
+ pipeline_metadata["component_count"])
280
+
281
+ if pipeline_metadata.get("max_runs_per_component"):
282
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_PIPELINE_MAX_RUNS,
283
+ pipeline_metadata["max_runs_per_component"])
284
+
285
+ if pipeline_metadata.get("connections"):
286
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_COMPONENT_CONNECTIONS,
287
+ json.dumps(pipeline_metadata["connections"]))
288
+
289
+ if pipeline_metadata.get("components"):
290
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_PIPELINE_METADATA,
291
+ json.dumps(pipeline_metadata["components"]))
292
+
293
+ if pipeline_metadata.get("input_data"):
294
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_PIPELINE_INPUT_DATA,
295
+ json.dumps(pipeline_metadata["input_data"]))
296
+
297
+ # Pipeline output data
298
+ if response:
299
+ # Sanitize output data for telemetry
300
+ sanitized_output = {}
301
+ for key, value in response.items():
302
+ if isinstance(value, (str, int, float, bool)):
303
+ sanitized_output[key] = value
304
+ elif isinstance(value, dict) and "replies" in value:
305
+ sanitized_output[key] = f"{len(value['replies'])} replies"
306
+ else:
307
+ sanitized_output[key] = str(type(value)).__name__
308
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_PIPELINE_OUTPUT_DATA,
309
+ json.dumps(sanitized_output))
310
+
311
+ # Fast LLM response extraction
312
+ for key, value in response.items():
313
+ if key in ["llm", "generator"] and isinstance(value, dict) and "replies" in value:
314
+ replies = value["replies"]
315
+ if replies and capture_message_content:
316
+ span.set_attribute(SemanticConvention.GEN_AI_CONTENT_COMPLETION, str(replies[0])[:500])
317
+ break
318
+
319
+ # Enhanced telemetry - retriever level
320
+ elif "retriever" in endpoint and isinstance(response, dict) and "documents" in response:
321
+ docs = response["documents"]
322
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_RETRIEVAL_COUNT, object_count(docs))
323
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_DOCUMENTS_COUNT, object_count(docs))
324
+
325
+ # Component identification
326
+ if instance:
327
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_COMPONENT_NAME, endpoint)
328
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_COMPONENT_TYPE, "retriever")
329
+
330
+ # Enhanced telemetry - generator level
331
+ elif "generator" in endpoint:
332
+ # Component identification
333
+ if instance:
334
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_COMPONENT_NAME, endpoint)
335
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_COMPONENT_TYPE, "generator")
336
+
337
+ if args and capture_message_content:
338
+ span.set_attribute(SemanticConvention.GEN_AI_PROMPT, str(args[0])[:500])
339
+
340
+ if isinstance(response, dict) and "replies" in response:
341
+ replies = response["replies"]
342
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_CONTEXT_COUNT, object_count(replies))
343
+
344
+ # Enhanced telemetry - prompt builder level
345
+ elif endpoint == "prompt_builder":
346
+ # Component identification
347
+ if instance:
348
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_COMPONENT_NAME, endpoint)
349
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_COMPONENT_TYPE, "prompt_builder")
350
+
351
+ if kwargs and capture_message_content:
352
+ for key, value in kwargs.items():
353
+ if key in ["documents", "question"] and value:
354
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_CONTEXT_COUNT, object_count([value]))
355
+ break
356
+
357
+ # Component visit tracking (simulate component execution count)
358
+ if endpoint != "pipeline" and instance:
359
+ # Simple visit counter - can be enhanced with actual state tracking
360
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_COMPONENT_VISITS, 1)
361
+
362
+ # Duration and status
363
+ execution_time = end_time - start_time
364
+ span.set_attribute(SemanticConvention.GEN_AI_CLIENT_OPERATION_DURATION, execution_time)
365
+
366
+ # Pipeline execution time tracking
367
+ if endpoint == "pipeline":
368
+ span.set_attribute(SemanticConvention.GEN_AI_FRAMEWORK_PIPELINE_EXECUTION_TIME, execution_time)
369
+
370
+ span.set_status(Status(StatusCode.OK))
371
+
372
+ # Metrics
373
+ if not disable_metrics:
374
+ record_framework_metrics(metrics, operation_type, SemanticConvention.GEN_AI_SYSTEM_HAYSTACK,
375
+ server_address, server_port, environment, application_name, start_time, end_time)
376
+
377
+ return response
@@ -19,7 +19,7 @@ def async_wrap_julep(gen_ai_endpoint, version, environment, application_name,
19
19
 
20
20
  This function wraps any given function to measure its execution time,
21
21
  log its operation, and trace its execution using OpenTelemetry.
22
-
22
+
23
23
  Parameters:
24
24
  - gen_ai_endpoint (str): A descriptor or name for the endpoint being traced.
25
25
  - version (str): The version of the application.
@@ -49,7 +49,7 @@ def async_wrap_julep(gen_ai_endpoint, version, environment, application_name,
49
49
 
50
50
  Returns:
51
51
  - The result of the wrapped function call.
52
-
52
+
53
53
  The wrapper initiates a span with the provided tracer, sets various attributes
54
54
  on the span based on the function's execution and response, and ensures
55
55
  errors are handled and logged appropriately.
@@ -19,7 +19,7 @@ def wrap_julep(gen_ai_endpoint, version, environment, application_name,
19
19
 
20
20
  This function wraps any given function to measure its execution time,
21
21
  log its operation, and trace its execution using OpenTelemetry.
22
-
22
+
23
23
  Parameters:
24
24
  - gen_ai_endpoint (str): A descriptor or name for the endpoint being traced.
25
25
  - version (str): The version of the Langchain application.
@@ -49,7 +49,7 @@ def wrap_julep(gen_ai_endpoint, version, environment, application_name,
49
49
 
50
50
  Returns:
51
51
  - The result of the wrapped function call.
52
-
52
+
53
53
  The wrapper initiates a span with the provided tracer, sets various attributes
54
54
  on the span based on the function's execution and response, and ensures
55
55
  errors are handled and logged appropriately.
@@ -9,7 +9,7 @@ def process_general_response(response, gen_ai_endpoint, server_port, server_addr
9
9
  environment, application_name, span, version="1.0.0"):
10
10
  """
11
11
  Process general LangChain Community operations (document loading, text splitting) and generate telemetry.
12
-
12
+
13
13
  Args:
14
14
  response: The response object from the LangChain Community operation
15
15
  gen_ai_endpoint: The endpoint identifier for the operation
@@ -19,7 +19,7 @@ def process_general_response(response, gen_ai_endpoint, server_port, server_addr
19
19
  application_name: Application name
20
20
  span: OpenTelemetry span
21
21
  version: Version string
22
-
22
+
23
23
  Returns:
24
24
  The original response object
25
25
  """