@s-hirano-ist/s-scripts 1.5.2 → 1.7.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/dist/find-duplicate-json-articles.js +0 -20
- package/dist/find-duplicate-json-articles.js.map +1 -1
- package/dist/infrastructures/articles-command-repository.d.ts +40 -0
- package/dist/infrastructures/articles-command-repository.d.ts.map +1 -0
- package/dist/infrastructures/articles-command-repository.js +40 -0
- package/dist/infrastructures/articles-command-repository.js.map +1 -0
- package/dist/infrastructures/books-command-repository.d.ts +17 -0
- package/dist/infrastructures/books-command-repository.d.ts.map +1 -0
- package/dist/infrastructures/books-command-repository.js +25 -0
- package/dist/infrastructures/books-command-repository.js.map +1 -0
- package/dist/infrastructures/images-command-repository.d.ts +17 -0
- package/dist/infrastructures/images-command-repository.d.ts.map +1 -0
- package/dist/infrastructures/images-command-repository.js +25 -0
- package/dist/infrastructures/images-command-repository.js.map +1 -0
- package/dist/infrastructures/notes-command-repository.d.ts +17 -0
- package/dist/infrastructures/notes-command-repository.d.ts.map +1 -0
- package/dist/infrastructures/notes-command-repository.js +25 -0
- package/dist/infrastructures/notes-command-repository.js.map +1 -0
- package/dist/rag/chunker.d.ts +10 -0
- package/dist/rag/chunker.d.ts.map +1 -0
- package/dist/rag/chunker.js +188 -0
- package/dist/rag/chunker.js.map +1 -0
- package/dist/rag/config.d.ts +44 -0
- package/dist/rag/config.d.ts.map +1 -0
- package/dist/rag/config.js +34 -0
- package/dist/rag/config.js.map +1 -0
- package/dist/rag/embedding.d.ts +15 -0
- package/dist/rag/embedding.d.ts.map +1 -0
- package/dist/rag/embedding.js +61 -0
- package/dist/rag/embedding.js.map +1 -0
- package/dist/rag/ingest.d.ts +3 -0
- package/dist/rag/ingest.d.ts.map +1 -0
- package/dist/rag/ingest.js +148 -0
- package/dist/rag/ingest.js.map +1 -0
- package/dist/rag/qdrant-client.d.ts +40 -0
- package/dist/rag/qdrant-client.d.ts.map +1 -0
- package/dist/rag/qdrant-client.js +160 -0
- package/dist/rag/qdrant-client.js.map +1 -0
- package/dist/rag/search.d.ts +3 -0
- package/dist/rag/search.d.ts.map +1 -0
- package/dist/rag/search.js +105 -0
- package/dist/rag/search.js.map +1 -0
- package/dist/reset-articles.js +10 -16
- package/dist/reset-articles.js.map +1 -1
- package/dist/reset-books.js +8 -17
- package/dist/reset-books.js.map +1 -1
- package/dist/reset-images.js +8 -17
- package/dist/reset-images.js.map +1 -1
- package/dist/reset-notes.js +8 -17
- package/dist/reset-notes.js.map +1 -1
- package/dist/revert-articles.js +9 -8
- package/dist/revert-articles.js.map +1 -1
- package/dist/revert-books.js +7 -9
- package/dist/revert-books.js.map +1 -1
- package/dist/revert-images.js +7 -9
- package/dist/revert-images.js.map +1 -1
- package/dist/revert-notes.js +7 -9
- package/dist/revert-notes.js.map +1 -1
- package/dist/update-raw-articles.js +1 -1
- package/dist/update-raw-articles.js.map +1 -1
- package/package.json +11 -4
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { pipeline, } from "@huggingface/transformers";
|
|
2
|
+
import { RAG_CONFIG } from "./config.js";
|
|
3
|
+
let embeddingPipeline = null;
|
|
4
|
+
/**
|
|
5
|
+
* Initialize the embedding model (lazy loading)
|
|
6
|
+
*/
|
|
7
|
+
async function getEmbeddingPipeline() {
|
|
8
|
+
if (!embeddingPipeline) {
|
|
9
|
+
console.log(`Loading embedding model: ${RAG_CONFIG.embedding.model}...`);
|
|
10
|
+
embeddingPipeline = (await pipeline("feature-extraction", RAG_CONFIG.embedding.model, { dtype: "fp32" }));
|
|
11
|
+
console.log("Embedding model loaded successfully.");
|
|
12
|
+
}
|
|
13
|
+
return embeddingPipeline;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Generate embedding for a single text
|
|
17
|
+
* @param text - Input text to embed
|
|
18
|
+
* @param isQuery - Whether this is a query (vs passage)
|
|
19
|
+
* @returns Embedding vector
|
|
20
|
+
*/
|
|
21
|
+
export async function embed(text, isQuery = false) {
|
|
22
|
+
const pipe = await getEmbeddingPipeline();
|
|
23
|
+
// E5 models require prefixes
|
|
24
|
+
const prefix = isQuery
|
|
25
|
+
? RAG_CONFIG.embedding.prefix.query
|
|
26
|
+
: RAG_CONFIG.embedding.prefix.passage;
|
|
27
|
+
const prefixedText = prefix + text;
|
|
28
|
+
const output = await pipe(prefixedText, {
|
|
29
|
+
pooling: "mean",
|
|
30
|
+
normalize: true,
|
|
31
|
+
});
|
|
32
|
+
// Convert to array
|
|
33
|
+
return Array.from(output.data);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Generate embeddings for multiple texts in batch
|
|
37
|
+
* @param texts - Array of input texts
|
|
38
|
+
* @param isQuery - Whether these are queries (vs passages)
|
|
39
|
+
* @returns Array of embedding vectors
|
|
40
|
+
*/
|
|
41
|
+
export async function embedBatch(texts, isQuery = false) {
|
|
42
|
+
const pipe = await getEmbeddingPipeline();
|
|
43
|
+
const prefix = isQuery
|
|
44
|
+
? RAG_CONFIG.embedding.prefix.query
|
|
45
|
+
: RAG_CONFIG.embedding.prefix.passage;
|
|
46
|
+
const prefixedTexts = texts.map((t) => prefix + t);
|
|
47
|
+
const outputs = await pipe(prefixedTexts, {
|
|
48
|
+
pooling: "mean",
|
|
49
|
+
normalize: true,
|
|
50
|
+
});
|
|
51
|
+
// outputs.data is a flat Float32Array, need to reshape
|
|
52
|
+
const embeddings = [];
|
|
53
|
+
const dim = RAG_CONFIG.qdrant.vectorSize;
|
|
54
|
+
for (let i = 0; i < texts.length; i++) {
|
|
55
|
+
const start = i * dim;
|
|
56
|
+
const end = start + dim;
|
|
57
|
+
embeddings.push(Array.from(outputs.data.slice(start, end)));
|
|
58
|
+
}
|
|
59
|
+
return embeddings;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=embedding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding.js","sourceRoot":"","sources":["../../src/rag/embedding.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,QAAQ,GACR,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,IAAI,iBAAiB,GAAqC,IAAI,CAAC;AAE/D;;GAEG;AACH,KAAK,UAAU,oBAAoB;IAClC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,4BAA4B,UAAU,CAAC,SAAS,CAAC,KAAK,KAAK,CAAC,CAAC;QACzE,iBAAiB,GAAG,CAAC,MAAM,QAAQ,CAClC,oBAAoB,EACpB,UAAU,CAAC,SAAS,CAAC,KAAK,EAC1B,EAAE,KAAK,EAAE,MAAM,EAAE,CACjB,CAAyC,CAAC;QAC3C,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,iBAAiB,CAAC;AAC1B,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,IAAY,EAAE,OAAO,GAAG,KAAK;IACxD,MAAM,IAAI,GAAG,MAAM,oBAAoB,EAAE,CAAC;IAE1C,6BAA6B;IAC7B,MAAM,MAAM,GAAG,OAAO;QACrB,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK;QACnC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC;IAEvC,MAAM,YAAY,GAAG,MAAM,GAAG,IAAI,CAAC;IAEnC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE;QACvC,OAAO,EAAE,MAAM;QACf,SAAS,EAAE,IAAI;KACf,CAAC,CAAC;IAEH,mBAAmB;IACnB,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAoB,CAAC,CAAC;AAChD,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC/B,KAAe,EACf,OAAO,GAAG,KAAK;IAEf,MAAM,IAAI,GAAG,MAAM,oBAAoB,EAAE,CAAC;IAE1C,MAAM,MAAM,GAAG,OAAO;QACrB,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK;QACnC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC;IAEvC,MAAM,aAAa,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAEnD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE;QACzC,OAAO,EAAE,MAAM;QACf,SAAS,EAAE,IAAI;KACf,CAAC,CAAC;IAEH,uDAAuD;IACvD,MAAM,UAAU,GAAe,EAAE,CAAC;IAClC,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,UAAU,CAAC;IAEzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,CAAC,GAAG,GAAG,CAAC;QACtB,MAAM,GAAG,GAAG,KAAK,GAAG,GAAG,CAAC;QACxB,UAAU,CAAC,IAAI,CACd,KAAK,CAAC,IAAI,CAAE,OAAO,CAAC,IAAqB,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAC5D,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../src/rag/ingest.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { glob } from "glob";
|
|
4
|
+
import { parseJsonArticle, parseMarkdown } from "./chunker.js";
|
|
5
|
+
import { RAG_CONFIG } from "./config.js";
|
|
6
|
+
import { embedBatch } from "./embedding.js";
|
|
7
|
+
import { ensureCollection, getCollectionStats, getExistingHashes, upsertPoints, } from "./qdrant-client.js";
|
|
8
|
+
const BATCH_SIZE = 20;
|
|
9
|
+
const MAX_RETRIES = 3;
|
|
10
|
+
const RETRY_DELAY_MS = 2000;
|
|
11
|
+
async function sleep(ms) {
|
|
12
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
13
|
+
}
|
|
14
|
+
async function withRetry(fn, retries = MAX_RETRIES) {
|
|
15
|
+
for (let i = 0; i < retries; i++) {
|
|
16
|
+
try {
|
|
17
|
+
return await fn();
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (i === retries - 1)
|
|
21
|
+
throw error;
|
|
22
|
+
console.log(` Retry ${i + 1}/${retries} after error...`);
|
|
23
|
+
await sleep(RETRY_DELAY_MS);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
throw new Error("Unreachable");
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* List all files to process
|
|
30
|
+
*/
|
|
31
|
+
async function listFiles() {
|
|
32
|
+
const files = [];
|
|
33
|
+
// JSON files
|
|
34
|
+
const jsonFiles = await glob(RAG_CONFIG.paths.json);
|
|
35
|
+
for (const path of jsonFiles) {
|
|
36
|
+
files.push({ path, type: "json" });
|
|
37
|
+
}
|
|
38
|
+
// Markdown files (supports array of patterns)
|
|
39
|
+
const mdPatterns = Array.isArray(RAG_CONFIG.paths.markdown)
|
|
40
|
+
? RAG_CONFIG.paths.markdown
|
|
41
|
+
: [RAG_CONFIG.paths.markdown];
|
|
42
|
+
for (const pattern of mdPatterns) {
|
|
43
|
+
const mdFiles = await glob(pattern);
|
|
44
|
+
for (const path of mdFiles) {
|
|
45
|
+
files.push({ path, type: "markdown" });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return files;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Parse a single file into chunks
|
|
52
|
+
*/
|
|
53
|
+
function parseFile(file) {
|
|
54
|
+
const content = readFileSync(file.path, "utf-8");
|
|
55
|
+
if (file.type === "json") {
|
|
56
|
+
return parseJsonArticle(file.path, content);
|
|
57
|
+
}
|
|
58
|
+
return parseMarkdown(file.path, content);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Ingest all documents
|
|
62
|
+
*/
|
|
63
|
+
async function ingest() {
|
|
64
|
+
console.log("Starting ingest...\n");
|
|
65
|
+
// Ensure collection exists
|
|
66
|
+
await ensureCollection();
|
|
67
|
+
// Get initial stats
|
|
68
|
+
const initialStats = await getCollectionStats();
|
|
69
|
+
console.log(`Initial points count: ${initialStats.pointsCount}\n`);
|
|
70
|
+
// List all files
|
|
71
|
+
const files = await listFiles();
|
|
72
|
+
console.log(`Found ${files.length} files to process`);
|
|
73
|
+
console.log(` - JSON: ${files.filter((f) => f.type === "json").length}`);
|
|
74
|
+
console.log(` - Markdown: ${files.filter((f) => f.type === "markdown").length}\n`);
|
|
75
|
+
// Parse all files into chunks
|
|
76
|
+
console.log("Parsing files...");
|
|
77
|
+
const allChunks = [];
|
|
78
|
+
for (const file of files) {
|
|
79
|
+
try {
|
|
80
|
+
const chunks = parseFile(file);
|
|
81
|
+
allChunks.push(...chunks);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
console.error(`Error parsing ${file.path}:`, error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
console.log(`Total chunks: ${allChunks.length}\n`);
|
|
88
|
+
// Get existing hashes for change detection
|
|
89
|
+
console.log("Checking for changes...");
|
|
90
|
+
const chunkIds = allChunks.map((c) => c.chunk_id);
|
|
91
|
+
const existingHashes = await getExistingHashes(chunkIds);
|
|
92
|
+
// Filter to only changed chunks
|
|
93
|
+
const changedChunks = allChunks.filter((chunk) => {
|
|
94
|
+
const existingHash = existingHashes.get(chunk.chunk_id);
|
|
95
|
+
return existingHash !== chunk.content_hash;
|
|
96
|
+
});
|
|
97
|
+
console.log(`Changed chunks: ${changedChunks.length}`);
|
|
98
|
+
console.log(`Skipped (unchanged): ${allChunks.length - changedChunks.length}\n`);
|
|
99
|
+
if (changedChunks.length === 0) {
|
|
100
|
+
console.log("No changes detected. Done!");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// Generate embeddings and upsert in batches
|
|
104
|
+
console.log("Generating embeddings and upserting...");
|
|
105
|
+
let processed = 0;
|
|
106
|
+
for (let i = 0; i < changedChunks.length; i += BATCH_SIZE) {
|
|
107
|
+
const batch = changedChunks.slice(i, i + BATCH_SIZE);
|
|
108
|
+
const texts = batch.map((c) => c.text);
|
|
109
|
+
// Generate embeddings
|
|
110
|
+
const embeddings = await embedBatch(texts, false);
|
|
111
|
+
// Prepare points
|
|
112
|
+
const points = batch.map((chunk, idx) => ({
|
|
113
|
+
id: chunk.chunk_id,
|
|
114
|
+
vector: embeddings[idx],
|
|
115
|
+
payload: chunk,
|
|
116
|
+
}));
|
|
117
|
+
// Upsert to Qdrant with retry
|
|
118
|
+
await withRetry(() => upsertPoints(points));
|
|
119
|
+
processed += batch.length;
|
|
120
|
+
console.log(` Progress: ${processed}/${changedChunks.length}`);
|
|
121
|
+
// Small delay between batches to avoid overwhelming Qdrant
|
|
122
|
+
await sleep(100);
|
|
123
|
+
}
|
|
124
|
+
// Get final stats
|
|
125
|
+
const finalStats = await getCollectionStats();
|
|
126
|
+
console.log(`\nFinal points count: ${finalStats.pointsCount}`);
|
|
127
|
+
console.log("Ingest completed successfully!");
|
|
128
|
+
}
|
|
129
|
+
async function main() {
|
|
130
|
+
const env = {
|
|
131
|
+
QDRANT_URL: process.env.QDRANT_URL,
|
|
132
|
+
};
|
|
133
|
+
if (!env.QDRANT_URL) {
|
|
134
|
+
throw new Error("QDRANT_URL environment variable is required.");
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
await ingest();
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
console.error("❌ エラーが発生しました:", error);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
main().catch((error) => {
|
|
145
|
+
console.error(error);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
});
|
|
148
|
+
//# sourceMappingURL=ingest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../src/rag/ingest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { QdrantClient } from "@qdrant/js-client-rest";
|
|
2
|
+
import { type QdrantPayload, type SearchResult } from "./config.js";
|
|
3
|
+
/**
|
|
4
|
+
* Get or create Qdrant client
|
|
5
|
+
*/
|
|
6
|
+
export declare function getQdrantClient(): QdrantClient;
|
|
7
|
+
/**
|
|
8
|
+
* Create collection if not exists
|
|
9
|
+
*/
|
|
10
|
+
export declare function ensureCollection(): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Upsert points to Qdrant
|
|
13
|
+
*/
|
|
14
|
+
export declare function upsertPoints(points: {
|
|
15
|
+
id: string;
|
|
16
|
+
vector: number[];
|
|
17
|
+
payload: QdrantPayload;
|
|
18
|
+
}[]): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Get existing content hashes for a set of chunk IDs
|
|
21
|
+
*/
|
|
22
|
+
export declare function getExistingHashes(chunkIds: string[]): Promise<Map<string, string>>;
|
|
23
|
+
/**
|
|
24
|
+
* Search for similar documents
|
|
25
|
+
*/
|
|
26
|
+
export declare function search(queryVector: number[], options?: {
|
|
27
|
+
topK?: number;
|
|
28
|
+
filter?: {
|
|
29
|
+
type?: "markdown_note" | "bookmark_json";
|
|
30
|
+
top_heading?: string;
|
|
31
|
+
};
|
|
32
|
+
}): Promise<SearchResult[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Get collection stats
|
|
35
|
+
*/
|
|
36
|
+
export declare function getCollectionStats(): Promise<{
|
|
37
|
+
pointsCount: number;
|
|
38
|
+
status: string;
|
|
39
|
+
}>;
|
|
40
|
+
//# sourceMappingURL=qdrant-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qdrant-client.d.ts","sourceRoot":"","sources":["../../src/rag/qdrant-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,KAAK,aAAa,EAAc,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhF;;GAEG;AACH,wBAAgB,eAAe,IAAI,YAAY,CAgB9C;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAmBtD;AAED;;GAEG;AACH,wBAAsB,YAAY,CACjC,MAAM,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,aAAa,CAAA;CAAE,EAAE,GAChE,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACtC,QAAQ,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CA4B9B;AAED;;GAEG;AACH,wBAAsB,MAAM,CAC3B,WAAW,EAAE,MAAM,EAAE,EACrB,OAAO,GAAE;IACR,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,eAAe,GAAG,eAAe,CAAC;QACzC,WAAW,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACG,GACJ,OAAO,CAAC,YAAY,EAAE,CAAC,CA6CzB;AAED;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CACf,CAAC,CAgBD"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { QdrantClient } from "@qdrant/js-client-rest";
|
|
2
|
+
import { RAG_CONFIG } from "./config.js";
|
|
3
|
+
let client = null;
|
|
4
|
+
/**
|
|
5
|
+
* Get or create Qdrant client
|
|
6
|
+
*/
|
|
7
|
+
export function getQdrantClient() {
|
|
8
|
+
if (!client) {
|
|
9
|
+
const url = process.env.QDRANT_URL;
|
|
10
|
+
const apiKey = process.env.QDRANT_API_KEY;
|
|
11
|
+
if (!url) {
|
|
12
|
+
throw new Error("QDRANT_URL environment variable is required");
|
|
13
|
+
}
|
|
14
|
+
client = new QdrantClient({
|
|
15
|
+
url,
|
|
16
|
+
apiKey,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return client;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create collection if not exists
|
|
23
|
+
*/
|
|
24
|
+
export async function ensureCollection() {
|
|
25
|
+
const qdrant = getQdrantClient();
|
|
26
|
+
const { collectionName, vectorSize, distance } = RAG_CONFIG.qdrant;
|
|
27
|
+
const collections = await qdrant.getCollections();
|
|
28
|
+
const exists = collections.collections.some((c) => c.name === collectionName);
|
|
29
|
+
if (!exists) {
|
|
30
|
+
console.log(`Creating collection: ${collectionName}`);
|
|
31
|
+
await qdrant.createCollection(collectionName, {
|
|
32
|
+
vectors: {
|
|
33
|
+
size: vectorSize,
|
|
34
|
+
distance,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
console.log(`Collection ${collectionName} created successfully.`);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
console.log(`Collection ${collectionName} already exists.`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Upsert points to Qdrant
|
|
45
|
+
*/
|
|
46
|
+
export async function upsertPoints(points) {
|
|
47
|
+
const qdrant = getQdrantClient();
|
|
48
|
+
const { collectionName } = RAG_CONFIG.qdrant;
|
|
49
|
+
// Qdrant requires numeric or UUID IDs, so we hash the chunk_id
|
|
50
|
+
const qdrantPoints = points.map((p) => ({
|
|
51
|
+
id: hashToUint(p.id),
|
|
52
|
+
vector: p.vector,
|
|
53
|
+
payload: p.payload,
|
|
54
|
+
}));
|
|
55
|
+
await qdrant.upsert(collectionName, {
|
|
56
|
+
wait: true,
|
|
57
|
+
points: qdrantPoints,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get existing content hashes for a set of chunk IDs
|
|
62
|
+
*/
|
|
63
|
+
export async function getExistingHashes(chunkIds) {
|
|
64
|
+
const qdrant = getQdrantClient();
|
|
65
|
+
const { collectionName } = RAG_CONFIG.qdrant;
|
|
66
|
+
const hashMap = new Map();
|
|
67
|
+
if (chunkIds.length === 0)
|
|
68
|
+
return hashMap;
|
|
69
|
+
// Convert chunk IDs to numeric IDs
|
|
70
|
+
const numericIds = chunkIds.map((id) => hashToUint(id));
|
|
71
|
+
try {
|
|
72
|
+
const result = await qdrant.retrieve(collectionName, {
|
|
73
|
+
ids: numericIds,
|
|
74
|
+
with_payload: ["chunk_id", "content_hash"],
|
|
75
|
+
});
|
|
76
|
+
for (const point of result) {
|
|
77
|
+
const payload = point.payload;
|
|
78
|
+
if (payload?.chunk_id && payload?.content_hash) {
|
|
79
|
+
hashMap.set(payload.chunk_id, payload.content_hash);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Collection might not exist or be empty
|
|
85
|
+
}
|
|
86
|
+
return hashMap;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Search for similar documents
|
|
90
|
+
*/
|
|
91
|
+
export async function search(queryVector, options = {}) {
|
|
92
|
+
const qdrant = getQdrantClient();
|
|
93
|
+
const { collectionName } = RAG_CONFIG.qdrant;
|
|
94
|
+
const { topK = 10, filter } = options;
|
|
95
|
+
// Build filter conditions
|
|
96
|
+
const filterConditions = [];
|
|
97
|
+
if (filter?.type) {
|
|
98
|
+
filterConditions.push({
|
|
99
|
+
key: "type",
|
|
100
|
+
match: { value: filter.type },
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (filter?.top_heading) {
|
|
104
|
+
filterConditions.push({
|
|
105
|
+
key: "top_heading",
|
|
106
|
+
match: { value: filter.top_heading },
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
const result = await qdrant.search(collectionName, {
|
|
110
|
+
vector: queryVector,
|
|
111
|
+
limit: topK,
|
|
112
|
+
with_payload: true,
|
|
113
|
+
filter: filterConditions.length > 0 ? { must: filterConditions } : undefined,
|
|
114
|
+
});
|
|
115
|
+
return result.map((r) => {
|
|
116
|
+
const payload = r.payload;
|
|
117
|
+
return {
|
|
118
|
+
score: r.score,
|
|
119
|
+
text: payload.text,
|
|
120
|
+
title: payload.title,
|
|
121
|
+
url: payload.url,
|
|
122
|
+
heading_path: payload.heading_path,
|
|
123
|
+
type: payload.type,
|
|
124
|
+
doc_id: payload.doc_id,
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get collection stats
|
|
130
|
+
*/
|
|
131
|
+
export async function getCollectionStats() {
|
|
132
|
+
const qdrant = getQdrantClient();
|
|
133
|
+
const { collectionName } = RAG_CONFIG.qdrant;
|
|
134
|
+
try {
|
|
135
|
+
const info = await qdrant.getCollection(collectionName);
|
|
136
|
+
return {
|
|
137
|
+
pointsCount: info.points_count ?? 0,
|
|
138
|
+
status: info.status,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return {
|
|
143
|
+
pointsCount: 0,
|
|
144
|
+
status: "not_found",
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Hash string to unsigned integer (for Qdrant point ID)
|
|
150
|
+
*/
|
|
151
|
+
function hashToUint(str) {
|
|
152
|
+
let hash = 0;
|
|
153
|
+
for (let i = 0; i < str.length; i++) {
|
|
154
|
+
const char = str.charCodeAt(i);
|
|
155
|
+
hash = (hash << 5) - hash + char;
|
|
156
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
157
|
+
}
|
|
158
|
+
return Math.abs(hash);
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=qdrant-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qdrant-client.js","sourceRoot":"","sources":["../../src/rag/qdrant-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/rag/search.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { embed } from "./embedding.js";
|
|
3
|
+
import { getCollectionStats, search } from "./qdrant-client.js";
|
|
4
|
+
/**
|
|
5
|
+
* Search for documents matching a query
|
|
6
|
+
*/
|
|
7
|
+
async function runSearch() {
|
|
8
|
+
// Parse command line arguments
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
if (args.length === 0) {
|
|
11
|
+
console.log("Usage: rag-search <query> [options]");
|
|
12
|
+
console.log("");
|
|
13
|
+
console.log("Options:");
|
|
14
|
+
console.log(" --top-k <number> Number of results (default: 5)");
|
|
15
|
+
console.log(" --type <type> Filter by type: markdown_note | bookmark_json");
|
|
16
|
+
console.log(" --heading <heading> Filter by top_heading");
|
|
17
|
+
console.log("");
|
|
18
|
+
console.log("Examples:");
|
|
19
|
+
console.log(' rag-search "ルネサンス 遠近法"');
|
|
20
|
+
console.log(' rag-search "AI 脆弱性" --type bookmark_json');
|
|
21
|
+
console.log(' rag-search "React" --heading javascript --top-k 10');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
// Parse options
|
|
25
|
+
let query = "";
|
|
26
|
+
let topK = 5;
|
|
27
|
+
let filterType;
|
|
28
|
+
let filterHeading;
|
|
29
|
+
for (let i = 0; i < args.length; i++) {
|
|
30
|
+
if (args[i] === "--top-k" && args[i + 1]) {
|
|
31
|
+
topK = Number.parseInt(args[i + 1], 10);
|
|
32
|
+
i++;
|
|
33
|
+
}
|
|
34
|
+
else if (args[i] === "--type" && args[i + 1]) {
|
|
35
|
+
filterType = args[i + 1];
|
|
36
|
+
i++;
|
|
37
|
+
}
|
|
38
|
+
else if (args[i] === "--heading" && args[i + 1]) {
|
|
39
|
+
filterHeading = args[i + 1];
|
|
40
|
+
i++;
|
|
41
|
+
}
|
|
42
|
+
else if (!args[i].startsWith("--")) {
|
|
43
|
+
query = args[i];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!query) {
|
|
47
|
+
console.error("Error: Query is required");
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
// Check collection status
|
|
51
|
+
const stats = await getCollectionStats();
|
|
52
|
+
if (stats.status === "not_found") {
|
|
53
|
+
console.error("Error: Collection not found. Run ingest first.");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
console.log(`Searching for: "${query}"`);
|
|
57
|
+
console.log(`Collection has ${stats.pointsCount} points\n`);
|
|
58
|
+
// Generate query embedding
|
|
59
|
+
console.log("Generating query embedding...");
|
|
60
|
+
const queryVector = await embed(query, true);
|
|
61
|
+
// Search
|
|
62
|
+
console.log("Searching...\n");
|
|
63
|
+
const results = await search(queryVector, {
|
|
64
|
+
topK,
|
|
65
|
+
filter: {
|
|
66
|
+
type: filterType,
|
|
67
|
+
top_heading: filterHeading,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
// Display results
|
|
71
|
+
console.log(`Found ${results.length} results:\n`);
|
|
72
|
+
console.log("=".repeat(80));
|
|
73
|
+
for (let i = 0; i < results.length; i++) {
|
|
74
|
+
const r = results[i];
|
|
75
|
+
console.log(`\n[${i + 1}] Score: ${r.score.toFixed(4)}`);
|
|
76
|
+
console.log(` Title: ${r.title}`);
|
|
77
|
+
console.log(` Type: ${r.type}`);
|
|
78
|
+
console.log(` Path: ${r.heading_path.join(" > ")}`);
|
|
79
|
+
if (r.url) {
|
|
80
|
+
console.log(` URL: ${r.url}`);
|
|
81
|
+
}
|
|
82
|
+
console.log(` Text: ${r.text.slice(0, 200)}${r.text.length > 200 ? "..." : ""}`);
|
|
83
|
+
console.log("-".repeat(80));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function main() {
|
|
87
|
+
const env = {
|
|
88
|
+
QDRANT_URL: process.env.QDRANT_URL,
|
|
89
|
+
};
|
|
90
|
+
if (!env.QDRANT_URL) {
|
|
91
|
+
throw new Error("QDRANT_URL environment variable is required.");
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
await runSearch();
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
console.error("❌ エラーが発生しました:", error);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
main().catch((error) => {
|
|
102
|
+
console.error(error);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/rag/search.ts"],"names":[],"mappings":"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"}
|
package/dist/reset-articles.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { ArticlesBatchDomainService } from "@s-hirano-ist/s-core/articles";
|
|
3
|
+
import { makeUserId } from "@s-hirano-ist/s-core/common";
|
|
3
4
|
import { createPushoverService } from "@s-hirano-ist/s-notification";
|
|
5
|
+
import { createArticlesCommandRepository } from "./infrastructures/articles-command-repository.js";
|
|
4
6
|
async function main() {
|
|
5
7
|
const env = {
|
|
6
8
|
DATABASE_URL: process.env.DATABASE_URL,
|
|
@@ -23,23 +25,15 @@ async function main() {
|
|
|
23
25
|
appToken: env.PUSHOVER_APP_TOKEN ?? "",
|
|
24
26
|
});
|
|
25
27
|
const userId = makeUserId(env.USERNAME_TO_EXPORT ?? "");
|
|
26
|
-
const UNEXPORTED = makeUnexportedStatus();
|
|
27
|
-
const LAST_UPDATED = makeLastUpdatedStatus();
|
|
28
|
-
const EXPORTED = "EXPORTED";
|
|
29
28
|
async function resetArticles() {
|
|
30
29
|
await prisma.$transaction(async (tx) => {
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
console.log(
|
|
37
|
-
|
|
38
|
-
const result = await tx.article.updateMany({
|
|
39
|
-
where: { userId, status: UNEXPORTED },
|
|
40
|
-
data: { status: LAST_UPDATED },
|
|
41
|
-
});
|
|
42
|
-
console.log(`💾 ${result.count}件の記事をリセットしました`);
|
|
30
|
+
// DDD: Create repository and domain service with transaction client
|
|
31
|
+
const commandRepository = createArticlesCommandRepository(tx);
|
|
32
|
+
const batchService = new ArticlesBatchDomainService(commandRepository);
|
|
33
|
+
// Execute batch reset through domain service
|
|
34
|
+
const result = await batchService.resetArticles(userId);
|
|
35
|
+
console.log(`💾 LAST_UPDATEDの記事をEXPORTEDに変更しました(${result.finalized.count}件)`);
|
|
36
|
+
console.log(`💾 ${result.marked.count}件の記事をリセットしました`);
|
|
43
37
|
});
|
|
44
38
|
}
|
|
45
39
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reset-articles.js","sourceRoot":"","sources":["../src/reset-articles.ts"],"names":[],"mappings":";AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"reset-articles.js","sourceRoot":"","sources":["../src/reset-articles.ts"],"names":[],"mappings":"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"}
|