@wirecat/cli-core 0.433.0 → 0.435.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/CHANGELOG.md +43 -1
- package/dist/cli/messenger/context.js +1 -0
- package/dist/cli/messenger/conversation-answers.d.ts +1 -0
- package/dist/cli/messenger/conversations-command.js +2 -2
- package/dist/cli/messenger/message-answers.d.ts +1 -0
- package/dist/cli/messenger/message-writes.d.ts +2 -0
- package/dist/cli/messenger/messages-search-command.js +6 -10
- package/dist/cli/messenger/models-command.js +34 -7
- package/dist/cli/messenger/search-answers.d.ts +3 -0
- package/dist/cli/messenger/search-answers.js +40 -8
- package/dist/cli/messenger/search-command.js +3 -1
- package/dist/cli/messenger/store-maintenance-command.js +11 -5
- package/dist/cli/model-catalogue-command.js +1 -1
- package/dist/cli/settings.d.ts +4 -0
- package/dist/cli/settings.js +5 -0
- package/dist/cli/shared-command-exports.d.ts +4 -0
- package/dist/embeddings/child.js +15 -5
- package/dist/embeddings/embed.d.ts +16 -2
- package/dist/embeddings/embed.js +57 -28
- package/dist/embeddings/models.d.ts +18 -0
- package/dist/embeddings/models.js +69 -0
- package/dist/embeddings/process.d.ts +6 -1
- package/dist/embeddings/process.js +23 -7
- package/dist/embeddings/rerank.d.ts +10 -0
- package/dist/embeddings/rerank.js +31 -0
- package/dist/entities/conversation-results.d.ts +2 -0
- package/dist/entities/evidence.d.ts +8 -0
- package/dist/entities/index.d.ts +37 -4
- package/dist/entities/listener.d.ts +4 -0
- package/dist/entities/message-context.d.ts +3 -0
- package/dist/entities/message-write.d.ts +4 -0
- package/dist/entities/message.d.ts +2 -0
- package/dist/entities/message.js +2 -0
- package/dist/entities/model.d.ts +5 -2
- package/dist/entities/model.js +5 -2
- package/dist/entities/store-maintenance.d.ts +11 -3
- package/dist/entities/store-maintenance.js +18 -2
- package/dist/entities/store-records.d.ts +1 -0
- package/dist/errors/reasons.js +7 -0
- package/dist/mcp/tool.d.ts +1 -1
- package/dist/mcp/tool.js +1 -0
- package/dist/services/deps.d.ts +3 -0
- package/dist/services/embeddings-maintenance.d.ts +10 -2
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.js +1 -0
- package/dist/services/messages-archive-search.d.ts +2 -2
- package/dist/services/messages-archive-search.js +4 -4
- package/dist/services/messages-discovery.d.ts +33 -4
- package/dist/services/messages-discovery.js +88 -3
- package/dist/services/messages-meaning.d.ts +2 -1
- package/dist/services/messages-meaning.js +7 -3
- package/dist/services/messages-rerank.d.ts +7 -0
- package/dist/services/messages-rerank.js +42 -0
- package/dist/services/messages-search.d.ts +5 -0
- package/dist/services/messages.d.ts +6 -2
- package/dist/services/messages.js +4 -3
- package/dist/services/model-downloads.d.ts +3 -1
- package/dist/services/model-downloads.js +20 -1
- package/dist/services/notes-search.d.ts +7 -2
- package/dist/services/notes-search.js +7 -4
- package/dist/services/search-all-meetings.d.ts +3 -2
- package/dist/services/search-all-meetings.js +8 -8
- package/dist/services/search-all.d.ts +7 -1
- package/dist/services/search-all.js +109 -45
- package/dist/store/drivers/bun-sqlite.d.ts +6 -0
- package/dist/store/drivers/bun-sqlite.js +16 -1
- package/dist/store/drivers/node-sqlite.d.ts +5 -1
- package/dist/store/drivers/node-sqlite.js +15 -1
- package/dist/store/exports.d.ts +1 -0
- package/dist/store/exports.js +1 -0
- package/dist/store/sqlite/accounts.js +2 -2
- package/dist/store/sqlite/chats.d.ts +1 -1
- package/dist/store/sqlite/chats.js +11 -4
- package/dist/store/sqlite/document-pictures.js +5 -7
- package/dist/store/sqlite/email-index.d.ts +1 -3
- package/dist/store/sqlite/email-index.js +5 -21
- package/dist/store/sqlite/emails.js +4 -3
- package/dist/store/sqlite/image-chunks.d.ts +3 -3
- package/dist/store/sqlite/image-chunks.js +55 -25
- package/dist/store/sqlite/manifest.js +2 -0
- package/dist/store/sqlite/meeting-vectors.js +11 -9
- package/dist/store/sqlite/message-chunks.d.ts +5 -2
- package/dist/store/sqlite/message-chunks.js +44 -20
- package/dist/store/sqlite/migrations.generated.js +22 -0
- package/dist/store/sqlite/note-index.js +2 -7
- package/dist/store/sqlite/open-store.js +4 -0
- package/dist/store/sqlite/open.js +4 -6
- package/dist/store/sqlite/schema.d.ts +118 -1
- package/dist/store/sqlite/schema.js +18 -1
- package/dist/store/sqlite/vector-index.d.ts +76 -0
- package/dist/store/sqlite/vector-index.js +205 -0
- package/dist/store/sqlite/vectors.d.ts +8 -5
- package/dist/store/sqlite/vectors.js +82 -39
- package/dist/store/vector-extension.d.ts +10 -0
- package/dist/store/vector-extension.js +53 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,49 @@ Notable changes to `@wirecat/cli-core` (`@wirecat/cli-messaging` up to 0.261.0,
|
|
|
5
5
|
minor version may break callers, and says how under "Changed — may break callers". `pnpm docs:check` checks the
|
|
6
6
|
shape of this file.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## 0.435.0 — 25.10.2026
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`search messages --discover` reranks its first 50 hits, on its own, once the reranker is downloaded.** A cross-encoder reads the question with each hit, headed by its chat, author and date and preceded by the message it replies to, and its score is mixed with discovery's own, not put in its place: both scaled to 0–1 over those 50, then 0.3 · discovery + 0.7 · reranker; the hits after keep their order. This is the winning variant of the [evidence-pipeline benchmark](https://github.com/WireCatLabs/cli-testing/tree/main/performance/search/evidence-pipeline#reranker-variants) (holdout Answer@3 67% → 81% with mMARCO). Each hit it read has `discovery.reranker_score`; `score` stays discovery's. The default is `mmarco` (136 MB, `models download mmarco`); `bge-reranker-v2-m3` (588 MB) or `off` is the profile setting `search_reranker`. Loading and scoring do not count against the word search's 2 s: scoring has 15 s of its own, and a reranking that would not finish in them is dropped, never the search. Measured on a Ryzen AI 9 HX 470 through Core's WebAssembly runtime, 50 hits take mmarco about 0.9–1.7 s and BGE about 7–10 s. Without the model, discovery keeps its order and says so: a `not reranked: …` note in the terminal, `missing`/`next` (`models.download`) in `--json` and MCP. A search never downloads a model and never sends text anywhere; `--exact`, `--newest` and strict queries are untouched. `models list` shows the rerankers as kind `reranker`.
|
|
13
|
+
- **`search all` finds messages by meaning too, on its own, once it can.** It runs the same meaning lookup as
|
|
14
|
+
`search messages --discover` (e5-small over each message's own vector, the 100 nearest, every account the search
|
|
15
|
+
reads) and fuses it with the word hits by reciprocal rank fusion (k = 60) into one messages list, before the merge
|
|
16
|
+
with mail and notes. The query's filters apply to both; a query with NOT, or with a filter under OR or in a group,
|
|
17
|
+
stays words only, since the meaning hits cannot follow it (`cat OR fish` still uses meaning). Each item's `foundBy` says which search found it, and the answer's
|
|
18
|
+
`messages` says how they were searched. Without the model or the vectors, `search all` reads words as before and
|
|
19
|
+
says so: a `messages: meaning search not used: …` note, and `missing`/`next` (`models.download` or
|
|
20
|
+
`conversations.rebuild`) in `--json` and MCP. `--exact` and `--backend server` stay words only. A search never
|
|
21
|
+
downloads a model, embeds anything or sends text anywhere. `searchAll`, `searchAllWithMeetings` and
|
|
22
|
+
`searchAllIncludingMeetings` take the meaning lane as an optional last argument; a caller that passes none
|
|
23
|
+
(memo's `searchEverywhere` today) searches messages by words alone. `meaningLane` is exported and needs only
|
|
24
|
+
`{ env, store, messenger: { app: { command } } }`, so memo can build one without the messenger's services.
|
|
25
|
+
Messages and notes share one loaded model within a `search all`, so searching both by meaning loads e5-small once:
|
|
26
|
+
on a 10,000-message store with 200 notes the median went from 1.7 s to 0.9 s.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- **A vector goes once no text in the store uses it, by one rule for every kind.** Writing messages, images, notes,
|
|
31
|
+
documents and their pictures, mail and meeting transcripts, and purging an account or a chat, each had their own
|
|
32
|
+
query; they now all share one, which drops every model's vector of a released hash that no chunk
|
|
33
|
+
holds any more, and keeps one a chunk of any kind still holds. Deleting a note, document, memory or email still
|
|
34
|
+
goes through its delete trigger, which applies the same rule. Three writes left vectors behind and no longer do: an image whose
|
|
35
|
+
picture changed, a meeting transcript whose text changed, and a purged chat (`store clear --left` and a chat
|
|
36
|
+
merged into another), which also left its messages' and images' chunks. The rule reaches each vector by its key
|
|
37
|
+
(model, hash), where most of the old queries scanned every vector once per hash.
|
|
38
|
+
- **`conversations embeddings clear --chat` drops the chat's message vectors too**, not only its conversations'; a
|
|
39
|
+
vector a chunk outside the chat shares stays, and `conversations embed --chat` makes them again.
|
|
40
|
+
- **`store rebuild` drops the vectors no stored text uses**, which older builds left behind, and says how many in
|
|
41
|
+
`unused_vectors_dropped`. `store check` counts them (`vectors.unused`) and, when there are any, notes that
|
|
42
|
+
`store rebuild` drops them.
|
|
43
|
+
|
|
44
|
+
## 0.434.0 — 25.10.2026
|
|
45
|
+
|
|
46
|
+
### Changed — may break callers
|
|
47
|
+
|
|
48
|
+
- **Search by meaning ranks by a sqlite-vec index in the store (store version 34), and every store connection loads the extension.** Each embedding model gets a `vec0` table, `vector_index_<id>`, listed in the new `vector_indexes`; triggers on `chunks`, `embeddings` and `messages` keep it in step inside each write's own transaction. `nearestMessages`, `nearestImages` and conversation search (without a person, project, scope or conversation list) take candidates from it, check them with the scan's own SQL and score them as before, so they answer exactly what the scan answers; the scan stays for the filters the index cannot express. A store that held vectors is indexed on its first open by this version, in short batches. **Version 34 raises `minCompatible` to 34**: a build without the extension cannot write chunks to a store with these triggers, so older CLIs refuse the file and ask to be upgraded. The extension ships in `@wirecat/cli-core-sqlite` 1.1.0 (now a required runtime piece on every platform, Windows included); when it is missing or does not load, opening the store fails with `store.vector-extension-missing` or `store.vector-extension-failed` and says to reinstall. Under Node the store is opened with `allowExtension`, and SQL's `load_extension()` is switched off again right after.
|
|
49
|
+
- **`store check` reports `vector_indexes`** — per model, the rows its index holds and the rows it should, and how many differ either way (`missing`, `extra`), compared whole — and fails `checks.vector_indexes` when a ready index is off.
|
|
50
|
+
- **Vectors are stored at length one.** `saveVectors` and a document picture's vector are scaled to unit length before they are kept, so search by dot product and by the index rank alike; a vector with a non-finite value is refused (`store.vector-invalid`). Vectors stored before keep their length. The `not_applicable.extensions` field ("SQLite needs none") is gone.
|
|
9
51
|
|
|
10
52
|
## 0.433.0 — 25.10.2026
|
|
11
53
|
|
|
@@ -414,6 +414,7 @@ export const messengerContext = (command, messenger, resolving = {}) => {
|
|
|
414
414
|
history: base.settings.keepFailedRuns,
|
|
415
415
|
searchCatchUp: base.settings.searchCatchUp,
|
|
416
416
|
pictures: base.settings.pictureSearch,
|
|
417
|
+
reranker: base.settings.searchReranker,
|
|
417
418
|
guard,
|
|
418
419
|
...(embedders ? { embedders } : {}),
|
|
419
420
|
connection: async () => {
|
|
@@ -66,6 +66,7 @@ export declare const conversationShown: (account: AccountKey, { summary, message
|
|
|
66
66
|
missing_terms: string[];
|
|
67
67
|
parent?: string | undefined;
|
|
68
68
|
found_by?: ("meaning" | "words")[] | undefined;
|
|
69
|
+
reranker_score?: number | undefined;
|
|
69
70
|
} | undefined;
|
|
70
71
|
context?: {
|
|
71
72
|
ref: string;
|
|
@@ -125,8 +125,8 @@ const embeddingStatusCommand = (messenger) => withModelOptions(annotate(new Comm
|
|
|
125
125
|
"\n");
|
|
126
126
|
});
|
|
127
127
|
const embeddingClearCommand = (messenger) => withModelOptions(destroysNothing(annotate(new Command("clear"), { output: { entity: "conversation_change" }, effects: LOCAL_WRITE }))
|
|
128
|
-
.summary("remove stored conversation vectors")
|
|
129
|
-
.description("drop a chat's vectors, or only one model's; messages and conversations are never touched")
|
|
128
|
+
.summary("remove a chat's stored conversation and message vectors")
|
|
129
|
+
.description("drop a chat's conversation and message vectors, or only one model's; messages and conversations are never touched")
|
|
130
130
|
.option("--chat <chat>", messenger.chatArgument)).action(async function () {
|
|
131
131
|
const options = this.optsWithGlobals();
|
|
132
132
|
const chat = chatOf(options);
|
|
@@ -43,6 +43,7 @@ export declare const threadAnswer: ({ items, ...thread }: ThreadContext, reader:
|
|
|
43
43
|
missing_terms: string[];
|
|
44
44
|
parent?: string | undefined;
|
|
45
45
|
found_by?: ("meaning" | "words")[] | undefined;
|
|
46
|
+
reranker_score?: number | undefined;
|
|
46
47
|
} | undefined;
|
|
47
48
|
context?: {
|
|
48
49
|
ref: string;
|
|
@@ -47,6 +47,7 @@ export declare const writtenAnswer: ({ operationId, sendId, message, messages, s
|
|
|
47
47
|
missing_terms: string[];
|
|
48
48
|
parent?: string | undefined;
|
|
49
49
|
found_by?: ("meaning" | "words")[] | undefined;
|
|
50
|
+
reranker_score?: number | undefined;
|
|
50
51
|
} | undefined;
|
|
51
52
|
context?: {
|
|
52
53
|
ref: string;
|
|
@@ -236,6 +237,7 @@ export declare const writtenAnswer: ({ operationId, sendId, message, messages, s
|
|
|
236
237
|
missing_terms: string[];
|
|
237
238
|
parent?: string | undefined;
|
|
238
239
|
found_by?: ("meaning" | "words")[] | undefined;
|
|
240
|
+
reranker_score?: number | undefined;
|
|
239
241
|
} | undefined;
|
|
240
242
|
context?: {
|
|
241
243
|
ref: string;
|
|
@@ -12,7 +12,7 @@ import { renderAnswer } from "../envelope.js";
|
|
|
12
12
|
import { positiveCount } from "../paging.js";
|
|
13
13
|
import { archiveSummary } from "./archive-summary.js";
|
|
14
14
|
import { messengerContext } from "./context.js";
|
|
15
|
-
import { messagesAnswer, offsetOf } from "./search-answers.js";
|
|
15
|
+
import { meaningNote, messagesAnswer, offsetOf } from "./search-answers.js";
|
|
16
16
|
import { backendOptions, backendRequest, noteServer } from "./search-backend-options.js";
|
|
17
17
|
import { syncOptions, syncRequest, syncsFirst } from "./search-sync-options.js";
|
|
18
18
|
import { threadOptions, threadRequest } from "./thread-options.js";
|
|
@@ -130,15 +130,11 @@ const searchAction = (messenger) => async function (given) {
|
|
|
130
130
|
context.renderer.note(`${from} → ${to.join(", ")}`);
|
|
131
131
|
for (const { source, reason } of found.skipped ?? [])
|
|
132
132
|
context.renderer.note(`${source} not searched: ${reason}`);
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
? ` — \`${command} conversations rebuild\` gives messages their vectors, a batch a run`
|
|
139
|
-
: "";
|
|
140
|
-
context.renderer.note(`meaning search not used: ${meaning.reason}${step}`);
|
|
141
|
-
}
|
|
133
|
+
if (found.meaningSkipped)
|
|
134
|
+
context.renderer.note(meaningNote(found.meaningSkipped, command));
|
|
135
|
+
const rerank = found.rerankSkipped;
|
|
136
|
+
if (rerank)
|
|
137
|
+
context.renderer.note(`not reranked: ${rerank.reason}${rerank.step === "models.download" ? ` — \`${command} models download ${rerank.model}\`` : ""}`);
|
|
142
138
|
if (found.picturesSkipped && found.picturesModel === undefined)
|
|
143
139
|
context.renderer.note(`pictures not searched: ${found.picturesSkipped}`);
|
|
144
140
|
const stemming = found.query?.stemming;
|
|
@@ -4,10 +4,10 @@ import { annotate } from "@wirecat/cli-base/commands";
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { isTextModelInstalled, textModelsDirectory } from "../../embeddings/embed.js";
|
|
6
6
|
import { embeddingKeys } from "../../embeddings/keys.js";
|
|
7
|
-
import { DEFAULT_TEXT_MODEL, TEXT_MODELS } from "../../embeddings/models.js";
|
|
7
|
+
import { DEFAULT_RERANK_MODEL, DEFAULT_TEXT_MODEL, RERANK_MODELS, TEXT_MODELS, } from "../../embeddings/models.js";
|
|
8
8
|
import { model as modelEntity } from "../../entities/model.js";
|
|
9
9
|
import { DEFAULT_IMAGE_MODEL, IMAGE_MODELS, imageModelsDirectory, isImageModelInstalled, } from "../../images/picture/models.js";
|
|
10
|
-
import { downloadImageModel, downloadTextModel } from "../../services/model-downloads.js";
|
|
10
|
+
import { downloadImageModel, downloadRerankModel, downloadTextModel } from "../../services/model-downloads.js";
|
|
11
11
|
import { install, installedBytes, isInstalled, megabytes, modelPath, modelsDirectory, vadPath, } from "../../speech/install.js";
|
|
12
12
|
import { orderedModels, speechModel, VAD } from "../../speech/models.js";
|
|
13
13
|
import { choose } from "../../speech/transcribe.js";
|
|
@@ -44,6 +44,17 @@ const textItem = (model, directory) => ({
|
|
|
44
44
|
default: model.id === DEFAULT_TEXT_MODEL,
|
|
45
45
|
path: join(directory, model.id),
|
|
46
46
|
});
|
|
47
|
+
const rerankItem = (model, directory, chosen) => ({
|
|
48
|
+
id: model.id,
|
|
49
|
+
kind: "reranker",
|
|
50
|
+
title: model.title,
|
|
51
|
+
languages: model.languages,
|
|
52
|
+
licence: model.licence,
|
|
53
|
+
size_bytes: model.files.reduce((sum, file) => sum + file.bytes, 0),
|
|
54
|
+
downloaded: isTextModelInstalled(model, directory),
|
|
55
|
+
default: model.id === chosen,
|
|
56
|
+
path: join(directory, model.id),
|
|
57
|
+
});
|
|
47
58
|
const imageItem = (model, directory) => ({
|
|
48
59
|
id: model.id,
|
|
49
60
|
kind: "image",
|
|
@@ -93,6 +104,16 @@ const downloadEmbeddingModel = async (command, messenger, id, options) => {
|
|
|
93
104
|
renderAnswer(command, context, itemOf(textItem(model, directory)), downloaded);
|
|
94
105
|
}, { unbounded: true });
|
|
95
106
|
};
|
|
107
|
+
const downloadReranker = async (command, messenger, id) => {
|
|
108
|
+
const context = baseContext(command, messenger.resolveSettings);
|
|
109
|
+
await context.run(async () => {
|
|
110
|
+
const { model, directory } = await downloadRerankModel(id, {
|
|
111
|
+
env: context.env,
|
|
112
|
+
progress: (line) => context.renderer.note(line),
|
|
113
|
+
});
|
|
114
|
+
renderAnswer(command, context, itemOf(rerankItem(model, directory, context.settings.searchReranker ?? DEFAULT_RERANK_MODEL)), downloaded);
|
|
115
|
+
}, { unbounded: true });
|
|
116
|
+
};
|
|
96
117
|
const modelKeysCommand = (messenger, tool) => {
|
|
97
118
|
const key = new Command("keys").description("API keys for embedding and analysis providers");
|
|
98
119
|
grantsAuthority(annotate(key.command("set"), {
|
|
@@ -136,7 +157,7 @@ export const modelsCommand = (messenger) => {
|
|
|
136
157
|
effects: LOCAL_READ,
|
|
137
158
|
examples: [`${tool} models list`, `${tool} models list kind:audio`],
|
|
138
159
|
}), ROOT_MODELS_QUERY)
|
|
139
|
-
.description("speech and
|
|
160
|
+
.description("speech, embedding and reranker models: downloaded or not, each kind's default")
|
|
140
161
|
.action(async function (words) {
|
|
141
162
|
const terms = nativeTerms(listQuery(typedQuery(words), ROOT_MODELS_QUERY, "models list"), "models list");
|
|
142
163
|
const context = baseContext(this, messenger.resolveSettings);
|
|
@@ -150,25 +171,29 @@ export const modelsCommand = (messenger) => {
|
|
|
150
171
|
const textModels = kinds === undefined || kinds === "text"
|
|
151
172
|
? TEXT_MODELS.filter((model) => id === undefined || model.id === id)
|
|
152
173
|
: [];
|
|
174
|
+
const rerankers = kinds === undefined || kinds === "reranker"
|
|
175
|
+
? RERANK_MODELS.filter((model) => id === undefined || model.id === id)
|
|
176
|
+
: [];
|
|
153
177
|
const chosen = audioModels.length > 0 ? choose(messenger, context.settings, {}, context.env).model.id : "";
|
|
154
178
|
const catalogue = [
|
|
155
179
|
...audioModels.map((model) => audioItem(model, modelsDirectory(context.env), chosen)),
|
|
156
180
|
...textModels.map((model) => textItem(model, textModelsDirectory(context.env))),
|
|
181
|
+
...rerankers.map((model) => rerankItem(model, textModelsDirectory(context.env), context.settings.searchReranker ?? DEFAULT_RERANK_MODEL)),
|
|
157
182
|
];
|
|
158
183
|
const items = catalogue.filter((item) => kept === undefined || item.downloaded === (kept === "true"));
|
|
159
184
|
const next = items
|
|
160
185
|
.filter((item) => item.default && !item.downloaded)
|
|
161
186
|
.map((item) => nextStep("models.download", { args: { model: item.id } }));
|
|
162
|
-
renderAnswer(this, context, everyModel("models list", items, next), () => items.map((item) => `${item.kind.padEnd(
|
|
187
|
+
renderAnswer(this, context, everyModel("models list", items, next), () => items.map((item) => `${item.kind.padEnd(8)} ${modelLine(item, 18)} ${item.languages}\n`).join(""));
|
|
163
188
|
});
|
|
164
189
|
});
|
|
165
190
|
reachesOtherService(annotate(models.command("download"), {
|
|
166
191
|
effects: FETCH,
|
|
167
192
|
output: { entity: "model" },
|
|
168
|
-
examples: [`${tool} models download ${DEFAULT_TEXT_MODEL}`],
|
|
193
|
+
examples: [`${tool} models download ${DEFAULT_TEXT_MODEL}`, `${tool} models download ${DEFAULT_RERANK_MODEL}`],
|
|
169
194
|
}))
|
|
170
|
-
.summary("download and verify one speech or
|
|
171
|
-
.description("download and verify one speech or
|
|
195
|
+
.summary("download and verify one speech, embedding or reranker model")
|
|
196
|
+
.description("download and verify one speech, embedding or reranker model: tens to hundreds of MB, each size in models list")
|
|
172
197
|
.argument("<model>", "a model id from models list")
|
|
173
198
|
.option("--accept-terms", "accept the model's licence terms, for a model that has its own")
|
|
174
199
|
.action(async function (id, options) {
|
|
@@ -176,6 +201,8 @@ export const modelsCommand = (messenger) => {
|
|
|
176
201
|
return downloadAudioModel(this, messenger, id);
|
|
177
202
|
if (TEXT_MODELS.some((model) => model.id === id))
|
|
178
203
|
return downloadEmbeddingModel(this, messenger, id, options);
|
|
204
|
+
if (RERANK_MODELS.some((model) => model.id === id))
|
|
205
|
+
return downloadReranker(this, messenger, id);
|
|
179
206
|
throw new CliError("validation_error", "unknown model; choose an id from models list");
|
|
180
207
|
});
|
|
181
208
|
return models;
|
|
@@ -2,6 +2,7 @@ import type { Missing, NextStep } from "@wirecat/cli-base/standards";
|
|
|
2
2
|
import type { InferOutput } from "valibot";
|
|
3
3
|
import type { ENTITIES } from "../../entities/index.js";
|
|
4
4
|
import type { FoundMessage, SearchFound } from "../../services/messages.js";
|
|
5
|
+
import type { MeaningSkipped } from "../../services/messages-discovery.js";
|
|
5
6
|
import type { FoundNote, NotesFoundOf } from "../../services/notes-search.js";
|
|
6
7
|
import type { SearchAllItem } from "../../services/search-all.js";
|
|
7
8
|
import type { MeetingSearchItem, SearchAllIncludingMeetingsFound } from "../../services/search-all-meetings.js";
|
|
@@ -20,6 +21,8 @@ export declare const coverageGaps: (found: Pick<SearchFound, "coverage" | "refre
|
|
|
20
21
|
next: NextStep[];
|
|
21
22
|
missing: Missing[];
|
|
22
23
|
};
|
|
24
|
+
/** The pretty note for messages searched by words only, with the command that would add meaning. */
|
|
25
|
+
export declare const meaningNote: (meaning: MeaningSkipped, command: string) => string;
|
|
23
26
|
export interface Paging {
|
|
24
27
|
limit: number;
|
|
25
28
|
page?: number;
|
|
@@ -31,6 +31,7 @@ export const messageHitItem = (hit, query = "") => {
|
|
|
31
31
|
missing_terms: hit.discovery.missingTerms,
|
|
32
32
|
parent: hit.discovery.parent,
|
|
33
33
|
found_by: hit.discovery.foundBy,
|
|
34
|
+
reranker_score: hit.discovery.rerankerScore,
|
|
34
35
|
})
|
|
35
36
|
: undefined,
|
|
36
37
|
email: hit.email
|
|
@@ -88,6 +89,31 @@ export const coverageGaps = (found, shown) => {
|
|
|
88
89
|
],
|
|
89
90
|
};
|
|
90
91
|
};
|
|
92
|
+
/** Messages searched by words only, and the step that would add meaning: `--discover` and `search all` alike. */
|
|
93
|
+
const messageMeaningGap = (meaning, field) => {
|
|
94
|
+
const next = [];
|
|
95
|
+
if (!meaning)
|
|
96
|
+
return { next, missing: [] };
|
|
97
|
+
if (meaning.step === "models.download")
|
|
98
|
+
next.push(nextStep("models.download", { args: { model: meaning.model } }));
|
|
99
|
+
if (meaning.step === "conversations.rebuild")
|
|
100
|
+
next.push(nextStep("conversations.rebuild", { args: {} }));
|
|
101
|
+
return {
|
|
102
|
+
next,
|
|
103
|
+
missing: [
|
|
104
|
+
{ field, why: `searched by words only: ${meaning.reason}`, ...(meaning.step ? { step: meaning.step } : {}) },
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
/** The pretty note for messages searched by words only, with the command that would add meaning. */
|
|
109
|
+
export const meaningNote = (meaning, command) => {
|
|
110
|
+
const step = meaning.step === "models.download"
|
|
111
|
+
? ` — \`${command} models download ${meaning.model}\`, then \`${command} conversations rebuild\` gives messages their vectors`
|
|
112
|
+
: meaning.step === "conversations.rebuild"
|
|
113
|
+
? ` — \`${command} conversations rebuild\` gives messages their vectors, a batch a run`
|
|
114
|
+
: "";
|
|
115
|
+
return `meaning search not used: ${meaning.reason}${step}`;
|
|
116
|
+
};
|
|
91
117
|
const gapsOf = (found) => {
|
|
92
118
|
const { next, missing } = coverageGaps(found, found.items.length);
|
|
93
119
|
const building = !found.wordsReady || (found.query?.stemming?.applied === false && found.query.stemming.reason === "building");
|
|
@@ -101,16 +127,19 @@ const gapsOf = (found) => {
|
|
|
101
127
|
step: "store.migrate",
|
|
102
128
|
});
|
|
103
129
|
}
|
|
104
|
-
const meaning = found.meaningSkipped;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
130
|
+
const meaning = messageMeaningGap(found.meaningSkipped, "discovery");
|
|
131
|
+
for (const step of meaning.next)
|
|
132
|
+
next.push(step);
|
|
133
|
+
for (const gap of meaning.missing)
|
|
134
|
+
missing.push(gap);
|
|
135
|
+
const rerank = found.rerankSkipped;
|
|
136
|
+
if (rerank) {
|
|
137
|
+
if (rerank.step === "models.download")
|
|
138
|
+
next.push(nextStep("models.download", { args: { model: rerank.model } }));
|
|
110
139
|
missing.push({
|
|
111
140
|
field: "discovery",
|
|
112
|
-
why: `
|
|
113
|
-
...(
|
|
141
|
+
why: `not reranked: ${rerank.reason}`,
|
|
142
|
+
...(rerank.step ? { step: rerank.step } : {}),
|
|
114
143
|
});
|
|
115
144
|
}
|
|
116
145
|
if (found.picturesModel !== undefined) {
|
|
@@ -197,6 +226,7 @@ export const notesAnswer = (command, found, { limit, page, again }, exact = fals
|
|
|
197
226
|
export const allAnswer = (command, found, { limit, page, again }, exact = false) => {
|
|
198
227
|
const scan = found.meetings?.complete === false ? found.meetings : undefined;
|
|
199
228
|
const notes = meaningGap(found.notes?.meaningSkipped, exact);
|
|
229
|
+
const messages = messageMeaningGap(found.messages?.meaningSkipped, "found_by");
|
|
200
230
|
return listOf(command, {
|
|
201
231
|
items: found.items.map(allHitItem),
|
|
202
232
|
limit,
|
|
@@ -214,6 +244,7 @@ export const allAnswer = (command, found, { limit, page, again }, exact = false)
|
|
|
214
244
|
]
|
|
215
245
|
: []),
|
|
216
246
|
...notes.next,
|
|
247
|
+
...messages.next.filter((step) => !notes.next.some((asked) => JSON.stringify(asked) === JSON.stringify(step))),
|
|
217
248
|
],
|
|
218
249
|
missing: [
|
|
219
250
|
...(scan
|
|
@@ -226,6 +257,7 @@ export const allAnswer = (command, found, { limit, page, again }, exact = false)
|
|
|
226
257
|
]
|
|
227
258
|
: []),
|
|
228
259
|
...notes.missing,
|
|
260
|
+
...messages.missing,
|
|
229
261
|
],
|
|
230
262
|
meta: metaOf(found.accounts, found.skipped),
|
|
231
263
|
});
|
|
@@ -11,7 +11,7 @@ import { commandAt } from "../steps.js";
|
|
|
11
11
|
import { messengerContext } from "./context.js";
|
|
12
12
|
import { conversationsSearchCommand } from "./conversations-command.js";
|
|
13
13
|
import { messagesSearchCommand } from "./messages-search-command.js";
|
|
14
|
-
import { allAnswer, notesAnswer, offsetOf, SOURCE_SEARCHED } from "./search-answers.js";
|
|
14
|
+
import { allAnswer, meaningNote, notesAnswer, offsetOf, SOURCE_SEARCHED, } from "./search-answers.js";
|
|
15
15
|
import { backendOptions, backendRequest, noteServer } from "./search-backend-options.js";
|
|
16
16
|
import { topicsSearchCommand } from "./topics-command.js";
|
|
17
17
|
const onlyOf = (value) => {
|
|
@@ -79,6 +79,8 @@ const allCommand = (messenger) => {
|
|
|
79
79
|
context.renderer.note(`${resource ?? source} not searched: ${reason}`);
|
|
80
80
|
if (found.notes?.meaningSkipped)
|
|
81
81
|
context.renderer.note(`notes by words only: ${found.notes.meaningSkipped}`);
|
|
82
|
+
if (found.messages?.meaningSkipped)
|
|
83
|
+
context.renderer.note(`messages: ${meaningNote(found.messages.meaningSkipped, messenger.app.command)}`);
|
|
82
84
|
const again = {
|
|
83
85
|
query: words.join(" "),
|
|
84
86
|
...(only === undefined ? {} : { only: only.join(",") }),
|
|
@@ -12,7 +12,7 @@ import { migrate } from "../../store/migrations.js";
|
|
|
12
12
|
import { openCache } from "../../store/open.js";
|
|
13
13
|
import { storePath } from "../../store/path.js";
|
|
14
14
|
import { deleteCopy, repairStore } from "../../store/repair.js";
|
|
15
|
-
import { drainNoteIndex, fillSearchIndex, fillStems, involvementStoreOver, noteIndexState, openSqlite, openStore, orphanPointers, pendingNormalization, resetAttachmentWords, resetNoteIndex, resetSearchIndex, resetStems, searchIndexState, stemmerCache, stemsState, } from "../../store/store.js";
|
|
15
|
+
import { drainNoteIndex, dropAllUnusedVectors, fillSearchIndex, fillStems, involvementStoreOver, noteIndexState, openSqlite, openStore, orphanPointers, pendingNormalization, resetAttachmentWords, resetNoteIndex, resetSearchIndex, resetStems, searchIndexState, stemmerCache, stemsState, vectorIndexCounts, } from "../../store/store.js";
|
|
16
16
|
import { environmentOf, outputFor } from "../context.js";
|
|
17
17
|
import { destroysNothing, LOCAL_READ, LOCAL_WRITE } from "../effects.js";
|
|
18
18
|
import { itemOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
@@ -217,6 +217,8 @@ const checkCommand = (messenger) => annotate(new Command("check"), { output: { e
|
|
|
217
217
|
const orphans = answer.orphanPointers.reduce((sum, { count }) => sum + count, 0);
|
|
218
218
|
if (orphans > 0)
|
|
219
219
|
renderer.note(`${orphans} pointers name a row that is gone or a type the store does not know — see orphan_pointers`);
|
|
220
|
+
if (answer.vectors && answer.vectors.unused > 0)
|
|
221
|
+
renderer.note(`${answer.vectors.unused} vectors belong to no text the store still holds — \`${command} store rebuild\` drops them`);
|
|
220
222
|
const older = answer.conversations.filter((chat) => chat.provider === messenger.provider && !chat.current).length;
|
|
221
223
|
if (older > 0) {
|
|
222
224
|
renderer.note(`${older} chats' conversations were built by older rules — \`${command} conversations links rebuild --chat <chat>\``);
|
|
@@ -241,11 +243,14 @@ const inspect = (path) => reading(path, (database) => {
|
|
|
241
243
|
const { bavail, bsize } = statfsSync(dirname(path));
|
|
242
244
|
const free = Number(bavail) * Number(bsize);
|
|
243
245
|
const behind = schema.version > 0 ? chatsBehind(database) : [];
|
|
246
|
+
const vectorIndexes = schema.version >= 34 ? vectorIndexCounts(database) : [];
|
|
244
247
|
const checks = {
|
|
245
248
|
schema: schema.version >= SPEAKS && schema.writable,
|
|
246
249
|
integrity: integrity.length === 1 && integrity[0] === "ok",
|
|
247
250
|
foreignKeys: foreignKeys === 0,
|
|
248
251
|
searchIndexes: Object.values(searchIndexes).every((state) => state === "ok"),
|
|
252
|
+
// One that is still filling is short by design; a ready one must hold exactly the rows its sources give.
|
|
253
|
+
vectorIndexes: vectorIndexes.every(({ ready, missing, extra }) => !ready || missing + extra === 0),
|
|
249
254
|
// A backup or a VACUUM needs about as much again.
|
|
250
255
|
disk: free >= size,
|
|
251
256
|
};
|
|
@@ -268,7 +273,7 @@ const inspect = (path) => reading(path, (database) => {
|
|
|
268
273
|
orphanPointers: schema.version > 0 ? orphanPointers(database) : [],
|
|
269
274
|
conversations: conversationsBuilt(database),
|
|
270
275
|
vectors: vectorsHeld(database),
|
|
271
|
-
|
|
276
|
+
vectorIndexes,
|
|
272
277
|
};
|
|
273
278
|
});
|
|
274
279
|
/** `rank = 1` compares the index with its table; without it a stale external-content index passes. */
|
|
@@ -282,8 +287,8 @@ const indexIntegrity = (database, index, rank = 1) => {
|
|
|
282
287
|
}
|
|
283
288
|
};
|
|
284
289
|
/**
|
|
285
|
-
* Vectors per model, and how many no chunk
|
|
286
|
-
*
|
|
290
|
+
* Vectors per model, and how many no chunk points at any more. Each write that drops chunks drops those
|
|
291
|
+
* (`dropUnusedVectors`), so a count above zero was most likely left by an older build; `store rebuild` drops them.
|
|
287
292
|
*/
|
|
288
293
|
const vectorsHeld = (database) => {
|
|
289
294
|
const exists = database.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'embeddings'").get();
|
|
@@ -415,7 +420,7 @@ const buildNoteIndex = (database, note, { reset = false } = {}) => {
|
|
|
415
420
|
};
|
|
416
421
|
const reindexCommand = (messenger) => destroysNothing(annotate(new Command("rebuild"), { output: { entity: "store_reindex" }, effects: LOCAL_WRITE }))
|
|
417
422
|
.summary("rebuild the local archive's search indexes")
|
|
418
|
-
.description("rebuild the word index, its typo vocabulary, the stems, the files' word index, the notes' indexes and who took part in what, from what is stored; loses nothing")
|
|
423
|
+
.description("rebuild the word index, its typo vocabulary, the stems, the files' word index, the notes' indexes and who took part in what, from what is stored, and drop the vectors no stored text uses; loses nothing")
|
|
419
424
|
.action(async function () {
|
|
420
425
|
const { renderer } = outputFor(this);
|
|
421
426
|
const path = storePath(environmentOf(this).env ?? process.env);
|
|
@@ -443,6 +448,7 @@ const reindexCommand = (messenger) => destroysNothing(annotate(new Command("rebu
|
|
|
443
448
|
...buildStems(database, (note) => renderer.note(note), { force: true }),
|
|
444
449
|
...buildNoteIndex(database, (note) => renderer.note(note), { reset: true }),
|
|
445
450
|
involvements: involvementStoreOver({ database, now: Date.now }).rebuild(),
|
|
451
|
+
unusedVectorsDropped: dropAllUnusedVectors(database),
|
|
446
452
|
};
|
|
447
453
|
});
|
|
448
454
|
result(this, answer);
|
|
@@ -15,7 +15,7 @@ export const MODEL_CATALOGUE_QUERY = defineQuery({
|
|
|
15
15
|
entity: "model",
|
|
16
16
|
backend: "native",
|
|
17
17
|
fields: [
|
|
18
|
-
termField("kind", "enum", "kind:audio", { values: ["audio", "text"] }),
|
|
18
|
+
termField("kind", "enum", "kind:audio", { values: ["audio", "text", "reranker"] }),
|
|
19
19
|
termField("id", "kw", "id:e5-small"),
|
|
20
20
|
termField("downloaded", "bool", "downloaded:true"),
|
|
21
21
|
],
|
package/dist/cli/settings.d.ts
CHANGED
|
@@ -82,6 +82,8 @@ export interface Settings extends AISettings {
|
|
|
82
82
|
searchCatchUp?: boolean;
|
|
83
83
|
/** Search and `images embed` read the pictures themselves, with a model on this machine. Off until set. */
|
|
84
84
|
pictureSearch?: boolean;
|
|
85
|
+
/** The reranker that orders `search messages --discover`'s first hits once it is downloaded, or `off`. */
|
|
86
|
+
searchReranker?: string;
|
|
85
87
|
limit: number;
|
|
86
88
|
/** Which page, 1-based. Per invocation only — a page number in a file is a setting nobody wants twice. */
|
|
87
89
|
page: number;
|
|
@@ -271,6 +273,7 @@ export declare const settingsFor: <T extends object = Record<never, never>>(app:
|
|
|
271
273
|
speech_model: v.OptionalSchema<v.StringSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
272
274
|
search_catch_up: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
273
275
|
picture_search: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
276
|
+
search_reranker: v.OptionalSchema<v.PicklistSchema<[...string[], "off"], (issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
274
277
|
update_check: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
275
278
|
skill_hint: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
276
279
|
}, (issue: v.StrictObjectIssue) => string>, undefined>;
|
|
@@ -343,6 +346,7 @@ export declare const settingsFor: <T extends object = Record<never, never>>(app:
|
|
|
343
346
|
speech_model: v.OptionalSchema<v.StringSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
344
347
|
search_catch_up: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
345
348
|
picture_search: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
349
|
+
search_reranker: v.OptionalSchema<v.PicklistSchema<[...string[], "off"], (issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
346
350
|
}, (issue: v.StrictObjectIssue) => string>, "has to be an object of profiles, by name">, {}>;
|
|
347
351
|
personal: v.OptionalSchema<v.StrictObjectSchema<{
|
|
348
352
|
readonly defaults: v.OptionalSchema<v.StrictObjectSchema<v.ObjectEntries, (issue: v.StrictObjectIssue) => string>, undefined>;
|
package/dist/cli/settings.js
CHANGED
|
@@ -3,6 +3,7 @@ import { CliError, configFilePath, resolvePaths, saveConfigFile } from "@wirecat
|
|
|
3
3
|
import * as v from "valibot";
|
|
4
4
|
import { AI_ENTRIES, resolveAISettings } from "../analysis/settings.js";
|
|
5
5
|
import { parseDuration } from "../domain/duration.js";
|
|
6
|
+
import { DEFAULT_RERANK_MODEL, RERANK_MODELS } from "../embeddings/models.js";
|
|
6
7
|
import { envName, readOnlyLockOf } from "../ports/app.js";
|
|
7
8
|
import { assertOverridesTighten, CEILING_KEY, LEVELS, layerPermissions, permissionOverrides, RESOURCES, } from "../sends/permissions.js";
|
|
8
9
|
import { storePath } from "../store/path.js";
|
|
@@ -63,6 +64,7 @@ const SHARED_PROFILE_ENTRIES = {
|
|
|
63
64
|
speech_model: v.optional(v.string(plain("has to be a model id from `models list`, in quotes"))),
|
|
64
65
|
search_catch_up: v.optional(flag),
|
|
65
66
|
picture_search: v.optional(flag),
|
|
67
|
+
search_reranker: v.optional(v.picklist([...RERANK_MODELS.map((model) => model.id), "off"], plain(`has to be off or a reranker id: ${RERANK_MODELS.map((model) => model.id).join(", ")}`))),
|
|
66
68
|
...AI_ENTRIES,
|
|
67
69
|
};
|
|
68
70
|
/**
|
|
@@ -228,6 +230,7 @@ export const settingsFor = (app, extension = {}) => {
|
|
|
228
230
|
const senderColors = fromLayers("sender_colors", false);
|
|
229
231
|
const searchCatchUp = fromLayers("search_catch_up", false);
|
|
230
232
|
const pictureSearch = fromLayers("picture_search", false);
|
|
233
|
+
const searchReranker = fromLayers("search_reranker", DEFAULT_RERANK_MODEL);
|
|
231
234
|
const keepRunsForDays = fromLayers("keep_runs_for_days", DEFAULT_KEEP_RUNS_FOR_DAYS);
|
|
232
235
|
// Set where an agent runs unattended; the config file and `--permission` cannot undo it.
|
|
233
236
|
const readOnlyLock = readOnlyLockOf(app, env);
|
|
@@ -275,6 +278,7 @@ export const settingsFor = (app, extension = {}) => {
|
|
|
275
278
|
senderColors: senderColors.value,
|
|
276
279
|
searchCatchUp: searchCatchUp.value,
|
|
277
280
|
pictureSearch: pictureSearch.value,
|
|
281
|
+
searchReranker: searchReranker.value,
|
|
278
282
|
limit: limit.value,
|
|
279
283
|
page: flags.page ?? 1,
|
|
280
284
|
all: flags.all === true,
|
|
@@ -308,6 +312,7 @@ export const settingsFor = (app, extension = {}) => {
|
|
|
308
312
|
senderColors: senderColors.from,
|
|
309
313
|
searchCatchUp: searchCatchUp.from,
|
|
310
314
|
pictureSearch: pictureSearch.from,
|
|
315
|
+
searchReranker: searchReranker.from,
|
|
311
316
|
record: record.from,
|
|
312
317
|
keepRunsForDays: keepRunsForDays.from,
|
|
313
318
|
sendsPerHour: sendsPerHour.from,
|
|
@@ -87,6 +87,7 @@ export declare const configCommand: (app: import("./entry.js").AppIdentity, conf
|
|
|
87
87
|
speech_model: import("valibot").OptionalSchema<import("valibot").StringSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
88
88
|
search_catch_up: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
89
89
|
picture_search: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
90
|
+
search_reranker: import("valibot").OptionalSchema<import("valibot").PicklistSchema<[...string[], "off"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
90
91
|
update_check: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
91
92
|
skill_hint: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
92
93
|
}, (issue: import("valibot").StrictObjectIssue) => string>, undefined>;
|
|
@@ -159,6 +160,7 @@ export declare const configCommand: (app: import("./entry.js").AppIdentity, conf
|
|
|
159
160
|
speech_model: import("valibot").OptionalSchema<import("valibot").StringSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
160
161
|
search_catch_up: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
161
162
|
picture_search: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
163
|
+
search_reranker: import("valibot").OptionalSchema<import("valibot").PicklistSchema<[...string[], "off"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
162
164
|
}, (issue: import("valibot").StrictObjectIssue) => string>, "has to be an object of profiles, by name">, {}>;
|
|
163
165
|
personal: import("valibot").OptionalSchema<import("valibot").StrictObjectSchema<{
|
|
164
166
|
readonly defaults: import("valibot").OptionalSchema<import("valibot").StrictObjectSchema<import("valibot").ObjectEntries, (issue: import("valibot").StrictObjectIssue) => string>, undefined>;
|
|
@@ -286,6 +288,7 @@ export declare const completeCommand: (messenger: import("./index.js").Messenger
|
|
|
286
288
|
speech_model: import("valibot").OptionalSchema<import("valibot").StringSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
287
289
|
search_catch_up: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
288
290
|
picture_search: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
291
|
+
search_reranker: import("valibot").OptionalSchema<import("valibot").PicklistSchema<[...string[], "off"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
289
292
|
update_check: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
290
293
|
skill_hint: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
291
294
|
}, (issue: import("valibot").StrictObjectIssue) => string>, undefined>;
|
|
@@ -358,6 +361,7 @@ export declare const completeCommand: (messenger: import("./index.js").Messenger
|
|
|
358
361
|
speech_model: import("valibot").OptionalSchema<import("valibot").StringSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
359
362
|
search_catch_up: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
360
363
|
picture_search: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
364
|
+
search_reranker: import("valibot").OptionalSchema<import("valibot").PicklistSchema<[...string[], "off"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
361
365
|
}, (issue: import("valibot").StrictObjectIssue) => string>, "has to be an object of profiles, by name">, {}>;
|
|
362
366
|
personal: import("valibot").OptionalSchema<import("valibot").StrictObjectSchema<{
|
|
363
367
|
readonly defaults: import("valibot").OptionalSchema<import("valibot").StrictObjectSchema<import("valibot").ObjectEntries, (issue: import("valibot").StrictObjectIssue) => string>, undefined>;
|
package/dist/embeddings/child.js
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import { createInterface } from "node:readline";
|
|
2
2
|
import { openEmbedder } from "./embed.js";
|
|
3
|
-
|
|
4
|
-
const
|
|
3
|
+
import { openReranker } from "./rerank.js";
|
|
4
|
+
const { model, directory, threads, rerank } = JSON.parse(process.argv[2] ?? "{}");
|
|
5
|
+
const session = rerank
|
|
6
|
+
? { reranker: await openReranker(model, directory, { threads }) }
|
|
7
|
+
: { embedder: await openEmbedder(model, directory, { threads }) };
|
|
5
8
|
// stdout carries the answers alone, one JSON line each: anything else there breaks the parent's reading.
|
|
6
9
|
const answer = (line) => process.stdout.write(`${JSON.stringify(line)}\n`);
|
|
7
10
|
answer({ ready: true });
|
|
8
11
|
for await (const line of createInterface({ input: process.stdin })) {
|
|
9
|
-
const
|
|
12
|
+
const request = JSON.parse(line);
|
|
13
|
+
const { id } = request;
|
|
10
14
|
try {
|
|
11
|
-
|
|
15
|
+
if (session.reranker)
|
|
16
|
+
answer({ id, scores: await session.reranker.score(request.question, request.texts) });
|
|
17
|
+
else if (session.embedder)
|
|
18
|
+
answer({
|
|
19
|
+
id,
|
|
20
|
+
vectors: (await session.embedder.embed(request.texts, request.kind)).map((vector) => Array.from(vector)),
|
|
21
|
+
});
|
|
12
22
|
}
|
|
13
23
|
catch (error) {
|
|
14
24
|
answer({ id, error: error instanceof Error ? error.message : String(error) });
|
|
15
25
|
}
|
|
16
26
|
}
|
|
17
|
-
await embedder
|
|
27
|
+
await (session.reranker ?? session.embedder)?.close();
|
|
18
28
|
process.exit(0);
|