Skip to content

Commit 425f627

Browse files
committed
feat: work
1 parent 9ba0376 commit 425f627

8 files changed

Lines changed: 250 additions & 133 deletions

File tree

src/langbot/pkg/api/http/controller/groups/monitoring.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@
66
from .. import group
77

88

9+
def parse_iso_datetime(datetime_str: str | None) -> datetime.datetime | None:
10+
"""Parse ISO 8601 datetime string, handling 'Z' suffix for UTC timezone"""
11+
if not datetime_str:
12+
return None
13+
# Replace 'Z' with '+00:00' for Python 3.10 compatibility
14+
if datetime_str.endswith('Z'):
15+
datetime_str = datetime_str[:-1] + '+00:00'
16+
dt = datetime.datetime.fromisoformat(datetime_str)
17+
# Convert to UTC and remove timezone info to match database storage (which stores UTC as naive datetime)
18+
if dt.tzinfo is not None:
19+
# Convert to UTC and remove timezone info
20+
dt = dt.astimezone(datetime.timezone.utc).replace(tzinfo=None)
21+
return dt
22+
23+
924
@group.group_class('monitoring', '/api/v1/monitoring')
1025
class MonitoringRouterGroup(group.RouterGroup):
1126
async def initialize(self) -> None:
@@ -19,8 +34,8 @@ async def get_overview() -> str:
1934
end_time_str = quart.request.args.get('endTime')
2035

2136
# Parse datetime
22-
start_time = datetime.datetime.fromisoformat(start_time_str) if start_time_str else None
23-
end_time = datetime.datetime.fromisoformat(end_time_str) if end_time_str else None
37+
start_time = parse_iso_datetime(start_time_str)
38+
end_time = parse_iso_datetime(end_time_str)
2439

