Skip to content

Commit a4e9c8c

Browse files
authored
QUO-790: Add list method for logs (#71)
* GET logs from clients * Enable users to get logs
1 parent 44d14d5 commit a4e9c8c

File tree

3 files changed

+165
-8
lines changed

3 files changed

+165
-8
lines changed

quotientai/async_client.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,16 @@ def __init__(self, api_key: str):
1919
)
2020

2121
@handle_async_errors
22-
async def _get(self, path: str, timeout: int = None) -> dict:
23-
response = await self.get(path, timeout=timeout)
22+
async def _get(self, path: str, params: Optional[Dict[str, Any]] = None, timeout: int = None) -> dict:
23+
"""
24+
Send an async GET request to the specified path.
25+
26+
Args:
27+
path: API endpoint path
28+
params: Optional query parameters
29+
timeout: Optional request timeout in seconds
30+
"""
31+
response = await self.get(path, params=params, timeout=timeout)
2432
return response
2533

2634
@handle_async_errors

quotientai/client.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,16 @@ def __init__(self, api_key: str):
1919
)
2020

2121
@handle_errors
22-
def _get(self, path: str, timeout: int = None) -> dict:
23-
response = self.get(path, timeout=timeout)
22+
def _get(self, path: str, params: Optional[Dict[str, Any]] = None, timeout: int = None) -> dict:
23+
"""
24+
Send a GET request to the specified path.
25+
26+
Args:
27+
path: API endpoint path
28+
params: Optional query parameters
29+
timeout: Optional request timeout in seconds
30+
"""
31+
response = self.get(path, params=params, timeout=timeout)
2432
return response
2533

2634
@handle_errors

quotientai/resources/logs.py

Lines changed: 145 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,99 @@
22
import asyncio
33
import httpx
44
import logging
5+
from dataclasses import dataclass
6+
from datetime import datetime
7+
8+
9+
@dataclass
10+
class Log:
11+
"""
12+
Represents a log entry from the QuotientAI API
13+
"""
14+
id: str
15+
app_name: str
16+
environment: str
17+
hallucination_detection: bool
18+
inconsistency_detection: bool
19+
user_query: str
20+
model_output: str
21+
documents: List[str]
22+
message_history: Optional[List[Dict[str, Any]]]
23+
instructions: Optional[List[str]]
24+
tags: Dict[str, Any]
25+
created_at: datetime
26+
27+
def __rich_repr__(self):
28+
yield "id", self.id
29+
yield "app_name", self.app_name
30+
yield "environment", self.environment
31+
yield "created_at", self.created_at
532

633

734
class LogsResource:
835
def __init__(self, client) -> None:
936
self._client = client
1037
self.logger = logging.getLogger(__name__)
1138

