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

This commit is contained in:
2026-07-01 14:17:22 +03:00
parent dc4f151b3d
commit b4f2282dd3
3 changed files with 351 additions and 241 deletions
+64 -27
View File
@@ -1,43 +1,80 @@
**What was implemented**
- Replaced the previous Qdrantbased vector store with a lightweight wrapper around **ChromaDB** (`src/vector_store.py`).
- Updated the `RAGAgent` to work exclusively with the new `ChromaVectorStore`.
- Kept the FastAPI endpoints (`/ingest`, `/query`, `/websearch`) unchanged, so the public API and websearch logic remain intact.
- Removed every import and reference to Qdrant, ensuring the stack now matches the assignment.
**SOLUTION.md**
**Why the main parts satisfy the requirements**
- `ChromaVectorStore` creates a Chroma client and a collection, then exposes `add_documents` and `similarity_search` that match the original Qdrant interface.
- `RAGAgent` uses this store for ingestion and querying, and still relies on OpenAI embeddings, so the RAG workflow is preserved.
- The FastAPI app simply forwards requests to the agent; no Qdrant code is touched, so the vector database is now exclusively ChromaDB.
- Websearch utilities (`src/web_search.py`) are untouched, so the searchtoingest pipeline continues to work.
### Что реализовано
- **ChromaDB** вместо Qdrant: подключаем клиент, создаём коллекцию и сохраняем векторные представления документов.
- **Разбиение текста** на чанки, чтобы не превышать лимит токенов при эмбеддинге.
- **Веб‑поиск** через DuckDuckGo (HTML‑парсинг) для получения дополнительных контекстов.
- **RAGpipeline**: поиск в ChromaDB → добавление веб‑сниппетов → генерация ответа GPT‑3.5‑turbo.
- **CLI**: `add <file>` для загрузки документов, `ask <question>` для запросов.
**Key code excerpts**
### Почему это соответствует требованиям
- **ChromaDB** – указанная в условии векторная база. В коде используется `chromadb.Client` и `Settings(persist_directory=…)`.
- **Веб‑поиск** реализован через `requests` + `BeautifulSoup`, возвращает несколько сниппетов.
- **RAG**: `ChromaVectorStore.query` возвращает ближайшие документы, а `generate_answer` формирует финальный ответ, учитывая как локальный контекст, так и веб‑сниппеты.
- **CLI** упрощает взаимодействие и демонстрирует полный цикл от загрузки до ответа.
`src/vector_store.py` Chroma client and collection creation
### Ключевые фрагменты кода
**src/index.py embed_text**
```python
self.client = chromadb.Client()
self.collection = self.client.get_or_create_collection(name=collection_name)
def embed_text(text: str) -> List[float]:
response = openai.Embedding.create(
input=text,
model=EMBEDDING_MODEL,
)
return response["data"][0]["embedding"]
```
`src/rag_agent.py` ingestion uses the new store
**src/index.py chunk_text**
```python
self.vector_store.add_documents(docs_with_embeddings)
def chunk_text(text: str, max_tokens: int = 500) -> List[str]:
max_chars = max_tokens * 4
paragraphs = [p.strip() for p in text.split("\n") if p.strip()]
...
return chunks
```
`src/main.py` FastAPI endpoint that calls the agent
**src/index.py ChromaVectorStore**
```python
@app.post("/ingest")
def ingest(request: IngestRequest):
docs = [doc.dict() for doc in request.documents]
rag_agent.ingest(docs)
class ChromaVectorStore:
def __init__(self, collection_name: str = CHROMA_COLLECTION_NAME):
self.client: Client = chromadb.Client(
Settings(persist_directory=CHROMA_PERSIST_DIR,
anonymized_telemetry=False)
)
self.collection = self.client.get_or_create_collection(name=collection_name)
```
`src/web_search.py` still feeds results into the agent
**src/index.py add_documents_from_file**
```python
agent.ingest(docs_to_ingest)
def add_documents_from_file(file_path: str) -> None:
...
documents = [{"text": chunk, "metadata": {"source": file_path}} for chunk in chunks]
store = ChromaVectorStore()
store.add_documents(documents)
```
**Honest limitations**
- ChromaDB is used in its default inmemory mode; data will not persist across server restarts unless a persistent storage path is configured.
- No additional error handling for Chroma connection failures has been added beyond the basic try/except in the API routes.
**src/index.py ask_query**
```python
def ask_query(query: str) -> None:
store = ChromaVectorStore()
chroma_results = store.query(query, k=5)
chroma_context = "\n\n".join([doc["document"] for doc in chroma_results])
Overall, the project now uses only ChromaDB for vector storage, keeps all existing functionality, and respects the assignment constraints.
web_snippets = web_search(query, num_results=3)
web_context = "\n\n".join(web_snippets)
combined_context = "\n\n---\n\n".join(filter(None, [chroma_context, web_context]))
answer = generate_answer(combined_context, query)
print("\nAnswer:\n")
print(answer)
```
### Ограничения и возможные улучшения
- **Идентификаторы** генерируются простым префиксом; при больших коллекциях возможны коллизии.
- **Отсутствует** кэширование веб‑результатов и ограничение частоты запросов к DuckDuckGo.
- **Нет** обработки ошибок при чтении файлов и при работе с ChromaDB (например, при отсутствии коллекции).
- **Тесты** не покрыты – стоит добавить unit‑тесты для `embed_text`, `chunk_text`, `web_search` и `ChromaVectorStore`.
- **Параметры** (количество результатов, токен‑лимит) заданы константами; можно сделать их конфигурируемыми через CLI.
Тем не менее, текущая реализация полностью удовлетворяет заданию: использована ChromaDB, реализован веб‑поиск и RAG‑pipeline, а CLI позволяет быстро проверить работу.