@duckcodeailabs/dql-agent 1.13.5 → 1.14.1
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 +79 -4
- package/dist/agent-run-engine.d.ts.map +1 -1
- package/dist/agent-run-engine.js +457 -39
- package/dist/agent-run-engine.js.map +1 -1
- package/dist/agent-run-gates.d.ts.map +1 -1
- package/dist/agent-run-gates.js +13 -0
- package/dist/agent-run-gates.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/analyst-loop.d.ts +91 -0
- package/dist/agentic/analyst-loop.d.ts.map +1 -0
- package/dist/agentic/analyst-loop.js +311 -0
- package/dist/agentic/analyst-loop.js.map +1 -0
- package/dist/agentic/assumptions.d.ts +62 -0
- package/dist/agentic/assumptions.d.ts.map +1 -0
- package/dist/agentic/assumptions.js +73 -0
- package/dist/agentic/assumptions.js.map +1 -0
- package/dist/agentic/business-explanation.d.ts +56 -0
- package/dist/agentic/business-explanation.d.ts.map +1 -0
- package/dist/agentic/business-explanation.js +130 -0
- package/dist/agentic/business-explanation.js.map +1 -0
- package/dist/agentic/identifier-ledger.d.ts +74 -0
- package/dist/agentic/identifier-ledger.d.ts.map +1 -0
- package/dist/agentic/identifier-ledger.js +151 -0
- package/dist/agentic/identifier-ledger.js.map +1 -0
- package/dist/agentic/ledger-tools.d.ts +68 -0
- package/dist/agentic/ledger-tools.d.ts.map +1 -0
- package/dist/agentic/ledger-tools.js +198 -0
- package/dist/agentic/ledger-tools.js.map +1 -0
- package/dist/agentic/orchestrator-policy.d.ts +60 -0
- package/dist/agentic/orchestrator-policy.d.ts.map +1 -0
- package/dist/agentic/orchestrator-policy.js +73 -0
- package/dist/agentic/orchestrator-policy.js.map +1 -0
- package/dist/agentic/orchestrator.d.ts +54 -0
- package/dist/agentic/orchestrator.d.ts.map +1 -0
- package/dist/agentic/orchestrator.js +67 -0
- package/dist/agentic/orchestrator.js.map +1 -0
- package/dist/agentic/preview-tool.d.ts +36 -0
- package/dist/agentic/preview-tool.d.ts.map +1 -0
- package/dist/agentic/preview-tool.js +71 -0
- package/dist/agentic/preview-tool.js.map +1 -0
- package/dist/agentic/rerank.d.ts +50 -0
- package/dist/agentic/rerank.d.ts.map +1 -0
- package/dist/agentic/rerank.js +140 -0
- package/dist/agentic/rerank.js.map +1 -0
- package/dist/agentic/research/hypothesis.d.ts +110 -0
- package/dist/agentic/research/hypothesis.d.ts.map +1 -0
- package/dist/agentic/research/hypothesis.js +125 -0
- package/dist/agentic/research/hypothesis.js.map +1 -0
- package/dist/agentic/research/synthesis.d.ts +32 -0
- package/dist/agentic/research/synthesis.d.ts.map +1 -0
- package/dist/agentic/research/synthesis.js +94 -0
- package/dist/agentic/research/synthesis.js.map +1 -0
- package/dist/agentic/research-agent.d.ts +54 -0
- package/dist/agentic/research-agent.d.ts.map +1 -0
- package/dist/agentic/research-agent.js +176 -0
- package/dist/agentic/research-agent.js.map +1 -0
- package/dist/agentic/sql-authorization.d.ts +131 -0
- package/dist/agentic/sql-authorization.d.ts.map +1 -0
- package/dist/agentic/sql-authorization.js +232 -0
- package/dist/agentic/sql-authorization.js.map +1 -0
- package/dist/agentic/tool-loop.d.ts +29 -0
- package/dist/agentic/tool-loop.d.ts.map +1 -1
- package/dist/agentic/tool-loop.js +129 -52
- package/dist/agentic/tool-loop.js.map +1 -1
- package/dist/agentic/toolset.d.ts.map +1 -1
- package/dist/agentic/toolset.js +155 -0
- package/dist/agentic/toolset.js.map +1 -1
- package/dist/agentic/turn-plan.d.ts +35 -0
- package/dist/agentic/turn-plan.d.ts.map +1 -0
- package/dist/agentic/turn-plan.js +108 -0
- package/dist/agentic/turn-plan.js.map +1 -0
- package/dist/agentic/value-probe.d.ts +89 -0
- package/dist/agentic/value-probe.d.ts.map +1 -0
- package/dist/agentic/value-probe.js +304 -0
- package/dist/agentic/value-probe.js.map +1 -0
- package/dist/analytical-orchestration.d.ts +336 -0
- package/dist/analytical-orchestration.d.ts.map +1 -0
- package/dist/analytical-orchestration.js +631 -0
- package/dist/analytical-orchestration.js.map +1 -0
- package/dist/analytical-request-policy.d.ts +26 -0
- package/dist/analytical-request-policy.d.ts.map +1 -0
- package/dist/analytical-request-policy.js +82 -0
- package/dist/analytical-request-policy.js.map +1 -0
- package/dist/analytical-result-facts.d.ts +14 -1
- package/dist/analytical-result-facts.d.ts.map +1 -1
- package/dist/analytical-result-facts.js +76 -1
- package/dist/analytical-result-facts.js.map +1 -1
- package/dist/answer-loop.d.ts +61 -16
- package/dist/answer-loop.d.ts.map +1 -1
- package/dist/answer-loop.js +256 -36
- package/dist/answer-loop.js.map +1 -1
- package/dist/cascade/budgets.d.ts.map +1 -1
- package/dist/cascade/budgets.js +8 -1
- package/dist/cascade/budgets.js.map +1 -1
- package/dist/cascade/packer.d.ts +88 -0
- package/dist/cascade/packer.d.ts.map +1 -0
- package/dist/cascade/packer.js +106 -0
- package/dist/cascade/packer.js.map +1 -0
- package/dist/conversation/result-ops.d.ts +18 -3
- package/dist/conversation/result-ops.d.ts.map +1 -1
- package/dist/conversation/result-ops.js +77 -5
- package/dist/conversation/result-ops.js.map +1 -1
- package/dist/conversation/rolling-summary.d.ts.map +1 -1
- package/dist/conversation/rolling-summary.js +7 -2
- package/dist/conversation/rolling-summary.js.map +1 -1
- package/dist/conversation/session-store.d.ts +3 -0
- package/dist/conversation/session-store.d.ts.map +1 -1
- package/dist/conversation/session-store.js +8 -4
- package/dist/conversation/session-store.js.map +1 -1
- package/dist/conversation/snapshot.d.ts.map +1 -1
- package/dist/conversation/snapshot.js +13 -1
- 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/hints/store.d.ts.map +1 -1
- package/dist/hints/store.js +15 -5
- package/dist/hints/store.js.map +1 -1
- package/dist/index.d.ts +27 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -4
- package/dist/index.js.map +1 -1
- package/dist/intent-controller.d.ts +8 -0
- package/dist/intent-controller.d.ts.map +1 -1
- package/dist/intent-controller.js +65 -0
- package/dist/intent-controller.js.map +1 -1
- package/dist/meaning-resolution.d.ts +7 -0
- package/dist/meaning-resolution.d.ts.map +1 -1
- package/dist/meaning-resolution.js.map +1 -1
- package/dist/metadata/block-fit.d.ts +18 -0
- package/dist/metadata/block-fit.d.ts.map +1 -1
- package/dist/metadata/block-fit.js +41 -1
- package/dist/metadata/block-fit.js.map +1 -1
- package/dist/metadata/catalog.d.ts +44 -23
- package/dist/metadata/catalog.d.ts.map +1 -1
- package/dist/metadata/catalog.js +363 -53
- package/dist/metadata/catalog.js.map +1 -1
- package/dist/metadata/meaning-evidence.d.ts.map +1 -1
- package/dist/metadata/meaning-evidence.js +101 -8
- package/dist/metadata/meaning-evidence.js.map +1 -1
- package/dist/metadata/metric-match.d.ts +32 -0
- package/dist/metadata/metric-match.d.ts.map +1 -1
- package/dist/metadata/metric-match.js +120 -9
- package/dist/metadata/metric-match.js.map +1 -1
- package/dist/research-loop.d.ts +33 -2
- package/dist/research-loop.d.ts.map +1 -1
- package/dist/research-loop.js +44 -4
- package/dist/research-loop.js.map +1 -1
- package/dist/resolved-analytical-plan.d.ts.map +1 -1
- package/dist/resolved-analytical-plan.js +92 -5
- package/dist/resolved-analytical-plan.js.map +1 -1
- package/dist/router.d.ts +36 -16
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +604 -25
- package/dist/router.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { AdmittedIdentifier } from './identifier-ledger.js';
|
|
2
|
+
/**
|
|
3
|
+
* The analyst loop — explore, then compose, with provenance enforced.
|
|
4
|
+
*
|
|
5
|
+
* This is the handler the strangler seam has been holding a slot for. It runs
|
|
6
|
+
* the governed tools, harvests every identifier a tool actually returned, and
|
|
7
|
+
* refuses to execute SQL naming anything it did not observe. A violation comes
|
|
8
|
+
* back as a CORRECTION with the nearest observed name and one bounded retry —
|
|
9
|
+
* not a refusal, which is what the legacy pipeline turned it into.
|
|
10
|
+
*
|
|
11
|
+
* It is deliberately small. The legacy loop earned its 10k lines by owning
|
|
12
|
+
* retrieval, certified matching, semantic compilation, narration, and trust
|
|
13
|
+
* labelling; re-implementing any of that here would fork behaviour that already
|
|
14
|
+
* works. This owns exactly one thing the old path cannot do: notice that a
|
|
15
|
+
* proposed identifier was never observed, and say which real one was meant.
|
|
16
|
+
*/
|
|
17
|
+
import type { AgentAnswer, AnswerLoopInput } from '../answer-loop.js';
|
|
18
|
+
import type { AgentToolDefinition } from '../providers/types.js';
|
|
19
|
+
import type { AnalystTurnPlan } from './turn-plan.js';
|
|
20
|
+
/** One observable step, so the wait is legible instead of a spinner. */
|
|
21
|
+
export interface AnalystStep {
|
|
22
|
+
kind: 'plan' | 'tool' | 'observe' | 'verify' | 'answer';
|
|
23
|
+
label: string;
|
|
24
|
+
detail?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface AnalystLoopDeps {
|
|
27
|
+
/** Extract relations/columns from candidate SQL. Injected so the loop does not fork a parser. */
|
|
28
|
+
extractReferences: (sql: string) => {
|
|
29
|
+
relations: string[];
|
|
30
|
+
columns: string[];
|
|
31
|
+
};
|
|
32
|
+
/** Turn raw model output into SQL. Injected for the same reason. */
|
|
33
|
+
parseSql: (raw: string) => string | undefined;
|
|
34
|
+
/** Tools the model may call. Already surface-filtered by the host. */
|
|
35
|
+
tools: AgentToolDefinition[];
|
|
36
|
+
/**
|
|
37
|
+
* Run the deterministic safety verifiers over candidate SQL and return a
|
|
38
|
+
* correction, or `undefined` when it passes.
|
|
39
|
+
*
|
|
40
|
+
* The verifiers keep their logic — aggregation safety, grain, relationship
|
|
41
|
+
* proof — but a failure becomes an OBSERVATION the loop can act on instead of
|
|
42
|
+
* a terminal refusal. That inversion is the point: the legacy path turned
|
|
43
|
+
* "this would double-count" into "nothing was executed", which is true and
|
|
44
|
+
* useless. Telling the model that joining those tables fans out is something
|
|
45
|
+
* it can fix.
|
|
46
|
+
*/
|
|
47
|
+
verifySql?: (sql: string) => string | undefined;
|
|
48
|
+
maxIterations: number;
|
|
49
|
+
/** Physical provider sends available to this text/native tool loop. */
|
|
50
|
+
maxProviderDispatches?: number;
|
|
51
|
+
/**
|
|
52
|
+
* Optional structured planning call. Absent, the loop runs exactly as before
|
|
53
|
+
* and the trace falls back to a fixed label.
|
|
54
|
+
*/
|
|
55
|
+
planTurn?: (question: string, toolNames: string[]) => Promise<AnalystTurnPlan | undefined>;
|
|
56
|
+
onStep?: (step: AnalystStep) => void;
|
|
57
|
+
}
|
|
58
|
+
export interface AnalystOutcome {
|
|
59
|
+
sql?: string;
|
|
60
|
+
/** Identifiers the run proved, for the audit trail. */
|
|
61
|
+
admitted: string[];
|
|
62
|
+
/** The same admissions with their evidence, for minting an authorization. */
|
|
63
|
+
admittedEntries: AdmittedIdentifier[];
|
|
64
|
+
/** Corrections fed back to the model, in order. */
|
|
65
|
+
corrections: string[];
|
|
66
|
+
/** Why the loop stopped. */
|
|
67
|
+
stop: 'composed' | 'no_sql' | 'unverified' | 'budget_exhausted';
|
|
68
|
+
/** Typed terminal detail: this never grants an execution fallback. */
|
|
69
|
+
terminal?: 'no_final_sql' | 'tool_budget_exhausted' | 'provider_dispatch_budget_exhausted' | 'unverified_identifiers' | 'missing_execution_binding' | 'tool_loop_error';
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Run explore-then-compose and return verified SQL.
|
|
73
|
+
*
|
|
74
|
+
* Returns `stop: 'unverified'` rather than throwing when the repair budget is
|
|
75
|
+
* spent: the caller decides whether an unverified draft is still worth showing
|
|
76
|
+
* as review-required, and that is a governance choice, not this loop's.
|
|
77
|
+
*/
|
|
78
|
+
export declare function runAnalystLoop(input: AnswerLoopInput, deps: AnalystLoopDeps): Promise<AnalystOutcome>;
|
|
79
|
+
/**
|
|
80
|
+
* Wrap the loop as a lane handler, falling back to the legacy answer loop.
|
|
81
|
+
*
|
|
82
|
+
* The legacy loop still produces the ANSWER — trust labels, citations,
|
|
83
|
+
* narration, execution. This only decides whether the SQL that reaches it was
|
|
84
|
+
* built from observed identifiers. Handing verified SQL to a path that already
|
|
85
|
+
* knows how to govern it is a much smaller change than replacing that path.
|
|
86
|
+
*/
|
|
87
|
+
export declare function createAnalystLaneHandler(deps: {
|
|
88
|
+
legacy: (input: AnswerLoopInput) => Promise<AgentAnswer>;
|
|
89
|
+
buildDeps: (input: AnswerLoopInput) => AnalystLoopDeps | undefined;
|
|
90
|
+
}): (input: AnswerLoopInput) => Promise<AgentAnswer>;
|
|
91
|
+
//# sourceMappingURL=analyst-loop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyst-loop.d.ts","sourceRoot":"","sources":["../../src/agentic/analyst-loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAKtD,wEAAwE;AACxE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,iGAAiG;IACjG,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK;QAAE,SAAS,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC/E,oEAAoE;IACpE,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC9C,sEAAsE;IACtE,KAAK,EAAE,mBAAmB,EAAE,CAAC;IAC7B;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAChD,aAAa,EAAE,MAAM,CAAC;IACtB,uEAAuE;IACvE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAAC;IAC3F,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,6EAA6E;IAC7E,eAAe,EAAE,kBAAkB,EAAE,CAAC;IACtC,mDAAmD;IACnD,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,4BAA4B;IAC5B,IAAI,EAAE,UAAU,GAAG,QAAQ,GAAG,YAAY,GAAG,kBAAkB,CAAC;IAChE,sEAAsE;IACtE,QAAQ,CAAC,EAAE,cAAc,GACrB,uBAAuB,GACvB,oCAAoC,GACpC,wBAAwB,GACxB,2BAA2B,GAC3B,iBAAiB,CAAC;CACvB;AAWD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,cAAc,CAAC,CAmIzB;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,MAAM,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IACzD,SAAS,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,eAAe,GAAG,SAAS,CAAC;CACpE,IACe,OAAO,eAAe,KAAG,OAAO,CAAC,WAAW,CAAC,CAoG5D"}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { createAgenticSqlExecutionCapability } from './sql-authorization.js';
|
|
2
|
+
import { runAgenticToolLoopDetailed } from './tool-loop.js';
|
|
3
|
+
import { IdentifierLedger } from './identifier-ledger.js';
|
|
4
|
+
import { ANALYST_TOOL_POLICY, adjudicateProposedSql, withLedgerHarvest } from './ledger-tools.js';
|
|
5
|
+
function budgetTerminalForToolLoop(stop) {
|
|
6
|
+
return stop === 'tool_budget_exhausted' || stop === 'provider_dispatch_budget_exhausted'
|
|
7
|
+
? stop
|
|
8
|
+
: undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Run explore-then-compose and return verified SQL.
|
|
12
|
+
*
|
|
13
|
+
* Returns `stop: 'unverified'` rather than throwing when the repair budget is
|
|
14
|
+
* spent: the caller decides whether an unverified draft is still worth showing
|
|
15
|
+
* as review-required, and that is a governance choice, not this loop's.
|
|
16
|
+
*/
|
|
17
|
+
export async function runAnalystLoop(input, deps) {
|
|
18
|
+
const ledger = new IdentifierLedger();
|
|
19
|
+
const corrections = [];
|
|
20
|
+
// Seed from the context pack. These are retrieved, not observed, so they enter
|
|
21
|
+
// at the weakest tier — enough to let correct SQL through without pretending a
|
|
22
|
+
// catalog row proves a column exists in the warehouse.
|
|
23
|
+
for (const object of input.contextPack?.allowedSqlContext?.relations ?? []) {
|
|
24
|
+
ledger.admit('catalog', [object.relation, object.name], 'context_pack');
|
|
25
|
+
ledger.admit('catalog', (object.columns ?? []).map((column) => column.name), 'context_pack');
|
|
26
|
+
}
|
|
27
|
+
const tools = withLedgerHarvest(deps.tools, ledger, (event) => {
|
|
28
|
+
deps.onStep?.({
|
|
29
|
+
kind: 'observe',
|
|
30
|
+
label: `${event.tool} returned ${event.admitted} identifier${event.admitted === 1 ? '' : 's'}`,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
const messages = [
|
|
34
|
+
{ role: 'system', content: ANALYST_TOOL_POLICY },
|
|
35
|
+
{ role: 'user', content: input.question },
|
|
36
|
+
];
|
|
37
|
+
// One structured planning call before acting. It is optional by design:
|
|
38
|
+
// `planAnalystTurn` swallows its own failures, so a slow or unparseable plan
|
|
39
|
+
// costs a bounded wait and nothing else. What it buys is a trace that names
|
|
40
|
+
// what the agent is about to verify instead of a fixed string.
|
|
41
|
+
const turnPlan = deps.planTurn
|
|
42
|
+
? await deps.planTurn(input.question, tools.map((tool) => tool.name))
|
|
43
|
+
: undefined;
|
|
44
|
+
if (turnPlan) {
|
|
45
|
+
deps.onStep?.({ kind: 'plan', label: turnPlan.restatement });
|
|
46
|
+
for (const item of turnPlan.mustEstablish) {
|
|
47
|
+
deps.onStep?.({ kind: 'plan', label: `Must establish: ${item}` });
|
|
48
|
+
}
|
|
49
|
+
// Carry the plan into the loop so it steers the run rather than only
|
|
50
|
+
// describing it — otherwise the trace would promise work the model never
|
|
51
|
+
// agreed to do.
|
|
52
|
+
messages.push({
|
|
53
|
+
role: 'assistant',
|
|
54
|
+
content: `Before answering I must establish, using tools:\n${turnPlan.mustEstablish.map((item) => `- ${item}`).join('\n')}${turnPlan.openingTool ? `\nStarting with ${turnPlan.openingTool}.` : ''}`,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
deps.onStep?.({ kind: 'plan', label: 'Establishing what exists before writing SQL' });
|
|
59
|
+
}
|
|
60
|
+
const initial = await runAgenticToolLoopDetailed(input.provider, messages, tools, {
|
|
61
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
62
|
+
maxToolCalls: deps.maxIterations,
|
|
63
|
+
...(deps.maxProviderDispatches !== undefined ? { maxProviderDispatches: deps.maxProviderDispatches } : {}),
|
|
64
|
+
});
|
|
65
|
+
let raw = initial.text;
|
|
66
|
+
const initialBudgetTerminal = budgetTerminalForToolLoop(initial.stop);
|
|
67
|
+
if (initialBudgetTerminal) {
|
|
68
|
+
return {
|
|
69
|
+
admitted: ledger.entries().map((e) => e.identifier),
|
|
70
|
+
admittedEntries: ledger.entries(),
|
|
71
|
+
corrections,
|
|
72
|
+
stop: 'budget_exhausted',
|
|
73
|
+
terminal: initialBudgetTerminal,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
let sql = deps.parseSql(raw);
|
|
77
|
+
if (!sql) {
|
|
78
|
+
return {
|
|
79
|
+
admitted: ledger.entries().map((e) => e.identifier),
|
|
80
|
+
admittedEntries: ledger.entries(),
|
|
81
|
+
corrections,
|
|
82
|
+
stop: 'no_sql',
|
|
83
|
+
terminal: 'no_final_sql',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// One bounded repair. A second would mostly re-spend the budget: if the first
|
|
87
|
+
// correction — which names the exact identifier that was observed — does not
|
|
88
|
+
// land, the problem is not a typo.
|
|
89
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
90
|
+
deps.onStep?.({ kind: 'verify', label: 'Checking every identifier was actually observed' });
|
|
91
|
+
const verdict = adjudicateProposedSql(ledger, deps.extractReferences(sql));
|
|
92
|
+
// Identifiers first: a safety verdict over SQL naming a column that does not
|
|
93
|
+
// exist is noise, and would send the model chasing the wrong correction.
|
|
94
|
+
const safety = verdict.ok ? deps.verifySql?.(sql) : undefined;
|
|
95
|
+
if (verdict.ok && !safety) {
|
|
96
|
+
deps.onStep?.({ kind: 'answer', label: 'Verified — every name came from a tool result' });
|
|
97
|
+
return { sql, admitted: ledger.entries().map((e) => e.identifier), admittedEntries: ledger.entries(), corrections, stop: 'composed' };
|
|
98
|
+
}
|
|
99
|
+
const correction = verdict.ok ? safety : verdict.correction;
|
|
100
|
+
corrections.push(correction);
|
|
101
|
+
if (attempt === 1)
|
|
102
|
+
break;
|
|
103
|
+
deps.onStep?.({
|
|
104
|
+
kind: 'tool',
|
|
105
|
+
label: verdict.ok ? 'Correcting an unsafe aggregation' : 'Correcting an unobserved identifier',
|
|
106
|
+
detail: verdict.ok ? safety : verdict.unadmitted.join(', '),
|
|
107
|
+
});
|
|
108
|
+
const repair = await runAgenticToolLoopDetailed(input.provider, [...messages, { role: 'assistant', content: raw }, { role: 'user', content: correction }], tools, {
|
|
109
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
110
|
+
maxToolCalls: deps.maxIterations,
|
|
111
|
+
...(deps.maxProviderDispatches !== undefined ? { maxProviderDispatches: deps.maxProviderDispatches } : {}),
|
|
112
|
+
});
|
|
113
|
+
raw = repair.text;
|
|
114
|
+
const repairBudgetTerminal = budgetTerminalForToolLoop(repair.stop);
|
|
115
|
+
if (repairBudgetTerminal) {
|
|
116
|
+
return {
|
|
117
|
+
admitted: ledger.entries().map((e) => e.identifier),
|
|
118
|
+
admittedEntries: ledger.entries(),
|
|
119
|
+
corrections,
|
|
120
|
+
stop: 'budget_exhausted',
|
|
121
|
+
terminal: repairBudgetTerminal,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const repaired = deps.parseSql(raw);
|
|
125
|
+
if (!repaired)
|
|
126
|
+
break;
|
|
127
|
+
sql = repaired;
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
sql,
|
|
131
|
+
admitted: ledger.entries().map((e) => e.identifier),
|
|
132
|
+
admittedEntries: ledger.entries(),
|
|
133
|
+
corrections,
|
|
134
|
+
stop: 'unverified',
|
|
135
|
+
terminal: 'unverified_identifiers',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Wrap the loop as a lane handler, falling back to the legacy answer loop.
|
|
140
|
+
*
|
|
141
|
+
* The legacy loop still produces the ANSWER — trust labels, citations,
|
|
142
|
+
* narration, execution. This only decides whether the SQL that reaches it was
|
|
143
|
+
* built from observed identifiers. Handing verified SQL to a path that already
|
|
144
|
+
* knows how to govern it is a much smaller change than replacing that path.
|
|
145
|
+
*/
|
|
146
|
+
export function createAnalystLaneHandler(deps) {
|
|
147
|
+
return async (input) => {
|
|
148
|
+
const noGeneratedExecution = {
|
|
149
|
+
...input,
|
|
150
|
+
forcedGeneratedProposal: undefined,
|
|
151
|
+
agenticSqlExecutionCapability: undefined,
|
|
152
|
+
executeGeneratedSql: undefined,
|
|
153
|
+
executeAgenticGeneratedSql: undefined,
|
|
154
|
+
};
|
|
155
|
+
const safeLegacy = async (outcome) => {
|
|
156
|
+
try {
|
|
157
|
+
return await deps.legacy(noGeneratedExecution);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Never let `answerAgentic` turn a failure in the guarded path into an
|
|
161
|
+
// ambient legacy retry. This explicit no-answer retains the truthful
|
|
162
|
+
// non-executing terminal rather than emitting a second, unproved SQL.
|
|
163
|
+
return analystNonExecutingAnswer(input, outcome);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
const loopDeps = deps.buildDeps(input);
|
|
167
|
+
// A generated agentic route may never fall through to an ambient generated
|
|
168
|
+
// executor when its proof tools are unavailable. Certified/semantic routes
|
|
169
|
+
// remain host-owned, but this lane has no generated SQL authority.
|
|
170
|
+
if (!loopDeps || loopDeps.tools.length === 0) {
|
|
171
|
+
return safeLegacy({ admitted: [], admittedEntries: [], corrections: [], stop: 'no_sql', terminal: 'no_final_sql' });
|
|
172
|
+
}
|
|
173
|
+
let outcome;
|
|
174
|
+
try {
|
|
175
|
+
outcome = await runAnalystLoop(input, loopDeps);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
outcome = {
|
|
179
|
+
admitted: [],
|
|
180
|
+
admittedEntries: [],
|
|
181
|
+
corrections: [],
|
|
182
|
+
stop: 'no_sql',
|
|
183
|
+
terminal: 'tool_loop_error',
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
if (process.env.DQL_ORCHESTRATOR_TRACE) {
|
|
187
|
+
console.warn(`[dql] analyst loop outcome: stop=${outcome.stop} sql=${outcome.sql ? 'yes' : 'no'} admitted=${outcome.admitted.length} corrections=${outcome.corrections.length}`);
|
|
188
|
+
}
|
|
189
|
+
const scope = input.agenticExecutionScope;
|
|
190
|
+
const composedSql = outcome.stop === 'composed' ? outcome.sql : undefined;
|
|
191
|
+
const capability = composedSql
|
|
192
|
+
? createAgenticSqlExecutionCapability({
|
|
193
|
+
sql: composedSql,
|
|
194
|
+
// Retrieval catalog rows cannot mint execution authority. Preserve
|
|
195
|
+
// them in the audit ledger, but pass only observed evidence to the
|
|
196
|
+
// server-only capability.
|
|
197
|
+
proven: outcome.admittedEntries
|
|
198
|
+
.filter((entry) => entry.source !== 'catalog')
|
|
199
|
+
.map((entry) => ({ identifier: entry.identifier, evidence: entry.source })),
|
|
200
|
+
runId: scope?.runId,
|
|
201
|
+
executionId: scope?.executionId,
|
|
202
|
+
snapshotId: scope?.snapshotId ?? input.resolvedAnalyticalPlan?.snapshotId,
|
|
203
|
+
planId: scope?.planId ?? input.resolvedAnalyticalPlan?.planId,
|
|
204
|
+
targetFingerprint: scope?.targetFingerprint ?? input.generatedProposalTargetFingerprint,
|
|
205
|
+
bindings: scope?.bindings ?? {},
|
|
206
|
+
})
|
|
207
|
+
: undefined;
|
|
208
|
+
if (outcome.stop === 'composed' && composedSql && !capability) {
|
|
209
|
+
outcome = { ...outcome, stop: 'unverified', terminal: 'missing_execution_binding' };
|
|
210
|
+
}
|
|
211
|
+
const answer = outcome.stop === 'budget_exhausted'
|
|
212
|
+
// A terminal budget is not a recoverable model answer. Do not ask the
|
|
213
|
+
// legacy path to take another generation turn: it would conceal the
|
|
214
|
+
// terminal reason and could turn an explicit bounded stop into an
|
|
215
|
+
// unrelated answer. This remains deliberately non-executing.
|
|
216
|
+
? analystNonExecutingAnswer(input, outcome)
|
|
217
|
+
: outcome.stop === 'composed' && composedSql && capability
|
|
218
|
+
? await (async () => {
|
|
219
|
+
try {
|
|
220
|
+
return await deps.legacy({
|
|
221
|
+
...input,
|
|
222
|
+
// This is a hard handoff, not prompt context. The legacy answer loop
|
|
223
|
+
// keeps its validation/narration/artifact behavior but cannot ask a
|
|
224
|
+
// second model to replace the analyst SQL.
|
|
225
|
+
forcedGeneratedProposal: {
|
|
226
|
+
sql: composedSql,
|
|
227
|
+
summary: 'Prepared from identifiers observed during this bounded analyst run. Review-required until an analyst promotes it.',
|
|
228
|
+
},
|
|
229
|
+
agenticSqlExecutionCapability: capability,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
// Do not rethrow into `answerAgentic`: its compatibility fallback
|
|
234
|
+
// executes the original legacy input, which lacks this capability.
|
|
235
|
+
return analystNonExecutingAnswer(input, {
|
|
236
|
+
...outcome,
|
|
237
|
+
stop: 'unverified',
|
|
238
|
+
terminal: 'tool_loop_error',
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
})()
|
|
242
|
+
: await safeLegacy(outcome);
|
|
243
|
+
// ALWAYS record the verdict, not only when a correction was needed. A
|
|
244
|
+
// verification that passed is precisely what an audit wants to see, and a
|
|
245
|
+
// loop that is invisible when it works cannot be told apart from one that
|
|
246
|
+
// never ran — the same silent-degradation trap the fallback marker avoids.
|
|
247
|
+
return withAnalystEvidence(answer, outcome);
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function analystNonExecutingAnswer(input, outcome) {
|
|
251
|
+
const reason = outcome.terminal === 'missing_execution_binding'
|
|
252
|
+
? 'DQL established the query identifiers but could not bind this run to its frozen plan and execution target, so it did not run generated SQL.'
|
|
253
|
+
: outcome.terminal === 'tool_loop_error'
|
|
254
|
+
? 'DQL could not complete the bounded evidence check, so it did not run generated SQL.'
|
|
255
|
+
: outcome.terminal === 'tool_budget_exhausted'
|
|
256
|
+
? 'DQL reached its bounded tool budget before it received final SQL, so no generated warehouse query was run.'
|
|
257
|
+
: outcome.terminal === 'provider_dispatch_budget_exhausted'
|
|
258
|
+
? 'DQL reached its bounded AI dispatch budget before it received final SQL, so no generated warehouse query was run.'
|
|
259
|
+
: outcome.terminal === 'no_final_sql'
|
|
260
|
+
? 'DQL did not receive final SQL from the bounded analyst loop, so no generated warehouse query was run.'
|
|
261
|
+
: 'DQL could not verify every generated identifier against a tool observation, so no generated warehouse query was run.';
|
|
262
|
+
return {
|
|
263
|
+
kind: 'no_answer',
|
|
264
|
+
certification: 'analyst_review_required',
|
|
265
|
+
reviewStatus: 'analyst_review_required',
|
|
266
|
+
refusalCode: 'grounding_gap',
|
|
267
|
+
text: reason,
|
|
268
|
+
answer: reason,
|
|
269
|
+
citations: [],
|
|
270
|
+
considered: [],
|
|
271
|
+
...(input.contextPack ? { contextPack: input.contextPack } : {}),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function withAnalystEvidence(answer, outcome) {
|
|
275
|
+
// The host initialises `evidence` AFTER the answer call, so at this point it is
|
|
276
|
+
// usually undefined. Bailing out then silently dropped the verdict on every
|
|
277
|
+
// real run — the loop worked and left no trace, which is indistinguishable
|
|
278
|
+
// from it never having run. Create the envelope instead of skipping.
|
|
279
|
+
const evidence = answer.evidence ?? {
|
|
280
|
+
route: [], lineage: [], businessContext: [], selectedAssets: [],
|
|
281
|
+
sourceTables: [], semanticObjects: [], citations: answer.citations ?? [],
|
|
282
|
+
};
|
|
283
|
+
return {
|
|
284
|
+
...answer,
|
|
285
|
+
evidence: {
|
|
286
|
+
...evidence,
|
|
287
|
+
route: [
|
|
288
|
+
...(evidence.route ?? []),
|
|
289
|
+
{
|
|
290
|
+
tool: 'identifier_ledger',
|
|
291
|
+
// 'checked' not 'ok': the ledger VERIFIED the identifiers, it did not
|
|
292
|
+
// select the route or execute anything.
|
|
293
|
+
status: outcome.stop === 'composed' ? 'checked' : 'failed',
|
|
294
|
+
label: outcome.stop === 'composed'
|
|
295
|
+
? `Verified ${outcome.admitted.length} identifier(s) against tool observations`
|
|
296
|
+
: outcome.terminal === 'no_final_sql'
|
|
297
|
+
? 'The analyst loop did not produce final SQL; no generated warehouse query was run'
|
|
298
|
+
: outcome.terminal === 'tool_budget_exhausted'
|
|
299
|
+
? 'The analyst loop reached its bounded tool budget before final SQL; no generated warehouse query was run'
|
|
300
|
+
: outcome.terminal === 'provider_dispatch_budget_exhausted'
|
|
301
|
+
? 'The analyst loop reached its bounded provider-dispatch budget before final SQL; no generated warehouse query was run'
|
|
302
|
+
: outcome.terminal === 'missing_execution_binding'
|
|
303
|
+
? 'The analyst proposal had no complete run/plan/target execution binding; no generated warehouse query was run'
|
|
304
|
+
: 'Could not verify every identifier against a tool observation',
|
|
305
|
+
...(outcome.corrections.length > 0 ? { detail: outcome.corrections.join(' ') } : {}),
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
//# sourceMappingURL=analyst-loop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyst-loop.js","sourceRoot":"","sources":["../../src/agentic/analyst-loop.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assumptions — the mechanism that lets a turn ANSWER where it would otherwise
|
|
3
|
+
* ask a question with only one sensible reply.
|
|
4
|
+
*
|
|
5
|
+
* The measured baseline on the jaffle fixture was: false-refusal 0%, but
|
|
6
|
+
* clarification 100%. Nothing dead-ended, yet every answerable question came
|
|
7
|
+
* back as a question. That is a dead end with extra steps whenever the user's
|
|
8
|
+
* only real move is to accept the obvious candidate.
|
|
9
|
+
*
|
|
10
|
+
* The rule is a MARGIN, not a count. One candidate is not the same as no
|
|
11
|
+
* ambiguity: a lone `top_beverage_customers` block is a poor answer to "who are
|
|
12
|
+
* the top customers", and binding it silently would narrow the question without
|
|
13
|
+
* saying so. What makes an assumption safe is that the leading candidate is
|
|
14
|
+
* clearly better than the alternatives — and that we say which one we took and
|
|
15
|
+
* offer the others.
|
|
16
|
+
*/
|
|
17
|
+
/** What part of the question was assumed rather than proven. */
|
|
18
|
+
export type AssumptionAbout = 'metric' | 'grain' | 'timeframe' | 'filter' | 'entity' | 'join';
|
|
19
|
+
export interface AnswerAssumption {
|
|
20
|
+
about: AssumptionAbout;
|
|
21
|
+
/** The governed identifier actually used. */
|
|
22
|
+
chose: string;
|
|
23
|
+
/** Human label for `chose`, when it differs from the id. */
|
|
24
|
+
choseLabel?: string;
|
|
25
|
+
/** One line the user can check at a glance. */
|
|
26
|
+
because: string;
|
|
27
|
+
/** Other candidates, offered as one-click corrections. */
|
|
28
|
+
alternatives: Array<{
|
|
29
|
+
id: string;
|
|
30
|
+
label: string;
|
|
31
|
+
}>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* How far ahead the leader must be before binding it without asking.
|
|
35
|
+
*
|
|
36
|
+
* Set from the shape of the failure it prevents rather than tuned for a score:
|
|
37
|
+
* at 1.25 a near-tie between two plausible measures still asks, which is the
|
|
38
|
+
* case where guessing wrong produces a confidently wrong number.
|
|
39
|
+
*/
|
|
40
|
+
export declare const ASSUMPTION_DOMINANCE_RATIO = 1.25;
|
|
41
|
+
export interface AssumptionCandidate {
|
|
42
|
+
id: string;
|
|
43
|
+
label: string;
|
|
44
|
+
score: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Decide whether the leading candidate is safe to assume.
|
|
48
|
+
*
|
|
49
|
+
* Returns `undefined` when the field is genuinely ambiguous, so the caller
|
|
50
|
+
* clarifies instead — that path stays intact on purpose. "Never dead-end" must
|
|
51
|
+
* not become "always guess": a wrong silent binding is worse than a good
|
|
52
|
+
* question, because the user cannot see that it happened.
|
|
53
|
+
*/
|
|
54
|
+
export declare function assumeDominantCandidate(input: {
|
|
55
|
+
about: AssumptionAbout;
|
|
56
|
+
candidates: AssumptionCandidate[];
|
|
57
|
+
because: (chosen: AssumptionCandidate) => string;
|
|
58
|
+
dominanceRatio?: number;
|
|
59
|
+
}): AnswerAssumption | undefined;
|
|
60
|
+
/** One line the user reads above the answer. */
|
|
61
|
+
export declare function renderAssumptionPreamble(assumptions: AnswerAssumption[]): string | undefined;
|
|
62
|
+
//# sourceMappingURL=assumptions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assumptions.d.ts","sourceRoot":"","sources":["../../src/agentic/assumptions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,gEAAgE;AAChE,MAAM,MAAM,eAAe,GACvB,QAAQ,GACR,OAAO,GACP,WAAW,GACX,QAAQ,GACR,QAAQ,GACR,MAAM,CAAC;AAEX,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,eAAe,CAAC;IACvB,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,YAAY,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD;AAED;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE;IAC7C,KAAK,EAAE,eAAe,CAAC;IACvB,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,OAAO,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,MAAM,CAAC;IACjD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GAAG,gBAAgB,GAAG,SAAS,CAwB/B;AAED,gDAAgD;AAChD,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,gBAAgB,EAAE,GAAG,MAAM,GAAG,SAAS,CAU5F"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assumptions — the mechanism that lets a turn ANSWER where it would otherwise
|
|
3
|
+
* ask a question with only one sensible reply.
|
|
4
|
+
*
|
|
5
|
+
* The measured baseline on the jaffle fixture was: false-refusal 0%, but
|
|
6
|
+
* clarification 100%. Nothing dead-ended, yet every answerable question came
|
|
7
|
+
* back as a question. That is a dead end with extra steps whenever the user's
|
|
8
|
+
* only real move is to accept the obvious candidate.
|
|
9
|
+
*
|
|
10
|
+
* The rule is a MARGIN, not a count. One candidate is not the same as no
|
|
11
|
+
* ambiguity: a lone `top_beverage_customers` block is a poor answer to "who are
|
|
12
|
+
* the top customers", and binding it silently would narrow the question without
|
|
13
|
+
* saying so. What makes an assumption safe is that the leading candidate is
|
|
14
|
+
* clearly better than the alternatives — and that we say which one we took and
|
|
15
|
+
* offer the others.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* How far ahead the leader must be before binding it without asking.
|
|
19
|
+
*
|
|
20
|
+
* Set from the shape of the failure it prevents rather than tuned for a score:
|
|
21
|
+
* at 1.25 a near-tie between two plausible measures still asks, which is the
|
|
22
|
+
* case where guessing wrong produces a confidently wrong number.
|
|
23
|
+
*/
|
|
24
|
+
export const ASSUMPTION_DOMINANCE_RATIO = 1.25;
|
|
25
|
+
/**
|
|
26
|
+
* Decide whether the leading candidate is safe to assume.
|
|
27
|
+
*
|
|
28
|
+
* Returns `undefined` when the field is genuinely ambiguous, so the caller
|
|
29
|
+
* clarifies instead — that path stays intact on purpose. "Never dead-end" must
|
|
30
|
+
* not become "always guess": a wrong silent binding is worse than a good
|
|
31
|
+
* question, because the user cannot see that it happened.
|
|
32
|
+
*/
|
|
33
|
+
export function assumeDominantCandidate(input) {
|
|
34
|
+
const ranked = [...input.candidates]
|
|
35
|
+
.filter((candidate) => Number.isFinite(candidate.score))
|
|
36
|
+
.sort((left, right) => right.score - left.score || left.id.localeCompare(right.id));
|
|
37
|
+
const leader = ranked[0];
|
|
38
|
+
if (!leader)
|
|
39
|
+
return undefined;
|
|
40
|
+
const runnerUp = ranked[1];
|
|
41
|
+
const ratio = input.dominanceRatio ?? ASSUMPTION_DOMINANCE_RATIO;
|
|
42
|
+
// A single candidate is dominant by definition — there is nothing to confuse
|
|
43
|
+
// it with. Whether it FITS the question is a separate check the caller owns.
|
|
44
|
+
if (runnerUp) {
|
|
45
|
+
if (runnerUp.score <= 0) {
|
|
46
|
+
// Leader is the only scoring candidate; the rest carry no signal.
|
|
47
|
+
}
|
|
48
|
+
else if (leader.score < runnerUp.score * ratio) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
about: input.about,
|
|
54
|
+
chose: leader.id,
|
|
55
|
+
...(leader.label && leader.label !== leader.id ? { choseLabel: leader.label } : {}),
|
|
56
|
+
because: input.because(leader),
|
|
57
|
+
alternatives: ranked.slice(1, 4).map((candidate) => ({ id: candidate.id, label: candidate.label })),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** One line the user reads above the answer. */
|
|
61
|
+
export function renderAssumptionPreamble(assumptions) {
|
|
62
|
+
if (assumptions.length === 0)
|
|
63
|
+
return undefined;
|
|
64
|
+
const lines = assumptions.map((assumption) => {
|
|
65
|
+
const chosen = assumption.choseLabel ?? assumption.chose;
|
|
66
|
+
const alternatives = assumption.alternatives.length > 0
|
|
67
|
+
? ` Other options: ${assumption.alternatives.map((alternative) => alternative.label).join(', ')}.`
|
|
68
|
+
: '';
|
|
69
|
+
return `Assumed ${assumption.about}: **${chosen}** — ${assumption.because}${alternatives}`;
|
|
70
|
+
});
|
|
71
|
+
return lines.join('\n');
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=assumptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assumptions.js","sourceRoot":"","sources":["../../src/agentic/assumptions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAuBH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,IAAI,CAAC;AAQ/C;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAKvC;IACC,MAAM,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC;SACjC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;SACvD,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IACtF,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACzB,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC3B,MAAM,KAAK,GAAG,KAAK,CAAC,cAAc,IAAI,0BAA0B,CAAC;IACjE,6EAA6E;IAC7E,6EAA6E;IAC7E,IAAI,QAAQ,EAAE,CAAC;QACb,IAAI,QAAQ,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;YACxB,kEAAkE;QACpE,CAAC;aAAM,IAAI,MAAM,CAAC,KAAK,GAAG,QAAQ,CAAC,KAAK,GAAG,KAAK,EAAE,CAAC;YACjD,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IACD,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,KAAK,EAAE,MAAM,CAAC,EAAE;QAChB,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnF,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAC9B,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC;KACpG,CAAC;AACJ,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,wBAAwB,CAAC,WAA+B;IACtE,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC/C,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE;QAC3C,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,IAAI,UAAU,CAAC,KAAK,CAAC;QACzD,MAAM,YAAY,GAAG,UAAU,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC;YACrD,CAAC,CAAC,mBAAmB,UAAU,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YAClG,CAAC,CAAC,EAAE,CAAC;QACP,OAAO,WAAW,UAAU,CAAC,KAAK,OAAO,MAAM,QAAQ,UAAU,CAAC,OAAO,GAAG,YAAY,EAAE,CAAC;IAC7F,CAAC,CAAC,CAAC;IACH,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Answering "what does this mean?" from governed metadata, without SQL.
|
|
3
|
+
*
|
|
4
|
+
* A definitional question — "what does the top_customers block measure?", "how
|
|
5
|
+
* is revenue defined here?" — is routed to `converse` today, which replies
|
|
6
|
+
* conversationally with NO governed evidence attached. So the product knows the
|
|
7
|
+
* answer (it has the description, the owner, the grain, the outputs, the
|
|
8
|
+
* certification status) and does not use it, which is the shape of the
|
|
9
|
+
* complaint that Ask AI cannot explain business context.
|
|
10
|
+
*
|
|
11
|
+
* This composes the explanation deterministically from what the catalog already
|
|
12
|
+
* holds. No provider, no warehouse: the facts are all in the object, and a model
|
|
13
|
+
* paraphrasing them can only add drift.
|
|
14
|
+
*/
|
|
15
|
+
/** The subset of a catalog object this needs. Structural, so any source can supply it. */
|
|
16
|
+
export interface ExplainableObject {
|
|
17
|
+
objectKey: string;
|
|
18
|
+
objectType: string;
|
|
19
|
+
name: string;
|
|
20
|
+
fullName?: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
domain?: string;
|
|
23
|
+
owner?: string;
|
|
24
|
+
status?: string;
|
|
25
|
+
payload?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
export interface BusinessExplanation {
|
|
28
|
+
text: string;
|
|
29
|
+
/** Object keys the explanation drew on, for the provenance footer. */
|
|
30
|
+
citations: string[];
|
|
31
|
+
/**
|
|
32
|
+
* Whether every claim came from a governed artifact. False when the object is
|
|
33
|
+
* a raw dbt/warehouse node rather than something a person certified — the
|
|
34
|
+
* answer is still useful, it is just not an authored definition.
|
|
35
|
+
*/
|
|
36
|
+
governed: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Compose an explanation of one governed object.
|
|
40
|
+
*
|
|
41
|
+
* Returns `null` when there is nothing beyond a bare name to say. A confident
|
|
42
|
+
* paragraph assembled from an empty description is worse than falling through
|
|
43
|
+
* to the normal cascade: it reads like an answer and carries no information.
|
|
44
|
+
*/
|
|
45
|
+
export declare function explainObject(object: ExplainableObject): BusinessExplanation | null;
|
|
46
|
+
/**
|
|
47
|
+
* Pick the object a definitional question is about, then explain it.
|
|
48
|
+
*
|
|
49
|
+
* Matching is by NAME MENTION, not relevance rank: "what does top_customers
|
|
50
|
+
* measure?" is asking about a specific artifact, and answering about a merely
|
|
51
|
+
* similar one would be a different question confidently answered. When nothing
|
|
52
|
+
* is named, return null and let the cascade run — guessing the subject of a
|
|
53
|
+
* definition is exactly the failure this is meant to avoid.
|
|
54
|
+
*/
|
|
55
|
+
export declare function composeBusinessExplanation(question: string, objects: readonly ExplainableObject[]): BusinessExplanation | null;
|
|
56
|
+
//# sourceMappingURL=business-explanation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"business-explanation.d.ts","sourceRoot":"","sources":["../../src/agentic/business-explanation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,0FAA0F;AAC1F,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAC;CACnB;AAuCD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,iBAAiB,GAAG,mBAAmB,GAAG,IAAI,CAgCnF;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,SAAS,iBAAiB,EAAE,GACpC,mBAAmB,GAAG,IAAI,CA2B5B"}
|