@kb-labs/adapters 0.5.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/.cursorrules +32 -0
- package/.github/workflows/ci.yml +13 -0
- package/.github/workflows/deploy.yml +28 -0
- package/.github/workflows/docker-build.yml +25 -0
- package/.github/workflows/drift-check.yml +10 -0
- package/.github/workflows/profiles-validate.yml +16 -0
- package/.github/workflows/release.yml +8 -0
- package/.kb/devkit/agents/devkit-maintainer/context.globs +15 -0
- package/.kb/devkit/agents/devkit-maintainer/permissions.yml +17 -0
- package/.kb/devkit/agents/devkit-maintainer/prompt.md +28 -0
- package/.kb/devkit/agents/devkit-maintainer/runbook.md +31 -0
- package/.kb/devkit/agents/docs-crafter/prompt.md +24 -0
- package/.kb/devkit/agents/docs-crafter/runbook.md +18 -0
- package/.kb/devkit/agents/release-manager/context.globs +7 -0
- package/.kb/devkit/agents/release-manager/prompt.md +27 -0
- package/.kb/devkit/agents/release-manager/runbook.md +17 -0
- package/.kb/devkit/agents/test-generator/context.globs +7 -0
- package/.kb/devkit/agents/test-generator/prompt.md +27 -0
- package/.kb/devkit/agents/test-generator/runbook.md +18 -0
- package/CONTRIBUTING.md +90 -0
- package/IMPLEMENTATION_COMPLETE.md +416 -0
- package/LICENSE +186 -0
- package/README-TEMPLATE.md +179 -0
- package/README.md +306 -0
- package/docs/DOCUMENTATION.md +74 -0
- package/docs/adr/0000-template.md +49 -0
- package/docs/adr/0001-architecture-and-repository-layout.md +33 -0
- package/docs/adr/0002-plugins-and-extensibility.md +46 -0
- package/docs/adr/0003-package-and-module-boundaries.md +37 -0
- package/docs/adr/0004-versioning-and-release-policy.md +38 -0
- package/docs/adr/0005-use-devkit-for-shared-tooling.md +48 -0
- package/docs/adr/0006-adopt-devkit-sync.md +47 -0
- package/docs/adr/0007-drift-kit-check.md +72 -0
- package/docs/adr/0008-devkit-sync-wrapper-strategy.md +67 -0
- package/docs/naming-convention.md +272 -0
- package/eslint.config.js +27 -0
- package/kb-labs.config.json +5 -0
- package/package.json +84 -0
- package/package.json.bin +25 -0
- package/package.json.lib +30 -0
- package/packages/adapters-analytics-duckdb/package.json +54 -0
- package/packages/adapters-analytics-duckdb/scripts/migrate-from-jsonl.mjs +253 -0
- package/packages/adapters-analytics-duckdb/src/index.ts +380 -0
- package/packages/adapters-analytics-duckdb/src/manifest.ts +36 -0
- package/packages/adapters-analytics-duckdb/src/schema.ts +161 -0
- package/packages/adapters-analytics-duckdb/tsconfig.build.json +15 -0
- package/packages/adapters-analytics-duckdb/tsconfig.json +9 -0
- package/packages/adapters-analytics-duckdb/tsup.config.ts +9 -0
- package/packages/adapters-analytics-file/README.md +32 -0
- package/packages/adapters-analytics-file/eslint.config.js +27 -0
- package/packages/adapters-analytics-file/package.json +50 -0
- package/packages/adapters-analytics-file/src/__tests__/daily-stats.spec.ts +287 -0
- package/packages/adapters-analytics-file/src/__tests__/scoped-analytics.test.ts +233 -0
- package/packages/adapters-analytics-file/src/index.test.ts +214 -0
- package/packages/adapters-analytics-file/src/index.ts +830 -0
- package/packages/adapters-analytics-file/src/manifest.ts +45 -0
- package/packages/adapters-analytics-file/tsconfig.build.json +15 -0
- package/packages/adapters-analytics-file/tsconfig.json +9 -0
- package/packages/adapters-analytics-file/tsup.config.ts +9 -0
- package/packages/adapters-analytics-sqlite/package.json +55 -0
- package/packages/adapters-analytics-sqlite/scripts/migrate-from-jsonl.mjs +194 -0
- package/packages/adapters-analytics-sqlite/src/index.ts +460 -0
- package/packages/adapters-analytics-sqlite/src/manifest.ts +41 -0
- package/packages/adapters-analytics-sqlite/tsconfig.build.json +15 -0
- package/packages/adapters-analytics-sqlite/tsconfig.json +9 -0
- package/packages/adapters-analytics-sqlite/tsup.config.ts +9 -0
- package/packages/adapters-environment-docker/README.md +28 -0
- package/packages/adapters-environment-docker/eslint.config.js +5 -0
- package/packages/adapters-environment-docker/package.json +49 -0
- package/packages/adapters-environment-docker/src/index.test.ts +138 -0
- package/packages/adapters-environment-docker/src/index.ts +439 -0
- package/packages/adapters-environment-docker/src/manifest.ts +65 -0
- package/packages/adapters-environment-docker/tsconfig.build.json +15 -0
- package/packages/adapters-environment-docker/tsconfig.json +16 -0
- package/packages/adapters-environment-docker/tsup.config.ts +9 -0
- package/packages/adapters-eventbus-cache/README.md +242 -0
- package/packages/adapters-eventbus-cache/eslint.config.js +27 -0
- package/packages/adapters-eventbus-cache/package.json +46 -0
- package/packages/adapters-eventbus-cache/src/index.test.ts +235 -0
- package/packages/adapters-eventbus-cache/src/index.ts +215 -0
- package/packages/adapters-eventbus-cache/src/manifest.ts +50 -0
- package/packages/adapters-eventbus-cache/src/types.ts +58 -0
- package/packages/adapters-eventbus-cache/tsconfig.build.json +15 -0
- package/packages/adapters-eventbus-cache/tsconfig.json +9 -0
- package/packages/adapters-eventbus-cache/tsup.config.ts +9 -0
- package/packages/adapters-fs/README.md +171 -0
- package/packages/adapters-fs/allowed.txt +1 -0
- package/packages/adapters-fs/conflict.txt +1 -0
- package/packages/adapters-fs/dest.txt +1 -0
- package/packages/adapters-fs/eslint.config.js +27 -0
- package/packages/adapters-fs/exists.txt +1 -0
- package/packages/adapters-fs/not-allowed.txt +1 -0
- package/packages/adapters-fs/other.txt +1 -0
- package/packages/adapters-fs/package.json +55 -0
- package/packages/adapters-fs/public/file1.txt +1 -0
- package/packages/adapters-fs/public/file2.txt +1 -0
- package/packages/adapters-fs/secret.txt +1 -0
- package/packages/adapters-fs/secrets/key.txt +1 -0
- package/packages/adapters-fs/src/index.test.ts +243 -0
- package/packages/adapters-fs/src/index.ts +258 -0
- package/packages/adapters-fs/src/manifest.ts +35 -0
- package/packages/adapters-fs/src/secure-storage.test.ts +380 -0
- package/packages/adapters-fs/src/secure-storage.ts +268 -0
- package/packages/adapters-fs/test.json +1 -0
- package/packages/adapters-fs/test.txt +1 -0
- package/packages/adapters-fs/test.xyz +1 -0
- package/packages/adapters-fs/test1.txt +1 -0
- package/packages/adapters-fs/test2.txt +1 -0
- package/packages/adapters-fs/tsconfig.build.json +15 -0
- package/packages/adapters-fs/tsconfig.json +9 -0
- package/packages/adapters-fs/tsup.config.ts +8 -0
- package/packages/adapters-fs/vitest.config.ts +19 -0
- package/packages/adapters-log-ringbuffer/README.md +228 -0
- package/packages/adapters-log-ringbuffer/eslint.config.js +27 -0
- package/packages/adapters-log-ringbuffer/package.json +47 -0
- package/packages/adapters-log-ringbuffer/src/__tests__/ring-buffer.test.ts +450 -0
- package/packages/adapters-log-ringbuffer/src/index.ts +212 -0
- package/packages/adapters-log-ringbuffer/src/manifest.ts +30 -0
- package/packages/adapters-log-ringbuffer/tsconfig.build.json +15 -0
- package/packages/adapters-log-ringbuffer/tsconfig.json +9 -0
- package/packages/adapters-log-ringbuffer/tsup.config.ts +9 -0
- package/packages/adapters-log-ringbuffer/vitest.config.ts +14 -0
- package/packages/adapters-log-sqlite/README.md +396 -0
- package/packages/adapters-log-sqlite/eslint.config.js +27 -0
- package/packages/adapters-log-sqlite/package.json +49 -0
- package/packages/adapters-log-sqlite/src/__tests__/log-persistence.test.ts +718 -0
- package/packages/adapters-log-sqlite/src/index.ts +1068 -0
- package/packages/adapters-log-sqlite/src/manifest.ts +36 -0
- package/packages/adapters-log-sqlite/src/schema.sql +46 -0
- package/packages/adapters-log-sqlite/tsconfig.build.json +15 -0
- package/packages/adapters-log-sqlite/tsconfig.json +9 -0
- package/packages/adapters-log-sqlite/tsup.config.ts +9 -0
- package/packages/adapters-log-sqlite/vitest.config.ts +15 -0
- package/packages/adapters-mongodb/README.md +147 -0
- package/packages/adapters-mongodb/eslint.config.js +27 -0
- package/packages/adapters-mongodb/package.json +53 -0
- package/packages/adapters-mongodb/src/index.ts +428 -0
- package/packages/adapters-mongodb/src/manifest.ts +45 -0
- package/packages/adapters-mongodb/src/secure-document.ts +231 -0
- package/packages/adapters-mongodb/tsconfig.build.json +15 -0
- package/packages/adapters-mongodb/tsconfig.json +9 -0
- package/packages/adapters-mongodb/tsup.config.ts +8 -0
- package/packages/adapters-openai/README.md +151 -0
- package/packages/adapters-openai/embeddings.ts +37 -0
- package/packages/adapters-openai/eslint.config.js +26 -0
- package/packages/adapters-openai/index.ts +22 -0
- package/packages/adapters-openai/package.json +57 -0
- package/packages/adapters-openai/src/embeddings-manifest.ts +45 -0
- package/packages/adapters-openai/src/embeddings.ts +104 -0
- package/packages/adapters-openai/src/index.ts +13 -0
- package/packages/adapters-openai/src/llm.ts +304 -0
- package/packages/adapters-openai/src/manifest.ts +47 -0
- package/packages/adapters-openai/tsconfig.build.json +15 -0
- package/packages/adapters-openai/tsconfig.json +9 -0
- package/packages/adapters-openai/tsup.config.ts +8 -0
- package/packages/adapters-pino/README.md +152 -0
- package/packages/adapters-pino/eslint.config.js +27 -0
- package/packages/adapters-pino/package.json +49 -0
- package/packages/adapters-pino/src/index.test.ts +44 -0
- package/packages/adapters-pino/src/index.ts +322 -0
- package/packages/adapters-pino/src/log-ring-buffer.ts +142 -0
- package/packages/adapters-pino/src/manifest.ts +49 -0
- package/packages/adapters-pino/tsconfig.build.json +15 -0
- package/packages/adapters-pino/tsconfig.json +9 -0
- package/packages/adapters-pino/tsup.config.ts +9 -0
- package/packages/adapters-pino-http/README.md +141 -0
- package/packages/adapters-pino-http/eslint.config.js +27 -0
- package/packages/adapters-pino-http/package.json +46 -0
- package/packages/adapters-pino-http/src/index.ts +229 -0
- package/packages/adapters-pino-http/tsconfig.build.json +15 -0
- package/packages/adapters-pino-http/tsconfig.json +9 -0
- package/packages/adapters-pino-http/tsup.config.ts +9 -0
- package/packages/adapters-qdrant/README.md +166 -0
- package/packages/adapters-qdrant/eslint.config.js +27 -0
- package/packages/adapters-qdrant/package.json +49 -0
- package/packages/adapters-qdrant/src/index.ts +490 -0
- package/packages/adapters-qdrant/src/manifest.ts +54 -0
- package/packages/adapters-qdrant/src/retry.ts +204 -0
- package/packages/adapters-qdrant/tsconfig.build.json +15 -0
- package/packages/adapters-qdrant/tsconfig.json +9 -0
- package/packages/adapters-qdrant/tsup.config.ts +9 -0
- package/packages/adapters-redis/README.md +159 -0
- package/packages/adapters-redis/eslint.config.js +27 -0
- package/packages/adapters-redis/package.json +49 -0
- package/packages/adapters-redis/src/index.ts +164 -0
- package/packages/adapters-redis/src/manifest.ts +49 -0
- package/packages/adapters-redis/tsconfig.build.json +15 -0
- package/packages/adapters-redis/tsconfig.json +9 -0
- package/packages/adapters-redis/tsup.config.ts +9 -0
- package/packages/adapters-snapshot-localfs/README.md +10 -0
- package/packages/adapters-snapshot-localfs/eslint.config.js +2 -0
- package/packages/adapters-snapshot-localfs/package.json +46 -0
- package/packages/adapters-snapshot-localfs/src/index.test.ts +40 -0
- package/packages/adapters-snapshot-localfs/src/index.ts +292 -0
- package/packages/adapters-snapshot-localfs/src/manifest.ts +32 -0
- package/packages/adapters-snapshot-localfs/tsconfig.build.json +15 -0
- package/packages/adapters-snapshot-localfs/tsconfig.json +16 -0
- package/packages/adapters-snapshot-localfs/tsup.config.ts +11 -0
- package/packages/adapters-sqlite/README.md +163 -0
- package/packages/adapters-sqlite/eslint.config.js +27 -0
- package/packages/adapters-sqlite/package.json +54 -0
- package/packages/adapters-sqlite/src/index.test.ts +245 -0
- package/packages/adapters-sqlite/src/index.ts +382 -0
- package/packages/adapters-sqlite/src/manifest.ts +47 -0
- package/packages/adapters-sqlite/src/secure-sql.test.ts +290 -0
- package/packages/adapters-sqlite/src/secure-sql.ts +281 -0
- package/packages/adapters-sqlite/tsconfig.build.json +15 -0
- package/packages/adapters-sqlite/tsconfig.json +9 -0
- package/packages/adapters-sqlite/tsup.config.ts +8 -0
- package/packages/adapters-sqlite/vitest.config.ts +19 -0
- package/packages/adapters-transport/README.md +170 -0
- package/packages/adapters-transport/eslint.config.js +27 -0
- package/packages/adapters-transport/package.json +49 -0
- package/packages/adapters-transport/src/__tests__/unix-socket-server.test.ts +550 -0
- package/packages/adapters-transport/src/index.ts +101 -0
- package/packages/adapters-transport/src/ipc-transport.ts +228 -0
- package/packages/adapters-transport/src/transport.ts +224 -0
- package/packages/adapters-transport/src/types.ts +92 -0
- package/packages/adapters-transport/src/unix-socket-server.ts +193 -0
- package/packages/adapters-transport/src/unix-socket-transport.ts +280 -0
- package/packages/adapters-transport/tsconfig.build.json +15 -0
- package/packages/adapters-transport/tsconfig.json +9 -0
- package/packages/adapters-transport/tsup.config.ts +9 -0
- package/packages/adapters-vibeproxy/README.md +159 -0
- package/packages/adapters-vibeproxy/eslint.config.js +27 -0
- package/packages/adapters-vibeproxy/package.json +51 -0
- package/packages/adapters-vibeproxy/src/index.ts +13 -0
- package/packages/adapters-vibeproxy/src/llm.ts +437 -0
- package/packages/adapters-vibeproxy/src/manifest.ts +51 -0
- package/packages/adapters-vibeproxy/tsconfig.build.json +15 -0
- package/packages/adapters-vibeproxy/tsconfig.json +9 -0
- package/packages/adapters-vibeproxy/tsup.config.ts +8 -0
- package/packages/adapters-workspace-agent/package.json +46 -0
- package/packages/adapters-workspace-agent/src/__tests__/adapter.test.ts +212 -0
- package/packages/adapters-workspace-agent/src/index.ts +220 -0
- package/packages/adapters-workspace-agent/src/manifest.ts +36 -0
- package/packages/adapters-workspace-agent/tsconfig.build.json +15 -0
- package/packages/adapters-workspace-agent/tsconfig.json +16 -0
- package/packages/adapters-workspace-agent/tsup.config.ts +11 -0
- package/packages/adapters-workspace-localfs/README.md +9 -0
- package/packages/adapters-workspace-localfs/eslint.config.js +2 -0
- package/packages/adapters-workspace-localfs/package.json +46 -0
- package/packages/adapters-workspace-localfs/src/index.test.ts +27 -0
- package/packages/adapters-workspace-localfs/src/index.ts +172 -0
- package/packages/adapters-workspace-localfs/src/manifest.ts +32 -0
- package/packages/adapters-workspace-localfs/tsconfig.build.json +15 -0
- package/packages/adapters-workspace-localfs/tsconfig.json +16 -0
- package/packages/adapters-workspace-localfs/tsup.config.ts +11 -0
- package/packages/adapters-workspace-worktree/README.md +9 -0
- package/packages/adapters-workspace-worktree/eslint.config.js +2 -0
- package/packages/adapters-workspace-worktree/package.json +46 -0
- package/packages/adapters-workspace-worktree/src/index.test.ts +38 -0
- package/packages/adapters-workspace-worktree/src/index.ts +245 -0
- package/packages/adapters-workspace-worktree/src/manifest.ts +38 -0
- package/packages/adapters-workspace-worktree/tsconfig.build.json +15 -0
- package/packages/adapters-workspace-worktree/tsconfig.json +16 -0
- package/packages/adapters-workspace-worktree/tsup.config.ts +11 -0
- package/pnpm-workspace.yaml +2800 -0
- package/prettierrc.json +1 -0
- package/scripts/devkit-sync.mjs +37 -0
- package/scripts/hooks/post-push +9 -0
- package/scripts/hooks/pre-commit +9 -0
- package/scripts/hooks/pre-push +9 -0
- package/test-integration.ts +242 -0
- package/test.txt +1 -0
- package/tsconfig.base.json +6 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +9 -0
- package/tsconfig.paths.json +26 -0
- package/tsconfig.tools.json +17 -0
- package/tsup.config.bin.ts +34 -0
- package/tsup.config.cli.ts +41 -0
- package/tsup.config.dual.ts +46 -0
- package/tsup.config.ts +36 -0
- package/tsup.external.json +103 -0
- package/vitest.config.ts +2 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @kb-labs/adapters-qdrant
|
|
3
|
+
* Qdrant adapter implementing IVectorStore interface.
|
|
4
|
+
*
|
|
5
|
+
* All Qdrant client calls that touch the network are wrapped with
|
|
6
|
+
* exponential-backoff retry logic ({@link withRetry}) so that transient
|
|
7
|
+
* failures (ECONNREFUSED, ETIMEDOUT, HTTP 503, etc.) are handled
|
|
8
|
+
* automatically without propagating to callers.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { createAdapter } from '@kb-labs/adapters-qdrant';
|
|
13
|
+
*
|
|
14
|
+
* const vectorStore = createAdapter({
|
|
15
|
+
* url: 'http://localhost:6333',
|
|
16
|
+
* apiKey: process.env.QDRANT_API_KEY,
|
|
17
|
+
* collectionName: 'my-collection',
|
|
18
|
+
* dimension: 1536,
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* await vectorStore.upsert([
|
|
22
|
+
* { id: '1', vector: [...], metadata: { text: 'hello' } },
|
|
23
|
+
* ]);
|
|
24
|
+
*
|
|
25
|
+
* const results = await vectorStore.search([...], 10);
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { QdrantClient } from "@qdrant/js-client-rest";
|
|
30
|
+
import type {
|
|
31
|
+
IVectorStore,
|
|
32
|
+
VectorRecord,
|
|
33
|
+
VectorSearchResult,
|
|
34
|
+
VectorFilter,
|
|
35
|
+
} from "@kb-labs/core-platform";
|
|
36
|
+
|
|
37
|
+
// Re-export manifest
|
|
38
|
+
export { manifest } from "./manifest.js";
|
|
39
|
+
// Re-export retry utilities so callers can customise if needed
|
|
40
|
+
export { withRetry, isTransientError } from "./retry.js";
|
|
41
|
+
export type { RetryOptions } from "./retry.js";
|
|
42
|
+
|
|
43
|
+
import { createHash } from "node:crypto";
|
|
44
|
+
import { withRetry, type RetryOptions } from "./retry.js";
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Configuration for Qdrant vector store adapter.
|
|
48
|
+
*/
|
|
49
|
+
export interface QdrantVectorStoreConfig {
|
|
50
|
+
/** Qdrant server URL (e.g., 'http://localhost:6333') */
|
|
51
|
+
url: string;
|
|
52
|
+
/** API key for authentication (optional) */
|
|
53
|
+
apiKey?: string;
|
|
54
|
+
/** Collection name (default: 'kb-vectors') */
|
|
55
|
+
collectionName?: string;
|
|
56
|
+
/** Vector dimension (default: 1536, for OpenAI text-embedding-3-small) */
|
|
57
|
+
dimension?: number;
|
|
58
|
+
/** Request timeout in ms (default: 30000) */
|
|
59
|
+
timeout?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Retry configuration applied to every Qdrant client call.
|
|
62
|
+
* Transient errors (ECONNREFUSED, ETIMEDOUT, HTTP 503 / 429 / 502 / 504)
|
|
63
|
+
* are retried automatically with full-jitter exponential back-off.
|
|
64
|
+
*
|
|
65
|
+
* Set `maxAttempts: 1` to disable retries entirely.
|
|
66
|
+
*/
|
|
67
|
+
retry?: RetryOptions;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Convert a string to a deterministic UUID v4-like format.
|
|
72
|
+
* Qdrant requires point IDs to be either unsigned integers or UUIDs.
|
|
73
|
+
*/
|
|
74
|
+
function stringToUUID(str: string): string {
|
|
75
|
+
const hash = createHash("sha256").update(str).digest();
|
|
76
|
+
// Format as UUID v4 (8-4-4-4-12 hex digits)
|
|
77
|
+
return [
|
|
78
|
+
hash.slice(0, 4).toString("hex"),
|
|
79
|
+
hash.slice(4, 6).toString("hex"),
|
|
80
|
+
hash.slice(6, 8).toString("hex"),
|
|
81
|
+
hash.slice(8, 10).toString("hex"),
|
|
82
|
+
hash.slice(10, 16).toString("hex"),
|
|
83
|
+
].join("-");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Default retry options used for all Qdrant client calls.
|
|
88
|
+
*
|
|
89
|
+
* Strategy: up to 4 attempts, full-jitter exponential back-off starting at
|
|
90
|
+
* 200 ms, capped at 10 s. That gives worst-case wait of ~10 s before the
|
|
91
|
+
* final attempt, which comfortably covers a Qdrant cold-start or a brief
|
|
92
|
+
* 503 during a rolling restart.
|
|
93
|
+
*/
|
|
94
|
+
const DEFAULT_RETRY_OPTIONS: Required<
|
|
95
|
+
Pick<RetryOptions, "maxAttempts" | "baseDelayMs" | "maxDelayMs">
|
|
96
|
+
> = {
|
|
97
|
+
maxAttempts: 4,
|
|
98
|
+
baseDelayMs: 200,
|
|
99
|
+
maxDelayMs: 10_000,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Qdrant implementation of IVectorStore interface.
|
|
104
|
+
*
|
|
105
|
+
* All network-facing operations (`search`, `upsert`, `delete`, `scroll`,
|
|
106
|
+
* `retrieve`, `getCollections`, `createCollection`, `getCollection`) are
|
|
107
|
+
* wrapped with {@link withRetry} to transparently handle transient errors.
|
|
108
|
+
*/
|
|
109
|
+
export class QdrantVectorStore implements IVectorStore {
|
|
110
|
+
private client: QdrantClient;
|
|
111
|
+
private collectionName: string;
|
|
112
|
+
private dimension: number;
|
|
113
|
+
private initialized = false;
|
|
114
|
+
private url: string;
|
|
115
|
+
private retryOptions: RetryOptions;
|
|
116
|
+
|
|
117
|
+
// Adaptive concurrency state
|
|
118
|
+
private concurrency = 3; // Start with 3 parallel batches
|
|
119
|
+
private consecutiveErrors = 0;
|
|
120
|
+
private consecutiveSuccesses = 0;
|
|
121
|
+
|
|
122
|
+
constructor(config: QdrantVectorStoreConfig) {
|
|
123
|
+
this.url = config.url;
|
|
124
|
+
this.client = new QdrantClient({
|
|
125
|
+
url: config.url,
|
|
126
|
+
apiKey: config.apiKey,
|
|
127
|
+
timeout: config.timeout ?? 30000,
|
|
128
|
+
});
|
|
129
|
+
this.collectionName = config.collectionName ?? "kb-vectors";
|
|
130
|
+
this.dimension = config.dimension ?? 1536;
|
|
131
|
+
// Merge caller-supplied retry options over the defaults
|
|
132
|
+
this.retryOptions = { ...DEFAULT_RETRY_OPTIONS, ...config.retry };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Private helpers
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Ensure collection exists with correct configuration.
|
|
141
|
+
*
|
|
142
|
+
* `getCollections` and `createCollection` are both wrapped with retry so
|
|
143
|
+
* that a fresh Qdrant instance that is still starting up (ECONNREFUSED) is
|
|
144
|
+
* handled gracefully.
|
|
145
|
+
*/
|
|
146
|
+
private async ensureCollection(): Promise<void> {
|
|
147
|
+
if (this.initialized) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const collections = await withRetry(
|
|
153
|
+
() => this.client.getCollections(),
|
|
154
|
+
this.retryOptions,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const exists = collections.collections.some(
|
|
158
|
+
(c) => c.name === this.collectionName,
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
if (!exists) {
|
|
162
|
+
await withRetry(
|
|
163
|
+
() =>
|
|
164
|
+
this.client.createCollection(this.collectionName, {
|
|
165
|
+
vectors: {
|
|
166
|
+
size: this.dimension,
|
|
167
|
+
distance: "Cosine",
|
|
168
|
+
},
|
|
169
|
+
}),
|
|
170
|
+
this.retryOptions,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.initialized = true;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
throw new Error(
|
|
177
|
+
`Failed to initialize Qdrant collection: ${error instanceof Error ? error.message : String(error)}`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
// IVectorStore — required methods
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Search for the nearest vectors.
|
|
188
|
+
*
|
|
189
|
+
* The underlying `this.client.search()` call is wrapped with
|
|
190
|
+
* {@link withRetry} to handle ECONNREFUSED, ETIMEDOUT, and HTTP 503
|
|
191
|
+
* transparently.
|
|
192
|
+
*/
|
|
193
|
+
async search(
|
|
194
|
+
query: number[],
|
|
195
|
+
limit: number,
|
|
196
|
+
filter?: VectorFilter,
|
|
197
|
+
): Promise<VectorSearchResult[]> {
|
|
198
|
+
await this.ensureCollection();
|
|
199
|
+
|
|
200
|
+
// Build Qdrant filter if provided
|
|
201
|
+
const qdrantFilter = filter
|
|
202
|
+
? {
|
|
203
|
+
must: [
|
|
204
|
+
{
|
|
205
|
+
key: filter.field,
|
|
206
|
+
match:
|
|
207
|
+
filter.operator === "eq" ? { value: filter.value } : undefined,
|
|
208
|
+
range:
|
|
209
|
+
filter.operator === "gt" || filter.operator === "gte"
|
|
210
|
+
? { gt: filter.value }
|
|
211
|
+
: filter.operator === "lt" || filter.operator === "lte"
|
|
212
|
+
? { lt: filter.value }
|
|
213
|
+
: undefined,
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
}
|
|
217
|
+
: undefined;
|
|
218
|
+
|
|
219
|
+
const searchParams = {
|
|
220
|
+
vector: query,
|
|
221
|
+
limit,
|
|
222
|
+
filter: qdrantFilter,
|
|
223
|
+
with_payload: true,
|
|
224
|
+
} as const;
|
|
225
|
+
|
|
226
|
+
const response = await withRetry(
|
|
227
|
+
() => this.client.search(this.collectionName, searchParams),
|
|
228
|
+
this.retryOptions,
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
return response.map((point) => ({
|
|
232
|
+
id: String(point.id),
|
|
233
|
+
score: point.score,
|
|
234
|
+
metadata: point.payload as Record<string, unknown> | undefined,
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Upsert vectors in batches with adaptive parallel processing.
|
|
240
|
+
*
|
|
241
|
+
* Each individual `this.client.upsert()` call is wrapped with
|
|
242
|
+
* {@link withRetry}. The adaptive-concurrency logic is preserved:
|
|
243
|
+
* on sustained success the concurrency goes up; on sustained failure
|
|
244
|
+
* it is halved (the retry wrapper already handles brief transient
|
|
245
|
+
* errors before they reach the concurrency bookkeeping).
|
|
246
|
+
*/
|
|
247
|
+
async upsert(vectors: VectorRecord[]): Promise<void> {
|
|
248
|
+
await this.ensureCollection();
|
|
249
|
+
|
|
250
|
+
if (vectors.length === 0) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const points = vectors.map((record) => ({
|
|
255
|
+
id: stringToUUID(record.id),
|
|
256
|
+
vector: record.vector,
|
|
257
|
+
payload: record.metadata ?? {},
|
|
258
|
+
}));
|
|
259
|
+
|
|
260
|
+
// Batch upsert with adaptive parallel processing (Qdrant supports up to 100 points per request)
|
|
261
|
+
const batchSize = 100;
|
|
262
|
+
const _totalBatches = Math.ceil(points.length / batchSize);
|
|
263
|
+
|
|
264
|
+
// Create all batches
|
|
265
|
+
type QdrantPoint = {
|
|
266
|
+
id: string;
|
|
267
|
+
vector: number[];
|
|
268
|
+
payload: Record<string, unknown>;
|
|
269
|
+
};
|
|
270
|
+
const batches: QdrantPoint[][] = [];
|
|
271
|
+
for (let i = 0; i < points.length; i += batchSize) {
|
|
272
|
+
batches.push(points.slice(i, i + batchSize) as QdrantPoint[]);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Process batches with adaptive concurrency
|
|
276
|
+
let batchIndex = 0;
|
|
277
|
+
while (batchIndex < batches.length) {
|
|
278
|
+
const currentConcurrency = Math.min(
|
|
279
|
+
this.concurrency,
|
|
280
|
+
batches.length - batchIndex,
|
|
281
|
+
);
|
|
282
|
+
const batchGroup = batches.slice(
|
|
283
|
+
batchIndex,
|
|
284
|
+
batchIndex + currentConcurrency,
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
const batchPromises = batchGroup.map(async (batch) => {
|
|
288
|
+
try {
|
|
289
|
+
// ⚡ Don't wait for indexing — let Qdrant index asynchronously.
|
|
290
|
+
// Transient failures are transparently retried before bubbling up.
|
|
291
|
+
await withRetry(
|
|
292
|
+
() =>
|
|
293
|
+
this.client.upsert(this.collectionName, {
|
|
294
|
+
wait: false,
|
|
295
|
+
points: batch,
|
|
296
|
+
}),
|
|
297
|
+
this.retryOptions,
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
// Success: increase concurrency gradually
|
|
301
|
+
this.consecutiveErrors = 0;
|
|
302
|
+
this.consecutiveSuccesses++;
|
|
303
|
+
if (this.consecutiveSuccesses >= 5 && this.concurrency < 10) {
|
|
304
|
+
this.concurrency++;
|
|
305
|
+
this.consecutiveSuccesses = 0;
|
|
306
|
+
}
|
|
307
|
+
} catch (error) {
|
|
308
|
+
// Persistent (non-transient) error after all retries — adjust concurrency
|
|
309
|
+
this.consecutiveSuccesses = 0;
|
|
310
|
+
this.consecutiveErrors++;
|
|
311
|
+
if (this.consecutiveErrors >= 2 && this.concurrency > 1) {
|
|
312
|
+
this.concurrency = Math.max(1, Math.floor(this.concurrency / 2));
|
|
313
|
+
this.consecutiveErrors = 0;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// Wait for this group of concurrent batches to complete
|
|
321
|
+
try {
|
|
322
|
+
await Promise.all(batchPromises);
|
|
323
|
+
batchIndex += batchGroup.length;
|
|
324
|
+
} catch (_error) {
|
|
325
|
+
// If group fails, retry with reduced concurrency (already adjusted above)
|
|
326
|
+
// Don't increment batchIndex — retry same batches
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Delete vectors by ID.
|
|
333
|
+
*
|
|
334
|
+
* The underlying `this.client.delete()` call is wrapped with
|
|
335
|
+
* {@link withRetry}.
|
|
336
|
+
*/
|
|
337
|
+
async delete(ids: string[]): Promise<void> {
|
|
338
|
+
await this.ensureCollection();
|
|
339
|
+
|
|
340
|
+
if (ids.length === 0) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const uuids = ids.map(stringToUUID);
|
|
345
|
+
|
|
346
|
+
await withRetry(
|
|
347
|
+
() =>
|
|
348
|
+
this.client.delete(this.collectionName, {
|
|
349
|
+
wait: false, // ⚡ Don't wait for indexing - let Qdrant process asynchronously
|
|
350
|
+
points: uuids,
|
|
351
|
+
}),
|
|
352
|
+
this.retryOptions,
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Return the total number of vectors in the collection.
|
|
358
|
+
*
|
|
359
|
+
* The underlying `this.client.getCollection()` call is wrapped with
|
|
360
|
+
* {@link withRetry}.
|
|
361
|
+
*/
|
|
362
|
+
async count(): Promise<number> {
|
|
363
|
+
await this.ensureCollection();
|
|
364
|
+
|
|
365
|
+
const info = await withRetry(
|
|
366
|
+
() => this.client.getCollection(this.collectionName),
|
|
367
|
+
this.retryOptions,
|
|
368
|
+
);
|
|
369
|
+
return info.points_count ?? 0;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ---------------------------------------------------------------------------
|
|
373
|
+
// IVectorStore — optional methods
|
|
374
|
+
// ---------------------------------------------------------------------------
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Get vectors by IDs.
|
|
378
|
+
*
|
|
379
|
+
* The underlying `this.client.retrieve()` call is wrapped with
|
|
380
|
+
* {@link withRetry}.
|
|
381
|
+
*/
|
|
382
|
+
async get(ids: string[]): Promise<VectorRecord[]> {
|
|
383
|
+
await this.ensureCollection();
|
|
384
|
+
|
|
385
|
+
if (ids.length === 0) {
|
|
386
|
+
return [];
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const response = await withRetry(
|
|
390
|
+
() =>
|
|
391
|
+
this.client.retrieve(this.collectionName, {
|
|
392
|
+
ids: ids.map((id) => stringToUUID(id)),
|
|
393
|
+
with_vector: true,
|
|
394
|
+
with_payload: true,
|
|
395
|
+
}),
|
|
396
|
+
this.retryOptions,
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
return response.map((point) => ({
|
|
400
|
+
id: String(point.id),
|
|
401
|
+
vector: point.vector as number[],
|
|
402
|
+
metadata: point.payload as Record<string, unknown> | undefined,
|
|
403
|
+
}));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Query vectors by metadata filter.
|
|
408
|
+
*
|
|
409
|
+
* The underlying `this.client.scroll()` call is wrapped with
|
|
410
|
+
* {@link withRetry}.
|
|
411
|
+
*/
|
|
412
|
+
async query(filter: VectorFilter): Promise<VectorRecord[]> {
|
|
413
|
+
await this.ensureCollection();
|
|
414
|
+
|
|
415
|
+
const response = await withRetry(
|
|
416
|
+
() =>
|
|
417
|
+
this.client.scroll(this.collectionName, {
|
|
418
|
+
filter: this.convertFilter(filter),
|
|
419
|
+
with_vector: true,
|
|
420
|
+
with_payload: true,
|
|
421
|
+
limit: 10000, // Max limit for bulk retrieval
|
|
422
|
+
}),
|
|
423
|
+
this.retryOptions,
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
return response.points.map((point) => ({
|
|
427
|
+
id: String(point.id),
|
|
428
|
+
vector: point.vector as number[],
|
|
429
|
+
metadata: point.payload as Record<string, unknown> | undefined,
|
|
430
|
+
}));
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ---------------------------------------------------------------------------
|
|
434
|
+
// Private helpers
|
|
435
|
+
// ---------------------------------------------------------------------------
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Convert platform VectorFilter to Qdrant filter format.
|
|
439
|
+
*/
|
|
440
|
+
private convertFilter(filter: VectorFilter): any {
|
|
441
|
+
const fieldParts = filter.field.split(".");
|
|
442
|
+
const fieldName = fieldParts[fieldParts.length - 1]; // Get last part after dots
|
|
443
|
+
|
|
444
|
+
switch (filter.operator) {
|
|
445
|
+
case "eq":
|
|
446
|
+
return { must: [{ key: fieldName, match: { value: filter.value } }] };
|
|
447
|
+
case "ne":
|
|
448
|
+
return {
|
|
449
|
+
must_not: [{ key: fieldName, match: { value: filter.value } }],
|
|
450
|
+
};
|
|
451
|
+
case "in":
|
|
452
|
+
return {
|
|
453
|
+
must: [{ key: fieldName, match: { any: filter.value as any[] } }],
|
|
454
|
+
};
|
|
455
|
+
case "nin":
|
|
456
|
+
return {
|
|
457
|
+
must_not: [{ key: fieldName, match: { any: filter.value as any[] } }],
|
|
458
|
+
};
|
|
459
|
+
default:
|
|
460
|
+
// For gt/gte/lt/lte - use range filter
|
|
461
|
+
return {
|
|
462
|
+
must: [
|
|
463
|
+
{ key: fieldName, range: { [filter.operator]: filter.value } },
|
|
464
|
+
],
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Create Qdrant vector store adapter.
|
|
472
|
+
* This is the factory function called by initPlatform() when loading adapters.
|
|
473
|
+
*/
|
|
474
|
+
export function createAdapter(
|
|
475
|
+
config?: QdrantVectorStoreConfig,
|
|
476
|
+
): QdrantVectorStore {
|
|
477
|
+
const fallbackUrl = process.env.QDRANT_URL ?? "http://localhost:6333";
|
|
478
|
+
const finalConfig: QdrantVectorStoreConfig = {
|
|
479
|
+
url: config?.url ?? fallbackUrl,
|
|
480
|
+
apiKey: config?.apiKey ?? process.env.QDRANT_API_KEY,
|
|
481
|
+
collectionName: config?.collectionName,
|
|
482
|
+
dimension: config?.dimension,
|
|
483
|
+
timeout: config?.timeout,
|
|
484
|
+
retry: config?.retry,
|
|
485
|
+
};
|
|
486
|
+
return new QdrantVectorStore(finalConfig);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Default export for direct import
|
|
490
|
+
export default createAdapter;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @kb-labs/adapters-qdrant/manifest
|
|
3
|
+
* Adapter manifest for Qdrant vector store.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { AdapterManifest } from "@kb-labs/core-platform";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Adapter manifest for Qdrant vector store.
|
|
10
|
+
*/
|
|
11
|
+
export const manifest: AdapterManifest = {
|
|
12
|
+
manifestVersion: "1.0.0",
|
|
13
|
+
id: "qdrant-vectorstore",
|
|
14
|
+
name: "Qdrant Vector Store",
|
|
15
|
+
version: "1.0.0",
|
|
16
|
+
description: "High-performance vector database for semantic search and RAG",
|
|
17
|
+
author: "KB Labs Team",
|
|
18
|
+
license: "KBPL-1.1",
|
|
19
|
+
type: "core",
|
|
20
|
+
implements: "IVectorStore",
|
|
21
|
+
capabilities: {
|
|
22
|
+
search: true,
|
|
23
|
+
batch: true,
|
|
24
|
+
custom: {
|
|
25
|
+
hybridSearch: true,
|
|
26
|
+
filtering: true,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
configSchema: {
|
|
30
|
+
url: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "Qdrant server URL (e.g., http://localhost:6333)",
|
|
33
|
+
},
|
|
34
|
+
apiKey: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "API key for authentication (optional)",
|
|
37
|
+
},
|
|
38
|
+
collectionName: {
|
|
39
|
+
type: "string",
|
|
40
|
+
default: "kb-vectors",
|
|
41
|
+
description: "Collection name",
|
|
42
|
+
},
|
|
43
|
+
dimension: {
|
|
44
|
+
type: "number",
|
|
45
|
+
default: 1536,
|
|
46
|
+
description: "Vector dimension (default: 1536 for OpenAI embeddings)",
|
|
47
|
+
},
|
|
48
|
+
timeout: {
|
|
49
|
+
type: "number",
|
|
50
|
+
default: 30000,
|
|
51
|
+
description: "Request timeout in milliseconds",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|