@duckcodeailabs/dql-cli 1.6.31 → 1.6.33
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/dist/apps-api.d.ts.map +1 -1
- package/dist/apps-api.js +3 -1
- package/dist/apps-api.js.map +1 -1
- package/dist/args.d.ts +18 -0
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js +37 -0
- package/dist/args.js.map +1 -1
- package/dist/assets/dql-notebook/assets/index-B6x1cRJo.js +5698 -0
- package/dist/assets/dql-notebook/index.html +1 -1
- package/dist/commands/agent.d.ts +47 -1
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +635 -27
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/app.d.ts.map +1 -1
- package/dist/commands/app.js +3 -1
- package/dist/commands/app.js.map +1 -1
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +111 -22
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/eval-judge.d.ts +37 -0
- package/dist/commands/eval-judge.d.ts.map +1 -0
- package/dist/commands/eval-judge.js +99 -0
- package/dist/commands/eval-judge.js.map +1 -0
- package/dist/commands/eval.d.ts +27 -1
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +143 -6
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/mcp.js +27 -3
- package/dist/commands/mcp.js.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/analytics-tools.d.ts.map +1 -1
- package/dist/llm/analytics-tools.js +12 -7
- package/dist/llm/analytics-tools.js.map +1 -1
- package/dist/llm/answer-loop-tools.d.ts +7 -0
- package/dist/llm/answer-loop-tools.d.ts.map +1 -0
- package/dist/llm/answer-loop-tools.js +67 -0
- package/dist/llm/answer-loop-tools.js.map +1 -0
- package/dist/llm/proposal-metadata.d.ts +5 -0
- package/dist/llm/proposal-metadata.d.ts.map +1 -0
- package/dist/llm/proposal-metadata.js +27 -0
- package/dist/llm/proposal-metadata.js.map +1 -0
- package/dist/llm/providers/claude-agent-sdk.d.ts +6 -0
- package/dist/llm/providers/claude-agent-sdk.d.ts.map +1 -1
- package/dist/llm/providers/claude-agent-sdk.js +12 -2
- package/dist/llm/providers/claude-agent-sdk.js.map +1 -1
- package/dist/llm/providers/claude-code.d.ts +7 -1
- package/dist/llm/providers/claude-code.d.ts.map +1 -1
- package/dist/llm/providers/claude-code.js +11 -2
- package/dist/llm/providers/claude-code.js.map +1 -1
- package/dist/llm/providers/dql-agent-provider.d.ts +29 -0
- package/dist/llm/providers/dql-agent-provider.d.ts.map +1 -1
- package/dist/llm/providers/dql-agent-provider.js +757 -26
- package/dist/llm/providers/dql-agent-provider.js.map +1 -1
- package/dist/llm/tools.d.ts.map +1 -1
- package/dist/llm/tools.js +34 -137
- package/dist/llm/tools.js.map +1 -1
- package/dist/llm/types.d.ts +67 -2
- package/dist/llm/types.d.ts.map +1 -1
- package/dist/local-runtime.d.ts +160 -5
- package/dist/local-runtime.d.ts.map +1 -1
- package/dist/local-runtime.js +1759 -187
- package/dist/local-runtime.js.map +1 -1
- package/dist/package.json +10 -10
- package/dist/providers/oauth/claude-oauth.d.ts +3 -3
- package/dist/providers/oauth/claude-oauth.d.ts.map +1 -1
- package/dist/providers/oauth/claude-oauth.js +3 -3
- package/dist/providers/oauth/claude-oauth.js.map +1 -1
- package/dist/providers/oauth/codex-oauth.d.ts +2 -3
- package/dist/providers/oauth/codex-oauth.d.ts.map +1 -1
- package/dist/providers/oauth/codex-oauth.js +6 -3
- package/dist/providers/oauth/codex-oauth.js.map +1 -1
- package/dist/settings/provider-settings.d.ts +1 -0
- package/dist/settings/provider-settings.d.ts.map +1 -1
- package/dist/settings/provider-settings.js +1 -1
- package/dist/settings/provider-settings.js.map +1 -1
- package/package.json +11 -11
- package/dist/assets/dql-notebook/assets/index-BpPs6_rz.js +0 -5648
package/dist/commands/agent.js
CHANGED
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
* [--user alice@acme.com] (filters Skills + records feedback as this user)
|
|
7
7
|
* [--domain growth] (scopes KG search)
|
|
8
8
|
* [--format json] (emits structured JSON instead of prose)
|
|
9
|
+
* [--thread <id>] (continue a persisted conversation thread: the
|
|
10
|
+
* question runs through the runtime's agent-run
|
|
11
|
+
* engine, which injects prior turns and records
|
|
12
|
+
* this one server-side)
|
|
13
|
+
*
|
|
14
|
+
* dql agent threads
|
|
15
|
+
* Lists persisted conversation threads (id, updated, title) from the runtime.
|
|
9
16
|
*
|
|
10
17
|
* dql agent reindex [path]
|
|
11
18
|
* Rebuilds .dql/cache/agent-kg.sqlite and metadata.sqlite from the
|
|
@@ -17,9 +24,11 @@
|
|
|
17
24
|
import { existsSync, readFileSync } from 'node:fs';
|
|
18
25
|
import { join, resolve } from 'node:path';
|
|
19
26
|
import { load as loadYaml } from 'js-yaml';
|
|
20
|
-
import { KGStore, MemoryStore, defaultKgPath, defaultMemoryPath, reindexProject, loadSkills, pickProvider, answer, buildLocalContextPack, deriveGeneratedDraftSlug, recordQueryRun, upsertGeneratedDraft, validateSqlAgainstLocalContext, } from '@duckcodeailabs/dql-agent';
|
|
27
|
+
import { KGStore, MemoryStore, defaultKgPath, defaultMemoryPath, reindexProject, loadSkills, pickProvider, answer, buildAnalysisQuestionPlan, buildLocalContextPack, coerceReasoningEffort, contextRetrievalBudgetForQuestion, deriveGeneratedDraftSlug, loadAgentSemanticLayer, recordQueryRun, recordRuntimeSchemaSnapshot, upsertGeneratedDqlArtifactDraft, upsertGeneratedDraft, validateSqlAgainstLocalContext, } from '@duckcodeailabs/dql-agent';
|
|
21
28
|
import { buildManifest, resolveDbtManifestPath } from '@duckcodeailabs/dql-core';
|
|
22
29
|
import { findProjectRoot } from '../local-runtime.js';
|
|
30
|
+
import { buildAnswerLoopTools, createGroundingContextExpander } from '../llm/answer-loop-tools.js';
|
|
31
|
+
import { judgeAnswer } from './eval-judge.js';
|
|
23
32
|
import { startProjectRuntime } from './notebook.js';
|
|
24
33
|
/**
|
|
25
34
|
* Resolve the runtime the agent posts certified blocks / generated SQL to.
|
|
@@ -46,6 +55,91 @@ async function resolveAgentRuntime(projectRoot, flags) {
|
|
|
46
55
|
const handle = await startProjectRuntime(projectRoot, { preferredPort: 0 });
|
|
47
56
|
return { runtimeBase: handle.url, close: handle.close };
|
|
48
57
|
}
|
|
58
|
+
async function fetchRuntimeSchemaContext(runtimeBase) {
|
|
59
|
+
try {
|
|
60
|
+
const response = await fetch(`${runtimeBase.replace(/\/$/, '')}/api/schema`);
|
|
61
|
+
if (!response.ok)
|
|
62
|
+
return [];
|
|
63
|
+
const raw = await response.json();
|
|
64
|
+
if (!Array.isArray(raw))
|
|
65
|
+
return [];
|
|
66
|
+
return raw
|
|
67
|
+
.map(normalizeRuntimeSchemaTable)
|
|
68
|
+
.filter((table) => Boolean(table))
|
|
69
|
+
.slice(0, 500);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function normalizeRuntimeSchemaTable(raw) {
|
|
76
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
77
|
+
return undefined;
|
|
78
|
+
const table = raw;
|
|
79
|
+
const relation = cleanRuntimeSchemaString(table.path) ?? cleanRuntimeSchemaString(table.name);
|
|
80
|
+
if (!relation)
|
|
81
|
+
return undefined;
|
|
82
|
+
const columns = Array.isArray(table.columns)
|
|
83
|
+
? table.columns
|
|
84
|
+
.map(normalizeRuntimeSchemaColumn)
|
|
85
|
+
.filter((column) => Boolean(column))
|
|
86
|
+
.slice(0, 120)
|
|
87
|
+
: [];
|
|
88
|
+
return {
|
|
89
|
+
relation,
|
|
90
|
+
name: cleanRuntimeSchemaString(table.name) ?? relation.split('.').pop() ?? relation,
|
|
91
|
+
source: cleanRuntimeSchemaString(table.source) ?? 'runtime schema',
|
|
92
|
+
columns,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function normalizeRuntimeSchemaColumn(raw) {
|
|
96
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
97
|
+
return undefined;
|
|
98
|
+
const column = raw;
|
|
99
|
+
const name = cleanRuntimeSchemaString(column.name);
|
|
100
|
+
if (!name)
|
|
101
|
+
return undefined;
|
|
102
|
+
return {
|
|
103
|
+
name,
|
|
104
|
+
type: cleanRuntimeSchemaString(column.type),
|
|
105
|
+
description: cleanRuntimeSchemaString(column.description),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function recordCliRuntimeSchemaSnapshot(projectRoot, schemaContext, source) {
|
|
109
|
+
if (schemaContext.length === 0)
|
|
110
|
+
return;
|
|
111
|
+
try {
|
|
112
|
+
recordRuntimeSchemaSnapshot(projectRoot, {
|
|
113
|
+
source,
|
|
114
|
+
tables: schemaContext.map((table) => ({
|
|
115
|
+
relation: table.relation,
|
|
116
|
+
schema: table.schema,
|
|
117
|
+
name: table.name,
|
|
118
|
+
description: table.description,
|
|
119
|
+
source: table.source,
|
|
120
|
+
columns: table.columns.map((column) => ({
|
|
121
|
+
name: column.name,
|
|
122
|
+
type: column.type,
|
|
123
|
+
description: column.description,
|
|
124
|
+
sampleValues: column.sampleValues?.slice(0, 8),
|
|
125
|
+
})),
|
|
126
|
+
})),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// Runtime schema snapshots are advisory local metadata and must not block answers.
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function cleanRuntimeSchemaString(value) {
|
|
134
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
|
|
135
|
+
}
|
|
136
|
+
function cliReasoningEffort(flags) {
|
|
137
|
+
return coerceReasoningEffort(flags.reasoningEffort);
|
|
138
|
+
}
|
|
139
|
+
function cliAnalysisDepth(flags) {
|
|
140
|
+
const value = flags.analysisDepth?.trim().toLowerCase();
|
|
141
|
+
return value === 'quick' || value === 'deep' ? value : undefined;
|
|
142
|
+
}
|
|
49
143
|
/** A DQL runtime answers `/api/connections` with a connector/connection payload. */
|
|
50
144
|
async function isDqlRuntime(base) {
|
|
51
145
|
try {
|
|
@@ -63,6 +157,8 @@ export async function runAgent(sub, rest, flags) {
|
|
|
63
157
|
switch (sub) {
|
|
64
158
|
case 'ask':
|
|
65
159
|
return runAsk(rest, flags);
|
|
160
|
+
case 'threads':
|
|
161
|
+
return runThreads(flags);
|
|
66
162
|
case 'reindex':
|
|
67
163
|
return runReindex(rest, flags);
|
|
68
164
|
case 'feedback':
|
|
@@ -70,8 +166,9 @@ export async function runAgent(sub, rest, flags) {
|
|
|
70
166
|
case 'eval':
|
|
71
167
|
return runEval(rest, flags);
|
|
72
168
|
default:
|
|
73
|
-
throw new Error('Usage: dql agent <ask|reindex|feedback|eval> [args]\n' +
|
|
74
|
-
' dql agent ask "<question>" [--provider claude|openai|gemini|ollama] [--user <id>] [--domain <d>]\n' +
|
|
169
|
+
throw new Error('Usage: dql agent <ask|threads|reindex|feedback|eval> [args]\n' +
|
|
170
|
+
' dql agent ask "<question>" [--provider claude|openai|gemini|ollama] [--user <id>] [--domain <d>] [--thread <id>]\n' +
|
|
171
|
+
' dql agent threads [--runtime-url <url>]\n' +
|
|
75
172
|
' dql agent reindex [path]\n' +
|
|
76
173
|
' dql agent feedback up|down --block <id> --question "..."\n' +
|
|
77
174
|
' dql agent eval agent-evals.yml [--provider claude|openai|gemini|ollama] [--execute] [--save]');
|
|
@@ -81,6 +178,12 @@ async function runAsk(rest, flags) {
|
|
|
81
178
|
const question = rest.join(' ').trim();
|
|
82
179
|
if (!question)
|
|
83
180
|
throw new Error('Usage: dql agent ask "<question>"');
|
|
181
|
+
// Thread-scoped ask: hand the question to the runtime's agent-run engine with
|
|
182
|
+
// the thread id, so the SERVER injects prior turns and persists this run as a
|
|
183
|
+
// new turn (the same conversation store the notebook UI uses).
|
|
184
|
+
const threadId = flags.thread;
|
|
185
|
+
if (threadId)
|
|
186
|
+
return runThreadAsk(question, threadId, flags);
|
|
84
187
|
const projectRoot = findProjectRoot(process.cwd());
|
|
85
188
|
const kgPath = defaultKgPath(projectRoot);
|
|
86
189
|
await reindexProject(projectRoot, { kgPath });
|
|
@@ -88,6 +191,8 @@ async function runAsk(rest, flags) {
|
|
|
88
191
|
const userId = flags.user;
|
|
89
192
|
const domain = flags.domain;
|
|
90
193
|
const format = flags.format;
|
|
194
|
+
const reasoningEffort = cliReasoningEffort(flags);
|
|
195
|
+
const requestedDepth = cliAnalysisDepth(flags);
|
|
91
196
|
const provider = await pickProvider(providerName);
|
|
92
197
|
const kg = new KGStore(kgPath);
|
|
93
198
|
const memory = new MemoryStore(defaultMemoryPath(projectRoot));
|
|
@@ -99,10 +204,31 @@ async function runAsk(rest, flags) {
|
|
|
99
204
|
scopes: ['project', 'user', 'artifact'],
|
|
100
205
|
limit: 6,
|
|
101
206
|
});
|
|
102
|
-
const
|
|
207
|
+
const semanticLayer = loadAgentSemanticLayer(projectRoot);
|
|
208
|
+
const questionPlan = buildAnalysisQuestionPlan(question);
|
|
209
|
+
const contextBudget = contextRetrievalBudgetForQuestion({
|
|
210
|
+
questionPlan,
|
|
211
|
+
requestedDepth,
|
|
212
|
+
reasoningEffort,
|
|
213
|
+
});
|
|
103
214
|
const manifest = buildManifest({ projectRoot, dbtManifestPath: resolveDbtManifestPath(projectRoot) ?? undefined });
|
|
104
215
|
const { runtimeBase, close } = await resolveAgentRuntime(projectRoot, flags);
|
|
105
216
|
closeRuntime = close;
|
|
217
|
+
const schemaContext = await fetchRuntimeSchemaContext(runtimeBase);
|
|
218
|
+
recordCliRuntimeSchemaSnapshot(projectRoot, schemaContext, 'direct CLI runtime schema');
|
|
219
|
+
const contextPack = await buildLocalContextPack(projectRoot, {
|
|
220
|
+
question,
|
|
221
|
+
surface: 'cli',
|
|
222
|
+
strictness: contextBudget.strictness,
|
|
223
|
+
limit: contextBudget.limit,
|
|
224
|
+
runtimeSchemaSnapshot: schemaContext.length > 0
|
|
225
|
+
? {
|
|
226
|
+
source: 'direct CLI runtime schema',
|
|
227
|
+
tables: schemaContext,
|
|
228
|
+
}
|
|
229
|
+
: undefined,
|
|
230
|
+
}).catch(() => undefined);
|
|
231
|
+
const answerLoopTools = buildAnswerLoopTools(projectRoot, { serverUrl: runtimeBase });
|
|
106
232
|
const result = await answer({
|
|
107
233
|
question,
|
|
108
234
|
provider,
|
|
@@ -111,7 +237,13 @@ async function runAsk(rest, flags) {
|
|
|
111
237
|
userId,
|
|
112
238
|
domain,
|
|
113
239
|
memoryContext,
|
|
240
|
+
semanticLayer,
|
|
241
|
+
schemaContext,
|
|
114
242
|
contextPack,
|
|
243
|
+
reasoningEffort,
|
|
244
|
+
analysisDepth: contextBudget.analysisDepth,
|
|
245
|
+
expandGroundingContext: createGroundingContextExpander(projectRoot),
|
|
246
|
+
answerLoopTools,
|
|
115
247
|
executeCertifiedBlock: async (node) => {
|
|
116
248
|
const block = manifest.blocks[node.name] ?? manifest.blocks[node.nodeId.replace(/^block:/, '')];
|
|
117
249
|
if (!block)
|
|
@@ -187,20 +319,39 @@ async function runAsk(rest, flags) {
|
|
|
187
319
|
});
|
|
188
320
|
return result;
|
|
189
321
|
},
|
|
190
|
-
captureGeneratedDraft: ({ question: draftQuestion, sql, intent, followUp, contextPack, sourceBlock, validationWarnings }) => {
|
|
322
|
+
captureGeneratedDraft: ({ question: draftQuestion, sql, intent, followUp, contextPack, sourceBlock, sourceDqlArtifact, dqlArtifact, proposedEntity, requestedFilters, requestedDimensions, validationWarnings, outputs }) => {
|
|
191
323
|
const slug = deriveGeneratedDraftSlug(draftQuestion);
|
|
192
324
|
const proposedDomain = sourceBlock?.domain ?? contextPack?.objects.find((object) => object.domain)?.domain ?? domain ?? 'misc';
|
|
325
|
+
if (dqlArtifact?.kind === 'semantic_block') {
|
|
326
|
+
return upsertGeneratedDqlArtifactDraft(projectRoot, {
|
|
327
|
+
slug,
|
|
328
|
+
question: draftQuestion,
|
|
329
|
+
proposedContractId: `${proposedDomain}.Unknown.${slug}`,
|
|
330
|
+
proposedDomain,
|
|
331
|
+
dqlArtifact,
|
|
332
|
+
sourceQuestion: followUp?.sourceQuestion,
|
|
333
|
+
sourceBlock: followUp?.sourceBlockName ?? sourceBlock?.name,
|
|
334
|
+
followupKind: followUp?.kind,
|
|
335
|
+
outputs,
|
|
336
|
+
contextPackId: contextPack?.id,
|
|
337
|
+
routeIntent: String(intent),
|
|
338
|
+
validationWarnings,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
193
341
|
return upsertGeneratedDraft(projectRoot, {
|
|
194
342
|
slug,
|
|
195
343
|
question: draftQuestion,
|
|
196
344
|
proposedSql: sql,
|
|
197
345
|
proposedContractId: `${proposedDomain}.Unknown.${slug}`,
|
|
198
346
|
proposedDomain,
|
|
347
|
+
proposedEntity,
|
|
348
|
+
sourceDqlArtifact,
|
|
199
349
|
sourceQuestion: followUp?.sourceQuestion,
|
|
200
350
|
sourceBlock: followUp?.sourceBlockName ?? sourceBlock?.name,
|
|
201
351
|
followupKind: followUp?.kind,
|
|
202
|
-
requestedFilters
|
|
203
|
-
requestedDimensions
|
|
352
|
+
requestedFilters,
|
|
353
|
+
requestedDimensions,
|
|
354
|
+
outputs,
|
|
204
355
|
contextPackId: contextPack?.id,
|
|
205
356
|
routeIntent: String(intent),
|
|
206
357
|
validationWarnings,
|
|
@@ -224,15 +375,7 @@ async function runAsk(rest, flags) {
|
|
|
224
375
|
console.log(`\nRows: ${result.result.rowCount}`);
|
|
225
376
|
console.log(JSON.stringify(result.result.rows.slice(0, 5), null, 2));
|
|
226
377
|
}
|
|
227
|
-
|
|
228
|
-
console.log(`\n--- Proposed SQL (review before saving as a block) ---\n${result.proposedSql}`);
|
|
229
|
-
if (result.suggestedViz)
|
|
230
|
-
console.log(`Viz: ${result.suggestedViz}`);
|
|
231
|
-
if (result.draftBlock?.path)
|
|
232
|
-
console.log(`Draft: ${result.draftBlock.path}`);
|
|
233
|
-
if (result.promoteCommand)
|
|
234
|
-
console.log(`Promote: ${result.promoteCommand}`);
|
|
235
|
-
}
|
|
378
|
+
printDqlArtifactPreview(result);
|
|
236
379
|
}
|
|
237
380
|
finally {
|
|
238
381
|
kg.close();
|
|
@@ -241,6 +384,103 @@ async function runAsk(rest, flags) {
|
|
|
241
384
|
await closeRuntime();
|
|
242
385
|
}
|
|
243
386
|
}
|
|
387
|
+
function printDqlArtifactPreview(result) {
|
|
388
|
+
const dqlSource = result.dqlArtifact?.source?.trim();
|
|
389
|
+
if (dqlSource) {
|
|
390
|
+
console.log(`\n--- DQL artifact (${result.dqlArtifact?.kind ?? 'draft'}) ---\n${dqlSource}`);
|
|
391
|
+
}
|
|
392
|
+
if (result.proposedSql) {
|
|
393
|
+
const label = dqlSource
|
|
394
|
+
? 'Compiled SQL preview'
|
|
395
|
+
: 'Proposed SQL (review before saving as a block)';
|
|
396
|
+
console.log(`\n--- ${label} ---\n${result.proposedSql}`);
|
|
397
|
+
if (result.suggestedViz)
|
|
398
|
+
console.log(`Viz: ${result.suggestedViz}`);
|
|
399
|
+
if (result.draftBlock?.path)
|
|
400
|
+
console.log(`Draft: ${result.draftBlock.path}`);
|
|
401
|
+
if (result.promoteCommand)
|
|
402
|
+
console.log(`Promote: ${result.promoteCommand}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* `dql agent ask --thread <id>` — POST the question to the runtime's
|
|
407
|
+
* `/api/agent-runs` with the threadId in the body. The server injects the
|
|
408
|
+
* thread's prior turns into the conversation context and records the completed
|
|
409
|
+
* run as the next turn, so follow-ups resolve "those"/"that product" correctly
|
|
410
|
+
* across CLI invocations (and across the notebook UI, which shares the store).
|
|
411
|
+
*/
|
|
412
|
+
async function runThreadAsk(question, threadId, flags) {
|
|
413
|
+
const projectRoot = findProjectRoot(process.cwd());
|
|
414
|
+
const format = flags.format;
|
|
415
|
+
const { runtimeBase, close } = await resolveAgentRuntime(projectRoot, flags);
|
|
416
|
+
try {
|
|
417
|
+
const reasoningEffort = cliReasoningEffort(flags);
|
|
418
|
+
const analysisDepth = cliAnalysisDepth(flags);
|
|
419
|
+
const response = await fetch(`${runtimeBase.replace(/\/$/, '')}/api/agent-runs`, {
|
|
420
|
+
method: 'POST',
|
|
421
|
+
headers: { 'Content-Type': 'application/json' },
|
|
422
|
+
body: JSON.stringify({
|
|
423
|
+
question,
|
|
424
|
+
threadId,
|
|
425
|
+
...(reasoningEffort ? { reasoningEffort } : {}),
|
|
426
|
+
...(analysisDepth ? { analysisDepth } : {}),
|
|
427
|
+
}),
|
|
428
|
+
});
|
|
429
|
+
if (!response.ok)
|
|
430
|
+
throw new Error(`Runtime returned ${response.status}: ${await response.text()}`);
|
|
431
|
+
const payload = (await response.json());
|
|
432
|
+
if (!payload.run)
|
|
433
|
+
throw new Error('Runtime did not return an agent run.');
|
|
434
|
+
if (format === 'json') {
|
|
435
|
+
console.log(JSON.stringify(payload.run, null, 2));
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const run = payload.run;
|
|
439
|
+
const badge = run.trustState === 'certified'
|
|
440
|
+
? '✓ Certified'
|
|
441
|
+
: run.trustState === 'grounded'
|
|
442
|
+
? '✓ Verified (grounded)'
|
|
443
|
+
: run.trustState === 'review_required'
|
|
444
|
+
? '! AI-generated · review required'
|
|
445
|
+
: run.trustState === 'blocked'
|
|
446
|
+
? '✕ Blocked'
|
|
447
|
+
: '· Reply';
|
|
448
|
+
console.log(`${badge}\n\n${(run.answer ?? run.summary ?? '').trim()}`);
|
|
449
|
+
console.log(`\nThread: ${threadId}`);
|
|
450
|
+
}
|
|
451
|
+
finally {
|
|
452
|
+
await close();
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
/** `dql agent threads` — list server-persisted conversation threads. */
|
|
456
|
+
async function runThreads(flags) {
|
|
457
|
+
const projectRoot = findProjectRoot(process.cwd());
|
|
458
|
+
const format = flags.format;
|
|
459
|
+
const { runtimeBase, close } = await resolveAgentRuntime(projectRoot, flags);
|
|
460
|
+
try {
|
|
461
|
+
const response = await fetch(`${runtimeBase.replace(/\/$/, '')}/api/agent/threads?limit=50`);
|
|
462
|
+
if (!response.ok)
|
|
463
|
+
throw new Error(`Runtime returned ${response.status}: ${await response.text()}`);
|
|
464
|
+
const payload = (await response.json());
|
|
465
|
+
const threads = Array.isArray(payload.threads) ? payload.threads : [];
|
|
466
|
+
if (format === 'json') {
|
|
467
|
+
console.log(JSON.stringify({ threads }, null, 2));
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (threads.length === 0) {
|
|
471
|
+
console.log('No conversation threads yet. Ask from the notebook UI, or continue one here with `dql agent ask "<question>" --thread <id>`.');
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
for (const thread of threads) {
|
|
475
|
+
const updated = thread.updatedAt ? new Date(thread.updatedAt).toISOString().replace('T', ' ').slice(0, 16) : 'unknown';
|
|
476
|
+
console.log(` ${thread.id} ${updated} [${thread.surface ?? 'notebook'}] ${thread.title ?? '(untitled)'}`);
|
|
477
|
+
}
|
|
478
|
+
console.log(`\n${threads.length} thread(s). Continue one: dql agent ask "<question>" --thread <id>`);
|
|
479
|
+
}
|
|
480
|
+
finally {
|
|
481
|
+
await close();
|
|
482
|
+
}
|
|
483
|
+
}
|
|
244
484
|
function recordCliQueryRun(projectRoot, run) {
|
|
245
485
|
try {
|
|
246
486
|
recordQueryRun(projectRoot, run);
|
|
@@ -256,7 +496,9 @@ async function runReindex(rest, flags) {
|
|
|
256
496
|
console.log(JSON.stringify({ ok: true, ...stats }, null, 2));
|
|
257
497
|
return;
|
|
258
498
|
}
|
|
259
|
-
|
|
499
|
+
const kgStatus = stats.kgRebuilt ? 'KG rebuilt' : 'KG fresh';
|
|
500
|
+
const catalogStatus = stats.metadataRefreshed ? 'metadata refreshed' : 'metadata fresh';
|
|
501
|
+
console.log(` ✓ ${kgStatus}; ${catalogStatus} — ${stats.nodes} nodes, ${stats.edges} edges, ${stats.skills} skill(s).`);
|
|
260
502
|
}
|
|
261
503
|
async function runFeedback(rest, flags) {
|
|
262
504
|
const rating = rest[0];
|
|
@@ -304,14 +546,27 @@ async function runEval(rest, flags) {
|
|
|
304
546
|
await reindexProject(projectRoot, { kgPath });
|
|
305
547
|
const providerName = flags.provider;
|
|
306
548
|
const provider = await pickProvider(providerName);
|
|
549
|
+
const reasoningEffort = cliReasoningEffort(flags);
|
|
550
|
+
const requestedDepth = cliAnalysisDepth(flags);
|
|
307
551
|
const kg = new KGStore(kgPath);
|
|
308
552
|
const memory = new MemoryStore(defaultMemoryPath(projectRoot));
|
|
309
553
|
const { skills } = loadSkills(projectRoot);
|
|
310
554
|
const execute = Boolean(flags.execute);
|
|
555
|
+
// R3.2: optional LLM-as-judge. Uses the same provider's completion; skipped
|
|
556
|
+
// gracefully when no provider is available so offline eval stays deterministic.
|
|
557
|
+
const judge = Boolean(flags.judge);
|
|
558
|
+
const judgeComplete = async ({ system, user }) => provider.generate([{ role: 'system', content: system }, { role: 'user', content: user }], {});
|
|
311
559
|
const runtimeBase = flags.runtimeUrl
|
|
312
560
|
?? flags.runtime
|
|
313
561
|
?? process.env.DQL_RUNTIME_URL
|
|
314
562
|
?? 'http://127.0.0.1:3474';
|
|
563
|
+
const semanticLayer = loadAgentSemanticLayer(projectRoot);
|
|
564
|
+
const expandGroundingContext = createGroundingContextExpander(projectRoot);
|
|
565
|
+
const answerLoopTools = buildAnswerLoopTools(projectRoot, { serverUrl: runtimeBase });
|
|
566
|
+
const schemaContext = execute
|
|
567
|
+
? await fetchRuntimeSchemaContext(runtimeBase)
|
|
568
|
+
: [];
|
|
569
|
+
recordCliRuntimeSchemaSnapshot(projectRoot, schemaContext, 'CLI eval runtime schema');
|
|
315
570
|
const manifest = execute
|
|
316
571
|
? buildManifest({ projectRoot, dbtManifestPath: resolveDbtManifestPath(projectRoot) ?? undefined })
|
|
317
572
|
: null;
|
|
@@ -324,12 +579,25 @@ async function runEval(rest, flags) {
|
|
|
324
579
|
scopes: ['project', 'user', 'artifact'],
|
|
325
580
|
limit: 6,
|
|
326
581
|
});
|
|
582
|
+
const questionPlan = buildAnalysisQuestionPlan(testCase.question, testCase.followUp);
|
|
583
|
+
const contextBudget = contextRetrievalBudgetForQuestion({
|
|
584
|
+
questionPlan,
|
|
585
|
+
requestedDepth,
|
|
586
|
+
reasoningEffort,
|
|
587
|
+
});
|
|
327
588
|
const contextPack = await buildLocalContextPack(projectRoot, {
|
|
328
589
|
question: testCase.question,
|
|
329
590
|
surface: 'cli-eval',
|
|
330
591
|
followUp: testCase.followUp,
|
|
331
592
|
selectedContext: testCase.selectedContext,
|
|
332
|
-
|
|
593
|
+
strictness: contextBudget.strictness,
|
|
594
|
+
limit: contextBudget.limit,
|
|
595
|
+
runtimeSchemaSnapshot: schemaContext.length > 0
|
|
596
|
+
? {
|
|
597
|
+
source: 'CLI eval runtime schema',
|
|
598
|
+
tables: schemaContext,
|
|
599
|
+
}
|
|
600
|
+
: undefined,
|
|
333
601
|
}).catch(() => undefined);
|
|
334
602
|
const result = await answer({
|
|
335
603
|
question: testCase.question,
|
|
@@ -339,16 +607,45 @@ async function runEval(rest, flags) {
|
|
|
339
607
|
skills,
|
|
340
608
|
memoryContext,
|
|
341
609
|
followUp: testCase.followUp,
|
|
610
|
+
semanticLayer,
|
|
611
|
+
schemaContext,
|
|
342
612
|
contextPack,
|
|
613
|
+
reasoningEffort,
|
|
614
|
+
analysisDepth: contextBudget.analysisDepth,
|
|
615
|
+
expandGroundingContext,
|
|
616
|
+
answerLoopTools,
|
|
343
617
|
executeCertifiedBlock: execute && manifest
|
|
344
618
|
? createCertifiedBlockExecutor(projectRoot, manifest, runtimeBase)
|
|
345
619
|
: undefined,
|
|
346
620
|
executeGeneratedSql: execute
|
|
347
621
|
? createGeneratedSqlExecutor(runtimeBase)
|
|
348
622
|
: undefined,
|
|
349
|
-
captureGeneratedDraft: ({ question: draftQuestion, sql, intent, followUp, contextPack: draftContextPack, sourceBlock, validationWarnings }) => {
|
|
623
|
+
captureGeneratedDraft: ({ question: draftQuestion, sql, intent, followUp, contextPack: draftContextPack, sourceBlock, sourceDqlArtifact, dqlArtifact, proposedEntity, requestedFilters, requestedDimensions, validationWarnings, outputs }) => {
|
|
350
624
|
const slug = deriveGeneratedDraftSlug(draftQuestion);
|
|
351
625
|
const proposedDomain = sourceBlock?.domain ?? draftContextPack?.objects.find((object) => object.domain)?.domain ?? testCase.domain ?? 'misc';
|
|
626
|
+
if (dqlArtifact?.kind === 'semantic_block') {
|
|
627
|
+
if (!flags.save) {
|
|
628
|
+
return {
|
|
629
|
+
path: previewGeneratedDraftPath(projectRoot, proposedDomain, slug),
|
|
630
|
+
askedTimes: 0,
|
|
631
|
+
proposedContractId: `${proposedDomain}.Unknown.${slug}`,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
return upsertGeneratedDqlArtifactDraft(projectRoot, {
|
|
635
|
+
slug,
|
|
636
|
+
question: draftQuestion,
|
|
637
|
+
proposedContractId: `${proposedDomain}.Unknown.${slug}`,
|
|
638
|
+
proposedDomain,
|
|
639
|
+
dqlArtifact,
|
|
640
|
+
sourceQuestion: followUp?.sourceQuestion,
|
|
641
|
+
sourceBlock: followUp?.sourceBlockName ?? sourceBlock?.name,
|
|
642
|
+
followupKind: followUp?.kind,
|
|
643
|
+
outputs,
|
|
644
|
+
contextPackId: draftContextPack?.id,
|
|
645
|
+
routeIntent: String(intent),
|
|
646
|
+
validationWarnings,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
352
649
|
if (!flags.save) {
|
|
353
650
|
return {
|
|
354
651
|
path: previewGeneratedDraftPath(projectRoot, proposedDomain, slug),
|
|
@@ -362,11 +659,14 @@ async function runEval(rest, flags) {
|
|
|
362
659
|
proposedSql: sql,
|
|
363
660
|
proposedContractId: `${proposedDomain}.Unknown.${slug}`,
|
|
364
661
|
proposedDomain,
|
|
662
|
+
proposedEntity,
|
|
663
|
+
sourceDqlArtifact,
|
|
365
664
|
sourceQuestion: followUp?.sourceQuestion,
|
|
366
665
|
sourceBlock: followUp?.sourceBlockName ?? sourceBlock?.name,
|
|
367
666
|
followupKind: followUp?.kind,
|
|
368
|
-
requestedFilters
|
|
369
|
-
requestedDimensions
|
|
667
|
+
requestedFilters,
|
|
668
|
+
requestedDimensions,
|
|
669
|
+
outputs,
|
|
370
670
|
contextPackId: draftContextPack?.id,
|
|
371
671
|
routeIntent: String(intent),
|
|
372
672
|
validationWarnings,
|
|
@@ -374,21 +674,42 @@ async function runEval(rest, flags) {
|
|
|
374
674
|
},
|
|
375
675
|
});
|
|
376
676
|
const evaluation = evaluateCase(testCase, result);
|
|
677
|
+
const durationMs = Date.now() - startedAt;
|
|
678
|
+
const draftSaved = Boolean(result.draftBlock?.path ?? result.draftBlockId);
|
|
679
|
+
const judgeVerdict = judge
|
|
680
|
+
? await judgeAnswer({
|
|
681
|
+
question: testCase.question,
|
|
682
|
+
sql: result.proposedSql ?? result.sql,
|
|
683
|
+
answerText: result.text,
|
|
684
|
+
trustLabel: result.trustLabelInfo?.display ?? result.certification,
|
|
685
|
+
resultSample: result.result?.rows,
|
|
686
|
+
}, judgeComplete)
|
|
687
|
+
: undefined;
|
|
377
688
|
results.push({
|
|
378
689
|
name: testCase.name ?? testCase.question,
|
|
379
690
|
passed: evaluation.failures.length === 0,
|
|
380
691
|
failures: evaluation.failures,
|
|
381
|
-
durationMs
|
|
692
|
+
durationMs,
|
|
382
693
|
executionMs: result.result?.executionTime,
|
|
694
|
+
executionMatched: evaluation.executionMatched,
|
|
695
|
+
...(judgeVerdict ? { judgeScore: judgeVerdict.score, judgePass: judgeVerdict.pass } : {}),
|
|
383
696
|
kind: result.kind,
|
|
384
697
|
route: result.contextPack?.routeDecision.route,
|
|
385
698
|
intent: result.contextPack?.routeDecision.intent,
|
|
386
699
|
reviewStatus: result.reviewStatus,
|
|
387
700
|
contextObjects: result.contextPack?.objects.length ?? 0,
|
|
388
701
|
followUp: Boolean(testCase.followUp),
|
|
389
|
-
draftSaved
|
|
702
|
+
draftSaved,
|
|
703
|
+
toolCalls: result.evidence?.toolCalls?.length ?? 0,
|
|
390
704
|
expected: testCase.expected,
|
|
391
705
|
validationCode: evaluation.validationCode,
|
|
706
|
+
trace: buildEvalTrace({
|
|
707
|
+
testCase,
|
|
708
|
+
result,
|
|
709
|
+
evaluation,
|
|
710
|
+
durationMs,
|
|
711
|
+
draftSaved,
|
|
712
|
+
}),
|
|
392
713
|
});
|
|
393
714
|
}
|
|
394
715
|
}
|
|
@@ -398,8 +719,18 @@ async function runEval(rest, flags) {
|
|
|
398
719
|
}
|
|
399
720
|
const passed = results.filter((r) => r.passed).length;
|
|
400
721
|
const metrics = computeEvalMetrics(results);
|
|
722
|
+
const thresholds = {
|
|
723
|
+
minToolRequirement: flags.minToolRequirement ?? null,
|
|
724
|
+
minExecutionMatch: flags.minExecutionMatch ?? null,
|
|
725
|
+
minJudgePass: flags.minJudgePass ?? null,
|
|
726
|
+
maxWrongCertified: flags.maxWrongCertified ?? null,
|
|
727
|
+
};
|
|
728
|
+
const thresholdsPassed = agentEvalThresholdsPass(metrics, thresholds);
|
|
729
|
+
const ok = passed === results.length && thresholdsPassed;
|
|
401
730
|
if (flags.format === 'json') {
|
|
402
|
-
console.log(JSON.stringify({ ok
|
|
731
|
+
console.log(JSON.stringify({ ok, passed, total: results.length, thresholds, metrics, results }, null, 2));
|
|
732
|
+
if (!ok)
|
|
733
|
+
process.exitCode = 1;
|
|
403
734
|
return;
|
|
404
735
|
}
|
|
405
736
|
for (const result of results) {
|
|
@@ -411,9 +742,25 @@ async function runEval(rest, flags) {
|
|
|
411
742
|
console.log(`Certified hit rate: ${formatRate(metrics.certified_hit_rate)}`);
|
|
412
743
|
console.log(`Generated follow-up pass rate: ${formatRate(metrics.generated_followup_pass_rate)}`);
|
|
413
744
|
console.log(`Safe refusal rate: ${formatRate(metrics.safe_refusal_rate)}`);
|
|
745
|
+
console.log(`Execution match rate: ${formatRate(metrics.execution_match_rate)}`);
|
|
746
|
+
console.log(`Tool requirement pass rate: ${formatRate(metrics.tool_requirement_pass_rate)}`);
|
|
747
|
+
console.log(`Tool-observed case count: ${metrics.tool_observed_case_count}`);
|
|
748
|
+
console.log(`Average tool calls: ${metrics.avg_tool_calls}`);
|
|
414
749
|
console.log(`Wrong certified count: ${metrics.wrong_certified_count}`);
|
|
415
750
|
console.log(`Draft saved count: ${metrics.draft_saved_count}`);
|
|
416
|
-
if (
|
|
751
|
+
if (thresholds.minToolRequirement !== null) {
|
|
752
|
+
console.log(`Tool requirement threshold: ${thresholds.minToolRequirement} (actual ${formatRate(metrics.tool_requirement_pass_rate)})`);
|
|
753
|
+
}
|
|
754
|
+
if (thresholds.minExecutionMatch !== null) {
|
|
755
|
+
console.log(`Execution-match threshold: ${thresholds.minExecutionMatch} (actual ${formatRate(metrics.execution_match_rate)})`);
|
|
756
|
+
}
|
|
757
|
+
if (thresholds.minJudgePass !== null) {
|
|
758
|
+
console.log(`Judge-pass threshold: ${thresholds.minJudgePass} (actual ${formatRate(metrics.judge_pass_rate)})`);
|
|
759
|
+
}
|
|
760
|
+
if (thresholds.maxWrongCertified !== null) {
|
|
761
|
+
console.log(`Wrong-certified ceiling: ${thresholds.maxWrongCertified} (actual ${metrics.wrong_certified_count})`);
|
|
762
|
+
}
|
|
763
|
+
if (!ok)
|
|
417
764
|
process.exitCode = 1;
|
|
418
765
|
}
|
|
419
766
|
function previewGeneratedDraftPath(projectRoot, domain, slug) {
|
|
@@ -433,6 +780,7 @@ function evaluateCase(testCase, result) {
|
|
|
433
780
|
return { failures: [] };
|
|
434
781
|
const failures = [];
|
|
435
782
|
let validationCode;
|
|
783
|
+
let executionMatched;
|
|
436
784
|
if (expected.kind && result.kind !== expected.kind)
|
|
437
785
|
failures.push(`kind expected ${expected.kind}, got ${result.kind}`);
|
|
438
786
|
if (expected.sourceTier && result.sourceTier !== expected.sourceTier)
|
|
@@ -466,6 +814,12 @@ function evaluateCase(testCase, result) {
|
|
|
466
814
|
if (saved !== expected.draftSaved)
|
|
467
815
|
failures.push(`draftSaved expected ${expected.draftSaved}, got ${saved}`);
|
|
468
816
|
}
|
|
817
|
+
if (typeof expected.minToolCalls === 'number') {
|
|
818
|
+
const actualToolCalls = result.evidence?.toolCalls?.length ?? 0;
|
|
819
|
+
if (actualToolCalls < expected.minToolCalls) {
|
|
820
|
+
failures.push(`toolCalls expected at least ${expected.minToolCalls}, got ${actualToolCalls}`);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
469
823
|
if ((expected.allowedRelationsOnly || expected.allowedColumnsOnly) && result.proposedSql) {
|
|
470
824
|
const validation = validateSqlAgainstLocalContext(result.proposedSql, result.contextPack, {
|
|
471
825
|
question: testCase.question,
|
|
@@ -479,10 +833,11 @@ function evaluateCase(testCase, result) {
|
|
|
479
833
|
}
|
|
480
834
|
if (expected.rows) {
|
|
481
835
|
const actualRows = result.result?.rows ?? [];
|
|
482
|
-
|
|
836
|
+
executionMatched = rowsEqual(actualRows, expected.rows);
|
|
837
|
+
if (!executionMatched)
|
|
483
838
|
failures.push('executed rows did not match expected rows');
|
|
484
839
|
}
|
|
485
|
-
return { failures, validationCode };
|
|
840
|
+
return { failures, validationCode, executionMatched };
|
|
486
841
|
}
|
|
487
842
|
function computeEvalMetrics(results) {
|
|
488
843
|
const certifiedCases = results.filter((result) => result.expected?.kind === 'certified' ||
|
|
@@ -494,18 +849,256 @@ function computeEvalMetrics(results) {
|
|
|
494
849
|
const executionTimes = results
|
|
495
850
|
.map((result) => result.executionMs)
|
|
496
851
|
.filter((value) => typeof value === 'number' && Number.isFinite(value));
|
|
852
|
+
const executionMatchCases = results.filter((result) => result.executionMatched !== undefined);
|
|
853
|
+
const toolRequiredCases = results.filter((result) => typeof result.expected?.minToolCalls === 'number');
|
|
854
|
+
const toolCallCounts = results.map((result) => result.toolCalls);
|
|
855
|
+
const judged = results.filter((result) => typeof result.judgeScore === 'number');
|
|
497
856
|
return {
|
|
498
857
|
certified_hit_rate: ratio(certifiedCases.filter((result) => result.passed && result.kind === 'certified').length, certifiedCases.length),
|
|
858
|
+
judge_mean_score: judged.length ? average(judged.map((result) => result.judgeScore ?? 0)) : null,
|
|
859
|
+
judge_pass_rate: judged.length ? ratio(judged.filter((result) => result.judgePass).length, judged.length) : null,
|
|
499
860
|
generated_followup_pass_rate: ratio(generatedFollowUpCases.filter((result) => result.passed).length, generatedFollowUpCases.length),
|
|
500
861
|
safe_refusal_rate: ratio(refusalCases.filter((result) => result.passed && result.kind === 'no_answer').length, refusalCases.length),
|
|
862
|
+
execution_match_rate: ratio(executionMatchCases.filter((result) => result.executionMatched).length, executionMatchCases.length),
|
|
863
|
+
tool_requirement_pass_rate: ratio(toolRequiredCases.filter((result) => result.toolCalls >= (result.expected?.minToolCalls ?? 0)).length, toolRequiredCases.length),
|
|
501
864
|
wrong_certified_count: results.filter((result) => result.kind === 'certified' &&
|
|
502
865
|
(result.expected?.kind ? result.expected.kind !== 'certified' : result.followUp)).length,
|
|
503
866
|
outside_context_rejection_count: results.filter((result) => result.validationCode === 'unknown_relation' || result.validationCode === 'unknown_column').length,
|
|
504
867
|
draft_saved_count: results.filter((result) => result.draftSaved).length,
|
|
868
|
+
tool_observed_case_count: results.filter((result) => result.toolCalls > 0).length,
|
|
869
|
+
avg_tool_calls: average(toolCallCounts),
|
|
505
870
|
avg_context_objects: average(results.map((result) => result.contextObjects)),
|
|
506
871
|
avg_execution_ms: executionTimes.length ? average(executionTimes) : null,
|
|
507
872
|
};
|
|
508
873
|
}
|
|
874
|
+
function agentEvalThresholdsPass(metrics, thresholds) {
|
|
875
|
+
// A rate threshold with no applicable cases (metric === null) is vacuously
|
|
876
|
+
// satisfied — you only fail when the metric exists and falls below the bar.
|
|
877
|
+
const rateOk = (metric, min) => min === null || min === undefined || metric === null || metric >= min;
|
|
878
|
+
return rateOk(metrics.tool_requirement_pass_rate, thresholds.minToolRequirement)
|
|
879
|
+
&& rateOk(metrics.execution_match_rate, thresholds.minExecutionMatch)
|
|
880
|
+
&& rateOk(metrics.judge_pass_rate, thresholds.minJudgePass)
|
|
881
|
+
&& (thresholds.maxWrongCertified === null
|
|
882
|
+
|| thresholds.maxWrongCertified === undefined
|
|
883
|
+
|| metrics.wrong_certified_count <= thresholds.maxWrongCertified);
|
|
884
|
+
}
|
|
885
|
+
function buildEvalTrace(input) {
|
|
886
|
+
const { testCase, result, evaluation, durationMs, draftSaved } = input;
|
|
887
|
+
const routeDecision = result.contextPack?.routeDecision;
|
|
888
|
+
const selectedRelations = result.contextPack?.retrievalDiagnostics.selectedRelations ?? [];
|
|
889
|
+
const allowedRelations = result.contextPack?.allowedSqlContext?.relations ?? [];
|
|
890
|
+
const followUp = testCase.followUp;
|
|
891
|
+
const toolCalls = result.evidence?.toolCalls ?? [];
|
|
892
|
+
const routeEvidence = result.evidence?.route ?? [];
|
|
893
|
+
const executionStatus = result.executionError
|
|
894
|
+
? 'failed'
|
|
895
|
+
: result.result
|
|
896
|
+
? 'passed'
|
|
897
|
+
: 'not_run';
|
|
898
|
+
const validationExpected = Boolean(testCase.expected?.allowedRelationsOnly || testCase.expected?.allowedColumnsOnly);
|
|
899
|
+
const validationStatus = evaluation.validationCode
|
|
900
|
+
? 'failed'
|
|
901
|
+
: validationExpected
|
|
902
|
+
? 'passed'
|
|
903
|
+
: 'not_run';
|
|
904
|
+
const rowsExpected = testCase.expected?.rows !== undefined;
|
|
905
|
+
const expectedMinToolCalls = testCase.expected?.minToolCalls;
|
|
906
|
+
const toolStatus = typeof expectedMinToolCalls === 'number'
|
|
907
|
+
? toolCalls.length >= expectedMinToolCalls ? 'passed' : 'failed'
|
|
908
|
+
: toolCalls.length > 0 ? 'passed' : routeEvidence.length > 0 ? 'info' : 'not_run';
|
|
909
|
+
const toolMessage = typeof expectedMinToolCalls === 'number'
|
|
910
|
+
? toolCalls.length >= expectedMinToolCalls
|
|
911
|
+
? `Observed ${toolCalls.length} provider tool call(s), meeting the minimum of ${expectedMinToolCalls}.`
|
|
912
|
+
: `Observed ${toolCalls.length} provider tool call(s), below the minimum of ${expectedMinToolCalls}.`
|
|
913
|
+
: toolCalls.length > 0
|
|
914
|
+
? `Observed ${toolCalls.length} provider tool call(s).`
|
|
915
|
+
: routeEvidence.length > 0
|
|
916
|
+
? `Captured ${routeEvidence.length} deterministic route evidence step(s).`
|
|
917
|
+
: 'No provider tool calls were observed for this answer.';
|
|
918
|
+
return [
|
|
919
|
+
{
|
|
920
|
+
stage: 'context',
|
|
921
|
+
status: result.contextPack ? 'passed' : 'not_run',
|
|
922
|
+
message: result.contextPack
|
|
923
|
+
? `Context pack ${result.contextPack.id} selected ${result.contextPack.objects.length} object(s).`
|
|
924
|
+
: 'No context pack was attached to the answer.',
|
|
925
|
+
payload: result.contextPack
|
|
926
|
+
? {
|
|
927
|
+
contextPackId: result.contextPack.id,
|
|
928
|
+
selectedObjectCount: result.contextPack.objects.length,
|
|
929
|
+
allowedRelationCount: allowedRelations.length,
|
|
930
|
+
selectedRelations: selectedRelations.slice(0, 12).map((relation) => relation.relation),
|
|
931
|
+
missingContext: result.contextPack.missingContext,
|
|
932
|
+
}
|
|
933
|
+
: undefined,
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
stage: 'rewrite',
|
|
937
|
+
status: followUp ? 'passed' : 'not_run',
|
|
938
|
+
message: followUp
|
|
939
|
+
? `Follow-up context attached (${followUp.kind}).`
|
|
940
|
+
: 'No follow-up rewrite/context was supplied for this case.',
|
|
941
|
+
payload: summarizeFollowUpForTrace(followUp),
|
|
942
|
+
},
|
|
943
|
+
{
|
|
944
|
+
stage: 'lane',
|
|
945
|
+
status: routeDecision ? 'passed' : 'not_run',
|
|
946
|
+
message: routeDecision
|
|
947
|
+
? `Lane ${routeDecision.route} / ${routeDecision.intent}.`
|
|
948
|
+
: 'No lane decision was attached to the answer.',
|
|
949
|
+
payload: routeDecision
|
|
950
|
+
? {
|
|
951
|
+
route: routeDecision.route,
|
|
952
|
+
intent: routeDecision.intent,
|
|
953
|
+
reason: routeDecision.reason,
|
|
954
|
+
trustLabel: routeDecision.trustLabel,
|
|
955
|
+
reviewStatus: routeDecision.reviewStatus,
|
|
956
|
+
exactObjectKey: routeDecision.exactObjectKey,
|
|
957
|
+
}
|
|
958
|
+
: undefined,
|
|
959
|
+
},
|
|
960
|
+
{
|
|
961
|
+
stage: 'tools',
|
|
962
|
+
status: toolStatus,
|
|
963
|
+
message: toolMessage,
|
|
964
|
+
payload: {
|
|
965
|
+
observedToolCalls: toolCalls.length,
|
|
966
|
+
expectedMinToolCalls,
|
|
967
|
+
providerToolCalls: toolCalls.slice(0, 12).map((call) => ({
|
|
968
|
+
order: call.order,
|
|
969
|
+
name: call.name,
|
|
970
|
+
status: call.status,
|
|
971
|
+
inputSummary: call.inputSummary,
|
|
972
|
+
outputSummary: call.outputSummary,
|
|
973
|
+
})),
|
|
974
|
+
routeEvidence: routeEvidence.slice(0, 12).map((step) => ({
|
|
975
|
+
tool: step.tool,
|
|
976
|
+
status: step.status,
|
|
977
|
+
label: step.label,
|
|
978
|
+
detail: step.detail,
|
|
979
|
+
})),
|
|
980
|
+
},
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
stage: 'answer',
|
|
984
|
+
status: result.kind === 'no_answer' ? 'failed' : 'passed',
|
|
985
|
+
message: `Answer kind ${result.kind}${result.sourceTier ? ` from ${result.sourceTier}` : ''}.`,
|
|
986
|
+
payload: {
|
|
987
|
+
kind: result.kind,
|
|
988
|
+
sourceTier: result.sourceTier,
|
|
989
|
+
certification: result.certification,
|
|
990
|
+
reviewStatus: result.reviewStatus,
|
|
991
|
+
route: result.route?.tier,
|
|
992
|
+
refusalCode: result.refusalCode,
|
|
993
|
+
sourceCertifiedBlock: result.sourceCertifiedBlock,
|
|
994
|
+
dqlArtifactKind: result.dqlArtifact?.kind,
|
|
995
|
+
providerUsed: result.providerUsed,
|
|
996
|
+
},
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
stage: 'validation',
|
|
1000
|
+
status: validationStatus,
|
|
1001
|
+
message: evaluation.validationCode
|
|
1002
|
+
? `SQL context validation failed with ${evaluation.validationCode}.`
|
|
1003
|
+
: validationExpected
|
|
1004
|
+
? 'SQL context validation passed.'
|
|
1005
|
+
: 'SQL context validation was not required by this case.',
|
|
1006
|
+
payload: {
|
|
1007
|
+
validationCode: evaluation.validationCode,
|
|
1008
|
+
failures: evaluation.failures,
|
|
1009
|
+
expectedAllowedRelationsOnly: testCase.expected?.allowedRelationsOnly,
|
|
1010
|
+
expectedAllowedColumnsOnly: testCase.expected?.allowedColumnsOnly,
|
|
1011
|
+
},
|
|
1012
|
+
},
|
|
1013
|
+
{
|
|
1014
|
+
stage: 'execution',
|
|
1015
|
+
status: executionStatus,
|
|
1016
|
+
message: result.executionError
|
|
1017
|
+
? result.executionError
|
|
1018
|
+
: result.result
|
|
1019
|
+
? `Executed and returned ${result.result.rowCount} row(s).`
|
|
1020
|
+
: 'No SQL/block execution result was captured.',
|
|
1021
|
+
payload: {
|
|
1022
|
+
rowCount: result.result?.rowCount,
|
|
1023
|
+
executionTime: result.result?.executionTime,
|
|
1024
|
+
executionMatched: rowsExpected ? evaluation.executionMatched : undefined,
|
|
1025
|
+
expectedRows: rowsExpected ? testCase.expected?.rows?.length : undefined,
|
|
1026
|
+
columns: summarizeResultColumns(result.result?.columns),
|
|
1027
|
+
},
|
|
1028
|
+
},
|
|
1029
|
+
{
|
|
1030
|
+
stage: 'draft',
|
|
1031
|
+
status: draftSaved ? 'passed' : 'not_run',
|
|
1032
|
+
message: draftSaved
|
|
1033
|
+
? `Draft captured at ${result.draftBlock?.path ?? result.draftBlockId}.`
|
|
1034
|
+
: 'No generated draft was captured.',
|
|
1035
|
+
payload: {
|
|
1036
|
+
draftBlockId: result.draftBlockId,
|
|
1037
|
+
draftPath: result.draftBlock?.path,
|
|
1038
|
+
promoteCommand: result.promoteCommand,
|
|
1039
|
+
},
|
|
1040
|
+
},
|
|
1041
|
+
{
|
|
1042
|
+
stage: 'scoring',
|
|
1043
|
+
status: evaluation.failures.length === 0 ? 'passed' : 'failed',
|
|
1044
|
+
message: evaluation.failures.length === 0
|
|
1045
|
+
? `Case passed in ${durationMs}ms.`
|
|
1046
|
+
: `Case failed ${evaluation.failures.length} check(s) in ${durationMs}ms.`,
|
|
1047
|
+
payload: {
|
|
1048
|
+
durationMs,
|
|
1049
|
+
expected: testCase.expected,
|
|
1050
|
+
},
|
|
1051
|
+
},
|
|
1052
|
+
];
|
|
1053
|
+
}
|
|
1054
|
+
function summarizeFollowUpForTrace(followUp) {
|
|
1055
|
+
if (!followUp)
|
|
1056
|
+
return undefined;
|
|
1057
|
+
const priorResultRef = followUp.priorResultRef
|
|
1058
|
+
? {
|
|
1059
|
+
id: followUp.priorResultRef.id,
|
|
1060
|
+
question: followUp.priorResultRef.question,
|
|
1061
|
+
columns: followUp.priorResultRef.columns,
|
|
1062
|
+
rowCount: followUp.priorResultRef.rowCount,
|
|
1063
|
+
sourceSql: truncateTraceText(followUp.priorResultRef.sourceSql, 4000),
|
|
1064
|
+
}
|
|
1065
|
+
: undefined;
|
|
1066
|
+
const priorDqlArtifact = followUp.priorDqlArtifact
|
|
1067
|
+
? {
|
|
1068
|
+
kind: followUp.priorDqlArtifact.kind,
|
|
1069
|
+
name: followUp.priorDqlArtifact.name,
|
|
1070
|
+
sourcePath: followUp.priorDqlArtifact.sourcePath,
|
|
1071
|
+
source: truncateTraceText(followUp.priorDqlArtifact.source, 4000),
|
|
1072
|
+
metrics: followUp.priorDqlArtifact.metrics,
|
|
1073
|
+
dimensions: followUp.priorDqlArtifact.dimensions,
|
|
1074
|
+
filters: followUp.priorDqlArtifact.filters,
|
|
1075
|
+
timeDimension: followUp.priorDqlArtifact.timeDimension,
|
|
1076
|
+
orderBy: followUp.priorDqlArtifact.orderBy,
|
|
1077
|
+
limit: followUp.priorDqlArtifact.limit,
|
|
1078
|
+
}
|
|
1079
|
+
: undefined;
|
|
1080
|
+
return {
|
|
1081
|
+
kind: followUp.kind,
|
|
1082
|
+
sourceTurnId: followUp.sourceTurnId,
|
|
1083
|
+
sourceBlockName: followUp.sourceBlockName,
|
|
1084
|
+
sourceQuestion: followUp.sourceQuestion,
|
|
1085
|
+
filters: followUp.filters,
|
|
1086
|
+
dimensions: followUp.dimensions,
|
|
1087
|
+
priorResultColumns: followUp.priorResultColumns,
|
|
1088
|
+
priorResultValues: followUp.priorResultValues,
|
|
1089
|
+
priorResultRef,
|
|
1090
|
+
priorDqlArtifact,
|
|
1091
|
+
priorLimit: followUp.priorLimit,
|
|
1092
|
+
priorMeasures: followUp.priorMeasures,
|
|
1093
|
+
resolvedReferences: followUp.resolvedReferences,
|
|
1094
|
+
unresolvedReferences: followUp.unresolvedReferences,
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
function truncateTraceText(value, maxLength) {
|
|
1098
|
+
if (!value || value.length <= maxLength)
|
|
1099
|
+
return value;
|
|
1100
|
+
return `${value.slice(0, maxLength)}...`;
|
|
1101
|
+
}
|
|
509
1102
|
function ratio(numerator, denominator) {
|
|
510
1103
|
if (denominator === 0)
|
|
511
1104
|
return null;
|
|
@@ -516,6 +1109,17 @@ function average(values) {
|
|
|
516
1109
|
return 0;
|
|
517
1110
|
return Number((values.reduce((sum, value) => sum + value, 0) / values.length).toFixed(2));
|
|
518
1111
|
}
|
|
1112
|
+
function summarizeResultColumns(columns) {
|
|
1113
|
+
return (columns ?? []).map((column) => {
|
|
1114
|
+
if (typeof column === 'string')
|
|
1115
|
+
return column;
|
|
1116
|
+
if (column && typeof column === 'object' && 'name' in column) {
|
|
1117
|
+
const name = column.name;
|
|
1118
|
+
return typeof name === 'string' ? name : JSON.stringify(column);
|
|
1119
|
+
}
|
|
1120
|
+
return String(column);
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
519
1123
|
function formatRate(value) {
|
|
520
1124
|
return value === null ? 'n/a' : `${Math.round(value * 1000) / 10}%`;
|
|
521
1125
|
}
|
|
@@ -588,6 +1192,10 @@ async function executeRuntimeCell(runtimeBase, cell) {
|
|
|
588
1192
|
return payload;
|
|
589
1193
|
}
|
|
590
1194
|
export const __test__ = {
|
|
1195
|
+
agentEvalThresholdsPass,
|
|
1196
|
+
buildEvalTrace,
|
|
1197
|
+
cliAnalysisDepth,
|
|
1198
|
+
cliReasoningEffort,
|
|
591
1199
|
computeEvalMetrics,
|
|
592
1200
|
evaluateCase,
|
|
593
1201
|
};
|