This commit is contained in:
@@ -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
|
Файлы
|
||||||
|
Ссылка (URL)
|
||||||
# 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
|
|
||||||
+16
-18
@@ -1,26 +1,24 @@
|
|||||||
{
|
{
|
||||||
"name": "agent-rag-memory",
|
"name": "rag-agent",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "A simple LangChain agent with RAG memory implemented in TypeScript",
|
"description": "A simple RAG agent with memory using OpenAI and FAISS",
|
||||||
"main": "dist/index.js",
|
"main": "src/index.js",
|
||||||
"type": "commonjs",
|
"bin": {
|
||||||
"scripts": {
|
"rag-agent": "./src/cli.js"
|
||||||
"build": "tsc",
|
},
|
||||||
"start": "ts-node src/index.ts"
|
"scripts": {
|
||||||
|
"start": "node src/cli.js"
|
||||||
},
|
},
|
||||||
"keywords": [
|
|
||||||
"langchain",
|
|
||||||
"rag",
|
|
||||||
"agent",
|
|
||||||
"typescript"
|
|
||||||
],
|
|
||||||
"author": "Your Name",
|
"author": "Your Name",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^20.11.0",
|
"@langchain/core": "^0.0.0",
|
||||||
"langchain": "^0.0.202",
|
"@langchain/openai": "^0.0.0",
|
||||||
"openai": "^4.20.0",
|
"@langchain/textsplitter": "^0.0.0",
|
||||||
"ts-node": "^10.9.1",
|
"@langchain/vectorstores": "^0.0.0",
|
||||||
"typescript": "^5.3.3"
|
"commander": "^10.0.0",
|
||||||
|
"dotenv": "^16.0.0",
|
||||||
|
"faiss-node": "^1.0.0",
|
||||||
|
"fs-extra": "^11.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+29
-11
@@ -1,14 +1,32 @@
|
|||||||
const { getChatCompletion } = require('./utils');
|
const { OpenAI } = require('@langchain/openai');
|
||||||
const retriever = require('./retriever');
|
const { RetrievalQAChain } = require('@langchain/chains');
|
||||||
|
const { initVectorStore } = require('./vectorStore');
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
async function ask(question) {
|
class Agent {
|
||||||
const passages = await retriever.getRelevantPassages(question, 3);
|
constructor() {
|
||||||
const context = passages.join('\n---\n');
|
this.llm = new OpenAI({
|
||||||
const prompt = `You are an assistant. Use the following context to answer the question.\n\nContext:\n${context}\n\nQuestion: ${question}\nAnswer:`;
|
temperature: 0.7,
|
||||||
const answer = await getChatCompletion(prompt);
|
openAIApiKey: process.env.OPENAI_API_KEY,
|
||||||
return answer;
|
});
|
||||||
|
this.vectorStore = null;
|
||||||
|
this.chain = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
async init() {
|
||||||
ask,
|
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 = new Agent();
|
||||||
+45
@@ -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 <file>')
|
||||||
|
.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 <question...>')
|
||||||
|
.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);
|
||||||
+1
-43
@@ -1,43 +1 @@
|
|||||||
const readline = require('readline');
|
module.exports = require('./agent');
|
||||||
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();
|
|
||||||
@@ -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,
|
||||||
|
};
|
||||||
+32
-26
@@ -1,33 +1,39 @@
|
|||||||
class VectorStore {
|
const { FAISS } = require('@langchain/vectorstores/faiss');
|
||||||
constructor() {
|
const { OpenAIEmbeddings } = require('@langchain/openai');
|
||||||
this.documents = [];
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
const VECTORSTORE_DIR = path.join(__dirname, '..', 'data', 'vectorstore');
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
addDocument(id, embedding, text) {
|
async function addDocuments(texts) {
|
||||||
this.documents.push({ id, embedding, text });
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
cosineSimilarity(a, b) {
|
async function clearVectorStore() {
|
||||||
let dot = 0;
|
if (fs.existsSync(VECTORSTORE_DIR)) {
|
||||||
let normA = 0;
|
fs.rmdirSync(VECTORSTORE_DIR, { recursive: true });
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = new VectorStore();
|
module.exports = {
|
||||||
module.exports = store;
|
initVectorStore,
|
||||||
|
addDocuments,
|
||||||
|
clearVectorStore,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user