feat: solution for 'Экзамен: RAG-агент с ChromaDB и веб-поиском'

This commit is contained in:
2026-06-30 00:39:05 +03:00
parent d6805973d6
commit c55f307e12
6 changed files with 226 additions and 26 deletions
+73 -26
View File
@@ -1,35 +1,82 @@
# Экзамен: RAG-агент с ChromaDB и веб-поиском # RAG Agent with ChromaDB and Web Search
Главная 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 OpenAIs GPT models.
Мои задания
Экзамен: RAG-агент с ChromaDB и веб-поиском
EN
Экзамен: RAG-агент с ChromaDB и веб-поиском
Зачёт
Версия 2
Дедлайн сдачи: 31.08.2026
В работе ## 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
Тип ответа 1. **Clone the repository**
Текст
Ссылка
Файлы
Ссылка (URL)
Прикреплённые файлы
Загрузить файл
Отправить на проверку
Отменить
Задание ```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-аге 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.
+17
View File
@@ -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"
}
}
+35
View File
@@ -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();
}
}
+25
View File
@@ -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);
});
+61
View File
@@ -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);
}
}
+15
View File
@@ -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 "";
}
}