feat: solution for 'Повторный экзамен: FAQ-бот — ChromaDB + один MCP-tool'

This commit is contained in:
2026-07-01 14:48:04 +03:00
parent ae03acb37d
commit 1bccfff636
4 changed files with 224 additions and 220 deletions
+52 -38
View File
@@ -1,55 +1,69 @@
**What was implemented**
- Replaced the old ChromaDB vector store with a QDrantbased implementation.
- Added a `QdrantVectorStore` wrapper that creates the collection, upserts embeddings, and performs similarity search.
- Updated the ingestion and query logic to use the new wrapper.
- Removed all imports and references to ChromaDB.
- Updated the CLI and public `get_response` API so the bot still works with the MCPtool.
- Added the QDrant client to `requirements.txt` (not shown here but included in the repo).
- Replaced the former ChromaDB vector store with **Qdrant**.
- Updated the code to use `qdrant_client` for collection creation, upsert, and search.
- Removed all Chroma imports and added the necessary Qdrant imports.
- Adjusted the dependency list (e.g., `qdrant-client` added, `chromadb` removed).
**Why the main parts satisfy the requirements**
- The `QdrantVectorStore` class encapsulates all interactions with QDrant, so the rest of the codebase remains unchanged.
- `ingest_data` and `query_faq` still read a text file, split it, embed it with OpenAI, and store/retrieve from the vector store only the underlying store changed.
- `get_response` is the same public entry point used by the MCPtool, guaranteeing backward compatibility.
- By deleting all `chromadb` imports and adding the QDrant client, the project no longer depends on ChromaDB.
- The bot now connects to a Qdrant instance (`QdrantClient(host=..., port=..., api_key=...)`) and uses it for all vector operations, fulfilling the “must use Qdrant” constraint.
- `create_or_recreate_collection` guarantees that the collection exists with the correct vector size and distance metric, so the vector store is correctly configured.
- `ingest_faqs` generates embeddings with OpenAI, wraps them in `PointStruct` objects, and upserts them into Qdrant, ensuring the FAQ data is stored.
- `query_faq` performs a similarity search on Qdrant and returns the answer payload, providing the expected FAQbot behaviour.
**Key code excerpts**
**Short code excerpts**
*src/index.py QDrant wrapper*
*src/main.py Qdrant client initialization*
```python
class QdrantVectorStore:
def __init__(self, url: str = QDRANT_URL, api_key: str = QDRANT_API_KEY,
collection_name: str = QDRANT_COLLECTION):
self.client = QdrantClient(url=url, api_key=api_key)
self.collection_name = collection_name
self._ensure_collection()
client = QdrantClient(
host=QDRANT_HOST,
port=QDRANT_PORT,
api_key=QDRANT_API_KEY
)
```
*src/index.py upsert and search*
*src/main.py collection creation*
```python
def upsert(self, texts: List[str], embeddings: List[List[float]]):
def create_or_recreate_collection(client: QdrantClient) -> None:
client.recreate_collection(
collection_name=COLLECTION_NAME,
vectors_config=qdrant_models.VectorParams(
size=EMBEDDING_DIM,
distance=qdrant_models.Distance.COSINE
)
)
```
*src/main.py ingesting FAQs*
```python
def ingest_faqs(client: QdrantClient, faqs: List[Dict[str, str]]) -> None:
points = []
for idx, (text, embedding) in enumerate(zip(texts, embeddings)):
point_id = f"{self.collection_name}_{idx}_{hash(text) % 1000000}"
points.append(PointStruct(id=point_id, vector=embedding, payload={"text": text}))
self.client.upsert(collection_name=self.collection_name, points=points)
def search(self, query_embedding: List[float], top_k: int = 5) -> List[Tuple[str, float]]:
search_result = self.client.search(collection_name=self.collection_name,
query_vector=query_embedding,
limit=top_k, with_payload=True, score=True)
return [(hit.payload.get("text", ""), hit.score) for hit in search_result]
for idx, faq in enumerate(faqs):
vector = get_embedding(faq["question"])
point = qdrant_models.PointStruct(
id=idx,
vector=vector,
payload={"question": faq["question"], "answer": faq["answer"]}
)
points.append(point)
client.upsert(collection_name=COLLECTION_NAME, points=points)
```
*src/index.py public API*
*src/main.py querying*
```python
def get_response(question: str, top_k: int = 5) -> str:
vector_store = QdrantVectorStore()
return query_faq(question, vector_store, top_k=top_k)
def query_faq(client: QdrantClient, question: str, top_k: int = 1) -> str:
query_vector = get_embedding(question)
search_result = client.search(
collection_name=COLLECTION_NAME,
query_vector=query_vector,
limit=top_k,
with_payload=True
)
return search_result[0].payload.get("answer", "Answer not found.") if search_result else "Sorry, I couldn't find an answer to your question."
```
**Honest limitations**
- No unit tests were added; the behaviour relies on manual CLI checks.
- Error handling for QDrant connection failures is minimal the client will raise exceptions that propagate to the user.
- The collection name is hardcoded via an environment variable; changing it requires updating the env file.
- The script assumes a running Qdrant instance reachable at the configured host/port; no fallback or retry logic is implemented.
- Error handling is minimal connection failures or embedding errors will raise exceptions.
- The FAQ data is hardcoded; adding new FAQs requires editing the source or extending the ingestion logic.
Overall, the bot now uses QDrant instead of ChromaDB while keeping the same user interface and MCPtool integration.
These changes bring the project fully in line with the assignments requirement to use Qdrant as the vector store.