ragaai-catalyst 2.2.4b5__py3-none-any.whl → 2.2.5b2__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 (45) hide show
  1. ragaai_catalyst/__init__.py +0 -2
  2. ragaai_catalyst/dataset.py +59 -1
  3. ragaai_catalyst/tracers/agentic_tracing/tracers/main_tracer.py +5 -285
  4. ragaai_catalyst/tracers/agentic_tracing/utils/__init__.py +0 -2
  5. ragaai_catalyst/tracers/agentic_tracing/utils/create_dataset_schema.py +1 -1
  6. ragaai_catalyst/tracers/exporters/__init__.py +1 -2
  7. ragaai_catalyst/tracers/exporters/file_span_exporter.py +0 -1
  8. ragaai_catalyst/tracers/exporters/ragaai_trace_exporter.py +23 -1
  9. ragaai_catalyst/tracers/tracer.py +6 -186
  10. {ragaai_catalyst-2.2.4b5.dist-info → ragaai_catalyst-2.2.5b2.dist-info}/METADATA +1 -1
  11. {ragaai_catalyst-2.2.4b5.dist-info → ragaai_catalyst-2.2.5b2.dist-info}/RECORD +14 -45
  12. ragaai_catalyst/experiment.py +0 -486
  13. ragaai_catalyst/tracers/agentic_tracing/tests/FinancialAnalysisSystem.ipynb +0 -536
  14. ragaai_catalyst/tracers/agentic_tracing/tests/GameActivityEventPlanner.ipynb +0 -134
  15. ragaai_catalyst/tracers/agentic_tracing/tests/TravelPlanner.ipynb +0 -563
  16. ragaai_catalyst/tracers/agentic_tracing/tests/__init__.py +0 -0
  17. ragaai_catalyst/tracers/agentic_tracing/tests/ai_travel_agent.py +0 -197
  18. ragaai_catalyst/tracers/agentic_tracing/tests/unique_decorator_test.py +0 -172
  19. ragaai_catalyst/tracers/agentic_tracing/tracers/agent_tracer.py +0 -687
  20. ragaai_catalyst/tracers/agentic_tracing/tracers/base.py +0 -1319
  21. ragaai_catalyst/tracers/agentic_tracing/tracers/custom_tracer.py +0 -347
  22. ragaai_catalyst/tracers/agentic_tracing/tracers/langgraph_tracer.py +0 -0
  23. ragaai_catalyst/tracers/agentic_tracing/tracers/llm_tracer.py +0 -1182
  24. ragaai_catalyst/tracers/agentic_tracing/tracers/network_tracer.py +0 -288
  25. ragaai_catalyst/tracers/agentic_tracing/tracers/tool_tracer.py +0 -557
  26. ragaai_catalyst/tracers/agentic_tracing/tracers/user_interaction_tracer.py +0 -129
  27. ragaai_catalyst/tracers/agentic_tracing/upload/upload_local_metric.py +0 -74
  28. ragaai_catalyst/tracers/agentic_tracing/utils/api_utils.py +0 -21
  29. ragaai_catalyst/tracers/agentic_tracing/utils/generic.py +0 -32
  30. ragaai_catalyst/tracers/agentic_tracing/utils/get_user_trace_metrics.py +0 -28
  31. ragaai_catalyst/tracers/agentic_tracing/utils/span_attributes.py +0 -133
  32. ragaai_catalyst/tracers/agentic_tracing/utils/supported_llm_provider.toml +0 -34
  33. ragaai_catalyst/tracers/exporters/raga_exporter.py +0 -467
  34. ragaai_catalyst/tracers/langchain_callback.py +0 -821
  35. ragaai_catalyst/tracers/llamaindex_callback.py +0 -361
  36. ragaai_catalyst/tracers/llamaindex_instrumentation.py +0 -424
  37. ragaai_catalyst/tracers/upload_traces.py +0 -170
  38. ragaai_catalyst/tracers/utils/convert_langchain_callbacks_output.py +0 -62
  39. ragaai_catalyst/tracers/utils/convert_llama_instru_callback.py +0 -69
  40. ragaai_catalyst/tracers/utils/extraction_logic_llama_index.py +0 -74
  41. ragaai_catalyst/tracers/utils/langchain_tracer_extraction_logic.py +0 -82
  42. ragaai_catalyst/tracers/utils/rag_trace_json_converter.py +0 -403
  43. {ragaai_catalyst-2.2.4b5.dist-info → ragaai_catalyst-2.2.5b2.dist-info}/WHEEL +0 -0
  44. {ragaai_catalyst-2.2.4b5.dist-info → ragaai_catalyst-2.2.5b2.dist-info}/licenses/LICENSE +0 -0
  45. {ragaai_catalyst-2.2.4b5.dist-info → ragaai_catalyst-2.2.5b2.dist-info}/top_level.txt +0 -0