39+
def list(
40+
self,
41+
*,
42+
app_name: Optional[str] = None,
43+
environment: Optional[str] = None,
44+
start_date: Optional[datetime] = None,
45+
end_date: Optional[datetime] = None,
46+
limit: Optional[int] = None,
47+
offset: Optional[int] = None,
48+
) -> List[Log]:
49+
"""
50+
List logs with optional filtering parameters.
51+
52+
Args:
53+
app_name: Filter logs by application name
54+
environment: Filter logs by environment
55+
start_date: Filter logs created after this date
56+
end_date: Filter logs created before this date
57+
limit: Maximum number of logs to return
58+
offset: Number of logs to skip
59+
"""
60+
params = {
61+
"app_name": app_name,
62+
"environment": environment,
63+
"start_date": start_date.isoformat() if start_date else None,
64+
"end_date": end_date.isoformat() if end_date else None,
65+
"limit": limit,
66+
"offset": offset,
67+
}
68+
# Remove None values
69+
params = {k: v for k, v in params.items() if v is not None}
70+
71+
try:
72+
response = self._client._get("/logs", params=params)
73+
data = response["logs"]
74+
75+
logs = []
76+
for log in data:
77+
logs.append(
78+
Log(
79+
id=log["id"],
80+
app_name=log["app_name"],
81+
environment=log["environment"],
82+
hallucination_detection=log["hallucination_detection"],
83+
inconsistency_detection=log["inconsistency_detection"],
84+
user_query=log["user_query"],
85+
model_output=log["model_output"],
86+
documents=log["documents"],
87+
message_history=log["message_history"],
88+
instructions=log["instructions"],
89+
tags=log["tags"],
90+
created_at=datetime.fromisoformat(log["created_at"]),
91+
)
92+
)
93+
return logs
94+
except Exception:
95+
self.logger.error("Error listing logs", exc_info=True)
96+
raise
97+
1298
def create(
1399
self,
14100
app_name: str,
@@ -21,7 +107,6 @@ def create(
21107
message_history: Optional[List[Dict[str, Any]]] = None,
22108
instructions: Optional[List[str]] = None,
23109
tags: Optional[Dict[str, Any]] = {},
24-
contexts: Optional[List[str]] = [],
25110
hallucination_detection_sample_rate: Optional[float] = 0,
26111
):
27112
"""
@@ -38,7 +123,6 @@ def create(
38123
"documents": documents,
39124
"message_history": message_history,
40125
"instructions": instructions,
41-
"contexts": contexts,
42126
"hallucination_detection_sample_rate": hallucination_detection_sample_rate,
43127
}
44128

@@ -55,6 +139,65 @@ def __init__(self, client) -> None:
55139
self._client = client
56140
self.logger = logging.getLogger(__name__)
57141

142+
async def list(
143+
self,
144+
*,
145+
app_name: Optional[str] = None,
146+
environment: Optional[str] = None,
147+
start_date: Optional[datetime] = None,
148+
end_date: Optional[datetime] = None,
149+
limit: Optional[int] = None,
150+
offset: Optional[int] = None,
151+
) -> List[Log]:
152+
"""
153+
List logs asynchronously with optional filtering parameters.
154+
155+
Args:
156+
app_name: Filter logs by application name
157+
environment: Filter logs by environment
158+
start_date: Filter logs created after this date
159+
end_date: Filter logs created before this date
160+
limit: Maximum number of logs to return
161+
offset: Number of logs to skip
162+
"""
163+
params = {
164+
"app_name": app_name,
165+
"environment": environment,
166+
"start_date": start_date.isoformat() if start_date else None,
167+
"end_date": end_date.isoformat() if end_date else None,
168+
"limit": limit,
169+
"offset": offset,
170+
}
171+
# Remove None values
172+
params = {k: v for k, v in params.items() if v is not None}
173+
174+
try:
175+
response = await self._client._get("/logs", params=params)
176+
data = response["logs"]
177+
178+
logs = []
179+
for log in data:
180+
logs.append(
181+
Log(
182+
id=log["id"],
183+
app_name=log["app_name"],
184+
environment=log["environment"],
185+
hallucination_detection=log["hallucination_detection"],
186+
inconsistency_detection=log["inconsistency_detection"],
187+
user_query=log["user_query"],
188+
model_output=log["model_output"],
189+
documents=log["documents"],
190+
message_history=log["message_history"],
191+
instructions=log["instructions"],
192+
tags=log["tags"],
193+
created_at=datetime.fromisoformat(log["created_at"]),
194+
)
195+
)
196+
return logs
197+
except Exception:
198+
self.logger.error("Error listing logs", exc_info=True)
199+
raise
200+
58201
async def create(
59202
self,
60203
app_name: str,
@@ -67,7 +210,6 @@ async def create(
67210
message_history: Optional[List[Dict[str, Any]]] = None,
68211
instructions: Optional[List[str]] = None,
69212
tags: Optional[Dict[str, Any]] = {},
70-
contexts: Optional[List[str]] = [],
71213
hallucination_detection_sample_rate: Optional[float] = 0,
72214
):
73215
"""
@@ -84,7 +226,6 @@ async def create(
84226
"documents": documents,
85227
"message_history": message_history,
86228
"instructions": instructions,
87-
"contexts": contexts,
88229
"hallucination_detection_sample_rate": hallucination_detection_sample_rate,
89230
}
90231

0 commit comments

Comments
 (0)