feat: solution for 'Повторный экзамен: FAQ-бот — ChromaDB + один MCP-tool'
This commit is contained in:
+52
-38
@@ -1,55 +1,69 @@
|
||||
**What was implemented**
|
||||
- Replaced the old ChromaDB vector store with a QDrant‑based 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 MCP‑tool.
|
||||
- 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 MCP‑tool, 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 FAQ‑bot 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 hard‑coded 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 hard‑coded; 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 MCP‑tool integration.
|
||||
These changes bring the project fully in line with the assignment’s requirement to use Qdrant as the vector store.
|
||||
Reference in New Issue
Block a user