@@ -1,1182 +0,0 @@
1
- from typing import Optional, Any, Dict, List
2
- import asyncio
3
- import psutil
4
- import wrapt
5
- import functools
6
- import json
7
- import os
8
- import time
9
- from datetime import datetime
10
- import uuid
11
- import contextvars
12
- import traceback
13
- import importlib
14
- import sys
15
- import logging
16
-
17
- try:
18
- from llama_index.core.base.llms.types import ChatResponse,TextBlock, ChatMessage
19
- except ImportError:
20
- logging.warning("Failed to import ChatResponse, TextBlock, ChatMessage. Some features from llamaindex may not work. Please upgrade to the latest version of llama_index or version (>=0.12)")
21
- from .base import BaseTracer
22
- from ..utils.llm_utils import (
23
- extract_model_name,
24
- extract_parameters,
25
- extract_token_usage,
26
- extract_input_data,
27
- calculate_llm_cost,
28
- sanitize_api_keys,
29
- sanitize_input,
30
- extract_llm_output,
31
- num_tokens_from_messages,
32
- get_model_cost
33
- )
34
- from ..utils.unique_decorator import generate_unique_hash
35
- from ..utils.file_name_tracker import TrackName
36
- from ..utils.span_attributes import SpanAttributes
37
-
38
- logger = logging.getLogger(__name__)
39
- logging_level = (
40
- logger.setLevel(logging.DEBUG)
41
- if os.getenv("DEBUG")
42
- else logger.setLevel(logging.INFO)
43
- )
44
-
45
-
46
- class LLMTracerMixin:
47
- def __init__(self, *args, **kwargs):
48
- super().__init__(*args, **kwargs)
49
- self.file_tracker = TrackName()
50
- self.patches = []
51
- try:
52
- self.model_costs = get_model_cost()
53
- except Exception as e:
54
- self.model_costs = {
55
- "default": {"input_cost_per_token": 0.0, "output_cost_per_token": 0.0}
56
- }
57
- self.MAX_PARAMETERS_TO_DISPLAY = 10
58
- self.current_llm_call_name = contextvars.ContextVar(
59
- "llm_call_name", default=None
60
- )
61
- self.component_network_calls = {}
62
- self.component_user_interaction = {}
63
- self.current_component_id = None
64
- self.total_tokens = 0
65
- self.total_cost = 0.0
66
- self.llm_data = {}
67
-
68
- self.auto_instrument_llm = False
69
- self.auto_instrument_user_interaction = False
70
- self.auto_instrument_file_io = False
71
- self.auto_instrument_network = False
72
-
73
- def check_package_available(self, package_name):
74
- """Check if a package is available in the environment"""
75
- try:
76
- importlib.import_module(package_name)
77
- return True
78
- except ImportError:
79
- return False
80
-
81
- def validate_openai_key(self):
82
- """Validate if OpenAI API key is available"""
83
- return bool(os.getenv("OPENAI_API_KEY"))
84
-
85
- def instrument_llm_calls(self):
86
- """Enable LLM instrumentation"""
87
- self.auto_instrument_llm = True
88
- # Check currently loaded modules
89
- if "vertexai" in sys.modules:
90
- self.patch_vertex_ai_methods(sys.modules["vertexai"])
91
- if "openai" in sys.modules and self.validate_openai_key():
92
- self.patch_openai_methods(sys.modules["openai"])
93
- self.patch_openai_beta_methods(sys.modules["openai"])
94
- if "litellm" in sys.modules:
95
- self.patch_litellm_methods(sys.modules["litellm"])
96
- if "anthropic" in sys.modules:
97
- self.patch_anthropic_methods(sys.modules["anthropic"])
98
- if "google.generativeai" in sys.modules:
99
- self.patch_google_genai_methods(sys.modules["google.generativeai"])
100
- if "langchain_google_vertexai" in sys.modules:
101
- self.patch_langchain_google_methods(sys.modules["langchain_google_vertexai"])
102
- if "langchain_google_genai" in sys.modules:
103
- self.patch_langchain_google_methods(sys.modules["langchain_google_genai"])
104
-
105
- if "langchain_openai" in sys.modules:
106
- self.patch_langchain_openai_methods(sys.modules["langchain_openai"])
107
- if "langchain_anthropic" in sys.modules:
108
- self.patch_langchain_anthropic_methods(sys.modules["langchain_anthropic"])
109
-
110
- if "llama_index" in sys.modules:
111
- self.patch_llama_index_methods(sys.modules["llama_index"])
112
-
113
- # Register hooks for future imports with availability checks
114
- if self.check_package_available("vertexai"):
115
- wrapt.register_post_import_hook(self.patch_vertex_ai_methods, "vertexai")
116
- wrapt.register_post_import_hook(
117
- self.patch_vertex_ai_methods, "vertexai.generative_models"
118
- )
119
-
120
- if self.check_package_available("openai") and self.validate_openai_key():
121
- wrapt.register_post_import_hook(self.patch_openai_methods, "openai")
122
- wrapt.register_post_import_hook(self.patch_openai_beta_methods, "openai")
123
-
124
- if self.check_package_available("litellm"):
125
- wrapt.register_post_import_hook(self.patch_litellm_methods, "litellm")
126
-
127
- if self.check_package_available("anthropic"):
128
- wrapt.register_post_import_hook(self.patch_anthropic_methods, "anthropic")
129
-
130
- if self.check_package_available("google.generativeai"):
131
- wrapt.register_post_import_hook(
132
- self.patch_google_genai_methods, "google.generativeai"
133
- )
134
-
135
- # Add hooks for LangChain integrations with availability checks
136
- if self.check_package_available("langchain_google_vertexai"):
137
- wrapt.register_post_import_hook(
138
- self.patch_langchain_google_methods, "langchain_google_vertexai"
139
- )
140
-
141
-
142
- # Add hooks for llama-index
143
- wrapt.register_post_import_hook(self.patch_llama_index_methods, "llama_index")
144
-
145
- if self.check_package_available("langchain_google_genai"):
146
- wrapt.register_post_import_hook(
147
- self.patch_langchain_google_methods, "langchain_google_genai"
148
- )
149
-
150
- if self.check_package_available("langchain_openai"):
151
- wrapt.register_post_import_hook(
152
- self.patch_langchain_openai_methods, "langchain_openai"
153
- )
154
- if self.check_package_available("langchain_anthropic"):
155
- wrapt.register_post_import_hook(
156
- self.patch_langchain_anthropic_methods, "langchain_anthropic"
157
- )
158
-
159
- def instrument_user_interaction_calls(self):
160
- """Enable user interaction instrumentation for LLM calls"""
161
- self.auto_instrument_user_interaction = True
162
-
163
- def instrument_network_calls(self):
164
- """Enable network instrumentation for LLM calls"""
165
- self.auto_instrument_network = True
166
-
167
- def instrument_file_io_calls(self):
168
- """Enable file IO instrumentation for LLM calls"""
169
- self.auto_instrument_file_io = True
170
-
171
- def patch_llama_index_methods(self, module):
172
- """Patch llama-index LLM methods"""
173
- try:
174
- # Handle OpenAI LLM from llama-index
175
- if hasattr(module, "llms"):
176
- # OpenAI
177
- if hasattr(module.llms, "openai"):
178
- openai_module = module.llms.openai
179
- if hasattr(openai_module, "OpenAI"):
180
- llm_class = getattr(openai_module, "OpenAI")
181
- self.wrap_method(llm_class, "complete")
182
- self.wrap_method(llm_class, "acomplete")
183
- self.wrap_method(llm_class, "chat")
184
- self.wrap_method(llm_class, "achat")
185
- self.wrap_method(llm_class, "stream_chat")
186
- # self.wrap_method(llm_class, "stream_achat")
187
- self.wrap_method(llm_class, "stream_complete")
188
- # self.wrap_method(llm_class, "stream_acomplete")
189
-
190
- # Anthropic
191
- if hasattr(module.llms, "anthropic"):
192
- anthropic_module = module.llms.anthropic
193
- if hasattr(anthropic_module, "Anthropic"):
194
- llm_class = getattr(anthropic_module, "Anthropic")
195
- self.wrap_method(llm_class, "complete")
196
- self.wrap_method(llm_class, "acomplete")
197
- self.wrap_method(llm_class, "chat")
198
- self.wrap_method(llm_class, "achat")
199
- self.wrap_method(llm_class, "stream_chat")
200
- # self.wrap_method(llm_class, "stream_achat")
201
-
202
- # Azure OpenAI
203
- if hasattr(module.llms, "azure_openai"):
204
- azure_module = module.llms.azure_openai
205
- if hasattr(azure_module, "AzureOpenAI"):
206
- llm_class = getattr(azure_module, "AzureOpenAI")
207
- self.wrap_method(llm_class, "complete")
208
- self.wrap_method(llm_class, "acomplete")
209
- self.wrap_method(llm_class, "chat")
210
- self.wrap_method(llm_class, "achat")
211
- self.wrap_method(llm_class, "stream_chat")
212
- # self.wrap_method(llm_class, "stream_achat")
213
-
214
- # LiteLLM
215
- if hasattr(module.llms, "litellm"):
216
- litellm_module = module.llms.litellm
217
- if hasattr(litellm_module, "LiteLLM"):
218
- llm_class = getattr(litellm_module, "LiteLLM")
219
- self.wrap_method(llm_class, "complete")
220
- self.wrap_method(llm_class, "acomplete")
221
- self.wrap_method(llm_class, "chat")
222
- self.wrap_method(llm_class, "achat")
223
-
224
- # Vertex AI
225
- if hasattr(module.llms, "vertex"):
226
- vertex_module = module.llms.vertex
227
- if hasattr(vertex_module, "Vertex"):
228
- llm_class = getattr(vertex_module, "Vertex")
229
- self.wrap_method(llm_class, "complete")
230
- self.wrap_method(llm_class, "acomplete")
231
- self.wrap_method(llm_class, "chat")
232
- self.wrap_method(llm_class, "achat")
233
-
234
- # Gemini
235
- if hasattr(module.llms, "gemini"):
236
- gemini_module = module.llms.gemini
237
- if hasattr(gemini_module, "Gemini"):
238
- llm_class = getattr(gemini_module, "Gemini")
239
- self.wrap_method(llm_class, "complete")
240
- self.wrap_method(llm_class, "acomplete")
241
- self.wrap_method(llm_class, "chat")
242
- self.wrap_method(llm_class, "achat")
243
-
244
- except Exception as e:
245
- # Log the error but continue execution
246
- print(f"Warning: Failed to patch llama-index methods: {str(e)}")
247
-
248
- def patch_openai_methods(self, module):
249
- try:
250
- if hasattr(module, "OpenAI"):
251
- client_class = getattr(module, "OpenAI")
252
- self.wrap_openai_client_methods(client_class)
253
- if hasattr(module, "AsyncOpenAI"):
254
- async_client_class = getattr(module, "AsyncOpenAI")
255
- self.wrap_openai_client_methods(async_client_class)
256
- except Exception as e:
257
- # Log the error but continue execution
258
- print(f"Warning: Failed to patch OpenAI methods: {str(e)}")
259
-
260
- def patch_langchain_openai_methods(self, module):
261
- try:
262
- if hasattr(module, 'ChatOpenAI'):
263
- client_class = getattr(module, "ChatOpenAI")
264
-
265
- if hasattr(client_class, "invoke"):
266
- self.wrap_langchain_openai_method(client_class, f"{client_class.__name__}.invoke")
267
- elif hasattr(client_class, "run"):
268
- self.wrap_langchain_openai_method(client_class, f"{client_class.__name__}.run")
269
- if hasattr(module, 'AsyncChatOpenAI'):
270
- if hasattr(client_class, "ainvoke"):
271
- self.wrap_langchain_openai_method(client_class, f"{client_class.__name__}.ainvoke")
272
- elif hasattr(client_class, "arun"):
273
- self.wrap_langchain_openai_method(client_class, f"{client_class.__name__}.arun")
274
- except Exception as e:
275
- # Log the error but continue execution
276
- print(f"Warning: Failed to patch OpenAI methods: {str(e)}")
277
-
278
- def patch_langchain_anthropic_methods(self, module):
279
- try:
280
- if hasattr(module, 'ChatAnthropic'):
281
- client_class = getattr(module, "ChatAnthropic")
282
- if hasattr(client_class, "invoke"):
283
- self.wrap_langchain_anthropic_method(client_class, f"{client_class.__name__}.invoke")
284
- if hasattr(client_class, "ainvoke"):
285
- self.wrap_langchain_anthropic_method(client_class, f"{client_class.__name__}.ainvoke")
286
- if hasattr(module, 'AsyncChatAnthropic'):
287
- async_client_class = getattr(module, "AsyncChatAnthropic")
288
- if hasattr(async_client_class, "ainvoke"):
289
- self.wrap_langchain_anthropic_method(async_client_class, f"{async_client_class.__name__}.ainvoke")
290
- if hasattr(async_client_class, "arun"):
291
- self.wrap_langchain_anthropic_method(async_client_class, f"{async_client_class.__name__}.arun")
292
- except Exception as e:
293
- # Log the error but continue execution
294
- print(f"Warning: Failed to patch Anthropic methods: {str(e)}")
295
-
296
- def patch_openai_beta_methods(self, openai_module):
297
- """
298
- Patch the new openai.beta endpoints (threads, runs, messages, etc.)
299
- so that calls like openai.beta.threads.create(...) or
300
- openai.beta.threads.runs.create(...) are automatically traced.
301
- """
302
- # Make sure openai_module has a 'beta' attribute
303
- openai_module.api_type = "openai"
304
- if not hasattr(openai_module, "beta"):
305
- return
306
-
307
- beta_module = openai_module.beta
308
-
309
- # Patch openai.beta.threads
310
- import openai
311
- openai.api_type = "openai"
312
- if hasattr(beta_module, "threads"):
313
- threads_obj = beta_module.threads
314
- # Patch top-level methods on openai.beta.threads
315
- for method_name in ["create", "list"]:
316
- if hasattr(threads_obj, method_name):
317
- self.wrap_method(threads_obj, method_name)
318
-
319
- # Patch the nested objects: messages, runs
320
- if hasattr(threads_obj, "messages"):
321
- messages_obj = threads_obj.messages
322
- for method_name in ["create", "list"]:
323
- if hasattr(messages_obj, method_name):
324
- self.wrap_method(messages_obj, method_name)
325
-
326
- if hasattr(threads_obj, "runs"):
327
- runs_obj = threads_obj.runs
328
- for method_name in ["create", "retrieve", "list"]:
329
- if hasattr(runs_obj, method_name):
330
- self.wrap_method(runs_obj, method_name)
331
-
332
- def patch_anthropic_methods(self, module):
333
- if hasattr(module, "Anthropic"):
334
- client_class = getattr(module, "Anthropic")
335
- self.wrap_anthropic_client_methods(client_class)
336
-
337
- def patch_google_genai_methods(self, module):
338
- # Patch direct Google GenerativeAI usage
339
- if hasattr(module, "GenerativeModel"):
340
- model_class = getattr(module, "GenerativeModel")
341
- self.wrap_genai_model_methods(model_class)
342
-
343
- # Patch LangChain integration
344
- if hasattr(module, "ChatGoogleGenerativeAI"):
345
- chat_class = getattr(module, "ChatGoogleGenerativeAI")
346
- # Wrap invoke method to capture messages
347
- original_invoke = chat_class.invoke
348
-
349
- def patched_invoke(self, messages, *args, **kwargs):
350
- # Store messages in the instance for later use
351
- self._last_messages = messages
352
- return original_invoke(self, messages, *args, **kwargs)
353
-
354
- chat_class.invoke = patched_invoke
355
-
356
- # LangChain v0.2+ uses invoke/ainvoke
357
- self.wrap_method(chat_class, "_generate")
358
- if hasattr(chat_class, "_agenerate"):
359
- self.wrap_method(chat_class, "_agenerate")
360
- # Fallback for completion methods
361
- if hasattr(chat_class, "complete"):
362
- self.wrap_method(chat_class, "complete")
363
- if hasattr(chat_class, "acomplete"):
364
- self.wrap_method(chat_class, "acomplete")
365
-
366
- def patch_vertex_ai_methods(self, module):
367
- # Patch the GenerativeModel class
368
- if hasattr(module, "generative_models"):
369
- gen_models = getattr(module, "generative_models")
370
- if hasattr(gen_models, "GenerativeModel"):
371
- model_class = getattr(gen_models, "GenerativeModel")
372
- self.wrap_vertex_model_methods(model_class)
373
-
374
- # Also patch the class directly if available
375
- if hasattr(module, "GenerativeModel"):
376
- model_class = getattr(module, "GenerativeModel")
377
- self.wrap_vertex_model_methods(model_class)
378
-
379
- def wrap_vertex_model_methods(self, model_class):
380
- # Patch both sync and async methods
381
- self.wrap_method(model_class, "generate_content")
382
- if hasattr(model_class, "generate_content_async"):
383
- self.wrap_method(model_class, "generate_content_async")
384
-
385
- def patch_litellm_methods(self, module):
386
- self.wrap_method(module, "completion")
387
- self.wrap_method(module, "acompletion")
388
-
389
- def patch_langchain_google_methods(self, module):
390
- """Patch LangChain's Google integration methods"""
391
- if hasattr(module, "ChatVertexAI"):
392
- chat_class = getattr(module, "ChatVertexAI")
393
- # LangChain v0.2+ uses invoke/ainvoke
394
- self.wrap_method(chat_class, "_generate")
395
- if hasattr(chat_class, "_agenerate"):
396
- self.wrap_method(chat_class, "_agenerate")
397
- # Fallback for completion methods
398
- if hasattr(chat_class, "complete"):
399
- self.wrap_method(chat_class, "complete")
400
- if hasattr(chat_class, "acomplete"):
401
- self.wrap_method(chat_class, "acomplete")
402
-
403
- if hasattr(module, "ChatGoogleGenerativeAI"):
404
- chat_class = getattr(module, "ChatGoogleGenerativeAI")
405
- # LangChain v0.2+ uses invoke/ainvoke
406
- self.wrap_method(chat_class, "_generate")
407
- if hasattr(chat_class, "_agenerate"):
408
- self.wrap_method(chat_class, "_agenerate")
409
- # Fallback for completion methods
410
- if hasattr(chat_class, "complete"):
411
- self.wrap_method(chat_class, "complete")
412
- if hasattr(chat_class, "acomplete"):
413
- self.wrap_method(chat_class, "acomplete")
414
-
415
- def wrap_openai_client_methods(self, client_class):
416
- original_init = client_class.__init__
417
-
418
- @functools.wraps(original_init)
419
- def patched_init(client_self, *args, **kwargs):
420
- original_init(client_self, *args, **kwargs)
421
- # Check if this is AsyncOpenAI or OpenAI
422
- is_async = "AsyncOpenAI" in client_class.__name__
423
-
424
- if is_async:
425
- # Patch async methods for AsyncOpenAI
426
- if hasattr(client_self.chat.completions, "create"):
427
- original_create = client_self.chat.completions.create
428
-
429
- @functools.wraps(original_create)
430
- async def wrapped_create(*args, **kwargs):
431
- return await self.trace_llm_call(
432
- original_create, *args, **kwargs
433
- )
434
-
435
- client_self.chat.completions.create = wrapped_create
436
- else:
437
- # Patch sync methods for OpenAI
438
- if hasattr(client_self.chat.completions, "create"):
439
- original_create = client_self.chat.completions.create
440
-
441
- @functools.wraps(original_create)
442
- def wrapped_create(*args, **kwargs):
443
- return self.trace_llm_call_sync(
444
- original_create, *args, **kwargs
445
- )
446
-
447
- client_self.chat.completions.create = wrapped_create
448
-
449
- setattr(client_class, "__init__", patched_init)
450
-
451
- def wrap_langchain_openai_method(self, client_class, method_name):
452
- method = method_name.split(".")[-1]
453
- original_init = getattr(client_class, method)
454
-
455
- @functools.wraps(original_init)
456
- def patched_init(*args, **kwargs):
457
- # Check if this is AsyncOpenAI or OpenAI
458
- is_async = "AsyncChatOpenAI" in client_class.__name__
459
-
460
- if is_async:
461
- return self.trace_llm_call(original_init, *args, **kwargs)
462
- else:
463
- return self.trace_llm_call_sync(original_init, *args, **kwargs)
464
-
465
- setattr(client_class, method, patched_init)
466
-
467
- def wrap_langchain_anthropic_method(self, client_class, method_name):
468
- original_init = getattr(client_class, method_name)
469
-
470
- @functools.wraps(original_init)
471
- def patched_init(*args, **kwargs):
472
- is_async = "AsyncChatAnthropic" in client_class.__name__
473
-
474
- if is_async:
475
- return self.trace_llm_call(original_init, *args, **kwargs)
476
- else:
477
- return self.trace_llm_call_sync(original_init, *args, **kwargs)
478
-
479
- setattr(client_class, method_name, patched_init)
480
-
481
- def wrap_anthropic_client_methods(self, client_class):
482
- original_init = client_class.__init__
483
-
484
- @functools.wraps(original_init)
485
- def patched_init(client_self, *args, **kwargs):
486
- original_init(client_self, *args, **kwargs)
487
- self.wrap_method(client_self.messages, "create")
488
- if hasattr(client_self.messages, "acreate"):
489
- self.wrap_method(client_self.messages, "acreate")
490
-
491
- setattr(client_class, "__init__", patched_init)
492
-
493
- def wrap_genai_model_methods(self, model_class):
494
- original_init = model_class.__init__
495
-
496
- @functools.wraps(original_init)
497
- def patched_init(model_self, *args, **kwargs):
498
- original_init(model_self, *args, **kwargs)
499
- self.wrap_method(model_self, "generate_content")
500
- if hasattr(model_self, "generate_content_async"):
501
- self.wrap_method(model_self, "generate_content_async")
502
-
503
- setattr(model_class, "__init__", patched_init)
504
-
505
- def wrap_method(self, obj, method_name):
506
- """
507
- Wrap a method with tracing functionality.
508
- Works for both class methods and instance methods.
509
- """
510
- # If obj is a class, we need to patch both the class and any existing instances
511
- if isinstance(obj, type):
512
- # Store the original class method
513
- original_method = getattr(obj, method_name)
514
-
515
- @wrapt.decorator
516
- def wrapper(wrapped, instance, args, kwargs):
517
- if asyncio.iscoroutinefunction(wrapped):
518
- return self.trace_llm_call(wrapped, *args, **kwargs)
519
- return self.trace_llm_call_sync(wrapped, *args, **kwargs)
520
-
521
- # Wrap the class method
522
- wrapped_method = wrapper(original_method)
523
- setattr(obj, method_name, wrapped_method)
524
- self.patches.append((obj, method_name, original_method))
525
-
526
- else:
527
- # For instance methods
528
- original_method = getattr(obj, method_name)
529
-
530
- @wrapt.decorator
531
- def wrapper(wrapped, instance, args, kwargs):
532
- if asyncio.iscoroutinefunction(wrapped):
533
- return self.trace_llm_call(wrapped, *args, **kwargs)
534
- return self.trace_llm_call_sync(wrapped, *args, **kwargs)
535
-
536
- wrapped_method = wrapper(original_method)
537
- setattr(obj, method_name, wrapped_method)
538
- self.patches.append((obj, method_name, original_method))
539
-
540
- def create_llm_component(
541
- self,
542
- component_id,
543
- hash_id,
544
- name,
545
- llm_type,
546
- version,
547
- memory_used,
548
- start_time,
549
- input_data,
550
- output_data,
551
- cost={},
552
- usage={},
553
- error=None,
554
- parameters={},
555
- ):
556
- try:
557
- # Update total metrics
558
- self.total_tokens += usage.get("total_tokens", 0)
559
- self.total_cost += cost.get("total_cost", 0)
560
-
561
- network_calls = []
562
- if self.auto_instrument_network:
563
- network_calls = self.component_network_calls.get(component_id, [])
564
-
565
- interactions = []
566
- if self.auto_instrument_user_interaction:
567
- input_output_interactions = []
568
- for interaction in self.component_user_interaction.get(component_id, []):
569
- if interaction["interaction_type"] in ["input", "output"]:
570
- input_output_interactions.append(interaction)
571
- interactions.extend(input_output_interactions)
572
- if self.auto_instrument_file_io:
573
- file_io_interactions = []
574
- for interaction in self.component_user_interaction.get(component_id, []):
575
- if interaction["interaction_type"] in ["file_read", "file_write"]:
576
- file_io_interactions.append(interaction)
577
- interactions.extend(file_io_interactions)
578
-
579
- parameters_to_display = {}
580
- if "run_manager" in parameters:
581
- parameters_obj = parameters["run_manager"]
582
- if hasattr(parameters_obj, "metadata"):
583
- metadata = parameters_obj.metadata
584
- # parameters = {'metadata': metadata}
585
- parameters_to_display.update(metadata)
586
-
587
- # Add only those keys in parameters that are single values and not objects, dict or list
588
- for key, value in parameters.items():
589
- if isinstance(value, (str, int, float, bool)):
590
- parameters_to_display[key] = value
591
-
592
- # Limit the number of parameters to display
593
- parameters_to_display = dict(
594
- list(parameters_to_display.items())[: self.MAX_PARAMETERS_TO_DISPLAY]
595
- )
596
-
597
- # Set the Context and GT
598
- span_gt = None
599
- span_context = None
600
- if name in self.span_attributes_dict:
601
- span_gt = self.span_attributes_dict[name].gt
602
- span_context = self.span_attributes_dict[name].context
603
-
604
- logger.debug(f"span context {span_context}, span_gt {span_gt}")
605
-
606
- # Tags
607
- tags = []
608
- if name in self.span_attributes_dict:
609
- tags = self.span_attributes_dict[name].tags or []
610
-
611
- # Get End Time
612
- end_time = datetime.now().astimezone().isoformat()
613
-
614
- # Metrics
615
- metrics = []
616
- if name in self.span_attributes_dict:
617
- raw_metrics = self.span_attributes_dict[name].metrics or []
618
- for metric in raw_metrics:
619
- base_metric_name = metric["name"]
620
- counter = sum(1 for x in self.visited_metrics if x.startswith(base_metric_name))
621
- metric_name = f'{base_metric_name}_{counter}' if counter > 0 else base_metric_name
622
- self.visited_metrics.append(metric_name)
623
- metric["name"] = metric_name
624
- metrics.append(metric)
625
-
626
- # TODO TO check i/p and o/p is according or not
627
- input = input_data["args"] if hasattr(input_data, "args") else input_data
628
- output = output_data.output_response if output_data else None
629
- prompt = self.convert_to_content(input)
630
- response = self.convert_to_content(output)
631
-
632
- # TODO: Execute & Add the User requested metrics here
633
- formatted_metrics = BaseTracer.get_formatted_metric(self.span_attributes_dict, self.project_id, name)
634
- if formatted_metrics:
635
- metrics.extend(formatted_metrics)
636
-
637
- component = {
638
- "id": component_id,
639
- "hash_id": hash_id,
640
- "source_hash_id": None,
641
- "type": "llm",
642
- "name": name,
643
- "start_time": start_time,
644
- "end_time": end_time,
645
- "error": error,
646
- "parent_id": self.current_agent_id.get(),
647
- "info": {
648
- "model": llm_type,
649
- "version": version,
650
- "memory_used": memory_used,
651
- "cost": cost,
652
- "tokens": usage,
653
- "tags": tags,
654
- **parameters_to_display,
655
- },
656
- "extra_info": parameters,
657
- "data": {
658
- "input": input,
659
- "output": output,
660
- "memory_used": memory_used,
661
- },
662
- "metrics": metrics,
663
- "network_calls": network_calls,
664
- "interactions": interactions,
665
- }
666
-
667
- # Assign context and gt if available
668
- component["data"]["gt"] = span_gt
669
- component["data"]["context"] = span_context
670
-
671
- # Reset the SpanAttributes context variable
672
- self.span_attributes_dict[name] = SpanAttributes(name)
673
-
674
- return component
675
- except Exception as e:
676
- raise Exception("Failed to create LLM component")
677
-
678
- def convert_to_content(self, input_data):
679
- try:
680
- if isinstance(input_data, dict):
681
- messages = input_data.get("kwargs", {}).get("messages", [])
682
- elif isinstance(input_data, list):
683
- if len(input_data)>0 and isinstance(input_data[0]['content'],ChatResponse):
684
- extracted_messages = []
685
- for item in input_data:
686
- chat_response = item.get('content')
687
- if hasattr(chat_response, 'message') and hasattr(chat_response.message, 'blocks'):
688
- for block in chat_response.message.blocks:
689
- if hasattr(block, 'text'):
690
- extracted_messages.append(block.text)
691
- messages=extracted_messages
692
- if isinstance(messages,list):
693
- return "\n".join(messages)
694
- elif len(input_data)>0 and isinstance(input_data[0]['content'],TextBlock):
695
- return " ".join(block.text for item in input_data for block in item['content'] if isinstance(block, TextBlock))
696
- elif len(input_data)>0 and isinstance(input_data[0]['content'],ChatMessage):
697
- return " ".join(block.text for block in input_data[0]['content'].blocks if isinstance(block, TextBlock))
698
- else:
699
- messages = input_data
700
- elif isinstance(input_data,ChatResponse):
701
- messages=input_data['content']
702
- else:
703
- return ""
704
- res=""
705
- res="\n".join(msg.get("content", "").strip() for msg in messages if msg.get("content"))
706
- except Exception as e:
707
- res=str(input_data)
708
- return res
709
-
710
- def process_content(content):
711
- if isinstance(content, str):
712
- return content.strip()
713
- elif isinstance(content, list):
714
- # Handle list of content blocks
715
- text_parts = []
716
- for block in content:
717
- if hasattr(block, 'text'):
718
- # Handle TextBlock-like objects
719
- text_parts.append(block.text.strip())
720
- elif isinstance(block, dict) and 'text' in block:
721
- # Handle dictionary with text field
722
- text_parts.append(block['text'].strip())
723
- return " ".join(text_parts)
724
- elif isinstance(content, dict):
725
- # Handle dictionary content
726
- return content.get('text', '').strip()
727
- return ""
728
-
729
-
730
- def start_component(self, component_id):
731
- """Start tracking network calls for a component"""
732
- self.component_network_calls[component_id] = []
733
- self.current_component_id = component_id
734
-
735
- def end_component(self, component_id):
736
- """Stop tracking network calls for a component"""
737
- self.current_component_id = None
738
-
739
- async def trace_llm_call(self, original_func, *args, **kwargs):
740
- """Trace an LLM API call"""
741
- if not self.is_active:
742
- return await original_func(*args, **kwargs)
743
-
744
- if not self.auto_instrument_llm:
745
- return await original_func(*args, **kwargs)
746
-
747
- start_time = datetime.now().astimezone().isoformat()
748
- start_memory = psutil.Process().memory_info().rss
749
- component_id = str(uuid.uuid4())
750
- hash_id = generate_unique_hash(original_func, args, kwargs)
751
-
752
- # Start tracking network calls for this component
753
- self.start_component(component_id)
754
-
755
- try:
756
- # Execute the LLM call
757
- result = await original_func(*args, **kwargs)
758
-
759
- # Calculate resource usage
760
- end_memory = psutil.Process().memory_info().rss
761
- memory_used = max(0, end_memory - start_memory)
762
-
763
- # Extract token usage and calculate cost
764
- model_name = extract_model_name(args, kwargs, result)
765
- if 'stream' in kwargs:
766
- stream = kwargs['stream']
767
- if stream:
768
- prompt_messages = kwargs['messages']
769
- # Create response message for streaming case
770
- response_message = {"role": "assistant", "content": result} if result else {"role": "assistant",
771
- "content": ""}
772
- token_usage = num_tokens_from_messages(model_name, prompt_messages, response_message)
773
- else:
774
- token_usage = extract_token_usage(result)
775
- else:
776
- token_usage = extract_token_usage(result)
777
- cost = calculate_llm_cost(token_usage, model_name, self.model_costs, self.model_custom_cost)
778
- parameters = extract_parameters(kwargs)
779
- input_data = extract_input_data(args, kwargs, result)
780
-
781
- # End tracking network calls for this component
782
- self.end_component(component_id)
783
-
784
- name = self.current_llm_call_name.get()
785
- if name is None:
786
- name = original_func.__name__
787
-
788
- # Create LLM component
789
- llm_component = self.create_llm_component(
790
- component_id=component_id,
791
- hash_id=hash_id,
792
- name=name,
793
- llm_type=model_name,
794
- version=None,
795
- memory_used=memory_used,
796
- start_time=start_time,
797
- input_data=input_data,
798
- output_data=extract_llm_output(result),
799
- cost=cost,
800
- usage=token_usage,
801
- parameters=parameters,
802
- )
803
-
804
- self.add_component(llm_component)
805
- self.llm_data = llm_component
806
-
807
- return result
808
-
809
- except Exception as e:
810
- error_component = {
811
- "code": 500,
812
- "type": type(e).__name__,
813
- "message": str(e),
814
- "details": {},
815
- }
816
-
817
- # End tracking network calls for this component
818
- self.end_component(component_id)
819
-
820
- name = self.current_llm_call_name.get()
821
- if name is None:
822
- name = original_func.__name__
823
-
824
- llm_component = self.create_llm_component(
825
- component_id=component_id,
826
- hash_id=hash_id,
827
- name=name,
828
- llm_type="unknown",
829
- version=None,
830
- memory_used=0,
831
- start_time=start_time,
832
- input_data=extract_input_data(args, kwargs, None),
833
- output_data=None,
834
- error=error_component,
835
- )
836
-
837
- self.add_component(llm_component)
838
-
839
- raise
840
-
841
- def trace_llm_call_sync(self, original_func, *args, **kwargs):
842
- """Sync version of trace_llm_call"""
843
- if not self.is_active:
844
- if asyncio.iscoroutinefunction(original_func):
845
- return asyncio.run(original_func(*args, **kwargs))
846
- return original_func(*args, **kwargs)
847
-
848
- if not self.auto_instrument_llm:
849
- return original_func(*args, **kwargs)
850
-
851
- start_time = datetime.now().astimezone().isoformat()
852
- component_id = str(uuid.uuid4())
853
- hash_id = generate_unique_hash(original_func, args, kwargs)
854
-
855
- # Start tracking network calls for this component
856
- self.start_component(component_id)
857
-
858
- # Calculate resource usage
859
- start_memory = psutil.Process().memory_info().rss
860
-
861
- try:
862
- # Execute the function
863
- if asyncio.iscoroutinefunction(original_func):
864
- result = asyncio.run(original_func(*args, **kwargs))
865
- else:
866
- result = original_func(*args, **kwargs)
867
-
868
- end_memory = psutil.Process().memory_info().rss
869
- memory_used = max(0, end_memory - start_memory)
870
-
871
- # Extract token usage and calculate cost
872
- model_name = extract_model_name(args, kwargs, result)
873
-
874
- if 'stream' in kwargs:
875
- stream = kwargs['stream']
876
- if stream:
877
- prompt_messages = kwargs['messages']
878
- # Create response message for streaming case
879
- response_message = {"role": "assistant", "content": result} if result else {"role": "assistant",
880
- "content": ""}
881
- token_usage = num_tokens_from_messages(model_name, prompt_messages, response_message)
882
- else:
883
- token_usage = extract_token_usage(result)
884
- else:
885
- token_usage = extract_token_usage(result)
886
- cost = calculate_llm_cost(token_usage, model_name, self.model_costs, self.model_custom_cost)
887
- parameters = extract_parameters(kwargs)
888
- input_data = extract_input_data(args, kwargs, result)
889
-
890
- # End tracking network calls for this component
891
- self.end_component(component_id)
892
-
893
- name = self.current_llm_call_name.get()
894
- if name is None:
895
- name = original_func.__name__
896
-
897
- # Create LLM component
898
- llm_component = self.create_llm_component(
899
- component_id=component_id,
900
- hash_id=hash_id,
901
- name=name,
902
- llm_type=model_name,
903
- version=None,
904
- memory_used=memory_used,
905
- start_time=start_time,
906
- input_data=input_data,
907
- output_data=extract_llm_output(result),
908
- cost=cost,
909
- usage=token_usage,
910
- parameters=parameters,
911
- )
912
- self.llm_data = llm_component
913
- self.add_component(llm_component)
914
-
915
- return result
916
-
917
- except Exception as e:
918
- error_component = {
919
- "code": 500,
920
- "type": type(e).__name__,
921
- "message": str(e),
922
- "details": {},
923
- }
924
-
925
- # End tracking network calls for this component
926
- self.end_component(component_id)
927
-
928
- name = self.current_llm_call_name.get()
929
- if name is None:
930
- name = original_func.__name__
931
-
932
- end_memory = psutil.Process().memory_info().rss
933
- memory_used = max(0, end_memory - start_memory)
934
-
935
- llm_component = self.create_llm_component(
936
- component_id=component_id,
937
- hash_id=hash_id,
938
- name=name,
939
- llm_type="unknown",
940
- version=None,
941
- memory_used=memory_used,
942
- start_time=start_time,
943
- input_data=extract_input_data(args, kwargs, None),
944
- output_data=None,
945
- error=error_component,
946
- )
947
- self.llm_data = llm_component
948
- self.add_component(llm_component, is_error=True)
949
-
950
- raise
951
-
952
- def trace_llm(
953
- self,
954
- name: str = None,
955
- tags: List[str] = [],
956
- metadata: Dict[str, Any] = {},
957
- metrics: List[Dict[str, Any]] = [],
958
- feedback: Optional[Any] = None,
959
- ):
960
-
961
- start_memory = psutil.Process().memory_info().rss
962
- start_time = datetime.now().astimezone().isoformat()
963
-
964
- if name not in self.span_attributes_dict:
965
- self.span_attributes_dict[name] = SpanAttributes(name)
966
- if tags:
967
- self.span(name).add_tags(tags)
968
- if metadata:
969
- self.span(name).add_metadata(metadata)
970
- if metrics:
971
- if isinstance(metrics, dict):
972
- metrics = [metrics]
973
- try:
974
- for metric in metrics:
975
- self.span(name).add_metrics(
976
- name=metric["name"],
977
- score=metric["score"],
978
- reasoning=metric.get("reasoning", ""),
979
- cost=metric.get("cost", None),
980
- latency=metric.get("latency", None),
981
- metadata=metric.get("metadata", {}),
982
- config=metric.get("config", {}),
983
- )
984
- except ValueError as e:
985
- logger.error(f"Validation Error: {e}")
986
- except Exception as e:
987
- logger.error(f"Error adding metric: {e}")
988
-
989
- if feedback:
990
- self.span(name).add_feedback(feedback)
991
-
992
- self.current_llm_call_name.set(name)
993
-
994
- def decorator(func):
995
- @functools.wraps(func)
996
- async def async_wrapper(*args, **kwargs):
997
- gt = kwargs.get("gt") if kwargs else None
998
- if gt is not None:
999
- span = self.span(name)
1000
- span.add_gt(gt)
1001
- self.current_llm_call_name.set(name)
1002
- if not self.is_active:
1003
- return await func(*args, **kwargs)
1004
-
1005
- component_id = str(uuid.uuid4())
1006
- parent_agent_id = self.current_agent_id.get()
1007
- self.start_component(component_id)
1008
-
1009
- error_info = None
1010
- result = None
1011
-
1012
- try:
1013
- result = await func(*args, **kwargs)
1014
- return result
1015
- except Exception as e:
1016
- error_component = {
1017
- "type": type(e).__name__,
1018
- "message": str(e),
1019
- "traceback": traceback.format_exc(),
1020
- "timestamp": datetime.now().astimezone().isoformat(),
1021
- }
1022
-
1023
- # End tracking network calls for this component
1024
- self.end_component(component_id)
1025
-
1026
- end_memory = psutil.Process().memory_info().rss
1027
- memory_used = max(0, end_memory - start_memory)
1028
-
1029
- llm_component = self.create_llm_component(
1030
- component_id=component_id,
1031
- hash_id=generate_unique_hash(func, args, kwargs),
1032
- name=name,
1033
- llm_type="unknown",
1034
- version=None,
1035
- memory_used=memory_used,
1036
- start_time=start_time,
1037
- input_data=extract_input_data(args, kwargs, None),
1038
- output_data=None,
1039
- error=error_component,
1040
- )
1041
- self.llm_data = llm_component
1042
- self.add_component(llm_component, is_error=True)
1043
-
1044
- raise
1045
- finally:
1046
-
1047
- llm_component = self.llm_data
1048
- if (name is not None) or (name != ""):
1049
- llm_component["name"] = name
1050
-
1051
- if name in self.span_attributes_dict:
1052
- span_gt = self.span_attributes_dict[name].gt
1053
- if span_gt is not None:
1054
- llm_component["data"]["gt"] = span_gt
1055
- span_context = self.span_attributes_dict[name].context
1056
- if span_context:
1057
- llm_component["data"]["context"] = span_context
1058
-
1059
- if error_info:
1060
- llm_component["error"] = error_info["error"]
1061
-
1062
- self.end_component(component_id)
1063
-
1064
- # metrics
1065
- metrics = []
1066
- if name in self.span_attributes_dict:
1067
- raw_metrics = self.span_attributes_dict[name].metrics or []
1068
- for metric in raw_metrics:
1069
- base_metric_name = metric["name"]
1070
- counter = sum(1 for x in self.visited_metrics if x.startswith(base_metric_name))
1071
- metric_name = f'{base_metric_name}_{counter}' if counter > 0 else base_metric_name
1072
- self.visited_metrics.append(metric_name)
1073
- metric["name"] = metric_name
1074
- metrics.append(metric)
1075
- llm_component["metrics"] = metrics
1076
- if parent_agent_id:
1077
- children = self.agent_children.get()
1078
- children.append(llm_component)
1079
- self.agent_children.set(children)
1080
- else:
1081
- self.add_component(llm_component)
1082
-
1083
- llm_component["interactions"] = self.component_user_interaction.get(
1084
- component_id, []
1085
- )
1086
- self.add_component(llm_component)
1087
-
1088
- @functools.wraps(func)
1089
- def sync_wrapper(*args, **kwargs):
1090
- gt = kwargs.get("gt") if kwargs else None
1091
- if gt is not None:
1092
- span = self.span(name)
1093
- span.add_gt(gt)
1094
- self.current_llm_call_name.set(name)
1095
- if not self.is_active:
1096
- return func(*args, **kwargs)
1097
-
1098
- component_id = str(uuid.uuid4())
1099
- parent_agent_id = self.current_agent_id.get()
1100
- self.start_component(component_id)
1101
-
1102
- start_time = datetime.now().astimezone().isoformat()
1103
- error_info = None
1104
- result = None
1105
-
1106
- try:
1107
- result = func(*args, **kwargs)
1108
- return result
1109
- except Exception as e:
1110
- error_component = {
1111
- "type": type(e).__name__,
1112
- "message": str(e),
1113
- "traceback": traceback.format_exc(),
1114
- "timestamp": datetime.now().astimezone().isoformat(),
1115
- }
1116
-
1117
- # End tracking network calls for this component
1118
- self.end_component(component_id)
1119
-
1120
- end_memory = psutil.Process().memory_info().rss
1121
- memory_used = max(0, end_memory - start_memory)
1122
-
1123
- llm_component = self.create_llm_component(
1124
- component_id=component_id,
1125
- hash_id=generate_unique_hash(func, args, kwargs),
1126
- name=name,
1127
- llm_type="unknown",
1128
- version=None,
1129
- memory_used=memory_used,
1130
- start_time=start_time,
1131
- input_data=extract_input_data(args, kwargs, None),
1132
- output_data=None,
1133
- error=error_component,
1134
- )
1135
- self.llm_data = llm_component
1136
- self.add_component(llm_component, is_error=True)
1137
-
1138
- raise
1139
- finally:
1140
- llm_component = self.llm_data
1141
- if (name is not None) or (name != ""):
1142
- llm_component["name"] = name
1143
-
1144
- if error_info:
1145
- llm_component["error"] = error_info["error"]
1146
-
1147
- self.end_component(component_id)
1148
- metrics = []
1149
- if name in self.span_attributes_dict:
1150
- raw_metrics = self.span_attributes_dict[name].metrics or []
1151
- for metric in raw_metrics:
1152
- base_metric_name = metric["name"]
1153
- counter = sum(1 for x in self.visited_metrics if x.startswith(base_metric_name))
1154
- metric_name = f'{base_metric_name}_{counter}' if counter > 0 else base_metric_name
1155
- self.visited_metrics.append(metric_name)
1156
- metric["name"] = metric_name
1157
- metrics.append(metric)
1158
- llm_component["metrics"] = metrics
1159
- if parent_agent_id:
1160
- children = self.agent_children.get()
1161
- children.append(llm_component)
1162
- self.agent_children.set(children)
1163
- else:
1164
- self.add_component(llm_component)
1165
-
1166
- llm_component["interactions"] = self.component_user_interaction.get(
1167
- component_id, []
1168
- )
1169
- self.add_component(llm_component)
1170
-
1171
- return async_wrapper if asyncio.iscoroutinefunction(func) else sync_wrapper
1172
-
1173
- return decorator
1174
-
1175
- def unpatch_llm_calls(self):
1176
- # Remove all patches
1177
- for obj, method_name, original_method in self.patches:
1178
- try:
1179
- setattr(obj, method_name, original_method)
1180
- except Exception as e:
1181
- print(f"Error unpatching {method_name}: {str(e)}")
1182
- self.patches = []