diff --git a/README.md b/README.md index 1fed409..4e91224 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,30 @@ -# Agent with RAG Memory +# Агент с RAG-памятью -This project demonstrates a simple LangChain agent that uses Retrieval-Augmented Generation (RAG) to answer questions based on a small set of documents. The implementation is written in TypeScript and follows the latest LangChain initialization patterns. +Главная +Мои задания +Агент с RAG-памятью +5Д +EN +Агент с RAG-памятью +Зачёт +Версия 9 +Дедлайн сдачи: 31.08.2026 -## Features +В работе -- **Updated Agent Initialization**: Uses `initializeAgentExecutorWithOptions` from LangChain. -- **Custom Text Splitter**: Configured with a chunk size of 1000 characters and an overlap of 200 characters. -- **RAG Memory**: Embeddings are stored in a FAISS vector store and queried via a RetrievalQA chain. -- **Simple Test Harness**: Runs a sample query and prints the agent's response. +Требуется доработка -## Prerequisites +Решение не соответствует ключевым требованиям задания: отсутствуют требуемые инструменты, некорректно реализован CLI и README содержит неверную информацию. Необходимо внести исправления. -- Node.js v18 or newer -- npm +Редактирование ответа -## Setup +Заполните ответ и отправьте работу на проверку преподавателю. -```bash -# Clone the repository -git clone https://github.com/your-username/agent-rag-memory.git -cd agent-rag-memory - -# Install dependencies -npm install - -# Create a .env file with your OpenAI API key -echo "OPENAI_API_KEY=your_api_key_here" > .env -``` - -## Running the Agent - -```bash -npm start -``` - -You should see output similar to: - -``` -=== Agent Response === -Paris -``` - -## Project Structure - -``` -agent-rag-memory/ -├── src/ -│ └── index.ts # Main implementation -├── package.json -├── tsconfig.json -└── README.md -``` - -## License - -MIT License \ No newline at end of file +Тип ответа +Текст +Ссылка +Файлы +Ссылка (URL) +Прикреплённые файлы +Загрузить файл +Отправить \ No newline at end of file diff --git a/package.json b/package.json index 999f740..f1bfabb 100644 --- a/package.json +++ b/package.json @@ -1,26 +1,24 @@ { - "name": "agent-rag-memory", + "name": "rag-agent", "version": "1.0.0", - "description": "A simple LangChain agent with RAG memory implemented in TypeScript", - "main": "dist/index.js", - "type": "commonjs", - "scripts": { - "build": "tsc", - "start": "ts-node src/index.ts" + "description": "A simple RAG agent with memory using OpenAI and FAISS", + "main": "src/index.js", + "bin": { + "rag-agent": "./src/cli.js" + }, + "scripts": { + "start": "node src/cli.js" }, - "keywords": [ - "langchain", - "rag", - "agent", - "typescript" - ], "author": "Your Name", "license": "MIT", "dependencies": { - "@types/node": "^20.11.0", - "langchain": "^0.0.202", - "openai": "^4.20.0", - "ts-node": "^10.9.1", - "typescript": "^5.3.3" + "@langchain/core": "^0.0.0", + "@langchain/openai": "^0.0.0", + "@langchain/textsplitter": "^0.0.0", + "@langchain/vectorstores": "^0.0.0", + "commander": "^10.0.0", + "dotenv": "^16.0.0", + "faiss-node": "^1.0.0", + "fs-extra": "^11.0.0" } } \ No newline at end of file diff --git a/src/agent.js b/src/agent.js index 6bf2cc2..4c62721 100644 --- a/src/agent.js +++ b/src/agent.js @@ -1,14 +1,32 @@ -const { getChatCompletion } = require('./utils'); -const retriever = require('./retriever'); +const { OpenAI } = require('@langchain/openai'); +const { RetrievalQAChain } = require('@langchain/chains'); +const { initVectorStore } = require('./vectorStore'); +require('dotenv').config(); -async function ask(question) { - const passages = await retriever.getRelevantPassages(question, 3); - const context = passages.join('\n---\n'); - const prompt = `You are an assistant. Use the following context to answer the question.\n\nContext:\n${context}\n\nQuestion: ${question}\nAnswer:`; - const answer = await getChatCompletion(prompt); - return answer; +class Agent { + constructor() { + this.llm = new OpenAI({ + temperature: 0.7, + openAIApiKey: process.env.OPENAI_API_KEY, + }); + this.vectorStore = null; + this.chain = null; + } + + async init() { + if (!this.vectorStore) { + this.vectorStore = await initVectorStore(); + } + if (!this.chain) { + this.chain = RetrievalQAChain.fromLLM(this.llm, this.vectorStore.asRetriever()); + } + } + + async ask(question) { + await this.init(); + const result = await this.chain.invoke({ question }); + return result.output; + } } -module.exports = { - ask, -}; \ No newline at end of file +module.exports = new Agent(); \ No newline at end of file diff --git a/src/cli.js b/src/cli.js new file mode 100644 index 0000000..d919841 --- /dev/null +++ b/src/cli.js @@ -0,0 +1,45 @@ +#!/usr/bin/env node +require('dotenv').config(); +const { program } = require('commander'); +const { addDocumentFromFile, clearMemory, agent } = require('./tools'); + +program + .name('rag-agent') + .description('CLI for a RAG agent with OpenAI and FAISS') + .version('1.0.0'); + +program + .command('add ') + .description('Add a document to the vector store') + .action(async (file) => { + try { + await addDocumentFromFile(file); + } catch (err) { + console.error('Error adding document:', err.message); + } + }); + +program + .command('query ') + .description('Ask a question to the agent') + .action(async (question) => { + try { + const answer = await agent.ask(question.join(' ')); + console.log('Answer:', answer); + } catch (err) { + console.error('Error querying agent:', err.message); + } + }); + +program + .command('clear') + .description('Clear all stored memory') + .action(async () => { + try { + await clearMemory(); + } catch (err) { + console.error('Error clearing memory:', err.message); + } + }); + +program.parseAsync(process.argv); \ No newline at end of file diff --git a/src/index.js b/src/index.js index 74d732e..62c5b0f 100644 --- a/src/index.js +++ b/src/index.js @@ -1,43 +1 @@ -const readline = require('readline'); -const agent = require('./agent'); -const retriever = require('./retriever'); - -async function init() { - // Load knowledge base from ./knowledge directory - await retriever.loadKnowledgeBase('./knowledge'); - console.log('Knowledge base loaded.'); -} - -async function main() { - await init(); - - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - prompt: 'You> ', - }); - - rl.prompt(); - - rl.on('line', async (line) => { - const trimmed = line.trim(); - if (trimmed.toLowerCase() === 'exit') { - rl.close(); - process.exit(0); - } - try { - const answer = await agent.ask(trimmed); - console.log(`Assistant: ${answer}\n`); - } catch (err) { - console.error(`Error: ${err.message}\n`); - } - rl.prompt(); - }); - - rl.on('close', () => { - console.log('Goodbye!'); - process.exit(0); - }); -} - -main(); \ No newline at end of file +module.exports = require('./agent'); \ No newline at end of file diff --git a/src/tools.js b/src/tools.js new file mode 100644 index 0000000..e65adf7 --- /dev/null +++ b/src/tools.js @@ -0,0 +1,29 @@ +const fs = require('fs'); +const { RecursiveCharacterTextSplitter } = require('@langchain/textsplitter'); +const { addDocuments, clearVectorStore } = require('./vectorStore'); +const agent = require('./agent'); + +async function addDocumentFromFile(filePath) { + if (!fs.existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + const content = fs.readFileSync(filePath, 'utf-8'); + const splitter = new RecursiveCharacterTextSplitter({ + chunkSize: 1000, + chunkOverlap: 200, + }); + const docs = await splitter.splitText(content); + await addDocuments(docs); + console.log(`Added ${docs.length} chunks from ${filePath}`); +} + +async function clearMemory() { + await clearVectorStore(); + console.log('Cleared vector store memory.'); +} + +module.exports = { + addDocumentFromFile, + clearMemory, + agent, +}; \ No newline at end of file diff --git a/src/vectorStore.js b/src/vectorStore.js index e418039..04f5766 100644 --- a/src/vectorStore.js +++ b/src/vectorStore.js @@ -1,33 +1,39 @@ -class VectorStore { - constructor() { - this.documents = []; - } +const { FAISS } = require('@langchain/vectorstores/faiss'); +const { OpenAIEmbeddings } = require('@langchain/openai'); +const fs = require('fs'); +const path = require('path'); +require('dotenv').config(); - addDocument(id, embedding, text) { - this.documents.push({ id, embedding, text }); - } +const VECTORSTORE_DIR = path.join(__dirname, '..', 'data', 'vectorstore'); - cosineSimilarity(a, b) { - let dot = 0; - let normA = 0; - let normB = 0; - for (let i = 0; i < a.length; i++) { - dot += a[i] * b[i]; - normA += a[i] * a[i]; - normB += b[i] * b[i]; - } - return dot / (Math.sqrt(normA) * Math.sqrt(normB)); +async function initVectorStore() { + if (!fs.existsSync(VECTORSTORE_DIR)) { + fs.mkdirSync(VECTORSTORE_DIR, { recursive: true }); } + const embeddings = new OpenAIEmbeddings({ + openAIApiKey: process.env.OPENAI_API_KEY, + }); + const vectorStore = await FAISS.load(embeddings, VECTORSTORE_DIR); + return vectorStore; +} - query(queryEmbedding, k) { - const sims = this.documents.map((doc) => ({ - doc, - similarity: this.cosineSimilarity(queryEmbedding, doc.embedding), - })); - sims.sort((a, b) => b.similarity - a.similarity); - return sims.slice(0, k).map((s) => s.doc); +async function addDocuments(texts) { + const embeddings = new OpenAIEmbeddings({ + openAIApiKey: process.env.OPENAI_API_KEY, + }); + const vectorStore = await FAISS.load(embeddings, VECTORSTORE_DIR); + await vectorStore.addDocuments(texts); + await vectorStore.save(); +} + +async function clearVectorStore() { + if (fs.existsSync(VECTORSTORE_DIR)) { + fs.rmdirSync(VECTORSTORE_DIR, { recursive: true }); } } -const store = new VectorStore(); -module.exports = store; \ No newline at end of file +module.exports = { + initVectorStore, + addDocuments, + clearVectorStore, +}; \ No newline at end of file