@vivantel/virage-store-qdrant 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/query-perf.d.ts +3 -0
- package/dist/query-perf.d.ts.map +1 -0
- package/dist/query-perf.js +81 -0
- package/dist/query-perf.js.map +1 -0
- package/dist/query-perf.test.d.ts +2 -0
- package/dist/query-perf.test.d.ts.map +1 -0
- package/dist/query-perf.test.js +113 -0
- package/dist/query-perf.test.js.map +1 -0
- package/dist/stats.d.ts +4 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +75 -0
- package/dist/stats.js.map +1 -0
- package/dist/stats.test.d.ts +2 -0
- package/dist/stats.test.d.ts.map +1 -0
- package/dist/stats.test.js +151 -0
- package/dist/stats.test.js.map +1 -0
- package/dist/store.d.ts +42 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +129 -0
- package/dist/store.js.map +1 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# @vivantel/virage-store-qdrant
|
|
2
|
+
|
|
3
|
+
Qdrant vector store for [`@vivantel/virage-core`](../rag-core/README.md). Works with both local Qdrant instances and Qdrant Cloud (SaaS).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @vivantel/virage-store-qdrant @vivantel/virage-core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start (JSON config)
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"vectorStore": {
|
|
16
|
+
"package": "@vivantel/virage-store-qdrant",
|
|
17
|
+
"config": {
|
|
18
|
+
"url": "${QDRANT_URL}",
|
|
19
|
+
"apiKey": "${QDRANT_API_KEY}"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
For a local instance (no API key required):
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"vectorStore": {
|
|
30
|
+
"package": "@vivantel/virage-store-qdrant",
|
|
31
|
+
"config": {
|
|
32
|
+
"url": "http://localhost:6333"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Configuration
|
|
39
|
+
|
|
40
|
+
| Option | Type | Default | Description |
|
|
41
|
+
|--------|------|---------|-------------|
|
|
42
|
+
| `url` | `string` | **required** | Qdrant instance URL |
|
|
43
|
+
| `apiKey` | `string` | `undefined` | API key for Qdrant Cloud |
|
|
44
|
+
| `collection` | `string` | `"documents"` | Collection name |
|
|
45
|
+
| `dimensions` | `number` | `1536` | Vector size — must match your embedder |
|
|
46
|
+
|
|
47
|
+
## TypeScript usage
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { QdrantVectorStore } from "@vivantel/virage-store-qdrant";
|
|
51
|
+
|
|
52
|
+
const store = new QdrantVectorStore({
|
|
53
|
+
url: process.env.QDRANT_URL!,
|
|
54
|
+
apiKey: process.env.QDRANT_API_KEY,
|
|
55
|
+
collection: "my-docs",
|
|
56
|
+
dimensions: 1536,
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Observability (ROADMAP v2)
|
|
61
|
+
|
|
62
|
+
### Index stats (`virage store stats`)
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
const stats = await store.getIndexStats();
|
|
66
|
+
// {
|
|
67
|
+
// totalVectors: 12500,
|
|
68
|
+
// indexType: "hnsw",
|
|
69
|
+
// annRecallAt10: 0.96,
|
|
70
|
+
// indexAgeHours: 0,
|
|
71
|
+
// deadTupleFraction: 0,
|
|
72
|
+
// suggestions: ["Collection looks healthy"]
|
|
73
|
+
// }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Query performance (`virage store perf --timeframe 24`)
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
const perf = await store.getQueryPerfReport(24);
|
|
80
|
+
// {
|
|
81
|
+
// timeframeHours: 24,
|
|
82
|
+
// p50LatencyMs: 3.2,
|
|
83
|
+
// p95LatencyMs: 8.7,
|
|
84
|
+
// p99LatencyMs: 14.1,
|
|
85
|
+
// slowQueryCount: 0,
|
|
86
|
+
// suggestedIndexes: ["Query performance looks healthy."]
|
|
87
|
+
// }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Query performance metrics require Qdrant's `/metrics` endpoint to be reachable. Metrics are returned as `-1` if telemetry is not available.
|
|
91
|
+
|
|
92
|
+
## Running Qdrant locally
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
docker run -p 6333:6333 qdrant/qdrant
|
|
96
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { QdrantVectorStore, type QdrantVectorStoreOptions } from "./store.js";
|
|
2
|
+
import type { VectorStore } from "@vivantel/virage-core";
|
|
3
|
+
/** Factory used by the JSON config loader. */
|
|
4
|
+
export declare function createVectorStore(config: Record<string, unknown>): VectorStore;
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,8CAA8C;AAC9C,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,WAAW,CAkBb"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { QdrantVectorStore } from "./store.js";
|
|
2
|
+
import { QdrantVectorStore } from "./store.js";
|
|
3
|
+
/** Factory used by the JSON config loader. */
|
|
4
|
+
export function createVectorStore(config) {
|
|
5
|
+
const url = typeof config.url === "string" ? config.url : undefined;
|
|
6
|
+
const path = typeof config.path === "string" ? config.path : undefined;
|
|
7
|
+
if (!url && !path) {
|
|
8
|
+
throw new Error("@vivantel/virage-store-qdrant: config.url or config.path is required");
|
|
9
|
+
}
|
|
10
|
+
return new QdrantVectorStore({
|
|
11
|
+
url,
|
|
12
|
+
path,
|
|
13
|
+
port: typeof config.port === "number" ? config.port : undefined,
|
|
14
|
+
apiKey: typeof config.apiKey === "string" ? config.apiKey : undefined,
|
|
15
|
+
collection: typeof config.collection === "string" ? config.collection : undefined,
|
|
16
|
+
dimensions: typeof config.dimensions === "number" ? config.dimensions : undefined,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAiC,MAAM,YAAY,CAAC;AAG9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/C,8CAA8C;AAC9C,MAAM,UAAU,iBAAiB,CAC/B,MAA+B;IAE/B,MAAM,GAAG,GAAG,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;IACpE,MAAM,IAAI,GAAG,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACvE,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CACb,sEAAsE,CACvE,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,iBAAiB,CAAC;QAC3B,GAAG;QACH,IAAI;QACJ,IAAI,EAAE,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QAC/D,MAAM,EAAE,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;QACrE,UAAU,EACR,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;QACvE,UAAU,EACR,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;KACxE,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-perf.d.ts","sourceRoot":"","sources":["../src/query-perf.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAc7D,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,eAAe,CAAC,CA4C1B"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const UNAVAILABLE = {
|
|
2
|
+
timeframeHours: 0,
|
|
3
|
+
p50LatencyMs: -1,
|
|
4
|
+
p95LatencyMs: -1,
|
|
5
|
+
p99LatencyMs: -1,
|
|
6
|
+
slowQueryCount: -1,
|
|
7
|
+
suggestedIndexes: [
|
|
8
|
+
"Qdrant telemetry not available. Ensure the /metrics endpoint is reachable " +
|
|
9
|
+
"and telemetry is enabled in your Qdrant configuration.",
|
|
10
|
+
],
|
|
11
|
+
};
|
|
12
|
+
export async function getQueryPerfReport(qdrantUrl, _collection, timeframeHours) {
|
|
13
|
+
const metricsUrl = `${qdrantUrl.replace(/\/$/, "")}/metrics`;
|
|
14
|
+
let text;
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch(metricsUrl, { signal: AbortSignal.timeout(5000) });
|
|
17
|
+
if (!res.ok)
|
|
18
|
+
return { ...UNAVAILABLE, timeframeHours };
|
|
19
|
+
text = await res.text();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return { ...UNAVAILABLE, timeframeHours };
|
|
23
|
+
}
|
|
24
|
+
const latencies = parsePrometheusHistogram(text);
|
|
25
|
+
if (latencies.length === 0) {
|
|
26
|
+
return { ...UNAVAILABLE, timeframeHours };
|
|
27
|
+
}
|
|
28
|
+
latencies.sort((a, b) => a - b);
|
|
29
|
+
const percentile = (p) => latencies[Math.floor((p / 100) * latencies.length)] ?? 0;
|
|
30
|
+
const p50 = percentile(50) * 1000;
|
|
31
|
+
const p95 = percentile(95) * 1000;
|
|
32
|
+
const p99 = percentile(99) * 1000;
|
|
33
|
+
const slowQueryCount = latencies.filter((ms) => ms * 1000 > 100).length;
|
|
34
|
+
const suggestedIndexes = [];
|
|
35
|
+
if (slowQueryCount > 0) {
|
|
36
|
+
suggestedIndexes.push(`${slowQueryCount} slow queries (>100ms) detected. Consider tuning hnsw_config ef or increasing hardware resources.`);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
suggestedIndexes.push("Query performance looks healthy.");
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
timeframeHours,
|
|
43
|
+
p50LatencyMs: Math.round(p50 * 10) / 10,
|
|
44
|
+
p95LatencyMs: Math.round(p95 * 10) / 10,
|
|
45
|
+
p99LatencyMs: Math.round(p99 * 10) / 10,
|
|
46
|
+
slowQueryCount,
|
|
47
|
+
suggestedIndexes,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Reconstruct a latency sample list from Prometheus histogram sum/count lines.
|
|
52
|
+
* Returns latency values in seconds.
|
|
53
|
+
*/
|
|
54
|
+
function parsePrometheusHistogram(text) {
|
|
55
|
+
const samples = [];
|
|
56
|
+
// Match _sum and _count lines for REST API duration metrics
|
|
57
|
+
const sumPattern = /^rest_api_response_duration_seconds_sum\{[^}]*\}\s+([\d.e+]+)/gm;
|
|
58
|
+
const countPattern = /^rest_api_response_duration_seconds_count\{[^}]*\}\s+([\d.e+]+)/gm;
|
|
59
|
+
const sums = [];
|
|
60
|
+
const counts = [];
|
|
61
|
+
for (const m of text.matchAll(sumPattern)) {
|
|
62
|
+
sums.push(parseFloat(m[1]));
|
|
63
|
+
}
|
|
64
|
+
for (const m of text.matchAll(countPattern)) {
|
|
65
|
+
counts.push(parseFloat(m[1]));
|
|
66
|
+
}
|
|
67
|
+
for (let i = 0; i < Math.min(sums.length, counts.length); i++) {
|
|
68
|
+
const count = counts[i];
|
|
69
|
+
const sum = sums[i];
|
|
70
|
+
if (count > 0 && sum >= 0) {
|
|
71
|
+
const mean = sum / count;
|
|
72
|
+
// Approximate distribution: repeat the mean `min(count, 10)` times
|
|
73
|
+
const weight = Math.min(Math.round(count), 10);
|
|
74
|
+
for (let j = 0; j < weight; j++) {
|
|
75
|
+
samples.push(mean);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return samples;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=query-perf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-perf.js","sourceRoot":"","sources":["../src/query-perf.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-perf.test.d.ts","sourceRoot":"","sources":["../src/query-perf.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { getQueryPerfReport } from "./query-perf.js";
|
|
3
|
+
// Prometheus histogram text helpers
|
|
4
|
+
function prometheusMetrics(entries) {
|
|
5
|
+
return entries
|
|
6
|
+
.map(({ sum, count }, i) => `rest_api_response_duration_seconds_sum{method="POST",endpoint="/search/${i}",status="200"} ${sum}\n` +
|
|
7
|
+
`rest_api_response_duration_seconds_count{method="POST",endpoint="/search/${i}",status="200"} ${count}`)
|
|
8
|
+
.join("\n");
|
|
9
|
+
}
|
|
10
|
+
function mockFetch(status, body) {
|
|
11
|
+
return vi.fn().mockResolvedValue({
|
|
12
|
+
ok: status >= 200 && status < 300,
|
|
13
|
+
text: vi.fn().mockResolvedValue(body),
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
vi.unstubAllGlobals();
|
|
18
|
+
});
|
|
19
|
+
const UNAVAILABLE_SENTINEL = -1;
|
|
20
|
+
describe("getQueryPerfReport (Qdrant)", () => {
|
|
21
|
+
describe("metrics endpoint unavailable", () => {
|
|
22
|
+
it("returns all -1 latencies when fetch throws", async () => {
|
|
23
|
+
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("ECONNREFUSED")));
|
|
24
|
+
const report = await getQueryPerfReport("http://localhost:6333", "docs", 24);
|
|
25
|
+
expect(report.p50LatencyMs).toBe(UNAVAILABLE_SENTINEL);
|
|
26
|
+
expect(report.p95LatencyMs).toBe(UNAVAILABLE_SENTINEL);
|
|
27
|
+
expect(report.p99LatencyMs).toBe(UNAVAILABLE_SENTINEL);
|
|
28
|
+
expect(report.slowQueryCount).toBe(UNAVAILABLE_SENTINEL);
|
|
29
|
+
});
|
|
30
|
+
it("returns all -1 latencies when /metrics returns non-OK status", async () => {
|
|
31
|
+
vi.stubGlobal("fetch", mockFetch(503, "Service Unavailable"));
|
|
32
|
+
const report = await getQueryPerfReport("http://localhost:6333", "docs", 24);
|
|
33
|
+
expect(report.p50LatencyMs).toBe(UNAVAILABLE_SENTINEL);
|
|
34
|
+
});
|
|
35
|
+
it("returns all -1 latencies when /metrics body has no matching histogram lines", async () => {
|
|
36
|
+
vi.stubGlobal("fetch", mockFetch(200, "# HELP other_metric something\nother_metric 1.0\n"));
|
|
37
|
+
const report = await getQueryPerfReport("http://localhost:6333", "docs", 24);
|
|
38
|
+
expect(report.p50LatencyMs).toBe(UNAVAILABLE_SENTINEL);
|
|
39
|
+
});
|
|
40
|
+
it("includes a telemetry-not-available hint in suggestedIndexes", async () => {
|
|
41
|
+
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("timeout")));
|
|
42
|
+
const report = await getQueryPerfReport("http://localhost:6333", "docs", 24);
|
|
43
|
+
expect(report.suggestedIndexes[0]).toMatch(/telemetry|metrics/i);
|
|
44
|
+
});
|
|
45
|
+
it("preserves timeframeHours even when unavailable", async () => {
|
|
46
|
+
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("timeout")));
|
|
47
|
+
const report = await getQueryPerfReport("http://localhost:6333", "docs", 48);
|
|
48
|
+
expect(report.timeframeHours).toBe(48);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe("metrics endpoint reachable", () => {
|
|
52
|
+
it("computes valid percentiles from Prometheus histogram (p50 ≤ p95 ≤ p99)", async () => {
|
|
53
|
+
// 5 entries, mean latencies 10ms–50ms (0.01–0.05 s)
|
|
54
|
+
const body = prometheusMetrics([
|
|
55
|
+
{ sum: 0.05, count: 5 }, // mean 0.01 s = 10 ms
|
|
56
|
+
{ sum: 0.06, count: 3 }, // mean 0.02 s = 20 ms
|
|
57
|
+
{ sum: 0.09, count: 3 }, // mean 0.03 s = 30 ms
|
|
58
|
+
{ sum: 0.16, count: 4 }, // mean 0.04 s = 40 ms
|
|
59
|
+
{ sum: 0.25, count: 5 }, // mean 0.05 s = 50 ms
|
|
60
|
+
]);
|
|
61
|
+
vi.stubGlobal("fetch", mockFetch(200, body));
|
|
62
|
+
const report = await getQueryPerfReport("http://localhost:6333", "docs", 24);
|
|
63
|
+
expect(report.p50LatencyMs).toBeGreaterThanOrEqual(0);
|
|
64
|
+
expect(report.p95LatencyMs).toBeGreaterThanOrEqual(report.p50LatencyMs);
|
|
65
|
+
expect(report.p99LatencyMs).toBeGreaterThanOrEqual(report.p95LatencyMs);
|
|
66
|
+
});
|
|
67
|
+
it("timeframeHours is passed through to the report", async () => {
|
|
68
|
+
const body = prometheusMetrics([{ sum: 0.05, count: 5 }]);
|
|
69
|
+
vi.stubGlobal("fetch", mockFetch(200, body));
|
|
70
|
+
const report = await getQueryPerfReport("http://localhost:6333", "docs", 72);
|
|
71
|
+
expect(report.timeframeHours).toBe(72);
|
|
72
|
+
});
|
|
73
|
+
it("counts slow queries when mean latency > 100 ms", async () => {
|
|
74
|
+
// 2 fast entries (10ms, 50ms) + 2 slow (150ms, 200ms)
|
|
75
|
+
const body = prometheusMetrics([
|
|
76
|
+
{ sum: 0.05, count: 5 }, // 10 ms
|
|
77
|
+
{ sum: 0.15, count: 3 }, // 50 ms
|
|
78
|
+
{ sum: 0.45, count: 3 }, // 150 ms — slow
|
|
79
|
+
{ sum: 0.4, count: 2 }, // 200 ms — slow
|
|
80
|
+
]);
|
|
81
|
+
vi.stubGlobal("fetch", mockFetch(200, body));
|
|
82
|
+
const report = await getQueryPerfReport("http://localhost:6333", "docs", 24);
|
|
83
|
+
expect(report.slowQueryCount).toBeGreaterThan(0);
|
|
84
|
+
expect(report.suggestedIndexes[0]).toMatch(/slow|hnsw/i);
|
|
85
|
+
});
|
|
86
|
+
it("returns healthy suggestion when no slow queries", async () => {
|
|
87
|
+
const body = prometheusMetrics([
|
|
88
|
+
{ sum: 0.02, count: 4 }, // 5 ms
|
|
89
|
+
{ sum: 0.06, count: 3 }, // 20 ms
|
|
90
|
+
]);
|
|
91
|
+
vi.stubGlobal("fetch", mockFetch(200, body));
|
|
92
|
+
const report = await getQueryPerfReport("http://localhost:6333", "docs", 24);
|
|
93
|
+
expect(report.slowQueryCount).toBe(0);
|
|
94
|
+
expect(report.suggestedIndexes[0]).toMatch(/healthy/i);
|
|
95
|
+
});
|
|
96
|
+
it("normalises the metrics URL by stripping a trailing slash", async () => {
|
|
97
|
+
const fetchSpy = mockFetch(200, prometheusMetrics([{ sum: 0.01, count: 2 }]));
|
|
98
|
+
vi.stubGlobal("fetch", fetchSpy);
|
|
99
|
+
await getQueryPerfReport("http://localhost:6333/", "docs", 24);
|
|
100
|
+
const calledUrl = fetchSpy.mock.calls[0][0];
|
|
101
|
+
expect(calledUrl).toBe("http://localhost:6333/metrics");
|
|
102
|
+
});
|
|
103
|
+
it("ignores histogram entries where count is 0", async () => {
|
|
104
|
+
const body = 'rest_api_response_duration_seconds_sum{method="POST"} 0\n' +
|
|
105
|
+
'rest_api_response_duration_seconds_count{method="POST"} 0\n';
|
|
106
|
+
vi.stubGlobal("fetch", mockFetch(200, body));
|
|
107
|
+
const report = await getQueryPerfReport("http://localhost:6333", "docs", 24);
|
|
108
|
+
// zero-count entry produces no samples → falls back to UNAVAILABLE
|
|
109
|
+
expect(report.p50LatencyMs).toBe(UNAVAILABLE_SENTINEL);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
//# sourceMappingURL=query-perf.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-perf.test.js","sourceRoot":"","sources":["../src/query-perf.test.ts"],"names":[],"mappings":"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"}
|
package/dist/stats.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAI3D,wBAAsB,aAAa,CACjC,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,UAAU,CAAC,CA8CrB"}
|
package/dist/stats.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export async function getIndexStats(client, collection) {
|
|
2
|
+
const info = await client.getCollection(collection);
|
|
3
|
+
const suggestions = [];
|
|
4
|
+
const totalVectors = info.points_count ?? 0;
|
|
5
|
+
const segmentsCount = info.segments_count ?? 0;
|
|
6
|
+
// Qdrant always uses HNSW internally
|
|
7
|
+
const indexType = "hnsw";
|
|
8
|
+
if (info.status === "red") {
|
|
9
|
+
suggestions.push(`Collection status is RED — check Qdrant logs for errors in "${collection}"`);
|
|
10
|
+
}
|
|
11
|
+
else if (info.status === "yellow") {
|
|
12
|
+
suggestions.push(`Collection status is YELLOW — indexing may be in progress for "${collection}"`);
|
|
13
|
+
}
|
|
14
|
+
if (segmentsCount > 20) {
|
|
15
|
+
suggestions.push(`High segment count (${segmentsCount}) — consider running collection optimization to merge segments`);
|
|
16
|
+
}
|
|
17
|
+
const annRecallAt10 = await computeAnnRecall(client, collection, info, totalVectors);
|
|
18
|
+
if (suggestions.length === 0) {
|
|
19
|
+
suggestions.push("Collection looks healthy");
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
totalVectors,
|
|
23
|
+
indexType,
|
|
24
|
+
annRecallAt10,
|
|
25
|
+
// Not applicable for Qdrant (no vacuum/dead-tuple concept)
|
|
26
|
+
indexAgeHours: 0,
|
|
27
|
+
deadTupleFraction: 0,
|
|
28
|
+
suggestions,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function extractVectorSize(info) {
|
|
32
|
+
const vectors = info.config?.params?.vectors;
|
|
33
|
+
if (!vectors)
|
|
34
|
+
return undefined;
|
|
35
|
+
// Single-vector mode: { size: number, distance: ... }
|
|
36
|
+
if ("size" in vectors &&
|
|
37
|
+
typeof vectors.size === "number") {
|
|
38
|
+
return vectors.size;
|
|
39
|
+
}
|
|
40
|
+
// Named-vector mode: { default: { size: number, ... }, ... }
|
|
41
|
+
const firstNamed = Object.values(vectors)[0];
|
|
42
|
+
return typeof firstNamed?.size === "number" ? firstNamed.size : undefined;
|
|
43
|
+
}
|
|
44
|
+
async function computeAnnRecall(client, collection, info, totalVectors) {
|
|
45
|
+
if (totalVectors < 10)
|
|
46
|
+
return -1;
|
|
47
|
+
const size = extractVectorSize(info);
|
|
48
|
+
if (!size)
|
|
49
|
+
return -1;
|
|
50
|
+
try {
|
|
51
|
+
const zeroVector = Array(size).fill(0);
|
|
52
|
+
const [exactResults, annResults] = await Promise.all([
|
|
53
|
+
client.search(collection, {
|
|
54
|
+
vector: zeroVector,
|
|
55
|
+
limit: 10,
|
|
56
|
+
params: { exact: true },
|
|
57
|
+
with_payload: false,
|
|
58
|
+
}),
|
|
59
|
+
client.search(collection, {
|
|
60
|
+
vector: zeroVector,
|
|
61
|
+
limit: 10,
|
|
62
|
+
params: { exact: false },
|
|
63
|
+
with_payload: false,
|
|
64
|
+
}),
|
|
65
|
+
]);
|
|
66
|
+
const exactIds = new Set(exactResults.map((r) => String(r.id)));
|
|
67
|
+
const annIds = annResults.map((r) => String(r.id));
|
|
68
|
+
const hits = annIds.filter((id) => exactIds.has(id)).length;
|
|
69
|
+
return hits / Math.max(exactIds.size, 1);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return -1;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.js","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.test.d.ts","sourceRoot":"","sources":["../src/stats.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { getIndexStats } from "./stats.js";
|
|
3
|
+
function makeCollectionInfo(overrides = {}) {
|
|
4
|
+
return {
|
|
5
|
+
points_count: 100,
|
|
6
|
+
segments_count: 3,
|
|
7
|
+
status: "green",
|
|
8
|
+
config: {
|
|
9
|
+
params: {
|
|
10
|
+
vectors: {
|
|
11
|
+
size: 4,
|
|
12
|
+
distance: "Cosine",
|
|
13
|
+
},
|
|
14
|
+
shard_number: 1,
|
|
15
|
+
replication_factor: 1,
|
|
16
|
+
write_consistency_factor: 1,
|
|
17
|
+
on_disk_payload: false,
|
|
18
|
+
},
|
|
19
|
+
optimizer_config: {},
|
|
20
|
+
wal_config: {},
|
|
21
|
+
quantization_config: null,
|
|
22
|
+
},
|
|
23
|
+
...overrides,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function makeClient(info, searchResults = [[], []]) {
|
|
27
|
+
return {
|
|
28
|
+
getCollection: vi.fn().mockResolvedValue(info),
|
|
29
|
+
search: vi
|
|
30
|
+
.fn()
|
|
31
|
+
.mockResolvedValueOnce(searchResults[0] ?? [])
|
|
32
|
+
.mockResolvedValueOnce(searchResults[1] ?? []),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
describe("getIndexStats (Qdrant)", () => {
|
|
36
|
+
describe("basic fields", () => {
|
|
37
|
+
it("always returns hnsw indexType", async () => {
|
|
38
|
+
const client = makeClient(makeCollectionInfo());
|
|
39
|
+
const stats = await getIndexStats(client, "documents");
|
|
40
|
+
expect(stats.indexType).toBe("hnsw");
|
|
41
|
+
});
|
|
42
|
+
it("reflects points_count as totalVectors", async () => {
|
|
43
|
+
const client = makeClient(makeCollectionInfo({ points_count: 4200 }));
|
|
44
|
+
const stats = await getIndexStats(client, "documents");
|
|
45
|
+
expect(stats.totalVectors).toBe(4200);
|
|
46
|
+
});
|
|
47
|
+
it("totalVectors defaults to 0 when points_count is undefined", async () => {
|
|
48
|
+
const client = makeClient(makeCollectionInfo({ points_count: undefined }));
|
|
49
|
+
const stats = await getIndexStats(client, "documents");
|
|
50
|
+
expect(stats.totalVectors).toBe(0);
|
|
51
|
+
});
|
|
52
|
+
it("always returns indexAgeHours = 0 and deadTupleFraction = 0", async () => {
|
|
53
|
+
const client = makeClient(makeCollectionInfo());
|
|
54
|
+
const stats = await getIndexStats(client, "documents");
|
|
55
|
+
expect(stats.indexAgeHours).toBe(0);
|
|
56
|
+
expect(stats.deadTupleFraction).toBe(0);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe("status warnings", () => {
|
|
60
|
+
it("includes RED alert in suggestions when status is red", async () => {
|
|
61
|
+
const client = makeClient(makeCollectionInfo({ status: "red" }));
|
|
62
|
+
const stats = await getIndexStats(client, "documents");
|
|
63
|
+
expect(stats.suggestions.some((s) => /RED/i.test(s))).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
it("includes YELLOW warning in suggestions when status is yellow", async () => {
|
|
66
|
+
const client = makeClient(makeCollectionInfo({ status: "yellow" }));
|
|
67
|
+
const stats = await getIndexStats(client, "documents");
|
|
68
|
+
expect(stats.suggestions.some((s) => /YELLOW/i.test(s))).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
it("returns healthy suggestion when status is green and segments < 20", async () => {
|
|
71
|
+
const client = makeClient(makeCollectionInfo({ status: "green", segments_count: 5 }));
|
|
72
|
+
const stats = await getIndexStats(client, "documents");
|
|
73
|
+
expect(stats.suggestions[0]).toMatch(/healthy/i);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe("segment count", () => {
|
|
77
|
+
it("suggests optimization when segments_count > 20", async () => {
|
|
78
|
+
const client = makeClient(makeCollectionInfo({ segments_count: 25 }));
|
|
79
|
+
const stats = await getIndexStats(client, "documents");
|
|
80
|
+
expect(stats.suggestions.some((s) => /segment/i.test(s))).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
it("does not warn about segments when count is exactly 20", async () => {
|
|
83
|
+
const client = makeClient(makeCollectionInfo({ segments_count: 20 }));
|
|
84
|
+
const stats = await getIndexStats(client, "documents");
|
|
85
|
+
expect(stats.suggestions.every((s) => !/segment/i.test(s))).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
describe("ANN recall", () => {
|
|
89
|
+
it("is -1 when totalVectors < 10", async () => {
|
|
90
|
+
const client = makeClient(makeCollectionInfo({ points_count: 5 }));
|
|
91
|
+
const stats = await getIndexStats(client, "documents");
|
|
92
|
+
expect(stats.annRecallAt10).toBe(-1);
|
|
93
|
+
});
|
|
94
|
+
it("is -1 when vector size cannot be determined (no config)", async () => {
|
|
95
|
+
const info = makeCollectionInfo({ points_count: 100 });
|
|
96
|
+
info.config = null;
|
|
97
|
+
const client = makeClient(info);
|
|
98
|
+
const stats = await getIndexStats(client, "documents");
|
|
99
|
+
expect(stats.annRecallAt10).toBe(-1);
|
|
100
|
+
});
|
|
101
|
+
it("is 1.0 when exact and ANN results are identical (single-vector mode)", async () => {
|
|
102
|
+
const ids = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
103
|
+
const resultSet = ids.map((id) => ({ id, score: 0.9 }));
|
|
104
|
+
const client = makeClient(makeCollectionInfo({ points_count: 100 }), [
|
|
105
|
+
resultSet,
|
|
106
|
+
resultSet,
|
|
107
|
+
]);
|
|
108
|
+
const stats = await getIndexStats(client, "documents");
|
|
109
|
+
expect(stats.annRecallAt10).toBe(1);
|
|
110
|
+
});
|
|
111
|
+
it("computes partial recall correctly", async () => {
|
|
112
|
+
const exactResults = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((id) => ({
|
|
113
|
+
id,
|
|
114
|
+
score: 0.9,
|
|
115
|
+
}));
|
|
116
|
+
const annResults = [1, 2, 3, 4, 5, 11, 12, 13, 14, 15].map((id) => ({
|
|
117
|
+
id,
|
|
118
|
+
score: 0.85,
|
|
119
|
+
}));
|
|
120
|
+
const client = makeClient(makeCollectionInfo({ points_count: 100 }), [
|
|
121
|
+
exactResults,
|
|
122
|
+
annResults,
|
|
123
|
+
]);
|
|
124
|
+
const stats = await getIndexStats(client, "documents");
|
|
125
|
+
expect(stats.annRecallAt10).toBeCloseTo(0.5, 5);
|
|
126
|
+
});
|
|
127
|
+
it("supports named-vector mode for vector size extraction", async () => {
|
|
128
|
+
const info = makeCollectionInfo({ points_count: 100 });
|
|
129
|
+
// Override vectors to use named-vector mode
|
|
130
|
+
info.config.params.vectors = {
|
|
131
|
+
default: { size: 4, distance: "Cosine" },
|
|
132
|
+
};
|
|
133
|
+
const ids = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
134
|
+
const resultSet = ids.map((id) => ({ id, score: 0.9 }));
|
|
135
|
+
const client = makeClient(info, [resultSet, resultSet]);
|
|
136
|
+
const stats = await getIndexStats(client, "documents");
|
|
137
|
+
expect(stats.annRecallAt10).toBe(1);
|
|
138
|
+
});
|
|
139
|
+
it("is -1 when search() throws", async () => {
|
|
140
|
+
const client = {
|
|
141
|
+
getCollection: vi
|
|
142
|
+
.fn()
|
|
143
|
+
.mockResolvedValue(makeCollectionInfo({ points_count: 100 })),
|
|
144
|
+
search: vi.fn().mockRejectedValue(new Error("collection not found")),
|
|
145
|
+
};
|
|
146
|
+
const stats = await getIndexStats(client, "documents");
|
|
147
|
+
expect(stats.annRecallAt10).toBe(-1);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
//# sourceMappingURL=stats.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.test.js","sourceRoot":"","sources":["../src/stats.test.ts"],"names":[],"mappings":"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"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { VectorStore, VectorDocument, VectorSearchResult, IndexStats, QueryPerfReport, Logger } from "@vivantel/virage-core";
|
|
2
|
+
export interface QdrantVectorStoreOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Qdrant instance URL. e.g. "http://localhost:6333" or "https://xyz.qdrant.io".
|
|
5
|
+
* Required unless `path` is provided.
|
|
6
|
+
*/
|
|
7
|
+
url?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Local storage directory for Qdrant data (file mode).
|
|
10
|
+
* When provided, connects to `http://localhost:<port>` — you must start Qdrant
|
|
11
|
+
* pointing at this directory, e.g.:
|
|
12
|
+
* docker run -v <path>:/qdrant/storage -p 6333:6333 qdrant/qdrant
|
|
13
|
+
* Mutually exclusive with `url`.
|
|
14
|
+
*/
|
|
15
|
+
path?: string;
|
|
16
|
+
/** Port used when `path` mode is active. Defaults to 6333. */
|
|
17
|
+
port?: number;
|
|
18
|
+
/** API key — required for Qdrant Cloud, omit for local instances. */
|
|
19
|
+
apiKey?: string;
|
|
20
|
+
/** Collection name. Defaults to "documents". */
|
|
21
|
+
collection?: string;
|
|
22
|
+
/** Vector dimensions — must match your embedder. Defaults to 1536. */
|
|
23
|
+
dimensions?: number;
|
|
24
|
+
}
|
|
25
|
+
export declare class QdrantVectorStore implements VectorStore {
|
|
26
|
+
readonly name = "qdrant";
|
|
27
|
+
private readonly client;
|
|
28
|
+
private readonly collection;
|
|
29
|
+
private readonly dimensions;
|
|
30
|
+
private readonly url;
|
|
31
|
+
private logger;
|
|
32
|
+
constructor(options: QdrantVectorStoreOptions);
|
|
33
|
+
setLogger(logger: Logger): void;
|
|
34
|
+
initialize(): Promise<void>;
|
|
35
|
+
upsert(documents: VectorDocument[]): Promise<void>;
|
|
36
|
+
deleteBySourceFile(sourceFiles: string[]): Promise<void>;
|
|
37
|
+
getCurrentState(): Promise<Map<string, string>>;
|
|
38
|
+
search(queryEmbedding: number[], topK: number): Promise<VectorSearchResult[]>;
|
|
39
|
+
getIndexStats(): Promise<IndexStats>;
|
|
40
|
+
getQueryPerfReport(timeframeHours: number): Promise<QueryPerfReport>;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EACd,kBAAkB,EAClB,UAAU,EACV,eAAe,EACf,MAAM,EACP,MAAM,uBAAuB,CAAC;AAK/B,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAKD,qBAAa,iBAAkB,YAAW,WAAW;IACnD,QAAQ,CAAC,IAAI,YAAY;IAEzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAuB;gBAEzB,OAAO,EAAE,wBAAwB;IAe7C,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIzB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB3B,MAAM,CAAC,SAAS,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BlD,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAaxD,eAAe,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAoC/C,MAAM,CACV,cAAc,EAAE,MAAM,EAAE,EACxB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAwB1B,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;IAIpC,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;CAG3E"}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { QdrantClient } from "@qdrant/js-client-rest";
|
|
2
|
+
import { getIndexStats } from "./stats.js";
|
|
3
|
+
import { getQueryPerfReport } from "./query-perf.js";
|
|
4
|
+
const UPSERT_BATCH_SIZE = 100;
|
|
5
|
+
const SCROLL_PAGE_SIZE = 250;
|
|
6
|
+
export class QdrantVectorStore {
|
|
7
|
+
name = "qdrant";
|
|
8
|
+
client;
|
|
9
|
+
collection;
|
|
10
|
+
dimensions;
|
|
11
|
+
url;
|
|
12
|
+
logger = null;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
if (!options.url && !options.path) {
|
|
15
|
+
throw new Error("QdrantVectorStore: either url or path is required");
|
|
16
|
+
}
|
|
17
|
+
const resolvedUrl = options.url ?? `http://localhost:${options.port ?? 6333}`;
|
|
18
|
+
this.url = resolvedUrl;
|
|
19
|
+
this.collection = options.collection ?? "documents";
|
|
20
|
+
this.dimensions = options.dimensions ?? 1536;
|
|
21
|
+
this.client = new QdrantClient({
|
|
22
|
+
url: resolvedUrl,
|
|
23
|
+
apiKey: options.apiKey,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
setLogger(logger) {
|
|
27
|
+
this.logger = logger.withTag("qdrant");
|
|
28
|
+
}
|
|
29
|
+
async initialize() {
|
|
30
|
+
this.logger?.info(`Connecting to qdrant at ${this.url}, collection: ${this.collection}`);
|
|
31
|
+
const { exists } = await this.client.collectionExists(this.collection);
|
|
32
|
+
if (!exists) {
|
|
33
|
+
await this.client.createCollection(this.collection, {
|
|
34
|
+
vectors: { size: this.dimensions, distance: "Cosine" },
|
|
35
|
+
});
|
|
36
|
+
this.logger?.debug(`Created collection "${this.collection}" (${this.dimensions}d, cosine)`);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
this.logger?.debug(`Collection "${this.collection}" already exists`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async upsert(documents) {
|
|
43
|
+
this.logger?.verbose(`Upserting ${documents.length} docs into "${this.collection}"`);
|
|
44
|
+
for (let i = 0; i < documents.length; i += UPSERT_BATCH_SIZE) {
|
|
45
|
+
const batch = documents.slice(i, i + UPSERT_BATCH_SIZE);
|
|
46
|
+
const points = batch.map((doc) => ({
|
|
47
|
+
id: crypto.randomUUID(),
|
|
48
|
+
vector: doc.embedding,
|
|
49
|
+
payload: {
|
|
50
|
+
content: doc.content,
|
|
51
|
+
metadata: doc.metadata,
|
|
52
|
+
source_file: doc.sourceFile,
|
|
53
|
+
commit_hash: doc.commitHash,
|
|
54
|
+
content_hash: doc.contentHash,
|
|
55
|
+
},
|
|
56
|
+
}));
|
|
57
|
+
await this.client.upsert(this.collection, {
|
|
58
|
+
wait: true,
|
|
59
|
+
points,
|
|
60
|
+
});
|
|
61
|
+
this.logger?.trace(` Upserted IDs: ${points.map((p) => p.id.slice(0, 8)).join(", ")}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async deleteBySourceFile(sourceFiles) {
|
|
65
|
+
if (sourceFiles.length === 0)
|
|
66
|
+
return;
|
|
67
|
+
this.logger?.verbose(`Deleting docs for ${sourceFiles.length} source file(s)`);
|
|
68
|
+
await this.client.delete(this.collection, {
|
|
69
|
+
wait: true,
|
|
70
|
+
filter: {
|
|
71
|
+
must: [{ key: "source_file", match: { any: sourceFiles } }],
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async getCurrentState() {
|
|
76
|
+
const state = new Map();
|
|
77
|
+
let offset;
|
|
78
|
+
do {
|
|
79
|
+
const response = await this.client.scroll(this.collection, {
|
|
80
|
+
with_payload: ["source_file", "commit_hash"],
|
|
81
|
+
with_vector: false,
|
|
82
|
+
limit: SCROLL_PAGE_SIZE,
|
|
83
|
+
offset,
|
|
84
|
+
});
|
|
85
|
+
for (const point of response.points) {
|
|
86
|
+
const payload = point.payload;
|
|
87
|
+
const sourceFile = typeof payload?.source_file === "string" ? payload.source_file : null;
|
|
88
|
+
const commitHash = typeof payload?.commit_hash === "string" ? payload.commit_hash : null;
|
|
89
|
+
if (sourceFile && commitHash) {
|
|
90
|
+
state.set(sourceFile, commitHash);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const raw = response.next_page_offset;
|
|
94
|
+
// ExtendedPointId (string | number) means more pages; anything else stops iteration
|
|
95
|
+
offset =
|
|
96
|
+
typeof raw === "string" || typeof raw === "number" ? raw : undefined;
|
|
97
|
+
} while (offset !== undefined);
|
|
98
|
+
this.logger?.verbose(`getCurrentState: ${state.size} source version(s)`);
|
|
99
|
+
return state;
|
|
100
|
+
}
|
|
101
|
+
async search(queryEmbedding, topK) {
|
|
102
|
+
this.logger?.debug(`Search: topK=${topK}`);
|
|
103
|
+
const results = await this.client.search(this.collection, {
|
|
104
|
+
vector: queryEmbedding,
|
|
105
|
+
limit: topK,
|
|
106
|
+
with_payload: true,
|
|
107
|
+
});
|
|
108
|
+
return results.map((r) => {
|
|
109
|
+
const payload = r.payload;
|
|
110
|
+
return {
|
|
111
|
+
id: String(r.id),
|
|
112
|
+
content: typeof payload?.content === "string" ? payload.content : "",
|
|
113
|
+
metadata: payload?.metadata &&
|
|
114
|
+
typeof payload.metadata === "object" &&
|
|
115
|
+
!Array.isArray(payload.metadata)
|
|
116
|
+
? payload.metadata
|
|
117
|
+
: {},
|
|
118
|
+
similarity: r.score,
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async getIndexStats() {
|
|
123
|
+
return getIndexStats(this.client, this.collection);
|
|
124
|
+
}
|
|
125
|
+
async getQueryPerfReport(timeframeHours) {
|
|
126
|
+
return getQueryPerfReport(this.url, this.collection, timeframeHours);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vivantel/virage-store-qdrant",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Qdrant vector store for @vivantel/rag-core (local and cloud)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"type-check": "tsc --noEmit",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"prepublishOnly": "npm run build",
|
|
27
|
+
"lint": "eslint src/",
|
|
28
|
+
"lint:fix": "eslint src/ --fix",
|
|
29
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
30
|
+
"fix": "npm run lint:fix && npm run format"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"rag",
|
|
34
|
+
"vector-store",
|
|
35
|
+
"qdrant",
|
|
36
|
+
"vector-database",
|
|
37
|
+
"semantic-search"
|
|
38
|
+
],
|
|
39
|
+
"author": "Vivantel",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/vivantel/virage",
|
|
44
|
+
"directory": "packages/virage-store-qdrant"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@qdrant/js-client-rest": "^1.18.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@vivantel/virage-core": "*"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/node": "^25.9.1",
|
|
54
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
55
|
+
"@vivantel/virage-core": "0.2.0",
|
|
56
|
+
"typescript": "^6.0.3",
|
|
57
|
+
"vitest": "^4.1.8"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=18.0.0"
|
|
61
|
+
}
|
|
62
|
+
}
|