@tachu/extensions 1.0.0-alpha.6 → 1.0.0-rc.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 +25 -236
- package/README.md +53 -998
- package/README_ZH.md +52 -973
- package/dist/backends/file.d.ts +6 -6
- package/dist/backends/file.d.ts.map +1 -1
- package/dist/backends/file.js +12 -12
- package/dist/backends/file.js.map +1 -1
- package/dist/backends/terminal.d.ts +6 -6
- package/dist/backends/terminal.d.ts.map +1 -1
- package/dist/backends/terminal.js +9 -9
- package/dist/backends/terminal.js.map +1 -1
- package/dist/backends/web.d.ts +6 -6
- package/dist/backends/web.d.ts.map +1 -1
- package/dist/backends/web.js +8 -8
- package/dist/backends/web.js.map +1 -1
- package/dist/common/path.d.ts +14 -14
- package/dist/common/path.d.ts.map +1 -1
- package/dist/common/path.js +3 -3
- package/dist/common/process.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/sse-adapter.d.ts +38 -38
- package/dist/mcp/sse-adapter.d.ts.map +1 -1
- package/dist/mcp/sse-adapter.js +34 -34
- package/dist/mcp/sse-adapter.js.map +1 -1
- package/dist/mcp/stdio-adapter.d.ts +38 -38
- package/dist/mcp/stdio-adapter.d.ts.map +1 -1
- package/dist/mcp/stdio-adapter.js +34 -34
- package/dist/mcp/stdio-adapter.js.map +1 -1
- package/dist/memory/fs-memory-system.d.ts +199 -58
- package/dist/memory/fs-memory-system.d.ts.map +1 -1
- package/dist/memory/fs-memory-system.js +290 -74
- package/dist/memory/fs-memory-system.js.map +1 -1
- package/dist/memory/index.d.ts +3 -0
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +3 -0
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/projection-outbox.d.ts +69 -0
- package/dist/memory/projection-outbox.d.ts.map +1 -0
- package/dist/memory/projection-outbox.js +187 -0
- package/dist/memory/projection-outbox.js.map +1 -0
- package/dist/memory/projection-projector.d.ts +16 -0
- package/dist/memory/projection-projector.d.ts.map +1 -0
- package/dist/memory/projection-projector.js +56 -0
- package/dist/memory/projection-projector.js.map +1 -0
- package/dist/memory/projection-worker.d.ts +28 -0
- package/dist/memory/projection-worker.d.ts.map +1 -0
- package/dist/memory/projection-worker.js +84 -0
- package/dist/memory/projection-worker.js.map +1 -0
- package/dist/observability/jsonl-emitter.d.ts +25 -25
- package/dist/observability/jsonl-emitter.d.ts.map +1 -1
- package/dist/observability/jsonl-emitter.js +25 -25
- package/dist/observability/jsonl-emitter.js.map +1 -1
- package/dist/observability/otel-emitter.d.ts +23 -23
- package/dist/observability/otel-emitter.d.ts.map +1 -1
- package/dist/observability/otel-emitter.js +37 -28
- package/dist/observability/otel-emitter.js.map +1 -1
- package/dist/providers/anthropic.d.ts +39 -39
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +35 -35
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/mock.d.ts +33 -33
- package/dist/providers/mock.d.ts.map +1 -1
- package/dist/providers/mock.js +24 -24
- package/dist/providers/mock.js.map +1 -1
- package/dist/providers/openai.d.ts +51 -50
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +81 -46
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/qwen.d.ts +39 -38
- package/dist/providers/qwen.d.ts.map +1 -1
- package/dist/providers/qwen.js +27 -24
- package/dist/providers/qwen.js.map +1 -1
- package/dist/safety/default-gate.d.ts +35 -35
- package/dist/safety/default-gate.d.ts.map +1 -1
- package/dist/safety/default-gate.js +3 -3
- package/dist/safety/default-gate.js.map +1 -1
- package/dist/tools/_shared/web-client.d.ts +1 -1
- package/dist/tools/_shared/web-client.js +1 -1
- package/dist/tools/apply-patch/executor.js.map +1 -1
- package/dist/tools/fetch-url/executor.d.ts +2 -2
- package/dist/tools/fetch-url/executor.js +7 -7
- package/dist/tools/git-blame/executor.js.map +1 -1
- package/dist/tools/git-branch/executor.js +2 -2
- package/dist/tools/git-branch/executor.js.map +1 -1
- package/dist/tools/git-diff/executor.js.map +1 -1
- package/dist/tools/git-show/executor.js.map +1 -1
- package/dist/tools/git-status/executor.js.map +1 -1
- package/dist/tools/glob/executor.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +3 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/list-dir/executor.js +1 -1
- package/dist/tools/list-dir/executor.js.map +1 -1
- package/dist/tools/read-file/executor.js +1 -1
- package/dist/tools/read-file/executor.js.map +1 -1
- package/dist/tools/run-shell/executor.js.map +1 -1
- package/dist/tools/run-tests/executor.js.map +1 -1
- package/dist/tools/run-typecheck/executor.js.map +1 -1
- package/dist/tools/search-code/executor.js +1 -1
- package/dist/tools/search-code/executor.js.map +1 -1
- package/dist/tools/shared.d.ts +9 -9
- package/dist/tools/web-fetch/errors.d.ts +2 -2
- package/dist/tools/web-fetch/errors.js +3 -3
- package/dist/tools/web-fetch/errors.js.map +1 -1
- package/dist/tools/web-fetch/executor.d.ts +2 -2
- package/dist/tools/web-fetch/executor.js +2 -2
- package/dist/tools/web-fetch/types.d.ts +7 -7
- package/dist/tools/web-fetch/types.d.ts.map +1 -1
- package/dist/tools/web-fetch/types.js +1 -1
- package/dist/tools/web-search/errors.d.ts +3 -3
- package/dist/tools/web-search/errors.js +3 -3
- package/dist/tools/web-search/executor.d.ts +2 -2
- package/dist/tools/web-search/executor.js +2 -2
- package/dist/tools/web-search/types.d.ts +4 -4
- package/dist/tools/web-search/types.d.ts.map +1 -1
- package/dist/tools/web-search/types.js +1 -1
- package/dist/transformers/document-to-text.d.ts +11 -11
- package/dist/transformers/document-to-text.d.ts.map +1 -1
- package/dist/transformers/document-to-text.js +11 -11
- package/dist/transformers/document-to-text.js.map +1 -1
- package/dist/transformers/image-to-text.d.ts +15 -15
- package/dist/transformers/image-to-text.d.ts.map +1 -1
- package/dist/transformers/image-to-text.js +15 -15
- package/dist/transformers/image-to-text.js.map +1 -1
- package/dist/vector/index.d.ts +4 -2
- package/dist/vector/index.d.ts.map +1 -1
- package/dist/vector/index.js +2 -2
- package/dist/vector/index.js.map +1 -1
- package/dist/vector/local-fs-index.d.ts +59 -0
- package/dist/vector/local-fs-index.d.ts.map +1 -0
- package/dist/vector/local-fs-index.js +216 -0
- package/dist/vector/local-fs-index.js.map +1 -0
- package/dist/vector/qdrant.d.ts +11 -73
- package/dist/vector/qdrant.d.ts.map +1 -1
- package/dist/vector/qdrant.js +29 -164
- package/dist/vector/qdrant.js.map +1 -1
- package/package.json +19 -4
- package/dist/vector/local-fs.d.ts +0 -81
- package/dist/vector/local-fs.d.ts.map +0 -1
- package/dist/vector/local-fs.js +0 -161
- package/dist/vector/local-fs.js.map +0 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, open, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { ProviderError, } from "@tachu/core";
|
|
5
|
+
function cosineSimilarity(a, b) {
|
|
6
|
+
const length = Math.min(a.length, b.length);
|
|
7
|
+
if (length === 0)
|
|
8
|
+
return 0;
|
|
9
|
+
let dot = 0;
|
|
10
|
+
let aNorm = 0;
|
|
11
|
+
let bNorm = 0;
|
|
12
|
+
for (let i = 0; i < length; i += 1) {
|
|
13
|
+
const ai = a[i] ?? 0;
|
|
14
|
+
const bi = b[i] ?? 0;
|
|
15
|
+
dot += ai * bi;
|
|
16
|
+
aNorm += ai * ai;
|
|
17
|
+
bNorm += bi * bi;
|
|
18
|
+
}
|
|
19
|
+
if (aNorm === 0 || bNorm === 0)
|
|
20
|
+
return 0;
|
|
21
|
+
return dot / (Math.sqrt(aNorm) * Math.sqrt(bNorm));
|
|
22
|
+
}
|
|
23
|
+
function payloadMatchesFilters(payload, filters) {
|
|
24
|
+
const must = filters.must ?? {};
|
|
25
|
+
for (const [key, value] of Object.entries(must)) {
|
|
26
|
+
if (payload[key] !== value)
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Pure-vector local filesystem index ( / / ).
|
|
33
|
+
*
|
|
34
|
+
* - Accepts only precomputed numeric `VectorPoint`s. Text-only `upsert(id, string, …)`
|
|
35
|
+
* shortcuts from pre- hosts are rejected — production projection paths
|
|
36
|
+
* must embed via {@link EmbeddingRuntime} before reaching this adapter.
|
|
37
|
+
* - Persisted as a single JSON snapshot at `.tachu/vector-index.json` with
|
|
38
|
+
* debounced atomic rewrites; safe for the CLI default deployment without
|
|
39
|
+
* pulling in an external vector database.
|
|
40
|
+
* - Implements {@link VectorIndexAdapter}, the only interface the
|
|
41
|
+
* {@link ProjectionWorker} and {@link DefaultSemanticRetrievalFacade}
|
|
42
|
+
* consume in production wiring.
|
|
43
|
+
*/
|
|
44
|
+
export class LocalFsVectorIndexAdapter {
|
|
45
|
+
filePath;
|
|
46
|
+
lockPath;
|
|
47
|
+
persistDebounceMs;
|
|
48
|
+
indexLimit;
|
|
49
|
+
entries = new Map();
|
|
50
|
+
initPromise;
|
|
51
|
+
persistTimer;
|
|
52
|
+
persisting = Promise.resolve();
|
|
53
|
+
constructor(options = {}) {
|
|
54
|
+
this.filePath = options.filePath ?? ".tachu/vector-index.json";
|
|
55
|
+
this.lockPath = `${this.filePath}.lock`;
|
|
56
|
+
this.persistDebounceMs = options.persistDebounceMs ?? 500;
|
|
57
|
+
this.indexLimit = options.indexLimit ?? 100_000;
|
|
58
|
+
this.initPromise = this.loadFromDisk();
|
|
59
|
+
}
|
|
60
|
+
async upsert(points, _ctx, signal) {
|
|
61
|
+
signal?.throwIfAborted();
|
|
62
|
+
await this.initPromise;
|
|
63
|
+
for (const point of points) {
|
|
64
|
+
if (typeof point.id !== "string" || point.id.length === 0) {
|
|
65
|
+
throw new ProviderError("PROVIDER_INVALID_REQUEST", "LocalFsVectorIndexAdapter.upsert requires a non-empty point id", { retryable: false });
|
|
66
|
+
}
|
|
67
|
+
if (!Array.isArray(point.vector) || point.vector.length === 0) {
|
|
68
|
+
throw new ProviderError("PROVIDER_INVALID_REQUEST", `LocalFsVectorIndexAdapter.upsert requires a numeric vector for ${point.id}; ` +
|
|
69
|
+
"legacy string-embed shortcuts are not supported", { retryable: false });
|
|
70
|
+
}
|
|
71
|
+
if (point.vector.some((value) => typeof value !== "number")) {
|
|
72
|
+
throw new ProviderError("PROVIDER_INVALID_REQUEST", `LocalFsVectorIndexAdapter.upsert vector for ${point.id} contains non-numeric values`, { retryable: false });
|
|
73
|
+
}
|
|
74
|
+
const cloned = {
|
|
75
|
+
id: point.id,
|
|
76
|
+
vector: [...point.vector],
|
|
77
|
+
payload: { ...(point.payload ?? {}) },
|
|
78
|
+
};
|
|
79
|
+
if (!this.entries.has(point.id) && this.entries.size >= this.indexLimit) {
|
|
80
|
+
// FIFO eviction: drop the oldest entry to keep the file bounded.
|
|
81
|
+
const oldest = this.entries.keys().next();
|
|
82
|
+
if (!oldest.done && oldest.value !== undefined) {
|
|
83
|
+
this.entries.delete(oldest.value);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
this.entries.set(point.id, cloned);
|
|
87
|
+
}
|
|
88
|
+
this.schedulePersist();
|
|
89
|
+
}
|
|
90
|
+
async searchVector(query, topK, filters, _ctx, signal) {
|
|
91
|
+
signal?.throwIfAborted();
|
|
92
|
+
await this.initPromise;
|
|
93
|
+
if (!Array.isArray(query) || query.some((value) => typeof value !== "number")) {
|
|
94
|
+
throw new ProviderError("PROVIDER_INVALID_REQUEST", "LocalFsVectorIndexAdapter.searchVector requires a numeric query vector", { retryable: false });
|
|
95
|
+
}
|
|
96
|
+
const k = Math.max(0, Math.floor(topK));
|
|
97
|
+
if (k === 0)
|
|
98
|
+
return [];
|
|
99
|
+
const ranked = [];
|
|
100
|
+
for (const entry of this.entries.values()) {
|
|
101
|
+
if (!payloadMatchesFilters(entry.payload, filters))
|
|
102
|
+
continue;
|
|
103
|
+
const score = cosineSimilarity(query, entry.vector);
|
|
104
|
+
ranked.push({ id: entry.id, score, metadata: { ...entry.payload } });
|
|
105
|
+
}
|
|
106
|
+
ranked.sort((a, b) => b.score - a.score);
|
|
107
|
+
return ranked.slice(0, k);
|
|
108
|
+
}
|
|
109
|
+
async delete(ids, _ctx, signal) {
|
|
110
|
+
signal?.throwIfAborted();
|
|
111
|
+
await this.initPromise;
|
|
112
|
+
if (ids.length === 0)
|
|
113
|
+
return;
|
|
114
|
+
let changed = false;
|
|
115
|
+
for (const id of ids) {
|
|
116
|
+
if (this.entries.delete(id))
|
|
117
|
+
changed = true;
|
|
118
|
+
}
|
|
119
|
+
if (changed)
|
|
120
|
+
this.schedulePersist();
|
|
121
|
+
}
|
|
122
|
+
/** Test-only inspection helper. Not part of the {@link VectorIndexAdapter} contract. */
|
|
123
|
+
snapshot() {
|
|
124
|
+
return [...this.entries.values()].map((entry) => ({
|
|
125
|
+
id: entry.id,
|
|
126
|
+
vector: [...entry.vector],
|
|
127
|
+
payload: { ...entry.payload },
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Flush any pending writes synchronously. Hosts wiring this adapter into a
|
|
132
|
+
* graceful shutdown should `await flush()` before the process exits to avoid
|
|
133
|
+
* losing buffered upserts.
|
|
134
|
+
*/
|
|
135
|
+
async flush() {
|
|
136
|
+
if (this.persistTimer !== undefined) {
|
|
137
|
+
clearTimeout(this.persistTimer);
|
|
138
|
+
this.persistTimer = undefined;
|
|
139
|
+
}
|
|
140
|
+
this.persisting = this.persisting.then(() => this.persistToDisk());
|
|
141
|
+
await this.persisting;
|
|
142
|
+
}
|
|
143
|
+
async loadFromDisk() {
|
|
144
|
+
const raw = await readFile(this.filePath, "utf8").catch(() => "");
|
|
145
|
+
if (!raw)
|
|
146
|
+
return;
|
|
147
|
+
let payload;
|
|
148
|
+
try {
|
|
149
|
+
payload = JSON.parse(raw);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
for (const entry of payload.entries ?? []) {
|
|
155
|
+
if (typeof entry?.id !== "string" ||
|
|
156
|
+
!Array.isArray(entry.vector) ||
|
|
157
|
+
entry.vector.some((value) => typeof value !== "number")) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
this.entries.set(entry.id, {
|
|
161
|
+
id: entry.id,
|
|
162
|
+
vector: [...entry.vector],
|
|
163
|
+
payload: { ...(entry.payload ?? {}) },
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
schedulePersist() {
|
|
168
|
+
if (this.persistTimer)
|
|
169
|
+
clearTimeout(this.persistTimer);
|
|
170
|
+
this.persistTimer = setTimeout(() => {
|
|
171
|
+
this.persisting = this.persisting.then(() => this.persistToDisk());
|
|
172
|
+
}, this.persistDebounceMs);
|
|
173
|
+
}
|
|
174
|
+
async acquireLock() {
|
|
175
|
+
for (let attempt = 0; attempt < 30; attempt += 1) {
|
|
176
|
+
try {
|
|
177
|
+
await mkdir(dirname(this.lockPath), { recursive: true });
|
|
178
|
+
const handle = await open(this.lockPath, "wx");
|
|
179
|
+
return async () => {
|
|
180
|
+
await handle.close().catch(() => undefined);
|
|
181
|
+
await rm(this.lockPath, { force: true }).catch(() => undefined);
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1)));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
throw new Error(`无法获取文件锁: ${this.lockPath}`);
|
|
189
|
+
}
|
|
190
|
+
async persistToDisk() {
|
|
191
|
+
const release = await this.acquireLock();
|
|
192
|
+
try {
|
|
193
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
194
|
+
const tempPath = `${this.filePath}.tmp`;
|
|
195
|
+
const payload = {
|
|
196
|
+
version: 1,
|
|
197
|
+
entries: [...this.entries.values()].map((entry) => ({
|
|
198
|
+
id: entry.id,
|
|
199
|
+
vector: [...entry.vector],
|
|
200
|
+
payload: { ...entry.payload },
|
|
201
|
+
})),
|
|
202
|
+
};
|
|
203
|
+
await writeFile(tempPath, JSON.stringify(payload), "utf8");
|
|
204
|
+
await rename(tempPath, this.filePath);
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
await release();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Keep `existsSync` reachable so tree-shakers don't drop the dependency
|
|
211
|
+
// when callers only invoke async paths.
|
|
212
|
+
static fileExists(path) {
|
|
213
|
+
return existsSync(path);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=local-fs-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-fs-index.js","sourceRoot":"","sources":["../../src/vector/local-fs-index.ts"],"names":[],"mappings":"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"}
|
package/dist/vector/qdrant.d.ts
CHANGED
|
@@ -1,86 +1,24 @@
|
|
|
1
|
-
import { type AdapterCallContext, type VectorHit, type
|
|
2
|
-
interface
|
|
1
|
+
import { type AdapterCallContext, type VectorHit, type VectorIndexAdapter, type VectorPayloadFilter, type VectorPoint } from "@tachu/core";
|
|
2
|
+
export interface QdrantVectorIndexAdapterOptions {
|
|
3
3
|
url: string;
|
|
4
4
|
apiKey?: string;
|
|
5
5
|
collectionName: string;
|
|
6
|
-
vectorSize
|
|
6
|
+
vectorSize: number;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
* Qdrant
|
|
9
|
+
* Qdrant pure vector index adapter (). No text embedding — vectors must be precomputed.
|
|
10
10
|
*/
|
|
11
|
-
export declare class
|
|
11
|
+
export declare class QdrantVectorIndexAdapter implements VectorIndexAdapter {
|
|
12
12
|
private readonly client;
|
|
13
13
|
private readonly collectionName;
|
|
14
14
|
private readonly vectorSize;
|
|
15
15
|
private ensured;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* @param options Qdrant 连接配置
|
|
21
|
-
*/
|
|
22
|
-
constructor(options: QdrantVectorStoreOptions);
|
|
23
|
-
/**
|
|
24
|
-
* 文本向量化(**开发/占位实现**,基于哈希词袋)。
|
|
25
|
-
*
|
|
26
|
-
* ⚠️ **生产部署请务必注入真实 embedder**:
|
|
27
|
-
*
|
|
28
|
-
* - 本方法采用简易哈希词袋(Hashing Trick)把文本投影到 `vectorSize` 维空间,
|
|
29
|
-
* 仅用于早期集成与开发环境跑通流程,**无法**刻画词序、语义相似度、跨语言对齐;
|
|
30
|
-
* - 在真实的召回与排序场景(跨 session 记忆、多文档检索、向量知识库)下会产生
|
|
31
|
-
* 大量误召和漏召。
|
|
32
|
-
*
|
|
33
|
-
* **推荐做法**(v1.0 起):
|
|
34
|
-
*
|
|
35
|
-
* 1. 由 Provider 层(OpenAI `text-embedding-3-*` / Anthropic 尚未官方支持,可
|
|
36
|
-
* 桥接第三方)生成向量;
|
|
37
|
-
* 2. 宿主侧在 `MemorySystem` 调用前统一用真实 embedder 计算向量,再调
|
|
38
|
-
* `upsert(id, vector, metadata)` 这条显式向量分支;
|
|
39
|
-
* 3. 或者继承 `QdrantVectorStore` 并覆盖 `embed`,直接接入业务自选 embedding 服务。
|
|
40
|
-
*
|
|
41
|
-
* 参见 `D1-LOW-06`:该占位实现的 QPS 基准与精度限制已记录在 benchmarks 目录。
|
|
42
|
-
*
|
|
43
|
-
* @param texts 文本数组
|
|
44
|
-
* @returns 向量数组(单位向量,维度为 `vectorSize`)
|
|
45
|
-
*/
|
|
46
|
-
embed(texts: string[]): Promise<number[][]>;
|
|
47
|
-
/**
|
|
48
|
-
* 写入或更新向量。
|
|
49
|
-
*
|
|
50
|
-
* @param id 条目 ID
|
|
51
|
-
* @param vectorOrText 向量或文本
|
|
52
|
-
* @param metadata 元数据
|
|
53
|
-
*/
|
|
54
|
-
upsert(id: string, vectorOrText: number[] | string, metadata: Record<string, unknown>): Promise<void>;
|
|
55
|
-
/**
|
|
56
|
-
* 相似度搜索。
|
|
57
|
-
*
|
|
58
|
-
* @param query 查询向量或文本
|
|
59
|
-
* @param topK 结果数量
|
|
60
|
-
* @returns 搜索结果
|
|
61
|
-
*/
|
|
62
|
-
search(query: VectorSearchQuery, _ctx: AdapterCallContext, signal?: AbortSignal): Promise<VectorHit[]>;
|
|
63
|
-
hybridSearch(denseVector: number[], sparseVector: SparseVector | null, k: number, filters: VectorPayloadFilter, _ctx: AdapterCallContext, signal?: AbortSignal): Promise<VectorHit[]>;
|
|
64
|
-
/**
|
|
65
|
-
* 删除条目。
|
|
66
|
-
*
|
|
67
|
-
* @param id 条目 ID
|
|
68
|
-
*/
|
|
69
|
-
delete(id: string): Promise<void>;
|
|
70
|
-
/**
|
|
71
|
-
* 清空集合。
|
|
72
|
-
*/
|
|
73
|
-
clear(): Promise<void>;
|
|
74
|
-
/**
|
|
75
|
-
* 返回集合内条目数量。
|
|
76
|
-
*
|
|
77
|
-
* 远程实现对 `client.count(...)` 发起一次真值查询(`exact: true`),避免本地缓存
|
|
78
|
-
* 漂移导致读到旧值(D1-LOW-07)。collection 不存在等异常会被包装成 `ProviderError`。
|
|
79
|
-
*
|
|
80
|
-
* @returns 条目数(远程真值)
|
|
81
|
-
*/
|
|
82
|
-
size(): Promise<number>;
|
|
16
|
+
constructor(options: QdrantVectorIndexAdapterOptions);
|
|
17
|
+
upsert(points: readonly VectorPoint[], _ctx: AdapterCallContext, signal?: AbortSignal): Promise<void>;
|
|
18
|
+
searchVector(query: number[], topK: number, filters: VectorPayloadFilter, _ctx: AdapterCallContext, signal?: AbortSignal): Promise<VectorHit[]>;
|
|
19
|
+
delete(ids: readonly string[], _ctx: AdapterCallContext, signal?: AbortSignal): Promise<void>;
|
|
83
20
|
private ensureCollection;
|
|
84
21
|
}
|
|
85
|
-
|
|
22
|
+
/** @deprecated Use {@link QdrantVectorIndexAdapter} (). */
|
|
23
|
+
export { QdrantVectorIndexAdapter as QdrantVectorStore };
|
|
86
24
|
//# sourceMappingURL=qdrant.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qdrant.d.ts","sourceRoot":"","sources":["../../src/vector/qdrant.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,KAAK,
|
|
1
|
+
{"version":3,"file":"qdrant.d.ts","sourceRoot":"","sources":["../../src/vector/qdrant.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EACjB,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,+BAA+B;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,wBAAyB,YAAW,kBAAkB;IACjE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,+BAA+B;IAS9C,MAAM,CACV,MAAM,EAAE,SAAS,WAAW,EAAE,EAC9B,IAAI,EAAE,kBAAkB,EACxB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC;IA6BV,YAAY,CAChB,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,mBAAmB,EAC5B,IAAI,EAAE,kBAAkB,EACxB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,SAAS,EAAE,CAAC;IAuCjB,MAAM,CACV,GAAG,EAAE,SAAS,MAAM,EAAE,EACtB,IAAI,EAAE,kBAAkB,EACxB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC;YAcF,gBAAgB;CAa/B;AAED,2DAA2D;AAC3D,OAAO,EAAE,wBAAwB,IAAI,iBAAiB,EAAE,CAAC"}
|
package/dist/vector/qdrant.js
CHANGED
|
@@ -1,96 +1,38 @@
|
|
|
1
1
|
import { ProviderError, } from "@tachu/core";
|
|
2
2
|
import { QdrantClient } from "@qdrant/js-client-rest";
|
|
3
|
-
const DEFAULT_VECTOR_SIZE = 256;
|
|
4
|
-
const hashToken = (token, size) => {
|
|
5
|
-
let hash = 0;
|
|
6
|
-
for (let index = 0; index < token.length; index += 1) {
|
|
7
|
-
hash = (hash * 31 + token.charCodeAt(index)) >>> 0;
|
|
8
|
-
}
|
|
9
|
-
return hash % size;
|
|
10
|
-
};
|
|
11
|
-
const embedText = (text, size) => {
|
|
12
|
-
const vector = new Array(size).fill(0);
|
|
13
|
-
const tokens = text
|
|
14
|
-
.toLowerCase()
|
|
15
|
-
.split(/[^a-z0-9\u4e00-\u9fa5]+/u)
|
|
16
|
-
.filter((token) => token.length > 0);
|
|
17
|
-
if (tokens.length === 0) {
|
|
18
|
-
return vector;
|
|
19
|
-
}
|
|
20
|
-
for (const token of tokens) {
|
|
21
|
-
const idx = hashToken(token, size);
|
|
22
|
-
vector[idx] += 1;
|
|
23
|
-
}
|
|
24
|
-
const norm = Math.sqrt(vector.reduce((sum, value) => sum + value * value, 0));
|
|
25
|
-
if (norm === 0) {
|
|
26
|
-
return vector;
|
|
27
|
-
}
|
|
28
|
-
return vector.map((value) => value / norm);
|
|
29
|
-
};
|
|
30
3
|
/**
|
|
31
|
-
* Qdrant
|
|
4
|
+
* Qdrant pure vector index adapter (). No text embedding — vectors must be precomputed.
|
|
32
5
|
*/
|
|
33
|
-
export class
|
|
6
|
+
export class QdrantVectorIndexAdapter {
|
|
34
7
|
client;
|
|
35
8
|
collectionName;
|
|
36
9
|
vectorSize;
|
|
37
10
|
ensured = false;
|
|
38
|
-
entryCount = 0;
|
|
39
|
-
/**
|
|
40
|
-
* 创建 Qdrant 向量存储。
|
|
41
|
-
*
|
|
42
|
-
* @param options Qdrant 连接配置
|
|
43
|
-
*/
|
|
44
11
|
constructor(options) {
|
|
45
12
|
this.client = new QdrantClient({
|
|
46
13
|
url: options.url,
|
|
47
14
|
...(options.apiKey ? { apiKey: options.apiKey } : {}),
|
|
48
15
|
});
|
|
49
16
|
this.collectionName = options.collectionName;
|
|
50
|
-
this.vectorSize = options.vectorSize
|
|
17
|
+
this.vectorSize = options.vectorSize;
|
|
51
18
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
* - 在真实的召回与排序场景(跨 session 记忆、多文档检索、向量知识库)下会产生
|
|
60
|
-
* 大量误召和漏召。
|
|
61
|
-
*
|
|
62
|
-
* **推荐做法**(v1.0 起):
|
|
63
|
-
*
|
|
64
|
-
* 1. 由 Provider 层(OpenAI `text-embedding-3-*` / Anthropic 尚未官方支持,可
|
|
65
|
-
* 桥接第三方)生成向量;
|
|
66
|
-
* 2. 宿主侧在 `MemorySystem` 调用前统一用真实 embedder 计算向量,再调
|
|
67
|
-
* `upsert(id, vector, metadata)` 这条显式向量分支;
|
|
68
|
-
* 3. 或者继承 `QdrantVectorStore` 并覆盖 `embed`,直接接入业务自选 embedding 服务。
|
|
69
|
-
*
|
|
70
|
-
* 参见 `D1-LOW-06`:该占位实现的 QPS 基准与精度限制已记录在 benchmarks 目录。
|
|
71
|
-
*
|
|
72
|
-
* @param texts 文本数组
|
|
73
|
-
* @returns 向量数组(单位向量,维度为 `vectorSize`)
|
|
74
|
-
*/
|
|
75
|
-
async embed(texts) {
|
|
76
|
-
return texts.map((text) => embedText(text, this.vectorSize));
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* 写入或更新向量。
|
|
80
|
-
*
|
|
81
|
-
* @param id 条目 ID
|
|
82
|
-
* @param vectorOrText 向量或文本
|
|
83
|
-
* @param metadata 元数据
|
|
84
|
-
*/
|
|
85
|
-
async upsert(id, vectorOrText, metadata) {
|
|
19
|
+
async upsert(points, _ctx, signal) {
|
|
20
|
+
signal?.throwIfAborted();
|
|
21
|
+
for (const point of points) {
|
|
22
|
+
if (!Array.isArray(point.vector) || point.vector.length !== this.vectorSize) {
|
|
23
|
+
throw new ProviderError("PROVIDER_INVALID_REQUEST", `QdrantVectorIndexAdapter expects vectors of size ${this.vectorSize}`, { retryable: false });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
86
26
|
try {
|
|
87
27
|
await this.ensureCollection();
|
|
88
|
-
const vector = typeof vectorOrText === "string" ? embedText(vectorOrText, this.vectorSize) : vectorOrText;
|
|
89
28
|
await this.client.upsert(this.collectionName, {
|
|
90
29
|
wait: true,
|
|
91
|
-
points:
|
|
30
|
+
points: points.map((point) => ({
|
|
31
|
+
id: point.id,
|
|
32
|
+
vector: point.vector,
|
|
33
|
+
payload: point.payload,
|
|
34
|
+
})),
|
|
92
35
|
});
|
|
93
|
-
this.entryCount += 1;
|
|
94
36
|
}
|
|
95
37
|
catch (error) {
|
|
96
38
|
throw new ProviderError("PROVIDER_UPSTREAM_ERROR", "Qdrant upsert 失败", {
|
|
@@ -99,42 +41,10 @@ export class QdrantVectorStore {
|
|
|
99
41
|
});
|
|
100
42
|
}
|
|
101
43
|
}
|
|
102
|
-
|
|
103
|
-
* 相似度搜索。
|
|
104
|
-
*
|
|
105
|
-
* @param query 查询向量或文本
|
|
106
|
-
* @param topK 结果数量
|
|
107
|
-
* @returns 搜索结果
|
|
108
|
-
*/
|
|
109
|
-
async search(query, _ctx, signal) {
|
|
110
|
-
signal?.throwIfAborted();
|
|
111
|
-
try {
|
|
112
|
-
await this.ensureCollection();
|
|
113
|
-
const vector = typeof query.query === "string"
|
|
114
|
-
? embedText(query.query, this.vectorSize)
|
|
115
|
-
: query.query;
|
|
116
|
-
const result = await this.client.search(this.collectionName, {
|
|
117
|
-
vector,
|
|
118
|
-
limit: query.topK,
|
|
119
|
-
with_payload: true,
|
|
120
|
-
});
|
|
121
|
-
return result.map((item) => ({
|
|
122
|
-
id: String(item.id),
|
|
123
|
-
score: item.score,
|
|
124
|
-
metadata: (item.payload ?? {}),
|
|
125
|
-
}));
|
|
126
|
-
}
|
|
127
|
-
catch (error) {
|
|
128
|
-
throw new ProviderError("PROVIDER_UPSTREAM_ERROR", "Qdrant search 失败", {
|
|
129
|
-
cause: error,
|
|
130
|
-
retryable: true,
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
async hybridSearch(denseVector, sparseVector, k, filters, _ctx, signal) {
|
|
44
|
+
async searchVector(query, topK, filters, _ctx, signal) {
|
|
135
45
|
signal?.throwIfAborted();
|
|
136
|
-
if (
|
|
137
|
-
throw new ProviderError("
|
|
46
|
+
if (!Array.isArray(query) || query.some((value) => typeof value !== "number")) {
|
|
47
|
+
throw new ProviderError("PROVIDER_INVALID_REQUEST", "QdrantVectorIndexAdapter.searchVector requires a numeric query vector", { retryable: false });
|
|
138
48
|
}
|
|
139
49
|
try {
|
|
140
50
|
await this.ensureCollection();
|
|
@@ -147,8 +57,8 @@ export class QdrantVectorStore {
|
|
|
147
57
|
}
|
|
148
58
|
: undefined;
|
|
149
59
|
const result = await this.client.search(this.collectionName, {
|
|
150
|
-
vector:
|
|
151
|
-
limit:
|
|
60
|
+
vector: query,
|
|
61
|
+
limit: topK,
|
|
152
62
|
with_payload: true,
|
|
153
63
|
...(filter !== undefined ? { filter } : {}),
|
|
154
64
|
});
|
|
@@ -159,22 +69,19 @@ export class QdrantVectorStore {
|
|
|
159
69
|
}));
|
|
160
70
|
}
|
|
161
71
|
catch (error) {
|
|
162
|
-
throw new ProviderError("PROVIDER_UPSTREAM_ERROR", "Qdrant
|
|
72
|
+
throw new ProviderError("PROVIDER_UPSTREAM_ERROR", "Qdrant search 失败", {
|
|
163
73
|
cause: error,
|
|
164
74
|
retryable: true,
|
|
165
75
|
});
|
|
166
76
|
}
|
|
167
77
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
*/
|
|
173
|
-
async delete(id) {
|
|
78
|
+
async delete(ids, _ctx, signal) {
|
|
79
|
+
signal?.throwIfAborted();
|
|
80
|
+
if (ids.length === 0)
|
|
81
|
+
return;
|
|
174
82
|
try {
|
|
175
83
|
await this.ensureCollection();
|
|
176
|
-
await this.client.delete(this.collectionName, { wait: true, points: [
|
|
177
|
-
this.entryCount = Math.max(0, this.entryCount - 1);
|
|
84
|
+
await this.client.delete(this.collectionName, { wait: true, points: [...ids] });
|
|
178
85
|
}
|
|
179
86
|
catch (error) {
|
|
180
87
|
throw new ProviderError("PROVIDER_UPSTREAM_ERROR", "Qdrant delete 失败", {
|
|
@@ -183,53 +90,9 @@ export class QdrantVectorStore {
|
|
|
183
90
|
});
|
|
184
91
|
}
|
|
185
92
|
}
|
|
186
|
-
/**
|
|
187
|
-
* 清空集合。
|
|
188
|
-
*/
|
|
189
|
-
async clear() {
|
|
190
|
-
try {
|
|
191
|
-
const exists = await this.client.collectionExists(this.collectionName);
|
|
192
|
-
if (exists.exists) {
|
|
193
|
-
await this.client.deleteCollection(this.collectionName);
|
|
194
|
-
}
|
|
195
|
-
this.ensured = false;
|
|
196
|
-
this.entryCount = 0;
|
|
197
|
-
await this.ensureCollection();
|
|
198
|
-
}
|
|
199
|
-
catch (error) {
|
|
200
|
-
throw new ProviderError("PROVIDER_UPSTREAM_ERROR", "Qdrant clear 失败", {
|
|
201
|
-
cause: error,
|
|
202
|
-
retryable: true,
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* 返回集合内条目数量。
|
|
208
|
-
*
|
|
209
|
-
* 远程实现对 `client.count(...)` 发起一次真值查询(`exact: true`),避免本地缓存
|
|
210
|
-
* 漂移导致读到旧值(D1-LOW-07)。collection 不存在等异常会被包装成 `ProviderError`。
|
|
211
|
-
*
|
|
212
|
-
* @returns 条目数(远程真值)
|
|
213
|
-
*/
|
|
214
|
-
async size() {
|
|
215
|
-
try {
|
|
216
|
-
await this.ensureCollection();
|
|
217
|
-
const response = await this.client.count(this.collectionName, { exact: true });
|
|
218
|
-
const remoteCount = typeof response?.count === "number" ? response.count : this.entryCount;
|
|
219
|
-
this.entryCount = remoteCount;
|
|
220
|
-
return remoteCount;
|
|
221
|
-
}
|
|
222
|
-
catch (error) {
|
|
223
|
-
throw new ProviderError("PROVIDER_UPSTREAM_ERROR", "Qdrant count 失败", {
|
|
224
|
-
cause: error,
|
|
225
|
-
retryable: true,
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
93
|
async ensureCollection() {
|
|
230
|
-
if (this.ensured)
|
|
94
|
+
if (this.ensured)
|
|
231
95
|
return;
|
|
232
|
-
}
|
|
233
96
|
const exists = await this.client.collectionExists(this.collectionName);
|
|
234
97
|
if (!exists.exists) {
|
|
235
98
|
await this.client.createCollection(this.collectionName, {
|
|
@@ -242,4 +105,6 @@ export class QdrantVectorStore {
|
|
|
242
105
|
this.ensured = true;
|
|
243
106
|
}
|
|
244
107
|
}
|
|
108
|
+
/** @deprecated Use {@link QdrantVectorIndexAdapter} (). */
|
|
109
|
+
export { QdrantVectorIndexAdapter as QdrantVectorStore };
|
|
245
110
|
//# sourceMappingURL=qdrant.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qdrant.js","sourceRoot":"","sources":["../../src/vector/qdrant.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,
|
|
1
|
+
{"version":3,"file":"qdrant.js","sourceRoot":"","sources":["../../src/vector/qdrant.ts"],"names":[],"mappings":"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"}
|