@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.
@@ -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 selectedReferencedObjects = catalog
104
- .getObjectsByKeys(selectedContextReferenceKeys(request.selectedContext, selectedObjects))
105
- .map((object) => ({ ...object, score: Math.max(object.score ?? 0, 20) }));
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: request.question,
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, ...selectedReferencedObjects, ...searchRows, ...runtimeObjects, ...selectedObjects]
115
- : [...selectedReferencedObjects, ...searchRows, ...runtimeObjects, ...selectedObjects]),
116
- question: request.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 ?? selectedReferencedObjects[0]?.objectKey ?? selected[0]?.objectKey ?? null;
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([...selectedReferencedObjects, ...selected, ...graphObjects, ...runtimeObjects, ...selectedObjects]),
126
- question: request.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: request.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 = normalizeSelectedContextRoot(value);
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' && typeof object.payload?.sql === 'string' && object.payload.sql.trim())
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 = normalizeSelectedContextRoot(value);
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 selectedContextReferenceKeys(value, selectedObjects) {
1472
- const root = normalizeSelectedContextRoot(value);
1473
- const keys = new Set();
1474
- const addBlock = (blockId) => {
1475
- if (typeof blockId !== 'string')
1476
- return;
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
- ...root,
1505
- ...nested,
1506
- requestTitle: root.title,
1507
- requestIntent: root.intent,
1508
- requestMode: root.mode,
1509
- context: nested,
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 parseJsonObject(raw) {
1513
- const trimmed = raw.trim();
1514
- if (!trimmed || (!trimmed.startsWith('{') && !trimmed.startsWith('[')))
1515
- return null;
1516
- try {
1517
- const parsed = JSON.parse(trimmed);
1518
- return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
1519
- ? parsed
1520
- : null;
1521
- }
1522
- catch {
1523
- return null;
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
  };