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.
- ragaai_catalyst/__init__.py +0 -2
- ragaai_catalyst/dataset.py +59 -1
- ragaai_catalyst/tracers/agentic_tracing/tracers/main_tracer.py +5 -285
- ragaai_catalyst/tracers/agentic_tracing/utils/__init__.py +0 -2
- ragaai_catalyst/tracers/agentic_tracing/utils/create_dataset_schema.py +1 -1
- ragaai_catalyst/tracers/exporters/__init__.py +1 -2
- ragaai_catalyst/tracers/exporters/file_span_exporter.py +0 -1
- ragaai_catalyst/tracers/exporters/ragaai_trace_exporter.py +23 -1
- ragaai_catalyst/tracers/tracer.py +6 -186
- {ragaai_catalyst-2.2.4b5.dist-info → ragaai_catalyst-2.2.5b2.dist-info}/METADATA +1 -1
- {ragaai_catalyst-2.2.4b5.dist-info → ragaai_catalyst-2.2.5b2.dist-info}/RECORD +14 -45
- ragaai_catalyst/experiment.py +0 -486
- ragaai_catalyst/tracers/agentic_tracing/tests/FinancialAnalysisSystem.ipynb +0 -536
- ragaai_catalyst/tracers/agentic_tracing/tests/GameActivityEventPlanner.ipynb +0 -134
- ragaai_catalyst/tracers/agentic_tracing/tests/TravelPlanner.ipynb +0 -563
- ragaai_catalyst/tracers/agentic_tracing/tests/__init__.py +0 -0
- ragaai_catalyst/tracers/agentic_tracing/tests/ai_travel_agent.py +0 -197
- ragaai_catalyst/tracers/agentic_tracing/tests/unique_decorator_test.py +0 -172
- ragaai_catalyst/tracers/agentic_tracing/tracers/agent_tracer.py +0 -687
- ragaai_catalyst/tracers/agentic_tracing/tracers/base.py +0 -1319
- ragaai_catalyst/tracers/agentic_tracing/tracers/custom_tracer.py +0 -347
- ragaai_catalyst/tracers/agentic_tracing/tracers/langgraph_tracer.py +0 -0
- ragaai_catalyst/tracers/agentic_tracing/tracers/llm_tracer.py +0 -1182
- ragaai_catalyst/tracers/agentic_tracing/tracers/network_tracer.py +0 -288
- ragaai_catalyst/tracers/agentic_tracing/tracers/tool_tracer.py +0 -557
- ragaai_catalyst/tracers/agentic_tracing/tracers/user_interaction_tracer.py +0 -129
- ragaai_catalyst/tracers/agentic_tracing/upload/upload_local_metric.py +0 -74
- ragaai_catalyst/tracers/agentic_tracing/utils/api_utils.py +0 -21
- ragaai_catalyst/tracers/agentic_tracing/utils/generic.py +0 -32
- ragaai_catalyst/tracers/agentic_tracing/utils/get_user_trace_metrics.py +0 -28
- ragaai_catalyst/tracers/agentic_tracing/utils/span_attributes.py +0 -133
- ragaai_catalyst/tracers/agentic_tracing/utils/supported_llm_provider.toml +0 -34
- ragaai_catalyst/tracers/exporters/raga_exporter.py +0 -467
- ragaai_catalyst/tracers/langchain_callback.py +0 -821
- ragaai_catalyst/tracers/llamaindex_callback.py +0 -361
- ragaai_catalyst/tracers/llamaindex_instrumentation.py +0 -424
- ragaai_catalyst/tracers/upload_traces.py +0 -170
- ragaai_catalyst/tracers/utils/convert_langchain_callbacks_output.py +0 -62
- ragaai_catalyst/tracers/utils/convert_llama_instru_callback.py +0 -69
- ragaai_catalyst/tracers/utils/extraction_logic_llama_index.py +0 -74
- ragaai_catalyst/tracers/utils/langchain_tracer_extraction_logic.py +0 -82
- ragaai_catalyst/tracers/utils/rag_trace_json_converter.py +0 -403
- {ragaai_catalyst-2.2.4b5.dist-info → ragaai_catalyst-2.2.5b2.dist-info}/WHEEL +0 -0
- {ragaai_catalyst-2.2.4b5.dist-info → ragaai_catalyst-2.2.5b2.dist-info}/licenses/LICENSE +0 -0
- {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 = []
|