ragaai-catalyst 2.1b0__py3-none-any.whl → 2.1b1__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 +1 -0
- ragaai_catalyst/dataset.py +1 -4
- ragaai_catalyst/evaluation.py +4 -5
- ragaai_catalyst/guard_executor.py +97 -0
- ragaai_catalyst/guardrails_manager.py +41 -15
- ragaai_catalyst/internal_api_completion.py +1 -1
- ragaai_catalyst/prompt_manager.py +7 -2
- ragaai_catalyst/ragaai_catalyst.py +1 -1
- ragaai_catalyst/synthetic_data_generation.py +7 -0
- ragaai_catalyst/tracers/__init__.py +1 -1
- ragaai_catalyst/tracers/agentic_tracing/__init__.py +3 -0
- ragaai_catalyst/tracers/agentic_tracing/agent_tracer.py +422 -0
- ragaai_catalyst/tracers/agentic_tracing/agentic_tracing.py +198 -0
- ragaai_catalyst/tracers/agentic_tracing/base.py +376 -0
- ragaai_catalyst/tracers/agentic_tracing/data_structure.py +248 -0
- ragaai_catalyst/tracers/agentic_tracing/examples/FinancialAnalysisSystem.ipynb +536 -0
- ragaai_catalyst/tracers/agentic_tracing/examples/GameActivityEventPlanner.ipynb +134 -0
- ragaai_catalyst/tracers/agentic_tracing/examples/TravelPlanner.ipynb +563 -0
- ragaai_catalyst/tracers/agentic_tracing/file_name_tracker.py +46 -0
- ragaai_catalyst/tracers/agentic_tracing/llm_tracer.py +808 -0
- ragaai_catalyst/tracers/agentic_tracing/network_tracer.py +286 -0
- ragaai_catalyst/tracers/agentic_tracing/sample.py +197 -0
- ragaai_catalyst/tracers/agentic_tracing/tool_tracer.py +247 -0
- ragaai_catalyst/tracers/agentic_tracing/unique_decorator.py +165 -0
- ragaai_catalyst/tracers/agentic_tracing/unique_decorator_test.py +172 -0
- ragaai_catalyst/tracers/agentic_tracing/upload_agentic_traces.py +187 -0
- ragaai_catalyst/tracers/agentic_tracing/upload_code.py +115 -0
- ragaai_catalyst/tracers/agentic_tracing/user_interaction_tracer.py +43 -0
- ragaai_catalyst/tracers/agentic_tracing/utils/__init__.py +3 -0
- ragaai_catalyst/tracers/agentic_tracing/utils/api_utils.py +18 -0
- ragaai_catalyst/tracers/agentic_tracing/utils/data_classes.py +61 -0
- ragaai_catalyst/tracers/agentic_tracing/utils/generic.py +32 -0
- ragaai_catalyst/tracers/agentic_tracing/utils/llm_utils.py +177 -0
- ragaai_catalyst/tracers/agentic_tracing/utils/model_costs.json +7823 -0
- ragaai_catalyst/tracers/agentic_tracing/utils/trace_utils.py +74 -0
- ragaai_catalyst/tracers/agentic_tracing/zip_list_of_unique_files.py +342 -0
- ragaai_catalyst/tracers/exporters/raga_exporter.py +1 -7
- ragaai_catalyst/tracers/tracer.py +30 -4
- ragaai_catalyst/tracers/upload_traces.py +127 -0
- ragaai_catalyst-2.1b1.dist-info/METADATA +43 -0
- ragaai_catalyst-2.1b1.dist-info/RECORD +56 -0
- {ragaai_catalyst-2.1b0.dist-info → ragaai_catalyst-2.1b1.dist-info}/WHEEL +1 -1
- ragaai_catalyst-2.1b0.dist-info/METADATA +0 -295
- ragaai_catalyst-2.1b0.dist-info/RECORD +0 -28
- {ragaai_catalyst-2.1b0.dist-info → ragaai_catalyst-2.1b1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,187 @@
|
|
1
|
+
import requests
|
2
|
+
import json
|
3
|
+
import os
|
4
|
+
from datetime import datetime
|
5
|
+
|
6
|
+
|
7
|
+
class UploadAgenticTraces:
|
8
|
+
def __init__(self,
|
9
|
+
json_file_path,
|
10
|
+
project_name,
|
11
|
+
project_id,
|
12
|
+
dataset_name,
|
13
|
+
user_detail,
|
14
|
+
base_url):
|
15
|
+
self.json_file_path = json_file_path
|
16
|
+
self.project_name = project_name
|
17
|
+
self.project_id = project_id
|
18
|
+
self.dataset_name = dataset_name
|
19
|
+
self.user_detail = user_detail
|
20
|
+
self.base_url = base_url
|
21
|
+
self.timeout = 99999
|
22
|
+
|
23
|
+
def _create_dataset_schema_with_trace(self):
|
24
|
+
SCHEMA_MAPPING_NEW = {
|
25
|
+
"trace_id": {"columnType": "traceId"},
|
26
|
+
"trace_uri": {"columnType": "traceUri"},
|
27
|
+
"prompt": {"columnType": "prompt"},
|
28
|
+
"response":{"columnType": "response"},
|
29
|
+
"context": {"columnType": "context"},
|
30
|
+
"llm_model": {"columnType":"pipeline"},
|
31
|
+
"recorded_on": {"columnType": "metadata"},
|
32
|
+
"embed_model": {"columnType":"pipeline"},
|
33
|
+
"log_source": {"columnType": "metadata"},
|
34
|
+
"vector_store":{"columnType":"pipeline"},
|
35
|
+
"feedback": {"columnType":"feedBack"}
|
36
|
+
}
|
37
|
+
def make_request():
|
38
|
+
headers = {
|
39
|
+
"Content-Type": "application/json",
|
40
|
+
"Authorization": f"Bearer {os.getenv('RAGAAI_CATALYST_TOKEN')}",
|
41
|
+
"X-Project-Name": self.project_name,
|
42
|
+
}
|
43
|
+
payload = json.dumps({
|
44
|
+
"datasetName": self.dataset_name,
|
45
|
+
# "schemaMapping": SCHEMA_MAPPING_NEW,
|
46
|
+
"traceFolderUrl": None,
|
47
|
+
})
|
48
|
+
response = requests.request("POST",
|
49
|
+
f"{self.base_url}/v1/llm/dataset/logs",
|
50
|
+
headers=headers,
|
51
|
+
data=payload,
|
52
|
+
timeout=self.timeout
|
53
|
+
)
|
54
|
+
|
55
|
+
return response
|
56
|
+
|
57
|
+
response = make_request()
|
58
|
+
|
59
|
+
if response.status_code == 401:
|
60
|
+
# get_token() # Fetch a new token and set it in the environment
|
61
|
+
response = make_request() # Retry the request
|
62
|
+
if response.status_code != 200:
|
63
|
+
return response.status_code
|
64
|
+
return response.status_code
|
65
|
+
|
66
|
+
|
67
|
+
def _get_presigned_url(self):
|
68
|
+
payload = json.dumps({
|
69
|
+
"datasetName": self.dataset_name,
|
70
|
+
"numFiles": 1,
|
71
|
+
})
|
72
|
+
headers = {
|
73
|
+
"Content-Type": "application/json",
|
74
|
+
"Authorization": f"Bearer {os.getenv('RAGAAI_CATALYST_TOKEN')}",
|
75
|
+
"X-Project-Name": self.project_name,
|
76
|
+
}
|
77
|
+
|
78
|
+
try:
|
79
|
+
response = requests.request("GET",
|
80
|
+
f"{self.base_url}/v1/llm/presigned-url",
|
81
|
+
headers=headers,
|
82
|
+
data=payload,
|
83
|
+
timeout=self.timeout)
|
84
|
+
if response.status_code == 200:
|
85
|
+
presignedUrls = response.json()["data"]["presignedUrls"][0]
|
86
|
+
return presignedUrls
|
87
|
+
except requests.exceptions.RequestException as e:
|
88
|
+
print(f"Error while getting presigned url: {e}")
|
89
|
+
return None
|
90
|
+
|
91
|
+
def _put_presigned_url(self, presignedUrl, filename):
|
92
|
+
headers = {
|
93
|
+
"Content-Type": "application/json",
|
94
|
+
}
|
95
|
+
|
96
|
+
if "blob.core.windows.net" in presignedUrl: # Azure
|
97
|
+
headers["x-ms-blob-type"] = "BlockBlob"
|
98
|
+
print(f"Uploading agentic traces...")
|
99
|
+
try:
|
100
|
+
with open(filename) as f:
|
101
|
+
payload = f.read().replace("\n", "").replace("\r", "").encode()
|
102
|
+
except Exception as e:
|
103
|
+
print(f"Error while reading file: {e}")
|
104
|
+
return None
|
105
|
+
try:
|
106
|
+
response = requests.request("PUT",
|
107
|
+
presignedUrl,
|
108
|
+
headers=headers,
|
109
|
+
data=payload,
|
110
|
+
timeout=self.timeout)
|
111
|
+
if response.status_code != 200 or response.status_code != 201:
|
112
|
+
return response, response.status_code
|
113
|
+
except requests.exceptions.RequestException as e:
|
114
|
+
print(f"Error while uploading to presigned url: {e}")
|
115
|
+
return None
|
116
|
+
|
117
|
+
def insert_traces(self, presignedUrl):
|
118
|
+
headers = {
|
119
|
+
"Authorization": f"Bearer {os.getenv('RAGAAI_CATALYST_TOKEN')}",
|
120
|
+
"Content-Type": "application/json",
|
121
|
+
"X-Project-Name": self.project_name,
|
122
|
+
}
|
123
|
+
payload = json.dumps({
|
124
|
+
"datasetName": self.dataset_name,
|
125
|
+
"presignedUrl": presignedUrl,
|
126
|
+
"datasetSpans": self._get_dataset_spans(), #Extra key for agentic traces
|
127
|
+
})
|
128
|
+
try:
|
129
|
+
response = requests.request("POST",
|
130
|
+
f"{self.base_url}/v1/llm/insert/trace",
|
131
|
+
headers=headers,
|
132
|
+
data=payload,
|
133
|
+
timeout=self.timeout)
|
134
|
+
except requests.exceptions.RequestException as e:
|
135
|
+
print(f"Error while inserting traces: {e}")
|
136
|
+
return None
|
137
|
+
|
138
|
+
def _get_dataset_spans(self):
|
139
|
+
try:
|
140
|
+
with open(self.json_file_path) as f:
|
141
|
+
data = json.load(f)
|
142
|
+
except Exception as e:
|
143
|
+
print(f"Error while reading file: {e}")
|
144
|
+
return None
|
145
|
+
try:
|
146
|
+
spans = data["data"][0]["spans"]
|
147
|
+
datasetSpans = []
|
148
|
+
for span in spans:
|
149
|
+
if span["type"] != "agent":
|
150
|
+
existing_span = next((s for s in datasetSpans if s["spanHash"] == span["hash_id"]), None)
|
151
|
+
if existing_span is None:
|
152
|
+
datasetSpans.append({
|
153
|
+
"spanId": span["id"],
|
154
|
+
"spanName": span["name"],
|
155
|
+
"spanHash": span["hash_id"],
|
156
|
+
"spanType": span["type"],
|
157
|
+
})
|
158
|
+
else:
|
159
|
+
datasetSpans.append({
|
160
|
+
"spanId": span["id"],
|
161
|
+
"spanName": span["name"],
|
162
|
+
"spanHash": span["hash_id"],
|
163
|
+
"spanType": span["type"],
|
164
|
+
})
|
165
|
+
children = span["data"]["children"]
|
166
|
+
for child in children:
|
167
|
+
existing_span = next((s for s in datasetSpans if s["spanHash"] == child["hash_id"]), None)
|
168
|
+
if existing_span is None:
|
169
|
+
datasetSpans.append({
|
170
|
+
"spanId": child["id"],
|
171
|
+
"spanName": child["name"],
|
172
|
+
"spanHash": child["hash_id"],
|
173
|
+
"spanType": child["type"],
|
174
|
+
})
|
175
|
+
return datasetSpans
|
176
|
+
except Exception as e:
|
177
|
+
print(f"Error while reading dataset spans: {e}")
|
178
|
+
return None
|
179
|
+
|
180
|
+
def upload_agentic_traces(self):
|
181
|
+
self._create_dataset_schema_with_trace()
|
182
|
+
presignedUrl = self._get_presigned_url()
|
183
|
+
if presignedUrl is None:
|
184
|
+
return
|
185
|
+
self._put_presigned_url(presignedUrl, self.json_file_path)
|
186
|
+
self.insert_traces(presignedUrl)
|
187
|
+
print("Agentic Traces uploaded")
|
@@ -0,0 +1,115 @@
|
|
1
|
+
from aiohttp import payload
|
2
|
+
import requests
|
3
|
+
import json
|
4
|
+
import os
|
5
|
+
import logging
|
6
|
+
logger = logging.getLogger(__name__)
|
7
|
+
|
8
|
+
def upload_code(hash_id, zip_path, project_name, dataset_name):
|
9
|
+
code_hashes_list = _fetch_dataset_code_hashes(project_name, dataset_name)
|
10
|
+
|
11
|
+
if hash_id not in code_hashes_list:
|
12
|
+
presigned_url = _fetch_presigned_url(project_name, dataset_name)
|
13
|
+
_put_zip_presigned_url(project_name, presigned_url, zip_path)
|
14
|
+
|
15
|
+
response = _insert_code(dataset_name, hash_id, presigned_url, project_name)
|
16
|
+
return response
|
17
|
+
else:
|
18
|
+
return "Code already exists"
|
19
|
+
|
20
|
+
def _fetch_dataset_code_hashes(project_name, dataset_name):
|
21
|
+
payload = {}
|
22
|
+
headers = {
|
23
|
+
"Authorization": f"Bearer {os.getenv('RAGAAI_CATALYST_TOKEN')}",
|
24
|
+
"X-Project-Name": project_name,
|
25
|
+
}
|
26
|
+
|
27
|
+
try:
|
28
|
+
response = requests.request("GET",
|
29
|
+
f"{os.getenv('RAGAAI_CATALYST_BASE_URL')}/v2/llm/dataset/code?datasetName={dataset_name}",
|
30
|
+
headers=headers,
|
31
|
+
data=payload,
|
32
|
+
timeout=99999)
|
33
|
+
|
34
|
+
if response.status_code == 200:
|
35
|
+
return response.json()["data"]["codeHashes"]
|
36
|
+
else:
|
37
|
+
raise Exception(f"Failed to fetch code hashes: {response.json()['message']}")
|
38
|
+
except requests.exceptions.RequestException as e:
|
39
|
+
logger.error(f"Failed to list datasets: {e}")
|
40
|
+
raise
|
41
|
+
|
42
|
+
def _fetch_presigned_url(project_name, dataset_name):
|
43
|
+
payload = json.dumps({
|
44
|
+
"datasetName": dataset_name,
|
45
|
+
"numFiles": 1,
|
46
|
+
"contentType": "application/zip"
|
47
|
+
})
|
48
|
+
|
49
|
+
headers = {
|
50
|
+
"Authorization": f"Bearer {os.getenv('RAGAAI_CATALYST_TOKEN')}",
|
51
|
+
"Content-Type": "application/json",
|
52
|
+
"X-Project-Name": project_name,
|
53
|
+
}
|
54
|
+
|
55
|
+
try:
|
56
|
+
response = requests.request("GET",
|
57
|
+
f"{os.getenv('RAGAAI_CATALYST_BASE_URL')}/v1/llm/presigned-url",
|
58
|
+
headers=headers,
|
59
|
+
data=payload,
|
60
|
+
timeout=99999)
|
61
|
+
|
62
|
+
if response.status_code == 200:
|
63
|
+
return response.json()["data"]["presignedUrls"][0]
|
64
|
+
else:
|
65
|
+
raise Exception(f"Failed to fetch code hashes: {response.json()['message']}")
|
66
|
+
except requests.exceptions.RequestException as e:
|
67
|
+
logger.error(f"Failed to list datasets: {e}")
|
68
|
+
raise
|
69
|
+
|
70
|
+
def _put_zip_presigned_url(project_name, presignedUrl, filename):
|
71
|
+
headers = {
|
72
|
+
"X-Project-Name": project_name,
|
73
|
+
"Content-Type": "application/zip",
|
74
|
+
}
|
75
|
+
|
76
|
+
if "blob.core.windows.net" in presignedUrl: # Azure
|
77
|
+
headers["x-ms-blob-type"] = "BlockBlob"
|
78
|
+
print(f"Uploading code...")
|
79
|
+
with open(filename, 'rb') as f:
|
80
|
+
payload = f.read()
|
81
|
+
|
82
|
+
response = requests.request("PUT",
|
83
|
+
presignedUrl,
|
84
|
+
headers=headers,
|
85
|
+
data=payload,
|
86
|
+
timeout=99999)
|
87
|
+
if response.status_code != 200 or response.status_code != 201:
|
88
|
+
return response, response.status_code
|
89
|
+
|
90
|
+
def _insert_code(dataset_name, hash_id, presigned_url, project_name):
|
91
|
+
payload = json.dumps({
|
92
|
+
"datasetName": dataset_name,
|
93
|
+
"codeHash": hash_id,
|
94
|
+
"presignedUrl": presigned_url
|
95
|
+
})
|
96
|
+
|
97
|
+
headers = {
|
98
|
+
'X-Project-Name': project_name,
|
99
|
+
'Content-Type': 'application/json',
|
100
|
+
'Authorization': f'Bearer {os.getenv("RAGAAI_CATALYST_TOKEN")}'
|
101
|
+
}
|
102
|
+
|
103
|
+
try:
|
104
|
+
response = requests.request("POST",
|
105
|
+
f"{os.getenv('RAGAAI_CATALYST_BASE_URL')}/v2/llm/dataset/code",
|
106
|
+
headers=headers,
|
107
|
+
data=payload,
|
108
|
+
timeout=99999)
|
109
|
+
if response.status_code == 200:
|
110
|
+
return response.json()["message"]
|
111
|
+
else:
|
112
|
+
raise Exception(f"Failed to insert code: {response.json()['message']}")
|
113
|
+
except requests.exceptions.RequestException as e:
|
114
|
+
logger.error(f"Failed to insert code: {e}")
|
115
|
+
raise
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import builtins
|
2
|
+
from datetime import datetime
|
3
|
+
import contextvars
|
4
|
+
import inspect
|
5
|
+
import uuid
|
6
|
+
|
7
|
+
class UserInteractionTracer:
|
8
|
+
def __init__(self, *args, **kwargs):
|
9
|
+
self.project_id = contextvars.ContextVar("project_id", default=None)
|
10
|
+
self.trace_id = contextvars.ContextVar("trace_id", default=None)
|
11
|
+
self.tracer = contextvars.ContextVar("tracer", default=None)
|
12
|
+
self.component_id = contextvars.ContextVar("component_id", default=None)
|
13
|
+
self.original_input = builtins.input
|
14
|
+
self.original_print = builtins.print
|
15
|
+
self.interactions = []
|
16
|
+
|
17
|
+
def traced_input(self, prompt=""):
|
18
|
+
# Get caller information
|
19
|
+
if prompt:
|
20
|
+
self.traced_print(prompt, end="")
|
21
|
+
try:
|
22
|
+
content = self.original_input()
|
23
|
+
except EOFError:
|
24
|
+
content = "" # Return empty string on EOF
|
25
|
+
|
26
|
+
self.interactions.append({
|
27
|
+
"id": str(uuid.uuid4()),
|
28
|
+
"interaction_type": "input",
|
29
|
+
"content": content,
|
30
|
+
"timestamp": datetime.now().isoformat()
|
31
|
+
})
|
32
|
+
return content
|
33
|
+
|
34
|
+
def traced_print(self, *args, **kwargs):
|
35
|
+
content = " ".join(str(arg) for arg in args)
|
36
|
+
|
37
|
+
self.interactions.append({
|
38
|
+
"id": str(uuid.uuid4()),
|
39
|
+
"interaction_type": "output",
|
40
|
+
"content": content,
|
41
|
+
"timestamp": datetime.now().isoformat()
|
42
|
+
})
|
43
|
+
return self.original_print(*args, **kwargs)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import requests
|
2
|
+
|
3
|
+
def fetch_analysis_trace(base_url, trace_id):
|
4
|
+
"""
|
5
|
+
Fetches the analysis trace data from the server.
|
6
|
+
|
7
|
+
:param base_url: The base URL of the server (e.g., "http://localhost:3000").
|
8
|
+
:param trace_id: The ID of the trace to fetch.
|
9
|
+
:return: The JSON response from the server if successful, otherwise None.
|
10
|
+
"""
|
11
|
+
try:
|
12
|
+
url = f"{base_url}/api/analysis_traces/{trace_id}"
|
13
|
+
response = requests.get(url)
|
14
|
+
response.raise_for_status() # Raise an error for bad responses (4xx, 5xx)
|
15
|
+
return response.json()
|
16
|
+
except requests.exceptions.RequestException as e:
|
17
|
+
print(f"Error fetching analysis trace: {e}")
|
18
|
+
return None
|
@@ -0,0 +1,61 @@
|
|
1
|
+
from dataclasses import dataclass, field
|
2
|
+
from typing import Dict, List, Any, Optional
|
3
|
+
|
4
|
+
|
5
|
+
@dataclass
|
6
|
+
class ProjectInfo:
|
7
|
+
project_name: str
|
8
|
+
start_time: float
|
9
|
+
end_time: float = field(default=0)
|
10
|
+
duration: float = field(default=0)
|
11
|
+
total_cost: float = field(default=0)
|
12
|
+
total_tokens: int = field(default=0)
|
13
|
+
|
14
|
+
|
15
|
+
@dataclass
|
16
|
+
class SystemInfo:
|
17
|
+
project_id: int
|
18
|
+
os_name: str
|
19
|
+
os_version: str
|
20
|
+
python_version: str
|
21
|
+
cpu_info: str
|
22
|
+
memory_total: float
|
23
|
+
installed_packages: str
|
24
|
+
|
25
|
+
|
26
|
+
@dataclass
|
27
|
+
class LLMCall:
|
28
|
+
name: str
|
29
|
+
model_name: str
|
30
|
+
input_prompt: str
|
31
|
+
output_response: str
|
32
|
+
tool_call: Dict
|
33
|
+
token_usage: Dict[str, int]
|
34
|
+
cost: Dict[str, float]
|
35
|
+
start_time: float = field(default=0)
|
36
|
+
end_time: float = field(default=0)
|
37
|
+
duration: float = field(default=0)
|
38
|
+
|
39
|
+
|
40
|
+
@dataclass
|
41
|
+
class ToolCall:
|
42
|
+
name: str
|
43
|
+
input_parameters: Dict[str, Any]
|
44
|
+
output: Any
|
45
|
+
start_time: float
|
46
|
+
end_time: float
|
47
|
+
duration: float
|
48
|
+
errors: Optional[str] = None
|
49
|
+
|
50
|
+
|
51
|
+
@dataclass
|
52
|
+
class AgentCall:
|
53
|
+
name: str
|
54
|
+
input_parameters: Dict[str, Any]
|
55
|
+
output: Any
|
56
|
+
start_time: float
|
57
|
+
end_time: float
|
58
|
+
duration: float
|
59
|
+
tool_calls: List[Dict[str, Any]]
|
60
|
+
llm_calls: List[Dict[str, Any]]
|
61
|
+
errors: Optional[str] = None
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import os
|
2
|
+
import logging
|
3
|
+
|
4
|
+
|
5
|
+
def get_db_path():
|
6
|
+
db_filename = "trace_data.db"
|
7
|
+
|
8
|
+
# First, try the package directory
|
9
|
+
package_dir = os.path.dirname(os.path.abspath(__file__))
|
10
|
+
public_dir = os.path.join(package_dir, "..", "ui", "dist")
|
11
|
+
package_db_path = os.path.join(public_dir, db_filename)
|
12
|
+
|
13
|
+
# Ensure the directory exists
|
14
|
+
os.makedirs(os.path.dirname(package_db_path), exist_ok=True)
|
15
|
+
|
16
|
+
if os.path.exists(os.path.dirname(package_db_path)):
|
17
|
+
logging.debug(f"Using package database: {package_db_path}")
|
18
|
+
return f"sqlite:///{package_db_path}"
|
19
|
+
|
20
|
+
# Then, try the local directory
|
21
|
+
local_db_path = os.path.join(os.getcwd(), "agentneo", "ui", "dist", db_filename)
|
22
|
+
if os.path.exists(os.path.dirname(local_db_path)):
|
23
|
+
logging.debug(f"Using local database: {local_db_path}")
|
24
|
+
return f"sqlite:///{local_db_path}"
|
25
|
+
|
26
|
+
# Finally, try the local "/dist" directory
|
27
|
+
local_dist_path = os.path.join(os.getcwd(), "dist", db_filename)
|
28
|
+
if os.path.exists(os.path.dirname(local_dist_path)):
|
29
|
+
logging.debug(f"Using local database: {local_dist_path}")
|
30
|
+
return f"sqlite:///{local_dist_path}"
|
31
|
+
|
32
|
+
return f"sqlite:///{package_db_path}"
|
@@ -0,0 +1,177 @@
|
|
1
|
+
from .data_classes import LLMCall
|
2
|
+
from .trace_utils import (
|
3
|
+
calculate_cost,
|
4
|
+
convert_usage_to_dict,
|
5
|
+
load_model_costs,
|
6
|
+
)
|
7
|
+
from importlib import resources
|
8
|
+
import json
|
9
|
+
import os
|
10
|
+
|
11
|
+
|
12
|
+
# Load the Json configuration
|
13
|
+
try:
|
14
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
15
|
+
model_costs_path = os.path.join(current_dir, "model_costs.json")
|
16
|
+
with open(model_costs_path, "r") as file:
|
17
|
+
config = json.load(file)
|
18
|
+
except FileNotFoundError:
|
19
|
+
from importlib.resources import files
|
20
|
+
with (files("") / "model_costs.json").open("r") as file:
|
21
|
+
config = json.load(file)
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
def extract_llm_output(result):
|
26
|
+
# Initialize variables
|
27
|
+
model_name = None
|
28
|
+
output_response = ""
|
29
|
+
function_call = None
|
30
|
+
tool_call = None
|
31
|
+
token_usage = {}
|
32
|
+
cost = {}
|
33
|
+
|
34
|
+
# Try to get model_name from result or result.content
|
35
|
+
model_name = None
|
36
|
+
if hasattr(result, "model"):
|
37
|
+
model_name = result.model
|
38
|
+
elif hasattr(result, "content"):
|
39
|
+
try:
|
40
|
+
content_dict = json.loads(result.content)
|
41
|
+
model_name = content_dict.get("model", None)
|
42
|
+
except (json.JSONDecodeError, TypeError):
|
43
|
+
model_name = None
|
44
|
+
|
45
|
+
# Try to get choices from result or result.content
|
46
|
+
choices = None
|
47
|
+
if hasattr(result, "choices"):
|
48
|
+
choices = result.choices
|
49
|
+
elif hasattr(result, "content"):
|
50
|
+
try:
|
51
|
+
content_dict = json.loads(result.content)
|
52
|
+
choices = content_dict.get("choices", None)
|
53
|
+
except (json.JSONDecodeError, TypeError):
|
54
|
+
choices = None
|
55
|
+
|
56
|
+
if choices and len(choices) > 0:
|
57
|
+
first_choice = choices[0]
|
58
|
+
|
59
|
+
# Get message or text
|
60
|
+
message = None
|
61
|
+
if hasattr(first_choice, "message"):
|
62
|
+
message = first_choice.message
|
63
|
+
elif isinstance(first_choice, dict) and "message" in first_choice:
|
64
|
+
message = first_choice["message"]
|
65
|
+
|
66
|
+
if message:
|
67
|
+
# For chat completion
|
68
|
+
# Get output_response
|
69
|
+
if hasattr(message, "content"):
|
70
|
+
output_response = message.content
|
71
|
+
elif isinstance(message, dict) and "content" in message:
|
72
|
+
output_response = message["content"]
|
73
|
+
|
74
|
+
# Get function_call
|
75
|
+
if hasattr(message, "function_call"):
|
76
|
+
function_call = message.function_call
|
77
|
+
elif isinstance(message, dict) and "function_call" in message:
|
78
|
+
function_call = message["function_call"]
|
79
|
+
|
80
|
+
# Get tool_calls (if any)
|
81
|
+
if hasattr(message, "tool_calls"):
|
82
|
+
tool_call = message.tool_calls
|
83
|
+
elif isinstance(message, dict) and "tool_calls" in message:
|
84
|
+
tool_call = message["tool_calls"]
|
85
|
+
else:
|
86
|
+
# For completion
|
87
|
+
# Get output_response
|
88
|
+
if hasattr(first_choice, "text"):
|
89
|
+
output_response = first_choice.text
|
90
|
+
elif isinstance(first_choice, dict) and "text" in first_choice:
|
91
|
+
output_response = first_choice["text"]
|
92
|
+
else:
|
93
|
+
output_response = ""
|
94
|
+
|
95
|
+
# No message, so no function_call or tool_call
|
96
|
+
function_call = None
|
97
|
+
tool_call = None
|
98
|
+
else:
|
99
|
+
output_response = ""
|
100
|
+
function_call = None
|
101
|
+
tool_call = None
|
102
|
+
|
103
|
+
# Set tool_call to function_call if tool_call is None
|
104
|
+
if not tool_call:
|
105
|
+
tool_call = function_call
|
106
|
+
|
107
|
+
# Parse tool_call
|
108
|
+
parsed_tool_call = None
|
109
|
+
if tool_call:
|
110
|
+
if isinstance(tool_call, dict):
|
111
|
+
arguments = tool_call.get("arguments", "{}")
|
112
|
+
name = tool_call.get("name", "")
|
113
|
+
else:
|
114
|
+
# Maybe it's an object with attributes
|
115
|
+
arguments = getattr(tool_call, "arguments", "{}")
|
116
|
+
name = getattr(tool_call, "name", "")
|
117
|
+
try:
|
118
|
+
if isinstance(arguments, str):
|
119
|
+
arguments = json.loads(arguments)
|
120
|
+
else:
|
121
|
+
arguments = arguments # If already a dict
|
122
|
+
except json.JSONDecodeError:
|
123
|
+
arguments = {}
|
124
|
+
parsed_tool_call = {"arguments": arguments, "name": name}
|
125
|
+
|
126
|
+
# Try to get token_usage from result.usage or result.content
|
127
|
+
usage = None
|
128
|
+
if hasattr(result, "usage"):
|
129
|
+
usage = result.usage
|
130
|
+
elif hasattr(result, "content"):
|
131
|
+
try:
|
132
|
+
content_dict = json.loads(result.content)
|
133
|
+
usage = content_dict.get("usage", {})
|
134
|
+
except (json.JSONDecodeError, TypeError):
|
135
|
+
usage = {}
|
136
|
+
else:
|
137
|
+
usage = {}
|
138
|
+
|
139
|
+
token_usage = convert_usage_to_dict(usage)
|
140
|
+
|
141
|
+
# Load model costs
|
142
|
+
model_costs = load_model_costs()
|
143
|
+
|
144
|
+
# Calculate cost
|
145
|
+
if model_name in model_costs:
|
146
|
+
model_config = model_costs[model_name]
|
147
|
+
input_cost_per_token = model_config.get("input_cost_per_token", 0.0)
|
148
|
+
output_cost_per_token = model_config.get("output_cost_per_token", 0.0)
|
149
|
+
reasoning_cost_per_token = model_config.get(
|
150
|
+
"reasoning_cost_per_token", output_cost_per_token
|
151
|
+
)
|
152
|
+
else:
|
153
|
+
# Default costs or log a warning
|
154
|
+
print(
|
155
|
+
f"Warning: Model '{model_name}' not found in config. Using default costs."
|
156
|
+
)
|
157
|
+
input_cost_per_token = 0.0
|
158
|
+
output_cost_per_token = 0.0
|
159
|
+
reasoning_cost_per_token = 0.0
|
160
|
+
|
161
|
+
cost = calculate_cost(
|
162
|
+
token_usage,
|
163
|
+
input_cost_per_token=input_cost_per_token,
|
164
|
+
output_cost_per_token=output_cost_per_token,
|
165
|
+
reasoning_cost_per_token=reasoning_cost_per_token,
|
166
|
+
)
|
167
|
+
|
168
|
+
llm_data = LLMCall(
|
169
|
+
name="",
|
170
|
+
model_name=model_name,
|
171
|
+
input_prompt="", # Not available here
|
172
|
+
output_response=output_response,
|
173
|
+
token_usage=token_usage,
|
174
|
+
cost=cost,
|
175
|
+
tool_call=parsed_tool_call,
|
176
|
+
)
|
177
|
+
return llm_data
|