@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
|
@@ -21,6 +21,10 @@ import {
|
|
|
21
21
|
SourcesTrigger,
|
|
22
22
|
} from "../components/ai-elements/sources";
|
|
23
23
|
import { AIModelSelector } from "../components/AIModelSelector";
|
|
24
|
+
import {
|
|
25
|
+
type AskVerification,
|
|
26
|
+
AskVerificationPanel,
|
|
27
|
+
} from "../components/AskVerificationPanel";
|
|
24
28
|
import {
|
|
25
29
|
ThoroughnessSelector,
|
|
26
30
|
type Thoroughness,
|
|
@@ -57,6 +61,7 @@ interface PageProps {
|
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
interface Citation {
|
|
64
|
+
evidenceId?: string;
|
|
60
65
|
docid: string;
|
|
61
66
|
uri: string;
|
|
62
67
|
startLine?: number;
|
|
@@ -88,12 +93,15 @@ interface AskResponse {
|
|
|
88
93
|
vectorsUsed: boolean;
|
|
89
94
|
answerGenerated: boolean;
|
|
90
95
|
totalResults: number;
|
|
96
|
+
verificationRequested?: boolean;
|
|
97
|
+
abstained?: boolean;
|
|
91
98
|
queryModes?: {
|
|
92
99
|
term: number;
|
|
93
100
|
intent: number;
|
|
94
101
|
hyde: boolean;
|
|
95
102
|
};
|
|
96
103
|
};
|
|
104
|
+
verification?: AskVerification;
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
interface Capabilities {
|
|
@@ -139,7 +147,7 @@ function renderAnswer(
|
|
|
139
147
|
const parts: React.ReactNode[] = [];
|
|
140
148
|
let key = 0;
|
|
141
149
|
|
|
142
|
-
const citationRegex = /\[(\d+)\]/g;
|
|
150
|
+
const citationRegex = /\[(?:evidence:([a-f0-9]{64})|(\d+))\]/g;
|
|
143
151
|
let lastIndex = 0;
|
|
144
152
|
let match: RegExpExecArray | null;
|
|
145
153
|
|
|
@@ -149,8 +157,13 @@ function renderAnswer(
|
|
|
149
157
|
parts.push(answer.slice(lastIndex, match.index));
|
|
150
158
|
}
|
|
151
159
|
|
|
152
|
-
const
|
|
153
|
-
const
|
|
160
|
+
const evidenceId = match[1];
|
|
161
|
+
const numericCitation = match[2] ? Number(match[2]) : undefined;
|
|
162
|
+
const citationIndex = evidenceId
|
|
163
|
+
? citations.findIndex((citation) => citation.evidenceId === evidenceId)
|
|
164
|
+
: (numericCitation ?? 0) - 1;
|
|
165
|
+
const citationNum = citationIndex + 1;
|
|
166
|
+
const citation = citations[citationIndex];
|
|
154
167
|
|
|
155
168
|
if (citation) {
|
|
156
169
|
parts.push(
|
|
@@ -186,6 +199,7 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
186
199
|
const [collections, setCollections] = useState<Collection[]>([]);
|
|
187
200
|
const [thoroughness, setThoroughness] = useState<Thoroughness>("balanced");
|
|
188
201
|
const [activePreset, setActivePreset] = useState("slim-tuned");
|
|
202
|
+
const [verify, setVerify] = useState(false);
|
|
189
203
|
|
|
190
204
|
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
191
205
|
const [selectedCollection, setSelectedCollection] = useState("");
|
|
@@ -318,6 +332,7 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
318
332
|
const requestBody: Record<string, unknown> = {
|
|
319
333
|
query: currentQuery,
|
|
320
334
|
limit: 5,
|
|
335
|
+
verify,
|
|
321
336
|
};
|
|
322
337
|
|
|
323
338
|
if (selectedCollection) {
|
|
@@ -394,6 +409,7 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
394
409
|
tagsInput,
|
|
395
410
|
thoroughness,
|
|
396
411
|
until,
|
|
412
|
+
verify,
|
|
397
413
|
]
|
|
398
414
|
);
|
|
399
415
|
|
|
@@ -443,6 +459,7 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
443
459
|
parseTagsCsv(tagsInput).length > 0
|
|
444
460
|
? `${tagMode}:${parseTagsCsv(tagsInput).join(",")}`
|
|
445
461
|
: null,
|
|
462
|
+
verify ? "verified" : null,
|
|
446
463
|
].filter((pill): pill is string => Boolean(pill));
|
|
447
464
|
|
|
448
465
|
return (
|
|
@@ -475,6 +492,16 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
475
492
|
value={thoroughness}
|
|
476
493
|
/>
|
|
477
494
|
|
|
495
|
+
<Button
|
|
496
|
+
aria-pressed={verify}
|
|
497
|
+
onClick={() => setVerify((value) => !value)}
|
|
498
|
+
size="sm"
|
|
499
|
+
type="button"
|
|
500
|
+
variant={verify ? "default" : "outline"}
|
|
501
|
+
>
|
|
502
|
+
Verify
|
|
503
|
+
</Button>
|
|
504
|
+
|
|
478
505
|
<div className="h-6 w-px bg-border/40" />
|
|
479
506
|
|
|
480
507
|
<AIModelSelector onPresetChange={setActivePreset} />
|
|
@@ -878,8 +905,19 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
878
905
|
</Sources>
|
|
879
906
|
)}
|
|
880
907
|
|
|
908
|
+
{entry.response.verification && (
|
|
909
|
+
<AskVerificationPanel
|
|
910
|
+
navigate={navigate}
|
|
911
|
+
verification={entry.response.verification}
|
|
912
|
+
/>
|
|
913
|
+
)}
|
|
914
|
+
|
|
881
915
|
<div className="flex items-center gap-2 text-muted-foreground/60 text-xs">
|
|
882
|
-
<span>
|
|
916
|
+
<span>
|
|
917
|
+
{entry.response.verification
|
|
918
|
+
? `${entry.response.verification.capsule.evidence.length} evidence spans`
|
|
919
|
+
: `${entry.response.results.length} results`}
|
|
920
|
+
</span>
|
|
883
921
|
{entry.response.meta.vectorsUsed && (
|
|
884
922
|
<Badge
|
|
885
923
|
className="font-mono text-[9px]"
|
package/src/serve/routes/api.ts
CHANGED
|
@@ -36,6 +36,7 @@ import type { StartJobError } from "../jobs";
|
|
|
36
36
|
import type { ResidentStatus } from "../status-model";
|
|
37
37
|
import type { CollectionWatchService } from "../watch-service";
|
|
38
38
|
|
|
39
|
+
import { buildVerifiedAsk } from "../../app/verified-ask";
|
|
39
40
|
import { modelsPull } from "../../cli/commands/models/pull";
|
|
40
41
|
import {
|
|
41
42
|
addCollection,
|
|
@@ -125,6 +126,7 @@ import {
|
|
|
125
126
|
resolveModelUri,
|
|
126
127
|
} from "../../llm/registry";
|
|
127
128
|
import {
|
|
129
|
+
answerTraceTerminalStatus,
|
|
128
130
|
generateGroundedAnswer,
|
|
129
131
|
processAnswerResultWithTrace,
|
|
130
132
|
} from "../../pipeline/answer";
|
|
@@ -289,14 +291,46 @@ export interface AskRequestBody {
|
|
|
289
291
|
category?: string;
|
|
290
292
|
author?: string;
|
|
291
293
|
maxAnswerTokens?: number;
|
|
294
|
+
verify?: boolean;
|
|
295
|
+
contextBudgetTokens?: number;
|
|
296
|
+
contextBudgetBytes?: number;
|
|
297
|
+
minScore?: number;
|
|
292
298
|
noExpand?: boolean;
|
|
293
299
|
noRerank?: boolean;
|
|
300
|
+
graph?: boolean;
|
|
301
|
+
noGraph?: boolean;
|
|
294
302
|
/** Comma-separated tags - filter to docs having ALL (AND) */
|
|
295
303
|
tagsAll?: string;
|
|
296
304
|
/** Comma-separated tags - filter to docs having ANY (OR) */
|
|
297
305
|
tagsAny?: string;
|
|
298
306
|
}
|
|
299
307
|
|
|
308
|
+
const ASK_REQUEST_KEYS = new Set<keyof AskRequestBody>([
|
|
309
|
+
"query",
|
|
310
|
+
"limit",
|
|
311
|
+
"collection",
|
|
312
|
+
"lang",
|
|
313
|
+
"intent",
|
|
314
|
+
"candidateLimit",
|
|
315
|
+
"exclude",
|
|
316
|
+
"queryModes",
|
|
317
|
+
"since",
|
|
318
|
+
"until",
|
|
319
|
+
"category",
|
|
320
|
+
"author",
|
|
321
|
+
"maxAnswerTokens",
|
|
322
|
+
"verify",
|
|
323
|
+
"contextBudgetTokens",
|
|
324
|
+
"contextBudgetBytes",
|
|
325
|
+
"minScore",
|
|
326
|
+
"noExpand",
|
|
327
|
+
"noRerank",
|
|
328
|
+
"graph",
|
|
329
|
+
"noGraph",
|
|
330
|
+
"tagsAll",
|
|
331
|
+
"tagsAny",
|
|
332
|
+
]);
|
|
333
|
+
|
|
300
334
|
export interface CreateCollectionRequestBody {
|
|
301
335
|
path: string;
|
|
302
336
|
name?: string;
|
|
@@ -512,11 +546,12 @@ async function startRestTrace(
|
|
|
512
546
|
async function finishRestTrace(
|
|
513
547
|
request: Request,
|
|
514
548
|
session: RetrievalTraceSession | null,
|
|
515
|
-
status: "completed" | "partial" | "failed",
|
|
549
|
+
status: "completed" | "partial" | "failed" | "cancelled",
|
|
516
550
|
response: Response
|
|
517
551
|
): Promise<Response> {
|
|
518
552
|
if (!session) return response;
|
|
519
|
-
const terminalStatus =
|
|
553
|
+
const terminalStatus =
|
|
554
|
+
request.signal.aborted || status === "cancelled" ? "cancelled" : status;
|
|
520
555
|
const finished = await session.finish(terminalStatus);
|
|
521
556
|
if (!finished.ok) {
|
|
522
557
|
return withRetrievalTraceHeader(
|
|
@@ -3503,6 +3538,9 @@ export async function handleSearch(
|
|
|
3503
3538
|
return errorResponse("VALIDATION", "Invalid JSON body");
|
|
3504
3539
|
}
|
|
3505
3540
|
|
|
3541
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
3542
|
+
return errorResponse("VALIDATION", "Request body must be an object");
|
|
3543
|
+
}
|
|
3506
3544
|
if (!body.query || typeof body.query !== "string") {
|
|
3507
3545
|
return errorResponse("VALIDATION", "Missing or invalid query");
|
|
3508
3546
|
}
|
|
@@ -4028,15 +4066,84 @@ export async function handleAsk(
|
|
|
4028
4066
|
return errorResponse("VALIDATION", "Invalid JSON body");
|
|
4029
4067
|
}
|
|
4030
4068
|
|
|
4069
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
4070
|
+
return errorResponse("VALIDATION", "Request body must be an object");
|
|
4071
|
+
}
|
|
4072
|
+
const unknownKey = Object.keys(body).find(
|
|
4073
|
+
(key) => !ASK_REQUEST_KEYS.has(key as keyof AskRequestBody)
|
|
4074
|
+
);
|
|
4075
|
+
if (unknownKey) {
|
|
4076
|
+
return errorResponse(
|
|
4077
|
+
"VALIDATION",
|
|
4078
|
+
`Unknown Ask request field: ${unknownKey}`
|
|
4079
|
+
);
|
|
4080
|
+
}
|
|
4031
4081
|
if (!body.query || typeof body.query !== "string") {
|
|
4032
4082
|
return errorResponse("VALIDATION", "Missing or invalid query");
|
|
4033
4083
|
}
|
|
4034
4084
|
|
|
4085
|
+
for (const field of [
|
|
4086
|
+
"verify",
|
|
4087
|
+
"noExpand",
|
|
4088
|
+
"noRerank",
|
|
4089
|
+
"graph",
|
|
4090
|
+
"noGraph",
|
|
4091
|
+
] as const) {
|
|
4092
|
+
if (body[field] !== undefined && typeof body[field] !== "boolean") {
|
|
4093
|
+
return errorResponse("VALIDATION", `${field} must be a boolean`);
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
for (const [field, value, maximum] of [
|
|
4097
|
+
["limit", body.limit, 20],
|
|
4098
|
+
["candidateLimit", body.candidateLimit, 100],
|
|
4099
|
+
["maxAnswerTokens", body.maxAnswerTokens, Number.MAX_SAFE_INTEGER],
|
|
4100
|
+
] as const) {
|
|
4101
|
+
if (
|
|
4102
|
+
value !== undefined &&
|
|
4103
|
+
(!Number.isSafeInteger(value) || value < 1 || value > maximum)
|
|
4104
|
+
) {
|
|
4105
|
+
return errorResponse(
|
|
4106
|
+
"VALIDATION",
|
|
4107
|
+
`${field} must be a positive integer${maximum < Number.MAX_SAFE_INTEGER ? ` no greater than ${maximum}` : ""}`
|
|
4108
|
+
);
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
4111
|
+
|
|
4035
4112
|
const rawQuery = body.query.trim();
|
|
4036
4113
|
if (!rawQuery) {
|
|
4037
4114
|
return errorResponse("VALIDATION", "Query cannot be empty");
|
|
4038
4115
|
}
|
|
4039
4116
|
|
|
4117
|
+
if (
|
|
4118
|
+
body.minScore !== undefined &&
|
|
4119
|
+
(typeof body.minScore !== "number" ||
|
|
4120
|
+
!Number.isFinite(body.minScore) ||
|
|
4121
|
+
body.minScore < 0 ||
|
|
4122
|
+
body.minScore > 1)
|
|
4123
|
+
) {
|
|
4124
|
+
return errorResponse("VALIDATION", "minScore must be between 0 and 1");
|
|
4125
|
+
}
|
|
4126
|
+
if (
|
|
4127
|
+
body.contextBudgetTokens !== undefined &&
|
|
4128
|
+
(!Number.isSafeInteger(body.contextBudgetTokens) ||
|
|
4129
|
+
body.contextBudgetTokens < 1)
|
|
4130
|
+
) {
|
|
4131
|
+
return errorResponse(
|
|
4132
|
+
"VALIDATION",
|
|
4133
|
+
"contextBudgetTokens must be a positive integer"
|
|
4134
|
+
);
|
|
4135
|
+
}
|
|
4136
|
+
if (
|
|
4137
|
+
body.contextBudgetBytes !== undefined &&
|
|
4138
|
+
(!Number.isSafeInteger(body.contextBudgetBytes) ||
|
|
4139
|
+
body.contextBudgetBytes < 1)
|
|
4140
|
+
) {
|
|
4141
|
+
return errorResponse(
|
|
4142
|
+
"VALIDATION",
|
|
4143
|
+
"contextBudgetBytes must be a positive integer"
|
|
4144
|
+
);
|
|
4145
|
+
}
|
|
4146
|
+
|
|
4040
4147
|
// Parse tag filters
|
|
4041
4148
|
let tagsAll: string[] | undefined;
|
|
4042
4149
|
let tagsAny: string[] | undefined;
|
|
@@ -4128,6 +4235,7 @@ export async function handleAsk(
|
|
|
4128
4235
|
collection: body.collection,
|
|
4129
4236
|
lang: body.lang,
|
|
4130
4237
|
intent: body.intent?.trim() || undefined,
|
|
4238
|
+
minScore: body.minScore,
|
|
4131
4239
|
noExpand: body.noExpand,
|
|
4132
4240
|
noRerank: body.noRerank,
|
|
4133
4241
|
candidateLimit:
|
|
@@ -4135,6 +4243,8 @@ export async function handleAsk(
|
|
|
4135
4243
|
? Math.min(body.candidateLimit, 100)
|
|
4136
4244
|
: undefined,
|
|
4137
4245
|
exclude,
|
|
4246
|
+
graph: body.graph,
|
|
4247
|
+
noGraph: body.noGraph,
|
|
4138
4248
|
queryModes: normalizedQueryModes,
|
|
4139
4249
|
tagsAll,
|
|
4140
4250
|
tagsAny,
|
|
@@ -4142,6 +4252,10 @@ export async function handleAsk(
|
|
|
4142
4252
|
until: body.until,
|
|
4143
4253
|
categories,
|
|
4144
4254
|
author,
|
|
4255
|
+
verify: body.verify,
|
|
4256
|
+
contextBudgetTokens: body.contextBudgetTokens,
|
|
4257
|
+
contextBudgetBytes: body.contextBudgetBytes,
|
|
4258
|
+
maxAnswerTokens: body.maxAnswerTokens,
|
|
4145
4259
|
};
|
|
4146
4260
|
const trace = await startRestTrace(ctx, {
|
|
4147
4261
|
query: normalizedQuery,
|
|
@@ -4166,7 +4280,7 @@ export async function handleAsk(
|
|
|
4166
4280
|
return finishRestTrace(
|
|
4167
4281
|
req,
|
|
4168
4282
|
trace.session,
|
|
4169
|
-
"failed",
|
|
4283
|
+
req.signal.aborted ? "cancelled" : "failed",
|
|
4170
4284
|
errorResponse("RUNTIME", unavailable.error.message, 500)
|
|
4171
4285
|
);
|
|
4172
4286
|
}
|
|
@@ -4182,6 +4296,38 @@ export async function handleAsk(
|
|
|
4182
4296
|
);
|
|
4183
4297
|
}
|
|
4184
4298
|
|
|
4299
|
+
if (body.verify && ctx.answerPort) {
|
|
4300
|
+
try {
|
|
4301
|
+
const verified = await buildVerifiedAsk(normalizedQuery, askOptions, {
|
|
4302
|
+
store: ctx.store,
|
|
4303
|
+
config: ctx.config,
|
|
4304
|
+
indexName: ctx.indexName,
|
|
4305
|
+
vectorIndex: ctx.vectorIndex,
|
|
4306
|
+
embedPort: ctx.embedPort,
|
|
4307
|
+
rerankPort: ctx.rerankPort,
|
|
4308
|
+
genPort: ctx.answerPort,
|
|
4309
|
+
traceSession: trace.session ?? undefined,
|
|
4310
|
+
});
|
|
4311
|
+
return finishRestTrace(
|
|
4312
|
+
req,
|
|
4313
|
+
trace.session,
|
|
4314
|
+
answerTraceTerminalStatus(verified.citations),
|
|
4315
|
+
jsonResponse(verified)
|
|
4316
|
+
);
|
|
4317
|
+
} catch (error) {
|
|
4318
|
+
return finishRestTrace(
|
|
4319
|
+
req,
|
|
4320
|
+
trace.session,
|
|
4321
|
+
"failed",
|
|
4322
|
+
errorResponse(
|
|
4323
|
+
"RUNTIME",
|
|
4324
|
+
error instanceof Error ? error.message : String(error),
|
|
4325
|
+
500
|
|
4326
|
+
)
|
|
4327
|
+
);
|
|
4328
|
+
}
|
|
4329
|
+
}
|
|
4330
|
+
|
|
4185
4331
|
// Run hybrid search first
|
|
4186
4332
|
const searchResult = await searchHybrid(
|
|
4187
4333
|
{
|