80 lines
4.5 KiB
Markdown
80 lines
4.5 KiB
Markdown
**SOLUTION.md**
|
||
|
||
### Что реализовано
|
||
- **ChromaDB** вместо Qdrant: подключаем клиент, создаём коллекцию и сохраняем векторные представления документов.
|
||
- **Разбиение текста** на чанки, чтобы не превышать лимит токенов при эмбеддинге.
|
||
- **Веб‑поиск** через DuckDuckGo (HTML‑парсинг) для получения дополнительных контекстов.
|
||
- **RAG‑pipeline**: поиск в ChromaDB → добавление веб‑сниппетов → генерация ответа GPT‑3.5‑turbo.
|
||
- **CLI**: `add <file>` для загрузки документов, `ask <question>` для запросов.
|
||
|
||
### Почему это соответствует требованиям
|
||
- **ChromaDB** – указанная в условии векторная база. В коде используется `chromadb.Client` и `Settings(persist_directory=…)`.
|
||
- **Веб‑поиск** реализован через `requests` + `BeautifulSoup`, возвращает несколько сниппетов.
|
||
- **RAG**: `ChromaVectorStore.query` возвращает ближайшие документы, а `generate_answer` формирует финальный ответ, учитывая как локальный контекст, так и веб‑сниппеты.
|
||
- **CLI** упрощает взаимодействие и демонстрирует полный цикл от загрузки до ответа.
|
||
|
||
### Ключевые фрагменты кода
|
||
|
||
**src/index.py – embed_text**
|
||
```python
|
||
def embed_text(text: str) -> List[float]:
|
||
response = openai.Embedding.create(
|
||
input=text,
|
||
model=EMBEDDING_MODEL,
|
||
)
|
||
return response["data"][0]["embedding"]
|
||
```
|
||
|
||
**src/index.py – chunk_text**
|
||
```python
|
||
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/index.py – ChromaVectorStore**
|
||
```python
|
||
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/index.py – add_documents_from_file**
|
||
```python
|
||
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)
|
||
```
|
||
|
||
**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])
|
||
|
||
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 позволяет быстро проверить работу. |