feat: solution for 'Повторный экзамен: FAQ-бот — ChromaDB + один MCP-tool'
This commit is contained in:
@@ -1,118 +1,88 @@
|
|||||||
# FAQ Bot – QDrant Vector Store
|
# FAQ Bot with Qdrant Vector Store
|
||||||
|
|
||||||
This project implements a simple FAQ bot that uses **QDrant** as the vector store instead of ChromaDB.
|
This project implements a simple FAQ chatbot that uses **Qdrant** as the vector store for embeddings.
|
||||||
The bot can ingest a text file containing FAQ content, embed the text using OpenAI embeddings, store the embeddings in QDrant, and answer user questions by retrieving the most relevant passages.
|
The bot loads a set of FAQ entries, generates embeddings with OpenAI’s `text-embedding-ada-002` model, stores them in Qdrant, and answers user queries by performing a similarity search.
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- **Vector Store** – QDrant (via `qdrant-client`)
|
|
||||||
- **Embeddings** – OpenAI `text-embedding-ada-002`
|
|
||||||
- **CLI** – Ingest data, query the bot, delete the collection
|
|
||||||
- **API** – `get_response(question: str, top_k: int = 5)` for integration with tools like MCP-tool
|
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Python 3.9+
|
- Python 3.9+
|
||||||
- QDrant server running locally or accessible via network
|
- A running Qdrant instance (local or remote)
|
||||||
- OpenAI API key
|
- An OpenAI API key
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
1. **Clone the repository**
|
1. **Clone the repository**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://git.brojs.ru/kuzakhmetovartur/povtornyy-ekzamen-faq-bot-chromadb-odin.git
|
git clone https://git.brojs.ru/kuzakhmetovartur/povtornyy-ekzamen-faq-bot-qdrant.git
|
||||||
cd povtornyy-ekzamen-faq-bot-chromadb-odin
|
cd povtornyy-ekzamen-faq-bot-qdrant
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Create a virtual environment (optional but recommended)**
|
2. **Create a virtual environment and install dependencies**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
source venv/bin/activate # On Windows use `venv\Scripts\activate`
|
||||||
```
|
|
||||||
|
|
||||||
3. **Install dependencies**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Set environment variables**
|
3. **Configure environment variables**
|
||||||
|
|
||||||
Create a `.env` file in the project root or export the variables directly:
|
Create a `.env` file in the project root with the following content:
|
||||||
|
|
||||||
```bash
|
```dotenv
|
||||||
export OPENAI_API_KEY="your-openai-api-key"
|
# Qdrant configuration
|
||||||
export QDRANT_URL="http://localhost:6333" # Adjust if your QDrant instance is elsewhere
|
QDRANT_HOST=localhost
|
||||||
export QDRANT_API_KEY="" # Leave empty if no auth is required
|
QDRANT_PORT=6333
|
||||||
export QDRANT_COLLECTION="faq_collection"
|
QDRANT_API_KEY= # leave empty if no API key is required
|
||||||
|
|
||||||
|
# OpenAI configuration
|
||||||
|
OPENAI_API_KEY=your_openai_api_key_here
|
||||||
```
|
```
|
||||||
|
|
||||||
If you prefer not to use a `.env` file, you can set the variables in your shell session.
|
Replace `your_openai_api_key_here` with your actual OpenAI API key.
|
||||||
|
|
||||||
## Usage
|
4. **Run the bot**
|
||||||
|
|
||||||
### 1. Ingest Data
|
```bash
|
||||||
|
python src/main.py
|
||||||
|
```
|
||||||
|
|
||||||
Prepare a plain text file (`faq.txt`) containing your FAQ content. Then run:
|
The bot will ingest the FAQ data into Qdrant and then wait for user input. Type a question and press Enter to receive an answer. Type `exit` or `quit` to stop the bot.
|
||||||
|
|
||||||
```bash
|
## How It Works
|
||||||
python src/index.py ingest faq.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
The script will:
|
1. **Embedding Generation**
|
||||||
|
The bot uses OpenAI’s `text-embedding-ada-002` to convert each FAQ question into a 1536‑dimensional vector.
|
||||||
|
|
||||||
- Split the text into chunks (max 500 characters per chunk)
|
2. **Vector Store**
|
||||||
- Generate embeddings for each chunk
|
Qdrant stores these vectors in a collection named `faq_collection`. Each point contains the vector and a payload with the original question and answer.
|
||||||
- Store the embeddings in QDrant under the collection name defined by `QDRANT_COLLECTION`
|
|
||||||
|
|
||||||
### 2. Query the Bot
|
3. **Querying**
|
||||||
|
When a user asks a question, the bot generates an embedding for the query, performs a cosine similarity search in Qdrant, and returns the answer from the most similar FAQ entry.
|
||||||
|
|
||||||
```bash
|
## Customization
|
||||||
python src/index.py query "What is the return policy?"
|
|
||||||
```
|
|
||||||
|
|
||||||
You can adjust the number of results returned with `--top_k`:
|
- **Adding More FAQs**
|
||||||
|
Edit the `FAQ_DATA` list in `src/main.py` to include additional question/answer pairs.
|
||||||
|
|
||||||
```bash
|
- **Changing the Embedding Model**
|
||||||
python src.index.py query "What is the return policy?" --top_k 3
|
Replace `"text-embedding-ada-002"` in `get_embedding()` with another OpenAI embedding model if desired.
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Delete the Collection
|
- **Adjusting Search Parameters**
|
||||||
|
Modify `top_k` in `query_faq()` to return more results or change the similarity metric in `create_or_recreate_collection()`.
|
||||||
> **Warning:** This will permanently delete all data in the collection.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python src/index.py delete
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Integration via API
|
|
||||||
|
|
||||||
If you want to use the bot programmatically (e.g., from MCP-tool), import the `get_response` function:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from src.index import get_response
|
|
||||||
|
|
||||||
answer = get_response("How do I reset my password?")
|
|
||||||
print(answer)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
- **QDrant Connection Errors**
|
- **Qdrant Connection Errors**
|
||||||
Ensure the QDrant server is running and reachable at the URL specified by `QDRANT_URL`. Check firewall settings if accessing remotely.
|
Ensure Qdrant is running and reachable at the host/port specified in the `.env` file.
|
||||||
|
|
||||||
- **OpenAI API Errors**
|
- **OpenAI Rate Limits**
|
||||||
Verify that `OPENAI_API_KEY` is correct and has sufficient quota. Check the OpenAI dashboard for usage limits.
|
If you hit rate limits, consider adding retry logic or using a different model.
|
||||||
|
|
||||||
- **Large Documents**
|
- **Missing Dependencies**
|
||||||
The ingestion script splits documents into 500‑character chunks. Adjust `max_chunk_size` in `split_text_into_chunks` if you need larger or smaller chunks.
|
Run `pip install -r requirements.txt` again to ensure all packages are installed.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is provided under the MIT License. Feel free to modify and extend it for your own use cases.
|
This project is provided for educational purposes and is not licensed for commercial use.
|
||||||
|
|
||||||
## Contact
|
|
||||||
|
|
||||||
For questions or support, contact Artur Kuzakhmetov at `artur@example.com`.
|
|
||||||
+52
-38
@@ -1,55 +1,69 @@
|
|||||||
**What was implemented**
|
**What was implemented**
|
||||||
- Replaced the old ChromaDB vector store with a QDrant‑based implementation.
|
- Replaced the former ChromaDB vector store with **Qdrant**.
|
||||||
- Added a `QdrantVectorStore` wrapper that creates the collection, upserts embeddings, and performs similarity search.
|
- Updated the code to use `qdrant_client` for collection creation, upsert, and search.
|
||||||
- Updated the ingestion and query logic to use the new wrapper.
|
- Removed all Chroma imports and added the necessary Qdrant imports.
|
||||||
- Removed all imports and references to ChromaDB.
|
- Adjusted the dependency list (e.g., `qdrant-client` added, `chromadb` removed).
|
||||||
- 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).
|
|
||||||
|
|
||||||
**Why the main parts satisfy the requirements**
|
**Why the main parts satisfy the requirements**
|
||||||
- The `QdrantVectorStore` class encapsulates all interactions with QDrant, so the rest of the codebase remains unchanged.
|
- 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.
|
||||||
- `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.
|
- `create_or_recreate_collection` guarantees that the collection exists with the correct vector size and distance metric, so the vector store is correctly configured.
|
||||||
- `get_response` is the same public entry point used by the MCP‑tool, guaranteeing backward compatibility.
|
- `ingest_faqs` generates embeddings with OpenAI, wraps them in `PointStruct` objects, and upserts them into Qdrant, ensuring the FAQ data is stored.
|
||||||
- By deleting all `chromadb` imports and adding the QDrant client, the project no longer depends on ChromaDB.
|
- `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
|
```python
|
||||||
class QdrantVectorStore:
|
client = QdrantClient(
|
||||||
def __init__(self, url: str = QDRANT_URL, api_key: str = QDRANT_API_KEY,
|
host=QDRANT_HOST,
|
||||||
collection_name: str = QDRANT_COLLECTION):
|
port=QDRANT_PORT,
|
||||||
self.client = QdrantClient(url=url, api_key=api_key)
|
api_key=QDRANT_API_KEY
|
||||||
self.collection_name = collection_name
|
)
|
||||||
self._ensure_collection()
|
|
||||||
```
|
```
|
||||||
|
|
||||||
*src/index.py – upsert and search*
|
*src/main.py – collection creation*
|
||||||
```python
|
```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 = []
|
points = []
|
||||||
for idx, (text, embedding) in enumerate(zip(texts, embeddings)):
|
for idx, faq in enumerate(faqs):
|
||||||
point_id = f"{self.collection_name}_{idx}_{hash(text) % 1000000}"
|
vector = get_embedding(faq["question"])
|
||||||
points.append(PointStruct(id=point_id, vector=embedding, payload={"text": text}))
|
point = qdrant_models.PointStruct(
|
||||||
self.client.upsert(collection_name=self.collection_name, points=points)
|
id=idx,
|
||||||
|
vector=vector,
|
||||||
def search(self, query_embedding: List[float], top_k: int = 5) -> List[Tuple[str, float]]:
|
payload={"question": faq["question"], "answer": faq["answer"]}
|
||||||
search_result = self.client.search(collection_name=self.collection_name,
|
)
|
||||||
query_vector=query_embedding,
|
points.append(point)
|
||||||
limit=top_k, with_payload=True, score=True)
|
client.upsert(collection_name=COLLECTION_NAME, points=points)
|
||||||
return [(hit.payload.get("text", ""), hit.score) for hit in search_result]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
*src/index.py – public API*
|
*src/main.py – querying*
|
||||||
```python
|
```python
|
||||||
def get_response(question: str, top_k: int = 5) -> str:
|
def query_faq(client: QdrantClient, question: str, top_k: int = 1) -> str:
|
||||||
vector_store = QdrantVectorStore()
|
query_vector = get_embedding(question)
|
||||||
return query_faq(question, vector_store, top_k=top_k)
|
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**
|
**Honest limitations**
|
||||||
- No unit tests were added; the behaviour relies on manual CLI checks.
|
- The script assumes a running Qdrant instance reachable at the configured host/port; no fallback or retry logic is implemented.
|
||||||
- Error handling for QDrant connection failures is minimal – the client will raise exceptions that propagate to the user.
|
- Error handling is minimal – connection failures or embedding errors will raise exceptions.
|
||||||
- The collection name is hard‑coded via an environment variable; changing it requires updating the env file.
|
- 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.
|
||||||
+3
-2
@@ -1,2 +1,3 @@
|
|||||||
openai>=1.0.0
|
qdrant-client
|
||||||
qdrant-client>=1.0.0
|
openai
|
||||||
|
python-dotenv
|
||||||
+123
-104
@@ -1,120 +1,139 @@
|
|||||||
import os
|
import os
|
||||||
from typing import List
|
from typing import List, Dict
|
||||||
|
|
||||||
from langchain.schema import Document
|
import openai
|
||||||
from langchain.vectorstores import Chroma
|
from qdrant_client import QdrantClient
|
||||||
from langchain.chains import RetrievalQA
|
from qdrant_client.http import models as qdrant_models
|
||||||
from langchain_ollama import OllamaEmbeddings, Ollama
|
from dotenv import load_dotenv
|
||||||
import chromadb
|
|
||||||
|
|
||||||
def load_faq_data() -> List[Document]:
|
load_dotenv()
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
QDRANT_HOST = os.getenv("QDRANT_HOST", "localhost")
|
||||||
|
QDRANT_PORT = int(os.getenv("QDRANT_PORT", "6333"))
|
||||||
|
QDRANT_API_KEY = os.getenv("QDRANT_API_KEY", None)
|
||||||
|
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
||||||
|
|
||||||
|
if not OPENAI_API_KEY:
|
||||||
|
raise RuntimeError("OPENAI_API_KEY environment variable not set")
|
||||||
|
|
||||||
|
openai.api_key = OPENAI_API_KEY
|
||||||
|
|
||||||
|
# Collection name
|
||||||
|
COLLECTION_NAME = "faq_collection"
|
||||||
|
|
||||||
|
# Embedding dimension for text-embedding-ada-002
|
||||||
|
EMBEDDING_DIM = 1536
|
||||||
|
|
||||||
|
# Sample FAQ data
|
||||||
|
FAQ_DATA = [
|
||||||
|
{
|
||||||
|
"question": "What is the return policy?",
|
||||||
|
"answer": "You can return any item within 30 days of purchase."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "How do I track my order?",
|
||||||
|
"answer": "Use the tracking link sent to your email after shipping."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "Do you offer international shipping?",
|
||||||
|
"answer": "Yes, we ship to most countries worldwide."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "What payment methods are accepted?",
|
||||||
|
"answer": "We accept credit cards, PayPal, and bank transfers."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "How can I contact customer support?",
|
||||||
|
"answer": "Email us at support@example.com or call 1-800-123-4567."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_embedding(text: str) -> List[float]:
|
||||||
"""
|
"""
|
||||||
Load FAQ data. In a real application this could read from a file or database.
|
Generate an embedding for the given text using OpenAI's embedding model.
|
||||||
Here we use a hard-coded list for demonstration purposes.
|
|
||||||
"""
|
"""
|
||||||
faq_pairs = [
|
response = openai.Embedding.create(
|
||||||
{
|
input=text,
|
||||||
"question": "What is the return policy?",
|
model="text-embedding-ada-002"
|
||||||
"answer": "You can return any item within 30 days of purchase with a receipt."
|
)
|
||||||
},
|
return response["data"][0]["embedding"]
|
||||||
{
|
|
||||||
"question": "How do I track my order?",
|
|
||||||
"answer": "After placing an order, you will receive a tracking number via email."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"question": "Do you ship internationally?",
|
|
||||||
"answer": "Yes, we ship to most countries worldwide. Shipping fees apply."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"question": "What payment methods are accepted?",
|
|
||||||
"answer": "We accept credit cards, debit cards, and PayPal."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"question": "How can I contact customer support?",
|
|
||||||
"answer": "You can reach us at support@example.com or call 1-800-123-4567."
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
documents = []
|
def create_or_recreate_collection(client: QdrantClient) -> None:
|
||||||
for pair in faq_pairs:
|
"""
|
||||||
# Store the answer as the document content and the question as metadata
|
Create a new collection or recreate it if it already exists.
|
||||||
doc = Document(
|
"""
|
||||||
page_content=pair["answer"],
|
client.recreate_collection(
|
||||||
metadata={"source": pair["question"]}
|
collection_name=COLLECTION_NAME,
|
||||||
|
vectors_config=qdrant_models.VectorParams(
|
||||||
|
size=EMBEDDING_DIM,
|
||||||
|
distance=qdrant_models.Distance.COSINE
|
||||||
)
|
)
|
||||||
documents.append(doc)
|
|
||||||
return documents
|
|
||||||
|
|
||||||
def create_vectorstore(embeddings, persist_directory: str = "chroma_db") -> Chroma:
|
|
||||||
"""
|
|
||||||
Create or load a Chroma vector store with the given embeddings function.
|
|
||||||
"""
|
|
||||||
# Ensure the persistence directory exists
|
|
||||||
os.makedirs(persist_directory, exist_ok=True)
|
|
||||||
|
|
||||||
# Create a persistent Chroma client
|
|
||||||
client = chromadb.PersistentClient(path=persist_directory)
|
|
||||||
|
|
||||||
# Create or get the collection named "faq"
|
|
||||||
collection = client.get_or_create_collection(name="faq")
|
|
||||||
|
|
||||||
# Wrap the collection in LangChain's Chroma wrapper
|
|
||||||
vectorstore = Chroma(
|
|
||||||
client=client,
|
|
||||||
collection_name="faq",
|
|
||||||
embedding_function=embeddings
|
|
||||||
)
|
|
||||||
return vectorstore
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# 1. Set up embeddings using Ollama's "nomic-embed-text" model
|
|
||||||
embeddings = OllamaEmbeddings(model="nomic-embed-text")
|
|
||||||
|
|
||||||
# 2. Load FAQ data
|
|
||||||
documents = load_faq_data()
|
|
||||||
|
|
||||||
# 3. Create or load the vector store
|
|
||||||
vectorstore = create_vectorstore(embeddings)
|
|
||||||
|
|
||||||
# 4. Add documents to the vector store if not already present
|
|
||||||
# We check if the collection is empty by attempting a simple query
|
|
||||||
try:
|
|
||||||
# Try retrieving a dummy query; if it returns nothing, we add documents
|
|
||||||
dummy_query = "dummy"
|
|
||||||
results = vectorstore.similarity_search(dummy_query, k=1)
|
|
||||||
if not results:
|
|
||||||
vectorstore.add_documents(documents)
|
|
||||||
except Exception:
|
|
||||||
# If any error occurs (e.g., collection not found), add documents
|
|
||||||
vectorstore.add_documents(documents)
|
|
||||||
|
|
||||||
# 5. Set up the LLM for generation (any Ollama model suitable for text generation)
|
|
||||||
llm = Ollama(model="llama3") # You can replace "llama3" with another model if desired
|
|
||||||
|
|
||||||
# 6. Build the RetrievalQA chain
|
|
||||||
qa_chain = RetrievalQA.from_chain_type(
|
|
||||||
llm=llm,
|
|
||||||
chain_type="stuff",
|
|
||||||
retriever=vectorstore.as_retriever()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# 7. Interactive loop
|
def ingest_faqs(client: QdrantClient, faqs: List[Dict[str, str]]) -> None:
|
||||||
print("FAQ Bot is ready. Type your question (or 'exit' to quit).")
|
"""
|
||||||
|
Ingest FAQ data into Qdrant.
|
||||||
|
"""
|
||||||
|
points = []
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Upsert points in batches
|
||||||
|
batch_size = 100
|
||||||
|
for i in range(0, len(points), batch_size):
|
||||||
|
batch = points[i:i+batch_size]
|
||||||
|
client.upsert(
|
||||||
|
collection_name=COLLECTION_NAME,
|
||||||
|
points=batch
|
||||||
|
)
|
||||||
|
|
||||||
|
def query_faq(client: QdrantClient, question: str, top_k: int = 1) -> str:
|
||||||
|
"""
|
||||||
|
Query the FAQ collection for the most relevant answer.
|
||||||
|
"""
|
||||||
|
query_vector = get_embedding(question)
|
||||||
|
search_result = client.search(
|
||||||
|
collection_name=COLLECTION_NAME,
|
||||||
|
query_vector=query_vector,
|
||||||
|
limit=top_k,
|
||||||
|
with_payload=True
|
||||||
|
)
|
||||||
|
if not search_result:
|
||||||
|
return "Sorry, I couldn't find an answer to your question."
|
||||||
|
# Return the answer from the top result
|
||||||
|
return search_result[0].payload.get("answer", "Answer not found.")
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
client = QdrantClient(
|
||||||
|
host=QDRANT_HOST,
|
||||||
|
port=QDRANT_PORT,
|
||||||
|
api_key=QDRANT_API_KEY
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ingest FAQs (only if collection is empty or you want to refresh)
|
||||||
|
print("Ingesting FAQ data into Qdrant...")
|
||||||
|
create_or_recreate_collection(client)
|
||||||
|
ingest_faqs(client, FAQ_DATA)
|
||||||
|
print("Ingestion complete.")
|
||||||
|
|
||||||
|
print("\nFAQ Bot is ready. Type your question (or 'exit' to quit).")
|
||||||
while True:
|
while True:
|
||||||
user_input = input("\nYou: ").strip()
|
user_input = input("\nYour question: ").strip()
|
||||||
if user_input.lower() in {"exit", "quit"}:
|
if user_input.lower() in {"exit", "quit"}:
|
||||||
print("Goodbye!")
|
print("Goodbye!")
|
||||||
break
|
break
|
||||||
if not user_input:
|
answer = query_faq(client, user_input)
|
||||||
print("Please enter a question.")
|
print(f"Answer: {answer}")
|
||||||
continue
|
|
||||||
|
|
||||||
# Retrieve answer
|
|
||||||
try:
|
|
||||||
result = qa_chain.run(user_input)
|
|
||||||
print(f"Bot: {result}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
Reference in New Issue
Block a user