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

This commit is contained in:
2026-07-01 11:01:06 +03:00
parent 2cf8df92ed
commit ddcd1f3423
5 changed files with 202 additions and 126 deletions
+65 -54
View File
@@ -1,67 +1,78 @@
#!/usr/bin/env python3
"""
FAQ Bot using ChromaDB and LangChain
"""
import os
import sys
import argparse
from pathlib import Path
from chromadb import Client
from chromadb.config import Settings
from dotenv import load_dotenv
from langchain_community.document_loaders import TextLoader
from langchain_community.embeddings import OllamaEmbeddings
from langchain_community.vectorstores.chromadb import Chroma
from langchain_ollama import Ollama
from langchain_community.tools.mcp_tool import MCPTool
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.llms.openai import OpenAIChat
from langchain.chains import RetrievalQA
from langchain.vectorstores import Chroma
# Load environment variables (if any)
load_dotenv()
from ingest import ingest_faq
from retriever import get_answer
# Directory containing FAQ documents (plain text files)
DATA_DIR = Path("data")
# Directory where ChromaDB will persist its data
CHROMA_DIR = Path("chroma_db")
def init_chroma(collection_name: str) -> Client:
# Ensure directories exist
DATA_DIR.mkdir(parents=True, exist_ok=True)
CHROMA_DIR.mkdir(parents=True, exist_ok=True)
# Load all text files from the data directory
documents = []
for txt_file in DATA_DIR.glob("*.txt"):
loader = TextLoader(str(txt_file))
documents.extend(loader.load())
# Create embeddings using Ollama's nomic-embed-text model
embeddings = OllamaEmbeddings(model="nomic-embed-text")
# Create or load the ChromaDB vector store
vectorstore = Chroma.from_documents(
documents,
embeddings,
persist_directory=str(CHROMA_DIR),
)
# Persist the vector store to disk
vectorstore.persist()
# Initialize the Ollama LLM for generation (e.g., llama3)
llm = Ollama(model="llama3")
# Instantiate the MCPTool with the LLM and vector store
mcp_tool = MCPTool(llm=llm, vectorstore=vectorstore)
def answer_question(question: str) -> str:
"""
Initialize a ChromaDB client and create a collection if it does not exist.
Answer a question using the MCPTool, which internally retrieves relevant
documents from the ChromaDB vector store and generates a response with
the Ollama LLM.
"""
client = Client(Settings(
chroma_db_impl="duckdb+parquet",
persist_directory="chromadb",
))
# Ensure collection exists
if collection_name not in client.list_collections():
client.create_collection(name=collection_name)
return client
return mcp_tool.run(question)
def main():
parser = argparse.ArgumentParser(description="FAQ Bot CLI")
subparsers = parser.add_subparsers(dest="command", required=True)
ingest_parser = subparsers.add_parser("ingest", help="Ingest FAQ file into ChromaDB")
ingest_parser.add_argument("faq_file", type=Path, help="Path to FAQ text file")
ingest_parser.add_argument("--collection", type=str, default="faq_collection", help="Chroma collection name")
query_parser = subparsers.add_parser("ask", help="Ask a question to the FAQ bot")
query_parser.add_argument("question", type=str, help="Your question")
query_parser.add_argument("--collection", type=str, default="faq_collection", help="Chroma collection name")
args = parser.parse_args()
# Ensure OpenAI API key is set
if "OPENAI_API_KEY" not in os.environ:
print("Error: OPENAI_API_KEY environment variable not set.", file=sys.stderr)
sys.exit(1)
client = init_chroma(args.collection)
if args.command == "ingest":
ingest_faq(args.faq_file, client, args.collection)
print(f"Ingestion completed. Collection '{args.collection}' updated.")
elif args.command == "ask":
answer = get_answer(args.question, client, args.collection)
print("\nAnswer:\n")
print(answer)
else:
parser.print_help()
def main() -> None:
"""
Simple command-line interface for the FAQ bot.
"""
print("FAQ Bot powered by ChromaDB and Ollama.")
print("Type 'exit' to quit.")
while True:
try:
user_input = input("\nYour question: ").strip()
except (KeyboardInterrupt, EOFError):
print("\nExiting.")
break
if user_input.lower() in {"exit", "quit"}:
print("Goodbye!")
break
if not user_input:
continue
answer = answer_question(user_input)
print(f"\nAnswer: {answer}")
if __name__ == "__main__":
main()