ragaai-catalyst 2.1.5b22__py3-none-any.whl → 2.1.5b23__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 +3 -1
- ragaai_catalyst/dataset.py +49 -1
- ragaai_catalyst/redteaming.py +171 -0
- ragaai_catalyst/synthetic_data_generation.py +1 -1
- ragaai_catalyst/tracers/agentic_tracing/tracers/agent_tracer.py +34 -33
- ragaai_catalyst/tracers/agentic_tracing/tracers/base.py +215 -46
- ragaai_catalyst/tracers/agentic_tracing/tracers/llm_tracer.py +237 -62
- ragaai_catalyst/tracers/agentic_tracing/tracers/main_tracer.py +0 -3
- ragaai_catalyst/tracers/agentic_tracing/upload/upload_local_metric.py +72 -0
- ragaai_catalyst/tracers/agentic_tracing/upload/upload_trace_metric.py +27 -11
- ragaai_catalyst/tracers/agentic_tracing/utils/llm_utils.py +3 -0
- ragaai_catalyst/tracers/agentic_tracing/utils/span_attributes.py +62 -28
- ragaai_catalyst/tracers/agentic_tracing/utils/unique_decorator.py +3 -1
- ragaai_catalyst/tracers/agentic_tracing/utils/zip_list_of_unique_files.py +40 -21
- ragaai_catalyst/tracers/tracer.py +6 -3
- {ragaai_catalyst-2.1.5b22.dist-info → ragaai_catalyst-2.1.5b23.dist-info}/METADATA +37 -2
- {ragaai_catalyst-2.1.5b22.dist-info → ragaai_catalyst-2.1.5b23.dist-info}/RECORD +20 -18
- {ragaai_catalyst-2.1.5b22.dist-info → ragaai_catalyst-2.1.5b23.dist-info}/LICENSE +0 -0
- {ragaai_catalyst-2.1.5b22.dist-info → ragaai_catalyst-2.1.5b23.dist-info}/WHEEL +0 -0
- {ragaai_catalyst-2.1.5b22.dist-info → ragaai_catalyst-2.1.5b23.dist-info}/top_level.txt +0 -0
@@ -13,7 +13,9 @@ import traceback
|
|
13
13
|
import importlib
|
14
14
|
import sys
|
15
15
|
from litellm import model_cost
|
16
|
+
from llama_index.core.base.llms.types import ChatResponse
|
16
17
|
|
18
|
+
from .base import BaseTracer
|
17
19
|
from ..utils.llm_utils import (
|
18
20
|
extract_model_name,
|
19
21
|
extract_parameters,
|
@@ -80,7 +82,6 @@ class LLMTracerMixin:
|
|
80
82
|
def instrument_llm_calls(self):
|
81
83
|
"""Enable LLM instrumentation"""
|
82
84
|
self.auto_instrument_llm = True
|
83
|
-
|
84
85
|
# Check currently loaded modules
|
85
86
|
if "vertexai" in sys.modules:
|
86
87
|
self.patch_vertex_ai_methods(sys.modules["vertexai"])
|
@@ -97,28 +98,32 @@ class LLMTracerMixin:
|
|
97
98
|
self.patch_langchain_google_methods(sys.modules["langchain_google_vertexai"])
|
98
99
|
if "langchain_google_genai" in sys.modules:
|
99
100
|
self.patch_langchain_google_methods(sys.modules["langchain_google_genai"])
|
101
|
+
|
100
102
|
if "langchain_openai" in sys.modules:
|
101
103
|
self.patch_langchain_openai_methods(sys.modules["langchain_openai"])
|
102
104
|
if "langchain_anthropic" in sys.modules:
|
103
105
|
self.patch_langchain_anthropic_methods(sys.modules["langchain_anthropic"])
|
104
106
|
|
107
|
+
if "llama_index" in sys.modules:
|
108
|
+
self.patch_llama_index_methods(sys.modules["llama_index"])
|
109
|
+
|
105
110
|
# Register hooks for future imports with availability checks
|
106
111
|
if self.check_package_available("vertexai"):
|
107
112
|
wrapt.register_post_import_hook(self.patch_vertex_ai_methods, "vertexai")
|
108
113
|
wrapt.register_post_import_hook(
|
109
114
|
self.patch_vertex_ai_methods, "vertexai.generative_models"
|
110
115
|
)
|
111
|
-
|
116
|
+
|
112
117
|
if self.check_package_available("openai") and self.validate_openai_key():
|
113
118
|
wrapt.register_post_import_hook(self.patch_openai_methods, "openai")
|
114
119
|
wrapt.register_post_import_hook(self.patch_openai_beta_methods, "openai")
|
115
|
-
|
120
|
+
|
116
121
|
if self.check_package_available("litellm"):
|
117
122
|
wrapt.register_post_import_hook(self.patch_litellm_methods, "litellm")
|
118
|
-
|
123
|
+
|
119
124
|
if self.check_package_available("anthropic"):
|
120
125
|
wrapt.register_post_import_hook(self.patch_anthropic_methods, "anthropic")
|
121
|
-
|
126
|
+
|
122
127
|
if self.check_package_available("google.generativeai"):
|
123
128
|
wrapt.register_post_import_hook(
|
124
129
|
self.patch_google_genai_methods, "google.generativeai"
|
@@ -129,12 +134,16 @@ class LLMTracerMixin:
|
|
129
134
|
wrapt.register_post_import_hook(
|
130
135
|
self.patch_langchain_google_methods, "langchain_google_vertexai"
|
131
136
|
)
|
137
|
+
|
138
|
+
|
139
|
+
# Add hooks for llama-index
|
140
|
+
wrapt.register_post_import_hook(self.patch_llama_index_methods, "llama_index")
|
132
141
|
|
133
142
|
if self.check_package_available("langchain_google_genai"):
|
134
143
|
wrapt.register_post_import_hook(
|
135
144
|
self.patch_langchain_google_methods, "langchain_google_genai"
|
136
145
|
)
|
137
|
-
|
146
|
+
|
138
147
|
if self.check_package_available("langchain_openai"):
|
139
148
|
wrapt.register_post_import_hook(
|
140
149
|
self.patch_langchain_openai_methods, "langchain_openai"
|
@@ -151,11 +160,88 @@ class LLMTracerMixin:
|
|
151
160
|
def instrument_network_calls(self):
|
152
161
|
"""Enable network instrumentation for LLM calls"""
|
153
162
|
self.auto_instrument_network = True
|
154
|
-
|
163
|
+
|
155
164
|
def instrument_file_io_calls(self):
|
156
165
|
"""Enable file IO instrumentation for LLM calls"""
|
157
166
|
self.auto_instrument_file_io = True
|
158
167
|
|
168
|
+
def patch_llama_index_methods(self, module):
|
169
|
+
"""Patch llama-index LLM methods"""
|
170
|
+
try:
|
171
|
+
# Handle OpenAI LLM from llama-index
|
172
|
+
if hasattr(module, "llms"):
|
173
|
+
# OpenAI
|
174
|
+
if hasattr(module.llms, "openai"):
|
175
|
+
openai_module = module.llms.openai
|
176
|
+
if hasattr(openai_module, "OpenAI"):
|
177
|
+
llm_class = getattr(openai_module, "OpenAI")
|
178
|
+
self.wrap_method(llm_class, "complete")
|
179
|
+
self.wrap_method(llm_class, "acomplete")
|
180
|
+
self.wrap_method(llm_class, "chat")
|
181
|
+
self.wrap_method(llm_class, "achat")
|
182
|
+
self.wrap_method(llm_class, "stream_chat")
|
183
|
+
# self.wrap_method(llm_class, "stream_achat")
|
184
|
+
self.wrap_method(llm_class, "stream_complete")
|
185
|
+
# self.wrap_method(llm_class, "stream_acomplete")
|
186
|
+
|
187
|
+
# Anthropic
|
188
|
+
if hasattr(module.llms, "anthropic"):
|
189
|
+
anthropic_module = module.llms.anthropic
|
190
|
+
if hasattr(anthropic_module, "Anthropic"):
|
191
|
+
llm_class = getattr(anthropic_module, "Anthropic")
|
192
|
+
self.wrap_method(llm_class, "complete")
|
193
|
+
self.wrap_method(llm_class, "acomplete")
|
194
|
+
self.wrap_method(llm_class, "chat")
|
195
|
+
self.wrap_method(llm_class, "achat")
|
196
|
+
self.wrap_method(llm_class, "stream_chat")
|
197
|
+
# self.wrap_method(llm_class, "stream_achat")
|
198
|
+
|
199
|
+
# Azure OpenAI
|
200
|
+
if hasattr(module.llms, "azure_openai"):
|
201
|
+
azure_module = module.llms.azure_openai
|
202
|
+
if hasattr(azure_module, "AzureOpenAI"):
|
203
|
+
llm_class = getattr(azure_module, "AzureOpenAI")
|
204
|
+
self.wrap_method(llm_class, "complete")
|
205
|
+
self.wrap_method(llm_class, "acomplete")
|
206
|
+
self.wrap_method(llm_class, "chat")
|
207
|
+
self.wrap_method(llm_class, "achat")
|
208
|
+
self.wrap_method(llm_class, "stream_chat")
|
209
|
+
# self.wrap_method(llm_class, "stream_achat")
|
210
|
+
|
211
|
+
# LiteLLM
|
212
|
+
if hasattr(module.llms, "litellm"):
|
213
|
+
litellm_module = module.llms.litellm
|
214
|
+
if hasattr(litellm_module, "LiteLLM"):
|
215
|
+
llm_class = getattr(litellm_module, "LiteLLM")
|
216
|
+
self.wrap_method(llm_class, "complete")
|
217
|
+
self.wrap_method(llm_class, "acomplete")
|
218
|
+
self.wrap_method(llm_class, "chat")
|
219
|
+
self.wrap_method(llm_class, "achat")
|
220
|
+
|
221
|
+
# Vertex AI
|
222
|
+
if hasattr(module.llms, "vertex"):
|
223
|
+
vertex_module = module.llms.vertex
|
224
|
+
if hasattr(vertex_module, "Vertex"):
|
225
|
+
llm_class = getattr(vertex_module, "Vertex")
|
226
|
+
self.wrap_method(llm_class, "complete")
|
227
|
+
self.wrap_method(llm_class, "acomplete")
|
228
|
+
self.wrap_method(llm_class, "chat")
|
229
|
+
self.wrap_method(llm_class, "achat")
|
230
|
+
|
231
|
+
# Gemini
|
232
|
+
if hasattr(module.llms, "gemini"):
|
233
|
+
gemini_module = module.llms.gemini
|
234
|
+
if hasattr(gemini_module, "Gemini"):
|
235
|
+
llm_class = getattr(gemini_module, "Gemini")
|
236
|
+
self.wrap_method(llm_class, "complete")
|
237
|
+
self.wrap_method(llm_class, "acomplete")
|
238
|
+
self.wrap_method(llm_class, "chat")
|
239
|
+
self.wrap_method(llm_class, "achat")
|
240
|
+
|
241
|
+
except Exception as e:
|
242
|
+
# Log the error but continue execution
|
243
|
+
print(f"Warning: Failed to patch llama-index methods: {str(e)}")
|
244
|
+
|
159
245
|
def patch_openai_methods(self, module):
|
160
246
|
try:
|
161
247
|
if hasattr(module, "OpenAI"):
|
@@ -167,12 +253,12 @@ class LLMTracerMixin:
|
|
167
253
|
except Exception as e:
|
168
254
|
# Log the error but continue execution
|
169
255
|
print(f"Warning: Failed to patch OpenAI methods: {str(e)}")
|
170
|
-
|
256
|
+
|
171
257
|
def patch_langchain_openai_methods(self, module):
|
172
258
|
try:
|
173
259
|
if hasattr(module, 'ChatOpenAI'):
|
174
260
|
client_class = getattr(module, "ChatOpenAI")
|
175
|
-
|
261
|
+
|
176
262
|
if hasattr(client_class, "invoke"):
|
177
263
|
self.wrap_langchain_openai_method(client_class, f"{client_class.__name__}.invoke")
|
178
264
|
elif hasattr(client_class, "run"):
|
@@ -185,7 +271,7 @@ class LLMTracerMixin:
|
|
185
271
|
except Exception as e:
|
186
272
|
# Log the error but continue execution
|
187
273
|
print(f"Warning: Failed to patch OpenAI methods: {str(e)}")
|
188
|
-
|
274
|
+
|
189
275
|
def patch_langchain_anthropic_methods(self, module):
|
190
276
|
try:
|
191
277
|
if hasattr(module, 'ChatAnthropic'):
|
@@ -211,6 +297,7 @@ class LLMTracerMixin:
|
|
211
297
|
openai.beta.threads.runs.create(...) are automatically traced.
|
212
298
|
"""
|
213
299
|
# Make sure openai_module has a 'beta' attribute
|
300
|
+
openai_module.api_type = "openai"
|
214
301
|
if not hasattr(openai_module, "beta"):
|
215
302
|
return
|
216
303
|
|
@@ -239,7 +326,6 @@ class LLMTracerMixin:
|
|
239
326
|
if hasattr(runs_obj, method_name):
|
240
327
|
self.wrap_method(runs_obj, method_name)
|
241
328
|
|
242
|
-
|
243
329
|
def patch_anthropic_methods(self, module):
|
244
330
|
if hasattr(module, "Anthropic"):
|
245
331
|
client_class = getattr(module, "Anthropic")
|
@@ -342,6 +428,7 @@ class LLMTracerMixin:
|
|
342
428
|
return await self.trace_llm_call(
|
343
429
|
original_create, *args, **kwargs
|
344
430
|
)
|
431
|
+
|
345
432
|
client_self.chat.completions.create = wrapped_create
|
346
433
|
else:
|
347
434
|
# Patch sync methods for OpenAI
|
@@ -353,10 +440,11 @@ class LLMTracerMixin:
|
|
353
440
|
return self.trace_llm_call_sync(
|
354
441
|
original_create, *args, **kwargs
|
355
442
|
)
|
443
|
+
|
356
444
|
client_self.chat.completions.create = wrapped_create
|
357
445
|
|
358
446
|
setattr(client_class, "__init__", patched_init)
|
359
|
-
|
447
|
+
|
360
448
|
def wrap_langchain_openai_method(self, client_class, method_name):
|
361
449
|
method = method_name.split(".")[-1]
|
362
450
|
original_init = getattr(client_class, method)
|
@@ -372,14 +460,14 @@ class LLMTracerMixin:
|
|
372
460
|
return self.trace_llm_call_sync(original_init, *args, **kwargs)
|
373
461
|
|
374
462
|
setattr(client_class, method, patched_init)
|
375
|
-
|
463
|
+
|
376
464
|
def wrap_langchain_anthropic_method(self, client_class, method_name):
|
377
465
|
original_init = getattr(client_class, method_name)
|
378
466
|
|
379
467
|
@functools.wraps(original_init)
|
380
468
|
def patched_init(*args, **kwargs):
|
381
469
|
is_async = "AsyncChatAnthropic" in client_class.__name__
|
382
|
-
|
470
|
+
|
383
471
|
if is_async:
|
384
472
|
return self.trace_llm_call(original_init, *args, **kwargs)
|
385
473
|
else:
|
@@ -447,20 +535,20 @@ class LLMTracerMixin:
|
|
447
535
|
self.patches.append((obj, method_name, original_method))
|
448
536
|
|
449
537
|
def create_llm_component(
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
538
|
+
self,
|
539
|
+
component_id,
|
540
|
+
hash_id,
|
541
|
+
name,
|
542
|
+
llm_type,
|
543
|
+
version,
|
544
|
+
memory_used,
|
545
|
+
start_time,
|
546
|
+
input_data,
|
547
|
+
output_data,
|
548
|
+
cost={},
|
549
|
+
usage={},
|
550
|
+
error=None,
|
551
|
+
parameters={},
|
464
552
|
):
|
465
553
|
# Update total metrics
|
466
554
|
self.total_tokens += usage.get("total_tokens", 0)
|
@@ -476,7 +564,7 @@ class LLMTracerMixin:
|
|
476
564
|
for interaction in self.component_user_interaction.get(component_id, []):
|
477
565
|
if interaction["interaction_type"] in ["input", "output"]:
|
478
566
|
input_output_interactions.append(interaction)
|
479
|
-
interactions.extend(input_output_interactions)
|
567
|
+
interactions.extend(input_output_interactions)
|
480
568
|
if self.auto_instrument_file_io:
|
481
569
|
file_io_interactions = []
|
482
570
|
for interaction in self.component_user_interaction.get(component_id, []):
|
@@ -502,13 +590,24 @@ class LLMTracerMixin:
|
|
502
590
|
list(parameters_to_display.items())[: self.MAX_PARAMETERS_TO_DISPLAY]
|
503
591
|
)
|
504
592
|
|
505
|
-
#
|
506
|
-
|
593
|
+
# Set the Context and GT
|
594
|
+
span_gt = None
|
595
|
+
span_context = None
|
596
|
+
if name in self.span_attributes_dict:
|
597
|
+
span_gt = self.span_attributes_dict[name].gt
|
598
|
+
span_context = self.span_attributes_dict[name].context
|
599
|
+
|
600
|
+
logger.debug(f"span context {span_context}, span_gt {span_gt}")
|
601
|
+
|
602
|
+
# Tags
|
507
603
|
tags = []
|
508
604
|
if name in self.span_attributes_dict:
|
509
605
|
tags = self.span_attributes_dict[name].tags or []
|
510
606
|
|
511
|
-
#
|
607
|
+
# Get End Time
|
608
|
+
end_time = datetime.now().astimezone().isoformat()
|
609
|
+
|
610
|
+
# Metrics
|
512
611
|
metrics = []
|
513
612
|
if name in self.span_attributes_dict:
|
514
613
|
raw_metrics = self.span_attributes_dict[name].metrics or []
|
@@ -517,17 +616,32 @@ class LLMTracerMixin:
|
|
517
616
|
counter = sum(1 for x in self.visited_metrics if x.startswith(base_metric_name))
|
518
617
|
metric_name = f'{base_metric_name}_{counter}' if counter > 0 else base_metric_name
|
519
618
|
self.visited_metrics.append(metric_name)
|
520
|
-
metric["name"] = metric_name
|
619
|
+
metric["name"] = metric_name
|
521
620
|
metrics.append(metric)
|
522
621
|
|
622
|
+
# TODO TO check i/p and o/p is according or not
|
623
|
+
input = input_data["args"] if hasattr(input_data, "args") else input_data
|
624
|
+
output = output_data.output_response if output_data else None
|
625
|
+
#print("Prompt input:",input)
|
626
|
+
prompt = self.convert_to_content(input)
|
627
|
+
#print("Prompt Output: ",prompt)
|
628
|
+
#print("Response input: ",output)
|
629
|
+
response = self.convert_to_content(output)
|
630
|
+
#print("Response output: ",response)
|
631
|
+
|
632
|
+
# TODO: Execute & Add the User requested metrics here
|
633
|
+
formatted_metric = BaseTracer.get_formatted_metric(self.span_attributes_dict, self.project_id, name, prompt, span_context, response, span_gt)
|
634
|
+
if formatted_metric is not None:
|
635
|
+
metrics.append(formatted_metric)
|
636
|
+
|
523
637
|
component = {
|
524
638
|
"id": component_id,
|
525
639
|
"hash_id": hash_id,
|
526
640
|
"source_hash_id": None,
|
527
641
|
"type": "llm",
|
528
642
|
"name": name,
|
529
|
-
"start_time": start_time
|
530
|
-
"end_time":
|
643
|
+
"start_time": start_time,
|
644
|
+
"end_time": end_time,
|
531
645
|
"error": error,
|
532
646
|
"parent_id": self.current_agent_id.get(),
|
533
647
|
"info": {
|
@@ -541,10 +655,8 @@ class LLMTracerMixin:
|
|
541
655
|
},
|
542
656
|
"extra_info": parameters,
|
543
657
|
"data": {
|
544
|
-
"input":
|
545
|
-
|
546
|
-
),
|
547
|
-
"output": output_data.output_response if output_data else None,
|
658
|
+
"input": input,
|
659
|
+
"output": output,
|
548
660
|
"memory_used": memory_used,
|
549
661
|
},
|
550
662
|
"metrics": metrics,
|
@@ -552,19 +664,79 @@ class LLMTracerMixin:
|
|
552
664
|
"interactions": interactions,
|
553
665
|
}
|
554
666
|
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
component["data"]["gt"] = span_gt
|
559
|
-
span_context = self.span_attributes_dict[name].context
|
560
|
-
if span_context:
|
561
|
-
component["data"]["context"] = span_context
|
667
|
+
# Assign context and gt if available
|
668
|
+
component["data"]["gt"] = span_gt
|
669
|
+
component["data"]["context"] = span_context
|
562
670
|
|
563
671
|
# Reset the SpanAttributes context variable
|
564
672
|
self.span_attributes_dict[name] = SpanAttributes(name)
|
565
673
|
|
566
674
|
return component
|
567
675
|
|
676
|
+
# def convert_to_content(self, input_data):
|
677
|
+
# if isinstance(input_data, dict):
|
678
|
+
# messages = input_data.get("kwargs", {}).get("messages", [])
|
679
|
+
# elif isinstance(input_data, list):
|
680
|
+
# messages = input_data
|
681
|
+
# else:
|
682
|
+
# return ""
|
683
|
+
# return "\n".join(process_content(msg.get("content", "")) for msg in messages if msg.get("content"))
|
684
|
+
|
685
|
+
def convert_to_content(self, input_data):
|
686
|
+
if isinstance(input_data, dict):
|
687
|
+
messages = input_data.get("kwargs", {}).get("messages", [])
|
688
|
+
elif isinstance(input_data, list):
|
689
|
+
if len(input_data)>0 and isinstance(input_data[0]['content'],ChatResponse):
|
690
|
+
extracted_messages = []
|
691
|
+
|
692
|
+
for item in input_data:
|
693
|
+
chat_response = item.get('content')
|
694
|
+
if hasattr(chat_response, 'message') and hasattr(chat_response.message, 'blocks'):
|
695
|
+
for block in chat_response.message.blocks:
|
696
|
+
if hasattr(block, 'text'):
|
697
|
+
extracted_messages.append(block.text)
|
698
|
+
messages=extracted_messages
|
699
|
+
if isinstance(messages,list):
|
700
|
+
return "\n".join(messages)
|
701
|
+
|
702
|
+
#messages=[msg["content"] for msg in input_data if isinstance(msg, dict) and "content" in msg]
|
703
|
+
#messages = [msg["content"].message for msg in input_data if isinstance(msg, dict) and "content" in msg and isinstance(msg["content"], ChatResponse)]
|
704
|
+
else:
|
705
|
+
messages = input_data
|
706
|
+
elif isinstance(input_data,ChatResponse):
|
707
|
+
messages=input_data['content']
|
708
|
+
else:
|
709
|
+
return ""
|
710
|
+
res=""
|
711
|
+
# try:
|
712
|
+
res="\n".join(msg.get("content", "").strip() for msg in messages if msg.get("content"))
|
713
|
+
# except Exception as e:
|
714
|
+
# print("Exception occured for: ",e)
|
715
|
+
# print("Input: ",input_data,"Meeage: ",messages)
|
716
|
+
# # import sys
|
717
|
+
# # sys.exit()
|
718
|
+
return res
|
719
|
+
|
720
|
+
def process_content(content):
|
721
|
+
if isinstance(content, str):
|
722
|
+
return content.strip()
|
723
|
+
elif isinstance(content, list):
|
724
|
+
# Handle list of content blocks
|
725
|
+
text_parts = []
|
726
|
+
for block in content:
|
727
|
+
if hasattr(block, 'text'):
|
728
|
+
# Handle TextBlock-like objects
|
729
|
+
text_parts.append(block.text.strip())
|
730
|
+
elif isinstance(block, dict) and 'text' in block:
|
731
|
+
# Handle dictionary with text field
|
732
|
+
text_parts.append(block['text'].strip())
|
733
|
+
return " ".join(text_parts)
|
734
|
+
elif isinstance(content, dict):
|
735
|
+
# Handle dictionary content
|
736
|
+
return content.get('text', '').strip()
|
737
|
+
return ""
|
738
|
+
|
739
|
+
|
568
740
|
def start_component(self, component_id):
|
569
741
|
"""Start tracking network calls for a component"""
|
570
742
|
self.component_network_calls[component_id] = []
|
@@ -582,7 +754,7 @@ class LLMTracerMixin:
|
|
582
754
|
if not self.auto_instrument_llm:
|
583
755
|
return await original_func(*args, **kwargs)
|
584
756
|
|
585
|
-
start_time = datetime.now().astimezone()
|
757
|
+
start_time = datetime.now().astimezone().isoformat()
|
586
758
|
start_memory = psutil.Process().memory_info().rss
|
587
759
|
component_id = str(uuid.uuid4())
|
588
760
|
hash_id = generate_unique_hash(original_func, args, kwargs)
|
@@ -605,7 +777,8 @@ class LLMTracerMixin:
|
|
605
777
|
if stream:
|
606
778
|
prompt_messages = kwargs['messages']
|
607
779
|
# Create response message for streaming case
|
608
|
-
response_message = {"role": "assistant", "content": result} if result else {"role": "assistant",
|
780
|
+
response_message = {"role": "assistant", "content": result} if result else {"role": "assistant",
|
781
|
+
"content": ""}
|
609
782
|
token_usage = num_tokens_from_messages(model_name, prompt_messages, response_message)
|
610
783
|
else:
|
611
784
|
token_usage = extract_token_usage(result)
|
@@ -685,7 +858,7 @@ class LLMTracerMixin:
|
|
685
858
|
if not self.auto_instrument_llm:
|
686
859
|
return original_func(*args, **kwargs)
|
687
860
|
|
688
|
-
start_time = datetime.now().astimezone()
|
861
|
+
start_time = datetime.now().astimezone().isoformat()
|
689
862
|
component_id = str(uuid.uuid4())
|
690
863
|
hash_id = generate_unique_hash(original_func, args, kwargs)
|
691
864
|
|
@@ -707,13 +880,14 @@ class LLMTracerMixin:
|
|
707
880
|
|
708
881
|
# Extract token usage and calculate cost
|
709
882
|
model_name = extract_model_name(args, kwargs, result)
|
710
|
-
|
883
|
+
|
711
884
|
if 'stream' in kwargs:
|
712
885
|
stream = kwargs['stream']
|
713
886
|
if stream:
|
714
887
|
prompt_messages = kwargs['messages']
|
715
888
|
# Create response message for streaming case
|
716
|
-
response_message = {"role": "assistant", "content": result} if result else {"role": "assistant",
|
889
|
+
response_message = {"role": "assistant", "content": result} if result else {"role": "assistant",
|
890
|
+
"content": ""}
|
717
891
|
token_usage = num_tokens_from_messages(model_name, prompt_messages, response_message)
|
718
892
|
else:
|
719
893
|
token_usage = extract_token_usage(result)
|
@@ -786,12 +960,12 @@ class LLMTracerMixin:
|
|
786
960
|
raise
|
787
961
|
|
788
962
|
def trace_llm(
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
963
|
+
self,
|
964
|
+
name: str = None,
|
965
|
+
tags: List[str] = [],
|
966
|
+
metadata: Dict[str, Any] = {},
|
967
|
+
metrics: List[Dict[str, Any]] = [],
|
968
|
+
feedback: Optional[Any] = None,
|
795
969
|
):
|
796
970
|
if name not in self.span_attributes_dict:
|
797
971
|
self.span_attributes_dict[name] = SpanAttributes(name)
|
@@ -817,7 +991,7 @@ class LLMTracerMixin:
|
|
817
991
|
logger.error(f"Validation Error: {e}")
|
818
992
|
except Exception as e:
|
819
993
|
logger.error(f"Error adding metric: {e}")
|
820
|
-
|
994
|
+
|
821
995
|
if feedback:
|
822
996
|
self.span(name).add_feedback(feedback)
|
823
997
|
|
@@ -871,8 +1045,9 @@ class LLMTracerMixin:
|
|
871
1045
|
|
872
1046
|
if error_info:
|
873
1047
|
llm_component["error"] = error_info["error"]
|
874
|
-
|
1048
|
+
|
875
1049
|
self.end_component(component_id)
|
1050
|
+
|
876
1051
|
# metrics
|
877
1052
|
metrics = []
|
878
1053
|
if name in self.span_attributes_dict:
|
@@ -882,7 +1057,7 @@ class LLMTracerMixin:
|
|
882
1057
|
counter = sum(1 for x in self.visited_metrics if x.startswith(base_metric_name))
|
883
1058
|
metric_name = f'{base_metric_name}_{counter}' if counter > 0 else base_metric_name
|
884
1059
|
self.visited_metrics.append(metric_name)
|
885
|
-
metric["name"] = metric_name
|
1060
|
+
metric["name"] = metric_name
|
886
1061
|
metrics.append(metric)
|
887
1062
|
llm_component["metrics"] = metrics
|
888
1063
|
if parent_agent_id:
|
@@ -946,9 +1121,9 @@ class LLMTracerMixin:
|
|
946
1121
|
counter = sum(1 for x in self.visited_metrics if x.startswith(base_metric_name))
|
947
1122
|
metric_name = f'{base_metric_name}_{counter}' if counter > 0 else base_metric_name
|
948
1123
|
self.visited_metrics.append(metric_name)
|
949
|
-
metric["name"] = metric_name
|
1124
|
+
metric["name"] = metric_name
|
950
1125
|
metrics.append(metric)
|
951
|
-
llm_component["metrics"] = metrics
|
1126
|
+
llm_component["metrics"] = metrics
|
952
1127
|
if parent_agent_id:
|
953
1128
|
children = self.agent_children.get()
|
954
1129
|
children.append(llm_component)
|
@@ -119,9 +119,6 @@ class AgenticTracing(
|
|
119
119
|
self.component_network_calls = {} # Store network calls per component
|
120
120
|
self.component_user_interaction = {}
|
121
121
|
|
122
|
-
# Create output directory if it doesn't exist
|
123
|
-
self.output_dir = Path("./traces") # Using default traces directory
|
124
|
-
self.output_dir.mkdir(exist_ok=True)
|
125
122
|
|
126
123
|
def start_component(self, component_id: str):
|
127
124
|
"""Start tracking network calls for a component"""
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
3
|
+
import requests
|
4
|
+
|
5
|
+
from ragaai_catalyst import RagaAICatalyst
|
6
|
+
|
7
|
+
logger = logging.getLogger(__name__)
|
8
|
+
logging_level = (
|
9
|
+
logger.setLevel(logging.DEBUG)
|
10
|
+
if os.getenv("DEBUG")
|
11
|
+
else logger.setLevel(logging.INFO)
|
12
|
+
)
|
13
|
+
|
14
|
+
def calculate_metric(project_id, metric_name, model, provider, prompt, response, context, expected_response=None):
|
15
|
+
user_id = "1"
|
16
|
+
org_domain = "raga"
|
17
|
+
|
18
|
+
headers = {
|
19
|
+
"Authorization": f"Bearer {os.getenv('RAGAAI_CATALYST_TOKEN')}",
|
20
|
+
"X-Project-Id": str(project_id), # TODO to change it to project_id
|
21
|
+
"Content-Type": "application/json"
|
22
|
+
}
|
23
|
+
|
24
|
+
payload = {
|
25
|
+
"data": [
|
26
|
+
{
|
27
|
+
"metric_name": metric_name,
|
28
|
+
"metric_config": {
|
29
|
+
"threshold": {
|
30
|
+
"isEditable": True,
|
31
|
+
"lte": 0.3
|
32
|
+
},
|
33
|
+
"model": model,
|
34
|
+
"orgDomain": org_domain,
|
35
|
+
"provider": provider,
|
36
|
+
"user_id": user_id,
|
37
|
+
"job_id": 1,
|
38
|
+
"metric_name": metric_name,
|
39
|
+
"request_id": 1
|
40
|
+
},
|
41
|
+
"trace_object": {
|
42
|
+
"Data": {
|
43
|
+
"DocId": "doc-1",
|
44
|
+
"Prompt": prompt,
|
45
|
+
"Response": response,
|
46
|
+
"Context": context,
|
47
|
+
"ExpectedResponse": "",
|
48
|
+
"ExpectedContext": expected_response,
|
49
|
+
"Chat": "",
|
50
|
+
"Instructions": "",
|
51
|
+
"SystemPrompt": "",
|
52
|
+
"Text": ""
|
53
|
+
},
|
54
|
+
"claims": {},
|
55
|
+
"last_computed_metrics": {
|
56
|
+
metric_name: {
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
}
|
61
|
+
]
|
62
|
+
}
|
63
|
+
|
64
|
+
try:
|
65
|
+
BASE_URL = RagaAICatalyst.BASE_URL
|
66
|
+
response = requests.post(f"{BASE_URL}/v1/llm/calculate-metric", headers=headers, json=payload, timeout=30)
|
67
|
+
logger.debug(f"Metric calculation response status {response.status_code}")
|
68
|
+
response.raise_for_status()
|
69
|
+
return response.json()
|
70
|
+
except requests.exceptions.RequestException as e:
|
71
|
+
logger.debug(f"Error in calculate-metric api: {e}, payload: {payload}")
|
72
|
+
raise Exception(f"Error in calculate-metric: {e}")
|