From dd29bd26500c299199f49aed2e4f5dd599d758db Mon Sep 17 00:00:00 2001 From: Huakun Shen Date: Mon, 27 Jan 2025 21:55:59 -0500 Subject: [PATCH] feat: RAG chat implemented --- .github/workflows/jsr-publish.yml | 1 + .vscode/settings.json | 3 + README.md | 26 +++++++ bun.lockb | Bin 218020 -> 232834 bytes deno-src/bucket.ts | 103 +++++++++++++++++++----- deno-src/dev.ts | 77 ------------------ deno-src/index.ts | 45 +---------- deno-src/main.ts | 96 +++++++++++------------ jsr.json | 7 +- package.json | 19 ++++- src/api.types.ts | 9 ++- src/lib/components/DatabaseList.svelte | 7 +- src/lib/components/TauriLink.svelte | 16 ++++ src/lib/components/app-sidebar.svelte | 9 +-- src/lib/deno.ts | 6 +- src/routes/about/+page.svelte | 35 ++++++--- src/routes/database/[id]/+page.svelte | 104 ++++++++++++++++++------- src/routes/database/[id]/+page.ts | 2 + svelte.config.js | 4 +- 19 files changed, 329 insertions(+), 240 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 deno-src/dev.ts create mode 100644 src/lib/components/TauriLink.svelte diff --git a/.github/workflows/jsr-publish.yml b/.github/workflows/jsr-publish.yml index 578212b..c6a7fc8 100644 --- a/.github/workflows/jsr-publish.yml +++ b/.github/workflows/jsr-publish.yml @@ -22,4 +22,5 @@ jobs: run: | bun install bun run build + bunx kksh@latest verify --publish bunx jsr publish --allow-slow-types diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b943dbc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "deno.enable": true +} \ No newline at end of file diff --git a/README.md b/README.md index dc8623c..62eeafc 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,29 @@ RAG means Retrieval-Augmented Generation. This extension is a local RAG app, that allows you to index a local directory of files and search them using a LLM model. + +If you don't know what RAG is, see [Wikipedia: RAG](https://en.wikipedia.org/wiki/Retrieval-augmented_generation) + +Basically, this extension allows you index local files and directories and search them using a LLM model. + +For now, only text files and pdf files are supported. + +The following file extensions are supported for `Add Files`: + +- `.txt` +- `.pdf` +- `.md` +- `.mdx` + +`.pdf` is not supported yet for `Add Directory`. + +> [!CAUTION] +> If you want other file extensions to be supported, please send a issue to the repository. +> I will add options to let user add dynamic file extensions if there are people using this extension. + +This is to prevent indexing other files you may not want to index, like lock files. + +## Sample Images + +![](https://i.imgur.com/SMwsks7.png) +![](https://i.imgur.com/KPkwhMN.png) diff --git a/bun.lockb b/bun.lockb index 72d7a942270ddece882746059b4458b767de21e6..0ac6552bf406dadbcdf5fa7a4583ab92ba89bcbd 100755 GIT binary patch delta 45888 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
I;fj2-Q_*((iKw-cJ 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 delta 37897 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 bJHe%D;mlDkqdmlLbzP1Wel@{mMYaD20(bZ> diff --git a/deno-src/bucket.ts b/deno-src/bucket.ts index 6039bf6..9f3031c 100644 --- a/deno-src/bucket.ts +++ b/deno-src/bucket.ts @@ -1,17 +1,22 @@ import { FaissStore } from '@langchain/community/vectorstores/faiss'; -import { OpenAIEmbeddings } from '@langchain/openai'; +import { ChatOpenAI, OpenAIEmbeddings } from '@langchain/openai'; import * as v from 'valibot'; import * as path from 'jsr:@std/path'; import { existsSync, readdirSync } from 'node:fs'; import { Document } from '@langchain/core/documents'; import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters'; import { DirectoryLoader } from 'langchain/document_loaders/fs/directory'; -import { JSONLoader, JSONLinesLoader } from 'langchain/document_loaders/fs/json'; import { TextLoader } from 'langchain/document_loaders/fs/text'; import { computeSha256FromText } from './crypto.ts'; import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf'; +import { DenoAPI } from '../src/api.types.ts'; +import { txtExts } from './constants.ts'; +import { AIMessageChunk } from '@langchain/core/messages'; export const embeddings = new OpenAIEmbeddings({ + // configuration: { + // baseURL: 'https://api.deepseek.com' + // }, model: 'text-embedding-3-large' }); @@ -27,8 +32,8 @@ export async function getDocsFromDirectory(directoryPath: string): Promise new JSONLoader(path, '/texts'), - '.jsonl': (path) => new JSONLinesLoader(path, '/html'), + // '.json': (path) => new JSONLoader(path, '/texts'), + // '.jsonl': (path) => new JSONLinesLoader(path, '/html'), '.txt': (path) => new TextLoader(path), '.md': (path) => new TextLoader(path), '.mdx': (path) => new TextLoader(path) @@ -38,23 +43,22 @@ export async function getDocsFromDirectory(directoryPath: string): Promise = new Set(); - constructor( - readonly bucketDir: string, - readonly bucketName: string - ) { + async init(bucketDir: string, bucketName: string) { + this.bucketDir = bucketDir; + this.bucketName = bucketName; this.bucketPath = path.join(this.bucketDir, this.bucketName); this.faissStorePath = path.join(this.bucketPath, 'faiss-store'); this.metadataPath = path.join(this.bucketPath, 'metadata.json'); - } - async init() { if (!existsSync(this.bucketPath)) { Deno.mkdirSync(this.bucketPath, { recursive: true }); } @@ -69,9 +73,6 @@ export class Bucket { } this.updateMetadata(); this._vectorStore = await this.getVectorStore(); - // if (this._vectorStore) { - // await this._vectorStore.save(this.faissStorePath); - // } } updateMetadata() { @@ -160,7 +161,7 @@ export class Bucket { this.updateSha256(docs); console.error('Updated sha256', this.filesSha256.size); // await this.addDocuments(fileteredDocs); - return this.vectorStore.addDocuments(fileteredDocs).catch((err) => { + await this.vectorStore.addDocuments(fileteredDocs).catch((err) => { console.error('Error adding documents', err); }); } @@ -184,4 +185,70 @@ export class Bucket { this.updateSha256(docs); await this.addDocuments(fileteredDocs); } + + async retrieve(query: string) { + const retriever = this.vectorStore.asRetriever(); + const docs = await retriever.invoke(query); + const docsText = docs.map((d) => d.pageContent).join(''); + return docsText; + } + + async query(question: string) { + const docsText = await this.retrieve(question); + const systemPrompt = `You are an assistant for question-answering tasks. +Use the following pieces of retrieved context to answer the question. +If you don't know the answer, just say that you don't know. +Use three sentences maximum and keep the answer concise. +Context: {context}:`; + + // Populate the system prompt with the retrieved context + const systemPromptFmt = systemPrompt.replace('{context}', docsText); + + // Create a model + const model = new ChatOpenAI({ + model: 'gpt-4o', + temperature: 0 + }); + + // Generate a response + const ans: AIMessageChunk = await model.invoke([ + { + role: 'system', + content: systemPromptFmt + }, + { + role: 'user', + content: question + } + ]); + return ans.content.toString(); + } + + async indexFiles(files: string[]) { + console.error('Indexing files', files); + for (const file of files) { + if (!existsSync(file)) { + throw new Error(`File ${file} does not exist`); + } + // check if file is directory + const stats = Deno.statSync(file); + console.error('Indexing file', file, 'stats.isFile', stats.isFile); + if (stats.isFile) { + const ext = path.extname(file); + if (txtExts.includes(ext)) { + console.error('Adding text file 1', file); + await this.addTextFile(file); + console.error('Finished adding text file', file); + } else if (ext === '.pdf') { + console.error('Adding pdf file', file); + await this.addPDF(file); + } else { + throw new Error(`Unsupported file type: ${ext}`); + } + } else { + console.error('Adding directory', file); + await this.addDirectory(file); + } + } + } } diff --git a/deno-src/dev.ts b/deno-src/dev.ts deleted file mode 100644 index f9c92d6..0000000 --- a/deno-src/dev.ts +++ /dev/null @@ -1,77 +0,0 @@ -// import { FaissStore } from '@langchain/community/vectorstores/faiss'; -// import { Bucket, embeddings, getDocsFromDirectory } from './bucket.ts'; -// import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters'; -// import { DirectoryLoader } from 'langchain/document_loaders/fs/directory'; -// import { JSONLoader, JSONLinesLoader } from 'langchain/document_loaders/fs/json'; -// import { TextLoader } from 'langchain/document_loaders/fs/text'; -// import { OpenAIEmbeddings } from '@langchain/openai'; -import { existsSync } from 'node:fs'; -// import path from 'path'; -import { txtExts } from './constants.ts'; -import { Bucket } from './bucket.ts'; -import path from 'node:path'; - -async function indexFiles(bucketName: string, files: string[]): Promise { - const bucket = new Bucket('./store', bucketName); - // const bucket = new Bucket(extensionSupportPath, bucketName); - console.error('bucket path', bucket.bucketPath); - console.error('files', files); - await bucket.init(); - for (const file of files) { - if (!existsSync(file)) { - throw new Error(`File ${file} does not exist`); - } - console.error('file', file); - // check if file is directory - const stats = Deno.statSync(file); - if (stats.isFile) { - const ext = path.extname(file); - if (txtExts.includes(ext)) { - console.error('Adding text file', file); - await bucket.addTextFile(file); - console.error('Finished adding text file', file); - } else if (ext === '.pdf') { - console.error('Adding pdf file', file); - await bucket.addPDF(file); - } else if (stats.isDirectory) { - console.error('Adding directory', file); - await bucket.addDirectory(file); - } else { - throw new Error(`Unsupported file type: ${ext}`); - } - } - } - await bucket.save(); -} -indexFiles('Kunkun Docs', ['/Users/hk/Dev/kunkun-docs/src/content/docs/developer/DX.mdx']); - -// const bucket = new Bucket( -// '/Users/hk/Dev/kunkun-extension-repos/kunkun-ext-rag/extensions_support', -// 'Kunkun Docs' -// ); -// await bucket.init(); -// const files = ['/Users/hk/Dev/kunkun-docs/src/content/docs/developer/manifest.mdx']; -// for (const file of files) { -// if (!existsSync(file)) { -// throw new Error(`File ${file} does not exist`); -// } -// console.error('file', file); -// // check if file is directory -// const stats = Deno.statSync(file); -// if (stats.isFile) { -// const ext = path.extname(file); -// if (txtExts.includes(ext)) { -// console.error('Adding text file', file); -// await bucket.addTextFile(file); -// } else if (ext === '.pdf') { -// console.error('Adding pdf file', file); -// await bucket.addPDF(file); -// } else if (stats.isDirectory) { -// console.error('Adding directory', file); -// await bucket.addDirectory(file); -// } else { -// throw new Error(`Unsupported file type: ${ext}`); -// } -// } -// } -// await bucket.save(); diff --git a/deno-src/index.ts b/deno-src/index.ts index f67477f..da58f1e 100644 --- a/deno-src/index.ts +++ b/deno-src/index.ts @@ -1,48 +1,5 @@ import { expose } from '@kunkun/api/runtime/deno'; import type { DenoAPI } from '../src/api.types.ts'; import { Bucket } from './bucket.ts'; -import { existsSync } from 'node:fs'; -import path from 'node:path'; -import { txtExts } from './constants.ts'; -export const extensionSupportPath = Deno.env.get('EXTENSION_SUPPORT'); -if (!extensionSupportPath) { - throw new Error('EXTENSION_SUPPORT is not set'); -} - -expose({ - async indexFiles(bucketName: string, files: string[]): Promise { - const cwd = Deno.cwd(); - console.error('cwd', cwd); - const bucket = new Bucket(extensionSupportPath, bucketName); - // const bucket = new Bucket(extensionSupportPath, bucketName); - console.error('bucket path', bucket.bucketPath); - console.error('files', files); - await bucket.init(); - for (const file of files) { - if (!existsSync(file)) { - throw new Error(`File ${file} does not exist`); - } - console.error('file', file); - // check if file is directory - const stats = Deno.statSync(file); - if (stats.isFile) { - const ext = path.extname(file); - if (txtExts.includes(ext)) { - console.error('Adding text file', file); - await bucket.addTextFile(file); - console.error('Finished adding text file', file); - } else if (ext === '.pdf') { - console.error('Adding pdf file', file); - await bucket.addPDF(file); - } else if (stats.isDirectory) { - console.error('Adding directory', file); - await bucket.addDirectory(file); - } else { - throw new Error(`Unsupported file type: ${ext}`); - } - } - } - await bucket.save(); - } -} satisfies DenoAPI); +expose(new Bucket() satisfies DenoAPI); diff --git a/deno-src/main.ts b/deno-src/main.ts index 6544331..09e5269 100644 --- a/deno-src/main.ts +++ b/deno-src/main.ts @@ -60,60 +60,60 @@ async function deleteDocuments(vectorStore: FaissStore, ids: string[]) { const vectorStore = await getVectorStore(); -// const llm = new ChatOpenAI({ -// model: "gpt-4o-mini", -// temperature: 0, -// }); +const llm = new ChatOpenAI({ + model: "gpt-4o-mini", + temperature: 0, +}); -// // Define prompt for question-answering -// const promptTemplate = await pull("rlm/rag-prompt"); +// Define prompt for question-answering +const promptTemplate = await pull("rlm/rag-prompt"); -// // Define state for application -// const InputStateAnnotation = Annotation.Root({ -// question: Annotation, -// }); +// Define state for application +const InputStateAnnotation = Annotation.Root({ + question: Annotation, +}); -// const StateAnnotation = Annotation.Root({ -// question: Annotation, -// context: Annotation, -// answer: Annotation, -// }); +const StateAnnotation = Annotation.Root({ + question: Annotation, + context: Annotation, + answer: Annotation, +}); -// // Define application steps -// const retrieve = async (state: typeof InputStateAnnotation.State) => { -// const retrievedDocs = await vectorStore.similaritySearch(state.question); -// return { context: retrievedDocs }; -// }; +// Define application steps +const retrieve = async (state: typeof InputStateAnnotation.State) => { + const retrievedDocs = await vectorStore.similaritySearch(state.question); + return { context: retrievedDocs }; +}; -// const generate = async (state: typeof StateAnnotation.State) => { -// const docsContent = state.context.map((doc) => doc.pageContent).join("\n"); -// const messages = await promptTemplate.invoke({ -// question: state.question, -// context: docsContent, -// }); -// const response = await llm.invoke(messages); -// return { answer: response.content }; -// }; +const generate = async (state: typeof StateAnnotation.State) => { + const docsContent = state.context.map((doc) => doc.pageContent).join("\n"); + const messages = await promptTemplate.invoke({ + question: state.question, + context: docsContent, + }); + const response = await llm.invoke(messages); + return { answer: response.content }; +}; -// // Compile application and test -// const graph = new StateGraph(StateAnnotation) -// .addNode("retrieve", retrieve) -// .addNode("generate", generate) -// .addEdge("__start__", "retrieve") -// .addEdge("retrieve", "generate") -// .addEdge("generate", "__end__") -// .compile(); +// Compile application and test +const graph = new StateGraph(StateAnnotation) + .addNode("retrieve", retrieve) + .addNode("generate", generate) + .addEdge("__start__", "retrieve") + .addEdge("retrieve", "generate") + .addEdge("generate", "__end__") + .compile(); -// let inputs = { question: "What is Task Decomposition?" }; +let inputs = { question: "What is Task Decomposition?" }; -// while (true) { -// const question = prompt("Enter your question (or 'exit' to quit): "); -// if (!question || question.toLowerCase() === "exit") { -// break; -// } +while (true) { + const question = prompt("Enter your question (or 'exit' to quit): "); + if (!question || question.toLowerCase() === "exit") { + break; + } -// const result = await graph.invoke({ question }); -// console.log("\nAnswer:"); -// console.log(result.answer); -// console.log("\n-------------------\n"); -// } + const result = await graph.invoke({ question }); + console.log("\nAnswer:"); + console.log(result.answer); + console.log("\n-------------------\n"); +} diff --git a/jsr.json b/jsr.json index 861de4f..6b4a582 100644 --- a/jsr.json +++ b/jsr.json @@ -1,19 +1,22 @@ { "name": "@kunkun/kunkun-ext-rag", - "version": "0.0.4", + "version": "0.0.5", "license": "MIT", "exports": "./mod.ts", "publish": { "include": ["mod.ts", "deno-src", "build", "LICENSE", "README.md", "package.json"] }, "imports": { + "@kunkun/api": "jsr:@kunkun/api@^0.0.52", "@langchain/community": "npm:@langchain/community@^0.3.22", "@langchain/core": "npm:@langchain/core@^0.3.27", "@langchain/langgraph": "npm:@langchain/langgraph@^0.2.38", "@langchain/openai": "npm:@langchain/openai@^0.3.16", "@langchain/textsplitters": "npm:@langchain/textsplitters@^0.1.0", + "pdf-parse": "npm:pdf-parse@^1.1.1", "@std/assert": "jsr:@std/assert@1", + "valibot": "jsr:@valibot/valibot@^0.42.1", "faiss-node": "npm:faiss-node@^0.5.1", - "langchain": "npm:langchain@^0.3.9" + "langchain": "npm:langchain@^0.3.12" } } diff --git a/package.json b/package.json index 36dc5a9..6a2679a 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "license": "MIT", "name": "kunkun-ext-rag", "draft": true, - "version": "0.0.4", + "version": "0.0.5", "private": true, "kunkun": { "name": "RAG", @@ -62,14 +62,25 @@ ] }, "shell:stdin-write", - "shell:kill" + "shell:kill", + { + "permission": "open:url", + "allow": [ + { + "url": "https://en.wikipedia.org/wiki/Retrieval-augmented_generation" + }, + { + "url": "https://github.com/kunkunsh/kunkun-ext-rag" + } + ] + } ], "customUiCmds": [ { "main": "/", "dist": "build", "devMain": "http://localhost:5173", - "name": "RAG", + "name": "Local RAG", "cmds": [] } ], @@ -88,9 +99,11 @@ "@iconify/svelte": "^4.2.0", "@kksh/api": "^0.0.55", "@kksh/svelte5": "0.1.15", + "@langchain/openai": "^0.4.2", "clsx": "^2.1.1", "lucide-svelte": "^0.474.0", "mode-watcher": "^0.5.1", + "svelte-markdown": "^0.4.1", "sveltekit-superforms": "^2.23.1", "tailwind-merge": "^2.6.0", "tailwind-variants": "^0.3.1", diff --git a/src/api.types.ts b/src/api.types.ts index 489212f..b6fa940 100644 --- a/src/api.types.ts +++ b/src/api.types.ts @@ -1,3 +1,10 @@ export interface DenoAPI { - indexFiles(bucketName: string, files: string[]): Promise; + init(bucketDir: string, bucketName: string): Promise; + addTextFile(filePath: string): Promise; + addPDF(filePath: string): Promise; + addDirectory(dir: string): Promise; + indexFiles(files: string[]): Promise; + save(): Promise; + retrieve(query: string): Promise; + query(query: string): Promise; } diff --git a/src/lib/components/DatabaseList.svelte b/src/lib/components/DatabaseList.svelte index 4beecba..a60669a 100644 --- a/src/lib/components/DatabaseList.svelte +++ b/src/lib/components/DatabaseList.svelte @@ -11,8 +11,9 @@ {dbInfo.name} AI Provider: {dbInfo.ai} - + - + + + {/snippet} diff --git a/src/lib/components/TauriLink.svelte b/src/lib/components/TauriLink.svelte new file mode 100644 index 0000000..cea6197 --- /dev/null +++ b/src/lib/components/TauriLink.svelte @@ -0,0 +1,16 @@ + + + { + e.preventDefault(); + open.url(href); + }} +> + {@render children()} + diff --git a/src/lib/components/app-sidebar.svelte b/src/lib/components/app-sidebar.svelte index 43b752c..61cd550 100644 --- a/src/lib/components/app-sidebar.svelte +++ b/src/lib/components/app-sidebar.svelte @@ -1,8 +1,7 @@ diff --git a/src/lib/deno.ts b/src/lib/deno.ts index 6ae6d6c..2720108 100644 --- a/src/lib/deno.ts +++ b/src/lib/deno.ts @@ -1,7 +1,7 @@ import { fs, shell, path, toast } from '@kksh/api/ui/iframe'; -import type { DenoAPI } from '../api.types'; +import type { DenoAPI } from '../api.types.ts'; -export async function getRpcAPI(env: { OPENAI_API_KEY: string; EXTENSION_SUPPORT: string }) { +export async function getRpcAPI(env: { OPENAI_API_KEY: string }) { await installDenoDeps().catch((err) => { return toast.error(`Failed to install deno dependencies; ${err.message}`); }); @@ -13,7 +13,7 @@ export async function getRpcAPI(env: { OPENAI_API_KEY: string; EXTENSION_SUPPORT { cwd, // allowAllEnv: true, - allowEnv: ['OPENAI_API_KEY', 'EXTENSION_SUPPORT', 'CWD'], + allowEnv: ['OPENAI_API_KEY', 'CWD'], allowWrite: ['$EXTENSION_SUPPORT'], allowAllRead: true, // allowAllWrite: true, diff --git a/src/routes/about/+page.svelte b/src/routes/about/+page.svelte index f514f24..6f46b1b 100644 --- a/src/routes/about/+page.svelte +++ b/src/routes/about/+page.svelte @@ -1,15 +1,30 @@ - - - About Page - - - - - - - +
+

About Page

+ Source Code: + + https://github.com/kunkunsh/kunkun-ext-rag + . +
+
+

+ Kunkun RAG Extension is a local RAG app, that allows you to index a local directory of files and + search them using a LLM model. +

+

+ If you don't know what RAG is, you can read more about it + + https://en.wikipedia.org/wiki/Retrieval-augmented_generation + . +

+

You can add files to a database. Currently only text files are supported.

+

+ Text Files with extension .txt, .md, .mdx are supported. If + you need other file types, send a feature request to the repo. +

+
diff --git a/src/routes/database/[id]/+page.svelte b/src/routes/database/[id]/+page.svelte index f97801a..939fa43 100644 --- a/src/routes/database/[id]/+page.svelte +++ b/src/routes/database/[id]/+page.svelte @@ -1,43 +1,51 @@
-

Manage Database

- - +

+ Manage Database + + + + + + Pick the files or directories you want to index into vector database. Then you can use the + database to answer questions. + + +

+
+ + +
+
{ + cancel(); + ans = ''; + loading = true; + if (query.length === 0) { + toast.error('Question is required'); + return; + } + ans = (await rpc?.api.query(query)) ?? ''; + query = ''; + loading = false; + }} + > +
+ + +
+ {#if loading} +
+ +
+ {:else} +
+ +
+ {/if} +
diff --git a/src/routes/database/[id]/+page.ts b/src/routes/database/[id]/+page.ts index 7184a07..f0f42f2 100644 --- a/src/routes/database/[id]/+page.ts +++ b/src/routes/database/[id]/+page.ts @@ -1,5 +1,7 @@ import type { PageLoad } from './$types'; +export const prerender = false; + export const load: PageLoad = ({ params: { id } }) => { return { id: parseInt(id) }; }; diff --git a/svelte.config.js b/svelte.config.js index 03d229d..e625f86 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -11,7 +11,9 @@ const config = { // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. // If your environment is not supported, or you settled on a specific environment, switch out the adapter. // See https://kit.svelte.dev/docs/adapters for more information about adapters. - adapter: adapter({}), + adapter: adapter({ + fallback: '400.html' + }), alias: { '@/*': './src/lib/*' }