@axiom-lattice/gateway 2.1.73 → 2.1.74
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/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +12 -0
- package/dist/index.js +623 -43
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +604 -24
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -5
- package/src/controllers/eval.ts +469 -0
- package/src/controllers/workflow-tracking.ts +2 -0
- package/src/routes/index.ts +3 -0
- package/src/services/eval-runner.ts +228 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiom-lattice/gateway",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.74",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/index.mjs",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -39,10 +39,11 @@
|
|
|
39
39
|
"pg": "^8.11.0",
|
|
40
40
|
"redis": "^5.0.1",
|
|
41
41
|
"uuid": "^9.0.1",
|
|
42
|
-
"@axiom-lattice/
|
|
43
|
-
"@axiom-lattice/
|
|
44
|
-
"@axiom-lattice/
|
|
45
|
-
"@axiom-lattice/
|
|
42
|
+
"@axiom-lattice/agent-eval": "2.1.58",
|
|
43
|
+
"@axiom-lattice/core": "2.1.64",
|
|
44
|
+
"@axiom-lattice/pg-stores": "1.0.54",
|
|
45
|
+
"@axiom-lattice/protocols": "2.1.33",
|
|
46
|
+
"@axiom-lattice/queue-redis": "1.0.32"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"@types/jest": "^29.5.14",
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
|
|
2
|
+
import { getStoreLattice } from "@axiom-lattice/core";
|
|
3
|
+
import { EvalStore } from "@axiom-lattice/protocols";
|
|
4
|
+
import { v4 as uuidv4 } from "uuid";
|
|
5
|
+
import { evalRunner, EvalStreamEvent } from "../services/eval-runner";
|
|
6
|
+
|
|
7
|
+
function getTenantId(request: FastifyRequest): string {
|
|
8
|
+
const userTenantId = (request as any).user?.tenantId;
|
|
9
|
+
if (userTenantId) {
|
|
10
|
+
return userTenantId;
|
|
11
|
+
}
|
|
12
|
+
return (request.headers["x-tenant-id"] as string) || "default";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getEvalStore(): EvalStore {
|
|
16
|
+
return getStoreLattice("default", "eval").store as EvalStore;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function createProject(
|
|
20
|
+
request: FastifyRequest<{ Body: Record<string, unknown> }>,
|
|
21
|
+
reply: FastifyReply
|
|
22
|
+
): Promise<{ success: boolean; message: string; data?: unknown }> {
|
|
23
|
+
try {
|
|
24
|
+
const tenantId = getTenantId(request);
|
|
25
|
+
const store = getEvalStore();
|
|
26
|
+
const id = uuidv4();
|
|
27
|
+
const data = request.body as any;
|
|
28
|
+
const project = await store.createProject(tenantId, id, {
|
|
29
|
+
name: data.name,
|
|
30
|
+
description: data.description,
|
|
31
|
+
version: data.version,
|
|
32
|
+
judgeModelConfig: data.judgeModelConfig ?? {},
|
|
33
|
+
targetServerConfig: data.targetServerConfig ?? {},
|
|
34
|
+
concurrency: data.concurrency ?? 1,
|
|
35
|
+
reportConfig: data.reportConfig,
|
|
36
|
+
});
|
|
37
|
+
return reply.status(201).send({
|
|
38
|
+
success: true,
|
|
39
|
+
message: "Successfully created project",
|
|
40
|
+
data: project,
|
|
41
|
+
});
|
|
42
|
+
} catch (err: unknown) {
|
|
43
|
+
const message = err instanceof Error ? err.message : "Failed to create project";
|
|
44
|
+
return reply.status(500).send({ success: false, message });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function listProjects(
|
|
49
|
+
request: FastifyRequest,
|
|
50
|
+
reply: FastifyReply
|
|
51
|
+
): Promise<{ success: boolean; message: string; data?: unknown }> {
|
|
52
|
+
try {
|
|
53
|
+
const tenantId = getTenantId(request);
|
|
54
|
+
const store = getEvalStore();
|
|
55
|
+
let projects = await store.getProjectsByTenant(tenantId);
|
|
56
|
+
|
|
57
|
+
// Seed a default project for new tenants
|
|
58
|
+
if (projects.length === 0) {
|
|
59
|
+
try {
|
|
60
|
+
const { modelLatticeManager } = await import("@axiom-lattice/core");
|
|
61
|
+
const models = modelLatticeManager.getAllLattices();
|
|
62
|
+
const first = models[0];
|
|
63
|
+
const judgeModel = first
|
|
64
|
+
? { modelKey: first.key }
|
|
65
|
+
: { provider: "openai", model: "gpt-4" };
|
|
66
|
+
|
|
67
|
+
const host = request.hostname || "localhost";
|
|
68
|
+
const baseUrl = `http://${host}:${process.env.PORT || 4001}`;
|
|
69
|
+
|
|
70
|
+
await store.createProject(tenantId, uuidv4(), {
|
|
71
|
+
name: "Default",
|
|
72
|
+
description: "Built-in project for testing eval against this server",
|
|
73
|
+
judgeModelConfig: judgeModel,
|
|
74
|
+
targetServerConfig: { base_url: baseUrl, api_key: "" },
|
|
75
|
+
concurrency: 3,
|
|
76
|
+
});
|
|
77
|
+
projects = await store.getProjectsByTenant(tenantId);
|
|
78
|
+
} catch { /* seed failure is non-fatal */ }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
success: true,
|
|
83
|
+
message: "Ok",
|
|
84
|
+
data: { records: projects, total: projects.length },
|
|
85
|
+
};
|
|
86
|
+
} catch (err: unknown) {
|
|
87
|
+
const message = err instanceof Error ? err.message : "Failed to list projects";
|
|
88
|
+
return reply.status(500).send({ success: false, message });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function getProject(
|
|
93
|
+
request: FastifyRequest<{ Params: { pid: string } }>,
|
|
94
|
+
reply: FastifyReply
|
|
95
|
+
): Promise<{ success: boolean; message: string; data?: unknown }> {
|
|
96
|
+
try {
|
|
97
|
+
const tenantId = getTenantId(request);
|
|
98
|
+
const store = getEvalStore();
|
|
99
|
+
const { pid } = request.params;
|
|
100
|
+
const project = await store.getProjectById(tenantId, pid);
|
|
101
|
+
if (!project) {
|
|
102
|
+
return reply.status(404).send({ success: false, message: "Project not found" });
|
|
103
|
+
}
|
|
104
|
+
const suites = await store.getSuitesByProject(tenantId, pid);
|
|
105
|
+
return {
|
|
106
|
+
success: true,
|
|
107
|
+
message: "Ok",
|
|
108
|
+
data: { project, suites },
|
|
109
|
+
};
|
|
110
|
+
} catch (err: unknown) {
|
|
111
|
+
const message = err instanceof Error ? err.message : "Failed to get project";
|
|
112
|
+
return reply.status(500).send({ success: false, message });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function updateProject(
|
|
117
|
+
request: FastifyRequest<{ Params: { pid: string }; Body: Record<string, unknown> }>,
|
|
118
|
+
reply: FastifyReply
|
|
119
|
+
): Promise<{ success: boolean; message: string; data?: unknown }> {
|
|
120
|
+
try {
|
|
121
|
+
const tenantId = getTenantId(request);
|
|
122
|
+
const store = getEvalStore();
|
|
123
|
+
const { pid } = request.params;
|
|
124
|
+
const existing = await store.getProjectById(tenantId, pid);
|
|
125
|
+
if (!existing) {
|
|
126
|
+
return reply.status(404).send({ success: false, message: "Project not found" });
|
|
127
|
+
}
|
|
128
|
+
const updated = await store.updateProject(tenantId, pid, request.body as any);
|
|
129
|
+
return {
|
|
130
|
+
success: true,
|
|
131
|
+
message: "Successfully updated project",
|
|
132
|
+
data: updated,
|
|
133
|
+
};
|
|
134
|
+
} catch (err: unknown) {
|
|
135
|
+
const message = err instanceof Error ? err.message : "Failed to update project";
|
|
136
|
+
return reply.status(500).send({ success: false, message });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function deleteProject(
|
|
141
|
+
request: FastifyRequest<{ Params: { pid: string } }>,
|
|
142
|
+
reply: FastifyReply
|
|
143
|
+
): Promise<{ success: boolean; message: string }> {
|
|
144
|
+
try {
|
|
145
|
+
const tenantId = getTenantId(request);
|
|
146
|
+
const store = getEvalStore();
|
|
147
|
+
const { pid } = request.params;
|
|
148
|
+
const existing = await store.getProjectById(tenantId, pid);
|
|
149
|
+
if (!existing) {
|
|
150
|
+
return reply.status(404).send({ success: false, message: "Project not found" });
|
|
151
|
+
}
|
|
152
|
+
const deleted = await store.deleteProject(tenantId, pid);
|
|
153
|
+
if (!deleted) {
|
|
154
|
+
return reply.status(500).send({ success: false, message: "Failed to delete project" });
|
|
155
|
+
}
|
|
156
|
+
return { success: true, message: "Successfully deleted project" };
|
|
157
|
+
} catch (err: unknown) {
|
|
158
|
+
const message = err instanceof Error ? err.message : "Failed to delete project";
|
|
159
|
+
return reply.status(500).send({ success: false, message });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export async function createSuite(
|
|
164
|
+
request: FastifyRequest<{ Params: { pid: string }; Body: Record<string, unknown> }>,
|
|
165
|
+
reply: FastifyReply
|
|
166
|
+
): Promise<{ success: boolean; message: string; data?: unknown }> {
|
|
167
|
+
try {
|
|
168
|
+
const tenantId = getTenantId(request);
|
|
169
|
+
const store = getEvalStore();
|
|
170
|
+
const { pid } = request.params;
|
|
171
|
+
const project = await store.getProjectById(tenantId, pid);
|
|
172
|
+
if (!project) {
|
|
173
|
+
return reply.status(404).send({ success: false, message: "Project not found" });
|
|
174
|
+
}
|
|
175
|
+
const id = uuidv4();
|
|
176
|
+
const data = request.body as any;
|
|
177
|
+
const suite = await store.createSuite(tenantId, pid, id, { name: data.name });
|
|
178
|
+
return reply.status(201).send({
|
|
179
|
+
success: true,
|
|
180
|
+
message: "Successfully created suite",
|
|
181
|
+
data: suite,
|
|
182
|
+
});
|
|
183
|
+
} catch (err: unknown) {
|
|
184
|
+
const message = err instanceof Error ? err.message : "Failed to create suite";
|
|
185
|
+
return reply.status(500).send({ success: false, message });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export async function updateSuite(
|
|
190
|
+
request: FastifyRequest<{ Params: { pid: string; sid: string }; Body: Record<string, unknown> }>,
|
|
191
|
+
reply: FastifyReply
|
|
192
|
+
): Promise<{ success: boolean; message: string; data?: unknown }> {
|
|
193
|
+
try {
|
|
194
|
+
const tenantId = getTenantId(request);
|
|
195
|
+
const store = getEvalStore();
|
|
196
|
+
const { sid } = request.params;
|
|
197
|
+
const existing = await store.getSuiteById(tenantId, sid);
|
|
198
|
+
if (!existing) {
|
|
199
|
+
return reply.status(404).send({ success: false, message: "Suite not found" });
|
|
200
|
+
}
|
|
201
|
+
const updated = await store.updateSuite(tenantId, sid, request.body as any);
|
|
202
|
+
return {
|
|
203
|
+
success: true,
|
|
204
|
+
message: "Successfully updated suite",
|
|
205
|
+
data: updated,
|
|
206
|
+
};
|
|
207
|
+
} catch (err: unknown) {
|
|
208
|
+
const message = err instanceof Error ? err.message : "Failed to update suite";
|
|
209
|
+
return reply.status(500).send({ success: false, message });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export async function deleteSuite(
|
|
214
|
+
request: FastifyRequest<{ Params: { pid: string; sid: string } }>,
|
|
215
|
+
reply: FastifyReply
|
|
216
|
+
): Promise<{ success: boolean; message: string }> {
|
|
217
|
+
try {
|
|
218
|
+
const tenantId = getTenantId(request);
|
|
219
|
+
const store = getEvalStore();
|
|
220
|
+
const { sid } = request.params;
|
|
221
|
+
const existing = await store.getSuiteById(tenantId, sid);
|
|
222
|
+
if (!existing) {
|
|
223
|
+
return reply.status(404).send({ success: false, message: "Suite not found" });
|
|
224
|
+
}
|
|
225
|
+
const deleted = await store.deleteSuite(tenantId, sid);
|
|
226
|
+
if (!deleted) {
|
|
227
|
+
return reply.status(500).send({ success: false, message: "Failed to delete suite" });
|
|
228
|
+
}
|
|
229
|
+
return { success: true, message: "Successfully deleted suite" };
|
|
230
|
+
} catch (err: unknown) {
|
|
231
|
+
const message = err instanceof Error ? err.message : "Failed to delete suite";
|
|
232
|
+
return reply.status(500).send({ success: false, message });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export async function createCase(
|
|
237
|
+
request: FastifyRequest<{ Params: { pid: string; sid: string }; Body: Record<string, unknown> }>,
|
|
238
|
+
reply: FastifyReply
|
|
239
|
+
): Promise<{ success: boolean; message: string; data?: unknown }> {
|
|
240
|
+
try {
|
|
241
|
+
const tenantId = getTenantId(request);
|
|
242
|
+
const store = getEvalStore();
|
|
243
|
+
const { sid } = request.params;
|
|
244
|
+
const suite = await store.getSuiteById(tenantId, sid);
|
|
245
|
+
if (!suite) {
|
|
246
|
+
return reply.status(404).send({ success: false, message: "Suite not found" });
|
|
247
|
+
}
|
|
248
|
+
const id = uuidv4();
|
|
249
|
+
const data = request.body as any;
|
|
250
|
+
const created = await store.createCase(tenantId, sid, id, {
|
|
251
|
+
inputMessage: data.inputMessage,
|
|
252
|
+
inputFiles: data.inputFiles,
|
|
253
|
+
steps: data.steps ?? [],
|
|
254
|
+
outputType: data.outputType ?? "message_content",
|
|
255
|
+
contentAssertion: data.contentAssertion ?? "",
|
|
256
|
+
rubrics: data.rubrics,
|
|
257
|
+
});
|
|
258
|
+
return reply.status(201).send({
|
|
259
|
+
success: true,
|
|
260
|
+
message: "Successfully created case",
|
|
261
|
+
data: created,
|
|
262
|
+
});
|
|
263
|
+
} catch (err: unknown) {
|
|
264
|
+
const message = err instanceof Error ? err.message : "Failed to create case";
|
|
265
|
+
return reply.status(500).send({ success: false, message });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export async function listCasesForSuite(
|
|
270
|
+
request: FastifyRequest<{ Params: { pid: string; sid: string } }>,
|
|
271
|
+
reply: FastifyReply
|
|
272
|
+
): Promise<{ success: boolean; message: string; data?: unknown }> {
|
|
273
|
+
try {
|
|
274
|
+
const tenantId = getTenantId(request);
|
|
275
|
+
const store = getEvalStore();
|
|
276
|
+
const { sid } = request.params;
|
|
277
|
+
const cases = await store.getCasesBySuite(tenantId, sid);
|
|
278
|
+
return {
|
|
279
|
+
success: true,
|
|
280
|
+
message: "Ok",
|
|
281
|
+
data: { records: cases, total: cases.length },
|
|
282
|
+
};
|
|
283
|
+
} catch (err: unknown) {
|
|
284
|
+
const message = err instanceof Error ? err.message : "Failed to list cases";
|
|
285
|
+
return reply.status(500).send({ success: false, message });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export async function updateCase(
|
|
290
|
+
request: FastifyRequest<{ Params: { pid: string; sid: string; cid: string }; Body: Record<string, unknown> }>,
|
|
291
|
+
reply: FastifyReply
|
|
292
|
+
): Promise<{ success: boolean; message: string; data?: unknown }> {
|
|
293
|
+
try {
|
|
294
|
+
const tenantId = getTenantId(request);
|
|
295
|
+
const store = getEvalStore();
|
|
296
|
+
const { cid } = request.params;
|
|
297
|
+
const existing = await store.getCaseById(tenantId, cid);
|
|
298
|
+
if (!existing) {
|
|
299
|
+
return reply.status(404).send({ success: false, message: "Case not found" });
|
|
300
|
+
}
|
|
301
|
+
const updated = await store.updateCase(tenantId, cid, request.body as any);
|
|
302
|
+
return {
|
|
303
|
+
success: true,
|
|
304
|
+
message: "Successfully updated case",
|
|
305
|
+
data: updated,
|
|
306
|
+
};
|
|
307
|
+
} catch (err: unknown) {
|
|
308
|
+
const message = err instanceof Error ? err.message : "Failed to update case";
|
|
309
|
+
return reply.status(500).send({ success: false, message });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export async function deleteCase(
|
|
314
|
+
request: FastifyRequest<{ Params: { pid: string; sid: string; cid: string } }>,
|
|
315
|
+
reply: FastifyReply
|
|
316
|
+
): Promise<{ success: boolean; message: string }> {
|
|
317
|
+
try {
|
|
318
|
+
const tenantId = getTenantId(request);
|
|
319
|
+
const store = getEvalStore();
|
|
320
|
+
const { cid } = request.params;
|
|
321
|
+
const existing = await store.getCaseById(tenantId, cid);
|
|
322
|
+
if (!existing) {
|
|
323
|
+
return reply.status(404).send({ success: false, message: "Case not found" });
|
|
324
|
+
}
|
|
325
|
+
const deleted = await store.deleteCase(tenantId, cid);
|
|
326
|
+
if (!deleted) {
|
|
327
|
+
return reply.status(500).send({ success: false, message: "Failed to delete case" });
|
|
328
|
+
}
|
|
329
|
+
return { success: true, message: "Successfully deleted case" };
|
|
330
|
+
} catch (err: unknown) {
|
|
331
|
+
const message = err instanceof Error ? err.message : "Failed to delete case";
|
|
332
|
+
return reply.status(500).send({ success: false, message });
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function registerEvalRoutes(app: FastifyInstance): void {
|
|
337
|
+
app.post("/api/eval/projects", createProject);
|
|
338
|
+
app.get("/api/eval/projects", listProjects);
|
|
339
|
+
app.get("/api/eval/projects/:pid", getProject);
|
|
340
|
+
app.put("/api/eval/projects/:pid", updateProject);
|
|
341
|
+
app.delete("/api/eval/projects/:pid", deleteProject);
|
|
342
|
+
|
|
343
|
+
app.post("/api/eval/projects/:pid/suites", createSuite);
|
|
344
|
+
app.put("/api/eval/projects/:pid/suites/:sid", updateSuite);
|
|
345
|
+
app.delete("/api/eval/projects/:pid/suites/:sid", deleteSuite);
|
|
346
|
+
|
|
347
|
+
app.post("/api/eval/projects/:pid/suites/:sid/cases", createCase);
|
|
348
|
+
app.get("/api/eval/projects/:pid/suites/:sid/cases", listCasesForSuite);
|
|
349
|
+
app.put("/api/eval/projects/:pid/suites/:sid/cases/:cid", updateCase);
|
|
350
|
+
app.delete("/api/eval/projects/:pid/suites/:sid/cases/:cid", deleteCase);
|
|
351
|
+
|
|
352
|
+
// POST /api/eval/projects/:pid/runs — trigger run
|
|
353
|
+
app.post("/api/eval/projects/:pid/runs", async (request, reply) => {
|
|
354
|
+
try {
|
|
355
|
+
const tenantId = getTenantId(request);
|
|
356
|
+
const { pid } = request.params as { pid: string };
|
|
357
|
+
const runId = await evalRunner.startRun(tenantId, pid);
|
|
358
|
+
reply.status(202).send({ success: true, message: "Run started", data: { run_id: runId } });
|
|
359
|
+
} catch (err) {
|
|
360
|
+
const msg = (err as Error).message;
|
|
361
|
+
const code = msg === "Project not found" ? 404 : msg.includes("already in progress") ? 409 : 500;
|
|
362
|
+
reply.status(code).send({ success: false, message: msg });
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// GET /api/eval/runs — list runs
|
|
367
|
+
app.get("/api/eval/runs", async (request, reply) => {
|
|
368
|
+
try {
|
|
369
|
+
const tenantId = getTenantId(request);
|
|
370
|
+
const query = request.query as { project_id?: string; status?: string };
|
|
371
|
+
const runs = await getEvalStore().getRunsByTenant(tenantId, { projectId: query.project_id, status: query.status });
|
|
372
|
+
reply.send({ success: true, message: "Ok", data: { records: runs, total: runs.length } });
|
|
373
|
+
} catch (err) {
|
|
374
|
+
reply.status(500).send({ success: false, message: (err as Error).message });
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// GET /api/eval/runs/:rid — get run detail with results
|
|
379
|
+
app.get("/api/eval/runs/:rid", async (request, reply) => {
|
|
380
|
+
try {
|
|
381
|
+
const tenantId = getTenantId(request);
|
|
382
|
+
const { rid } = request.params as { rid: string };
|
|
383
|
+
const run = await getEvalStore().getRunById(tenantId, rid);
|
|
384
|
+
if (!run) return reply.status(404).send({ success: false, message: "Run not found" });
|
|
385
|
+
const results = await getEvalStore().getResultsByRun(tenantId, rid);
|
|
386
|
+
reply.send({ success: true, message: "Ok", data: { ...run, results } });
|
|
387
|
+
} catch (err) {
|
|
388
|
+
reply.status(500).send({ success: false, message: (err as Error).message });
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// GET /api/eval/runs/:rid/stream — SSE progress stream
|
|
393
|
+
app.get("/api/eval/runs/:rid/stream", async (request, reply) => {
|
|
394
|
+
reply.hijack();
|
|
395
|
+
reply.raw.writeHead(200, {
|
|
396
|
+
"Content-Type": "text/event-stream",
|
|
397
|
+
"Cache-Control": "no-cache",
|
|
398
|
+
Connection: "keep-alive",
|
|
399
|
+
"Access-Control-Allow-Origin": "*",
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
const { rid } = request.params as { rid: string };
|
|
403
|
+
const emitter = evalRunner.getEventEmitter();
|
|
404
|
+
const eventKey = `run:${rid}`;
|
|
405
|
+
|
|
406
|
+
if (!evalRunner.isRunning(rid)) {
|
|
407
|
+
const tenantId = getTenantId(request);
|
|
408
|
+
const run = await getEvalStore().getRunById(tenantId, rid);
|
|
409
|
+
if (run) {
|
|
410
|
+
const eventType = run.status === "completed" ? "completed" : "error";
|
|
411
|
+
reply.raw.write(`event: ${eventType}\ndata: ${JSON.stringify({ passed: run.passedCases, failed: run.failedCases, avgScore: run.avgScore })}\n\n`);
|
|
412
|
+
}
|
|
413
|
+
reply.raw.end();
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const handler = (event: EvalStreamEvent): void => {
|
|
418
|
+
reply.raw.write(`event: ${event.type}\ndata: ${JSON.stringify(event.data)}\n\n`);
|
|
419
|
+
if (event.type === "completed" || event.type === "error") {
|
|
420
|
+
emitter.off(eventKey, handler);
|
|
421
|
+
reply.raw.end();
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
emitter.on(eventKey, handler);
|
|
426
|
+
|
|
427
|
+
request.raw.on("close", () => {
|
|
428
|
+
emitter.off(eventKey, handler);
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// POST /api/eval/runs/:rid/abort — abort run
|
|
433
|
+
app.post("/api/eval/runs/:rid/abort", async (request, reply) => {
|
|
434
|
+
try {
|
|
435
|
+
const { rid } = request.params as { rid: string };
|
|
436
|
+
const aborted = await evalRunner.abortRun(rid);
|
|
437
|
+
if (!aborted) return reply.status(404).send({ success: false, message: "Run not found or not running" });
|
|
438
|
+
reply.send({ success: true, message: "Run aborted" });
|
|
439
|
+
} catch (err) {
|
|
440
|
+
reply.status(500).send({ success: false, message: (err as Error).message });
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// DELETE /api/eval/runs/:rid — delete run and results
|
|
445
|
+
app.delete("/api/eval/runs/:rid", async (request, reply) => {
|
|
446
|
+
try {
|
|
447
|
+
const tenantId = getTenantId(request);
|
|
448
|
+
const { rid } = request.params as { rid: string };
|
|
449
|
+
const deleted = await getEvalStore().deleteRun(tenantId, rid);
|
|
450
|
+
if (!deleted) return reply.status(404).send({ success: false, message: "Run not found" });
|
|
451
|
+
reply.send({ success: true, message: "Run deleted" });
|
|
452
|
+
} catch (err) {
|
|
453
|
+
reply.status(500).send({ success: false, message: (err as Error).message });
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// GET /api/eval/reports/projects/:pid — aggregated report
|
|
458
|
+
app.get("/api/eval/reports/projects/:pid", async (request, reply) => {
|
|
459
|
+
try {
|
|
460
|
+
const tenantId = getTenantId(request);
|
|
461
|
+
const { pid } = request.params as { pid: string };
|
|
462
|
+
const report = await getEvalStore().getProjectReport(tenantId, pid);
|
|
463
|
+
if (!report) return reply.status(404).send({ success: false, message: "Project not found" });
|
|
464
|
+
reply.send({ success: true, message: "Ok", data: report });
|
|
465
|
+
} catch (err) {
|
|
466
|
+
reply.status(500).send({ success: false, message: (err as Error).message });
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
}
|
|
@@ -27,6 +27,7 @@ interface ApiResponse<T = any> {
|
|
|
27
27
|
async function getDefinitionsFromAssistants(tenantId: string): Promise<Array<{
|
|
28
28
|
assistantId: string;
|
|
29
29
|
assistantName: string;
|
|
30
|
+
description?: string;
|
|
30
31
|
topologyEdges: { from: string; to: string; purpose: string }[];
|
|
31
32
|
totalEdges: number;
|
|
32
33
|
}>> {
|
|
@@ -51,6 +52,7 @@ async function getDefinitionsFromAssistants(tenantId: string): Promise<Array<{
|
|
|
51
52
|
results.push({
|
|
52
53
|
assistantId: a.id,
|
|
53
54
|
assistantName: a.name,
|
|
55
|
+
description: a.description,
|
|
54
56
|
topologyEdges: mw.config.edges,
|
|
55
57
|
totalEdges: mw.config.edges.length,
|
|
56
58
|
});
|
package/src/routes/index.ts
CHANGED
|
@@ -37,6 +37,7 @@ import { registerWorkspaceRoutes } from "../controllers/workspace";
|
|
|
37
37
|
import { registerDatabaseConfigRoutes } from "../controllers/database-configs";
|
|
38
38
|
import { registerMetricsServerConfigRoutes } from "../controllers/metrics-configs";
|
|
39
39
|
import { registerMcpServerConfigRoutes } from "../controllers/mcp-configs";
|
|
40
|
+
import { registerEvalRoutes } from "../controllers/eval";
|
|
40
41
|
import { registerUserRoutes } from "../controllers/users";
|
|
41
42
|
import { registerTenantRoutes } from "../controllers/tenants";
|
|
42
43
|
import { registerAuthRoutes } from "../controllers/auth";
|
|
@@ -320,6 +321,8 @@ export const registerLatticeRoutes = (app: FastifyInstance): void => {
|
|
|
320
321
|
|
|
321
322
|
registerSandboxProxyRoutes(app);
|
|
322
323
|
|
|
324
|
+
registerEvalRoutes(app);
|
|
325
|
+
|
|
323
326
|
registerWorkspaceRoutes(app);
|
|
324
327
|
|
|
325
328
|
registerDatabaseConfigRoutes(app);
|