@gmickel/gno 1.19.0 → 1.20.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/README.md +12 -7
- package/assets/skill/SKILL.md +27 -12
- package/assets/skill/mcp-reference.md +7 -2
- package/assets/skill/recipes/citation-and-provenance.md +32 -9
- package/package.json +1 -1
- package/spec/cli.md +42 -17
- package/spec/evals-agentic.md +87 -5
- package/spec/mcp.md +53 -3
- package/spec/output-schemas/ask.schema.json +198 -0
- package/spec/output-schemas/claim-verification.schema.json +291 -0
- package/spec/output-schemas/context-capsule-v1.schema.json +36 -1
- package/src/app/context-runtime-contract.ts +10 -5
- package/src/app/context-runtime-input.ts +29 -1
- package/src/app/context-runtime-types.ts +4 -0
- package/src/app/context-runtime.ts +5 -1
- package/src/app/context-surface.ts +4 -0
- package/src/app/verified-ask.ts +291 -0
- package/src/cli/commands/ask-format.ts +255 -0
- package/src/cli/commands/ask.ts +40 -149
- package/src/cli/program.ts +32 -1
- package/src/core/context-budget.ts +6 -0
- package/src/core/context-capsule-retrieval-schema.ts +4 -0
- package/src/core/context-capsule-schema.ts +17 -0
- package/src/core/context-capsule-validation.ts +3 -2
- package/src/core/context-capsule.ts +18 -0
- package/src/core/context-compiler.ts +33 -21
- package/src/core/context-evidence.ts +6 -0
- package/src/core/retrieval-trace-evidence-origin.ts +3 -0
- package/src/core/retrieval-trace-session.ts +15 -2
- package/src/llm/errors.ts +10 -1
- package/src/llm/httpGeneration.ts +11 -1
- package/src/llm/nodeLlamaCpp/generation.ts +54 -10
- package/src/llm/types.ts +6 -0
- package/src/mcp/tools/ask.ts +228 -0
- package/src/mcp/tools/context.ts +28 -7
- package/src/mcp/tools/index.ts +9 -0
- package/src/pipeline/claim-verification-schema.ts +235 -0
- package/src/pipeline/claim-verification.ts +487 -0
- package/src/pipeline/claim-verifier.ts +474 -0
- package/src/pipeline/types.ts +25 -0
- package/src/sdk/client.ts +35 -2
- package/src/serve/public/components/AskVerificationPanel.tsx +189 -0
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/pages/Ask.tsx +42 -4
- package/src/serve/routes/api.ts +149 -3
package/src/cli/commands/ask.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
} from "../../llm/types";
|
|
15
15
|
import type { AskOptions, AskResult, Citation } from "../../pipeline/types";
|
|
16
16
|
|
|
17
|
+
import { buildVerifiedAsk } from "../../app/verified-ask";
|
|
17
18
|
import {
|
|
18
19
|
finishRetrievalTraceAfterError,
|
|
19
20
|
retrievalTraceFilters,
|
|
@@ -39,6 +40,8 @@ import {
|
|
|
39
40
|
} from "../progress";
|
|
40
41
|
import { initStore } from "./shared";
|
|
41
42
|
|
|
43
|
+
export { formatAsk } from "./ask-format";
|
|
44
|
+
|
|
42
45
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
43
46
|
// Types
|
|
44
47
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -83,9 +86,11 @@ export async function ask(
|
|
|
83
86
|
options: AskCommandOptions = {}
|
|
84
87
|
): Promise<AskCommandResult> {
|
|
85
88
|
const limit = options.limit ?? 5;
|
|
89
|
+
const globals = getGlobals();
|
|
86
90
|
|
|
87
91
|
const initResult = await initStore({
|
|
88
92
|
configPath: options.configPath,
|
|
93
|
+
indexName: globals.index,
|
|
89
94
|
collection: options.collection,
|
|
90
95
|
syncConfig: false,
|
|
91
96
|
});
|
|
@@ -103,7 +108,9 @@ export async function ask(
|
|
|
103
108
|
let traceSession: RetrievalTraceSession | undefined;
|
|
104
109
|
|
|
105
110
|
try {
|
|
106
|
-
const
|
|
111
|
+
const verificationRequested = options.verify === true;
|
|
112
|
+
const answerRequested =
|
|
113
|
+
verificationRequested || Boolean(options.answer && !options.noAnswer);
|
|
107
114
|
const embedUri = resolveModelUri(
|
|
108
115
|
config,
|
|
109
116
|
"embed",
|
|
@@ -111,7 +118,7 @@ export async function ask(
|
|
|
111
118
|
options.collection
|
|
112
119
|
);
|
|
113
120
|
const expandUri =
|
|
114
|
-
!options.noExpand && !options.queryModes?.length
|
|
121
|
+
!verificationRequested && !options.noExpand && !options.queryModes?.length
|
|
115
122
|
? resolveModelUri(
|
|
116
123
|
config,
|
|
117
124
|
"expand",
|
|
@@ -136,6 +143,7 @@ export async function ask(
|
|
|
136
143
|
query,
|
|
137
144
|
filters: retrievalTraceFilters({ ...options, limit }),
|
|
138
145
|
pipeline: "ask",
|
|
146
|
+
indexName: globals.index,
|
|
139
147
|
modelUris: [embedUri, expandUri, answerUri, rerankUri].filter(
|
|
140
148
|
(value): value is string => Boolean(value)
|
|
141
149
|
),
|
|
@@ -147,7 +155,6 @@ export async function ask(
|
|
|
147
155
|
const llm = new LlmAdapter(config);
|
|
148
156
|
|
|
149
157
|
// Resolve download policy from env/flags
|
|
150
|
-
const globals = getGlobals();
|
|
151
158
|
const policy = resolveDownloadPolicy(process.env, {
|
|
152
159
|
offline: globals.offline,
|
|
153
160
|
});
|
|
@@ -254,6 +261,34 @@ export async function ask(
|
|
|
254
261
|
};
|
|
255
262
|
}
|
|
256
263
|
|
|
264
|
+
if (verificationRequested && answerPort) {
|
|
265
|
+
const verified = await buildVerifiedAsk(
|
|
266
|
+
query,
|
|
267
|
+
{ ...options, limit },
|
|
268
|
+
{
|
|
269
|
+
store,
|
|
270
|
+
config,
|
|
271
|
+
indexName: globals.index,
|
|
272
|
+
vectorIndex,
|
|
273
|
+
embedPort,
|
|
274
|
+
rerankPort,
|
|
275
|
+
genPort: answerPort,
|
|
276
|
+
traceSession,
|
|
277
|
+
}
|
|
278
|
+
);
|
|
279
|
+
const finalized = await traceSession?.finish(
|
|
280
|
+
answerTraceTerminalStatus(verified.citations)
|
|
281
|
+
);
|
|
282
|
+
if (finalized && !finalized.ok) {
|
|
283
|
+
return { success: false, error: finalized.error.message };
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
success: true,
|
|
287
|
+
data: verified,
|
|
288
|
+
metadata: traceSession?.metadata(),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
257
292
|
// Run hybrid search
|
|
258
293
|
const searchResult = await searchHybrid(deps, query, {
|
|
259
294
|
limit,
|
|
@@ -267,6 +302,8 @@ export async function ask(
|
|
|
267
302
|
tagsAll: options.tagsAll,
|
|
268
303
|
tagsAny: options.tagsAny,
|
|
269
304
|
exclude: options.exclude,
|
|
305
|
+
minScore: options.minScore,
|
|
306
|
+
graph: options.graph,
|
|
270
307
|
queryModes: options.queryModes,
|
|
271
308
|
noExpand: options.noExpand,
|
|
272
309
|
noRerank: options.noRerank,
|
|
@@ -394,149 +431,3 @@ export async function ask(
|
|
|
394
431
|
await store.close();
|
|
395
432
|
}
|
|
396
433
|
}
|
|
397
|
-
|
|
398
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
399
|
-
// Formatters
|
|
400
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
401
|
-
|
|
402
|
-
interface FormatOptions {
|
|
403
|
-
showSources?: boolean;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// oxlint-disable-next-line max-lines-per-function -- terminal formatting with conditional sections
|
|
407
|
-
function formatTerminal(data: AskResult, opts: FormatOptions = {}): string {
|
|
408
|
-
const lines: string[] = [];
|
|
409
|
-
const hasAnswer = Boolean(data.answer);
|
|
410
|
-
|
|
411
|
-
// Show answer if present
|
|
412
|
-
if (data.answer) {
|
|
413
|
-
lines.push("Answer:");
|
|
414
|
-
lines.push(data.answer);
|
|
415
|
-
lines.push("");
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Show cited sources (only sources actually referenced in answer)
|
|
419
|
-
if (data.citations && data.citations.length > 0) {
|
|
420
|
-
lines.push("Cited Sources:");
|
|
421
|
-
for (let i = 0; i < data.citations.length; i++) {
|
|
422
|
-
const c = data.citations[i];
|
|
423
|
-
if (c) {
|
|
424
|
-
lines.push(` [${i + 1}] ${c.uri}`);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
lines.push("");
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// Show all retrieved sources if:
|
|
431
|
-
// - No answer was generated (retrieval-only mode)
|
|
432
|
-
// - User explicitly requested with --show-sources
|
|
433
|
-
const showAllSources = !hasAnswer || opts.showSources;
|
|
434
|
-
if (showAllSources && data.results.length > 0) {
|
|
435
|
-
lines.push(hasAnswer ? "All Retrieved Sources:" : "Sources:");
|
|
436
|
-
for (const r of data.results) {
|
|
437
|
-
lines.push(` [${r.docid}] ${r.uri}`);
|
|
438
|
-
if (r.title) {
|
|
439
|
-
lines.push(` ${r.title}`);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
} else if (hasAnswer && data.results.length > 0) {
|
|
443
|
-
// Hint about --show-sources when we have more sources
|
|
444
|
-
const citedCount = data.citations?.length ?? 0;
|
|
445
|
-
if (data.results.length > citedCount) {
|
|
446
|
-
lines.push(
|
|
447
|
-
`(${data.results.length} sources retrieved, use --show-sources to list all)`
|
|
448
|
-
);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
if (!data.answer && data.results.length === 0) {
|
|
453
|
-
lines.push("No relevant sources found.");
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
return lines.join("\n");
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
function formatMarkdown(data: AskResult, opts: FormatOptions = {}): string {
|
|
460
|
-
const lines: string[] = [];
|
|
461
|
-
const hasAnswer = Boolean(data.answer);
|
|
462
|
-
|
|
463
|
-
lines.push(`# Question: ${data.query}`);
|
|
464
|
-
lines.push("");
|
|
465
|
-
|
|
466
|
-
if (data.answer) {
|
|
467
|
-
lines.push("## Answer");
|
|
468
|
-
lines.push("");
|
|
469
|
-
lines.push(data.answer);
|
|
470
|
-
lines.push("");
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Show cited sources (only sources actually referenced in answer)
|
|
474
|
-
if (data.citations && data.citations.length > 0) {
|
|
475
|
-
lines.push("## Cited Sources");
|
|
476
|
-
lines.push("");
|
|
477
|
-
for (let i = 0; i < data.citations.length; i++) {
|
|
478
|
-
const c = data.citations[i];
|
|
479
|
-
if (c) {
|
|
480
|
-
lines.push(`**[${i + 1}]** \`${c.uri}\``);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
lines.push("");
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// Show all retrieved sources if no answer or --show-sources
|
|
487
|
-
const showAllSources = !hasAnswer || opts.showSources;
|
|
488
|
-
if (showAllSources) {
|
|
489
|
-
lines.push(hasAnswer ? "## All Retrieved Sources" : "## Sources");
|
|
490
|
-
lines.push("");
|
|
491
|
-
|
|
492
|
-
for (let i = 0; i < data.results.length; i++) {
|
|
493
|
-
const r = data.results[i];
|
|
494
|
-
if (!r) {
|
|
495
|
-
continue;
|
|
496
|
-
}
|
|
497
|
-
lines.push(`${i + 1}. **${r.title || r.source.relPath}**`);
|
|
498
|
-
lines.push(` - URI: \`${r.uri}\``);
|
|
499
|
-
lines.push(` - Score: ${r.score.toFixed(2)}`);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
if (data.results.length === 0) {
|
|
503
|
-
lines.push("*No relevant sources found.*");
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
lines.push("");
|
|
508
|
-
lines.push("---");
|
|
509
|
-
lines.push(
|
|
510
|
-
`*Mode: ${data.mode} | Expanded: ${data.meta.expanded} | Reranked: ${data.meta.reranked}*`
|
|
511
|
-
);
|
|
512
|
-
|
|
513
|
-
return lines.join("\n");
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* Format ask result for output.
|
|
518
|
-
*/
|
|
519
|
-
export function formatAsk(
|
|
520
|
-
result: AskCommandResult,
|
|
521
|
-
options: AskCommandOptions
|
|
522
|
-
): string {
|
|
523
|
-
if (!result.success) {
|
|
524
|
-
return options.json
|
|
525
|
-
? JSON.stringify({
|
|
526
|
-
error: { code: "ASK_FAILED", message: result.error },
|
|
527
|
-
})
|
|
528
|
-
: `Error: ${result.error}`;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
const formatOpts: FormatOptions = { showSources: options.showSources };
|
|
532
|
-
|
|
533
|
-
if (options.json) {
|
|
534
|
-
return JSON.stringify(result.data, null, 2);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
if (options.md) {
|
|
538
|
-
return formatMarkdown(result.data, formatOpts);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
return formatTerminal(result.data, formatOpts);
|
|
542
|
-
}
|
package/src/cli/program.ts
CHANGED
|
@@ -635,7 +635,9 @@ function wireSearchCommands(program: Command): void {
|
|
|
635
635
|
|
|
636
636
|
const limit = cmdOpts.limit
|
|
637
637
|
? parsePositiveInt("limit", cmdOpts.limit)
|
|
638
|
-
:
|
|
638
|
+
: cmdOpts.verify
|
|
639
|
+
? 5
|
|
640
|
+
: getDefaultLimit(format);
|
|
639
641
|
const categories = parseCsvValues(cmdOpts.category);
|
|
640
642
|
const exclude = parseCsvValues(cmdOpts.exclude);
|
|
641
643
|
|
|
@@ -1108,8 +1110,16 @@ function wireSearchCommands(program: Command): void {
|
|
|
1108
1110
|
)
|
|
1109
1111
|
.option("-C, --candidate-limit <num>", "max candidates passed to reranking")
|
|
1110
1112
|
.option("--answer", "generate short grounded answer")
|
|
1113
|
+
.option(
|
|
1114
|
+
"--verify",
|
|
1115
|
+
"generate and verify every claim against a closed Context Capsule"
|
|
1116
|
+
)
|
|
1111
1117
|
.option("--no-answer", "force retrieval-only output")
|
|
1112
1118
|
.option("--max-answer-tokens <num>", "max answer tokens")
|
|
1119
|
+
.option("--context-budget-tokens <num>", "verified Context token budget")
|
|
1120
|
+
.option("--context-budget-bytes <num>", "verified Context byte budget")
|
|
1121
|
+
.option("--min-score <score>", "minimum retrieval score (0-1)")
|
|
1122
|
+
.option("--graph", "include bounded graph expansion")
|
|
1113
1123
|
.option("--show-sources", "show all retrieved sources (not just cited)")
|
|
1114
1124
|
.option("--json", "JSON output")
|
|
1115
1125
|
.option("--md", "Markdown output")
|
|
@@ -1140,6 +1150,22 @@ function wireSearchCommands(program: Command): void {
|
|
|
1140
1150
|
const maxAnswerTokens = cmdOpts.maxAnswerTokens
|
|
1141
1151
|
? parsePositiveInt("max-answer-tokens", cmdOpts.maxAnswerTokens)
|
|
1142
1152
|
: undefined;
|
|
1153
|
+
const contextBudgetTokens = cmdOpts.contextBudgetTokens
|
|
1154
|
+
? parsePositiveInt("context-budget-tokens", cmdOpts.contextBudgetTokens)
|
|
1155
|
+
: undefined;
|
|
1156
|
+
const contextBudgetBytes = cmdOpts.contextBudgetBytes
|
|
1157
|
+
? parsePositiveInt("context-budget-bytes", cmdOpts.contextBudgetBytes)
|
|
1158
|
+
: undefined;
|
|
1159
|
+
const minScore = parseOptionalFloat("min-score", cmdOpts.minScore);
|
|
1160
|
+
if (minScore !== undefined && (minScore < 0 || minScore > 1)) {
|
|
1161
|
+
throw new CliError("VALIDATION", "min-score must be between 0 and 1");
|
|
1162
|
+
}
|
|
1163
|
+
if (cmdOpts.verify && cmdOpts.noAnswer) {
|
|
1164
|
+
throw new CliError(
|
|
1165
|
+
"VALIDATION",
|
|
1166
|
+
"--verify cannot be combined with --no-answer"
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1143
1169
|
const categories = parseCsvValues(cmdOpts.category);
|
|
1144
1170
|
const exclude = parseCsvValues(cmdOpts.exclude);
|
|
1145
1171
|
|
|
@@ -1188,6 +1214,8 @@ function wireSearchCommands(program: Command): void {
|
|
|
1188
1214
|
author: cmdOpts.author as string | undefined,
|
|
1189
1215
|
intent: cmdOpts.intent as string | undefined,
|
|
1190
1216
|
exclude,
|
|
1217
|
+
minScore,
|
|
1218
|
+
graph: Boolean(cmdOpts.graph),
|
|
1191
1219
|
queryModes,
|
|
1192
1220
|
noExpand: depthPolicy.noExpand,
|
|
1193
1221
|
noRerank: depthPolicy.noRerank,
|
|
@@ -1196,7 +1224,10 @@ function wireSearchCommands(program: Command): void {
|
|
|
1196
1224
|
// Commander creates separate cmdOpts.noAnswer for --no-answer flag
|
|
1197
1225
|
answer: Boolean(cmdOpts.answer),
|
|
1198
1226
|
noAnswer: Boolean(cmdOpts.noAnswer),
|
|
1227
|
+
verify: Boolean(cmdOpts.verify),
|
|
1199
1228
|
maxAnswerTokens,
|
|
1229
|
+
contextBudgetTokens,
|
|
1230
|
+
contextBudgetBytes,
|
|
1200
1231
|
showSources,
|
|
1201
1232
|
json: format === "json",
|
|
1202
1233
|
md: format === "md",
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* canonical Capsule payload, including coverage, omissions, and guidance.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import type { FusionSource } from "../pipeline/types";
|
|
10
|
+
|
|
9
11
|
export const CONTEXT_OMISSION_REASONS = [
|
|
10
12
|
"duplicate",
|
|
11
13
|
"overlap",
|
|
@@ -43,6 +45,10 @@ export interface MaterializedContextCandidate<
|
|
|
43
45
|
text: string;
|
|
44
46
|
facets: string[];
|
|
45
47
|
retrievalRank: number;
|
|
48
|
+
/** Absent only for legacy results that predate planner provenance metadata. */
|
|
49
|
+
retrievalSources?: FusionSource[];
|
|
50
|
+
/** Absent only for legacy results that predate planner provenance metadata. */
|
|
51
|
+
graphExpanded?: boolean;
|
|
46
52
|
value: T;
|
|
47
53
|
}
|
|
48
54
|
|
|
@@ -59,10 +59,14 @@ export const contextCapsuleRetrievalSchema = z
|
|
|
59
59
|
.object({
|
|
60
60
|
author: z.string().min(1).max(256).nullable(),
|
|
61
61
|
lang: z.string().min(1).max(64).nullable(),
|
|
62
|
+
intent: z.string().min(1).max(16_384).nullable().optional(),
|
|
63
|
+
exclude: z.array(nonEmptyText.max(256)).max(128).optional(),
|
|
64
|
+
minScore: z.number().min(0).max(1).nullable().optional(),
|
|
62
65
|
queryModes: z.array(queryModeSchema).max(128),
|
|
63
66
|
limit: z.number().int().positive(),
|
|
64
67
|
candidateLimit: z.number().int().positive(),
|
|
65
68
|
graphRequested: z.boolean(),
|
|
69
|
+
rerankRequested: z.boolean().optional(),
|
|
66
70
|
})
|
|
67
71
|
.strict(),
|
|
68
72
|
capabilityStates: z
|
|
@@ -29,6 +29,14 @@ const positiveIntegerSchema = z.number().int().positive();
|
|
|
29
29
|
const nonNegativeIntegerSchema = z.number().int().nonnegative();
|
|
30
30
|
const COLLECTION_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
31
31
|
const collectionSchema = nonEmptyTextSchema.max(64).regex(COLLECTION_PATTERN);
|
|
32
|
+
const retrievalSourceSchema = z.enum([
|
|
33
|
+
"bm25",
|
|
34
|
+
"vector",
|
|
35
|
+
"bm25_variant",
|
|
36
|
+
"vector_variant",
|
|
37
|
+
"hyde",
|
|
38
|
+
"graph",
|
|
39
|
+
]);
|
|
32
40
|
const compareCodeUnits = (left: string, right: string): number =>
|
|
33
41
|
left < right ? -1 : left > right ? 1 : 0;
|
|
34
42
|
|
|
@@ -244,6 +252,15 @@ export const contextCapsuleEvidenceSchema = z
|
|
|
244
252
|
contextIds: z.array(sha256Schema).max(128),
|
|
245
253
|
retrievalRank: positiveIntegerSchema,
|
|
246
254
|
selectionRank: positiveIntegerSchema,
|
|
255
|
+
retrievalSources: z
|
|
256
|
+
.array(retrievalSourceSchema)
|
|
257
|
+
.min(1)
|
|
258
|
+
.max(6)
|
|
259
|
+
.refine((sources) => new Set(sources).size === sources.length, {
|
|
260
|
+
message: "retrievalSources must be unique",
|
|
261
|
+
})
|
|
262
|
+
.optional(),
|
|
263
|
+
graphExpanded: z.boolean().optional(),
|
|
247
264
|
facets: z.array(nonEmptyTextSchema.max(512)).max(128),
|
|
248
265
|
trust: z.literal("untrusted"),
|
|
249
266
|
egress: z.enum([
|
|
@@ -352,11 +352,12 @@ export const validateContextCapsulePayload = (
|
|
|
352
352
|
});
|
|
353
353
|
}
|
|
354
354
|
const semanticRequested = value.retrieval.depthPolicy !== "fast";
|
|
355
|
+
const rerankRequested =
|
|
356
|
+
value.retrieval.request.rerankRequested ?? semanticRequested;
|
|
355
357
|
if (
|
|
356
358
|
value.retrieval.capabilityStates.semanticSearch.requested !==
|
|
357
359
|
semanticRequested ||
|
|
358
|
-
value.retrieval.capabilityStates.reranking.requested !==
|
|
359
|
-
semanticRequested ||
|
|
360
|
+
value.retrieval.capabilityStates.reranking.requested !== rerankRequested ||
|
|
360
361
|
value.retrieval.capabilityStates.graphExpansion.requested !==
|
|
361
362
|
value.retrieval.request.graphRequested
|
|
362
363
|
) {
|
|
@@ -84,6 +84,17 @@ const normalizePayload = (
|
|
|
84
84
|
value.retrieval.request.lang === null
|
|
85
85
|
? null
|
|
86
86
|
: normalizeText(value.retrieval.request.lang),
|
|
87
|
+
...(value.retrieval.request.intent === undefined
|
|
88
|
+
? {}
|
|
89
|
+
: {
|
|
90
|
+
intent:
|
|
91
|
+
value.retrieval.request.intent === null
|
|
92
|
+
? null
|
|
93
|
+
: normalizeText(value.retrieval.request.intent),
|
|
94
|
+
}),
|
|
95
|
+
...(value.retrieval.request.exclude === undefined
|
|
96
|
+
? {}
|
|
97
|
+
: { exclude: normalizeSet(value.retrieval.request.exclude) }),
|
|
87
98
|
queryModes: value.retrieval.request.queryModes.map((mode) => ({
|
|
88
99
|
...mode,
|
|
89
100
|
text: normalizeText(mode.text),
|
|
@@ -110,6 +121,13 @@ const normalizePayload = (
|
|
|
110
121
|
documentDate: normalizeDocumentDate(item.documentDate),
|
|
111
122
|
observedAt: normalizeDate(item.observedAt),
|
|
112
123
|
contextIds: normalizeSet(item.contextIds),
|
|
124
|
+
...(item.retrievalSources === undefined
|
|
125
|
+
? {}
|
|
126
|
+
: {
|
|
127
|
+
retrievalSources: [...new Set(item.retrievalSources)].sort(
|
|
128
|
+
compareCodeUnits
|
|
129
|
+
),
|
|
130
|
+
}),
|
|
113
131
|
facets: normalizeSet(item.facets),
|
|
114
132
|
})),
|
|
115
133
|
guidance: {
|
|
@@ -64,8 +64,8 @@ export interface ContextRetrievalRequest extends HybridSearchOptions {
|
|
|
64
64
|
export interface ContextRetrievalCandidate {
|
|
65
65
|
result: SearchResult;
|
|
66
66
|
retrievalRank: number;
|
|
67
|
-
retrievalSources
|
|
68
|
-
graphExpanded
|
|
67
|
+
retrievalSources?: FusionSource[];
|
|
68
|
+
graphExpanded?: boolean;
|
|
69
69
|
contextIds: string[];
|
|
70
70
|
observedAt: string | null;
|
|
71
71
|
}
|
|
@@ -97,9 +97,13 @@ export interface ContextCompilerInput {
|
|
|
97
97
|
categories?: string[];
|
|
98
98
|
author?: string;
|
|
99
99
|
lang?: string;
|
|
100
|
+
intent?: string;
|
|
101
|
+
exclude?: string[];
|
|
102
|
+
minScore?: number;
|
|
100
103
|
since?: string;
|
|
101
104
|
until?: string;
|
|
102
105
|
graph?: boolean;
|
|
106
|
+
noRerank?: boolean;
|
|
103
107
|
limit?: number;
|
|
104
108
|
candidateLimit?: number;
|
|
105
109
|
/** Frozen once by the caller; never defaulted from wall-clock time. */
|
|
@@ -164,16 +168,9 @@ const compareCodeUnits = (left: string, right: string): number => {
|
|
|
164
168
|
};
|
|
165
169
|
|
|
166
170
|
const plannerMeta = (
|
|
167
|
-
result: SearchResult
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
result[SEARCH_RESULT_PLANNER_METADATA] ?? {
|
|
171
|
-
retrievalRank: fallbackRank,
|
|
172
|
-
mirrorHash: result.conversion?.mirrorHash ?? "",
|
|
173
|
-
seq: 0,
|
|
174
|
-
sources: [],
|
|
175
|
-
graphExpanded: false,
|
|
176
|
-
};
|
|
171
|
+
result: SearchResult
|
|
172
|
+
): SearchResultPlannerMetadata | undefined =>
|
|
173
|
+
result[SEARCH_RESULT_PLANNER_METADATA];
|
|
177
174
|
|
|
178
175
|
const compareSearchResults = (
|
|
179
176
|
left: SearchResult,
|
|
@@ -219,7 +216,9 @@ const referenceFromResult = (
|
|
|
219
216
|
const normalizeMaterialized = <T>(
|
|
220
217
|
draft: ContextMaterializedDraft<T>,
|
|
221
218
|
facets: string[],
|
|
222
|
-
retrievalRank: number
|
|
219
|
+
retrievalRank: number,
|
|
220
|
+
retrievalSources: FusionSource[] | undefined,
|
|
221
|
+
graphExpanded: boolean | undefined
|
|
223
222
|
): MaterializedContextCandidate<T> => {
|
|
224
223
|
const text = draft.text;
|
|
225
224
|
if (
|
|
@@ -250,6 +249,10 @@ const normalizeMaterialized = <T>(
|
|
|
250
249
|
text,
|
|
251
250
|
facets,
|
|
252
251
|
retrievalRank,
|
|
252
|
+
...(retrievalSources === undefined
|
|
253
|
+
? {}
|
|
254
|
+
: { retrievalSources: [...retrievalSources].sort(compareCodeUnits) }),
|
|
255
|
+
...(graphExpanded === undefined ? {} : { graphExpanded }),
|
|
253
256
|
value: draft.value,
|
|
254
257
|
};
|
|
255
258
|
};
|
|
@@ -352,10 +355,13 @@ export const planContextEvidence = async <T, P>(
|
|
|
352
355
|
categories: input.categories,
|
|
353
356
|
author: input.author,
|
|
354
357
|
lang: input.lang,
|
|
358
|
+
intent: input.intent,
|
|
359
|
+
exclude: input.exclude,
|
|
360
|
+
minScore: input.minScore,
|
|
355
361
|
since: temporalRange.since,
|
|
356
362
|
until: temporalRange.until,
|
|
357
363
|
graph: hasRerankBudget ? input.graph : false,
|
|
358
|
-
noRerank: hasRerankBudget ?
|
|
364
|
+
noRerank: input.noRerank || !hasRerankBudget ? true : undefined,
|
|
359
365
|
limit: resultLimit === undefined ? undefined : Math.max(1, resultLimit),
|
|
360
366
|
candidateLimit:
|
|
361
367
|
rerankLimit === undefined ? undefined : Math.max(1, rerankLimit),
|
|
@@ -377,7 +383,7 @@ export const planContextEvidence = async <T, P>(
|
|
|
377
383
|
const results = decoratedResults
|
|
378
384
|
.map((result, index) => ({
|
|
379
385
|
result,
|
|
380
|
-
retrievalRank: plannerMeta(result
|
|
386
|
+
retrievalRank: plannerMeta(result)?.retrievalRank ?? index + 1,
|
|
381
387
|
}))
|
|
382
388
|
.sort(
|
|
383
389
|
(left, right) =>
|
|
@@ -407,8 +413,8 @@ export const planContextEvidence = async <T, P>(
|
|
|
407
413
|
const referencesByCandidate: ContextCandidateReference[] = [];
|
|
408
414
|
|
|
409
415
|
for (const [index, result] of results.entries()) {
|
|
410
|
-
const meta = plannerMeta(result
|
|
411
|
-
for (const source of meta
|
|
416
|
+
const meta = plannerMeta(result);
|
|
417
|
+
for (const source of meta?.sources ?? []) retrievalSources.add(source);
|
|
412
418
|
const retrievalReference = referenceFromResult(result);
|
|
413
419
|
if (!isContextUriInScope(result.uri, indexName, collections, uriPrefix)) {
|
|
414
420
|
for (const facet of facetPlan) {
|
|
@@ -431,9 +437,13 @@ export const planContextEvidence = async <T, P>(
|
|
|
431
437
|
}
|
|
432
438
|
plannedCandidates.push({
|
|
433
439
|
result,
|
|
434
|
-
retrievalRank: meta
|
|
435
|
-
|
|
436
|
-
|
|
440
|
+
retrievalRank: meta?.retrievalRank ?? index + 1,
|
|
441
|
+
...(meta === undefined
|
|
442
|
+
? {}
|
|
443
|
+
: {
|
|
444
|
+
retrievalSources: [...meta.sources].sort(compareCodeUnits),
|
|
445
|
+
graphExpanded: meta.graphExpanded,
|
|
446
|
+
}),
|
|
437
447
|
contextIds:
|
|
438
448
|
guidance.idsByResultIdentity.get(
|
|
439
449
|
contextGuidanceResultIdentity(result)
|
|
@@ -486,7 +496,9 @@ export const planContextEvidence = async <T, P>(
|
|
|
486
496
|
normalizeMaterialized(
|
|
487
497
|
outcome.candidate,
|
|
488
498
|
matchedFacets,
|
|
489
|
-
plannedCandidate.retrievalRank
|
|
499
|
+
plannedCandidate.retrievalRank,
|
|
500
|
+
plannedCandidate.retrievalSources,
|
|
501
|
+
plannedCandidate.graphExpanded
|
|
490
502
|
)
|
|
491
503
|
);
|
|
492
504
|
}
|
|
@@ -490,6 +490,12 @@ export const toContextCapsuleEvidence = (
|
|
|
490
490
|
text: candidate.text,
|
|
491
491
|
retrievalRank: candidate.retrievalRank,
|
|
492
492
|
selectionRank,
|
|
493
|
+
...(candidate.retrievalSources === undefined
|
|
494
|
+
? {}
|
|
495
|
+
: { retrievalSources: [...candidate.retrievalSources] }),
|
|
496
|
+
...(candidate.graphExpanded === undefined
|
|
497
|
+
? {}
|
|
498
|
+
: { graphExpanded: candidate.graphExpanded }),
|
|
493
499
|
facets: [...candidate.facets],
|
|
494
500
|
};
|
|
495
501
|
};
|
|
@@ -130,6 +130,9 @@ export class RetrievalTraceEvidenceOrigins {
|
|
|
130
130
|
for (const run of runs.filter(({ kind }) => kind === "get")) {
|
|
131
131
|
this.addStoredFallback(run.runId, run.payload);
|
|
132
132
|
}
|
|
133
|
+
for (const run of runs.filter(({ kind }) => kind === "context")) {
|
|
134
|
+
this.addStoredFallback(run.runId, run.payload);
|
|
135
|
+
}
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
group(
|
|
@@ -128,7 +128,16 @@ const evidenceFromCapsule = (
|
|
|
128
128
|
startLine: item.startLine,
|
|
129
129
|
endLine: item.endLine,
|
|
130
130
|
passageHash: item.passageHash,
|
|
131
|
-
rank: item.
|
|
131
|
+
...(item.selectionRank === undefined ? {} : { rank: item.selectionRank }),
|
|
132
|
+
...(item.retrievalRank === undefined
|
|
133
|
+
? {}
|
|
134
|
+
: { plannerRank: item.retrievalRank }),
|
|
135
|
+
...(item.retrievalSources === undefined
|
|
136
|
+
? {}
|
|
137
|
+
: { sources: item.retrievalSources }),
|
|
138
|
+
...(item.graphExpanded === undefined
|
|
139
|
+
? {}
|
|
140
|
+
: { graphExpanded: item.graphExpanded }),
|
|
132
141
|
}));
|
|
133
142
|
|
|
134
143
|
export class RetrievalTraceSession {
|
|
@@ -324,7 +333,11 @@ export class RetrievalTraceSession {
|
|
|
324
333
|
});
|
|
325
334
|
if (!run.ok) return this.softenWriteFailure();
|
|
326
335
|
this.persistedRecords += 1;
|
|
327
|
-
|
|
336
|
+
const appended = await this.appendEvent("context", payload, runId);
|
|
337
|
+
if (appended.ok && appended.value !== "disabled") {
|
|
338
|
+
this.evidenceOrigins.addFallback(runId, payload.evidence);
|
|
339
|
+
}
|
|
340
|
+
return appended;
|
|
328
341
|
}
|
|
329
342
|
|
|
330
343
|
async recordEvidence(
|
package/src/llm/errors.ts
CHANGED
|
@@ -22,7 +22,8 @@ export type LlmErrorCode =
|
|
|
22
22
|
| "OUT_OF_MEMORY"
|
|
23
23
|
| "INVALID_URI"
|
|
24
24
|
| "LOCK_FAILED"
|
|
25
|
-
| "AUTO_DOWNLOAD_DISABLED"
|
|
25
|
+
| "AUTO_DOWNLOAD_DISABLED"
|
|
26
|
+
| "STRUCTURED_OUTPUT_UNAVAILABLE";
|
|
26
27
|
|
|
27
28
|
export interface LlmError {
|
|
28
29
|
code: LlmErrorCode;
|
|
@@ -248,3 +249,11 @@ export function autoDownloadDisabledError(uri: string): LlmError {
|
|
|
248
249
|
suggestion: "Run 'gno models pull' to download models manually.",
|
|
249
250
|
});
|
|
250
251
|
}
|
|
252
|
+
|
|
253
|
+
export function structuredOutputUnavailableError(uri: string): LlmError {
|
|
254
|
+
return llmError("STRUCTURED_OUTPUT_UNAVAILABLE", {
|
|
255
|
+
message: `JSON Schema constrained generation is unavailable for model: ${uri}`,
|
|
256
|
+
modelUri: uri,
|
|
257
|
+
retryable: false,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
import type { GenerationPort, GenParams, LlmResult } from "./types";
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
inferenceFailedError,
|
|
12
|
+
structuredOutputUnavailableError,
|
|
13
|
+
} from "./errors";
|
|
11
14
|
|
|
12
15
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
13
16
|
// Types
|
|
@@ -42,6 +45,7 @@ export class HttpGeneration implements GenerationPort {
|
|
|
42
45
|
private readonly apiUrl: string;
|
|
43
46
|
private readonly modelName: string;
|
|
44
47
|
readonly modelUri: string;
|
|
48
|
+
readonly structuredOutput = "none" as const;
|
|
45
49
|
|
|
46
50
|
constructor(modelUri: string) {
|
|
47
51
|
this.modelUri = modelUri;
|
|
@@ -63,6 +67,12 @@ export class HttpGeneration implements GenerationPort {
|
|
|
63
67
|
prompt: string,
|
|
64
68
|
params?: GenParams
|
|
65
69
|
): Promise<LlmResult<string>> {
|
|
70
|
+
if (params?.jsonSchema) {
|
|
71
|
+
return {
|
|
72
|
+
ok: false,
|
|
73
|
+
error: structuredOutputUnavailableError(this.modelUri),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
66
76
|
try {
|
|
67
77
|
const response = await fetch(this.apiUrl, {
|
|
68
78
|
method: "POST",
|