@duckcodeailabs/dql-agent 1.6.6 → 1.6.8
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/answer-loop.d.ts +17 -0
- package/dist/answer-loop.d.ts.map +1 -1
- package/dist/answer-loop.js +479 -102
- package/dist/answer-loop.js.map +1 -1
- package/dist/app-builder.d.ts.map +1 -1
- package/dist/app-builder.js +80 -38
- package/dist/app-builder.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/kg/build.d.ts.map +1 -1
- package/dist/kg/build.js +1 -0
- package/dist/kg/build.js.map +1 -1
- package/dist/kg/types.d.ts +2 -0
- package/dist/kg/types.d.ts.map +1 -1
- package/dist/metadata/catalog.d.ts +10 -1
- package/dist/metadata/catalog.d.ts.map +1 -1
- package/dist/metadata/catalog.js +125 -67
- package/dist/metadata/catalog.js.map +1 -1
- package/dist/metadata/drafts.d.ts +25 -0
- package/dist/metadata/drafts.d.ts.map +1 -0
- package/dist/metadata/drafts.js +108 -0
- package/dist/metadata/drafts.js.map +1 -0
- package/dist/metadata/sql-context-validation.d.ts +28 -0
- package/dist/metadata/sql-context-validation.d.ts.map +1 -0
- package/dist/metadata/sql-context-validation.js +340 -0
- package/dist/metadata/sql-context-validation.js.map +1 -0
- package/package.json +4 -4
package/dist/metadata/catalog.js
CHANGED
|
@@ -97,33 +97,34 @@ export async function buildLocalContextPack(projectRoot, request) {
|
|
|
97
97
|
const catalog = openMetadataCatalog(projectRoot);
|
|
98
98
|
try {
|
|
99
99
|
const mode = request.mode ?? 'question';
|
|
100
|
+
const followUp = normalizeFollowUpContext(request.followUp);
|
|
100
101
|
const runtimeSnapshot = request.runtimeSchemaSnapshot ?? catalog.latestRuntimeSchemaSnapshot();
|
|
101
102
|
const runtimeObjects = runtimeSnapshot ? runtimeSchemaObjects(runtimeSnapshot) : [];
|
|
102
103
|
const selectedObjects = selectedContextObjects(request.selectedContext);
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
const followUpObjects = followUpContextObjects(followUp);
|
|
105
|
+
const followUpSourceObjects = catalog.getObjectsByKeys(followUpSourceObjectKeys(followUp));
|
|
106
|
+
const searchQuery = buildFollowUpSearchQuery(request.question, followUp);
|
|
106
107
|
const searchRows = catalog.searchObjects({
|
|
107
|
-
query:
|
|
108
|
+
query: searchQuery,
|
|
108
109
|
objectTypes: request.objectTypes,
|
|
109
110
|
limit: Math.max(request.limit ?? 80, 20),
|
|
110
111
|
});
|
|
111
112
|
const exact = request.focusObjectKey ? catalog.getObject(request.focusObjectKey) : null;
|
|
112
113
|
const ranked = rankMetadataObjects({
|
|
113
114
|
rows: mergeObjects(exact
|
|
114
|
-
? [exact, ...
|
|
115
|
-
: [...
|
|
116
|
-
question:
|
|
115
|
+
? [exact, ...followUpSourceObjects, ...followUpObjects, ...searchRows, ...runtimeObjects, ...selectedObjects]
|
|
116
|
+
: [...followUpSourceObjects, ...followUpObjects, ...searchRows, ...runtimeObjects, ...selectedObjects]),
|
|
117
|
+
question: searchQuery,
|
|
117
118
|
limit: request.limit ?? 80,
|
|
118
119
|
});
|
|
119
|
-
const selected = ranked.selected;
|
|
120
|
-
const focusObjectKey = request.focusObjectKey ??
|
|
120
|
+
const selected = mergeObjects([...followUpSourceObjects, ...followUpObjects, ...ranked.selected]);
|
|
121
|
+
const focusObjectKey = request.focusObjectKey ?? selected[0]?.objectKey ?? null;
|
|
121
122
|
const edgeWalk = catalog.edgesForKeys(selected.map((row) => row.objectKey), 3);
|
|
122
123
|
const edgeObjectKeys = Array.from(new Set(edgeWalk.flatMap((edge) => [edge.fromKey, edge.toKey])));
|
|
123
124
|
const graphObjects = catalog.getObjectsByKeys(edgeObjectKeys);
|
|
124
125
|
const objects = rankMetadataObjects({
|
|
125
|
-
rows: mergeObjects([...
|
|
126
|
-
question:
|
|
126
|
+
rows: mergeObjects([...followUpSourceObjects, ...followUpObjects, ...selected, ...graphObjects, ...runtimeObjects, ...selectedObjects]),
|
|
127
|
+
question: searchQuery,
|
|
127
128
|
limit: request.limit ?? 120,
|
|
128
129
|
}).selected;
|
|
129
130
|
const objectKeys = objects.map((row) => row.objectKey);
|
|
@@ -137,7 +138,7 @@ export async function buildLocalContextPack(projectRoot, request) {
|
|
|
137
138
|
const evidenceRoles = buildEvidenceRoles(objects, queryRuns);
|
|
138
139
|
const reranked = rankMetadataObjects({
|
|
139
140
|
rows: mergeObjects([...searchRows, ...objects]),
|
|
140
|
-
question:
|
|
141
|
+
question: searchQuery,
|
|
141
142
|
limit: request.limit ?? 120,
|
|
142
143
|
});
|
|
143
144
|
const conflicts = buildCandidateConflicts(reranked.ranked);
|
|
@@ -152,6 +153,7 @@ export async function buildLocalContextPack(projectRoot, request) {
|
|
|
152
153
|
const payload = {
|
|
153
154
|
id: '',
|
|
154
155
|
question: request.question,
|
|
156
|
+
followUp: followUp ?? undefined,
|
|
155
157
|
focusObjectKey,
|
|
156
158
|
mode,
|
|
157
159
|
trustLabel,
|
|
@@ -682,6 +684,7 @@ function objectFromKGNode(node) {
|
|
|
682
684
|
businessRules: node.businessRules ?? [],
|
|
683
685
|
caveats: node.caveats ?? [],
|
|
684
686
|
llmContext: node.llmContext,
|
|
687
|
+
referencedBy: node.referencedBy ?? [],
|
|
685
688
|
};
|
|
686
689
|
return {
|
|
687
690
|
objectKey: objectKeyFromKGNode(node),
|
|
@@ -838,6 +841,7 @@ function addManifestBlockDetails(manifest, objects) {
|
|
|
838
841
|
chartType: block.chartType,
|
|
839
842
|
blockType: block.blockType,
|
|
840
843
|
tests: block.tests,
|
|
844
|
+
draftMetadata: block.draftMetadata,
|
|
841
845
|
}),
|
|
842
846
|
}));
|
|
843
847
|
}
|
|
@@ -959,10 +963,12 @@ function addProjectDiagnostics(manifest, semanticLayer, diagnostics) {
|
|
|
959
963
|
function planContextPackRoute(input) {
|
|
960
964
|
const intent = input.request.intent ?? classifyMetadataIntent(input.request.question, input.request.followUp);
|
|
961
965
|
const exact = findExactCertifiedObject(input.request.question, intent, input.objects);
|
|
966
|
+
const exactExampleMatch = exact ? hasExactExampleQuestion(input.request.question, exact) : false;
|
|
962
967
|
const missingContext = buildMissingContext(input.request, intent, input.objects, input.allowedSqlContext);
|
|
963
968
|
const selectedEvidence = input.evidenceRoles.slice(0, 16);
|
|
964
969
|
if (exact && (intent === 'exact_certified_lookup'
|
|
965
970
|
|| intent === 'definition_lookup'
|
|
971
|
+
|| exactExampleMatch
|
|
966
972
|
|| (intent === 'ad_hoc_ranking' && objectNameInQuestion(input.request.question, exact)))) {
|
|
967
973
|
return {
|
|
968
974
|
route: 'certified',
|
|
@@ -1075,6 +1081,8 @@ function classifyMetadataIntent(question, followUp) {
|
|
|
1075
1081
|
return 'ad_hoc_ranking';
|
|
1076
1082
|
if (/\b(block|certified|saved|existing|approved|governed)\b/.test(lower))
|
|
1077
1083
|
return 'exact_certified_lookup';
|
|
1084
|
+
if (isDirectKpiValueQuestion(question))
|
|
1085
|
+
return 'exact_certified_lookup';
|
|
1078
1086
|
if (/\b(show|list|find|which|who|how many|how much|metric|kpi|dashboard|performance|revenue|sales|points|goals|orders|customers|users)\b/.test(lower))
|
|
1079
1087
|
return 'ad_hoc_ranking';
|
|
1080
1088
|
return 'clarify';
|
|
@@ -1098,6 +1106,11 @@ function findExactCertifiedObject(question, intent, objects) {
|
|
|
1098
1106
|
hasCompatibleMetadataRankingDirection(question, object));
|
|
1099
1107
|
if (namedExact)
|
|
1100
1108
|
return namedExact;
|
|
1109
|
+
const exampleExact = candidates.find((object) => object.objectType === 'dql_block' &&
|
|
1110
|
+
hasExactExampleQuestion(question, object) &&
|
|
1111
|
+
hasCompatibleMetadataRankingDirection(question, object));
|
|
1112
|
+
if (exampleExact)
|
|
1113
|
+
return exampleExact;
|
|
1101
1114
|
if (isGeneratedMetadataIntent(intent))
|
|
1102
1115
|
return undefined;
|
|
1103
1116
|
return candidates.find((object) => object.objectType === 'dql_block' &&
|
|
@@ -1116,6 +1129,15 @@ function objectNameInQuestion(question, object) {
|
|
|
1116
1129
|
const fullName = normalizeSearchText(object.fullName ?? '');
|
|
1117
1130
|
return Boolean(name && q.includes(name)) || Boolean(fullName && q.includes(fullName));
|
|
1118
1131
|
}
|
|
1132
|
+
function hasExactExampleQuestion(question, object) {
|
|
1133
|
+
const q = normalizeSearchText(question);
|
|
1134
|
+
if (!q)
|
|
1135
|
+
return false;
|
|
1136
|
+
const examples = Array.isArray(object.payload?.examples) ? object.payload.examples : [];
|
|
1137
|
+
return examples.some((example) => example &&
|
|
1138
|
+
typeof example === 'object' &&
|
|
1139
|
+
normalizeSearchText(String(example.question ?? '')) === q);
|
|
1140
|
+
}
|
|
1119
1141
|
function hasMeaningfulObjectOverlap(question, object) {
|
|
1120
1142
|
const terms = new Set(tokenize(question));
|
|
1121
1143
|
if (terms.size === 0)
|
|
@@ -1132,6 +1154,17 @@ function hasMeaningfulObjectOverlap(question, object) {
|
|
|
1132
1154
|
function looksLikeDifferentGrainQuestion(question) {
|
|
1133
1155
|
return /\b(for|where|only|specific|single|individual|named|called|by|break\s*down|breakdown|drill|compare|versus|vs\.?|segment|least|lowest|fewest|bottom|why|changed?|driver|anomal|exception)\b/i.test(question);
|
|
1134
1156
|
}
|
|
1157
|
+
function isDirectKpiValueQuestion(question) {
|
|
1158
|
+
const lower = question.toLowerCase();
|
|
1159
|
+
if (/\b(by|break\s*down|breakdown|drill|compare|versus|vs\.?|segment|cohort|top|bottom|best|worst|highest|lowest|least|fewest|rank|ranking|most|why|changed?|driver|anomal|exception)\b/.test(lower)) {
|
|
1160
|
+
return false;
|
|
1161
|
+
}
|
|
1162
|
+
if (isEntityQuestion(question))
|
|
1163
|
+
return false;
|
|
1164
|
+
const asksForValue = /\b(what\s+(?:is|was|were|are)|how\s+(?:much|many)|show|report|calculate|give\s+me|tell\s+me)\b/.test(lower);
|
|
1165
|
+
const metricLanguage = /\b(revenue|sales|arr|mrr|bookings|orders|customers|users|churn|retention|conversion|rate|count|total|points|goals|kpi|metric)\b/.test(lower);
|
|
1166
|
+
return asksForValue && metricLanguage;
|
|
1167
|
+
}
|
|
1135
1168
|
function hasCompatibleMetadataRankingDirection(question, object) {
|
|
1136
1169
|
const questionDirection = rankingDirection(question);
|
|
1137
1170
|
if (!questionDirection)
|
|
@@ -1231,7 +1264,7 @@ function isTimeLikeColumn(name) {
|
|
|
1231
1264
|
return /\b(date|time|day|week|month|quarter|year|season|period|created_at|updated_at)\b/i.test(name);
|
|
1232
1265
|
}
|
|
1233
1266
|
function selectedRows(value) {
|
|
1234
|
-
const root =
|
|
1267
|
+
const root = value && typeof value === 'object' ? value : null;
|
|
1235
1268
|
const selected = root?.selectedBlock && typeof root.selectedBlock === 'object' ? root.selectedBlock : root;
|
|
1236
1269
|
const candidates = [
|
|
1237
1270
|
selected?.resultSample,
|
|
@@ -1313,6 +1346,7 @@ function evidenceReasonForObject(object) {
|
|
|
1313
1346
|
}
|
|
1314
1347
|
function buildAllowedSqlContext(objects, edges) {
|
|
1315
1348
|
const byRelation = new Map();
|
|
1349
|
+
const objectsByKey = new Map(objects.map((object) => [object.objectKey, object]));
|
|
1316
1350
|
const addRelation = (relation) => {
|
|
1317
1351
|
const key = normalizeRelationKey(relation.relation);
|
|
1318
1352
|
if (!key)
|
|
@@ -1330,9 +1364,15 @@ function buildAllowedSqlContext(objects, edges) {
|
|
|
1330
1364
|
});
|
|
1331
1365
|
};
|
|
1332
1366
|
for (const object of objects) {
|
|
1367
|
+
if (object.objectType === 'warehouse_table' && !warehouseTableHasTrustedReference(object, objectsByKey)) {
|
|
1368
|
+
continue;
|
|
1369
|
+
}
|
|
1333
1370
|
const relation = metadataRelationFromObject(object);
|
|
1334
1371
|
if (relation)
|
|
1335
1372
|
addRelation(relation);
|
|
1373
|
+
if (object.objectType === 'dql_block' && !isCertifiedMetadataObject(object)) {
|
|
1374
|
+
continue;
|
|
1375
|
+
}
|
|
1336
1376
|
for (const table of metadataStringArray(object.payload?.tableDependencies)) {
|
|
1337
1377
|
addRelation({
|
|
1338
1378
|
relation: table,
|
|
@@ -1352,7 +1392,6 @@ function buildAllowedSqlContext(objects, edges) {
|
|
|
1352
1392
|
});
|
|
1353
1393
|
}
|
|
1354
1394
|
}
|
|
1355
|
-
const objectsByKey = new Map(objects.map((object) => [object.objectKey, object]));
|
|
1356
1395
|
for (const edge of edges) {
|
|
1357
1396
|
if (edge.edgeType !== 'maps_to_dbt_model' && edge.edgeType !== 'uses_dbt_model')
|
|
1358
1397
|
continue;
|
|
@@ -1366,7 +1405,10 @@ function buildAllowedSqlContext(objects, edges) {
|
|
|
1366
1405
|
return {
|
|
1367
1406
|
relations: Array.from(byRelation.values()).sort((a, b) => a.relation.localeCompare(b.relation)),
|
|
1368
1407
|
sourceBlockSql: objects
|
|
1369
|
-
.filter((object) => object.objectType === 'dql_block' &&
|
|
1408
|
+
.filter((object) => object.objectType === 'dql_block' &&
|
|
1409
|
+
isCertifiedMetadataObject(object) &&
|
|
1410
|
+
typeof object.payload?.sql === 'string' &&
|
|
1411
|
+
object.payload.sql.trim())
|
|
1370
1412
|
.slice(0, 8)
|
|
1371
1413
|
.map((object) => ({
|
|
1372
1414
|
objectKey: object.objectKey,
|
|
@@ -1376,6 +1418,18 @@ function buildAllowedSqlContext(objects, edges) {
|
|
|
1376
1418
|
})),
|
|
1377
1419
|
};
|
|
1378
1420
|
}
|
|
1421
|
+
function warehouseTableHasTrustedReference(object, objectsByKey) {
|
|
1422
|
+
const refs = metadataStringArray(object.payload?.referencedBy);
|
|
1423
|
+
if (refs.length === 0)
|
|
1424
|
+
return true;
|
|
1425
|
+
if (refs.some((ref) => !ref.startsWith('block:')))
|
|
1426
|
+
return true;
|
|
1427
|
+
return refs.some((ref) => {
|
|
1428
|
+
const blockName = ref.slice('block:'.length);
|
|
1429
|
+
const block = objectsByKey.get(`dql:block:${blockName}`);
|
|
1430
|
+
return block ? isCertifiedMetadataObject(block) : false;
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1379
1433
|
function metadataRelationFromObject(object) {
|
|
1380
1434
|
if (object.objectType === 'dbt_column' || object.objectType === 'runtime_column') {
|
|
1381
1435
|
const relation = metadataPayloadString(object, 'relation');
|
|
@@ -1444,7 +1498,7 @@ function runtimeSchemaObjects(snapshot) {
|
|
|
1444
1498
|
});
|
|
1445
1499
|
}
|
|
1446
1500
|
function selectedContextObjects(value) {
|
|
1447
|
-
const root =
|
|
1501
|
+
const root = value && typeof value === 'object' ? value : null;
|
|
1448
1502
|
if (!root)
|
|
1449
1503
|
return [];
|
|
1450
1504
|
const selected = root.selectedBlock && typeof root.selectedBlock === 'object' ? root.selectedBlock : root;
|
|
@@ -1468,60 +1522,65 @@ function selectedContextObjects(value) {
|
|
|
1468
1522
|
}),
|
|
1469
1523
|
}];
|
|
1470
1524
|
}
|
|
1471
|
-
function
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
const
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
const name = blockId.replace(/^block:/i, '').trim();
|
|
1478
|
-
if (name)
|
|
1479
|
-
keys.add(`dql:block:${name}`);
|
|
1480
|
-
};
|
|
1481
|
-
if (root) {
|
|
1482
|
-
const selected = root.selectedBlock && typeof root.selectedBlock === 'object' ? root.selectedBlock : root;
|
|
1483
|
-
addBlock(selected.blockId);
|
|
1484
|
-
addBlock(root.sourceBlockId);
|
|
1485
|
-
addBlock(root.blockId);
|
|
1486
|
-
}
|
|
1487
|
-
for (const object of selectedObjects) {
|
|
1488
|
-
addBlock(object.payload?.blockId);
|
|
1489
|
-
}
|
|
1490
|
-
return Array.from(keys);
|
|
1491
|
-
}
|
|
1492
|
-
function normalizeSelectedContextRoot(value) {
|
|
1493
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
1525
|
+
function normalizeFollowUpContext(value) {
|
|
1526
|
+
if (!value || typeof value !== 'object')
|
|
1527
|
+
return null;
|
|
1528
|
+
const record = value;
|
|
1529
|
+
const kind = record.kind === 'drilldown' ? 'drilldown' : record.kind === 'generic' ? 'generic' : null;
|
|
1530
|
+
if (!kind)
|
|
1494
1531
|
return null;
|
|
1495
|
-
const original = value;
|
|
1496
|
-
const fromSql = typeof original.sql === 'string' ? parseJsonObject(original.sql) : null;
|
|
1497
|
-
const root = fromSql ?? original;
|
|
1498
|
-
const nested = root.context && typeof root.context === 'object' && !Array.isArray(root.context)
|
|
1499
|
-
? root.context
|
|
1500
|
-
: null;
|
|
1501
|
-
if (!nested)
|
|
1502
|
-
return root;
|
|
1503
1532
|
return {
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1533
|
+
kind,
|
|
1534
|
+
sourceBlockName: stringValue(record.sourceBlockName),
|
|
1535
|
+
sourceQuestion: stringValue(record.sourceQuestion),
|
|
1536
|
+
sourceAnswer: stringValue(record.sourceAnswer),
|
|
1537
|
+
filters: metadataStringArray(record.filters),
|
|
1538
|
+
dimensions: metadataStringArray(record.dimensions),
|
|
1510
1539
|
};
|
|
1511
1540
|
}
|
|
1512
|
-
function
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1541
|
+
function followUpSourceObjectKeys(followUp) {
|
|
1542
|
+
if (!followUp?.sourceBlockName)
|
|
1543
|
+
return [];
|
|
1544
|
+
return [`dql:block:${followUp.sourceBlockName}`];
|
|
1545
|
+
}
|
|
1546
|
+
function followUpContextObjects(followUp) {
|
|
1547
|
+
if (!followUp)
|
|
1548
|
+
return [];
|
|
1549
|
+
const text = [
|
|
1550
|
+
followUp.kind,
|
|
1551
|
+
followUp.sourceBlockName ?? '',
|
|
1552
|
+
followUp.sourceQuestion ?? '',
|
|
1553
|
+
followUp.sourceAnswer ?? '',
|
|
1554
|
+
...(followUp.filters ?? []),
|
|
1555
|
+
...(followUp.dimensions ?? []),
|
|
1556
|
+
].join(' ');
|
|
1557
|
+
return [{
|
|
1558
|
+
objectKey: `selected:followup:${sha256(stableStringify(followUp)).slice(0, 16)}`,
|
|
1559
|
+
objectType: 'selected_context',
|
|
1560
|
+
name: followUp.kind === 'drilldown' ? 'Follow-up drilldown request' : 'Follow-up request',
|
|
1561
|
+
description: text.trim() || undefined,
|
|
1562
|
+
status: 'transient_context',
|
|
1563
|
+
sourceSystem: 'agent follow-up context',
|
|
1564
|
+
payload: compactObject({
|
|
1565
|
+
kind: followUp.kind,
|
|
1566
|
+
sourceBlockName: followUp.sourceBlockName,
|
|
1567
|
+
sourceQuestion: followUp.sourceQuestion,
|
|
1568
|
+
sourceAnswer: followUp.sourceAnswer,
|
|
1569
|
+
filters: followUp.filters,
|
|
1570
|
+
dimensions: followUp.dimensions,
|
|
1571
|
+
}),
|
|
1572
|
+
}];
|
|
1573
|
+
}
|
|
1574
|
+
function buildFollowUpSearchQuery(question, followUp) {
|
|
1575
|
+
if (!followUp)
|
|
1576
|
+
return question;
|
|
1577
|
+
return [
|
|
1578
|
+
question,
|
|
1579
|
+
followUp.sourceBlockName ?? '',
|
|
1580
|
+
followUp.sourceQuestion ?? '',
|
|
1581
|
+
...(followUp.filters ?? []),
|
|
1582
|
+
...(followUp.dimensions ?? []),
|
|
1583
|
+
].filter(Boolean).join(' ');
|
|
1525
1584
|
}
|
|
1526
1585
|
function normalizeRuntimeSchemaTables(tables) {
|
|
1527
1586
|
const byRelation = new Map();
|
|
@@ -1633,7 +1692,6 @@ function mergeObject(a, b) {
|
|
|
1633
1692
|
return {
|
|
1634
1693
|
...a,
|
|
1635
1694
|
...b,
|
|
1636
|
-
score: Math.max(a.score ?? 0, b.score ?? 0) || undefined,
|
|
1637
1695
|
description: b.description || a.description,
|
|
1638
1696
|
payload: compactObject({ ...(a.payload ?? {}), ...(b.payload ?? {}) }),
|
|
1639
1697
|
};
|