@gmickel/gno 1.18.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 +14 -7
- package/assets/skill/SKILL.md +54 -12
- package/assets/skill/mcp-reference.md +7 -2
- package/assets/skill/recipes/citation-and-provenance.md +32 -9
- package/package.json +2 -1
- package/spec/AGENTS.md +83 -0
- package/spec/CLAUDE.md +83 -0
- package/spec/bench-fixture.schema.json +137 -0
- package/spec/cli.md +2919 -0
- package/spec/db/schema.sql +442 -0
- package/spec/evals-agentic.md +592 -0
- package/spec/evals.md +1106 -0
- package/spec/mcp.md +2279 -0
- package/spec/output-schemas/activation-verification.schema.json +515 -0
- package/spec/output-schemas/ask.schema.json +564 -0
- package/spec/output-schemas/backlinks.schema.json +131 -0
- package/spec/output-schemas/bench-result.schema.json +120 -0
- package/spec/output-schemas/capture-receipt.schema.json +143 -0
- package/spec/output-schemas/claim-verification.schema.json +291 -0
- package/spec/output-schemas/collection-list.schema.json +45 -0
- package/spec/output-schemas/context-capsule-v1.schema.json +726 -0
- package/spec/output-schemas/context-capsule-verification.schema.json +1338 -0
- package/spec/output-schemas/context-list.schema.json +21 -0
- package/spec/output-schemas/doctor.schema.json +313 -0
- package/spec/output-schemas/error.schema.json +30 -0
- package/spec/output-schemas/expansion.schema.json +37 -0
- package/spec/output-schemas/get.schema.json +140 -0
- package/spec/output-schemas/graph-query.schema.json +99 -0
- package/spec/output-schemas/graph.schema.json +371 -0
- package/spec/output-schemas/links-list.schema.json +186 -0
- package/spec/output-schemas/mcp-add-collection-result.schema.json +23 -0
- package/spec/output-schemas/mcp-capture-result.schema.json +152 -0
- package/spec/output-schemas/mcp-http-error.schema.json +30 -0
- package/spec/output-schemas/mcp-job-list.schema.json +58 -0
- package/spec/output-schemas/mcp-job-status.schema.json +224 -0
- package/spec/output-schemas/mcp-remove-result.schema.json +39 -0
- package/spec/output-schemas/mcp-sync-result.schema.json +41 -0
- package/spec/output-schemas/mcp-tag-result.schema.json +33 -0
- package/spec/output-schemas/models-list.schema.json +93 -0
- package/spec/output-schemas/multi-get.schema.json +103 -0
- package/spec/output-schemas/process-status.schema.json +119 -0
- package/spec/output-schemas/query-diagnose.schema.json +123 -0
- package/spec/output-schemas/resident-status.schema.json +154 -0
- package/spec/output-schemas/retrieval-trace-common.schema.json +492 -0
- package/spec/output-schemas/retrieval-trace-delete.schema.json +16 -0
- package/spec/output-schemas/retrieval-trace-export.schema.json +61 -0
- package/spec/output-schemas/retrieval-trace-filters.schema.json +139 -0
- package/spec/output-schemas/retrieval-trace-judgment.schema.json +15 -0
- package/spec/output-schemas/retrieval-trace-list.schema.json +18 -0
- package/spec/output-schemas/retrieval-trace-payloads.schema.json +178 -0
- package/spec/output-schemas/retrieval-trace-purge.schema.json +31 -0
- package/spec/output-schemas/retrieval-trace-qrels.schema.json +303 -0
- package/spec/output-schemas/retrieval-trace-replay.schema.json +286 -0
- package/spec/output-schemas/retrieval-trace-show.schema.json +69 -0
- package/spec/output-schemas/retrieval-trace-summary.schema.json +65 -0
- package/spec/output-schemas/search-result.schema.json +154 -0
- package/spec/output-schemas/search-results.schema.json +338 -0
- package/spec/output-schemas/similar.schema.json +84 -0
- package/spec/output-schemas/status.schema.json +676 -0
- package/spec/output-schemas/tags-list.schema.json +48 -0
- 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 +7 -0
- package/src/app/context-runtime.ts +20 -2
- 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 +144 -183
- package/src/cli/commands/context-build.ts +56 -9
- package/src/cli/commands/get.ts +64 -3
- package/src/cli/commands/query.ts +62 -23
- package/src/cli/commands/replay.ts +140 -0
- package/src/cli/commands/search.ts +48 -3
- package/src/cli/commands/shared.ts +3 -1
- package/src/cli/commands/trace.ts +200 -0
- package/src/cli/commands/vsearch.ts +75 -53
- package/src/cli/program.ts +287 -1
- package/src/config/index.ts +9 -0
- package/src/config/retrieval-traces.ts +56 -0
- package/src/config/types.ts +4 -0
- 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 +44 -25
- package/src/core/context-evidence.ts +6 -0
- package/src/core/retrieval-qrels.ts +405 -0
- package/src/core/retrieval-replay-candidate.ts +368 -0
- package/src/core/retrieval-replay-types.ts +109 -0
- package/src/core/retrieval-replay-validation.ts +89 -0
- package/src/core/retrieval-replay.ts +441 -0
- package/src/core/retrieval-trace-evidence-origin.ts +178 -0
- package/src/core/retrieval-trace-export.ts +113 -0
- package/src/core/retrieval-trace-filter-normalization.ts +27 -0
- package/src/core/retrieval-trace-filters.ts +19 -0
- package/src/core/retrieval-trace-management-helpers.ts +247 -0
- package/src/core/retrieval-trace-management-types.ts +132 -0
- package/src/core/retrieval-trace-management.ts +422 -0
- package/src/core/retrieval-trace-request.ts +141 -0
- package/src/core/retrieval-trace-session.ts +507 -0
- package/src/core/retrieval-trace.ts +472 -0
- 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 +87 -15
- package/src/mcp/tools/get.ts +35 -1
- package/src/mcp/tools/index.ts +83 -0
- package/src/mcp/tools/query.ts +95 -64
- package/src/mcp/tools/search.ts +36 -13
- package/src/mcp/tools/trace.ts +143 -0
- package/src/mcp/tools/vsearch.ts +71 -38
- package/src/pipeline/answer.ts +167 -26
- 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/graph-retrieval.ts +15 -1
- package/src/pipeline/hybrid.ts +151 -43
- package/src/pipeline/search.ts +36 -3
- package/src/pipeline/trace-metadata.ts +47 -0
- package/src/pipeline/types.ts +68 -0
- package/src/pipeline/vsearch.ts +101 -38
- package/src/sdk/client.ts +415 -73
- package/src/sdk/documents.ts +48 -1
- package/src/sdk/index.ts +17 -0
- package/src/sdk/types.ts +28 -0
- package/src/serve/context-capsule.ts +67 -8
- package/src/serve/public/app.tsx +12 -1
- package/src/serve/public/components/AskVerificationPanel.tsx +189 -0
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/lib/workspace-tabs.ts +2 -0
- package/src/serve/public/pages/Ask.tsx +42 -4
- package/src/serve/public/pages/Dashboard.tsx +10 -0
- package/src/serve/public/pages/TraceHistory.tsx +478 -0
- package/src/serve/public/pages/trace-history-detail.tsx +224 -0
- package/src/serve/retrieval-trace.ts +28 -0
- package/src/serve/routes/api.ts +508 -68
- package/src/serve/routes/traces.ts +156 -0
- package/src/serve/server.ts +87 -2
- package/src/store/index.ts +31 -0
- package/src/store/migrations/014-retrieval-traces.ts +303 -0
- package/src/store/migrations/index.ts +2 -0
- package/src/store/retrieval-trace-codec.ts +384 -0
- package/src/store/sqlite/adapter.ts +153 -1
- package/src/store/sqlite/retrieval-trace-management-store.ts +341 -0
- package/src/store/sqlite/retrieval-trace-retention.ts +349 -0
- package/src/store/sqlite/retrieval-trace-rows.ts +267 -0
- package/src/store/sqlite/retrieval-trace-store.ts +515 -0
- package/src/store/types.ts +297 -0
- package/src/store/vector/sqlite-vec.ts +76 -1
- package/src/store/vector/types.ts +1 -1
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,
|
|
@@ -94,6 +95,14 @@ import {
|
|
|
94
95
|
resolveNotePreset,
|
|
95
96
|
type NotePresetId,
|
|
96
97
|
} from "../../core/note-presets";
|
|
98
|
+
import {
|
|
99
|
+
retrievalTraceFilters,
|
|
100
|
+
startRetrievalTraceRequest,
|
|
101
|
+
} from "../../core/retrieval-trace-request";
|
|
102
|
+
import {
|
|
103
|
+
evidenceFromExactDocument,
|
|
104
|
+
RetrievalTraceSession,
|
|
105
|
+
} from "../../core/retrieval-trace-session";
|
|
97
106
|
import { extractSections } from "../../core/sections";
|
|
98
107
|
import { normalizeStructuredQueryInput } from "../../core/structured-query";
|
|
99
108
|
import {
|
|
@@ -117,8 +126,9 @@ import {
|
|
|
117
126
|
resolveModelUri,
|
|
118
127
|
} from "../../llm/registry";
|
|
119
128
|
import {
|
|
129
|
+
answerTraceTerminalStatus,
|
|
120
130
|
generateGroundedAnswer,
|
|
121
|
-
|
|
131
|
+
processAnswerResultWithTrace,
|
|
122
132
|
} from "../../pipeline/answer";
|
|
123
133
|
import { diagnoseQueryTarget } from "../../pipeline/diagnose";
|
|
124
134
|
import { searchHybrid } from "../../pipeline/hybrid";
|
|
@@ -145,6 +155,10 @@ import {
|
|
|
145
155
|
} from "../context";
|
|
146
156
|
import { analyzeImportPath } from "../import-preview";
|
|
147
157
|
import { getActiveJob, getJobStatus, startJob } from "../jobs";
|
|
158
|
+
import {
|
|
159
|
+
requestRetrievalTraceId,
|
|
160
|
+
withRetrievalTraceHeader,
|
|
161
|
+
} from "../retrieval-trace";
|
|
148
162
|
import { buildAppStatus, type StatusBuildDeps } from "../status";
|
|
149
163
|
|
|
150
164
|
/** Mutable context holder for hot-reloading presets */
|
|
@@ -277,14 +291,46 @@ export interface AskRequestBody {
|
|
|
277
291
|
category?: string;
|
|
278
292
|
author?: string;
|
|
279
293
|
maxAnswerTokens?: number;
|
|
294
|
+
verify?: boolean;
|
|
295
|
+
contextBudgetTokens?: number;
|
|
296
|
+
contextBudgetBytes?: number;
|
|
297
|
+
minScore?: number;
|
|
280
298
|
noExpand?: boolean;
|
|
281
299
|
noRerank?: boolean;
|
|
300
|
+
graph?: boolean;
|
|
301
|
+
noGraph?: boolean;
|
|
282
302
|
/** Comma-separated tags - filter to docs having ALL (AND) */
|
|
283
303
|
tagsAll?: string;
|
|
284
304
|
/** Comma-separated tags - filter to docs having ANY (OR) */
|
|
285
305
|
tagsAny?: string;
|
|
286
306
|
}
|
|
287
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
|
+
|
|
288
334
|
export interface CreateCollectionRequestBody {
|
|
289
335
|
path: string;
|
|
290
336
|
name?: string;
|
|
@@ -460,6 +506,66 @@ function errorResponse(
|
|
|
460
506
|
);
|
|
461
507
|
}
|
|
462
508
|
|
|
509
|
+
interface RestTraceStart {
|
|
510
|
+
session: RetrievalTraceSession | null;
|
|
511
|
+
error: Response | null;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async function startRestTrace(
|
|
515
|
+
ctx: ServerContext,
|
|
516
|
+
input: {
|
|
517
|
+
query: string;
|
|
518
|
+
goal?: string;
|
|
519
|
+
filters?: Record<string, unknown>;
|
|
520
|
+
pipeline: string;
|
|
521
|
+
modelUris?: string[];
|
|
522
|
+
}
|
|
523
|
+
): Promise<RestTraceStart> {
|
|
524
|
+
const started = await startRetrievalTraceRequest({
|
|
525
|
+
store: ctx.store,
|
|
526
|
+
config: ctx.config,
|
|
527
|
+
query: input.query,
|
|
528
|
+
goal: input.goal,
|
|
529
|
+
filters: input.filters,
|
|
530
|
+
pipeline: input.pipeline,
|
|
531
|
+
indexName: ctx.indexName,
|
|
532
|
+
modelUris: input.modelUris,
|
|
533
|
+
});
|
|
534
|
+
return started.ok
|
|
535
|
+
? { session: started.value, error: null }
|
|
536
|
+
: {
|
|
537
|
+
session: null,
|
|
538
|
+
error: errorResponse(
|
|
539
|
+
"RUNTIME",
|
|
540
|
+
`Retrieval trace start failed: ${started.error.message}`,
|
|
541
|
+
500
|
|
542
|
+
),
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async function finishRestTrace(
|
|
547
|
+
request: Request,
|
|
548
|
+
session: RetrievalTraceSession | null,
|
|
549
|
+
status: "completed" | "partial" | "failed" | "cancelled",
|
|
550
|
+
response: Response
|
|
551
|
+
): Promise<Response> {
|
|
552
|
+
if (!session) return response;
|
|
553
|
+
const terminalStatus =
|
|
554
|
+
request.signal.aborted || status === "cancelled" ? "cancelled" : status;
|
|
555
|
+
const finished = await session.finish(terminalStatus);
|
|
556
|
+
if (!finished.ok) {
|
|
557
|
+
return withRetrievalTraceHeader(
|
|
558
|
+
errorResponse(
|
|
559
|
+
"RUNTIME",
|
|
560
|
+
`Retrieval trace finalization failed: ${finished.error.message}`,
|
|
561
|
+
500
|
|
562
|
+
),
|
|
563
|
+
session
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
return withRetrievalTraceHeader(response, session);
|
|
567
|
+
}
|
|
568
|
+
|
|
463
569
|
function jobConflictResponse(jobResult: StartJobError): Response {
|
|
464
570
|
return errorResponse("CONFLICT", jobResult.error, 409, {
|
|
465
571
|
activeJobId: jobResult.activeJobId,
|
|
@@ -1639,7 +1745,8 @@ export async function handleDocsAutocomplete(
|
|
|
1639
1745
|
export async function handleDoc(
|
|
1640
1746
|
store: SqliteAdapter,
|
|
1641
1747
|
config: Config,
|
|
1642
|
-
url: URL
|
|
1748
|
+
url: URL,
|
|
1749
|
+
request?: Request
|
|
1643
1750
|
): Promise<Response> {
|
|
1644
1751
|
const uri = url.searchParams.get("uri");
|
|
1645
1752
|
if (!uri) {
|
|
@@ -1679,7 +1786,7 @@ export async function handleDoc(
|
|
|
1679
1786
|
});
|
|
1680
1787
|
const source = await buildSourceMeta(config.collections, doc);
|
|
1681
1788
|
|
|
1682
|
-
|
|
1789
|
+
const responseData = {
|
|
1683
1790
|
docid: doc.docid,
|
|
1684
1791
|
uri: doc.uri,
|
|
1685
1792
|
title: doc.title,
|
|
@@ -1690,7 +1797,52 @@ export async function handleDoc(
|
|
|
1690
1797
|
tags,
|
|
1691
1798
|
source,
|
|
1692
1799
|
capabilities,
|
|
1800
|
+
};
|
|
1801
|
+
const continuationTraceId = request
|
|
1802
|
+
? requestRetrievalTraceId(request)
|
|
1803
|
+
: undefined;
|
|
1804
|
+
if (!continuationTraceId) return jsonResponse(responseData);
|
|
1805
|
+
|
|
1806
|
+
const resumed = await RetrievalTraceSession.resume({
|
|
1807
|
+
store,
|
|
1808
|
+
config: config.retrievalTraces,
|
|
1809
|
+
traceId: continuationTraceId,
|
|
1693
1810
|
});
|
|
1811
|
+
if (!resumed.ok) {
|
|
1812
|
+
return errorResponse("RUNTIME", resumed.error.message, 500);
|
|
1813
|
+
}
|
|
1814
|
+
const traceSession = resumed.value;
|
|
1815
|
+
if (!traceSession) return jsonResponse(responseData);
|
|
1816
|
+
|
|
1817
|
+
if (content !== null) {
|
|
1818
|
+
const endLine = content.split("\n").length;
|
|
1819
|
+
const evidence = evidenceFromExactDocument({
|
|
1820
|
+
docid: doc.docid,
|
|
1821
|
+
uri: doc.uri,
|
|
1822
|
+
sourceHash: doc.sourceHash,
|
|
1823
|
+
mirrorHash: doc.mirrorHash ?? undefined,
|
|
1824
|
+
content,
|
|
1825
|
+
startLine: 1,
|
|
1826
|
+
endLine,
|
|
1827
|
+
});
|
|
1828
|
+
if (evidence) {
|
|
1829
|
+
const got = await traceSession.recordEvidence("get", [evidence]);
|
|
1830
|
+
if (!got.ok) {
|
|
1831
|
+
return withRetrievalTraceHeader(
|
|
1832
|
+
errorResponse("RUNTIME", got.error.message, 500),
|
|
1833
|
+
traceSession
|
|
1834
|
+
);
|
|
1835
|
+
}
|
|
1836
|
+
const opened = await traceSession.recordEvidence("open", [evidence]);
|
|
1837
|
+
if (!opened.ok) {
|
|
1838
|
+
return withRetrievalTraceHeader(
|
|
1839
|
+
errorResponse("RUNTIME", opened.error.message, 500),
|
|
1840
|
+
traceSession
|
|
1841
|
+
);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
return withRetrievalTraceHeader(jsonResponse(responseData), traceSession);
|
|
1694
1846
|
}
|
|
1695
1847
|
|
|
1696
1848
|
/**
|
|
@@ -3372,9 +3524,13 @@ export async function handleCreateDoc(
|
|
|
3372
3524
|
* Returns search results.
|
|
3373
3525
|
*/
|
|
3374
3526
|
export async function handleSearch(
|
|
3375
|
-
|
|
3527
|
+
contextOrStore: ServerContext | SqliteAdapter,
|
|
3376
3528
|
req: Request
|
|
3377
3529
|
): Promise<Response> {
|
|
3530
|
+
const context =
|
|
3531
|
+
"store" in contextOrStore ? contextOrStore : (null as ServerContext | null);
|
|
3532
|
+
const store =
|
|
3533
|
+
"store" in contextOrStore ? contextOrStore.store : contextOrStore;
|
|
3378
3534
|
let body: SearchRequestBody;
|
|
3379
3535
|
try {
|
|
3380
3536
|
body = (await req.json()) as SearchRequestBody;
|
|
@@ -3382,6 +3538,9 @@ export async function handleSearch(
|
|
|
3382
3538
|
return errorResponse("VALIDATION", "Invalid JSON body");
|
|
3383
3539
|
}
|
|
3384
3540
|
|
|
3541
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
3542
|
+
return errorResponse("VALIDATION", "Request body must be an object");
|
|
3543
|
+
}
|
|
3385
3544
|
if (!body.query || typeof body.query !== "string") {
|
|
3386
3545
|
return errorResponse("VALIDATION", "Missing or invalid query");
|
|
3387
3546
|
}
|
|
@@ -3486,13 +3645,30 @@ export async function handleSearch(
|
|
|
3486
3645
|
author,
|
|
3487
3646
|
};
|
|
3488
3647
|
|
|
3489
|
-
const
|
|
3648
|
+
const trace = context
|
|
3649
|
+
? await startRestTrace(context, {
|
|
3650
|
+
query: rawQuery,
|
|
3651
|
+
filters: retrievalTraceFilters(options),
|
|
3652
|
+
pipeline: "bm25",
|
|
3653
|
+
})
|
|
3654
|
+
: { session: null, error: null };
|
|
3655
|
+
if (trace.error) return trace.error;
|
|
3656
|
+
|
|
3657
|
+
const result = await searchBm25(store, rawQuery, {
|
|
3658
|
+
...options,
|
|
3659
|
+
traceSession: trace.session ?? undefined,
|
|
3660
|
+
});
|
|
3490
3661
|
|
|
3491
3662
|
if (!result.ok) {
|
|
3492
|
-
return
|
|
3663
|
+
return finishRestTrace(
|
|
3664
|
+
req,
|
|
3665
|
+
trace.session,
|
|
3666
|
+
"failed",
|
|
3667
|
+
errorResponse("RUNTIME", result.error.message, 500)
|
|
3668
|
+
);
|
|
3493
3669
|
}
|
|
3494
3670
|
|
|
3495
|
-
return jsonResponse(result.value);
|
|
3671
|
+
return withRetrievalTraceHeader(jsonResponse(result.value), trace.session);
|
|
3496
3672
|
}
|
|
3497
3673
|
|
|
3498
3674
|
/**
|
|
@@ -3626,6 +3802,41 @@ export async function handleQuery(
|
|
|
3626
3802
|
: undefined;
|
|
3627
3803
|
const author = body.author?.trim() || undefined;
|
|
3628
3804
|
|
|
3805
|
+
const queryOptions = {
|
|
3806
|
+
limit: Math.min(body.limit ?? 20, 50),
|
|
3807
|
+
minScore: body.minScore,
|
|
3808
|
+
collection: body.collection,
|
|
3809
|
+
lang: body.lang,
|
|
3810
|
+
intent: body.intent?.trim() || undefined,
|
|
3811
|
+
candidateLimit:
|
|
3812
|
+
body.candidateLimit !== undefined
|
|
3813
|
+
? Math.min(body.candidateLimit, 100)
|
|
3814
|
+
: undefined,
|
|
3815
|
+
exclude,
|
|
3816
|
+
queryModes: normalizedQueryModes,
|
|
3817
|
+
noExpand: body.noExpand,
|
|
3818
|
+
noRerank: body.noRerank,
|
|
3819
|
+
graph: body.graph === true,
|
|
3820
|
+
noGraph: body.noGraph,
|
|
3821
|
+
tagsAll,
|
|
3822
|
+
tagsAny,
|
|
3823
|
+
since: body.since,
|
|
3824
|
+
until: body.until,
|
|
3825
|
+
categories,
|
|
3826
|
+
author,
|
|
3827
|
+
};
|
|
3828
|
+
const trace = await startRestTrace(ctx, {
|
|
3829
|
+
query: normalizedQuery,
|
|
3830
|
+
filters: retrievalTraceFilters(queryOptions),
|
|
3831
|
+
pipeline: "hybrid",
|
|
3832
|
+
modelUris: [
|
|
3833
|
+
ctx.embedPort?.modelUri,
|
|
3834
|
+
ctx.expandPort?.modelUri,
|
|
3835
|
+
ctx.rerankPort?.modelUri,
|
|
3836
|
+
].filter((value): value is string => Boolean(value)),
|
|
3837
|
+
});
|
|
3838
|
+
if (trace.error) return trace.error;
|
|
3839
|
+
|
|
3629
3840
|
const result = await searchHybrid(
|
|
3630
3841
|
{
|
|
3631
3842
|
store: ctx.store,
|
|
@@ -3637,35 +3848,21 @@ export async function handleQuery(
|
|
|
3637
3848
|
},
|
|
3638
3849
|
normalizedQuery,
|
|
3639
3850
|
{
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
collection: body.collection,
|
|
3643
|
-
lang: body.lang,
|
|
3644
|
-
intent: body.intent?.trim() || undefined,
|
|
3645
|
-
candidateLimit:
|
|
3646
|
-
body.candidateLimit !== undefined
|
|
3647
|
-
? Math.min(body.candidateLimit, 100)
|
|
3648
|
-
: undefined,
|
|
3649
|
-
exclude,
|
|
3650
|
-
queryModes: normalizedQueryModes,
|
|
3651
|
-
noExpand: body.noExpand,
|
|
3652
|
-
noRerank: body.noRerank,
|
|
3653
|
-
graph: body.graph === true,
|
|
3654
|
-
noGraph: body.noGraph,
|
|
3655
|
-
tagsAll,
|
|
3656
|
-
tagsAny,
|
|
3657
|
-
since: body.since,
|
|
3658
|
-
until: body.until,
|
|
3659
|
-
categories,
|
|
3660
|
-
author,
|
|
3851
|
+
...queryOptions,
|
|
3852
|
+
traceSession: trace.session ?? undefined,
|
|
3661
3853
|
}
|
|
3662
3854
|
);
|
|
3663
3855
|
|
|
3664
3856
|
if (!result.ok) {
|
|
3665
|
-
return
|
|
3857
|
+
return finishRestTrace(
|
|
3858
|
+
req,
|
|
3859
|
+
trace.session,
|
|
3860
|
+
"failed",
|
|
3861
|
+
errorResponse("RUNTIME", result.error.message, 500)
|
|
3862
|
+
);
|
|
3666
3863
|
}
|
|
3667
3864
|
|
|
3668
|
-
return jsonResponse(result.value);
|
|
3865
|
+
return withRetrievalTraceHeader(jsonResponse(result.value), trace.session);
|
|
3669
3866
|
}
|
|
3670
3867
|
|
|
3671
3868
|
/**
|
|
@@ -3869,21 +4066,81 @@ export async function handleAsk(
|
|
|
3869
4066
|
return errorResponse("VALIDATION", "Invalid JSON body");
|
|
3870
4067
|
}
|
|
3871
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
|
+
}
|
|
3872
4081
|
if (!body.query || typeof body.query !== "string") {
|
|
3873
4082
|
return errorResponse("VALIDATION", "Missing or invalid query");
|
|
3874
4083
|
}
|
|
3875
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
|
+
|
|
3876
4112
|
const rawQuery = body.query.trim();
|
|
3877
4113
|
if (!rawQuery) {
|
|
3878
4114
|
return errorResponse("VALIDATION", "Query cannot be empty");
|
|
3879
4115
|
}
|
|
3880
4116
|
|
|
3881
|
-
|
|
3882
|
-
|
|
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
|
+
) {
|
|
3883
4131
|
return errorResponse(
|
|
3884
|
-
"
|
|
3885
|
-
"
|
|
3886
|
-
|
|
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"
|
|
3887
4144
|
);
|
|
3888
4145
|
}
|
|
3889
4146
|
|
|
@@ -3973,6 +4230,103 @@ export async function handleAsk(
|
|
|
3973
4230
|
const author = body.author?.trim() || undefined;
|
|
3974
4231
|
|
|
3975
4232
|
const limit = Math.min(body.limit ?? 5, 20);
|
|
4233
|
+
const askOptions = {
|
|
4234
|
+
limit,
|
|
4235
|
+
collection: body.collection,
|
|
4236
|
+
lang: body.lang,
|
|
4237
|
+
intent: body.intent?.trim() || undefined,
|
|
4238
|
+
minScore: body.minScore,
|
|
4239
|
+
noExpand: body.noExpand,
|
|
4240
|
+
noRerank: body.noRerank,
|
|
4241
|
+
candidateLimit:
|
|
4242
|
+
body.candidateLimit !== undefined
|
|
4243
|
+
? Math.min(body.candidateLimit, 100)
|
|
4244
|
+
: undefined,
|
|
4245
|
+
exclude,
|
|
4246
|
+
graph: body.graph,
|
|
4247
|
+
noGraph: body.noGraph,
|
|
4248
|
+
queryModes: normalizedQueryModes,
|
|
4249
|
+
tagsAll,
|
|
4250
|
+
tagsAny,
|
|
4251
|
+
since: body.since,
|
|
4252
|
+
until: body.until,
|
|
4253
|
+
categories,
|
|
4254
|
+
author,
|
|
4255
|
+
verify: body.verify,
|
|
4256
|
+
contextBudgetTokens: body.contextBudgetTokens,
|
|
4257
|
+
contextBudgetBytes: body.contextBudgetBytes,
|
|
4258
|
+
maxAnswerTokens: body.maxAnswerTokens,
|
|
4259
|
+
};
|
|
4260
|
+
const trace = await startRestTrace(ctx, {
|
|
4261
|
+
query: normalizedQuery,
|
|
4262
|
+
filters: retrievalTraceFilters(askOptions),
|
|
4263
|
+
pipeline: "ask",
|
|
4264
|
+
modelUris: [
|
|
4265
|
+
ctx.embedPort?.modelUri,
|
|
4266
|
+
ctx.expandPort?.modelUri,
|
|
4267
|
+
ctx.answerPort?.modelUri,
|
|
4268
|
+
ctx.rerankPort?.modelUri,
|
|
4269
|
+
].filter((value): value is string => Boolean(value)),
|
|
4270
|
+
});
|
|
4271
|
+
if (trace.error) return trace.error;
|
|
4272
|
+
|
|
4273
|
+
if (!ctx.capabilities.answer) {
|
|
4274
|
+
const unavailable = await trace.session?.recordCapability(
|
|
4275
|
+
"answer_generation",
|
|
4276
|
+
"unavailable",
|
|
4277
|
+
"model_unavailable"
|
|
4278
|
+
);
|
|
4279
|
+
if (unavailable && !unavailable.ok) {
|
|
4280
|
+
return finishRestTrace(
|
|
4281
|
+
req,
|
|
4282
|
+
trace.session,
|
|
4283
|
+
req.signal.aborted ? "cancelled" : "failed",
|
|
4284
|
+
errorResponse("RUNTIME", unavailable.error.message, 500)
|
|
4285
|
+
);
|
|
4286
|
+
}
|
|
4287
|
+
return finishRestTrace(
|
|
4288
|
+
req,
|
|
4289
|
+
trace.session,
|
|
4290
|
+
"failed",
|
|
4291
|
+
errorResponse(
|
|
4292
|
+
"UNAVAILABLE",
|
|
4293
|
+
"Answer generation not available. No answer model loaded.",
|
|
4294
|
+
503
|
|
4295
|
+
)
|
|
4296
|
+
);
|
|
4297
|
+
}
|
|
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
|
+
}
|
|
3976
4330
|
|
|
3977
4331
|
// Run hybrid search first
|
|
3978
4332
|
const searchResult = await searchHybrid(
|
|
@@ -3986,29 +4340,18 @@ export async function handleAsk(
|
|
|
3986
4340
|
},
|
|
3987
4341
|
normalizedQuery,
|
|
3988
4342
|
{
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
lang: body.lang,
|
|
3992
|
-
intent: body.intent?.trim() || undefined,
|
|
3993
|
-
noExpand: body.noExpand,
|
|
3994
|
-
noRerank: body.noRerank,
|
|
3995
|
-
candidateLimit:
|
|
3996
|
-
body.candidateLimit !== undefined
|
|
3997
|
-
? Math.min(body.candidateLimit, 100)
|
|
3998
|
-
: undefined,
|
|
3999
|
-
exclude,
|
|
4000
|
-
queryModes: normalizedQueryModes,
|
|
4001
|
-
tagsAll,
|
|
4002
|
-
tagsAny,
|
|
4003
|
-
since: body.since,
|
|
4004
|
-
until: body.until,
|
|
4005
|
-
categories,
|
|
4006
|
-
author,
|
|
4343
|
+
...askOptions,
|
|
4344
|
+
traceSession: trace.session ?? undefined,
|
|
4007
4345
|
}
|
|
4008
4346
|
);
|
|
4009
4347
|
|
|
4010
4348
|
if (!searchResult.ok) {
|
|
4011
|
-
return
|
|
4349
|
+
return finishRestTrace(
|
|
4350
|
+
req,
|
|
4351
|
+
trace.session,
|
|
4352
|
+
"failed",
|
|
4353
|
+
errorResponse("RUNTIME", searchResult.error.message, 500)
|
|
4354
|
+
);
|
|
4012
4355
|
}
|
|
4013
4356
|
|
|
4014
4357
|
const results = searchResult.value.results;
|
|
@@ -4020,20 +4363,93 @@ export async function handleAsk(
|
|
|
4020
4363
|
let answerGenerated = false;
|
|
4021
4364
|
|
|
4022
4365
|
if (ctx.answerPort) {
|
|
4023
|
-
const
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
normalizedQuery,
|
|
4027
|
-
results,
|
|
4028
|
-
maxTokens
|
|
4366
|
+
const attempted = await trace.session?.recordCapability(
|
|
4367
|
+
"answer_generation",
|
|
4368
|
+
"attempted"
|
|
4029
4369
|
);
|
|
4370
|
+
if (attempted && !attempted.ok) {
|
|
4371
|
+
return finishRestTrace(
|
|
4372
|
+
req,
|
|
4373
|
+
trace.session,
|
|
4374
|
+
"failed",
|
|
4375
|
+
errorResponse("RUNTIME", attempted.error.message, 500)
|
|
4376
|
+
);
|
|
4377
|
+
}
|
|
4378
|
+
const maxTokens = body.maxAnswerTokens ?? 512;
|
|
4379
|
+
let rawResult: Awaited<ReturnType<typeof generateGroundedAnswer>>;
|
|
4380
|
+
try {
|
|
4381
|
+
rawResult = await generateGroundedAnswer(
|
|
4382
|
+
{ genPort: ctx.answerPort, store: ctx.store },
|
|
4383
|
+
normalizedQuery,
|
|
4384
|
+
results,
|
|
4385
|
+
maxTokens
|
|
4386
|
+
);
|
|
4387
|
+
} catch (error) {
|
|
4388
|
+
await trace.session?.recordCapability(
|
|
4389
|
+
"answer_generation",
|
|
4390
|
+
"failed",
|
|
4391
|
+
"generation_failed"
|
|
4392
|
+
);
|
|
4393
|
+
return finishRestTrace(
|
|
4394
|
+
req,
|
|
4395
|
+
trace.session,
|
|
4396
|
+
"failed",
|
|
4397
|
+
errorResponse(
|
|
4398
|
+
"RUNTIME",
|
|
4399
|
+
error instanceof Error ? error.message : String(error),
|
|
4400
|
+
500
|
|
4401
|
+
)
|
|
4402
|
+
);
|
|
4403
|
+
}
|
|
4030
4404
|
|
|
4031
4405
|
if (rawResult) {
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4406
|
+
try {
|
|
4407
|
+
const processed = await processAnswerResultWithTrace(
|
|
4408
|
+
rawResult,
|
|
4409
|
+
trace.session ?? undefined
|
|
4410
|
+
);
|
|
4411
|
+
answer = processed.answer;
|
|
4412
|
+
citations = processed.citations;
|
|
4413
|
+
answerContext = processed.answerContext;
|
|
4414
|
+
answerGenerated = true;
|
|
4415
|
+
const used = await trace.session?.recordCapability(
|
|
4416
|
+
"answer_generation",
|
|
4417
|
+
"used"
|
|
4418
|
+
);
|
|
4419
|
+
if (used && !used.ok) {
|
|
4420
|
+
throw new Error(`Trace recording failed: ${used.error.message}`);
|
|
4421
|
+
}
|
|
4422
|
+
} catch (error) {
|
|
4423
|
+
await trace.session?.recordCapability(
|
|
4424
|
+
"answer_generation",
|
|
4425
|
+
"failed",
|
|
4426
|
+
"generation_failed"
|
|
4427
|
+
);
|
|
4428
|
+
return finishRestTrace(
|
|
4429
|
+
req,
|
|
4430
|
+
trace.session,
|
|
4431
|
+
"failed",
|
|
4432
|
+
errorResponse(
|
|
4433
|
+
"RUNTIME",
|
|
4434
|
+
error instanceof Error ? error.message : String(error),
|
|
4435
|
+
500
|
|
4436
|
+
)
|
|
4437
|
+
);
|
|
4438
|
+
}
|
|
4439
|
+
} else {
|
|
4440
|
+
const failed = await trace.session?.recordCapability(
|
|
4441
|
+
"answer_generation",
|
|
4442
|
+
"failed",
|
|
4443
|
+
"generation_failed"
|
|
4444
|
+
);
|
|
4445
|
+
if (failed && !failed.ok) {
|
|
4446
|
+
return finishRestTrace(
|
|
4447
|
+
req,
|
|
4448
|
+
trace.session,
|
|
4449
|
+
"failed",
|
|
4450
|
+
errorResponse("RUNTIME", failed.error.message, 500)
|
|
4451
|
+
);
|
|
4452
|
+
}
|
|
4037
4453
|
}
|
|
4038
4454
|
}
|
|
4039
4455
|
|
|
@@ -4058,7 +4474,13 @@ export async function handleAsk(
|
|
|
4058
4474
|
},
|
|
4059
4475
|
};
|
|
4060
4476
|
|
|
4061
|
-
|
|
4477
|
+
const complete = answerGenerated && (citations?.length ?? 0) > 0;
|
|
4478
|
+
return finishRestTrace(
|
|
4479
|
+
req,
|
|
4480
|
+
trace.session,
|
|
4481
|
+
complete ? "completed" : "partial",
|
|
4482
|
+
jsonResponse(askResult)
|
|
4483
|
+
);
|
|
4062
4484
|
}
|
|
4063
4485
|
|
|
4064
4486
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -4671,7 +5093,7 @@ export async function routeApi(
|
|
|
4671
5093
|
}
|
|
4672
5094
|
|
|
4673
5095
|
if (path === "/api/doc") {
|
|
4674
|
-
return handleDoc(store, config, url);
|
|
5096
|
+
return handleDoc(store, config, url, req);
|
|
4675
5097
|
}
|
|
4676
5098
|
|
|
4677
5099
|
if (path === "/api/doc-asset") {
|
|
@@ -4679,7 +5101,25 @@ export async function routeApi(
|
|
|
4679
5101
|
}
|
|
4680
5102
|
|
|
4681
5103
|
if (path === "/api/search" && req.method === "POST") {
|
|
4682
|
-
return handleSearch(
|
|
5104
|
+
return handleSearch(
|
|
5105
|
+
{
|
|
5106
|
+
store,
|
|
5107
|
+
config,
|
|
5108
|
+
indexName: "default",
|
|
5109
|
+
vectorIndex: null,
|
|
5110
|
+
embedPort: null,
|
|
5111
|
+
expandPort: null,
|
|
5112
|
+
answerPort: null,
|
|
5113
|
+
rerankPort: null,
|
|
5114
|
+
capabilities: {
|
|
5115
|
+
bm25: true,
|
|
5116
|
+
vector: false,
|
|
5117
|
+
hybrid: false,
|
|
5118
|
+
answer: false,
|
|
5119
|
+
},
|
|
5120
|
+
},
|
|
5121
|
+
req
|
|
5122
|
+
);
|
|
4683
5123
|
}
|
|
4684
5124
|
|
|
4685
5125
|
// Unknown API route
|