@gmickel/gno 1.18.0 → 1.19.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 +5 -3
- package/assets/skill/SKILL.md +27 -0
- 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 +2894 -0
- package/spec/db/schema.sql +442 -0
- package/spec/evals-agentic.md +510 -0
- package/spec/evals.md +1106 -0
- package/spec/mcp.md +2229 -0
- package/spec/output-schemas/activation-verification.schema.json +515 -0
- package/spec/output-schemas/ask.schema.json +366 -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/collection-list.schema.json +45 -0
- package/spec/output-schemas/context-capsule-v1.schema.json +691 -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-types.ts +3 -0
- package/src/app/context-runtime.ts +15 -1
- package/src/cli/commands/ask.ts +106 -36
- 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 +255 -0
- 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-compiler.ts +11 -4
- 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 +175 -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 +494 -0
- package/src/core/retrieval-trace.ts +472 -0
- package/src/mcp/tools/context.ts +59 -8
- package/src/mcp/tools/get.ts +35 -1
- package/src/mcp/tools/index.ts +74 -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/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 +43 -0
- package/src/pipeline/vsearch.ts +101 -38
- package/src/sdk/client.ts +380 -71
- 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/globals.built.css +1 -1
- package/src/serve/public/lib/workspace-tabs.ts +2 -0
- 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 +366 -72
- 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/mcp/tools/vsearch.ts
CHANGED
|
@@ -6,11 +6,21 @@
|
|
|
6
6
|
|
|
7
7
|
import { join as pathJoin } from "node:path";
|
|
8
8
|
|
|
9
|
+
import type { EmbeddingPort } from "../../llm/types";
|
|
9
10
|
import type { SearchResult, SearchResults } from "../../pipeline/types";
|
|
10
11
|
import type { ToolContext } from "../server";
|
|
11
12
|
|
|
12
13
|
import { decorateUriForIndex, parseUri } from "../../app/constants";
|
|
13
14
|
import { createNonTtyProgressRenderer } from "../../cli/progress";
|
|
15
|
+
import {
|
|
16
|
+
finishRetrievalTraceAfterError,
|
|
17
|
+
retrievalTraceFilters,
|
|
18
|
+
startRetrievalTraceRequest,
|
|
19
|
+
} from "../../core/retrieval-trace-request";
|
|
20
|
+
import {
|
|
21
|
+
attachRetrievalTraceMetadata,
|
|
22
|
+
type RetrievalTraceSession,
|
|
23
|
+
} from "../../core/retrieval-trace-session";
|
|
14
24
|
import { LlmAdapter } from "../../llm/nodeLlamaCpp/adapter";
|
|
15
25
|
import { resolveDownloadPolicy } from "../../llm/policy";
|
|
16
26
|
import { resolveModelUri } from "../../llm/registry";
|
|
@@ -125,29 +135,55 @@ export function handleVsearch(
|
|
|
125
135
|
undefined,
|
|
126
136
|
args.collection
|
|
127
137
|
);
|
|
138
|
+
const options = {
|
|
139
|
+
limit: args.limit ?? 5,
|
|
140
|
+
minScore: args.minScore,
|
|
141
|
+
collection: args.collection,
|
|
142
|
+
intent: args.intent,
|
|
143
|
+
exclude: args.exclude,
|
|
144
|
+
since: args.since,
|
|
145
|
+
until: args.until,
|
|
146
|
+
categories: args.categories,
|
|
147
|
+
author: args.author,
|
|
148
|
+
tagsAll: normalizeTagFilters(args.tagsAll),
|
|
149
|
+
tagsAny: normalizeTagFilters(args.tagsAny),
|
|
150
|
+
};
|
|
151
|
+
let traceSession: RetrievalTraceSession | undefined;
|
|
152
|
+
const traceStart = await startRetrievalTraceRequest({
|
|
153
|
+
store: ctx.store,
|
|
154
|
+
config: ctx.config,
|
|
155
|
+
query: args.query,
|
|
156
|
+
filters: retrievalTraceFilters(options),
|
|
157
|
+
pipeline: "vector",
|
|
158
|
+
indexName: ctx.indexName,
|
|
159
|
+
modelUris: [modelUri],
|
|
160
|
+
});
|
|
161
|
+
if (!traceStart.ok) throw new Error(traceStart.error.message);
|
|
162
|
+
traceSession = traceStart.value ?? undefined;
|
|
128
163
|
|
|
129
|
-
|
|
130
|
-
const policy = resolveDownloadPolicy(process.env, {});
|
|
164
|
+
let embedPort: EmbeddingPort | null = null;
|
|
131
165
|
|
|
132
|
-
|
|
133
|
-
|
|
166
|
+
try {
|
|
167
|
+
// Resolve download policy from env (MCP has no CLI flags)
|
|
168
|
+
const policy = resolveDownloadPolicy(process.env, {});
|
|
134
169
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const embedResult = await llm.createEmbeddingPort(modelUri, {
|
|
138
|
-
policy,
|
|
139
|
-
onProgress: (progress) => downloadProgress("embed", progress),
|
|
140
|
-
});
|
|
141
|
-
if (!embedResult.ok) {
|
|
142
|
-
throw new Error(
|
|
143
|
-
`Failed to load embedding model: ${embedResult.error.message}. ` +
|
|
144
|
-
"Ensure models are downloaded with: gno models pull"
|
|
145
|
-
);
|
|
146
|
-
}
|
|
170
|
+
// Non-TTY progress for MCP (periodic lines to stderr, not \r)
|
|
171
|
+
const downloadProgress = createNonTtyProgressRenderer();
|
|
147
172
|
|
|
148
|
-
|
|
173
|
+
// Create LLM adapter for embeddings
|
|
174
|
+
const llm = new LlmAdapter(ctx.config);
|
|
175
|
+
const embedResult = await llm.createEmbeddingPort(modelUri, {
|
|
176
|
+
policy,
|
|
177
|
+
onProgress: (progress) => downloadProgress("embed", progress),
|
|
178
|
+
});
|
|
179
|
+
if (!embedResult.ok) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`Failed to load embedding model: ${embedResult.error.message}. ` +
|
|
182
|
+
"Ensure models are downloaded with: gno models pull"
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
embedPort = embedResult.value;
|
|
149
186
|
|
|
150
|
-
try {
|
|
151
187
|
// Embed query with contextual formatting
|
|
152
188
|
const queryEmbedResult = await embedPort.embed(
|
|
153
189
|
formatQueryForEmbedding(args.query, embedPort.modelUri)
|
|
@@ -196,17 +232,8 @@ export function handleVsearch(
|
|
|
196
232
|
args.query,
|
|
197
233
|
queryEmbedding,
|
|
198
234
|
{
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
collection: args.collection,
|
|
202
|
-
intent: args.intent,
|
|
203
|
-
exclude: args.exclude,
|
|
204
|
-
since: args.since,
|
|
205
|
-
until: args.until,
|
|
206
|
-
categories: args.categories,
|
|
207
|
-
author: args.author,
|
|
208
|
-
tagsAll: normalizeTagFilters(args.tagsAll),
|
|
209
|
-
tagsAny: normalizeTagFilters(args.tagsAny),
|
|
235
|
+
...options,
|
|
236
|
+
traceSession,
|
|
210
237
|
}
|
|
211
238
|
);
|
|
212
239
|
|
|
@@ -217,17 +244,23 @@ export function handleVsearch(
|
|
|
217
244
|
// Enrich with absPath
|
|
218
245
|
const enrichedResults = enrichWithAbsPath(result.value.results, ctx);
|
|
219
246
|
|
|
220
|
-
return
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
247
|
+
return attachRetrievalTraceMetadata(
|
|
248
|
+
{
|
|
249
|
+
...result.value,
|
|
250
|
+
results: enrichedResults,
|
|
251
|
+
meta: {
|
|
252
|
+
...result.value.meta,
|
|
253
|
+
// Add queryLanguage hint if provided (per spec, lang is a hint for vsearch)
|
|
254
|
+
...(args.lang ? { queryLanguage: args.lang } : {}),
|
|
255
|
+
},
|
|
227
256
|
},
|
|
228
|
-
|
|
257
|
+
traceSession
|
|
258
|
+
);
|
|
259
|
+
} catch (cause) {
|
|
260
|
+
await finishRetrievalTraceAfterError(traceSession, cause);
|
|
261
|
+
throw cause;
|
|
229
262
|
} finally {
|
|
230
|
-
await embedPort
|
|
263
|
+
await embedPort?.dispose();
|
|
231
264
|
}
|
|
232
265
|
},
|
|
233
266
|
formatSearchResults
|
package/src/pipeline/answer.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* @module src/pipeline/answer
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import type { RetrievalTraceSession } from "../core/retrieval-trace-session";
|
|
8
9
|
import type { GenerationPort } from "../llm/types";
|
|
9
10
|
import type { StorePort } from "../store/types";
|
|
10
11
|
import type {
|
|
@@ -15,6 +16,11 @@ import type {
|
|
|
15
16
|
} from "./types";
|
|
16
17
|
|
|
17
18
|
import { buildAnswerPrompt, type AnswerPromptSource } from "./answer-prompt";
|
|
19
|
+
import { attachCitationTraceMetadata } from "./trace-metadata";
|
|
20
|
+
import {
|
|
21
|
+
CITATION_TRACE_METADATA,
|
|
22
|
+
SEARCH_RESULT_PLANNER_METADATA,
|
|
23
|
+
} from "./types";
|
|
18
24
|
|
|
19
25
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
26
|
// Constants
|
|
@@ -24,6 +30,11 @@ import { buildAnswerPrompt, type AnswerPromptSource } from "./answer-prompt";
|
|
|
24
30
|
export const ABSTENTION_MESSAGE =
|
|
25
31
|
"I don't have enough information in the provided sources to answer this question.";
|
|
26
32
|
|
|
33
|
+
export const answerTraceTerminalStatus = (
|
|
34
|
+
citations: readonly Citation[] | undefined
|
|
35
|
+
): "completed" | "partial" =>
|
|
36
|
+
citations && citations.length > 0 ? "completed" : "partial";
|
|
37
|
+
|
|
27
38
|
/** Max characters per document (~8K tokens) */
|
|
28
39
|
const MAX_DOC_CHARS = 32_000;
|
|
29
40
|
|
|
@@ -86,6 +97,65 @@ interface SelectedSource {
|
|
|
86
97
|
reason: string;
|
|
87
98
|
}
|
|
88
99
|
|
|
100
|
+
interface ExactAnswerPassage {
|
|
101
|
+
text: string;
|
|
102
|
+
startLine: number;
|
|
103
|
+
endLine: number;
|
|
104
|
+
passageHash: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const sha256 = (value: string): string =>
|
|
108
|
+
new Bun.CryptoHasher("sha256").update(value).digest("hex");
|
|
109
|
+
|
|
110
|
+
const completeLinePrefix = (
|
|
111
|
+
content: string,
|
|
112
|
+
startLine: number,
|
|
113
|
+
maxChars: number
|
|
114
|
+
): ExactAnswerPassage | null => {
|
|
115
|
+
const lines = content.split("\n");
|
|
116
|
+
if (lines.length === 0) return null;
|
|
117
|
+
const selected: string[] = [];
|
|
118
|
+
let characters = 0;
|
|
119
|
+
for (const line of lines) {
|
|
120
|
+
const addition = selected.length === 0 ? line.length : line.length + 1;
|
|
121
|
+
if (characters + addition > maxChars) break;
|
|
122
|
+
selected.push(line);
|
|
123
|
+
characters += addition;
|
|
124
|
+
}
|
|
125
|
+
if (selected.length === 0) return null;
|
|
126
|
+
const text = selected.join("\n");
|
|
127
|
+
if (!text.trim()) return null;
|
|
128
|
+
return {
|
|
129
|
+
text,
|
|
130
|
+
startLine,
|
|
131
|
+
endLine: startLine + selected.length - 1,
|
|
132
|
+
passageHash: sha256(text),
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const anchoredCompleteLines = (
|
|
137
|
+
content: string,
|
|
138
|
+
metadata: SearchResult[typeof SEARCH_RESULT_PLANNER_METADATA]
|
|
139
|
+
): ExactAnswerPassage | null => {
|
|
140
|
+
if (
|
|
141
|
+
!metadata?.passageHash ||
|
|
142
|
+
metadata.startLine === undefined ||
|
|
143
|
+
metadata.endLine === undefined ||
|
|
144
|
+
metadata.endLine < metadata.startLine
|
|
145
|
+
) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const lines = content.split("\n");
|
|
149
|
+
const text = lines.slice(metadata.startLine - 1, metadata.endLine).join("\n");
|
|
150
|
+
if (
|
|
151
|
+
text.split("\n").length !== metadata.endLine - metadata.startLine + 1 ||
|
|
152
|
+
sha256(text) !== metadata.passageHash
|
|
153
|
+
) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
return completeLinePrefix(text, metadata.startLine, MAX_DOC_CHARS);
|
|
157
|
+
};
|
|
158
|
+
|
|
89
159
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
90
160
|
// Citation Processing
|
|
91
161
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -423,53 +493,78 @@ export async function generateGroundedAnswer(
|
|
|
423
493
|
): Promise<AnswerGenerationResult | null> {
|
|
424
494
|
const { genPort, store } = deps;
|
|
425
495
|
const sourceSelection = selectAdaptiveSources(query, results);
|
|
496
|
+
const finalRanks = new Map(
|
|
497
|
+
results.map((result, index) => [result, index + 1] as const)
|
|
498
|
+
);
|
|
426
499
|
const promptSources: AnswerPromptSource[] = [];
|
|
427
500
|
const citations: Citation[] = [];
|
|
428
501
|
let citationIndex = 0;
|
|
429
502
|
|
|
430
503
|
for (const r of sourceSelection.selected) {
|
|
431
|
-
let
|
|
432
|
-
|
|
504
|
+
let passage: ExactAnswerPassage | null = null;
|
|
505
|
+
const plannerMetadata = r[SEARCH_RESULT_PLANNER_METADATA];
|
|
506
|
+
const sourceHash = r.source.sourceHash;
|
|
507
|
+
const mirrorHash = r.conversion?.mirrorHash;
|
|
433
508
|
|
|
434
509
|
// Try to fetch full document content if store available
|
|
435
|
-
if (store &&
|
|
436
|
-
const contentResult = await store.getContent(
|
|
510
|
+
if (store && mirrorHash) {
|
|
511
|
+
const contentResult = await store.getContent(mirrorHash);
|
|
437
512
|
if (contentResult.ok && contentResult.value) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
content = `${content.slice(0, MAX_DOC_CHARS)}\n\n[... truncated ...]`;
|
|
513
|
+
if (sha256(contentResult.value) === mirrorHash) {
|
|
514
|
+
passage =
|
|
515
|
+
anchoredCompleteLines(contentResult.value, plannerMetadata) ??
|
|
516
|
+
completeLinePrefix(contentResult.value, 1, MAX_DOC_CHARS);
|
|
443
517
|
}
|
|
444
518
|
}
|
|
445
519
|
}
|
|
446
520
|
|
|
447
|
-
//
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
521
|
+
// Snippet fallback is allowed only when the planner proves it is the exact
|
|
522
|
+
// complete canonical chunk, never an FTS marker/ellipsis presentation.
|
|
523
|
+
if (
|
|
524
|
+
!passage &&
|
|
525
|
+
plannerMetadata?.passageHash &&
|
|
526
|
+
plannerMetadata.startLine !== undefined &&
|
|
527
|
+
plannerMetadata.endLine !== undefined &&
|
|
528
|
+
sha256(r.snippet) === plannerMetadata.passageHash &&
|
|
529
|
+
r.snippet.split("\n").length ===
|
|
530
|
+
plannerMetadata.endLine - plannerMetadata.startLine + 1
|
|
531
|
+
) {
|
|
532
|
+
passage = completeLinePrefix(
|
|
533
|
+
r.snippet,
|
|
534
|
+
plannerMetadata.startLine,
|
|
535
|
+
MAX_SNIPPET_CHARS
|
|
536
|
+
);
|
|
456
537
|
}
|
|
538
|
+
if (!(passage && sourceHash && mirrorHash)) continue;
|
|
457
539
|
|
|
458
540
|
citationIndex += 1;
|
|
459
541
|
promptSources.push({
|
|
460
542
|
index: citationIndex,
|
|
461
543
|
docid: r.docid,
|
|
462
544
|
uri: r.uri,
|
|
463
|
-
content,
|
|
545
|
+
content: passage.text,
|
|
464
546
|
guidance: r.context,
|
|
465
547
|
});
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
548
|
+
citations.push(
|
|
549
|
+
attachCitationTraceMetadata(
|
|
550
|
+
{
|
|
551
|
+
docid: r.docid,
|
|
552
|
+
uri: r.uri,
|
|
553
|
+
startLine: passage.startLine,
|
|
554
|
+
endLine: passage.endLine,
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
sourceHash,
|
|
558
|
+
mirrorHash,
|
|
559
|
+
passageHash: passage.passageHash,
|
|
560
|
+
seq: plannerMetadata?.seq,
|
|
561
|
+
rank: finalRanks.get(r) ?? 1,
|
|
562
|
+
plannerRank: plannerMetadata?.retrievalRank,
|
|
563
|
+
sources: plannerMetadata?.sources,
|
|
564
|
+
graphExpanded: plannerMetadata?.graphExpanded,
|
|
565
|
+
}
|
|
566
|
+
)
|
|
567
|
+
);
|
|
473
568
|
}
|
|
474
569
|
|
|
475
570
|
if (promptSources.length === 0) {
|
|
@@ -528,3 +623,49 @@ export function processAnswerResult(rawResult: AnswerGenerationResult): {
|
|
|
528
623
|
answerContext: rawResult.answerContext,
|
|
529
624
|
};
|
|
530
625
|
}
|
|
626
|
+
|
|
627
|
+
/** Process first, then record only citations retained in the final answer. */
|
|
628
|
+
export async function processAnswerResultWithTrace(
|
|
629
|
+
rawResult: AnswerGenerationResult,
|
|
630
|
+
traceSession?: RetrievalTraceSession
|
|
631
|
+
): Promise<ReturnType<typeof processAnswerResult>> {
|
|
632
|
+
const processed = processAnswerResult(rawResult);
|
|
633
|
+
if (!traceSession || processed.citations.length === 0) return processed;
|
|
634
|
+
const evidence = processed.citations.flatMap((citation) => {
|
|
635
|
+
const metadata = citation[CITATION_TRACE_METADATA];
|
|
636
|
+
if (
|
|
637
|
+
!metadata ||
|
|
638
|
+
citation.startLine === undefined ||
|
|
639
|
+
citation.endLine === undefined
|
|
640
|
+
) {
|
|
641
|
+
return [];
|
|
642
|
+
}
|
|
643
|
+
return [
|
|
644
|
+
{
|
|
645
|
+
docid: citation.docid,
|
|
646
|
+
uri: citation.uri,
|
|
647
|
+
sourceHash: metadata.sourceHash,
|
|
648
|
+
mirrorHash: metadata.mirrorHash,
|
|
649
|
+
passageHash: metadata.passageHash,
|
|
650
|
+
startLine: citation.startLine,
|
|
651
|
+
endLine: citation.endLine,
|
|
652
|
+
rank: metadata.rank,
|
|
653
|
+
...(metadata.seq === undefined ? {} : { seq: metadata.seq }),
|
|
654
|
+
...(metadata.plannerRank === undefined
|
|
655
|
+
? {}
|
|
656
|
+
: { plannerRank: metadata.plannerRank }),
|
|
657
|
+
...(metadata.sources === undefined
|
|
658
|
+
? {}
|
|
659
|
+
: { sources: metadata.sources }),
|
|
660
|
+
...(metadata.graphExpanded === undefined
|
|
661
|
+
? {}
|
|
662
|
+
: { graphExpanded: metadata.graphExpanded }),
|
|
663
|
+
},
|
|
664
|
+
];
|
|
665
|
+
});
|
|
666
|
+
const recorded = await traceSession.recordEvidence("cite", evidence);
|
|
667
|
+
if (!recorded.ok) {
|
|
668
|
+
throw new Error(`Trace recording failed: ${recorded.error.message}`);
|
|
669
|
+
}
|
|
670
|
+
return processed;
|
|
671
|
+
}
|
|
@@ -62,6 +62,7 @@ const matchesDocumentFilters = (
|
|
|
62
62
|
until?: string;
|
|
63
63
|
categories?: string[];
|
|
64
64
|
author?: string;
|
|
65
|
+
relPathPrefix?: string;
|
|
65
66
|
}
|
|
66
67
|
): boolean => {
|
|
67
68
|
if (
|
|
@@ -201,6 +202,7 @@ export async function expandGraphCandidates(
|
|
|
201
202
|
until?: string;
|
|
202
203
|
categories?: string[];
|
|
203
204
|
author?: string;
|
|
205
|
+
relPathPrefix?: string;
|
|
204
206
|
} = {}
|
|
205
207
|
): Promise<GraphRetrievalResult> {
|
|
206
208
|
const maxCandidates = Math.max(
|
|
@@ -249,6 +251,13 @@ export async function expandGraphCandidates(
|
|
|
249
251
|
if (!doc.mirrorHash) {
|
|
250
252
|
continue;
|
|
251
253
|
}
|
|
254
|
+
if (
|
|
255
|
+
options.relPathPrefix !== undefined &&
|
|
256
|
+
doc.relPath !== options.relPathPrefix &&
|
|
257
|
+
!doc.relPath.startsWith(`${options.relPathPrefix}/`)
|
|
258
|
+
) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
252
261
|
const rank =
|
|
253
262
|
seedCandidates.findIndex(
|
|
254
263
|
(candidate) => candidate.mirrorHash === doc.mirrorHash
|
|
@@ -325,7 +334,12 @@ export async function expandGraphCandidates(
|
|
|
325
334
|
}
|
|
326
335
|
|
|
327
336
|
const metadataFilteredDocs = docsResult.value.filter(
|
|
328
|
-
(doc) =>
|
|
337
|
+
(doc) =>
|
|
338
|
+
doc.mirrorHash &&
|
|
339
|
+
(options.relPathPrefix === undefined ||
|
|
340
|
+
doc.relPath === options.relPathPrefix ||
|
|
341
|
+
doc.relPath.startsWith(`${options.relPathPrefix}/`)) &&
|
|
342
|
+
matchesDocumentFilters(doc, options)
|
|
329
343
|
);
|
|
330
344
|
const docs = await filterDocsByTags(store, metadataFilteredDocs, options);
|
|
331
345
|
const docByDocid = new Map(docs.map((doc) => [doc.docid, doc]));
|