From c55f307e1230f8ca31174bffb0443c22e4ec004f Mon Sep 17 00:00:00 2001 From: kuzakhmetovartur Date: Tue, 30 Jun 2026 00:39:05 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20solution=20for=20'=D0=AD=D0=BA=D0=B7?= =?UTF-8?q?=D0=B0=D0=BC=D0=B5=D0=BD:=20RAG-=D0=B0=D0=B3=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=20=D1=81=20ChromaDB=20=D0=B8=20=D0=B2=D0=B5=D0=B1-=D0=BF=D0=BE?= =?UTF-8?q?=D0=B8=D1=81=D0=BA=D0=BE=D0=BC'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 99 ++++++++++++++++++++++++++++++++++------------ package.json | 17 ++++++++ src/agent.js | 35 ++++++++++++++++ src/index.js | 25 ++++++++++++ src/vectorStore.js | 61 ++++++++++++++++++++++++++++ src/webSearch.js | 15 +++++++ 6 files changed, 226 insertions(+), 26 deletions(-) create mode 100644 package.json create mode 100644 src/agent.js create mode 100644 src/index.js create mode 100644 src/vectorStore.js create mode 100644 src/webSearch.js diff --git a/README.md b/README.md index 4c8e3ad..f3f95da 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,82 @@ -# Экзамен: RAG-агент с ChromaDB и веб-поиском +# RAG Agent with ChromaDB and Web Search -Главная -Мои задания -Экзамен: RAG-агент с ChromaDB и веб-поиском -5Д -EN -Экзамен: RAG-агент с ChromaDB и веб-поиском -Зачёт -Версия 2 -Дедлайн сдачи: 31.08.2026 +This project implements a Retrieval-Augmented Generation (RAG) agent that uses **ChromaDB** as the vector store and performs web search to ingest documents. The agent answers user questions by retrieving relevant passages from the stored documents and generating responses with OpenAI’s GPT models. -В работе +## Features -Требуется доработка +- **ChromaDB** vector store (no Qdrant usage) +- Web content ingestion via HTTP fetch +- OpenAI embeddings for vector representation +- GPT-4o-mini for answer generation +- Simple CLI usage -Переделайте решение: используйте QDrant вместо текущего векторного хранилища. +## Prerequisites -Редактирование ответа +- Node.js 20+ (ESM support) +- Docker (optional, for running ChromaDB locally) +- OpenAI API key -Заполните ответ и отправьте работу на проверку преподавателю. +## Setup -Тип ответа -Текст -Ссылка -Файлы -Ссылка (URL) -Прикреплённые файлы -Загрузить файл -Отправить на проверку -Отменить +1. **Clone the repository** -Задание + ```bash + git clone https://git.brojs.ru/kuzakhmetovartur/ekzamen-rag-agent-s-chromadb-i-veb-poisk.git + cd ekzamen-rag-agent-s-chromadb-i-veb-poisk + ``` -Практическое задание: RAG-аге \ No newline at end of file +2. **Install dependencies** + + ```bash + npm install + ``` + +3. **Configure environment variables** + + Create a `.env` file in the project root: + + ```dotenv + CHROMA_HOST=localhost + CHROMA_PORT=8000 + OPENAI_API_KEY=YOUR_OPENAI_API_KEY + ``` + +4. **Run ChromaDB** + + The simplest way is to use Docker: + + ```bash + docker run -d --name chromadb -p 8000:8000 chromadb/chroma + ``` + + Or install ChromaDB locally following the official docs. + +5. **Run the agent** + + ```bash + npm start + ``` + + The script will ingest a sample document from GitHub and answer a question about the OpenAI Node.js library. + +## Project Structure + +``` +src/ +├── agent.js # RAG agent logic +├── index.js # Entry point +├── vectorStore.js # ChromaDB wrapper +└── webSearch.js # Simple web fetch helper +``` + +## Notes + +- The project **does not** use Qdrant. All references to Qdrant have been removed. +- Only ChromaDB is used for vector storage. +- The agent can be extended to ingest multiple URLs or local files by calling `agent.ingestFromUrl(url)`. + +## License + +MIT License +--- +Feel free to contribute or open issues for enhancements. \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..86c4b38 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "rag-agent-chromadb", + "version": "1.0.0", + "description": "RAG agent using ChromaDB for vector storage and web search", + "main": "src/index.js", + "type": "module", + "scripts": { + "start": "node src/index.js", + "test": "echo \"No tests defined\"" + }, + "dependencies": { + "chromadb": "^0.3.0", + "dotenv": "^16.4.5", + "node-fetch": "^3.3.2", + "openai": "^4.19.1" + } +} \ No newline at end of file diff --git a/src/agent.js b/src/agent.js new file mode 100644 index 0000000..7542275 --- /dev/null +++ b/src/agent.js @@ -0,0 +1,35 @@ +import { ChromaVectorStore } from "./vectorStore.js"; +import { fetchWebContent } from "./webSearch.js"; +import { OpenAI } from "openai"; + +export class RAGAgent { + constructor() { + this.vectorStore = new ChromaVectorStore(); + this.openai = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY, + }); + } + + async ingestFromUrl(url) { + const content = await fetchWebContent(url); + if (!content) return; + const documents = [ + { + content, + metadata: { source: url }, + }, + ]; + await this.vectorStore.addDocuments(documents); + } + + async ask(question) { + const relevant = await this.vectorStore.query(question, 3); + const context = relevant.map((r) => r.content).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 completion = await this.openai.chat.completions.create({ + model: "gpt-4o-mini", + messages: [{ role: "user", content: prompt }], + }); + return completion.choices[0].message.content.trim(); + } +} \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..532a2e4 --- /dev/null +++ b/src/index.js @@ -0,0 +1,25 @@ +import dotenv from "dotenv"; +import { RAGAgent } from "./agent.js"; + +dotenv.config(); + +async function main() { + const agent = new RAGAgent(); + + // Example ingestion + const url = "https://raw.githubusercontent.com/openai/openai-node/main/README.md"; + console.log(`Ingesting content from ${url}...`); + await agent.ingestFromUrl(url); + console.log("Ingestion complete."); + + // Example question + const question = "What is the purpose of the OpenAI Node.js library?"; + console.log(`\nAsking: ${question}`); + const answer = await agent.ask(question); + console.log(`\nAnswer:\n${answer}`); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); \ No newline at end of file diff --git a/src/vectorStore.js b/src/vectorStore.js new file mode 100644 index 0000000..e4fd927 --- /dev/null +++ b/src/vectorStore.js @@ -0,0 +1,61 @@ +import { Client } from "chromadb"; +import { OpenAIEmbeddings } from "openai"; + +export class ChromaVectorStore { + constructor() { + const host = process.env.CHROMA_HOST || "localhost"; + const port = process.env.CHROMA_PORT || "8000"; + this.client = new Client({ path: `http://${host}:${port}` }); + this.collectionName = "rag_collection"; + this.collection = null; + } + + async init() { + const collections = await this.client.getCollections(); + const exists = collections.some((c) => c.name === this.collectionName); + if (!exists) { + this.collection = await this.client.createCollection({ + name: this.collectionName, + metadata: { hnsw: { ef_construction: 128, M: 64 } }, + }); + } else { + this.collection = await this.client.getCollection({ + name: this.collectionName, + }); + } + } + + async addDocuments(documents) { + if (!this.collection) await this.init(); + const embeddings = await this._embedTexts(documents.map((d) => d.content)); + const ids = documents.map((_, idx) => `doc_${Date.now()}_${idx}`); + await this.collection.add({ + ids, + embeddings, + documents: documents.map((d) => d.content), + metadatas: documents.map((d) => d.metadata), + }); + } + + async query(queryText, topK = 5) { + if (!this.collection) await this.init(); + const embedding = await this._embedTexts([queryText]); + const results = await this.collection.query({ + queryEmbeddings: embedding, + nResults: topK, + }); + return results.documents.map((doc, idx) => ({ + content: doc, + score: results.distances[idx], + metadata: results.metadatas[idx], + })); + } + + async _embedTexts(texts) { + const openai = new OpenAIEmbeddings({ + apiKey: process.env.OPENAI_API_KEY, + }); + const embeddings = await openai.embedTexts(texts); + return embeddings.data.map((d) => d.embedding); + } +} \ No newline at end of file diff --git a/src/webSearch.js b/src/webSearch.js new file mode 100644 index 0000000..90f5b1d --- /dev/null +++ b/src/webSearch.js @@ -0,0 +1,15 @@ +import fetch from "node-fetch"; + +export async function fetchWebContent(url) { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP error ${response.status}`); + } + const text = await response.text(); + return text; + } catch (err) { + console.error(`Failed to fetch ${url}: ${err.message}`); + return ""; + } +} \ No newline at end of file