@axiom-lattice/gateway 2.1.111 → 2.1.113
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 +13 -13
- package/CHANGELOG.md +20 -0
- package/dist/index.js +2118 -319
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1948 -145
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -6
- package/src/controllers/__tests__/workflow-tracking.test.ts +113 -0
- package/src/controllers/connections.ts +7 -0
- package/src/controllers/eval.ts +44 -4
- package/src/controllers/export-import.ts +166 -0
- package/src/controllers/run.ts +30 -4
- package/src/controllers/workflow-tracking.ts +105 -11
- package/src/export_registrations/a2a-api-key.registration.ts +89 -0
- package/src/export_registrations/agent.registration.ts +80 -0
- package/src/export_registrations/binding.registration.ts +106 -0
- package/src/export_registrations/channel-installation.registration.ts +88 -0
- package/src/export_registrations/collection.registration.ts +85 -0
- package/src/export_registrations/connection.registration.ts +97 -0
- package/src/export_registrations/database-config.registration.ts +94 -0
- package/src/export_registrations/eval.registration.ts +334 -0
- package/src/export_registrations/index.ts +31 -0
- package/src/export_registrations/mcp-config.registration.ts +97 -0
- package/src/export_registrations/menu.registration.ts +91 -0
- package/src/export_registrations/metrics-config.registration.ts +94 -0
- package/src/export_registrations/skill.registration.ts +88 -0
- package/src/export_registrations/task.registration.ts +99 -0
- package/src/index.ts +18 -8
- package/src/routes/index.ts +42 -1
- package/src/services/ExportImportService.ts +296 -0
- package/src/services/__tests__/ExportImportService.test.ts +130 -0
- package/src/services/eval-runner.ts +63 -55
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { ExportImportService } from '../ExportImportService';
|
|
2
|
+
import { ExportableEntityRegistry } from '@axiom-lattice/core';
|
|
3
|
+
import type { ExportableEntityDefinition, ExportableEntity } from '@axiom-lattice/core';
|
|
4
|
+
|
|
5
|
+
function makeDef(
|
|
6
|
+
entityType: string,
|
|
7
|
+
entities: ExportableEntity[] = [],
|
|
8
|
+
dependsOn: string[] = [],
|
|
9
|
+
): ExportableEntityDefinition {
|
|
10
|
+
return {
|
|
11
|
+
entityType,
|
|
12
|
+
label: entityType,
|
|
13
|
+
category: 'core',
|
|
14
|
+
dependsOn,
|
|
15
|
+
cascadeParents: [],
|
|
16
|
+
listForExport: jest.fn().mockResolvedValue(entities),
|
|
17
|
+
previewImport: jest.fn().mockResolvedValue({ conflicts: [], insertions: [], errors: [] }),
|
|
18
|
+
applyImport: jest.fn().mockResolvedValue({ newId: 'new-id' }),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('ExportImportService', () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
(ExportableEntityRegistry as any).instance = undefined;
|
|
25
|
+
const registry = ExportableEntityRegistry.getInstance();
|
|
26
|
+
const s1 = { _exportId: '', data: { id: 's1', name: 'Skill 1' } };
|
|
27
|
+
const s2 = { _exportId: '', data: { id: 's2', name: 'Skill 2' } };
|
|
28
|
+
registry.register(makeDef('skill', [s1, s2]));
|
|
29
|
+
registry.register(makeDef('agent', [], ['skill']));
|
|
30
|
+
registry.register(makeDef('binding', [], ['agent']));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('generates a valid ExportBundle', async () => {
|
|
34
|
+
const svc = new ExportImportService();
|
|
35
|
+
const { bundle } = await svc.export('tenant-1', ['skill']);
|
|
36
|
+
|
|
37
|
+
expect(bundle.version).toBe(1);
|
|
38
|
+
expect(bundle.sourceTenantId).toBe('tenant-1');
|
|
39
|
+
expect(bundle.entities.skill).toHaveLength(2);
|
|
40
|
+
expect(bundle.entities.skill[0]._exportId).toMatch(/^exp-skill-0/);
|
|
41
|
+
expect(bundle.entities.skill[0].data.id).toBe('s1');
|
|
42
|
+
expect(bundle.entities.skill[0].data).not.toHaveProperty('tenantId');
|
|
43
|
+
expect(bundle.entities.skill[0].data).not.toHaveProperty('createdAt');
|
|
44
|
+
expect(bundle.entities.skill[0].data).not.toHaveProperty('updatedAt');
|
|
45
|
+
expect(bundle.dependencyOrder).toEqual(['skill']);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('filters entities by entityIds', async () => {
|
|
49
|
+
const svc = new ExportImportService();
|
|
50
|
+
const { bundle } = await svc.export('tenant-1', ['skill'], { skill: ['s1'] });
|
|
51
|
+
expect(bundle.entities.skill).toHaveLength(1);
|
|
52
|
+
expect(bundle.entities.skill[0].data.id).toBe('s1');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('exportPreview detects missing dependencies', async () => {
|
|
56
|
+
const svc = new ExportImportService();
|
|
57
|
+
const result = await svc.exportPreview('tenant-1', ['agent']);
|
|
58
|
+
expect(result.missingDependencies).toContain('skill');
|
|
59
|
+
expect(result.cascadeAdditions).toEqual([]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('export includes transitive deps when explicitly requested', async () => {
|
|
63
|
+
const svc = new ExportImportService();
|
|
64
|
+
const { bundle } = await svc.export('tenant-1', ['skill', 'agent']);
|
|
65
|
+
expect(bundle.dependencyOrder).toContain('skill');
|
|
66
|
+
expect(bundle.dependencyOrder).toContain('agent');
|
|
67
|
+
expect(bundle.dependencyOrder.indexOf('skill')).toBeLessThan(bundle.dependencyOrder.indexOf('agent'));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('previewImport returns insertions for new entities', async () => {
|
|
71
|
+
const svc = new ExportImportService();
|
|
72
|
+
const bundle = {
|
|
73
|
+
version: 1 as const,
|
|
74
|
+
exportedAt: new Date().toISOString(),
|
|
75
|
+
sourceTenantId: 't1',
|
|
76
|
+
entities: {
|
|
77
|
+
skill: [{ _exportId: 'exp-s-1', data: { id: 's1', name: 'Skill' } }],
|
|
78
|
+
},
|
|
79
|
+
dependencyOrder: ['skill'],
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
await svc.previewImport('tenant-2', bundle);
|
|
83
|
+
const registry = ExportableEntityRegistry.getInstance();
|
|
84
|
+
expect(registry.get('skill').previewImport).toHaveBeenCalled();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('throws on invalid bundle version', async () => {
|
|
88
|
+
const svc = new ExportImportService();
|
|
89
|
+
await expect(
|
|
90
|
+
svc.previewImport('tenant-2', {
|
|
91
|
+
version: 99 as any,
|
|
92
|
+
entities: {},
|
|
93
|
+
dependencyOrder: [],
|
|
94
|
+
exportedAt: '',
|
|
95
|
+
sourceTenantId: '',
|
|
96
|
+
}),
|
|
97
|
+
).rejects.toThrow(/version/);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('throws on missing entities field', async () => {
|
|
101
|
+
const svc = new ExportImportService();
|
|
102
|
+
await expect(
|
|
103
|
+
svc.previewImport('tenant-2', {
|
|
104
|
+
version: 1,
|
|
105
|
+
exportedAt: '',
|
|
106
|
+
sourceTenantId: '',
|
|
107
|
+
dependencyOrder: [],
|
|
108
|
+
} as any),
|
|
109
|
+
).rejects.toThrow(/entities/);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('getExportJob returns undefined for unknown jobId', () => {
|
|
113
|
+
const svc = new ExportImportService();
|
|
114
|
+
expect(svc.getExportJob('nonexistent', 'tenant-1')).toBeUndefined();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('getExportJob returns bundle after export', async () => {
|
|
118
|
+
const svc = new ExportImportService();
|
|
119
|
+
const { jobId, bundle } = await svc.export('tenant-1', ['skill']);
|
|
120
|
+
const retrieved = svc.getExportJob(jobId, 'tenant-1');
|
|
121
|
+
expect(retrieved).toEqual(bundle);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('listExportableTypes returns registered types', () => {
|
|
125
|
+
const svc = new ExportImportService();
|
|
126
|
+
const types = svc.listExportableTypes();
|
|
127
|
+
expect(types).toHaveLength(3);
|
|
128
|
+
expect(types.map((t) => t.entityType)).toEqual(['skill', 'agent', 'binding']);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { EventEmitter } from "events";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
getStoreLattice,
|
|
4
|
+
modelLatticeManager,
|
|
5
|
+
LatticeEvalProject,
|
|
6
|
+
} from "@axiom-lattice/core";
|
|
4
7
|
import type { EvalStore } from "@axiom-lattice/protocols";
|
|
5
8
|
import type {
|
|
6
9
|
CaseRunResult,
|
|
@@ -8,8 +11,7 @@ import type {
|
|
|
8
11
|
LatticeEvalProjectType,
|
|
9
12
|
LatticeEvalSuiteType,
|
|
10
13
|
LatticeEvalLogEvent,
|
|
11
|
-
|
|
12
|
-
} from "@axiom-lattice/agent-eval";
|
|
14
|
+
} from "@axiom-lattice/core";
|
|
13
15
|
import { v4 as uuidv4 } from "uuid";
|
|
14
16
|
|
|
15
17
|
export interface EvalStreamEvent {
|
|
@@ -48,6 +50,12 @@ class EvalRunner {
|
|
|
48
50
|
const project = await store.getProjectById(tenantId, projectId);
|
|
49
51
|
if (!project) throw new Error("Project not found");
|
|
50
52
|
|
|
53
|
+
for (const ctx of this.runs.values()) {
|
|
54
|
+
if (ctx.projectId === projectId) {
|
|
55
|
+
throw new Error("A run is already in progress for this project");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
51
59
|
const existingRuns = await store.getRunsByTenant(tenantId, { projectId, status: "running" });
|
|
52
60
|
if (existingRuns.length > 0) {
|
|
53
61
|
throw new Error("A run is already in progress for this project");
|
|
@@ -65,8 +73,8 @@ class EvalRunner {
|
|
|
65
73
|
cases: cases.map((c) => ({
|
|
66
74
|
caseId: c.id,
|
|
67
75
|
input: { message: c.inputMessage, files: c.inputFiles },
|
|
68
|
-
steps: c.steps
|
|
69
|
-
output: { type:
|
|
76
|
+
steps: c.steps,
|
|
77
|
+
output: { type: "message_content" },
|
|
70
78
|
eval: {
|
|
71
79
|
content_assertion: c.contentAssertion,
|
|
72
80
|
eval_rubrics: c.rubrics?.map((r) => ({
|
|
@@ -79,39 +87,35 @@ class EvalRunner {
|
|
|
79
87
|
});
|
|
80
88
|
}
|
|
81
89
|
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const judgeCfg = project.judgeModelConfig as Record<string, unknown>;
|
|
87
|
-
const hasModelKey = Boolean(judgeCfg.modelKey);
|
|
88
|
-
const hasApiKey = Boolean(judgeCfg.apiKeyEnvName || judgeCfg.apiKey);
|
|
89
|
-
const hasCredentials = hasApiKey;
|
|
90
|
-
|
|
91
|
-
let judgeModelConfig: { modelKey?: string; model?: any } = {};
|
|
90
|
+
const judgeCfg = (project.judgeModelConfig ?? {}) as Record<string, unknown>;
|
|
91
|
+
const judgeModelKey = (judgeCfg.modelKey as string) || "";
|
|
92
|
+
let resolvedJudgeModelKey: string;
|
|
92
93
|
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const firstModel = modelLatticeManager.getAllLattices()[0];
|
|
97
|
-
if (firstModel) {
|
|
98
|
-
judgeModelConfig = { modelKey: firstModel.key };
|
|
99
|
-
} else {
|
|
100
|
-
judgeModelConfig = { model: judgeCfg as any };
|
|
94
|
+
if (judgeModelKey) {
|
|
95
|
+
if (!modelLatticeManager.hasLattice(judgeModelKey)) {
|
|
96
|
+
throw new Error(`Judge model "${judgeModelKey}" is not registered`);
|
|
101
97
|
}
|
|
98
|
+
resolvedJudgeModelKey = judgeModelKey;
|
|
102
99
|
} else {
|
|
103
|
-
|
|
100
|
+
const firstModel = modelLatticeManager.getAllLattices()[0];
|
|
101
|
+
if (!firstModel) {
|
|
102
|
+
throw new Error("No model registered — cannot run eval without a judge model");
|
|
103
|
+
}
|
|
104
|
+
resolvedJudgeModelKey = firstModel.key;
|
|
105
|
+
console.warn(`[eval-runner] Project "${project.name}" has no judge modelKey — falling back to first registered model "${firstModel.key}"`);
|
|
104
106
|
}
|
|
105
107
|
|
|
108
|
+
const runId = uuidv4();
|
|
109
|
+
const concurrency = Math.max(1, Math.floor((project as unknown as Record<string, unknown>).concurrency as number) || 3);
|
|
110
|
+
await store.createRun(tenantId, projectId, runId, { totalCases, concurrency });
|
|
111
|
+
|
|
106
112
|
const projectConfig: LatticeEvalProjectType = {
|
|
107
113
|
projectName: project.name,
|
|
108
114
|
version: project.version,
|
|
109
115
|
description: project.description,
|
|
110
116
|
suites: evalSuites,
|
|
111
|
-
judge_agent_config:
|
|
117
|
+
judge_agent_config: { modelKey: resolvedJudgeModelKey },
|
|
112
118
|
lattice_server_config: {
|
|
113
|
-
base_url: (project.targetServerConfig as Record<string, string>).base_url || "",
|
|
114
|
-
api_key: (project.targetServerConfig as Record<string, string>).api_key || "",
|
|
115
119
|
tenant_id: tenantId,
|
|
116
120
|
},
|
|
117
121
|
concurrency,
|
|
@@ -163,37 +167,41 @@ class EvalRunner {
|
|
|
163
167
|
const runPromise = (async () => {
|
|
164
168
|
try {
|
|
165
169
|
const evalProject = new LatticeEvalProject(projectConfig, onCaseComplete);
|
|
166
|
-
const { report } = await evalProject.runAllSuitesBatch(concurrency);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
170
|
+
const { report } = await evalProject.runAllSuitesBatch(concurrency, abortController.signal);
|
|
171
|
+
|
|
172
|
+
if (!abortController.signal.aborted) {
|
|
173
|
+
const completedCount = stats.passed + stats.failed;
|
|
174
|
+
await store.updateRunStatus(tenantId, runId, {
|
|
175
|
+
status: "completed",
|
|
176
|
+
completedAt: new Date(),
|
|
177
|
+
passedCases: stats.passed,
|
|
178
|
+
failedCases: stats.failed,
|
|
179
|
+
avgScore: completedCount > 0 ? stats.totalScore / completedCount : 0,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
this.eventEmitter.emit(`run:${runId}`, {
|
|
183
|
+
type: "completed",
|
|
184
|
+
runId,
|
|
185
|
+
data: { passed: stats.passed, failed: stats.failed, avgScore: completedCount > 0 ? stats.totalScore / completedCount : 0 },
|
|
186
|
+
} satisfies EvalStreamEvent);
|
|
187
|
+
}
|
|
182
188
|
|
|
183
189
|
return { report };
|
|
184
190
|
} catch (err) {
|
|
185
191
|
const errorMsg = (err as Error).message;
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
192
|
+
if (!abortController.signal.aborted) {
|
|
193
|
+
await store.updateRunStatus(tenantId, runId, {
|
|
194
|
+
status: "failed",
|
|
195
|
+
error: errorMsg,
|
|
196
|
+
completedAt: new Date(),
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
this.eventEmitter.emit(`run:${runId}`, {
|
|
200
|
+
type: "error",
|
|
201
|
+
runId,
|
|
202
|
+
data: { message: errorMsg },
|
|
203
|
+
} satisfies EvalStreamEvent);
|
|
204
|
+
}
|
|
197
205
|
|
|
198
206
|
throw err;
|
|
199
207
|
} finally {
|