@gmickel/gno 0.17.0 → 0.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 +25 -1
- package/package.json +1 -1
- package/src/cli/commands/ask.ts +7 -0
- package/src/cli/commands/models/use.ts +1 -0
- package/src/cli/program.ts +42 -0
- package/src/config/types.ts +2 -0
- package/src/llm/nodeLlamaCpp/generation.ts +3 -1
- package/src/llm/registry.ts +1 -0
- package/src/llm/types.ts +2 -0
- package/src/mcp/tools/index.ts +7 -0
- package/src/mcp/tools/query.ts +6 -0
- package/src/mcp/tools/search.ts +4 -0
- package/src/mcp/tools/vsearch.ts +4 -0
- package/src/pipeline/exclude.ts +69 -0
- package/src/pipeline/expansion.ts +39 -4
- package/src/pipeline/hybrid.ts +59 -18
- package/src/pipeline/intent.ts +152 -0
- package/src/pipeline/rerank.ts +81 -44
- package/src/pipeline/search.ts +34 -1
- package/src/pipeline/types.ts +15 -0
- package/src/pipeline/vsearch.ts +41 -1
- package/src/serve/public/lib/retrieval-filters.ts +10 -0
- package/src/serve/public/pages/Ask.tsx +189 -1
- package/src/serve/public/pages/Search.tsx +78 -2
- package/src/serve/routes/api.ts +161 -48
package/src/serve/routes/api.ts
CHANGED
|
@@ -66,6 +66,8 @@ export interface SearchRequestBody {
|
|
|
66
66
|
limit?: number;
|
|
67
67
|
minScore?: number;
|
|
68
68
|
collection?: string;
|
|
69
|
+
intent?: string;
|
|
70
|
+
exclude?: string;
|
|
69
71
|
since?: string;
|
|
70
72
|
until?: string;
|
|
71
73
|
/** Comma-separated category filters */
|
|
@@ -83,6 +85,9 @@ export interface QueryRequestBody {
|
|
|
83
85
|
minScore?: number;
|
|
84
86
|
collection?: string;
|
|
85
87
|
lang?: string;
|
|
88
|
+
intent?: string;
|
|
89
|
+
candidateLimit?: number;
|
|
90
|
+
exclude?: string;
|
|
86
91
|
since?: string;
|
|
87
92
|
until?: string;
|
|
88
93
|
/** Comma-separated category filters */
|
|
@@ -102,6 +107,10 @@ export interface AskRequestBody {
|
|
|
102
107
|
limit?: number;
|
|
103
108
|
collection?: string;
|
|
104
109
|
lang?: string;
|
|
110
|
+
intent?: string;
|
|
111
|
+
candidateLimit?: number;
|
|
112
|
+
exclude?: string;
|
|
113
|
+
queryModes?: QueryModeInput[];
|
|
105
114
|
since?: string;
|
|
106
115
|
until?: string;
|
|
107
116
|
/** Comma-separated category filters */
|
|
@@ -169,6 +178,73 @@ function parseCommaSeparatedValues(input: string): string[] {
|
|
|
169
178
|
);
|
|
170
179
|
}
|
|
171
180
|
|
|
181
|
+
function parseQueryModesInput(value: unknown): {
|
|
182
|
+
queryModes?: QueryModeInput[];
|
|
183
|
+
error?: Response;
|
|
184
|
+
} {
|
|
185
|
+
if (value === undefined) {
|
|
186
|
+
return {};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!Array.isArray(value)) {
|
|
190
|
+
return {
|
|
191
|
+
error: errorResponse(
|
|
192
|
+
"VALIDATION",
|
|
193
|
+
"queryModes must be an array of { mode, text } objects"
|
|
194
|
+
),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const queryModes: QueryModeInput[] = [];
|
|
199
|
+
let hydeCount = 0;
|
|
200
|
+
|
|
201
|
+
for (const [index, entry] of value.entries()) {
|
|
202
|
+
if (!entry || typeof entry !== "object") {
|
|
203
|
+
return {
|
|
204
|
+
error: errorResponse(
|
|
205
|
+
"VALIDATION",
|
|
206
|
+
`queryModes[${index}] must be an object`
|
|
207
|
+
),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const mode = (entry as { mode?: unknown }).mode;
|
|
212
|
+
const text = (entry as { text?: unknown }).text;
|
|
213
|
+
if (mode !== "term" && mode !== "intent" && mode !== "hyde") {
|
|
214
|
+
return {
|
|
215
|
+
error: errorResponse(
|
|
216
|
+
"VALIDATION",
|
|
217
|
+
`queryModes[${index}].mode must be one of: term, intent, hyde`
|
|
218
|
+
),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (typeof text !== "string" || !text.trim()) {
|
|
222
|
+
return {
|
|
223
|
+
error: errorResponse(
|
|
224
|
+
"VALIDATION",
|
|
225
|
+
`queryModes[${index}].text must be a non-empty string`
|
|
226
|
+
),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (mode === "hyde") {
|
|
231
|
+
hydeCount += 1;
|
|
232
|
+
if (hydeCount > 1) {
|
|
233
|
+
return {
|
|
234
|
+
error: errorResponse(
|
|
235
|
+
"VALIDATION",
|
|
236
|
+
"Only one hyde mode is allowed in queryModes"
|
|
237
|
+
),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
queryModes.push({ mode, text: text.trim() });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return { queryModes };
|
|
246
|
+
}
|
|
247
|
+
|
|
172
248
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
173
249
|
// Route Handlers
|
|
174
250
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -1093,6 +1169,15 @@ export async function handleSearch(
|
|
|
1093
1169
|
if (body.until !== undefined && typeof body.until !== "string") {
|
|
1094
1170
|
return errorResponse("VALIDATION", "until must be a string");
|
|
1095
1171
|
}
|
|
1172
|
+
if (body.intent !== undefined && typeof body.intent !== "string") {
|
|
1173
|
+
return errorResponse("VALIDATION", "intent must be a string");
|
|
1174
|
+
}
|
|
1175
|
+
if (body.exclude !== undefined && typeof body.exclude !== "string") {
|
|
1176
|
+
return errorResponse(
|
|
1177
|
+
"VALIDATION",
|
|
1178
|
+
"exclude must be a comma-separated string"
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1096
1181
|
if (body.category !== undefined && typeof body.category !== "string") {
|
|
1097
1182
|
return errorResponse(
|
|
1098
1183
|
"VALIDATION",
|
|
@@ -1132,6 +1217,9 @@ export async function handleSearch(
|
|
|
1132
1217
|
const categories = body.category
|
|
1133
1218
|
? parseCommaSeparatedValues(body.category)
|
|
1134
1219
|
: undefined;
|
|
1220
|
+
const exclude = body.exclude
|
|
1221
|
+
? parseCommaSeparatedValues(body.exclude)
|
|
1222
|
+
: undefined;
|
|
1135
1223
|
const author = body.author?.trim() || undefined;
|
|
1136
1224
|
|
|
1137
1225
|
// Only BM25 supported in web UI (vector/hybrid require LLM ports)
|
|
@@ -1139,6 +1227,8 @@ export async function handleSearch(
|
|
|
1139
1227
|
limit: Math.min(body.limit || 10, 50),
|
|
1140
1228
|
minScore: body.minScore,
|
|
1141
1229
|
collection: body.collection,
|
|
1230
|
+
intent: body.intent?.trim() || undefined,
|
|
1231
|
+
exclude,
|
|
1142
1232
|
tagsAll,
|
|
1143
1233
|
tagsAny,
|
|
1144
1234
|
since: body.since,
|
|
@@ -1208,6 +1298,24 @@ export async function handleQuery(
|
|
|
1208
1298
|
if (body.until !== undefined && typeof body.until !== "string") {
|
|
1209
1299
|
return errorResponse("VALIDATION", "until must be a string");
|
|
1210
1300
|
}
|
|
1301
|
+
if (body.intent !== undefined && typeof body.intent !== "string") {
|
|
1302
|
+
return errorResponse("VALIDATION", "intent must be a string");
|
|
1303
|
+
}
|
|
1304
|
+
if (body.exclude !== undefined && typeof body.exclude !== "string") {
|
|
1305
|
+
return errorResponse(
|
|
1306
|
+
"VALIDATION",
|
|
1307
|
+
"exclude must be a comma-separated string"
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
if (
|
|
1311
|
+
body.candidateLimit !== undefined &&
|
|
1312
|
+
(typeof body.candidateLimit !== "number" || body.candidateLimit < 1)
|
|
1313
|
+
) {
|
|
1314
|
+
return errorResponse(
|
|
1315
|
+
"VALIDATION",
|
|
1316
|
+
"candidateLimit must be a positive integer"
|
|
1317
|
+
);
|
|
1318
|
+
}
|
|
1211
1319
|
if (body.category !== undefined && typeof body.category !== "string") {
|
|
1212
1320
|
return errorResponse(
|
|
1213
1321
|
"VALIDATION",
|
|
@@ -1218,54 +1326,11 @@ export async function handleQuery(
|
|
|
1218
1326
|
return errorResponse("VALIDATION", "author must be a string");
|
|
1219
1327
|
}
|
|
1220
1328
|
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
"VALIDATION",
|
|
1227
|
-
"queryModes must be an array of { mode, text } objects"
|
|
1228
|
-
);
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
queryModes = [];
|
|
1232
|
-
let hydeCount = 0;
|
|
1233
|
-
|
|
1234
|
-
for (const [index, entry] of body.queryModes.entries()) {
|
|
1235
|
-
if (!entry || typeof entry !== "object") {
|
|
1236
|
-
return errorResponse(
|
|
1237
|
-
"VALIDATION",
|
|
1238
|
-
`queryModes[${index}] must be an object`
|
|
1239
|
-
);
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
const mode = (entry as { mode?: unknown }).mode;
|
|
1243
|
-
const text = (entry as { text?: unknown }).text;
|
|
1244
|
-
if (mode !== "term" && mode !== "intent" && mode !== "hyde") {
|
|
1245
|
-
return errorResponse(
|
|
1246
|
-
"VALIDATION",
|
|
1247
|
-
`queryModes[${index}].mode must be one of: term, intent, hyde`
|
|
1248
|
-
);
|
|
1249
|
-
}
|
|
1250
|
-
if (typeof text !== "string" || !text.trim()) {
|
|
1251
|
-
return errorResponse(
|
|
1252
|
-
"VALIDATION",
|
|
1253
|
-
`queryModes[${index}].text must be a non-empty string`
|
|
1254
|
-
);
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
if (mode === "hyde") {
|
|
1258
|
-
hydeCount += 1;
|
|
1259
|
-
if (hydeCount > 1) {
|
|
1260
|
-
return errorResponse(
|
|
1261
|
-
"VALIDATION",
|
|
1262
|
-
"Only one hyde mode is allowed in queryModes"
|
|
1263
|
-
);
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
queryModes.push({ mode, text: text.trim() });
|
|
1268
|
-
}
|
|
1329
|
+
const { queryModes, error: queryModesError } = parseQueryModesInput(
|
|
1330
|
+
body.queryModes
|
|
1331
|
+
);
|
|
1332
|
+
if (queryModesError) {
|
|
1333
|
+
return queryModesError;
|
|
1269
1334
|
}
|
|
1270
1335
|
|
|
1271
1336
|
// Parse tag filters
|
|
@@ -1297,6 +1362,9 @@ export async function handleQuery(
|
|
|
1297
1362
|
const categories = body.category
|
|
1298
1363
|
? parseCommaSeparatedValues(body.category)
|
|
1299
1364
|
: undefined;
|
|
1365
|
+
const exclude = body.exclude
|
|
1366
|
+
? parseCommaSeparatedValues(body.exclude)
|
|
1367
|
+
: undefined;
|
|
1300
1368
|
const author = body.author?.trim() || undefined;
|
|
1301
1369
|
|
|
1302
1370
|
const result = await searchHybrid(
|
|
@@ -1314,6 +1382,12 @@ export async function handleQuery(
|
|
|
1314
1382
|
minScore: body.minScore,
|
|
1315
1383
|
collection: body.collection,
|
|
1316
1384
|
lang: body.lang,
|
|
1385
|
+
intent: body.intent?.trim() || undefined,
|
|
1386
|
+
candidateLimit:
|
|
1387
|
+
body.candidateLimit !== undefined
|
|
1388
|
+
? Math.min(body.candidateLimit, 100)
|
|
1389
|
+
: undefined,
|
|
1390
|
+
exclude,
|
|
1317
1391
|
queryModes,
|
|
1318
1392
|
noExpand: body.noExpand,
|
|
1319
1393
|
noRerank: body.noRerank,
|
|
@@ -1377,6 +1451,24 @@ export async function handleAsk(
|
|
|
1377
1451
|
if (body.until !== undefined && typeof body.until !== "string") {
|
|
1378
1452
|
return errorResponse("VALIDATION", "until must be a string");
|
|
1379
1453
|
}
|
|
1454
|
+
if (body.intent !== undefined && typeof body.intent !== "string") {
|
|
1455
|
+
return errorResponse("VALIDATION", "intent must be a string");
|
|
1456
|
+
}
|
|
1457
|
+
if (body.exclude !== undefined && typeof body.exclude !== "string") {
|
|
1458
|
+
return errorResponse(
|
|
1459
|
+
"VALIDATION",
|
|
1460
|
+
"exclude must be a comma-separated string"
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
if (
|
|
1464
|
+
body.candidateLimit !== undefined &&
|
|
1465
|
+
(typeof body.candidateLimit !== "number" || body.candidateLimit < 1)
|
|
1466
|
+
) {
|
|
1467
|
+
return errorResponse(
|
|
1468
|
+
"VALIDATION",
|
|
1469
|
+
"candidateLimit must be a positive integer"
|
|
1470
|
+
);
|
|
1471
|
+
}
|
|
1380
1472
|
if (body.category !== undefined && typeof body.category !== "string") {
|
|
1381
1473
|
return errorResponse(
|
|
1382
1474
|
"VALIDATION",
|
|
@@ -1387,6 +1479,13 @@ export async function handleAsk(
|
|
|
1387
1479
|
return errorResponse("VALIDATION", "author must be a string");
|
|
1388
1480
|
}
|
|
1389
1481
|
|
|
1482
|
+
const { queryModes, error: queryModesError } = parseQueryModesInput(
|
|
1483
|
+
body.queryModes
|
|
1484
|
+
);
|
|
1485
|
+
if (queryModesError) {
|
|
1486
|
+
return queryModesError;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1390
1489
|
if (body.tagsAll) {
|
|
1391
1490
|
try {
|
|
1392
1491
|
tagsAll = parseAndValidateTagFilter(body.tagsAll);
|
|
@@ -1412,6 +1511,9 @@ export async function handleAsk(
|
|
|
1412
1511
|
const categories = body.category
|
|
1413
1512
|
? parseCommaSeparatedValues(body.category)
|
|
1414
1513
|
: undefined;
|
|
1514
|
+
const exclude = body.exclude
|
|
1515
|
+
? parseCommaSeparatedValues(body.exclude)
|
|
1516
|
+
: undefined;
|
|
1415
1517
|
const author = body.author?.trim() || undefined;
|
|
1416
1518
|
|
|
1417
1519
|
const limit = Math.min(body.limit ?? 5, 20);
|
|
@@ -1431,8 +1533,15 @@ export async function handleAsk(
|
|
|
1431
1533
|
limit,
|
|
1432
1534
|
collection: body.collection,
|
|
1433
1535
|
lang: body.lang,
|
|
1536
|
+
intent: body.intent?.trim() || undefined,
|
|
1434
1537
|
noExpand: body.noExpand,
|
|
1435
1538
|
noRerank: body.noRerank,
|
|
1539
|
+
candidateLimit:
|
|
1540
|
+
body.candidateLimit !== undefined
|
|
1541
|
+
? Math.min(body.candidateLimit, 100)
|
|
1542
|
+
: undefined,
|
|
1543
|
+
exclude,
|
|
1544
|
+
queryModes,
|
|
1436
1545
|
tagsAll,
|
|
1437
1546
|
tagsAny,
|
|
1438
1547
|
since: body.since,
|
|
@@ -1483,6 +1592,10 @@ export async function handleAsk(
|
|
|
1483
1592
|
expanded: searchResult.value.meta.expanded ?? false,
|
|
1484
1593
|
reranked: searchResult.value.meta.reranked ?? false,
|
|
1485
1594
|
vectorsUsed: searchResult.value.meta.vectorsUsed ?? false,
|
|
1595
|
+
intent: searchResult.value.meta.intent,
|
|
1596
|
+
candidateLimit: searchResult.value.meta.candidateLimit,
|
|
1597
|
+
exclude: searchResult.value.meta.exclude,
|
|
1598
|
+
queryModes: searchResult.value.meta.queryModes,
|
|
1486
1599
|
answerGenerated,
|
|
1487
1600
|
totalResults: results.length,
|
|
1488
1601
|
answerContext,
|