@gmickel/gno 1.9.0 → 1.10.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/assets/skill/SKILL.md +28 -16
- package/assets/skill/cli-reference.md +30 -0
- package/assets/skill/examples.md +20 -0
- package/assets/skill/mcp-reference.md +7 -1
- package/package.json +1 -1
- package/src/cli/commands/graph.ts +89 -2
- package/src/cli/commands/links.ts +237 -54
- package/src/cli/commands/query.ts +212 -0
- package/src/cli/commands/ref-parser.ts +8 -103
- package/src/cli/options.ts +4 -0
- package/src/cli/program.ts +176 -9
- package/src/config/content-types.ts +14 -0
- package/src/core/graph-query.ts +137 -0
- package/src/core/graph-resolver.ts +117 -0
- package/src/core/ref-parser.ts +145 -0
- package/src/ingestion/frontmatter.ts +95 -2
- package/src/ingestion/sync.ts +281 -1
- package/src/mcp/tools/get.ts +1 -1
- package/src/mcp/tools/index.ts +89 -1
- package/src/mcp/tools/links.ts +83 -1
- package/src/mcp/tools/multi-get.ts +1 -1
- package/src/mcp/tools/query.ts +207 -0
- package/src/pipeline/diagnose.ts +302 -0
- package/src/pipeline/filters.ts +119 -0
- package/src/pipeline/hybrid.ts +90 -17
- package/src/pipeline/types.ts +32 -0
- package/src/publish/export-service.ts +1 -1
- package/src/sdk/documents.ts +2 -2
- package/src/serve/routes/api.ts +194 -0
- package/src/serve/routes/graph.ts +173 -1
- package/src/serve/server.ts +24 -1
- package/src/store/migrations/010-typed-edges.ts +67 -0
- package/src/store/migrations/011-doc-edge-traversal-indexes.ts +30 -0
- package/src/store/migrations/index.ts +4 -0
- package/src/store/sqlite/adapter.ts +826 -102
- package/src/store/types.ts +141 -0
package/src/mcp/tools/get.ts
CHANGED
|
@@ -10,11 +10,11 @@ import type { DocumentRow, StorePort } from "../../store/types";
|
|
|
10
10
|
import type { ToolContext } from "../server";
|
|
11
11
|
|
|
12
12
|
import { decorateUriForIndex, parseUri } from "../../app/constants";
|
|
13
|
-
import { parseRef } from "../../cli/commands/ref-parser";
|
|
14
13
|
import {
|
|
15
14
|
getDocumentCapabilities,
|
|
16
15
|
type DocumentCapabilities,
|
|
17
16
|
} from "../../core/document-capabilities";
|
|
17
|
+
import { parseRef } from "../../core/ref-parser";
|
|
18
18
|
import { runTool, type ToolResult } from "./index";
|
|
19
19
|
|
|
20
20
|
interface GetInput {
|
package/src/mcp/tools/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { handleJobStatus } from "./job-status";
|
|
|
23
23
|
import {
|
|
24
24
|
handleBacklinks,
|
|
25
25
|
handleGraph,
|
|
26
|
+
handleGraphQuery,
|
|
26
27
|
handleGraphNeighbors,
|
|
27
28
|
handleGraphPath,
|
|
28
29
|
handleLinks,
|
|
@@ -31,7 +32,7 @@ import {
|
|
|
31
32
|
import { handleListJobs } from "./list-jobs";
|
|
32
33
|
import { handleListTags } from "./list-tags";
|
|
33
34
|
import { handleMultiGet } from "./multi-get";
|
|
34
|
-
import { handleQuery } from "./query";
|
|
35
|
+
import { handleQuery, handleQueryDiagnose } from "./query";
|
|
35
36
|
import { handleRemoveCollection } from "./remove-collection";
|
|
36
37
|
import { handleSearch } from "./search";
|
|
37
38
|
import { handleStatus } from "./status";
|
|
@@ -64,6 +65,8 @@ export const MCP_TOOL_DESCRIPTIONS = {
|
|
|
64
65
|
"Vector semantic search. Finds conceptually similar docs with different wording. Best after embeddings are current; use intent to disambiguate short terms. Use gno_query for default hybrid retrieval.",
|
|
65
66
|
query:
|
|
66
67
|
"Hybrid search (BM25 + vector + optional expansion/reranking). Recommended default. Use intent for ambiguous terms, queryModes to combine term/intent/hyde strategies, fast=true for quick lookup, thorough=true when recall matters, and candidateLimit to trade latency for coverage.",
|
|
68
|
+
queryDiagnose:
|
|
69
|
+
"Diagnose why one target document does or does not appear for a query. Use when an important doc is missing, a filter may exclude it, or you need stage-by-stage BM25/vector/fusion/graph/rerank evidence before changing retrieval strategy.",
|
|
67
70
|
get: "Retrieve one document by gno:// URI, docid (#abc123), or collection/path. After search results include line, pass fromLine and lineCount to fetch only the relevant range before expanding to the full document.",
|
|
68
71
|
multiGet:
|
|
69
72
|
"Retrieve multiple documents by refs array or glob pattern. Use after gno_search/gno_query to batch top result URIs/docids; set maxBytes and lineNumbers to control context size.",
|
|
@@ -499,6 +502,16 @@ export const queryInputSchema = z.object({
|
|
|
499
502
|
tagsAny: z.array(z.string()).optional().describe("Require ANY of these tags"),
|
|
500
503
|
});
|
|
501
504
|
|
|
505
|
+
export const queryDiagnoseInputSchema = queryInputSchema.extend({
|
|
506
|
+
target: z
|
|
507
|
+
.string()
|
|
508
|
+
.trim()
|
|
509
|
+
.min(1, "Target reference cannot be empty")
|
|
510
|
+
.describe(
|
|
511
|
+
"Target document reference to diagnose (gno URI, docid, or collection/path)"
|
|
512
|
+
),
|
|
513
|
+
});
|
|
514
|
+
|
|
502
515
|
const getInputSchema = z.object({
|
|
503
516
|
ref: z
|
|
504
517
|
.string()
|
|
@@ -715,6 +728,67 @@ const graphPathInputSchema = graphInputSchema.extend({
|
|
|
715
728
|
.describe("Maximum relationship hops to search"),
|
|
716
729
|
});
|
|
717
730
|
|
|
731
|
+
export const graphQueryInputSchema = z.object({
|
|
732
|
+
ref: z
|
|
733
|
+
.string()
|
|
734
|
+
.trim()
|
|
735
|
+
.min(1, "Reference cannot be empty")
|
|
736
|
+
.describe(
|
|
737
|
+
"Root document reference for typed-edge traversal: gno URI, docid, or collection/path"
|
|
738
|
+
),
|
|
739
|
+
direction: z
|
|
740
|
+
.enum(["both", "out", "in"])
|
|
741
|
+
.default("both")
|
|
742
|
+
.describe("Which typed edges to traverse"),
|
|
743
|
+
edgeType: z
|
|
744
|
+
.string()
|
|
745
|
+
.trim()
|
|
746
|
+
.min(1)
|
|
747
|
+
.optional()
|
|
748
|
+
.describe("Semantic edge type filter, e.g. mentions or works_at"),
|
|
749
|
+
relation: z
|
|
750
|
+
.string()
|
|
751
|
+
.trim()
|
|
752
|
+
.min(1)
|
|
753
|
+
.optional()
|
|
754
|
+
.describe("Alias for edgeType; must match edgeType when both are set"),
|
|
755
|
+
maxDepth: z
|
|
756
|
+
.number()
|
|
757
|
+
.int()
|
|
758
|
+
.min(1)
|
|
759
|
+
.max(6)
|
|
760
|
+
.optional()
|
|
761
|
+
.describe("Maximum typed-edge hops to traverse"),
|
|
762
|
+
depth: z
|
|
763
|
+
.number()
|
|
764
|
+
.int()
|
|
765
|
+
.min(1)
|
|
766
|
+
.max(6)
|
|
767
|
+
.optional()
|
|
768
|
+
.describe("Alias for maxDepth"),
|
|
769
|
+
maxNodes: z
|
|
770
|
+
.number()
|
|
771
|
+
.int()
|
|
772
|
+
.min(1)
|
|
773
|
+
.max(1000)
|
|
774
|
+
.default(100)
|
|
775
|
+
.describe("Returned node cap"),
|
|
776
|
+
frontierLimit: z
|
|
777
|
+
.number()
|
|
778
|
+
.int()
|
|
779
|
+
.min(1)
|
|
780
|
+
.max(1000)
|
|
781
|
+
.default(100)
|
|
782
|
+
.describe("Per-depth frontier cap"),
|
|
783
|
+
visitedLimit: z
|
|
784
|
+
.number()
|
|
785
|
+
.int()
|
|
786
|
+
.min(1)
|
|
787
|
+
.max(5000)
|
|
788
|
+
.default(500)
|
|
789
|
+
.describe("SQL traversal visited-row cap"),
|
|
790
|
+
});
|
|
791
|
+
|
|
718
792
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
719
793
|
// Tool Result Type
|
|
720
794
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -846,6 +920,13 @@ export function registerTools(server: McpServer, ctx: ToolContext): void {
|
|
|
846
920
|
(args) => handleQuery(args, ctx)
|
|
847
921
|
);
|
|
848
922
|
|
|
923
|
+
server.tool(
|
|
924
|
+
"gno_query_diagnose",
|
|
925
|
+
MCP_TOOL_DESCRIPTIONS.queryDiagnose,
|
|
926
|
+
queryDiagnoseInputSchema.shape,
|
|
927
|
+
(args) => handleQueryDiagnose(args, ctx)
|
|
928
|
+
);
|
|
929
|
+
|
|
849
930
|
server.tool(
|
|
850
931
|
"gno_get",
|
|
851
932
|
MCP_TOOL_DESCRIPTIONS.get,
|
|
@@ -902,6 +983,13 @@ export function registerTools(server: McpServer, ctx: ToolContext): void {
|
|
|
902
983
|
(args) => handleGraph(args, ctx)
|
|
903
984
|
);
|
|
904
985
|
|
|
986
|
+
server.tool(
|
|
987
|
+
"gno_graph_query",
|
|
988
|
+
"Run bounded traversal over typed doc_edges from one root document. Use for explicit relationship questions like 'what does Alice work_at within 2 hops?' or to inspect typed graph hints; returns schemaVersion, root, nodes, edges, caps, and truncation.",
|
|
989
|
+
graphQueryInputSchema.shape,
|
|
990
|
+
(args) => handleGraphQuery(args, ctx)
|
|
991
|
+
);
|
|
992
|
+
|
|
905
993
|
server.tool(
|
|
906
994
|
"gno_graph_neighbors",
|
|
907
995
|
"Find graph neighbors around a document/node. Use for relationship questions, missed obvious related docs, or unfamiliar corpus navigation after gno_query identifies a seed; returns incoming/outgoing wiki, markdown, and optional similarity edges. Follow with gno_get for targeted reads.",
|
package/src/mcp/tools/links.ts
CHANGED
|
@@ -10,14 +10,18 @@ import type {
|
|
|
10
10
|
BacklinkRow,
|
|
11
11
|
DocLinkRow,
|
|
12
12
|
DocumentRow,
|
|
13
|
+
GraphQueryDirection,
|
|
14
|
+
GraphQueryResult,
|
|
13
15
|
GraphLink,
|
|
14
16
|
GraphNode,
|
|
15
17
|
GraphResult,
|
|
16
18
|
} from "../../store/types";
|
|
17
19
|
import type { ToolContext } from "../server";
|
|
18
20
|
|
|
19
|
-
import {
|
|
21
|
+
import { normalizeContentTypes } from "../../config";
|
|
20
22
|
import { MCP_ERRORS } from "../../core/errors";
|
|
23
|
+
import { diagnoseGraphQuery } from "../../core/graph-query";
|
|
24
|
+
import { parseRef } from "../../core/ref-parser";
|
|
21
25
|
import { normalizeCollectionName } from "../../core/validation";
|
|
22
26
|
import { getActivePreset } from "../../llm/registry";
|
|
23
27
|
import { createVectorIndexPort } from "../../store/vector";
|
|
@@ -648,6 +652,18 @@ interface GraphPathInput extends GraphInput {
|
|
|
648
652
|
maxDepth?: number;
|
|
649
653
|
}
|
|
650
654
|
|
|
655
|
+
interface GraphQueryInput {
|
|
656
|
+
ref: string;
|
|
657
|
+
direction?: GraphQueryDirection;
|
|
658
|
+
edgeType?: string;
|
|
659
|
+
relation?: string;
|
|
660
|
+
maxDepth?: number;
|
|
661
|
+
depth?: number;
|
|
662
|
+
maxNodes?: number;
|
|
663
|
+
frontierLimit?: number;
|
|
664
|
+
visitedLimit?: number;
|
|
665
|
+
}
|
|
666
|
+
|
|
651
667
|
interface GraphNeighbor {
|
|
652
668
|
node: GraphNode;
|
|
653
669
|
direction: "out" | "in";
|
|
@@ -860,6 +876,72 @@ function formatGraphNeighborsResult(data: GraphNeighborsResult): string {
|
|
|
860
876
|
return lines.join("\n");
|
|
861
877
|
}
|
|
862
878
|
|
|
879
|
+
function formatGraphQueryResult(data: GraphQueryResult): string {
|
|
880
|
+
const lines = [
|
|
881
|
+
`Typed graph query from ${data.root.uri}: ${data.nodes.length} nodes, ${data.edges.length} edges`,
|
|
882
|
+
`Direction: ${data.meta.direction}, depth: ${data.meta.maxDepth}`,
|
|
883
|
+
];
|
|
884
|
+
if (data.meta.edgeType) {
|
|
885
|
+
lines.push(`Edge type: ${data.meta.edgeType}`);
|
|
886
|
+
}
|
|
887
|
+
if (data.meta.truncated) {
|
|
888
|
+
lines.push("Traversal truncated by configured caps");
|
|
889
|
+
}
|
|
890
|
+
for (const edge of data.edges.slice(0, 20)) {
|
|
891
|
+
lines.push(
|
|
892
|
+
` ${edge.source} -> ${edge.target} (${edge.edgeType}, ${edge.confidence}, ${edge.edgeSource})`
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
return lines.join("\n");
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
export function handleGraphQuery(
|
|
899
|
+
args: GraphQueryInput,
|
|
900
|
+
ctx: ToolContext
|
|
901
|
+
): Promise<ToolResult> {
|
|
902
|
+
return runTool(
|
|
903
|
+
ctx,
|
|
904
|
+
"gno_graph_query",
|
|
905
|
+
async () => {
|
|
906
|
+
const edgeTypeValue = args.edgeType?.trim();
|
|
907
|
+
const relationValue = args.relation?.trim();
|
|
908
|
+
if (edgeTypeValue && relationValue && edgeTypeValue !== relationValue) {
|
|
909
|
+
throw new Error(
|
|
910
|
+
"VALIDATION: edgeType and relation are aliases and must match when both are provided"
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
if (
|
|
914
|
+
args.maxDepth !== undefined &&
|
|
915
|
+
args.depth !== undefined &&
|
|
916
|
+
args.maxDepth !== args.depth
|
|
917
|
+
) {
|
|
918
|
+
throw new Error(
|
|
919
|
+
"VALIDATION: maxDepth and depth are aliases and must match when both are provided"
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
const result = await diagnoseGraphQuery(ctx.store, args.ref, {
|
|
924
|
+
direction: args.direction ?? "both",
|
|
925
|
+
edgeType: edgeTypeValue || relationValue || undefined,
|
|
926
|
+
maxDepth: args.maxDepth ?? args.depth,
|
|
927
|
+
maxNodes: args.maxNodes,
|
|
928
|
+
frontierLimit: args.frontierLimit,
|
|
929
|
+
visitedLimit: args.visitedLimit,
|
|
930
|
+
contentTypeRules: normalizeContentTypes(ctx.config.contentTypes ?? [])
|
|
931
|
+
.rules,
|
|
932
|
+
});
|
|
933
|
+
if (!result.success) {
|
|
934
|
+
throw new Error(
|
|
935
|
+
`${result.isValidation ? "VALIDATION" : "RUNTIME"}: ${result.error}`
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
return result.data;
|
|
940
|
+
},
|
|
941
|
+
formatGraphQueryResult
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
|
|
863
945
|
export function handleGraphNeighbors(
|
|
864
946
|
args: GraphNeighborsInput,
|
|
865
947
|
ctx: ToolContext
|
|
@@ -10,7 +10,7 @@ import type { DocumentRow, StorePort } from "../../store/types";
|
|
|
10
10
|
import type { ToolContext } from "../server";
|
|
11
11
|
|
|
12
12
|
import { decorateUriForIndex, parseUri } from "../../app/constants";
|
|
13
|
-
import { parseRef } from "../../
|
|
13
|
+
import { parseRef } from "../../core/ref-parser";
|
|
14
14
|
import { runTool, type ToolResult } from "./index";
|
|
15
15
|
|
|
16
16
|
interface MultiGetInput {
|
package/src/mcp/tools/query.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
GenerationPort,
|
|
12
12
|
RerankPort,
|
|
13
13
|
} from "../../llm/types";
|
|
14
|
+
import type { QueryDiagnoseResult } from "../../pipeline/diagnose";
|
|
14
15
|
import type {
|
|
15
16
|
QueryModeInput,
|
|
16
17
|
SearchResult,
|
|
@@ -20,11 +21,16 @@ import type { ToolContext } from "../server";
|
|
|
20
21
|
|
|
21
22
|
import { decorateUriForIndex, parseUri } from "../../app/constants";
|
|
22
23
|
import { createNonTtyProgressRenderer } from "../../cli/progress";
|
|
24
|
+
import {
|
|
25
|
+
fingerprintContentTypeRules,
|
|
26
|
+
normalizeContentTypes,
|
|
27
|
+
} from "../../config";
|
|
23
28
|
import { resolveDepthPolicy } from "../../core/depth-policy";
|
|
24
29
|
import { normalizeStructuredQueryInput } from "../../core/structured-query";
|
|
25
30
|
import { LlmAdapter } from "../../llm/nodeLlamaCpp/adapter";
|
|
26
31
|
import { resolveDownloadPolicy } from "../../llm/policy";
|
|
27
32
|
import { getActivePreset, resolveModelUri } from "../../llm/registry";
|
|
33
|
+
import { diagnoseQueryTarget } from "../../pipeline/diagnose";
|
|
28
34
|
import { type HybridSearchDeps, searchHybrid } from "../../pipeline/hybrid";
|
|
29
35
|
import {
|
|
30
36
|
createVectorIndexPort,
|
|
@@ -56,6 +62,10 @@ interface QueryInput {
|
|
|
56
62
|
tagsAny?: string[];
|
|
57
63
|
}
|
|
58
64
|
|
|
65
|
+
interface QueryDiagnoseInput extends QueryInput {
|
|
66
|
+
target: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
59
69
|
/**
|
|
60
70
|
* Enrich results with absPath derived from each result's URI.
|
|
61
71
|
*/
|
|
@@ -128,6 +138,29 @@ function formatSearchResults(data: SearchResults): string {
|
|
|
128
138
|
return lines.join("\n");
|
|
129
139
|
}
|
|
130
140
|
|
|
141
|
+
function formatQueryDiagnoseResult(data: QueryDiagnoseResult): string {
|
|
142
|
+
if (data.target.status !== "diagnosed") {
|
|
143
|
+
return `Target ${data.target.ref}: ${data.target.status}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const lines = [
|
|
147
|
+
`Query diagnose for ${data.target.uri ?? data.target.ref}`,
|
|
148
|
+
`Mode: ${data.meta.mode}, results: ${data.meta.totalResults}`,
|
|
149
|
+
"",
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
for (const stage of data.stages) {
|
|
153
|
+
const rank = stage.rank === null ? "-" : `#${stage.rank}`;
|
|
154
|
+
const score = stage.score === null ? "-" : Number(stage.score).toFixed(3);
|
|
155
|
+
const reason = stage.dropReason ? `, ${stage.dropReason}` : "";
|
|
156
|
+
lines.push(
|
|
157
|
+
`${stage.id}: ${stage.status}, present=${stage.present}, rank=${rank}, score=${score}${reason}`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return lines.join("\n");
|
|
162
|
+
}
|
|
163
|
+
|
|
131
164
|
/**
|
|
132
165
|
* Handle gno_query tool call.
|
|
133
166
|
*/
|
|
@@ -311,3 +344,177 @@ export function handleQuery(
|
|
|
311
344
|
formatSearchResults
|
|
312
345
|
);
|
|
313
346
|
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Handle gno_query_diagnose tool call.
|
|
350
|
+
*/
|
|
351
|
+
export function handleQueryDiagnose(
|
|
352
|
+
args: QueryDiagnoseInput,
|
|
353
|
+
ctx: ToolContext
|
|
354
|
+
): Promise<ToolResult> {
|
|
355
|
+
return runTool(
|
|
356
|
+
ctx,
|
|
357
|
+
"gno_query_diagnose",
|
|
358
|
+
// oxlint-disable-next-line max-lines-per-function -- mirrors query setup for diagnostic trace ports
|
|
359
|
+
async () => {
|
|
360
|
+
let collection: string | undefined;
|
|
361
|
+
if (args.collection) {
|
|
362
|
+
const canonical = ctx.collections.find(
|
|
363
|
+
(c) => c.name.toLowerCase() === args.collection?.toLowerCase()
|
|
364
|
+
);
|
|
365
|
+
if (!canonical) {
|
|
366
|
+
throw new Error(`Collection not found: ${args.collection}`);
|
|
367
|
+
}
|
|
368
|
+
collection = canonical.name;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const normalizedInput = normalizeStructuredQueryInput(
|
|
372
|
+
args.query,
|
|
373
|
+
args.queryModes ?? []
|
|
374
|
+
);
|
|
375
|
+
if (!normalizedInput.ok) {
|
|
376
|
+
throw new Error(normalizedInput.error.message);
|
|
377
|
+
}
|
|
378
|
+
const queryText = normalizedInput.value.query;
|
|
379
|
+
const queryModes =
|
|
380
|
+
normalizedInput.value.queryModes.length > 0
|
|
381
|
+
? normalizedInput.value.queryModes
|
|
382
|
+
: undefined;
|
|
383
|
+
|
|
384
|
+
const preset = getActivePreset(ctx.config);
|
|
385
|
+
const llm = new LlmAdapter(ctx.config);
|
|
386
|
+
const policy = resolveDownloadPolicy(process.env, {});
|
|
387
|
+
const downloadProgress = createNonTtyProgressRenderer();
|
|
388
|
+
|
|
389
|
+
let embedPort: EmbeddingPort | null = null;
|
|
390
|
+
let expandPort: GenerationPort | null = null;
|
|
391
|
+
let rerankPort: RerankPort | null = null;
|
|
392
|
+
let vectorIndex: VectorIndexPort | null = null;
|
|
393
|
+
const embedUri = resolveModelUri(
|
|
394
|
+
ctx.config,
|
|
395
|
+
"embed",
|
|
396
|
+
undefined,
|
|
397
|
+
collection
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
const hasStructuredModes = Boolean(queryModes?.length);
|
|
402
|
+
const depthPolicy = resolveDepthPolicy({
|
|
403
|
+
presetId: preset.id,
|
|
404
|
+
fast: args.fast,
|
|
405
|
+
thorough: args.thorough,
|
|
406
|
+
expand: args.expand,
|
|
407
|
+
rerank: args.rerank,
|
|
408
|
+
candidateLimit: args.candidateLimit,
|
|
409
|
+
hasStructuredModes,
|
|
410
|
+
});
|
|
411
|
+
const { noExpand, noRerank } = depthPolicy;
|
|
412
|
+
|
|
413
|
+
if (!args.fast) {
|
|
414
|
+
const embedResult = await llm.createEmbeddingPort(embedUri, {
|
|
415
|
+
policy,
|
|
416
|
+
onProgress: (progress) => downloadProgress("embed", progress),
|
|
417
|
+
});
|
|
418
|
+
if (embedResult.ok) {
|
|
419
|
+
embedPort = embedResult.value;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (!noExpand && !hasStructuredModes) {
|
|
424
|
+
const genResult = await llm.createExpansionPort(
|
|
425
|
+
resolveModelUri(ctx.config, "expand", undefined, collection),
|
|
426
|
+
{
|
|
427
|
+
policy,
|
|
428
|
+
onProgress: (progress) => downloadProgress("expand", progress),
|
|
429
|
+
}
|
|
430
|
+
);
|
|
431
|
+
if (genResult.ok) {
|
|
432
|
+
expandPort = genResult.value;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (!noRerank) {
|
|
437
|
+
const rerankResult = await llm.createRerankPort(
|
|
438
|
+
resolveModelUri(ctx.config, "rerank", undefined, collection),
|
|
439
|
+
{
|
|
440
|
+
policy,
|
|
441
|
+
onProgress: (progress) => downloadProgress("rerank", progress),
|
|
442
|
+
}
|
|
443
|
+
);
|
|
444
|
+
if (rerankResult.ok) {
|
|
445
|
+
rerankPort = rerankResult.value;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (embedPort) {
|
|
450
|
+
const embedInitResult = await embedPort.init();
|
|
451
|
+
if (embedInitResult.ok) {
|
|
452
|
+
const dimensions = embedPort.dimensions();
|
|
453
|
+
const db = ctx.store.getRawDb();
|
|
454
|
+
const vectorResult = await createVectorIndexPort(db, {
|
|
455
|
+
model: embedUri,
|
|
456
|
+
dimensions,
|
|
457
|
+
});
|
|
458
|
+
if (vectorResult.ok) {
|
|
459
|
+
vectorIndex = vectorResult.value;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const deps: HybridSearchDeps = {
|
|
465
|
+
store: ctx.store,
|
|
466
|
+
config: ctx.config,
|
|
467
|
+
vectorIndex,
|
|
468
|
+
embedPort,
|
|
469
|
+
expandPort,
|
|
470
|
+
rerankPort,
|
|
471
|
+
};
|
|
472
|
+
const contentTypeRules = normalizeContentTypes(
|
|
473
|
+
ctx.config.contentTypes ?? []
|
|
474
|
+
).rules;
|
|
475
|
+
|
|
476
|
+
const result = await diagnoseQueryTarget(deps, queryText, {
|
|
477
|
+
target: args.target,
|
|
478
|
+
limit: args.limit ?? 5,
|
|
479
|
+
minScore: args.minScore,
|
|
480
|
+
collection,
|
|
481
|
+
queryLanguageHint: args.lang,
|
|
482
|
+
intent: args.intent,
|
|
483
|
+
candidateLimit: depthPolicy.candidateLimit,
|
|
484
|
+
exclude: args.exclude,
|
|
485
|
+
since: args.since,
|
|
486
|
+
until: args.until,
|
|
487
|
+
categories: args.categories,
|
|
488
|
+
author: args.author,
|
|
489
|
+
noExpand,
|
|
490
|
+
noRerank,
|
|
491
|
+
graph: args.graph === true,
|
|
492
|
+
noGraph: args.noGraph || args.fast,
|
|
493
|
+
queryModes,
|
|
494
|
+
tagsAll: normalizeTagFilters(args.tagsAll),
|
|
495
|
+
tagsAny: normalizeTagFilters(args.tagsAny),
|
|
496
|
+
contentTypeRules,
|
|
497
|
+
contentTypeRulesFingerprint:
|
|
498
|
+
fingerprintContentTypeRules(contentTypeRules),
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
if (!result.ok) {
|
|
502
|
+
throw new Error(result.error.message);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return result.value;
|
|
506
|
+
} finally {
|
|
507
|
+
if (embedPort) {
|
|
508
|
+
await embedPort.dispose();
|
|
509
|
+
}
|
|
510
|
+
if (expandPort) {
|
|
511
|
+
await expandPort.dispose();
|
|
512
|
+
}
|
|
513
|
+
if (rerankPort) {
|
|
514
|
+
await rerankPort.dispose();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
formatQueryDiagnoseResult
|
|
519
|
+
);
|
|
520
|
+
}
|