@jami-studio/core 0.92.26 → 0.92.28
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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +23 -0
- package/corpus/core/package.json +2 -1
- package/corpus/core/src/deploy/build.ts +56 -2
- package/corpus/core/src/event-bus/bus.ts +138 -140
- package/corpus/core/src/event-bus/registry.ts +81 -79
- package/corpus/core/src/file-upload/registry.ts +135 -125
- package/corpus/core/src/notifications/registry.ts +218 -216
- package/corpus/core/src/observability/store.ts +1313 -1321
- package/corpus/core/src/private-blob/registry.ts +246 -242
- package/corpus/core/src/secrets/register.ts +132 -129
- package/corpus/core/src/shared/global-scope.ts +75 -0
- package/corpus/core/src/shared/init-memo.ts +94 -0
- package/corpus/core/src/sharing/registry.ts +193 -194
- package/corpus/core/src/tracking/providers.ts +425 -422
- package/corpus/core/src/tracking/registry.ts +102 -102
- package/dist/collab/routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +23 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +36 -2
- package/dist/deploy/build.js.map +1 -1
- package/dist/event-bus/bus.d.ts.map +1 -1
- package/dist/event-bus/bus.js +8 -6
- package/dist/event-bus/bus.js.map +1 -1
- package/dist/event-bus/registry.d.ts.map +1 -1
- package/dist/event-bus/registry.js +10 -6
- package/dist/event-bus/registry.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/file-upload/registry.d.ts.map +1 -1
- package/dist/file-upload/registry.js +28 -8
- package/dist/file-upload/registry.js.map +1 -1
- package/dist/notifications/registry.d.ts.map +1 -1
- package/dist/notifications/registry.js +6 -5
- package/dist/notifications/registry.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/observability/store.d.ts +1 -1
- package/dist/observability/store.d.ts.map +1 -1
- package/dist/observability/store.js +311 -316
- package/dist/observability/store.js.map +1 -1
- package/dist/private-blob/registry.d.ts.map +1 -1
- package/dist/private-blob/registry.js +18 -13
- package/dist/private-blob/registry.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/secrets/register.d.ts.map +1 -1
- package/dist/secrets/register.js +11 -7
- package/dist/secrets/register.js.map +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/shared/global-scope.d.ts +55 -0
- package/dist/shared/global-scope.d.ts.map +1 -0
- package/dist/shared/global-scope.js +70 -0
- package/dist/shared/global-scope.js.map +1 -0
- package/dist/shared/init-memo.d.ts +34 -0
- package/dist/shared/init-memo.d.ts.map +1 -0
- package/dist/shared/init-memo.js +80 -0
- package/dist/shared/init-memo.js.map +1 -0
- package/dist/sharing/registry.d.ts.map +1 -1
- package/dist/sharing/registry.js +2 -16
- package/dist/sharing/registry.js.map +1 -1
- package/dist/tracking/providers.d.ts.map +1 -1
- package/dist/tracking/providers.js +11 -9
- package/dist/tracking/providers.js.map +1 -1
- package/dist/tracking/registry.d.ts.map +1 -1
- package/dist/tracking/registry.js +5 -5
- package/dist/tracking/registry.js.map +1 -1
- package/package.json +2 -1
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { getDbExec, intType, isPostgres, retryOnDdlRace, } from "../db/client.js";
|
|
10
10
|
import { ensureTableExists, ensureColumnExists, ensureIndexExists, } from "../db/ddl-guard.js";
|
|
11
11
|
import { isDuplicateColumnError } from "../db/migrations.js";
|
|
12
|
+
import { createInitMemo } from "../shared/init-memo.js";
|
|
12
13
|
function safeJsonParse(value, fallback) {
|
|
13
14
|
if (!value)
|
|
14
15
|
return fallback;
|
|
@@ -43,244 +44,238 @@ function withUserFilter(baseWhere, baseArgs, userId) {
|
|
|
43
44
|
args: [...baseArgs, userId],
|
|
44
45
|
};
|
|
45
46
|
}
|
|
46
|
-
let _initPromise
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
CREATE TABLE IF NOT EXISTS agent_trace_spans (
|
|
53
|
-
id TEXT PRIMARY KEY,
|
|
54
|
-
run_id TEXT NOT NULL,
|
|
55
|
-
thread_id TEXT,
|
|
56
|
-
user_id TEXT,
|
|
57
|
-
parent_span_id TEXT,
|
|
58
|
-
span_type TEXT NOT NULL,
|
|
59
|
-
name TEXT NOT NULL,
|
|
60
|
-
input_tokens ${intType()} NOT NULL DEFAULT 0,
|
|
61
|
-
output_tokens ${intType()} NOT NULL DEFAULT 0,
|
|
62
|
-
cache_read_tokens ${intType()} NOT NULL DEFAULT 0,
|
|
63
|
-
cache_write_tokens ${intType()} NOT NULL DEFAULT 0,
|
|
64
|
-
cost_cents_x100 ${intType()} NOT NULL DEFAULT 0,
|
|
65
|
-
duration_ms ${intType()} NOT NULL DEFAULT 0,
|
|
66
|
-
status TEXT NOT NULL DEFAULT 'success',
|
|
67
|
-
error_message TEXT,
|
|
68
|
-
metadata TEXT,
|
|
69
|
-
created_at ${intType()} NOT NULL
|
|
70
|
-
)
|
|
47
|
+
// workerd-safe memo: the raw `let _initPromise` pattern wedged every agent
|
|
48
|
+
// chat run on the unified Cloudflare runtime when an early-responding first
|
|
49
|
+
// request created (and froze) the init promise — see shared/init-memo.ts.
|
|
50
|
+
export const ensureObservabilityTables = createInitMemo(async () => {
|
|
51
|
+
const client = getDbExec();
|
|
52
|
+
const traceSpansCreateSql = `
|
|
53
|
+
CREATE TABLE IF NOT EXISTS agent_trace_spans (
|
|
54
|
+
id TEXT PRIMARY KEY,
|
|
55
|
+
run_id TEXT NOT NULL,
|
|
56
|
+
thread_id TEXT,
|
|
57
|
+
user_id TEXT,
|
|
58
|
+
parent_span_id TEXT,
|
|
59
|
+
span_type TEXT NOT NULL,
|
|
60
|
+
name TEXT NOT NULL,
|
|
61
|
+
input_tokens ${intType()} NOT NULL DEFAULT 0,
|
|
62
|
+
output_tokens ${intType()} NOT NULL DEFAULT 0,
|
|
63
|
+
cache_read_tokens ${intType()} NOT NULL DEFAULT 0,
|
|
64
|
+
cache_write_tokens ${intType()} NOT NULL DEFAULT 0,
|
|
65
|
+
cost_cents_x100 ${intType()} NOT NULL DEFAULT 0,
|
|
66
|
+
duration_ms ${intType()} NOT NULL DEFAULT 0,
|
|
67
|
+
status TEXT NOT NULL DEFAULT 'success',
|
|
68
|
+
error_message TEXT,
|
|
69
|
+
metadata TEXT,
|
|
70
|
+
created_at ${intType()} NOT NULL
|
|
71
|
+
)
|
|
71
72
|
`;
|
|
72
|
-
|
|
73
|
-
CREATE TABLE IF NOT EXISTS agent_trace_summaries (
|
|
74
|
-
run_id TEXT PRIMARY KEY,
|
|
75
|
-
thread_id TEXT,
|
|
76
|
-
user_id TEXT,
|
|
77
|
-
total_spans ${intType()} NOT NULL DEFAULT 0,
|
|
78
|
-
llm_calls ${intType()} NOT NULL DEFAULT 0,
|
|
79
|
-
tool_calls ${intType()} NOT NULL DEFAULT 0,
|
|
80
|
-
successful_tools ${intType()} NOT NULL DEFAULT 0,
|
|
81
|
-
failed_tools ${intType()} NOT NULL DEFAULT 0,
|
|
82
|
-
total_duration_ms ${intType()} NOT NULL DEFAULT 0,
|
|
83
|
-
total_cost_cents_x100 ${intType()} NOT NULL DEFAULT 0,
|
|
84
|
-
total_input_tokens ${intType()} NOT NULL DEFAULT 0,
|
|
85
|
-
total_output_tokens ${intType()} NOT NULL DEFAULT 0,
|
|
86
|
-
model TEXT NOT NULL DEFAULT '',
|
|
87
|
-
created_at ${intType()} NOT NULL
|
|
88
|
-
)
|
|
73
|
+
const traceSummariesCreateSql = `
|
|
74
|
+
CREATE TABLE IF NOT EXISTS agent_trace_summaries (
|
|
75
|
+
run_id TEXT PRIMARY KEY,
|
|
76
|
+
thread_id TEXT,
|
|
77
|
+
user_id TEXT,
|
|
78
|
+
total_spans ${intType()} NOT NULL DEFAULT 0,
|
|
79
|
+
llm_calls ${intType()} NOT NULL DEFAULT 0,
|
|
80
|
+
tool_calls ${intType()} NOT NULL DEFAULT 0,
|
|
81
|
+
successful_tools ${intType()} NOT NULL DEFAULT 0,
|
|
82
|
+
failed_tools ${intType()} NOT NULL DEFAULT 0,
|
|
83
|
+
total_duration_ms ${intType()} NOT NULL DEFAULT 0,
|
|
84
|
+
total_cost_cents_x100 ${intType()} NOT NULL DEFAULT 0,
|
|
85
|
+
total_input_tokens ${intType()} NOT NULL DEFAULT 0,
|
|
86
|
+
total_output_tokens ${intType()} NOT NULL DEFAULT 0,
|
|
87
|
+
model TEXT NOT NULL DEFAULT '',
|
|
88
|
+
created_at ${intType()} NOT NULL
|
|
89
|
+
)
|
|
89
90
|
`;
|
|
90
|
-
|
|
91
|
-
CREATE TABLE IF NOT EXISTS agent_feedback (
|
|
92
|
-
id TEXT PRIMARY KEY,
|
|
93
|
-
run_id TEXT,
|
|
94
|
-
thread_id TEXT,
|
|
95
|
-
message_seq ${intType()},
|
|
96
|
-
feedback_type TEXT NOT NULL,
|
|
97
|
-
value TEXT NOT NULL DEFAULT '',
|
|
98
|
-
user_id TEXT,
|
|
99
|
-
created_at ${intType()} NOT NULL
|
|
100
|
-
)
|
|
91
|
+
const feedbackCreateSql = `
|
|
92
|
+
CREATE TABLE IF NOT EXISTS agent_feedback (
|
|
93
|
+
id TEXT PRIMARY KEY,
|
|
94
|
+
run_id TEXT,
|
|
95
|
+
thread_id TEXT,
|
|
96
|
+
message_seq ${intType()},
|
|
97
|
+
feedback_type TEXT NOT NULL,
|
|
98
|
+
value TEXT NOT NULL DEFAULT '',
|
|
99
|
+
user_id TEXT,
|
|
100
|
+
created_at ${intType()} NOT NULL
|
|
101
|
+
)
|
|
101
102
|
`;
|
|
102
|
-
|
|
103
|
-
CREATE TABLE IF NOT EXISTS agent_satisfaction_scores (
|
|
104
|
-
id TEXT PRIMARY KEY,
|
|
105
|
-
thread_id TEXT NOT NULL,
|
|
106
|
-
user_id TEXT,
|
|
107
|
-
frustration_score REAL NOT NULL DEFAULT 0,
|
|
108
|
-
rephrasing_score REAL NOT NULL DEFAULT 0,
|
|
109
|
-
abandonment_score REAL NOT NULL DEFAULT 0,
|
|
110
|
-
sentiment_score REAL NOT NULL DEFAULT 0,
|
|
111
|
-
length_trend_score REAL NOT NULL DEFAULT 0,
|
|
112
|
-
computed_at ${intType()} NOT NULL
|
|
113
|
-
)
|
|
103
|
+
const satisfactionScoresCreateSql = `
|
|
104
|
+
CREATE TABLE IF NOT EXISTS agent_satisfaction_scores (
|
|
105
|
+
id TEXT PRIMARY KEY,
|
|
106
|
+
thread_id TEXT NOT NULL,
|
|
107
|
+
user_id TEXT,
|
|
108
|
+
frustration_score REAL NOT NULL DEFAULT 0,
|
|
109
|
+
rephrasing_score REAL NOT NULL DEFAULT 0,
|
|
110
|
+
abandonment_score REAL NOT NULL DEFAULT 0,
|
|
111
|
+
sentiment_score REAL NOT NULL DEFAULT 0,
|
|
112
|
+
length_trend_score REAL NOT NULL DEFAULT 0,
|
|
113
|
+
computed_at ${intType()} NOT NULL
|
|
114
|
+
)
|
|
114
115
|
`;
|
|
115
|
-
|
|
116
|
-
CREATE TABLE IF NOT EXISTS agent_evals (
|
|
117
|
-
id TEXT PRIMARY KEY,
|
|
118
|
-
run_id TEXT NOT NULL,
|
|
119
|
-
thread_id TEXT,
|
|
120
|
-
user_id TEXT,
|
|
121
|
-
eval_type TEXT NOT NULL,
|
|
122
|
-
criteria TEXT NOT NULL,
|
|
123
|
-
score REAL NOT NULL DEFAULT 0,
|
|
124
|
-
reasoning TEXT,
|
|
125
|
-
metadata TEXT,
|
|
126
|
-
created_at ${intType()} NOT NULL
|
|
127
|
-
)
|
|
116
|
+
const evalsCreateSql = `
|
|
117
|
+
CREATE TABLE IF NOT EXISTS agent_evals (
|
|
118
|
+
id TEXT PRIMARY KEY,
|
|
119
|
+
run_id TEXT NOT NULL,
|
|
120
|
+
thread_id TEXT,
|
|
121
|
+
user_id TEXT,
|
|
122
|
+
eval_type TEXT NOT NULL,
|
|
123
|
+
criteria TEXT NOT NULL,
|
|
124
|
+
score REAL NOT NULL DEFAULT 0,
|
|
125
|
+
reasoning TEXT,
|
|
126
|
+
metadata TEXT,
|
|
127
|
+
created_at ${intType()} NOT NULL
|
|
128
|
+
)
|
|
128
129
|
`;
|
|
129
|
-
|
|
130
|
-
CREATE TABLE IF NOT EXISTS agent_eval_datasets (
|
|
131
|
-
id TEXT PRIMARY KEY,
|
|
132
|
-
name TEXT NOT NULL,
|
|
133
|
-
description TEXT NOT NULL DEFAULT '',
|
|
134
|
-
entries TEXT NOT NULL DEFAULT '[]',
|
|
135
|
-
created_at ${intType()} NOT NULL,
|
|
136
|
-
updated_at ${intType()} NOT NULL
|
|
137
|
-
)
|
|
130
|
+
const evalDatasetsCreateSql = `
|
|
131
|
+
CREATE TABLE IF NOT EXISTS agent_eval_datasets (
|
|
132
|
+
id TEXT PRIMARY KEY,
|
|
133
|
+
name TEXT NOT NULL,
|
|
134
|
+
description TEXT NOT NULL DEFAULT '',
|
|
135
|
+
entries TEXT NOT NULL DEFAULT '[]',
|
|
136
|
+
created_at ${intType()} NOT NULL,
|
|
137
|
+
updated_at ${intType()} NOT NULL
|
|
138
|
+
)
|
|
138
139
|
`;
|
|
139
|
-
|
|
140
|
-
CREATE TABLE IF NOT EXISTS agent_experiments (
|
|
141
|
-
id TEXT PRIMARY KEY,
|
|
142
|
-
name TEXT NOT NULL,
|
|
143
|
-
status TEXT NOT NULL DEFAULT 'draft',
|
|
144
|
-
variants TEXT NOT NULL DEFAULT '[]',
|
|
145
|
-
metrics TEXT NOT NULL DEFAULT '[]',
|
|
146
|
-
assignment_level TEXT NOT NULL DEFAULT 'user',
|
|
147
|
-
started_at ${intType()},
|
|
148
|
-
ended_at ${intType()},
|
|
149
|
-
created_at ${intType()} NOT NULL,
|
|
150
|
-
owner_email TEXT
|
|
151
|
-
)
|
|
140
|
+
const experimentsCreateSql = `
|
|
141
|
+
CREATE TABLE IF NOT EXISTS agent_experiments (
|
|
142
|
+
id TEXT PRIMARY KEY,
|
|
143
|
+
name TEXT NOT NULL,
|
|
144
|
+
status TEXT NOT NULL DEFAULT 'draft',
|
|
145
|
+
variants TEXT NOT NULL DEFAULT '[]',
|
|
146
|
+
metrics TEXT NOT NULL DEFAULT '[]',
|
|
147
|
+
assignment_level TEXT NOT NULL DEFAULT 'user',
|
|
148
|
+
started_at ${intType()},
|
|
149
|
+
ended_at ${intType()},
|
|
150
|
+
created_at ${intType()} NOT NULL,
|
|
151
|
+
owner_email TEXT
|
|
152
|
+
)
|
|
152
153
|
`;
|
|
153
|
-
|
|
154
|
-
CREATE TABLE IF NOT EXISTS agent_experiment_assignments (
|
|
155
|
-
experiment_id TEXT NOT NULL,
|
|
156
|
-
user_id TEXT NOT NULL,
|
|
157
|
-
variant_id TEXT NOT NULL,
|
|
158
|
-
assigned_at ${intType()} NOT NULL,
|
|
159
|
-
PRIMARY KEY (experiment_id, user_id)
|
|
160
|
-
)
|
|
154
|
+
const experimentAssignmentsCreateSql = `
|
|
155
|
+
CREATE TABLE IF NOT EXISTS agent_experiment_assignments (
|
|
156
|
+
experiment_id TEXT NOT NULL,
|
|
157
|
+
user_id TEXT NOT NULL,
|
|
158
|
+
variant_id TEXT NOT NULL,
|
|
159
|
+
assigned_at ${intType()} NOT NULL,
|
|
160
|
+
PRIMARY KEY (experiment_id, user_id)
|
|
161
|
+
)
|
|
161
162
|
`;
|
|
162
|
-
|
|
163
|
-
CREATE TABLE IF NOT EXISTS agent_experiment_results (
|
|
164
|
-
id TEXT PRIMARY KEY,
|
|
165
|
-
experiment_id TEXT NOT NULL,
|
|
166
|
-
variant_id TEXT NOT NULL,
|
|
167
|
-
metric TEXT NOT NULL,
|
|
168
|
-
value REAL NOT NULL DEFAULT 0,
|
|
169
|
-
sample_size ${intType()} NOT NULL DEFAULT 0,
|
|
170
|
-
confidence_low REAL NOT NULL DEFAULT 0,
|
|
171
|
-
confidence_high REAL NOT NULL DEFAULT 0,
|
|
172
|
-
computed_at ${intType()} NOT NULL
|
|
173
|
-
)
|
|
163
|
+
const experimentResultsCreateSql = `
|
|
164
|
+
CREATE TABLE IF NOT EXISTS agent_experiment_results (
|
|
165
|
+
id TEXT PRIMARY KEY,
|
|
166
|
+
experiment_id TEXT NOT NULL,
|
|
167
|
+
variant_id TEXT NOT NULL,
|
|
168
|
+
metric TEXT NOT NULL,
|
|
169
|
+
value REAL NOT NULL DEFAULT 0,
|
|
170
|
+
sample_size ${intType()} NOT NULL DEFAULT 0,
|
|
171
|
+
confidence_low REAL NOT NULL DEFAULT 0,
|
|
172
|
+
confidence_high REAL NOT NULL DEFAULT 0,
|
|
173
|
+
computed_at ${intType()} NOT NULL
|
|
174
|
+
)
|
|
174
175
|
`;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
throw err;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
// Indexes for common query patterns
|
|
243
|
-
const indexes = [
|
|
244
|
-
`CREATE INDEX IF NOT EXISTS idx_trace_spans_run ON agent_trace_spans (run_id)`,
|
|
245
|
-
`CREATE INDEX IF NOT EXISTS idx_trace_spans_thread ON agent_trace_spans (thread_id)`,
|
|
246
|
-
`CREATE INDEX IF NOT EXISTS idx_trace_spans_created ON agent_trace_spans (created_at)`,
|
|
247
|
-
`CREATE INDEX IF NOT EXISTS idx_trace_summaries_created ON agent_trace_summaries (created_at)`,
|
|
248
|
-
`CREATE INDEX IF NOT EXISTS idx_trace_summaries_user ON agent_trace_summaries (user_id, created_at)`,
|
|
249
|
-
`CREATE INDEX IF NOT EXISTS idx_trace_spans_user ON agent_trace_spans (user_id)`,
|
|
250
|
-
`CREATE INDEX IF NOT EXISTS idx_feedback_thread ON agent_feedback (thread_id)`,
|
|
251
|
-
`CREATE INDEX IF NOT EXISTS idx_feedback_created ON agent_feedback (created_at)`,
|
|
252
|
-
`CREATE INDEX IF NOT EXISTS idx_feedback_user ON agent_feedback (user_id, created_at)`,
|
|
253
|
-
`CREATE INDEX IF NOT EXISTS idx_satisfaction_thread ON agent_satisfaction_scores (thread_id)`,
|
|
254
|
-
`CREATE INDEX IF NOT EXISTS idx_satisfaction_user ON agent_satisfaction_scores (user_id, computed_at)`,
|
|
255
|
-
`CREATE INDEX IF NOT EXISTS idx_evals_run ON agent_evals (run_id)`,
|
|
256
|
-
`CREATE INDEX IF NOT EXISTS idx_evals_created ON agent_evals (created_at)`,
|
|
257
|
-
`CREATE INDEX IF NOT EXISTS idx_evals_user ON agent_evals (user_id, created_at)`,
|
|
258
|
-
`CREATE INDEX IF NOT EXISTS idx_experiment_results_exp ON agent_experiment_results (experiment_id)`,
|
|
259
|
-
];
|
|
260
|
-
for (const sql of indexes) {
|
|
261
|
-
try {
|
|
262
|
-
await client.execute(sql);
|
|
263
|
-
}
|
|
264
|
-
catch {
|
|
265
|
-
// Index might already exist
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
})().catch((err) => {
|
|
269
|
-
_initPromise = undefined;
|
|
176
|
+
if (isPostgres()) {
|
|
177
|
+
// PG guard: probe → guarded DDL → re-probe; skips lock on already-migrated path
|
|
178
|
+
await ensureTableExists("agent_trace_spans", traceSpansCreateSql);
|
|
179
|
+
await ensureTableExists("agent_trace_summaries", traceSummariesCreateSql);
|
|
180
|
+
await ensureTableExists("agent_feedback", feedbackCreateSql);
|
|
181
|
+
await ensureTableExists("agent_satisfaction_scores", satisfactionScoresCreateSql);
|
|
182
|
+
await ensureTableExists("agent_evals", evalsCreateSql);
|
|
183
|
+
await ensureTableExists("agent_eval_datasets", evalDatasetsCreateSql);
|
|
184
|
+
await ensureTableExists("agent_experiments", experimentsCreateSql);
|
|
185
|
+
await ensureTableExists("agent_experiment_assignments", experimentAssignmentsCreateSql);
|
|
186
|
+
await ensureTableExists("agent_experiment_results", experimentResultsCreateSql);
|
|
187
|
+
await ensureColumnExists("agent_experiments", "owner_email", `ALTER TABLE agent_experiments ADD COLUMN IF NOT EXISTS owner_email TEXT`);
|
|
188
|
+
for (const table of USER_SCOPED_TABLES) {
|
|
189
|
+
await ensureColumnExists(table, "user_id", `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS user_id TEXT`);
|
|
190
|
+
}
|
|
191
|
+
await ensureIndexExists("idx_trace_spans_run", `CREATE INDEX IF NOT EXISTS idx_trace_spans_run ON agent_trace_spans (run_id)`);
|
|
192
|
+
await ensureIndexExists("idx_trace_spans_thread", `CREATE INDEX IF NOT EXISTS idx_trace_spans_thread ON agent_trace_spans (thread_id)`);
|
|
193
|
+
await ensureIndexExists("idx_trace_spans_created", `CREATE INDEX IF NOT EXISTS idx_trace_spans_created ON agent_trace_spans (created_at)`);
|
|
194
|
+
await ensureIndexExists("idx_trace_summaries_created", `CREATE INDEX IF NOT EXISTS idx_trace_summaries_created ON agent_trace_summaries (created_at)`);
|
|
195
|
+
await ensureIndexExists("idx_trace_summaries_user", `CREATE INDEX IF NOT EXISTS idx_trace_summaries_user ON agent_trace_summaries (user_id, created_at)`);
|
|
196
|
+
await ensureIndexExists("idx_trace_spans_user", `CREATE INDEX IF NOT EXISTS idx_trace_spans_user ON agent_trace_spans (user_id)`);
|
|
197
|
+
await ensureIndexExists("idx_feedback_thread", `CREATE INDEX IF NOT EXISTS idx_feedback_thread ON agent_feedback (thread_id)`);
|
|
198
|
+
await ensureIndexExists("idx_feedback_created", `CREATE INDEX IF NOT EXISTS idx_feedback_created ON agent_feedback (created_at)`);
|
|
199
|
+
await ensureIndexExists("idx_feedback_user", `CREATE INDEX IF NOT EXISTS idx_feedback_user ON agent_feedback (user_id, created_at)`);
|
|
200
|
+
await ensureIndexExists("idx_satisfaction_thread", `CREATE INDEX IF NOT EXISTS idx_satisfaction_thread ON agent_satisfaction_scores (thread_id)`);
|
|
201
|
+
await ensureIndexExists("idx_satisfaction_user", `CREATE INDEX IF NOT EXISTS idx_satisfaction_user ON agent_satisfaction_scores (user_id, computed_at)`);
|
|
202
|
+
await ensureIndexExists("idx_evals_run", `CREATE INDEX IF NOT EXISTS idx_evals_run ON agent_evals (run_id)`);
|
|
203
|
+
await ensureIndexExists("idx_evals_created", `CREATE INDEX IF NOT EXISTS idx_evals_created ON agent_evals (created_at)`);
|
|
204
|
+
await ensureIndexExists("idx_evals_user", `CREATE INDEX IF NOT EXISTS idx_evals_user ON agent_evals (user_id, created_at)`);
|
|
205
|
+
await ensureIndexExists("idx_experiment_results_exp", `CREATE INDEX IF NOT EXISTS idx_experiment_results_exp ON agent_experiment_results (experiment_id)`);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
// SQLite (local dev): no lock problem — keep the original behaviour.
|
|
209
|
+
await retryOnDdlRace(() => client.execute(traceSpansCreateSql));
|
|
210
|
+
await retryOnDdlRace(() => client.execute(traceSummariesCreateSql));
|
|
211
|
+
await retryOnDdlRace(() => client.execute(feedbackCreateSql));
|
|
212
|
+
await retryOnDdlRace(() => client.execute(satisfactionScoresCreateSql));
|
|
213
|
+
await retryOnDdlRace(() => client.execute(evalsCreateSql));
|
|
214
|
+
await retryOnDdlRace(() => client.execute(evalDatasetsCreateSql));
|
|
215
|
+
await retryOnDdlRace(() => client.execute(experimentsCreateSql));
|
|
216
|
+
// Additive migration for DBs created before the owner column shipped
|
|
217
|
+
// (any pre-existing rows have NULL owner — see `updateExperiment` for
|
|
218
|
+
// the migration semantics). Mutations on those rows fall back to the
|
|
219
|
+
// standard authentication gate but cannot enforce per-owner scoping
|
|
220
|
+
// until they're re-saved.
|
|
221
|
+
try {
|
|
222
|
+
await client.execute(`ALTER TABLE agent_experiments ADD COLUMN owner_email TEXT`);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
// Column already exists — expected after first run.
|
|
226
|
+
}
|
|
227
|
+
await retryOnDdlRace(() => client.execute(experimentAssignmentsCreateSql));
|
|
228
|
+
await retryOnDdlRace(() => client.execute(experimentResultsCreateSql));
|
|
229
|
+
// Idempotent column upgrades for DBs created before per-user
|
|
230
|
+
// isolation. SQLite has no `ADD COLUMN IF NOT EXISTS`; Postgres
|
|
231
|
+
// surfaces "column ... already exists". `isDuplicateColumnError`
|
|
232
|
+
// (from db/migrations.ts) recognizes both shapes.
|
|
233
|
+
for (const table of USER_SCOPED_TABLES) {
|
|
234
|
+
try {
|
|
235
|
+
await client.execute(`ALTER TABLE ${table} ADD COLUMN user_id TEXT`);
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
if (isDuplicateColumnError(err))
|
|
239
|
+
continue;
|
|
270
240
|
throw err;
|
|
271
|
-
}
|
|
241
|
+
}
|
|
272
242
|
}
|
|
273
|
-
|
|
274
|
-
|
|
243
|
+
// Indexes for common query patterns
|
|
244
|
+
const indexes = [
|
|
245
|
+
`CREATE INDEX IF NOT EXISTS idx_trace_spans_run ON agent_trace_spans (run_id)`,
|
|
246
|
+
`CREATE INDEX IF NOT EXISTS idx_trace_spans_thread ON agent_trace_spans (thread_id)`,
|
|
247
|
+
`CREATE INDEX IF NOT EXISTS idx_trace_spans_created ON agent_trace_spans (created_at)`,
|
|
248
|
+
`CREATE INDEX IF NOT EXISTS idx_trace_summaries_created ON agent_trace_summaries (created_at)`,
|
|
249
|
+
`CREATE INDEX IF NOT EXISTS idx_trace_summaries_user ON agent_trace_summaries (user_id, created_at)`,
|
|
250
|
+
`CREATE INDEX IF NOT EXISTS idx_trace_spans_user ON agent_trace_spans (user_id)`,
|
|
251
|
+
`CREATE INDEX IF NOT EXISTS idx_feedback_thread ON agent_feedback (thread_id)`,
|
|
252
|
+
`CREATE INDEX IF NOT EXISTS idx_feedback_created ON agent_feedback (created_at)`,
|
|
253
|
+
`CREATE INDEX IF NOT EXISTS idx_feedback_user ON agent_feedback (user_id, created_at)`,
|
|
254
|
+
`CREATE INDEX IF NOT EXISTS idx_satisfaction_thread ON agent_satisfaction_scores (thread_id)`,
|
|
255
|
+
`CREATE INDEX IF NOT EXISTS idx_satisfaction_user ON agent_satisfaction_scores (user_id, computed_at)`,
|
|
256
|
+
`CREATE INDEX IF NOT EXISTS idx_evals_run ON agent_evals (run_id)`,
|
|
257
|
+
`CREATE INDEX IF NOT EXISTS idx_evals_created ON agent_evals (created_at)`,
|
|
258
|
+
`CREATE INDEX IF NOT EXISTS idx_evals_user ON agent_evals (user_id, created_at)`,
|
|
259
|
+
`CREATE INDEX IF NOT EXISTS idx_experiment_results_exp ON agent_experiment_results (experiment_id)`,
|
|
260
|
+
];
|
|
261
|
+
for (const sql of indexes) {
|
|
262
|
+
try {
|
|
263
|
+
await client.execute(sql);
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
// Index might already exist
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}, { label: "observability" });
|
|
275
270
|
// ─── Trace span CRUD ─────────────────────────────────────────────────
|
|
276
271
|
export async function insertTraceSpan(span) {
|
|
277
272
|
await ensureObservabilityTables();
|
|
278
273
|
const client = getDbExec();
|
|
279
274
|
await client.execute({
|
|
280
|
-
sql: `INSERT INTO agent_trace_spans
|
|
281
|
-
(id, run_id, thread_id, user_id, parent_span_id, span_type, name,
|
|
282
|
-
input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
|
|
283
|
-
cost_cents_x100, duration_ms, status, error_message, metadata, created_at)
|
|
275
|
+
sql: `INSERT INTO agent_trace_spans
|
|
276
|
+
(id, run_id, thread_id, user_id, parent_span_id, span_type, name,
|
|
277
|
+
input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
|
|
278
|
+
cost_cents_x100, duration_ms, status, error_message, metadata, created_at)
|
|
284
279
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
285
280
|
args: [
|
|
286
281
|
span.id,
|
|
@@ -310,22 +305,22 @@ export async function upsertTraceSummary(summary) {
|
|
|
310
305
|
// owner is recorded it shouldn't change under us.
|
|
311
306
|
if (isPostgres()) {
|
|
312
307
|
await client.execute({
|
|
313
|
-
sql: `INSERT INTO agent_trace_summaries
|
|
314
|
-
(run_id, thread_id, user_id, total_spans, llm_calls, tool_calls,
|
|
315
|
-
successful_tools, failed_tools, total_duration_ms,
|
|
316
|
-
total_cost_cents_x100, total_input_tokens, total_output_tokens,
|
|
317
|
-
model, created_at)
|
|
318
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
319
|
-
ON CONFLICT (run_id) DO UPDATE SET
|
|
320
|
-
total_spans = EXCLUDED.total_spans,
|
|
321
|
-
llm_calls = EXCLUDED.llm_calls,
|
|
322
|
-
tool_calls = EXCLUDED.tool_calls,
|
|
323
|
-
successful_tools = EXCLUDED.successful_tools,
|
|
324
|
-
failed_tools = EXCLUDED.failed_tools,
|
|
325
|
-
total_duration_ms = EXCLUDED.total_duration_ms,
|
|
326
|
-
total_cost_cents_x100 = EXCLUDED.total_cost_cents_x100,
|
|
327
|
-
total_input_tokens = EXCLUDED.total_input_tokens,
|
|
328
|
-
total_output_tokens = EXCLUDED.total_output_tokens,
|
|
308
|
+
sql: `INSERT INTO agent_trace_summaries
|
|
309
|
+
(run_id, thread_id, user_id, total_spans, llm_calls, tool_calls,
|
|
310
|
+
successful_tools, failed_tools, total_duration_ms,
|
|
311
|
+
total_cost_cents_x100, total_input_tokens, total_output_tokens,
|
|
312
|
+
model, created_at)
|
|
313
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
314
|
+
ON CONFLICT (run_id) DO UPDATE SET
|
|
315
|
+
total_spans = EXCLUDED.total_spans,
|
|
316
|
+
llm_calls = EXCLUDED.llm_calls,
|
|
317
|
+
tool_calls = EXCLUDED.tool_calls,
|
|
318
|
+
successful_tools = EXCLUDED.successful_tools,
|
|
319
|
+
failed_tools = EXCLUDED.failed_tools,
|
|
320
|
+
total_duration_ms = EXCLUDED.total_duration_ms,
|
|
321
|
+
total_cost_cents_x100 = EXCLUDED.total_cost_cents_x100,
|
|
322
|
+
total_input_tokens = EXCLUDED.total_input_tokens,
|
|
323
|
+
total_output_tokens = EXCLUDED.total_output_tokens,
|
|
329
324
|
model = EXCLUDED.model`,
|
|
330
325
|
args: [
|
|
331
326
|
summary.runId,
|
|
@@ -347,22 +342,22 @@ export async function upsertTraceSummary(summary) {
|
|
|
347
342
|
}
|
|
348
343
|
else {
|
|
349
344
|
await client.execute({
|
|
350
|
-
sql: `INSERT INTO agent_trace_summaries
|
|
351
|
-
(run_id, thread_id, user_id, total_spans, llm_calls, tool_calls,
|
|
352
|
-
successful_tools, failed_tools, total_duration_ms,
|
|
353
|
-
total_cost_cents_x100, total_input_tokens, total_output_tokens,
|
|
354
|
-
model, created_at)
|
|
355
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
356
|
-
ON CONFLICT (run_id) DO UPDATE SET
|
|
357
|
-
total_spans = EXCLUDED.total_spans,
|
|
358
|
-
llm_calls = EXCLUDED.llm_calls,
|
|
359
|
-
tool_calls = EXCLUDED.tool_calls,
|
|
360
|
-
successful_tools = EXCLUDED.successful_tools,
|
|
361
|
-
failed_tools = EXCLUDED.failed_tools,
|
|
362
|
-
total_duration_ms = EXCLUDED.total_duration_ms,
|
|
363
|
-
total_cost_cents_x100 = EXCLUDED.total_cost_cents_x100,
|
|
364
|
-
total_input_tokens = EXCLUDED.total_input_tokens,
|
|
365
|
-
total_output_tokens = EXCLUDED.total_output_tokens,
|
|
345
|
+
sql: `INSERT INTO agent_trace_summaries
|
|
346
|
+
(run_id, thread_id, user_id, total_spans, llm_calls, tool_calls,
|
|
347
|
+
successful_tools, failed_tools, total_duration_ms,
|
|
348
|
+
total_cost_cents_x100, total_input_tokens, total_output_tokens,
|
|
349
|
+
model, created_at)
|
|
350
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
351
|
+
ON CONFLICT (run_id) DO UPDATE SET
|
|
352
|
+
total_spans = EXCLUDED.total_spans,
|
|
353
|
+
llm_calls = EXCLUDED.llm_calls,
|
|
354
|
+
tool_calls = EXCLUDED.tool_calls,
|
|
355
|
+
successful_tools = EXCLUDED.successful_tools,
|
|
356
|
+
failed_tools = EXCLUDED.failed_tools,
|
|
357
|
+
total_duration_ms = EXCLUDED.total_duration_ms,
|
|
358
|
+
total_cost_cents_x100 = EXCLUDED.total_cost_cents_x100,
|
|
359
|
+
total_input_tokens = EXCLUDED.total_input_tokens,
|
|
360
|
+
total_output_tokens = EXCLUDED.total_output_tokens,
|
|
366
361
|
model = EXCLUDED.model`,
|
|
367
362
|
args: [
|
|
368
363
|
summary.runId,
|
|
@@ -435,9 +430,9 @@ export async function getTraceSummaries(opts) {
|
|
|
435
430
|
const limit = opts.limit ?? 100;
|
|
436
431
|
const { where, args } = withUserFilter("created_at >= ?", [sinceMs], opts.userId);
|
|
437
432
|
const { rows } = await client.execute({
|
|
438
|
-
sql: `SELECT * FROM agent_trace_summaries
|
|
439
|
-
WHERE ${where}
|
|
440
|
-
ORDER BY created_at DESC
|
|
433
|
+
sql: `SELECT * FROM agent_trace_summaries
|
|
434
|
+
WHERE ${where}
|
|
435
|
+
ORDER BY created_at DESC
|
|
441
436
|
LIMIT ?`,
|
|
442
437
|
args: [...args, limit],
|
|
443
438
|
});
|
|
@@ -460,8 +455,8 @@ export async function insertFeedback(entry) {
|
|
|
460
455
|
await ensureObservabilityTables();
|
|
461
456
|
const client = getDbExec();
|
|
462
457
|
await client.execute({
|
|
463
|
-
sql: `INSERT INTO agent_feedback
|
|
464
|
-
(id, run_id, thread_id, message_seq, feedback_type, value, user_id, created_at)
|
|
458
|
+
sql: `INSERT INTO agent_feedback
|
|
459
|
+
(id, run_id, thread_id, message_seq, feedback_type, value, user_id, created_at)
|
|
465
460
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
466
461
|
args: [
|
|
467
462
|
entry.id,
|
|
@@ -509,8 +504,8 @@ export async function getFeedbackStats(sinceMs, opts = {}) {
|
|
|
509
504
|
const client = getDbExec();
|
|
510
505
|
const { where, args } = withUserFilter("created_at >= ?", [sinceMs], opts.userId);
|
|
511
506
|
const { rows } = await client.execute({
|
|
512
|
-
sql: `SELECT feedback_type, value, COUNT(*) as cnt
|
|
513
|
-
FROM agent_feedback WHERE ${where}
|
|
507
|
+
sql: `SELECT feedback_type, value, COUNT(*) as cnt
|
|
508
|
+
FROM agent_feedback WHERE ${where}
|
|
514
509
|
GROUP BY feedback_type, value`,
|
|
515
510
|
args,
|
|
516
511
|
});
|
|
@@ -536,16 +531,16 @@ export async function upsertSatisfactionScore(score) {
|
|
|
536
531
|
const client = getDbExec();
|
|
537
532
|
if (isPostgres()) {
|
|
538
533
|
await client.execute({
|
|
539
|
-
sql: `INSERT INTO agent_satisfaction_scores
|
|
540
|
-
(id, thread_id, user_id, frustration_score, rephrasing_score,
|
|
541
|
-
abandonment_score, sentiment_score, length_trend_score, computed_at)
|
|
542
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
543
|
-
ON CONFLICT (id) DO UPDATE SET
|
|
544
|
-
frustration_score = EXCLUDED.frustration_score,
|
|
545
|
-
rephrasing_score = EXCLUDED.rephrasing_score,
|
|
546
|
-
abandonment_score = EXCLUDED.abandonment_score,
|
|
547
|
-
sentiment_score = EXCLUDED.sentiment_score,
|
|
548
|
-
length_trend_score = EXCLUDED.length_trend_score,
|
|
534
|
+
sql: `INSERT INTO agent_satisfaction_scores
|
|
535
|
+
(id, thread_id, user_id, frustration_score, rephrasing_score,
|
|
536
|
+
abandonment_score, sentiment_score, length_trend_score, computed_at)
|
|
537
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
538
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
539
|
+
frustration_score = EXCLUDED.frustration_score,
|
|
540
|
+
rephrasing_score = EXCLUDED.rephrasing_score,
|
|
541
|
+
abandonment_score = EXCLUDED.abandonment_score,
|
|
542
|
+
sentiment_score = EXCLUDED.sentiment_score,
|
|
543
|
+
length_trend_score = EXCLUDED.length_trend_score,
|
|
549
544
|
computed_at = EXCLUDED.computed_at`,
|
|
550
545
|
args: [
|
|
551
546
|
score.id,
|
|
@@ -562,16 +557,16 @@ export async function upsertSatisfactionScore(score) {
|
|
|
562
557
|
}
|
|
563
558
|
else {
|
|
564
559
|
await client.execute({
|
|
565
|
-
sql: `INSERT INTO agent_satisfaction_scores
|
|
566
|
-
(id, thread_id, user_id, frustration_score, rephrasing_score,
|
|
567
|
-
abandonment_score, sentiment_score, length_trend_score, computed_at)
|
|
568
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
569
|
-
ON CONFLICT (id) DO UPDATE SET
|
|
570
|
-
frustration_score = EXCLUDED.frustration_score,
|
|
571
|
-
rephrasing_score = EXCLUDED.rephrasing_score,
|
|
572
|
-
abandonment_score = EXCLUDED.abandonment_score,
|
|
573
|
-
sentiment_score = EXCLUDED.sentiment_score,
|
|
574
|
-
length_trend_score = EXCLUDED.length_trend_score,
|
|
560
|
+
sql: `INSERT INTO agent_satisfaction_scores
|
|
561
|
+
(id, thread_id, user_id, frustration_score, rephrasing_score,
|
|
562
|
+
abandonment_score, sentiment_score, length_trend_score, computed_at)
|
|
563
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
564
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
565
|
+
frustration_score = EXCLUDED.frustration_score,
|
|
566
|
+
rephrasing_score = EXCLUDED.rephrasing_score,
|
|
567
|
+
abandonment_score = EXCLUDED.abandonment_score,
|
|
568
|
+
sentiment_score = EXCLUDED.sentiment_score,
|
|
569
|
+
length_trend_score = EXCLUDED.length_trend_score,
|
|
575
570
|
computed_at = EXCLUDED.computed_at`,
|
|
576
571
|
args: [
|
|
577
572
|
score.id,
|
|
@@ -606,7 +601,7 @@ export async function getSatisfactionScores(opts) {
|
|
|
606
601
|
}
|
|
607
602
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
608
603
|
const { rows } = await client.execute({
|
|
609
|
-
sql: `SELECT * FROM agent_satisfaction_scores ${where}
|
|
604
|
+
sql: `SELECT * FROM agent_satisfaction_scores ${where}
|
|
610
605
|
ORDER BY computed_at DESC LIMIT ?`,
|
|
611
606
|
args: [...args, opts.limit ?? 100],
|
|
612
607
|
});
|
|
@@ -617,8 +612,8 @@ export async function insertEvalResult(result) {
|
|
|
617
612
|
await ensureObservabilityTables();
|
|
618
613
|
const client = getDbExec();
|
|
619
614
|
await client.execute({
|
|
620
|
-
sql: `INSERT INTO agent_evals
|
|
621
|
-
(id, run_id, thread_id, user_id, eval_type, criteria, score, reasoning, metadata, created_at)
|
|
615
|
+
sql: `INSERT INTO agent_evals
|
|
616
|
+
(id, run_id, thread_id, user_id, eval_type, criteria, score, reasoning, metadata, created_at)
|
|
622
617
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
623
618
|
args: [
|
|
624
619
|
result.id,
|
|
@@ -649,14 +644,14 @@ export async function getEvalStats(sinceMs, opts = {}) {
|
|
|
649
644
|
const client = getDbExec();
|
|
650
645
|
const { where, args } = withUserFilter("created_at >= ?", [sinceMs], opts.userId);
|
|
651
646
|
const { rows: totalRows } = await client.execute({
|
|
652
|
-
sql: `SELECT COUNT(*) as cnt, AVG(score) as avg_score
|
|
647
|
+
sql: `SELECT COUNT(*) as cnt, AVG(score) as avg_score
|
|
653
648
|
FROM agent_evals WHERE ${where}`,
|
|
654
649
|
args,
|
|
655
650
|
});
|
|
656
651
|
const t = (totalRows[0] ?? {});
|
|
657
652
|
const { rows: criteriaRows } = await client.execute({
|
|
658
|
-
sql: `SELECT criteria, AVG(score) as avg_score, COUNT(*) as cnt
|
|
659
|
-
FROM agent_evals WHERE ${where}
|
|
653
|
+
sql: `SELECT criteria, AVG(score) as avg_score, COUNT(*) as cnt
|
|
654
|
+
FROM agent_evals WHERE ${where}
|
|
660
655
|
GROUP BY criteria ORDER BY cnt DESC`,
|
|
661
656
|
args,
|
|
662
657
|
});
|
|
@@ -675,8 +670,8 @@ export async function insertEvalDataset(dataset) {
|
|
|
675
670
|
await ensureObservabilityTables();
|
|
676
671
|
const client = getDbExec();
|
|
677
672
|
await client.execute({
|
|
678
|
-
sql: `INSERT INTO agent_eval_datasets
|
|
679
|
-
(id, name, description, entries, created_at, updated_at)
|
|
673
|
+
sql: `INSERT INTO agent_eval_datasets
|
|
674
|
+
(id, name, description, entries, created_at, updated_at)
|
|
680
675
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
681
676
|
args: [
|
|
682
677
|
dataset.id,
|
|
@@ -735,9 +730,9 @@ export async function insertExperiment(exp) {
|
|
|
735
730
|
await ensureObservabilityTables();
|
|
736
731
|
const client = getDbExec();
|
|
737
732
|
await client.execute({
|
|
738
|
-
sql: `INSERT INTO agent_experiments
|
|
739
|
-
(id, name, status, variants, metrics, assignment_level,
|
|
740
|
-
started_at, ended_at, created_at, owner_email)
|
|
733
|
+
sql: `INSERT INTO agent_experiments
|
|
734
|
+
(id, name, status, variants, metrics, assignment_level,
|
|
735
|
+
started_at, ended_at, created_at, owner_email)
|
|
741
736
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
742
737
|
args: [
|
|
743
738
|
exp.id,
|
|
@@ -813,11 +808,11 @@ export async function upsertAssignment(assignment) {
|
|
|
813
808
|
const client = getDbExec();
|
|
814
809
|
if (isPostgres()) {
|
|
815
810
|
await client.execute({
|
|
816
|
-
sql: `INSERT INTO agent_experiment_assignments
|
|
817
|
-
(experiment_id, user_id, variant_id, assigned_at)
|
|
818
|
-
VALUES (?, ?, ?, ?)
|
|
819
|
-
ON CONFLICT (experiment_id, user_id) DO UPDATE SET
|
|
820
|
-
variant_id = EXCLUDED.variant_id,
|
|
811
|
+
sql: `INSERT INTO agent_experiment_assignments
|
|
812
|
+
(experiment_id, user_id, variant_id, assigned_at)
|
|
813
|
+
VALUES (?, ?, ?, ?)
|
|
814
|
+
ON CONFLICT (experiment_id, user_id) DO UPDATE SET
|
|
815
|
+
variant_id = EXCLUDED.variant_id,
|
|
821
816
|
assigned_at = EXCLUDED.assigned_at`,
|
|
822
817
|
args: [
|
|
823
818
|
assignment.experimentId,
|
|
@@ -829,8 +824,8 @@ export async function upsertAssignment(assignment) {
|
|
|
829
824
|
}
|
|
830
825
|
else {
|
|
831
826
|
await client.execute({
|
|
832
|
-
sql: `INSERT OR REPLACE INTO agent_experiment_assignments
|
|
833
|
-
(experiment_id, user_id, variant_id, assigned_at)
|
|
827
|
+
sql: `INSERT OR REPLACE INTO agent_experiment_assignments
|
|
828
|
+
(experiment_id, user_id, variant_id, assigned_at)
|
|
834
829
|
VALUES (?, ?, ?, ?)`,
|
|
835
830
|
args: [
|
|
836
831
|
assignment.experimentId,
|
|
@@ -845,7 +840,7 @@ export async function getAssignment(experimentId, userId) {
|
|
|
845
840
|
await ensureObservabilityTables();
|
|
846
841
|
const client = getDbExec();
|
|
847
842
|
const { rows } = await client.execute({
|
|
848
|
-
sql: `SELECT * FROM agent_experiment_assignments
|
|
843
|
+
sql: `SELECT * FROM agent_experiment_assignments
|
|
849
844
|
WHERE experiment_id = ? AND user_id = ?`,
|
|
850
845
|
args: [experimentId, userId],
|
|
851
846
|
});
|
|
@@ -864,9 +859,9 @@ export async function insertExperimentResult(result) {
|
|
|
864
859
|
await ensureObservabilityTables();
|
|
865
860
|
const client = getDbExec();
|
|
866
861
|
await client.execute({
|
|
867
|
-
sql: `INSERT INTO agent_experiment_results
|
|
868
|
-
(id, experiment_id, variant_id, metric, value,
|
|
869
|
-
sample_size, confidence_low, confidence_high, computed_at)
|
|
862
|
+
sql: `INSERT INTO agent_experiment_results
|
|
863
|
+
(id, experiment_id, variant_id, metric, value,
|
|
864
|
+
sample_size, confidence_low, confidence_high, computed_at)
|
|
870
865
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
871
866
|
args: [
|
|
872
867
|
result.id,
|
|
@@ -885,8 +880,8 @@ export async function getExperimentResults(experimentId) {
|
|
|
885
880
|
await ensureObservabilityTables();
|
|
886
881
|
const client = getDbExec();
|
|
887
882
|
const { rows } = await client.execute({
|
|
888
|
-
sql: `SELECT * FROM agent_experiment_results
|
|
889
|
-
WHERE experiment_id = ?
|
|
883
|
+
sql: `SELECT * FROM agent_experiment_results
|
|
884
|
+
WHERE experiment_id = ?
|
|
890
885
|
ORDER BY computed_at DESC`,
|
|
891
886
|
args: [experimentId],
|
|
892
887
|
});
|
|
@@ -904,29 +899,29 @@ export async function getObservabilityOverview(sinceMs, opts = {}) {
|
|
|
904
899
|
const computed = withUserFilter("computed_at >= ?", [sinceMs], opts.userId);
|
|
905
900
|
const [tracesResult, satisfactionResult, feedbackResult, evalsResult] = await Promise.all([
|
|
906
901
|
client.execute({
|
|
907
|
-
sql: `SELECT
|
|
908
|
-
COUNT(*) as total_runs,
|
|
909
|
-
COALESCE(SUM(total_cost_cents_x100), 0) as total_cost,
|
|
910
|
-
COALESCE(AVG(total_duration_ms), 0) as avg_duration,
|
|
911
|
-
COALESCE(SUM(successful_tools), 0) as success_tools,
|
|
912
|
-
COALESCE(SUM(tool_calls), 0) as total_tools
|
|
902
|
+
sql: `SELECT
|
|
903
|
+
COUNT(*) as total_runs,
|
|
904
|
+
COALESCE(SUM(total_cost_cents_x100), 0) as total_cost,
|
|
905
|
+
COALESCE(AVG(total_duration_ms), 0) as avg_duration,
|
|
906
|
+
COALESCE(SUM(successful_tools), 0) as success_tools,
|
|
907
|
+
COALESCE(SUM(tool_calls), 0) as total_tools
|
|
913
908
|
FROM agent_trace_summaries WHERE ${created.where}`,
|
|
914
909
|
args: created.args,
|
|
915
910
|
}),
|
|
916
911
|
client.execute({
|
|
917
|
-
sql: `SELECT COALESCE(AVG(frustration_score), 0) as avg_frustration
|
|
912
|
+
sql: `SELECT COALESCE(AVG(frustration_score), 0) as avg_frustration
|
|
918
913
|
FROM agent_satisfaction_scores WHERE ${computed.where}`,
|
|
919
914
|
args: computed.args,
|
|
920
915
|
}),
|
|
921
916
|
client.execute({
|
|
922
|
-
sql: `SELECT
|
|
923
|
-
COALESCE(SUM(CASE WHEN feedback_type = 'thumbs_up' THEN 1 ELSE 0 END), 0) as up,
|
|
924
|
-
COALESCE(SUM(CASE WHEN feedback_type IN ('thumbs_up', 'thumbs_down') THEN 1 ELSE 0 END), 0) as total
|
|
917
|
+
sql: `SELECT
|
|
918
|
+
COALESCE(SUM(CASE WHEN feedback_type = 'thumbs_up' THEN 1 ELSE 0 END), 0) as up,
|
|
919
|
+
COALESCE(SUM(CASE WHEN feedback_type IN ('thumbs_up', 'thumbs_down') THEN 1 ELSE 0 END), 0) as total
|
|
925
920
|
FROM agent_feedback WHERE ${created.where}`,
|
|
926
921
|
args: created.args,
|
|
927
922
|
}),
|
|
928
923
|
client.execute({
|
|
929
|
-
sql: `SELECT COALESCE(AVG(score), 0) as avg_score
|
|
924
|
+
sql: `SELECT COALESCE(AVG(score), 0) as avg_score
|
|
930
925
|
FROM agent_evals WHERE ${created.where}`,
|
|
931
926
|
args: created.args,
|
|
932
927
|
}),
|