Chương trình này là một ứng dụng chatbot mạnh mẽ, cho phép người dùng tương tác với các tài liệu nội bộ thông qua giao diện Telegram. Nó tích hợp các công nghệ tiên tiến như Large Language Model (LLM), Vector Database (ChromaDB), và các thư viện xử lý tài liệu, cung cấp khả năng tìm kiếm ngữ nghĩa và trả lời câu hỏi dựa trên nội dung của tài liệu đã tải lên. Chương trình này sử dụng thư viện Watchdog để theo dõi thư mục tài liệu, đảm bảo nội dung mới được cập nhật vào hệ thống một cách tự động.
1. Kiến Trúc và Các Thành Phần Chính:
- Theo dõi Thư mục Tài liệu (Watchdog):
- Thư viện
watchdog
được sử dụng để theo dõi các thay đổi trong thư mụcdocuments
. Khi có tệp tin mới được thêm vào hoặc thay đổi, chương trình tự động kích hoạt quy trình xử lý tài liệu. - Điểm mạnh: Tự động hóa quy trình cập nhật tài liệu, giảm thiểu công sức quản lý thủ công.
- Điểm cần lưu ý: Cần xử lý các trường hợp lỗi, tệp tin lỗi, hoặc các sự kiện thay đổi file không mong muốn.
- Thư viện
- Xử Lý Tài Liệu:
- Hỗ trợ tải và xử lý các tệp PDF và DOCX sử dụng thư viện
PyPDFLoader
vàDocx2txtLoader
. - Sử dụng
RecursiveCharacterTextSplitter
để chia nhỏ tài liệu thành các đoạn văn bản nhỏ hơn để tối ưu hiệu quả tìm kiếm. - Điểm mạnh: Hỗ trợ nhiều định dạng tài liệu phổ biến, tối ưu hóa việc tìm kiếm thông tin nhờ việc chia nhỏ văn bản.
- Điểm cần lưu ý: Có thể mở rộng hỗ trợ nhiều định dạng hơn và tối ưu tham số chia nhỏ văn bản.
- Hỗ trợ tải và xử lý các tệp PDF và DOCX sử dụng thư viện
- Vector Database (ChromaDB):
- Sử dụng
Chroma
để lưu trữ vector embeddings của các đoạn văn bản, cho phép tìm kiếm ngữ nghĩa hiệu quả. - Điểm mạnh: Tìm kiếm ngữ nghĩa hiệu quả, cung cấp kết quả tìm kiếm chính xác dựa trên ý nghĩa của câu hỏi.
- Điểm cần lưu ý: Cần quản lý và tối ưu việc lưu trữ vector database.
- Sử dụng
- Mô hình Embedding (Hugging Face Embeddings):
- Sử dụng
HuggingFaceEmbeddings
để chuyển đổi văn bản thành các vector embedding. Việc sử dụng mô hìnhintfloat/multilingual-e5-large
hỗ trợ đa ngôn ngữ, cho phép chương trình xử lý nhiều ngôn ngữ khác nhau. - Điểm mạnh: Mô hình đa ngôn ngữ mạnh mẽ, cung cấp khả năng chuyển đổi văn bản tốt.
- Điểm cần lưu ý: Có thể thử nghiệm các mô hình embedding khác để tối ưu hiệu suất.
- Sử dụng
- Mô hình Ngôn ngữ Lớn (Ollama LLM):
- Sử dụng
OllamaLLM
để giao tiếp với LLM. Chương trình hiện đang hỗ trợ mô hìnhllama3:latest
. - Điểm mạnh: Cung cấp khả năng trả lời câu hỏi thông minh dựa trên nội dung tài liệu, hỗ trợ các mô hình LLM khác nhau.
- Điểm cần lưu ý: Cần tùy chỉnh prompt để tối ưu hiệu suất và độ chính xác.
- Sử dụng
- Kết nối Telegram Bot:
- Sử dụng thư viện
python-telegram-bot
để tạo giao diện chatbot trên Telegram. - Điểm mạnh: Dễ sử dụng, dễ tiếp cận, giao diện thân thiện với người dùng.
- Điểm cần lưu ý: Cần quản lý bot token cẩn thận và xử lý các lỗi kết nối.
- Sử dụng thư viện
- Quản lý Trạng Thái Hội thoại:
- Sử dụng
ConversationHandler
để quản lý các trạng thái của quá trình tải tệp tin lên. - Điểm mạnh: Quản lý luồng hội thoại rõ ràng, cải thiện trải nghiệm người dùng.
- Điểm cần lưu ý: Cần mở rộng để quản lý nhiều luồng hội thoại khác nhau.
- Quản lý Lịch sử Hội thoại (ConversationBufferMemory):
- Sử dụng
ConversationBufferMemory
để lưu trữ lịch sử hội thoại, giúp LLM trả lời câu hỏi theo ngữ cảnh. - Điểm mạnh: Tăng tính tự nhiên của cuộc trò chuyện, giúp LLM nhớ các thông tin đã được đề cập trước đó.
- Điểm cần lưu ý: Cần quản lý dung lượng bộ nhớ để tránh các vấn đề về hiệu suất.
- Sử dụng
2. Quy Trình Hoạt Động:
- Khởi Tạo: Chương trình khởi tạo các thành phần chính, bao gồm LLM, vector database, embedding model và kết nối Telegram Bot.
- Theo Dõi Tài Liệu: Luồng theo dõi thư mục sẽ bắt đầu theo dõi sự thay đổi. Khi có tài liệu mới hoặc chỉnh sửa, chương trình sẽ:
- Tải tài liệu sử dụng thư viện tương ứng (PDF, DOCX).
- Chia nhỏ tài liệu thành các đoạn văn bản nhỏ hơn.
- Tạo vector embedding cho từng đoạn văn bản.
- Lưu trữ vector embedding vào ChromaDB.
- Tương Tác với Người Dùng:
- Người dùng gửi tin nhắn qua Telegram.
- Chương trình sử dụng LLM để tạo câu trả lời.
- Vector database được sử dụng để tìm kiếm các đoạn văn bản liên quan đến câu hỏi của người dùng.
- Câu trả lời và các tài liệu tham khảo được gửi lại cho người dùng.
- Tải Tài Liệu:
- Người dùng có thể sử dụng lệnh
/upload
để tải tài liệu trực tiếp lên bot thông qua Telegram. - Chương trình sẽ tải, xử lý, và cập nhật vào vector database.
- Người dùng có thể sử dụng lệnh
3. Phân Tích Chi Tiết:
- Ưu điểm:
- Tự động hóa: Tự động cập nhật nội dung tài liệu khi có thay đổi.
- Tìm kiếm ngữ nghĩa: Cung cấp kết quả tìm kiếm chính xác dựa trên ý nghĩa của câu hỏi, không chỉ dựa trên từ khóa.
- Tương tác tự nhiên: Sử dụng LLM để tạo câu trả lời mạch lạc, tự nhiên.
- Dễ sử dụng: Giao diện Telegram thân thiện và dễ tiếp cận.
- Hỗ trợ đa ngôn ngữ: Mô hình embedding đa ngôn ngữ giúp chương trình hoạt động hiệu quả với nhiều ngôn ngữ khác nhau.
- Hạn chế:
- Yêu cầu tài nguyên: Mô hình LLM và vector database có thể tốn tài nguyên, cần có cấu hình máy tính đủ mạnh để chạy trơn tru.
- Quản lý lỗi: Cần thêm các cơ chế xử lý lỗi để đảm bảo chương trình hoạt động ổn định.
- Cải thiện Prompt: Prompt cần được điều chỉnh để tối ưu cho ngữ cảnh của từng loại tài liệu.
- Khả năng mở rộng: Cần mở rộng để hỗ trợ nhiều định dạng tài liệu và nhiều mô hình LLM hơn.
- Quản lý bộ nhớ: Cần theo dõi và quản lý việc sử dụng bộ nhớ, đặc biệt là khi lượng tài liệu lớn.
4. Các Gợi Ý Cải Tiến:
- Hỗ trợ thêm định dạng tài liệu: Mở rộng hỗ trợ các định dạng khác như TXT, CSV, HTML.
- Tối ưu prompt cho LLM: Tạo các prompt khác nhau cho các loại tài liệu khác nhau để tăng độ chính xác.
- Cải thiện xử lý lỗi: Thêm các cơ chế xử lý lỗi mạnh mẽ hơn, bao gồm logging chi tiết và thông báo lỗi cho người dùng.
- Hỗ trợ nhiều mô hình LLM: Cho phép người dùng lựa chọn mô hình LLM khác nhau để tùy biến theo nhu cầu.
- Nâng cao tính bảo mật: Mã hóa thông tin nhạy cảm (nếu có) và quản lý quyền truy cập vào tài liệu.
- Theo dõi hiệu suất: Ghi lại thời gian xử lý, số lần tìm kiếm thành công, để tối ưu hóa hiệu suất.
- Giao diện web: Phát triển giao diện web để người dùng có thể sử dụng chatbot trên nhiều nền tảng.
- Cải thiện trải nghiệm người dùng: Thêm các tính năng như tải nhiều tệp tin cùng lúc, hiển thị nguồn gốc của thông tin.
- Tích hợp các công cụ khác: Tích hợp với các công cụ khác như Jira, Confluence, Slack để tạo thành một hệ sinh thái làm việc.
5. Kết Luận:
Chương trình này là một giải pháp hiệu quả cho việc xây dựng chatbot tài liệu nội bộ. Với việc tích hợp các công nghệ hiện đại như LLM, Vector Database, nó mang lại trải nghiệm tìm kiếm và tương tác tài liệu mạnh mẽ. Mặc dù còn một số hạn chế, với những cải tiến được đề xuất, chương trình có tiềm năng lớn để trở thành một công cụ hữu ích trong môi trường làm việc chuyên nghiệp.
Bài viết này cung cấp một cái nhìn chi tiết về chương trình của bạn, giúp bạn và người đọc hiểu rõ hơn về cách thức hoạt động, ưu nhược điểm, và các hướng phát triển tiềm năng. Hy vọng nó sẽ hữu ích cho bạn!
import os
import time
import logging
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from threading import Thread
from langchain_ollama import OllamaLLM as OllamaLLM
from langchain_ollama import OllamaEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from telegram import Update
from telegram.ext import Updater, CommandHandler, MessageHandler, filters, CallbackContext, ApplicationBuilder, ConversationHandler
from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove
from langchain_community.document_loaders import PyPDFLoader, Docx2txtLoader
from langchain.prompts import PromptTemplate
from langchain.memory import ConversationBufferMemory
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.chains import ConversationalRetrievalChain
# Cấu hình Ollama
OLLAMA_BASE_URL = "http://localhost:11434"
# MODEL_NAME = "qwen2.5:14b"
MODEL_NAME = "llama3:latest"
# Load env
from dotenv import load_dotenv
load_dotenv()
# Cấu hình Telegram Bot
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
# Đường dẫn đến thư mục tài liệu
DOCUMENT_FOLDER = "documents"
# Ensure documents folder exists
os.makedirs(DOCUMENT_FOLDER, exist_ok=True)
# Vector Database (ChromaDB)
PERSIST_DIRECTORY = "db5"
# Configure logging
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
# Khởi tạo Ollama LLM và Embedding (Cập nhật)
ollama_llm = OllamaLLM(base_url=OLLAMA_BASE_URL, model=MODEL_NAME)
# ollama_embedding = OllamaEmbeddings(base_url=OLLAMA_BASE_URL, model=MODEL_NAME)
# Use Hugging Face Embedding
model_name = "intfloat/multilingual-e5-large"
model_kwargs = {'device': 'cpu'}
encode_kwargs = {'normalize_embeddings': False}
ollama_embedding = HuggingFaceEmbeddings(
model_name=model_name,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs
)
# Khởi tạo ChromaDB (Cập nhật)
vectordb = Chroma(persist_directory=PERSIST_DIRECTORY, embedding_function=ollama_embedding)
# Retrieval Chain
prompt_template = """
You are a helpful assistant that can extract information from provided documents. Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer. If the question is not about the provided context, say "I cannot answer this question."
Please cite the source documents in your response.
Context:
{context}
Question: {question}
Helpful Answer:
"""
PROMPT = PromptTemplate(template=prompt_template, input_variables=["context", "question"])
retriever = vectordb.as_retriever(search_kwargs={"k": 3})
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True, output_key="answer")
qa_chain = ConversationalRetrievalChain.from_llm(
llm=ollama_llm,
retriever=retriever,
memory=memory,
combine_docs_chain_kwargs={"prompt": PROMPT},
return_source_documents=True,
)
# Memory
chat_history = {}
# Upload states
UPLOAD_FILE = 1
# Hàm Xử Lý Tài Liệu
def process_document(file_path):
try:
documents = []
if file_path.lower().endswith(".pdf"):
loader = PyPDFLoader(file_path)
documents.extend(loader.load())
elif file_path.lower().endswith(".docx"):
loader = Docx2txtLoader(file_path)
documents.extend(loader.load())
if documents:
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
texts = text_splitter.split_documents(documents)
logging.info(f"Total documents: {len(documents)}") # Log document count
logging.info(f"Total text chunks: {len(texts)}") # Log text chunk count
for i, text in enumerate(texts):
logging.debug(f"Text chunk {i}: {text}") # Log the actual text chunks
# Add to vector db
vectordb.add_documents(texts)
print(f"Đã cập nhật tài liệu: {file_path}")
else:
print(f"Không thể xử lý tài liệu: {file_path}")
except Exception as e:
logging.error(f"Lỗi xử lý tài liệu {file_path}: {e}", exc_info=True)
# Watchdog Event Handler
class DocumentChangeHandler(FileSystemEventHandler):
def on_created(self, event):
if not event.is_directory:
file_path = event.src_path
process_document(file_path)
def on_modified(self, event):
if not event.is_directory:
file_path = event.src_path
process_document(file_path)
# Hàm Chạy Theo Dõi Thư Mục
def watch_document_folder():
event_handler = DocumentChangeHandler()
observer = Observer()
observer.schedule(event_handler, DOCUMENT_FOLDER, recursive=True)
observer.start()
try:
while True:
time.sleep(5)
except KeyboardInterrupt:
observer.stop()
observer.join()
async def handle_message(update: Update, context: CallbackContext):
user_id = update.message.from_user.id
user_question = update.message.text
try:
# Add retrieval logging here
response = await qa_chain.ainvoke({"question": user_question})
retrieved_docs = response['source_documents']
logging.info(f"Retrieved Documents for Query: {user_question}")
for doc in retrieved_docs:
logging.info(f" - {doc.page_content} - Source: {doc.metadata}")
await context.bot.send_message(chat_id=update.effective_chat.id, text=response['answer'])
except Exception as e:
logging.error(f"Lỗi xử lý tin nhắn: {e}", exc_info=True)
await context.bot.send_message(
chat_id=update.effective_chat.id, text="Đã xảy ra lỗi khi xử lý tin nhắn. Vui lòng thử lại sau."
)
def start(update: Update, context: CallbackContext):
context.bot.send_message(
chat_id=update.effective_chat.id,
text="Chào bạn, mình là chatbot hỗ trợ tìm kiếm tài liệu nội bộ! Sử dụng /upload để tải tệp tin lên hệ thống."
)
async def upload_start(update: Update, context: CallbackContext):
await update.message.reply_text(
"Vui lòng gửi tệp tin PDF hoặc DOCX bạn muốn tải lên. Chỉ chấp nhận các tệp có định dạng .pdf hoặc .docx."
)
return UPLOAD_FILE
async def handle_upload(update: Update, context: CallbackContext):
try:
# Check if document is uploaded
if not update.message.document:
await update.message.reply_text("Vui lòng gửi một tệp tin.")
return ConversationHandler.END
# Get file
document = update.message.document
# Check file extension
if not document.file_name.lower().endswith(('.pdf', '.docx')):
await update.message.reply_text("Chỉ chấp nhận các tệp có định dạng .pdf hoặc .docx.")
return ConversationHandler.END
# Download file
file = await update.message.document.get_file()
file_path = os.path.join(DOCUMENT_FOLDER, document.file_name)
await file.download_to_drive(file_path)
# Process document
process_document(file_path)
await update.message.reply_text(f"Tệp {document.file_name} đã được tải lên và xử lý thành công!")
except Exception as e:
logging.error(f"Lỗi tải tệp: {e}", exc_info=True)
await update.message.reply_text("Đã xảy ra lỗi khi tải tệp. Vui lòng thử lại.")
return ConversationHandler.END
def cancel(update: Update, context: CallbackContext):
update.message.reply_text(
"Hủy tải tệp tin.", reply_markup=ReplyKeyboardRemove()
)
return ConversationHandler.END
def main():
# Tạo luồng cho việc theo dõi thư mục
watch_thread = Thread(target=watch_document_folder, daemon=True)
watch_thread.start()
# Khởi tạo Telegram Bot (Cập nhật)
application = ApplicationBuilder().token(TELEGRAM_BOT_TOKEN).build()
# Tạo ConversationHandler cho chức năng upload
conv_handler = ConversationHandler(
entry_points=[CommandHandler('upload', upload_start)],
states={
UPLOAD_FILE: [
MessageHandler(
filters.Document.PDF | filters.Document.DOCX,
handle_upload
)
],
},
fallbacks=[CommandHandler('cancel', cancel)]
)
application.add_handler(CommandHandler("start", start))
application.add_handler(conv_handler)
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
application.run_polling()
if __name__ == '__main__':
# Load dữ liệu ban đầu
for filename in os.listdir(DOCUMENT_FOLDER):
file_path = os.path.join(DOCUMENT_FOLDER, filename)
process_document(file_path)
main()