@mongoosejs/studio 0.5.2 → 0.5.4
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/backend/actions/ChatThread/streamChatMessage.js +12 -12
- package/backend/chatAgent/agentSystemPrompt.js +22 -10
- package/backend/chatAgent/getAgentTools.js +19 -8
- package/backend/chatAgent/runChatAgent.js +17 -0
- package/backend/index.js +3 -0
- package/debugging.js +11 -0
- package/eval/agent/cases.js +168 -0
- package/eval/agent/fixtures/carsharing.js +147 -0
- package/eval/agent/fixtures/ecommerce.js +152 -0
- package/eval/agent/run.js +323 -0
- package/frontend/index.js +1 -2
- package/frontend/public/app.js +395 -23
- package/frontend/public/tw.css +52 -0
- package/frontend/src/_util/recent-pages-history.js +116 -0
- package/frontend/src/chat/chat-message-script/chat-message-script.html +1 -6
- package/frontend/src/chat/chat.js +2 -2
- package/frontend/src/document/document.html +2 -2
- package/frontend/src/index.js +188 -10
- package/frontend/src/modal/modal.css +26 -1
- package/frontend/src/modal/modal.html +2 -2
- package/frontend/src/modal/modal.js +5 -1
- package/frontend/src/navbar/navbar.html +77 -6
- package/frontend/src/navbar/navbar.js +65 -4
- package/index.js +2 -1
- package/package.json +3 -2
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const mongoose = require('mongoose');
|
|
4
|
+
|
|
5
|
+
const { Schema } = mongoose;
|
|
6
|
+
|
|
7
|
+
const userSchema = new Schema({
|
|
8
|
+
name: String,
|
|
9
|
+
email: String,
|
|
10
|
+
signupDate: Date,
|
|
11
|
+
isDeleted: { type: Boolean, default: false }
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const productSchema = new Schema({
|
|
15
|
+
name: String,
|
|
16
|
+
category: { type: String, enum: ['electronics', 'apparel', 'home', 'books'] },
|
|
17
|
+
price: Number,
|
|
18
|
+
inventory: Number,
|
|
19
|
+
createdAt: Date
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const orderSchema = new Schema({
|
|
23
|
+
userId: { type: Schema.Types.ObjectId, ref: 'User' },
|
|
24
|
+
items: [{
|
|
25
|
+
productId: { type: Schema.Types.ObjectId, ref: 'Product' },
|
|
26
|
+
quantity: Number,
|
|
27
|
+
price: Number
|
|
28
|
+
}],
|
|
29
|
+
total: Number,
|
|
30
|
+
status: { type: String, enum: ['pending', 'paid', 'shipped', 'delivered', 'cancelled'] },
|
|
31
|
+
placedAt: Date,
|
|
32
|
+
paidAt: Date
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
name: 'ecommerce',
|
|
37
|
+
defineModels(connection) {
|
|
38
|
+
return {
|
|
39
|
+
User: connection.model('User', userSchema),
|
|
40
|
+
Product: connection.model('Product', productSchema),
|
|
41
|
+
Order: connection.model('Order', orderSchema)
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
async seed({ User, Product, Order }) {
|
|
45
|
+
const users = await User.create([
|
|
46
|
+
{ name: 'Alice Smith', email: 'alice@example.com', signupDate: new Date('2025-01-15'), isDeleted: false },
|
|
47
|
+
{ name: 'Bob Jones', email: 'bob@example.com', signupDate: new Date('2025-04-22'), isDeleted: false },
|
|
48
|
+
{ name: 'Carol Wu', email: 'carol@example.com', signupDate: new Date('2025-09-08'), isDeleted: true },
|
|
49
|
+
{ name: 'David Park', email: 'david@example.com', signupDate: new Date('2025-12-03'), isDeleted: false }
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
const products = await Product.create([
|
|
53
|
+
{ name: 'Laptop', category: 'electronics', price: 1200, inventory: 12, createdAt: new Date('2024-11-01') },
|
|
54
|
+
{ name: 'Headphones', category: 'electronics', price: 150, inventory: 45, createdAt: new Date('2024-11-01') },
|
|
55
|
+
{ name: 'T-Shirt', category: 'apparel', price: 25, inventory: 200, createdAt: new Date('2025-01-15') },
|
|
56
|
+
{ name: 'Lamp', category: 'home', price: 60, inventory: 30, createdAt: new Date('2025-02-10') },
|
|
57
|
+
{ name: 'Novel', category: 'books', price: 18, inventory: 100, createdAt: new Date('2025-03-04') }
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
const monthsAgo = (n) => {
|
|
61
|
+
const d = new Date('2026-05-13T12:00:00.000Z');
|
|
62
|
+
d.setUTCMonth(d.getUTCMonth() - n);
|
|
63
|
+
return d;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
await Order.create([
|
|
67
|
+
{
|
|
68
|
+
userId: users[0]._id,
|
|
69
|
+
items: [{ productId: products[0]._id, quantity: 1, price: 1200 }],
|
|
70
|
+
total: 1200,
|
|
71
|
+
status: 'delivered',
|
|
72
|
+
placedAt: monthsAgo(5),
|
|
73
|
+
paidAt: monthsAgo(5)
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
userId: users[0]._id,
|
|
77
|
+
items: [{ productId: products[1]._id, quantity: 2, price: 150 }],
|
|
78
|
+
total: 300,
|
|
79
|
+
status: 'paid',
|
|
80
|
+
placedAt: monthsAgo(3),
|
|
81
|
+
paidAt: monthsAgo(3)
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
userId: users[1]._id,
|
|
85
|
+
items: [
|
|
86
|
+
{ productId: products[2]._id, quantity: 3, price: 25 },
|
|
87
|
+
{ productId: products[4]._id, quantity: 1, price: 18 }
|
|
88
|
+
],
|
|
89
|
+
total: 93,
|
|
90
|
+
status: 'shipped',
|
|
91
|
+
placedAt: monthsAgo(4),
|
|
92
|
+
paidAt: monthsAgo(4)
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
userId: users[1]._id,
|
|
96
|
+
items: [{ productId: products[3]._id, quantity: 1, price: 60 }],
|
|
97
|
+
total: 60,
|
|
98
|
+
status: 'paid',
|
|
99
|
+
placedAt: monthsAgo(2),
|
|
100
|
+
paidAt: monthsAgo(2)
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
userId: users[3]._id,
|
|
104
|
+
items: [{ productId: products[0]._id, quantity: 1, price: 1200 }],
|
|
105
|
+
total: 1200,
|
|
106
|
+
status: 'cancelled',
|
|
107
|
+
placedAt: monthsAgo(2),
|
|
108
|
+
paidAt: null
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
userId: users[3]._id,
|
|
112
|
+
items: [{ productId: products[1]._id, quantity: 1, price: 150 }],
|
|
113
|
+
total: 150,
|
|
114
|
+
status: 'paid',
|
|
115
|
+
placedAt: monthsAgo(1),
|
|
116
|
+
paidAt: monthsAgo(1)
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
userId: users[1]._id,
|
|
120
|
+
items: [{ productId: products[4]._id, quantity: 4, price: 18 }],
|
|
121
|
+
total: 72,
|
|
122
|
+
status: 'paid',
|
|
123
|
+
placedAt: monthsAgo(1),
|
|
124
|
+
paidAt: monthsAgo(1)
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
userId: users[3]._id,
|
|
128
|
+
items: [{ productId: products[2]._id, quantity: 2, price: 25 }],
|
|
129
|
+
total: 50,
|
|
130
|
+
status: 'pending',
|
|
131
|
+
placedAt: monthsAgo(0),
|
|
132
|
+
paidAt: null
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
userId: users[0]._id,
|
|
136
|
+
items: [{ productId: products[3]._id, quantity: 2, price: 60 }],
|
|
137
|
+
total: 120,
|
|
138
|
+
status: 'cancelled',
|
|
139
|
+
placedAt: monthsAgo(6),
|
|
140
|
+
paidAt: null
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
userId: users[1]._id,
|
|
144
|
+
items: [{ productId: products[0]._id, quantity: 1, price: 1200 }],
|
|
145
|
+
total: 1200,
|
|
146
|
+
status: 'delivered',
|
|
147
|
+
placedAt: monthsAgo(4),
|
|
148
|
+
paidAt: monthsAgo(4)
|
|
149
|
+
}
|
|
150
|
+
]);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const mongoose = require('mongoose');
|
|
7
|
+
const { MongoMemoryReplSet } = require('mongodb-memory-server');
|
|
8
|
+
|
|
9
|
+
const studio = require('../..');
|
|
10
|
+
const runChatAgent = require('../../backend/chatAgent/runChatAgent');
|
|
11
|
+
const createSandbox = require('../../backend/sandbox/createSandbox');
|
|
12
|
+
const allCases = require('./cases');
|
|
13
|
+
const fixtures = {
|
|
14
|
+
ecommerce: require('./fixtures/ecommerce'),
|
|
15
|
+
carsharing: require('./fixtures/carsharing')
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const args = parseArgs(process.argv.slice(2));
|
|
19
|
+
|
|
20
|
+
studio.enableDebugging();
|
|
21
|
+
|
|
22
|
+
main().catch(err => {
|
|
23
|
+
console.error(err);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
async function main() {
|
|
28
|
+
if (args.help) {
|
|
29
|
+
printHelp();
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const llmOptions = buildLlmOptionsFromEnv();
|
|
34
|
+
if (!llmOptions) {
|
|
35
|
+
console.error('No LLM API key found. Set one of:');
|
|
36
|
+
console.error(' ANTHROPIC_API_KEY');
|
|
37
|
+
console.error(' OPENAI_API_KEY');
|
|
38
|
+
console.error(' GOOGLE_GEMINI_API_KEY');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
if (args.model) {
|
|
42
|
+
llmOptions.model = args.model;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const selected = filterCases(allCases, args);
|
|
46
|
+
if (selected.length === 0) {
|
|
47
|
+
console.error('No cases matched the filter.');
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
const runs = args.runs ?? 1;
|
|
51
|
+
|
|
52
|
+
let replSet = null;
|
|
53
|
+
let uri = process.env.MONGODB_URI;
|
|
54
|
+
if (!uri) {
|
|
55
|
+
console.log('Starting in-memory MongoDB replSet...');
|
|
56
|
+
replSet = await MongoMemoryReplSet.create({
|
|
57
|
+
replSet: { name: 'rs0', count: 1 },
|
|
58
|
+
binary: { version: process.env.MONGOMS_VERSION || '8.0.0' }
|
|
59
|
+
});
|
|
60
|
+
uri = replSet.getUri();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const runId = new Date().toISOString().replace(/[:.]/g, '-');
|
|
64
|
+
const traceDir = args.noTraces ? null : path.join(__dirname, 'traces', runId);
|
|
65
|
+
if (traceDir) {
|
|
66
|
+
fs.mkdirSync(traceDir, { recursive: true });
|
|
67
|
+
console.log(`Writing traces to ${traceDir}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log(`Running ${selected.length} case(s) × ${runs} run(s) using provider "${llmOptions.providerName}" (${llmOptions.model || 'default model'})\n`);
|
|
71
|
+
|
|
72
|
+
const results = [];
|
|
73
|
+
for (const caseDef of selected) {
|
|
74
|
+
for (let i = 0; i < runs; i++) {
|
|
75
|
+
const result = await runCase(uri, caseDef, llmOptions);
|
|
76
|
+
result.runIndex = i;
|
|
77
|
+
results.push(result);
|
|
78
|
+
if (traceDir) {
|
|
79
|
+
const filename = `${caseDef.id}${runs > 1 ? `__run${i}` : ''}.json`;
|
|
80
|
+
fs.writeFileSync(path.join(traceDir, filename), JSON.stringify(serializeResult(result), null, 2));
|
|
81
|
+
}
|
|
82
|
+
printCaseLine(result);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
printSummary(results);
|
|
87
|
+
|
|
88
|
+
if (replSet) {
|
|
89
|
+
await replSet.stop();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const overallPass = results.filter(r => r.passed).length / results.length;
|
|
93
|
+
process.exit(overallPass >= (args.threshold ?? 0.8) ? 0 : 1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function runCase(uri, caseDef, llmOptions) {
|
|
97
|
+
const fixture = fixtures[caseDef.fixture];
|
|
98
|
+
if (!fixture) {
|
|
99
|
+
return {
|
|
100
|
+
caseDef,
|
|
101
|
+
trace: emptyTrace(caseDef),
|
|
102
|
+
assertionError: new Error(`unknown fixture "${caseDef.fixture}"`),
|
|
103
|
+
passed: false
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const dbName = `eval_${caseDef.id.replace(/[^a-z0-9]/gi, '_')}_${Date.now()}`;
|
|
108
|
+
const conn = mongoose.createConnection();
|
|
109
|
+
await conn.openUri(uri, { dbName });
|
|
110
|
+
|
|
111
|
+
let trace = emptyTrace(caseDef);
|
|
112
|
+
trace.model = llmOptions.model || null;
|
|
113
|
+
trace.provider = llmOptions.providerName;
|
|
114
|
+
|
|
115
|
+
const started = Date.now();
|
|
116
|
+
try {
|
|
117
|
+
const models = fixture.defineModels(conn);
|
|
118
|
+
await fixture.seed(models);
|
|
119
|
+
|
|
120
|
+
const stream = runChatAgent({
|
|
121
|
+
db: conn,
|
|
122
|
+
llmMessages: [{ role: 'user', content: [{ type: 'text', text: caseDef.prompt }] }],
|
|
123
|
+
currentDateTime: caseDef.currentDateTime ?? '2026-05-13T12:00:00',
|
|
124
|
+
options: llmOptions
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
for await (const event of stream) {
|
|
128
|
+
if (typeof event === 'string') {
|
|
129
|
+
trace.text += event;
|
|
130
|
+
} else if (event.toolCall) {
|
|
131
|
+
trace.toolCalls.push({ toolName: event.toolCall.toolName, input: event.toolCall.input });
|
|
132
|
+
} else if (event.toolResult) {
|
|
133
|
+
trace.toolResults.push({ toolName: event.toolResult.toolName, output: summarizeToolOutput(event.toolResult.output) });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} catch (err) {
|
|
137
|
+
trace.error = err?.message || String(err);
|
|
138
|
+
}
|
|
139
|
+
trace.durationMs = Date.now() - started;
|
|
140
|
+
trace.finalCode = extractFinalCode(trace.text);
|
|
141
|
+
|
|
142
|
+
await executeFinalCode(conn, trace);
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
await conn.dropDatabase();
|
|
146
|
+
} catch (_) { /* ignore */ }
|
|
147
|
+
await conn.close();
|
|
148
|
+
|
|
149
|
+
let assertionError = null;
|
|
150
|
+
try {
|
|
151
|
+
caseDef.assert(trace);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
assertionError = err;
|
|
154
|
+
}
|
|
155
|
+
const passed = !trace.error && !assertionError;
|
|
156
|
+
|
|
157
|
+
return { caseDef, trace, assertionError, passed };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function executeFinalCode(conn, trace) {
|
|
161
|
+
if (!trace.finalCode) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const sandbox = createSandbox({ db: conn });
|
|
165
|
+
try {
|
|
166
|
+
trace.output = await sandbox.runScript({ script: trace.finalCode });
|
|
167
|
+
trace.logs = sandbox.getLogs();
|
|
168
|
+
} catch (err) {
|
|
169
|
+
trace.executionError = err?.message || String(err);
|
|
170
|
+
} finally {
|
|
171
|
+
try { await sandbox.close(); } catch (_) { /* ignore */ }
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function emptyTrace(caseDef) {
|
|
176
|
+
return {
|
|
177
|
+
caseId: caseDef.id,
|
|
178
|
+
category: caseDef.category,
|
|
179
|
+
fixture: caseDef.fixture,
|
|
180
|
+
prompt: caseDef.prompt,
|
|
181
|
+
text: '',
|
|
182
|
+
toolCalls: [],
|
|
183
|
+
toolResults: [],
|
|
184
|
+
finalCode: null,
|
|
185
|
+
output: undefined,
|
|
186
|
+
logs: null,
|
|
187
|
+
executionError: null,
|
|
188
|
+
error: null,
|
|
189
|
+
durationMs: 0
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function extractFinalCode(text) {
|
|
194
|
+
if (!text) return null;
|
|
195
|
+
const matches = [...text.matchAll(/```(?:javascript|js)?\s*\n([\s\S]*?)```/g)];
|
|
196
|
+
if (matches.length === 0) return null;
|
|
197
|
+
return matches[matches.length - 1][1].trim();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function summarizeToolOutput(output) {
|
|
201
|
+
if (output == null) return null;
|
|
202
|
+
if (typeof output !== 'object') return output;
|
|
203
|
+
const summary = {};
|
|
204
|
+
for (const [k, v] of Object.entries(output)) {
|
|
205
|
+
if (Array.isArray(v)) {
|
|
206
|
+
summary[k] = { __array: true, length: v.length, sample: v.slice(0, 2) };
|
|
207
|
+
} else if (v && typeof v === 'object') {
|
|
208
|
+
const str = safeStringify(v);
|
|
209
|
+
summary[k] = str.length > 500 ? str.slice(0, 500) + '...[truncated]' : v;
|
|
210
|
+
} else {
|
|
211
|
+
summary[k] = v;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return summary;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function safeStringify(v) {
|
|
218
|
+
try { return JSON.stringify(v); } catch (_) { return String(v); }
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function serializeResult(result) {
|
|
222
|
+
return {
|
|
223
|
+
caseId: result.caseDef.id,
|
|
224
|
+
category: result.caseDef.category,
|
|
225
|
+
fixture: result.caseDef.fixture,
|
|
226
|
+
prompt: result.caseDef.prompt,
|
|
227
|
+
runIndex: result.runIndex,
|
|
228
|
+
passed: result.passed,
|
|
229
|
+
assertionError: result.assertionError
|
|
230
|
+
? { message: result.assertionError.message, stack: result.assertionError.stack }
|
|
231
|
+
: null,
|
|
232
|
+
trace: result.trace
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function buildLlmOptionsFromEnv() {
|
|
237
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
238
|
+
return { anthropicAPIKey: process.env.ANTHROPIC_API_KEY, providerName: 'anthropic' };
|
|
239
|
+
}
|
|
240
|
+
if (process.env.OPENAI_API_KEY) {
|
|
241
|
+
return { openAIAPIKey: process.env.OPENAI_API_KEY, providerName: 'openai' };
|
|
242
|
+
}
|
|
243
|
+
if (process.env.GOOGLE_GEMINI_API_KEY) {
|
|
244
|
+
return { googleGeminiAPIKey: process.env.GOOGLE_GEMINI_API_KEY, providerName: 'google' };
|
|
245
|
+
}
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function filterCases(cases, args) {
|
|
250
|
+
return cases.filter(c => {
|
|
251
|
+
if (args.id && c.id !== args.id) return false;
|
|
252
|
+
if (args.category && c.category !== args.category) return false;
|
|
253
|
+
return true;
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function printCaseLine(result) {
|
|
258
|
+
const status = result.passed ? 'PASS' : 'FAIL';
|
|
259
|
+
const symbol = result.passed ? '✓' : '✗';
|
|
260
|
+
const dur = `${result.trace.durationMs}ms`;
|
|
261
|
+
const tools = `${result.trace.toolCalls.length} tool calls`;
|
|
262
|
+
console.log(` ${symbol} [${status}] ${result.caseDef.category}/${result.caseDef.id} — ${dur}, ${tools}`);
|
|
263
|
+
if (!result.passed) {
|
|
264
|
+
if (result.trace.error) {
|
|
265
|
+
console.log(` error: ${result.trace.error}`);
|
|
266
|
+
}
|
|
267
|
+
if (result.assertionError) {
|
|
268
|
+
console.log(` assertion: ${result.assertionError.message}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function printSummary(results) {
|
|
274
|
+
const byCategory = {};
|
|
275
|
+
for (const r of results) {
|
|
276
|
+
const cat = r.caseDef.category;
|
|
277
|
+
byCategory[cat] = byCategory[cat] || { passed: 0, total: 0 };
|
|
278
|
+
byCategory[cat].total += 1;
|
|
279
|
+
if (r.passed) byCategory[cat].passed += 1;
|
|
280
|
+
}
|
|
281
|
+
console.log('\n=== Summary ===');
|
|
282
|
+
for (const [cat, { passed, total }] of Object.entries(byCategory)) {
|
|
283
|
+
const pct = ((passed / total) * 100).toFixed(0);
|
|
284
|
+
console.log(` ${cat.padEnd(12)} ${passed}/${total} (${pct}%)`);
|
|
285
|
+
}
|
|
286
|
+
const totalPassed = results.filter(r => r.passed).length;
|
|
287
|
+
const pct = ((totalPassed / results.length) * 100).toFixed(0);
|
|
288
|
+
console.log(` ${'overall'.padEnd(12)} ${totalPassed}/${results.length} (${pct}%)`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function parseArgs(argv) {
|
|
292
|
+
const out = {};
|
|
293
|
+
for (const arg of argv) {
|
|
294
|
+
if (arg === '--help' || arg === '-h') { out.help = true; continue; }
|
|
295
|
+
if (arg === '--no-traces') { out.noTraces = true; continue; }
|
|
296
|
+
const m = arg.match(/^--([a-zA-Z-]+)=(.*)$/);
|
|
297
|
+
if (m) {
|
|
298
|
+
const key = m[1].replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
299
|
+
const value = m[2];
|
|
300
|
+
out[key] = /^\d+$/.test(value) ? Number(value) : value;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return out;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function printHelp() {
|
|
307
|
+
console.log(`Usage: node eval/agent/run.js [options]
|
|
308
|
+
|
|
309
|
+
Options:
|
|
310
|
+
--id=<case-id> Run only the case with this id
|
|
311
|
+
--category=<name> Run only cases in this category (simple|nested|dashboard|ambiguous)
|
|
312
|
+
--runs=<n> Run each case N times (default 1)
|
|
313
|
+
--model=<id> Override LLM model (e.g. claude-haiku-4-5-20251001)
|
|
314
|
+
--threshold=<0..1> Exit non-zero if overall pass rate is below this (default 0.8)
|
|
315
|
+
--no-traces Don't write trace JSON files to disk
|
|
316
|
+
--help Show this help
|
|
317
|
+
|
|
318
|
+
Environment:
|
|
319
|
+
ANTHROPIC_API_KEY | OPENAI_API_KEY | GOOGLE_GEMINI_API_KEY (set exactly one)
|
|
320
|
+
MONGODB_URI Use an existing MongoDB instead of the in-memory replSet
|
|
321
|
+
MONGOMS_VERSION MongoDB version for the in-memory replSet
|
|
322
|
+
`);
|
|
323
|
+
}
|
package/frontend/index.js
CHANGED
|
@@ -35,8 +35,7 @@ module.exports = async function frontend(apiUrl, isLambda, options, workspace) {
|
|
|
35
35
|
baseURL: apiUrl,
|
|
36
36
|
isLambda,
|
|
37
37
|
mothershipUrl: apiKey ? mothershipUrl : null,
|
|
38
|
-
workspace: workspaceData
|
|
39
|
-
hasLLMAPIKey: [options?.anthropicAPIKey, options?.googleGeminiAPIKey, options?.openAIAPIKey].some(v => v != null)
|
|
38
|
+
workspace: workspaceData
|
|
40
39
|
};
|
|
41
40
|
|
|
42
41
|
if (isLambda) {
|