2540
metrics = await self.ap.monitoring_service.get_overview_metrics(
2641
bot_ids=bot_ids if bot_ids else None,
@@ -43,8 +58,8 @@ async def get_messages() -> str:
4358
offset = int(quart.request.args.get('offset', 0))
4459

4560
# Parse datetime
46-
start_time = datetime.datetime.fromisoformat(start_time_str) if start_time_str else None
47-
end_time = datetime.datetime.fromisoformat(end_time_str) if end_time_str else None
61+
start_time = parse_iso_datetime(start_time_str)
62+
end_time = parse_iso_datetime(end_time_str)
4863

4964
messages, total = await self.ap.monitoring_service.get_messages(
5065
bot_ids=bot_ids if bot_ids else None,
@@ -76,8 +91,8 @@ async def get_llm_calls() -> str:
7691
offset = int(quart.request.args.get('offset', 0))
7792

7893
# Parse datetime
79-
start_time = datetime.datetime.fromisoformat(start_time_str) if start_time_str else None
80-
end_time = datetime.datetime.fromisoformat(end_time_str) if end_time_str else None
94+
start_time = parse_iso_datetime(start_time_str)
95+
end_time = parse_iso_datetime(end_time_str)
8196

8297
llm_calls, total = await self.ap.monitoring_service.get_llm_calls(
8398
bot_ids=bot_ids if bot_ids else None,
@@ -110,8 +125,8 @@ async def get_sessions() -> str:
110125
offset = int(quart.request.args.get('offset', 0))
111126

112127
# Parse datetime
113-
start_time = datetime.datetime.fromisoformat(start_time_str) if start_time_str else None
114-
end_time = datetime.datetime.fromisoformat(end_time_str) if end_time_str else None
128+
start_time = parse_iso_datetime(start_time_str)
129+
end_time = parse_iso_datetime(end_time_str)
115130

116131
# Parse is_active
117132
is_active = None
@@ -149,8 +164,8 @@ async def get_errors() -> str:
149164
offset = int(quart.request.args.get('offset', 0))
150165

151166
# Parse datetime
152-
start_time = datetime.datetime.fromisoformat(start_time_str) if start_time_str else None
153-
end_time = datetime.datetime.fromisoformat(end_time_str) if end_time_str else None
167+
start_time = parse_iso_datetime(start_time_str)
168+
end_time = parse_iso_datetime(end_time_str)
154169

155170
errors, total = await self.ap.monitoring_service.get_errors(
156171
bot_ids=bot_ids if bot_ids else None,
@@ -181,8 +196,8 @@ async def get_all_data() -> str:
181196
limit = int(quart.request.args.get('limit', 50))
182197

183198
# Parse datetime
184-
start_time = datetime.datetime.fromisoformat(start_time_str) if start_time_str else None
185-
end_time = datetime.datetime.fromisoformat(end_time_str) if end_time_str else None
199+
start_time = parse_iso_datetime(start_time_str)
200+
end_time = parse_iso_datetime(end_time_str)
186201

187202
# Get overview metrics
188203
overview = await self.ap.monitoring_service.get_overview_metrics(

src/langbot/pkg/api/http/service/monitoring.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ async def record_message(
3636
message_id = str(uuid.uuid4())
3737
message_data = {
3838
'id': message_id,
39-
'timestamp': datetime.datetime.now(),
39+
'timestamp': datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None),
4040
'bot_id': bot_id,
4141
'bot_name': bot_name,
4242
'pipeline_id': pipeline_id,
@@ -74,7 +74,7 @@ async def record_llm_call(
7474
call_id = str(uuid.uuid4())
7575
call_data = {
7676
'id': call_id,
77-
'timestamp': datetime.datetime.now(),
77+
'timestamp': datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None),
7878
'model_name': model_name,
7979
'input_tokens': input_tokens,
8080
'output_tokens': output_tokens,
@@ -114,8 +114,8 @@ async def record_session_start(
114114
'pipeline_id': pipeline_id,
115115
'pipeline_name': pipeline_name,
116116
'message_count': 0,
117-
'start_time': datetime.datetime.now(),
118-
'last_activity': datetime.datetime.now(),
117+
'start_time': datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None),
118+
'last_activity': datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None),
119119
'is_active': True,
120120
'platform': platform,
121121
'user_id': user_id,
@@ -131,7 +131,7 @@ async def update_session_activity(self, session_id: str) -> None:
131131
sqlalchemy.update(persistence_monitoring.MonitoringSession)
132132
.where(persistence_monitoring.MonitoringSession.session_id == session_id)
133133
.values({
134-
'last_activity': datetime.datetime.now(),
134+
'last_activity': datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None),
135135
'message_count': persistence_monitoring.MonitoringSession.message_count + 1,
136136
})
137137
)
@@ -151,7 +151,7 @@ async def record_error(
151151
error_id = str(uuid.uuid4())
152152
error_data = {
153153
'id': error_id,
154-
'timestamp': datetime.datetime.now(),
154+
'timestamp': datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None),
155155
'error_type': error_type,
156156
'error_message': error_message,
157157
'bot_id': bot_id,
@@ -286,12 +286,16 @@ async def get_messages(
286286
query = query.limit(limit).offset(offset)
287287

288288
result = await self.ap.persistence_mgr.execute_async(query)
289-
messages = result.all()
289+
messages_rows = result.all()
290290

291-
return (
292-
[self.ap.persistence_mgr.serialize_model(persistence_monitoring.MonitoringMessage, msg) for msg in messages],
293-
total,
294-
)
291+
serialized = []
292+
for row in messages_rows:
293+
# Extract model instance from Row (SQLAlchemy returns Row objects)
294+
msg = row[0] if isinstance(row, tuple) else row
295+
serialized_msg = self.ap.persistence_mgr.serialize_model(persistence_monitoring.MonitoringMessage, msg)
296+
serialized.append(serialized_msg)
297+
298+
return (serialized, total)
295299

296300
async def get_llm_calls(
297301
self,
@@ -332,10 +336,10 @@ async def get_llm_calls(
332336
query = query.limit(limit).offset(offset)
333337

334338
result = await self.ap.persistence_mgr.execute_async(query)
335-
llm_calls = result.all()
339+
llm_calls_rows = result.all()
336340

337341
return (
338-
[self.ap.persistence_mgr.serialize_model(persistence_monitoring.MonitoringLLMCall, call) for call in llm_calls],
342+
[self.ap.persistence_mgr.serialize_model(persistence_monitoring.MonitoringLLMCall, row[0] if isinstance(row, tuple) else row) for row in llm_calls_rows],
339343
total,
340344
)
341345

@@ -381,10 +385,10 @@ async def get_sessions(
381385
query = query.limit(limit).offset(offset)
382386

383387
result = await self.ap.persistence_mgr.execute_async(query)
384-
sessions = result.all()
388+
sessions_rows = result.all()
385389

386390
return (
387-
[self.ap.persistence_mgr.serialize_model(persistence_monitoring.MonitoringSession, session) for session in sessions],
391+
[self.ap.persistence_mgr.serialize_model(persistence_monitoring.MonitoringSession, row[0] if isinstance(row, tuple) else row) for row in sessions_rows],
388392
total,
389393
)
390394

@@ -427,9 +431,9 @@ async def get_errors(
427431
query = query.limit(limit).offset(offset)
428432

429433
result = await self.ap.persistence_mgr.execute_async(query)
430-
errors = result.all()
434+
errors_rows = result.all()
431435

432436
return (
433-
[self.ap.persistence_mgr.serialize_model(persistence_monitoring.MonitoringError, error) for error in errors],
437+
[self.ap.persistence_mgr.serialize_model(persistence_monitoring.MonitoringError, row[0] if isinstance(row, tuple) else row) for row in errors_rows],
434438
total,
435439
)

web/src/app/home/monitoring/components/overview-cards/OverviewCards.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default function OverviewCards({
3737
: undefined,
3838
},
3939
{
40-
title: t('monitoring.llmCalls'),
40+
title: t('monitoring.llmCallsCount'),
4141
value: metrics?.llmCalls || 0,
4242
icon: (
4343
<svg

0 commit comments

Comments
 (0)