@duckcodeailabs/dql-agent 1.13.4 → 1.14.0
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/agent-run-engine.d.ts +87 -7
- package/dist/agent-run-engine.d.ts.map +1 -1
- package/dist/agent-run-engine.js +559 -40
- package/dist/agent-run-engine.js.map +1 -1
- package/dist/agent-run-store.d.ts.map +1 -1
- package/dist/agent-run-store.js +42 -25
- package/dist/agent-run-store.js.map +1 -1
- package/dist/agentic/tool-loop.d.ts.map +1 -1
- package/dist/agentic/tool-loop.js +100 -32
- package/dist/agentic/tool-loop.js.map +1 -1
- package/dist/aggregation-safety-proof.d.ts +41 -0
- package/dist/aggregation-safety-proof.d.ts.map +1 -0
- package/dist/aggregation-safety-proof.js +511 -0
- package/dist/aggregation-safety-proof.js.map +1 -0
- package/dist/analytical-compatibility.d.ts.map +1 -1
- package/dist/analytical-compatibility.js +42 -25
- package/dist/analytical-compatibility.js.map +1 -1
- package/dist/analytical-execution-graph.d.ts +4 -21
- package/dist/analytical-execution-graph.d.ts.map +1 -1
- package/dist/analytical-execution-graph.js +27 -0
- package/dist/analytical-execution-graph.js.map +1 -1
- package/dist/analytical-frame.d.ts +12 -1
- package/dist/analytical-frame.d.ts.map +1 -1
- package/dist/analytical-frame.js +102 -2
- package/dist/analytical-frame.js.map +1 -1
- package/dist/analytical-orchestration.d.ts +256 -0
- package/dist/analytical-orchestration.d.ts.map +1 -0
- package/dist/analytical-orchestration.js +419 -0
- package/dist/analytical-orchestration.js.map +1 -0
- package/dist/analytical-result-facts.d.ts +54 -1
- package/dist/analytical-result-facts.d.ts.map +1 -1
- package/dist/analytical-result-facts.js +183 -0
- package/dist/analytical-result-facts.js.map +1 -1
- package/dist/answer-loop.d.ts +44 -3
- package/dist/answer-loop.d.ts.map +1 -1
- package/dist/answer-loop.js +771 -38
- package/dist/answer-loop.js.map +1 -1
- package/dist/cascade/budgets.js +1 -1
- package/dist/cascade/route-policy.d.ts +1 -1
- package/dist/cascade/route-policy.d.ts.map +1 -1
- package/dist/cascade/route-policy.js +2 -0
- package/dist/cascade/route-policy.js.map +1 -1
- package/dist/conversation/snapshot.d.ts.map +1 -1
- package/dist/conversation/snapshot.js +3 -0
- package/dist/conversation/snapshot.js.map +1 -1
- package/dist/conversation/turn-trust.d.ts.map +1 -1
- package/dist/conversation/turn-trust.js +6 -0
- package/dist/conversation/turn-trust.js.map +1 -1
- package/dist/generated-analytical-proposal.d.ts +37 -0
- package/dist/generated-analytical-proposal.d.ts.map +1 -0
- package/dist/generated-analytical-proposal.js +219 -0
- package/dist/generated-analytical-proposal.js.map +1 -0
- package/dist/index.d.ts +11 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/intent-controller.d.ts +8 -1
- package/dist/intent-controller.d.ts.map +1 -1
- package/dist/intent-controller.js.map +1 -1
- package/dist/kg/build.js +102 -6
- package/dist/kg/build.js.map +1 -1
- package/dist/meaning-resolution.d.ts +30 -0
- package/dist/meaning-resolution.d.ts.map +1 -1
- package/dist/meaning-resolution.js +61 -8
- package/dist/meaning-resolution.js.map +1 -1
- package/dist/metadata/analysis-planner.d.ts.map +1 -1
- package/dist/metadata/analysis-planner.js +46 -2
- package/dist/metadata/analysis-planner.js.map +1 -1
- package/dist/metadata/block-fit.d.ts.map +1 -1
- package/dist/metadata/block-fit.js +55 -2
- package/dist/metadata/block-fit.js.map +1 -1
- package/dist/metadata/catalog.d.ts +24 -0
- package/dist/metadata/catalog.d.ts.map +1 -1
- package/dist/metadata/catalog.js +227 -42
- package/dist/metadata/catalog.js.map +1 -1
- package/dist/metadata/meaning-evidence.d.ts.map +1 -1
- package/dist/metadata/meaning-evidence.js +408 -36
- package/dist/metadata/meaning-evidence.js.map +1 -1
- package/dist/metadata/sql-context-validation.d.ts +3 -0
- package/dist/metadata/sql-context-validation.d.ts.map +1 -1
- package/dist/metadata/sql-context-validation.js +16 -5
- package/dist/metadata/sql-context-validation.js.map +1 -1
- package/dist/plan-execution-adapter.d.ts +9 -1
- package/dist/plan-execution-adapter.d.ts.map +1 -1
- package/dist/plan-execution-adapter.js +81 -32
- package/dist/plan-execution-adapter.js.map +1 -1
- package/dist/provider-egress.d.ts +122 -0
- package/dist/provider-egress.d.ts.map +1 -0
- package/dist/provider-egress.js +504 -0
- package/dist/provider-egress.js.map +1 -0
- package/dist/providers/claude.d.ts.map +1 -1
- package/dist/providers/claude.js +76 -33
- package/dist/providers/claude.js.map +1 -1
- package/dist/providers/dispatch.d.ts +15 -0
- package/dist/providers/dispatch.d.ts.map +1 -0
- package/dist/providers/dispatch.js +25 -0
- package/dist/providers/dispatch.js.map +1 -0
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +16 -8
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/index.d.ts +2 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +1 -0
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/ollama.d.ts.map +1 -1
- package/dist/providers/ollama.js +29 -10
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +92 -36
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/types.d.ts +43 -0
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js.map +1 -1
- package/dist/relationship-proof.d.ts +19 -0
- package/dist/relationship-proof.d.ts.map +1 -0
- package/dist/relationship-proof.js +75 -0
- package/dist/relationship-proof.js.map +1 -0
- package/dist/research-loop.d.ts.map +1 -1
- package/dist/research-loop.js +17 -11
- package/dist/research-loop.js.map +1 -1
- package/dist/resolved-analytical-plan.d.ts +25 -3
- package/dist/resolved-analytical-plan.d.ts.map +1 -1
- package/dist/resolved-analytical-plan.js +460 -31
- package/dist/resolved-analytical-plan.js.map +1 -1
- package/dist/router.d.ts +38 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +1060 -53
- package/dist/router.js.map +1 -1
- package/dist/semantic-identities.d.ts +18 -0
- package/dist/semantic-identities.d.ts.map +1 -0
- package/dist/semantic-identities.js +60 -0
- package/dist/semantic-identities.js.map +1 -0
- package/dist/semantic-proof-authority.d.ts +29 -0
- package/dist/semantic-proof-authority.d.ts.map +1 -0
- package/dist/semantic-proof-authority.js +34 -0
- package/dist/semantic-proof-authority.js.map +1 -0
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meaning-evidence.d.ts","sourceRoot":"","sources":["../../src/metadata/meaning-evidence.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"meaning-evidence.d.ts","sourceRoot":"","sources":["../../src/metadata/meaning-evidence.ts"],"names":[],"mappings":"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"}
|
|
@@ -40,6 +40,10 @@ export function buildMeaningEvidencePackage(question, questionPlan, ranked) {
|
|
|
40
40
|
eligible.sort((left, right) => right.relevanceScore - left.relevanceScore
|
|
41
41
|
|| left.item.rank - right.item.rank
|
|
42
42
|
|| left.item.row.objectKey.localeCompare(right.item.row.objectKey));
|
|
43
|
+
const requiredSemanticMetricKey = questionPlan.requestedShape.measures.length > 0
|
|
44
|
+
? eligible.find((candidate) => candidate.evidenceClass === 'semantic'
|
|
45
|
+
&& candidate.item.row.objectType === 'semantic_metric')?.item.row.objectKey
|
|
46
|
+
: undefined;
|
|
43
47
|
const selectedByClass = {
|
|
44
48
|
certified: [], semantic: [], sql: [],
|
|
45
49
|
};
|
|
@@ -47,6 +51,15 @@ export function buildMeaningEvidencePackage(question, questionPlan, ranked) {
|
|
|
47
51
|
for (const candidate of eligible) {
|
|
48
52
|
const lane = selectedByClass[candidate.evidenceClass];
|
|
49
53
|
if (candidate.evidenceClass === 'semantic') {
|
|
54
|
+
// Dimensions and metrics share the semantic trust class. Preserve one
|
|
55
|
+
// retrieved metric for a measure-bearing question before member matches
|
|
56
|
+
// consume the bounded lane; deterministic compatibility still decides
|
|
57
|
+
// whether that metric is executable.
|
|
58
|
+
if (requiredSemanticMetricKey
|
|
59
|
+
&& candidate.item.row.objectKey !== requiredSemanticMetricKey
|
|
60
|
+
&& lane.length >= MAX_CANDIDATES_PER_CLASS - 1
|
|
61
|
+
&& !lane.some((selectedCandidate) => selectedCandidate.item.row.objectKey === requiredSemanticMetricKey))
|
|
62
|
+
continue;
|
|
50
63
|
const definitionKey = normalizeText(candidate.item.row.description ?? '');
|
|
51
64
|
const meaningKey = definitionKey ? `${candidate.item.row.domain ?? ''}|${definitionKey}` : '';
|
|
52
65
|
const existingIndex = meaningKey ? semanticMeaningIndexes.get(meaningKey) : undefined;
|
|
@@ -130,8 +143,28 @@ function attachRelationshipEvidence(candidates, contextObjects) {
|
|
|
130
143
|
if (identitiesByEntity.size === 0)
|
|
131
144
|
return;
|
|
132
145
|
for (const candidate of candidates) {
|
|
146
|
+
for (const dimension of candidate.analyticalCapability?.dimensions ?? []) {
|
|
147
|
+
const pathRelationships = new Set(dimension.relationshipPathIds ?? []);
|
|
148
|
+
for (const reference of [dimension.entityId, ...(dimension.nativeGroupingPath ?? [])]) {
|
|
149
|
+
for (const alias of entityAliasesForRelationshipMatch(reference)) {
|
|
150
|
+
for (const identity of identitiesByEntity.get(alias) ?? [])
|
|
151
|
+
pathRelationships.add(identity);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (pathRelationships.size > 0)
|
|
155
|
+
dimension.relationshipPathIds = [...pathRelationships].sort();
|
|
156
|
+
}
|
|
133
157
|
const touched = new Set();
|
|
134
|
-
|
|
158
|
+
const capabilityPathEntities = candidate.analyticalCapability?.dimensions.flatMap((dimension) => [
|
|
159
|
+
dimension.entityId,
|
|
160
|
+
...(dimension.nativeGroupingPath ?? []),
|
|
161
|
+
]) ?? [];
|
|
162
|
+
for (const reference of [
|
|
163
|
+
candidate.primaryEntity,
|
|
164
|
+
candidate.analyticalCapability?.primaryEntityId,
|
|
165
|
+
...capabilityPathEntities,
|
|
166
|
+
...(candidate.sourceObjects ?? []),
|
|
167
|
+
]) {
|
|
135
168
|
if (!reference)
|
|
136
169
|
continue;
|
|
137
170
|
for (const alias of entityAliasesForRelationshipMatch(reference)) {
|
|
@@ -150,10 +183,74 @@ function entityAliasesForRelationshipMatch(reference) {
|
|
|
150
183
|
}
|
|
151
184
|
export function toAgentRetrievalEvidence(evidence, questionPlan, options = {}) {
|
|
152
185
|
const maxRelevance = Math.max(1, ...evidence.candidates.map((candidate) => candidate.relevanceScore));
|
|
153
|
-
const candidates = evidence.candidates.map((candidate) => (
|
|
186
|
+
const candidates = evidence.candidates.map((candidate) => agentCandidateFromMeaning(candidate, maxRelevance));
|
|
187
|
+
const trustedBindings = trustedMemberBindingEvidence(questionPlan, candidates, options.contextObjects ?? []);
|
|
188
|
+
for (const member of trustedBindings.candidates) {
|
|
189
|
+
if (!candidates.some((candidate) => candidate.id === member.id))
|
|
190
|
+
candidates.push(member);
|
|
191
|
+
}
|
|
192
|
+
const clarificationCandidates = supplementalClarificationEvidence(questionPlan, options.contextObjects ?? [], candidates);
|
|
193
|
+
attachRelationshipEvidence(candidates, options.contextObjects ?? []);
|
|
194
|
+
attachRelationshipEvidence(clarificationCandidates, options.contextObjects ?? []);
|
|
195
|
+
// A member the reader named that survived NO grounding lane. Live value
|
|
196
|
+
// lookup is opt-in per project (`agent.runtimeValueGrounding`), so with it
|
|
197
|
+
// disabled "What customer type is Wesley Jenkins?" produced zero grounded
|
|
198
|
+
// filters and the run quietly returned every customer instead of saying it
|
|
199
|
+
// could not bind him.
|
|
200
|
+
const groundedFilters = [
|
|
201
|
+
...trustedBindings.filters,
|
|
202
|
+
...groundedMemberFilters(questionPlan, candidates, options.contextObjects ?? []),
|
|
203
|
+
].filter((filter, index, all) => all.findIndex((other) => other.field === filter.field && other.value === filter.value) === index);
|
|
204
|
+
const groundedValues = new Set(groundedFilters.map((filter) => normalizeText(String(filter.value))));
|
|
205
|
+
const unboundCandidates = uniqueStrings((questionPlan.requestedShape.filters ?? [])
|
|
206
|
+
.filter((term) => term.trim().length > 0 && !groundedValues.has(normalizeText(term))));
|
|
207
|
+
// The planner emits stemmed variants beside the literal ("Wesley Jenkins" and
|
|
208
|
+
// "wesley jenkin"). Naming both back to the reader reads like two different
|
|
209
|
+
// missing values, so keep only the longest form of each.
|
|
210
|
+
const unboundMemberTerms = unboundCandidates.filter((term) => {
|
|
211
|
+
const normalized = normalizeText(term);
|
|
212
|
+
return !unboundCandidates.some((other) => {
|
|
213
|
+
const otherNormalized = normalizeText(other);
|
|
214
|
+
return otherNormalized !== normalized
|
|
215
|
+
&& otherNormalized.length > normalized.length
|
|
216
|
+
&& otherNormalized.startsWith(normalized);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
return {
|
|
220
|
+
snapshotId: options.snapshotId,
|
|
221
|
+
sourceFingerprint: options.sourceFingerprint,
|
|
222
|
+
knowledgeLens: options.knowledgeLens,
|
|
223
|
+
analyticalPolicies: options.analyticalPolicies,
|
|
224
|
+
candidates,
|
|
225
|
+
...(clarificationCandidates.length > 0 ? { clarificationCandidates } : {}),
|
|
226
|
+
...(unboundMemberTerms.length > 0 ? { unboundMemberTerms } : {}),
|
|
227
|
+
parsedIntent: {
|
|
228
|
+
measures: questionPlan.requestedShape.measures,
|
|
229
|
+
dimensions: questionPlan.requestedShape.dimensions,
|
|
230
|
+
filters: groundedFilters,
|
|
231
|
+
...(analysisTimeGrain(questionPlan)
|
|
232
|
+
? { timeGrain: analysisTimeGrain(questionPlan) }
|
|
233
|
+
: {}),
|
|
234
|
+
...(questionPlan.requestedShape.rankingDirection
|
|
235
|
+
? { order: questionPlan.requestedShape.rankingDirection === 'top' ? 'desc' : 'asc' }
|
|
236
|
+
: {}),
|
|
237
|
+
...(questionPlan.requestedShape.topN ? { limit: questionPlan.requestedShape.topN.n } : {}),
|
|
238
|
+
},
|
|
239
|
+
diagnostics: {
|
|
240
|
+
searchedKinds: [...new Set([...candidates, ...clarificationCandidates].map((candidate) => candidate.kind))],
|
|
241
|
+
durationMs: options.durationMs,
|
|
242
|
+
truncated: options.truncated ?? false,
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function agentCandidateFromMeaning(candidate, maxRelevance = 1) {
|
|
247
|
+
return {
|
|
154
248
|
id: candidate.objectKey,
|
|
155
249
|
qualifiedId: candidate.qualifiedId,
|
|
156
250
|
kind: agentEvidenceKind(candidate),
|
|
251
|
+
...(semanticObjectType(candidate.objectType)
|
|
252
|
+
? { semanticObjectType: semanticObjectType(candidate.objectType) }
|
|
253
|
+
: {}),
|
|
157
254
|
trustTier: candidate.trustTier,
|
|
158
255
|
name: candidate.name,
|
|
159
256
|
aliases: candidate.aliases,
|
|
@@ -164,7 +261,7 @@ export function toAgentRetrievalEvidence(evidence, questionPlan, options = {}) {
|
|
|
164
261
|
domain: candidate.domain,
|
|
165
262
|
semanticModel: candidate.semanticModel,
|
|
166
263
|
primaryEntity: candidate.businessShape.entities[0],
|
|
167
|
-
dimensions: candidate.businessShape.dimensions,
|
|
264
|
+
dimensions: dimensionLookupAliases(candidate.businessShape.dimensions),
|
|
168
265
|
timeGrains: candidate.businessShape.timeGrains,
|
|
169
266
|
requiredParameters: candidate.businessShape.parameters,
|
|
170
267
|
sourceObjects: candidate.businessShape.sourceRelations,
|
|
@@ -177,34 +274,261 @@ export function toAgentRetrievalEvidence(evidence, questionPlan, options = {}) {
|
|
|
177
274
|
analyticalCapability: candidate.analyticalCapability,
|
|
178
275
|
eligible: true,
|
|
179
276
|
exactMatch: candidate.relevanceReasons.includes("exact name or alias"),
|
|
180
|
-
}));
|
|
181
|
-
attachRelationshipEvidence(candidates, options.contextObjects ?? []);
|
|
182
|
-
const groundedFilters = groundedMemberFilters(questionPlan, candidates, options.contextObjects ?? []);
|
|
183
|
-
return {
|
|
184
|
-
snapshotId: options.snapshotId,
|
|
185
|
-
sourceFingerprint: options.sourceFingerprint,
|
|
186
|
-
knowledgeLens: options.knowledgeLens,
|
|
187
|
-
analyticalPolicies: options.analyticalPolicies,
|
|
188
|
-
candidates,
|
|
189
|
-
parsedIntent: {
|
|
190
|
-
measures: questionPlan.requestedShape.measures,
|
|
191
|
-
dimensions: questionPlan.requestedShape.dimensions,
|
|
192
|
-
filters: groundedFilters,
|
|
193
|
-
...(analysisTimeGrain(questionPlan)
|
|
194
|
-
? { timeGrain: analysisTimeGrain(questionPlan) }
|
|
195
|
-
: {}),
|
|
196
|
-
...(questionPlan.requestedShape.rankingDirection
|
|
197
|
-
? { order: questionPlan.requestedShape.rankingDirection === 'top' ? 'desc' : 'asc' }
|
|
198
|
-
: {}),
|
|
199
|
-
...(questionPlan.requestedShape.topN ? { limit: questionPlan.requestedShape.topN.n } : {}),
|
|
200
|
-
},
|
|
201
|
-
diagnostics: {
|
|
202
|
-
searchedKinds: [...new Set(candidates.map((candidate) => candidate.kind))],
|
|
203
|
-
durationMs: options.durationMs,
|
|
204
|
-
truncated: options.truncated ?? false,
|
|
205
|
-
},
|
|
206
277
|
};
|
|
207
278
|
}
|
|
279
|
+
function trustedMemberBindingEvidence(plan, candidates, objects) {
|
|
280
|
+
const dimensions = new Map();
|
|
281
|
+
for (const object of objects) {
|
|
282
|
+
if (object.objectType !== 'semantic_dimension')
|
|
283
|
+
continue;
|
|
284
|
+
const id = firstString(object.payload?.qualifiedId, object.fullName);
|
|
285
|
+
if (!id)
|
|
286
|
+
continue;
|
|
287
|
+
dimensions.set(normalizeText(object.name), { id, definition: object.description });
|
|
288
|
+
dimensions.set(normalizeDimensionRole(object.name), { id, definition: object.description });
|
|
289
|
+
}
|
|
290
|
+
for (const candidate of candidates) {
|
|
291
|
+
const capability = normalizeMetricCapabilityContract(candidate.analyticalCapability);
|
|
292
|
+
for (const dimension of capability?.dimensions ?? []) {
|
|
293
|
+
const value = { id: dimension.dimensionId, entityId: dimension.entityId };
|
|
294
|
+
dimensions.set(normalizeText(dimension.dimensionId), value);
|
|
295
|
+
dimensions.set(normalizeDimensionRole(dimension.dimensionId), value);
|
|
296
|
+
}
|
|
297
|
+
for (const dimensionId of candidate.dimensions ?? []) {
|
|
298
|
+
if (!/[:./]/.test(dimensionId))
|
|
299
|
+
continue;
|
|
300
|
+
const value = { id: dimensionId, entityId: normalizeDimensionRole(dimensionId) };
|
|
301
|
+
dimensions.set(normalizeText(dimensionId), value);
|
|
302
|
+
dimensions.set(normalizeDimensionRole(dimensionId), value);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const cards = [];
|
|
306
|
+
const filters = [];
|
|
307
|
+
for (const binding of plan.requestedShape.memberBindings ?? []) {
|
|
308
|
+
if (binding.source !== 'prior_result' && binding.source !== 'clarification')
|
|
309
|
+
continue;
|
|
310
|
+
// A deictic binding used to be discarded outright, which threw away the
|
|
311
|
+
// referent of every "this customer" follow-up and left the turn ungrounded.
|
|
312
|
+
// Cardinality is what matters: exactly one candidate is unambiguous and
|
|
313
|
+
// safe to bind; several must not be fanned into an OR'd filter, and are
|
|
314
|
+
// surfaced as a clarification instead.
|
|
315
|
+
if (binding.confidence === 'deictic' && binding.values.length !== 1)
|
|
316
|
+
continue;
|
|
317
|
+
const dimension = dimensions.get(normalizeText(binding.dimension))
|
|
318
|
+
?? dimensions.get(normalizeDimensionRole(binding.dimension));
|
|
319
|
+
if (!dimension)
|
|
320
|
+
continue;
|
|
321
|
+
for (const value of binding.values) {
|
|
322
|
+
filters.push({ field: dimension.id, value });
|
|
323
|
+
const id = `semantic:member:${encodeURIComponent(dimension.id)}:${encodeURIComponent(normalizeText(value))}`;
|
|
324
|
+
cards.push({
|
|
325
|
+
id,
|
|
326
|
+
qualifiedId: id,
|
|
327
|
+
kind: 'semantic_member',
|
|
328
|
+
trustTier: 'semantic',
|
|
329
|
+
name: value,
|
|
330
|
+
aliases: [value],
|
|
331
|
+
definition: `Trusted ${binding.source.replace('_', ' ')} member for ${dimension.id}.`,
|
|
332
|
+
primaryEntity: dimension.entityId,
|
|
333
|
+
dimensions: [dimension.id],
|
|
334
|
+
relevanceScore: 1,
|
|
335
|
+
matchReasons: [`typed ${binding.source} member binding`],
|
|
336
|
+
compatibility: 'compatible',
|
|
337
|
+
eligible: true,
|
|
338
|
+
exactMatch: true,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return { candidates: cards, filters };
|
|
343
|
+
}
|
|
344
|
+
function supplementalClarificationEvidence(plan, objects, existing) {
|
|
345
|
+
const MAX_SUPPLEMENTAL_CANDIDATES = 12;
|
|
346
|
+
const MAX_PER_REQUESTED_ROLE = 3;
|
|
347
|
+
const existingIds = new Set(existing.map((candidate) => candidate.id));
|
|
348
|
+
const requestedMeasures = expandedRequestedTerms(plan.requestedShape.measures, objects);
|
|
349
|
+
const requestedDimensions = expandedRequestedTerms(plan.requestedShape.dimensions, objects);
|
|
350
|
+
const objectCards = objects.flatMap((object) => {
|
|
351
|
+
if (existingIds.has(object.objectKey))
|
|
352
|
+
return [];
|
|
353
|
+
if (!['semantic_metric', 'semantic_measure', 'semantic_dimension'].includes(object.objectType))
|
|
354
|
+
return [];
|
|
355
|
+
const names = aliasesFor(object).map(normalizeText);
|
|
356
|
+
const role = normalizeDimensionRole(object.name);
|
|
357
|
+
const relevant = object.objectType === 'semantic_metric' || object.objectType === 'semantic_measure'
|
|
358
|
+
? requestedMeasures.some((term) => names.some((name) => phraseTermsMatch(term, name)))
|
|
359
|
+
: requestedDimensions.some((term) => names.some((name) => phraseTermsMatch(term, name))
|
|
360
|
+
|| (isGeographicRole(term) && isGeographicRole(role)));
|
|
361
|
+
if (!relevant)
|
|
362
|
+
return [];
|
|
363
|
+
const card = candidateCard(object, 'semantic', 1, 1, aliasesFor(object), ['requested clarification capability'], []);
|
|
364
|
+
return [agentCandidateFromMeaning(card)];
|
|
365
|
+
});
|
|
366
|
+
const declaredDimensionCards = existing.flatMap((candidate) => (candidate.dimensions ?? []).flatMap((dimensionId) => {
|
|
367
|
+
if (!/[:./]/.test(dimensionId) || existingIds.has(dimensionId))
|
|
368
|
+
return [];
|
|
369
|
+
const role = normalizeDimensionRole(dimensionId);
|
|
370
|
+
const relevant = requestedDimensions.some((term) => phraseTermsMatch(term, role) || (isGeographicRole(term) && isGeographicRole(role)));
|
|
371
|
+
if (!relevant)
|
|
372
|
+
return [];
|
|
373
|
+
return [{
|
|
374
|
+
id: dimensionId,
|
|
375
|
+
qualifiedId: dimensionId,
|
|
376
|
+
kind: 'semantic_member',
|
|
377
|
+
trustTier: 'semantic',
|
|
378
|
+
name: dimensionId.split(/[.:/]/).at(-1) ?? dimensionId,
|
|
379
|
+
aliases: [role],
|
|
380
|
+
definition: `Modeled ${role} dimension available through ${candidate.name}.`,
|
|
381
|
+
primaryEntity: candidate.primaryEntity,
|
|
382
|
+
dimensions: [dimensionId],
|
|
383
|
+
relevanceScore: candidate.relevanceScore,
|
|
384
|
+
matchReasons: ['requested clarification capability role'],
|
|
385
|
+
compatibility: 'partial',
|
|
386
|
+
eligible: true,
|
|
387
|
+
exactMatch: false,
|
|
388
|
+
}];
|
|
389
|
+
}));
|
|
390
|
+
// A bare ranking ("who are the top customers") names an ENTITY and no
|
|
391
|
+
// measure, so every lane above — each keyed on a REQUESTED measure or
|
|
392
|
+
// dimension term — returns nothing, and the clarification has no choices to
|
|
393
|
+
// offer. Surface the governed measures that can actually order that entity.
|
|
394
|
+
const rankingMeasureCards = (() => {
|
|
395
|
+
if (!plan.requestedShape.rankingDirection)
|
|
396
|
+
return [];
|
|
397
|
+
if (requestedMeasures.length > 0)
|
|
398
|
+
return [];
|
|
399
|
+
const entityTerms = requestedDimensions;
|
|
400
|
+
if (entityTerms.length === 0)
|
|
401
|
+
return [];
|
|
402
|
+
return objects.flatMap((object) => {
|
|
403
|
+
if (existingIds.has(object.objectKey))
|
|
404
|
+
return [];
|
|
405
|
+
if (!['semantic_metric', 'semantic_measure'].includes(object.objectType))
|
|
406
|
+
return [];
|
|
407
|
+
const card = agentCandidateFromMeaning(candidateCard(object, 'semantic', 1, 1, aliasesFor(object), ['ranking measure for the requested entity'], []));
|
|
408
|
+
// It must be reportable at the ranked entity's grain. `semanticModel`
|
|
409
|
+
// covers a measure that declares its model but no dimension list.
|
|
410
|
+
const grainTerms = [
|
|
411
|
+
card.primaryEntity ?? '',
|
|
412
|
+
card.semanticModel ?? '',
|
|
413
|
+
...(card.dimensions ?? []),
|
|
414
|
+
].map(normalizeText).filter(Boolean);
|
|
415
|
+
if (!entityTerms.some((term) => grainTerms.some((grain) => grainTermsMatch(term, grain))))
|
|
416
|
+
return [];
|
|
417
|
+
// A count of the ranked entity gives every row the same score.
|
|
418
|
+
const aggregation = normalizeText(card.aggregation ?? '');
|
|
419
|
+
const candidateNames = [card.name, ...(card.aliases ?? [])].map(normalizeText);
|
|
420
|
+
if (/count/.test(aggregation)
|
|
421
|
+
&& entityTerms.some((term) => candidateNames.some((name) => grainTermsMatch(term, name))))
|
|
422
|
+
return [];
|
|
423
|
+
// The registry emits a bare alias node beside the entity-qualified metric
|
|
424
|
+
// (`semantic:metric:revenue` next to `semantic:metric:order_item.revenue`).
|
|
425
|
+
// The alias carries no aggregation, grain, or prose — only a raw dbt
|
|
426
|
+
// unique_id as its description — so it can never be proven compatible and
|
|
427
|
+
// must not take a choice slot from the node that can.
|
|
428
|
+
if (!card.aggregation)
|
|
429
|
+
return [];
|
|
430
|
+
return [card];
|
|
431
|
+
}).sort((left, right) => right.relevanceScore - left.relevanceScore || left.id.localeCompare(right.id));
|
|
432
|
+
})();
|
|
433
|
+
const unique = [...objectCards, ...declaredDimensionCards, ...rankingMeasureCards]
|
|
434
|
+
.filter((candidate, index, all) => all.findIndex((other) => (other.qualifiedId ?? other.id) === (candidate.qualifiedId ?? candidate.id)) === index);
|
|
435
|
+
const roleLanes = [
|
|
436
|
+
...requestedDimensions.map((term) => ({ kind: 'dimension', term })),
|
|
437
|
+
...requestedMeasures.map((term) => ({ kind: 'metric', term })),
|
|
438
|
+
];
|
|
439
|
+
const selected = [];
|
|
440
|
+
// The ranking lane has no requested term to match on — that absence is the
|
|
441
|
+
// whole reason it exists — so it seeds the selection directly.
|
|
442
|
+
for (const candidate of rankingMeasureCards.slice(0, MAX_PER_REQUESTED_ROLE)) {
|
|
443
|
+
if (!selected.some((item) => (item.qualifiedId ?? item.id) === (candidate.qualifiedId ?? candidate.id))) {
|
|
444
|
+
selected.push(candidate);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
for (const lane of roleLanes) {
|
|
448
|
+
const matches = unique.filter((candidate) => {
|
|
449
|
+
const candidateTerms = [candidate.name, ...(candidate.aliases ?? [])].map(normalizeText);
|
|
450
|
+
if (lane.kind === 'metric') {
|
|
451
|
+
return candidate.kind === 'semantic_metric'
|
|
452
|
+
&& candidateTerms.some((term) => phraseTermsMatch(lane.term, term));
|
|
453
|
+
}
|
|
454
|
+
if (candidate.kind !== 'semantic_member')
|
|
455
|
+
return false;
|
|
456
|
+
return candidateTerms.some((term) => phraseTermsMatch(lane.term, term))
|
|
457
|
+
|| (isGeographicRole(lane.term) && candidateTerms.some(isGeographicRole));
|
|
458
|
+
}).sort((left, right) => right.relevanceScore - left.relevanceScore || left.id.localeCompare(right.id));
|
|
459
|
+
for (const candidate of matches.slice(0, MAX_PER_REQUESTED_ROLE)) {
|
|
460
|
+
if (!selected.some((item) => (item.qualifiedId ?? item.id) === (candidate.qualifiedId ?? candidate.id))) {
|
|
461
|
+
selected.push(candidate);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return selected.slice(0, MAX_SUPPLEMENTAL_CANDIDATES);
|
|
466
|
+
}
|
|
467
|
+
function expandedRequestedTerms(terms, objects) {
|
|
468
|
+
const vocabulary = new Map();
|
|
469
|
+
for (const object of objects) {
|
|
470
|
+
if (object.objectType !== 'dql_term')
|
|
471
|
+
continue;
|
|
472
|
+
const words = [object.name, ...stringArray(object.payload?.synonyms)].map(normalizeText).filter(Boolean);
|
|
473
|
+
for (const word of words) {
|
|
474
|
+
const peers = vocabulary.get(word) ?? new Set();
|
|
475
|
+
for (const peer of words)
|
|
476
|
+
peers.add(peer);
|
|
477
|
+
vocabulary.set(word, peers);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
const expanded = new Set();
|
|
481
|
+
for (const term of terms.map(normalizeText)) {
|
|
482
|
+
expanded.add(term);
|
|
483
|
+
const tokens = term.split(' ');
|
|
484
|
+
tokens.forEach((token, index) => {
|
|
485
|
+
for (const peer of vocabulary.get(token) ?? []) {
|
|
486
|
+
expanded.add(tokens.map((value, tokenIndex) => tokenIndex === index ? peer : value).join(' '));
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return [...expanded];
|
|
491
|
+
}
|
|
492
|
+
function phraseTermsMatch(left, right) {
|
|
493
|
+
return left === right || left.endsWith(` ${right}`) || right.endsWith(` ${left}`);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Grain comparison for the ranking-measure lane only.
|
|
497
|
+
*
|
|
498
|
+
* A measure frequently declares the MODEL it lives on ("customers") where the
|
|
499
|
+
* question names the ENTITY ("customer"), and a plain term match misses that by
|
|
500
|
+
* one character. This stays local to grain matching rather than loosening
|
|
501
|
+
* `phraseTermsMatch`, which decides identity elsewhere.
|
|
502
|
+
*/
|
|
503
|
+
function grainTermsMatch(entityTerm, grainTerm) {
|
|
504
|
+
if (phraseTermsMatch(entityTerm, grainTerm))
|
|
505
|
+
return true;
|
|
506
|
+
const singular = (value) => value.replace(/ies$/, 'y').replace(/s$/, '');
|
|
507
|
+
return phraseTermsMatch(singular(entityTerm), singular(grainTerm));
|
|
508
|
+
}
|
|
509
|
+
function normalizeDimensionRole(value) {
|
|
510
|
+
const leaf = value.split(/[.:/]/).at(-1) ?? value;
|
|
511
|
+
const normalized = normalizeText(leaf);
|
|
512
|
+
if (/^customer(?: name| id| key| label)?$/.test(normalized))
|
|
513
|
+
return 'customer';
|
|
514
|
+
if (/^location(?: name| id| key| label)?$/.test(normalized))
|
|
515
|
+
return 'location';
|
|
516
|
+
if (/^region(?: name| id| key| label)?$/.test(normalized))
|
|
517
|
+
return 'region';
|
|
518
|
+
if (/^product(?: name| id| key| label)?$/.test(normalized))
|
|
519
|
+
return 'product';
|
|
520
|
+
return normalized;
|
|
521
|
+
}
|
|
522
|
+
function dimensionLookupAliases(dimensions) {
|
|
523
|
+
return uniqueStrings(dimensions.flatMap((dimension) => {
|
|
524
|
+
const leaf = dimension.split(/[.:/]/).at(-1) ?? dimension;
|
|
525
|
+
const role = leaf.replace(/(?:_name|_id|_key|_label)$/i, '');
|
|
526
|
+
return role && role !== leaf ? [dimension, role] : [dimension];
|
|
527
|
+
}));
|
|
528
|
+
}
|
|
529
|
+
function isGeographicRole(value) {
|
|
530
|
+
return /^(?:region|location|geography|territory|market|site|store)$/.test(normalizeDimensionRole(value));
|
|
531
|
+
}
|
|
208
532
|
/**
|
|
209
533
|
* Bind current-turn values only when an authorized runtime-value observation
|
|
210
534
|
* and a semantic dimension's exact physical table/column identity agree. Text
|
|
@@ -303,16 +627,28 @@ function normalizeIdentifier(value) {
|
|
|
303
627
|
*/
|
|
304
628
|
export function applyContextPackCompatibility(evidence, pack, selectedEvidenceId) {
|
|
305
629
|
const certifiedFits = new Map(pack.retrievalDiagnostics.certifiedCandidateFits.map((fit) => [fit.objectKey, fit]));
|
|
306
|
-
const semanticEvidence = new Set(pack.routeDecision.selectedEvidence
|
|
307
|
-
.filter((item) => item.role === 'semantic_metric')
|
|
308
|
-
.map((item) => item.objectKey));
|
|
309
630
|
return {
|
|
310
631
|
...evidence,
|
|
311
632
|
candidates: evidence.candidates.map((candidate) => {
|
|
312
633
|
if (candidate.kind === 'certified_block') {
|
|
313
634
|
const fit = certifiedFits.get(candidate.id);
|
|
635
|
+
const unrequestedStaticScope = fit?.fit.reasons.some((reason) => reason.startsWith('certified static scope is not requested:')) ?? false;
|
|
636
|
+
const exactAuthoredExample = fit?.action === 'certified_answer'
|
|
637
|
+
&& pack.routeDecision.exactObjectKey === candidate.id
|
|
638
|
+
&& fit.fit.reasons.includes('question matches a certified example');
|
|
314
639
|
return {
|
|
315
640
|
...candidate,
|
|
641
|
+
// Lexical equality to a block name/alias is useful retrieval signal,
|
|
642
|
+
// but it is not exact analytical authority. Only the selected block's
|
|
643
|
+
// authored exact-example fit may grant the router shortcut.
|
|
644
|
+
exactMatch: exactAuthoredExample,
|
|
645
|
+
...(exactAuthoredExample
|
|
646
|
+
? { analyticalFitClass: 'exact' }
|
|
647
|
+
: { analyticalFitClass: undefined }),
|
|
648
|
+
// A statically scoped certified artifact is not an alternative for a
|
|
649
|
+
// broader question. Exclude it before both meaning resolution and
|
|
650
|
+
// identifier-bound clarification; later fit rejection is too late.
|
|
651
|
+
eligible: unrequestedStaticScope ? false : candidate.eligible,
|
|
316
652
|
compatibility: fit?.action === 'certified_answer'
|
|
317
653
|
? 'compatible'
|
|
318
654
|
: fit?.action === 'rejected_for_fit'
|
|
@@ -321,7 +657,7 @@ export function applyContextPackCompatibility(evidence, pack, selectedEvidenceId
|
|
|
321
657
|
};
|
|
322
658
|
}
|
|
323
659
|
if ((candidate.kind === 'semantic_metric' || candidate.kind === 'semantic_member')
|
|
324
|
-
&& (
|
|
660
|
+
&& (Boolean(candidate.analyticalCapability) || selectedEvidenceId === candidate.id)
|
|
325
661
|
&& (selectedEvidenceId === candidate.id
|
|
326
662
|
|| (pack.routeDecision.route !== 'clarify' && pack.routeDecision.route !== 'conflict'))) {
|
|
327
663
|
const requestedDimensions = pack.questionPlan.requestedShape.dimensions.map((dimension) => normalizeText(dimension));
|
|
@@ -330,7 +666,10 @@ export function applyContextPackCompatibility(evidence, pack, selectedEvidenceId
|
|
|
330
666
|
const requestedTimeGrain = analysisTimeGrain(pack.questionPlan);
|
|
331
667
|
const availableTimeGrains = (candidate.timeGrains ?? []).map((grain) => grain.toLowerCase());
|
|
332
668
|
const timeGrainFits = !requestedTimeGrain || availableTimeGrains.includes(requestedTimeGrain);
|
|
333
|
-
|
|
669
|
+
const requestedMeasures = pack.questionPlan.requestedShape.measures.map((measure) => normalizeText(measure));
|
|
670
|
+
const metricPhrases = [candidate.name, ...(candidate.aliases ?? [])].map((value) => normalizeText(value));
|
|
671
|
+
const measuresFit = requestedMeasures.length === 0 || requestedMeasures.every((measure) => metricPhrases.some((phrase) => phrase === measure || phrase.includes(measure)));
|
|
672
|
+
return { ...candidate, compatibility: dimensionsFit && timeGrainFits && measuresFit ? 'compatible' : 'partial' };
|
|
334
673
|
}
|
|
335
674
|
if (candidate.trustTier === 'governed_sql')
|
|
336
675
|
return { ...candidate, compatibility: 'partial' };
|
|
@@ -450,6 +789,21 @@ function agentEvidenceKind(candidate) {
|
|
|
450
789
|
return 'sql_column';
|
|
451
790
|
return 'sql_table';
|
|
452
791
|
}
|
|
792
|
+
function semanticObjectType(objectType) {
|
|
793
|
+
if (objectType === 'semantic_metric')
|
|
794
|
+
return 'metric';
|
|
795
|
+
if (objectType === 'semantic_measure')
|
|
796
|
+
return 'measure';
|
|
797
|
+
if (objectType === 'semantic_dimension')
|
|
798
|
+
return 'dimension';
|
|
799
|
+
if (objectType === 'semantic_entity')
|
|
800
|
+
return 'entity';
|
|
801
|
+
if (objectType === 'semantic_model')
|
|
802
|
+
return 'model';
|
|
803
|
+
if (objectType === 'semantic_saved_query')
|
|
804
|
+
return 'saved_query';
|
|
805
|
+
return undefined;
|
|
806
|
+
}
|
|
453
807
|
function aliasesFor(object) {
|
|
454
808
|
const payload = object.payload ?? {};
|
|
455
809
|
return uniqueStrings([
|
|
@@ -467,7 +821,7 @@ function lexicalRelevance(normalizedQuestion, questionTokens, object, aliases) {
|
|
|
467
821
|
const normalizedAliases = aliases.map(normalizeText).filter(Boolean);
|
|
468
822
|
const reasons = [];
|
|
469
823
|
let score = 0;
|
|
470
|
-
if (normalizedAliases.some((alias) => alias
|
|
824
|
+
if (normalizedAliases.some((alias) => isExactAnalyticalPhrase(normalizedQuestion, alias))) {
|
|
471
825
|
score += 80;
|
|
472
826
|
reasons.push('exact name or alias');
|
|
473
827
|
}
|
|
@@ -525,7 +879,25 @@ function addPeer(peers, key, peer) {
|
|
|
525
879
|
peers.set(key, values);
|
|
526
880
|
}
|
|
527
881
|
function normalizeText(value) {
|
|
528
|
-
return value.toLowerCase()
|
|
882
|
+
return value.toLowerCase()
|
|
883
|
+
.replace(/%/g, ' percentage ')
|
|
884
|
+
.replace(/[_./:-]+/g, ' ')
|
|
885
|
+
.replace(/[^a-z0-9 ]+/g, ' ')
|
|
886
|
+
.replace(/\s+/g, ' ')
|
|
887
|
+
.trim();
|
|
888
|
+
}
|
|
889
|
+
function isExactAnalyticalPhrase(question, alias) {
|
|
890
|
+
if (!alias)
|
|
891
|
+
return false;
|
|
892
|
+
if (alias === question)
|
|
893
|
+
return true;
|
|
894
|
+
const index = question.indexOf(alias);
|
|
895
|
+
if (index < 0)
|
|
896
|
+
return false;
|
|
897
|
+
const remaining = `${question.slice(0, index)} ${question.slice(index + alias.length)}`
|
|
898
|
+
.replace(/\s+/g, ' ')
|
|
899
|
+
.trim();
|
|
900
|
+
return remaining.length === 0 || remaining.split(' ').every((token) => ['a', 'an', 'are', 'can', 'could', 'do', 'does', 'for', 'give', 'is', 'me', 'of', 'please', 'show', 'tell', 'the', 'what', 'which'].includes(token));
|
|
529
901
|
}
|
|
530
902
|
function tokenSet(value) {
|
|
531
903
|
return new Set(value.split(/\s+/).filter((token) => token.length >= 2));
|