|
|
import gradio as gr
|
|
|
import time
|
|
|
from datetime import datetime
|
|
|
import json
|
|
|
|
|
|
from ..rag_pipeline import ChatAssistant, get_embeddings, vretrieve
|
|
|
from ..rag_pipeline import determine_action, judge_prompt, request_retrieve_prompt
|
|
|
from ..utils import load_local
|
|
|
|
|
|
|
|
|
AVAILABLE_MODELS = {
|
|
|
|
|
|
"mistral medium (mistral)": ("mistral-medium", "mistral"),
|
|
|
"mistral small (mistral)": ("mistral-small", "mistral"),
|
|
|
"llama3 8B" : ("llama3:8b", "ollama"),
|
|
|
"llama3.1 8B": ("llama3.1:8b", "ollama"),
|
|
|
"gpt-oss 20B": ("gpt-oss-20b", "ollama"),
|
|
|
"gemma3 12B": ("gemma3:12b", "ollama"),
|
|
|
|
|
|
|
|
|
}
|
|
|
DEFAULT_MODEL_KEY = "mistral medium (mistral)"
|
|
|
|
|
|
EMBEDDING_MODEL_ID = "alibaba-nlp/gte-multilingual-base"
|
|
|
VECTORSTORE_PATH = "master/knowledge/vectorstore_law"
|
|
|
LOG_FILE_PATH = "log.txt"
|
|
|
MAX_HISTORY_CONVERSATION = 1
|
|
|
|
|
|
CUSTOMER = """
|
|
|
{"name":"Đảng Cộng sản Việt Nam",
|
|
|
"birth":1930,
|
|
|
"current_position":"Đảng cầm quyền và là chính đảng duy nhất được phép hoạt động tại Việt Nam",
|
|
|
"email":"ngjabach@example.com",
|
|
|
"relations":[]}
|
|
|
"""
|
|
|
|
|
|
sys = """
|
|
|
**Persona và Vai trò:**
|
|
|
Bạn là một Trợ lý AI Phân tích Pháp lý, hoạt động như một chuyên gia trong dự án VNPT AI Agent. Vai trò của bạn là cung cấp các phân tích khách quan, chính xác và dựa trên bằng chứng, giúp bảo vệ khách hàng khỏi các rủi ro thông tin trên không gian mạng. Giọng văn của bạn phải luôn chuyên nghiệp, có thẩm quyền và hoàn toàn trung lập.
|
|
|
|
|
|
**Nhiệm vụ cốt lõi:**
|
|
|
Phân tích và đối chiếu thông tin từ các nguồn được chỉ định (bài viết, tin tức, v.v.) với một cơ sở tri thức pháp lý đã được xác thực. Dựa trên sự đối chiếu này, bạn sẽ đưa ra những đánh giá và khuyến nghị cụ thể.
|
|
|
|
|
|
**Các Nguyên tắc Bắt buộc:**
|
|
|
|
|
|
1. **Tính toàn vẹn của Nguồn:** Mọi kết luận, nhận định và phân tích của bạn phải xuất phát **DUY NHẤT** từ "Tài liệu Nguồn" được cung cấp trong mỗi yêu cầu. **Nghiêm cấm** việc sử dụng kiến thức bên ngoài, thông tin chưa được xác thực, hoặc các giả định cá nhân.
|
|
|
2. **Tổng hợp và Trích xuất:** Nhiệm vụ của bạn là tổng hợp và chắt lọc thông tin liên quan từ tài liệu nguồn để trả lời truy vấn một cách chính xác. Không diễn giải hoặc sáng tạo thông tin ngoài phạm vi tài liệu.
|
|
|
3. **Xử lý Thông tin Không Đầy đủ:** Nếu tài liệu được cung cấp không chứa thông tin cần thiết để trả lời một câu hỏi, bạn phải tuyên bố rõ ràng: *"Dựa trên các tài liệu được cung cấp, không có đủ thông tin để đưa ra nhận định về vấn đề này."* Tuyệt đối không được phỏng đoán.
|
|
|
4. **Duy trì Tính Khách quan:** Luôn giữ một lập trường phân tích, không thiên vị. Tránh mọi hình thức suy đoán, ý kiến cá nhân, hoặc sử dụng ngôn ngữ mang tính cảm tính. Mọi lập luận phải được củng cố bằng các trích dẫn hoặc tham chiếu trực tiếp từ tài liệu.
|
|
|
5. **Tập trung vào Từng Nhiệm vụ:** Chỉ sử dụng lịch sử trò chuyện để làm rõ bối cảnh của câu hỏi hiện tại. Mỗi yêu cầu phân tích là một nhiệm vụ độc lập và phải được xử lý dựa trên bộ tài liệu mới được cung cấp cho yêu cầu đó."""
|
|
|
|
|
|
|
|
|
vectorstore, docs = None, None
|
|
|
print("Initializing models and data...")
|
|
|
embedding_model = get_embeddings(EMBEDDING_MODEL_ID, show_progress=False)
|
|
|
vectorstore, docs = load_local(VECTORSTORE_PATH, embedding_model)
|
|
|
print("Initialization complete.")
|
|
|
|
|
|
|
|
|
def log(log_txt: str):
|
|
|
"""Appends a log entry to the log file."""
|
|
|
with open(LOG_FILE_PATH, "a", encoding="utf-8") as log_file:
|
|
|
log_file.write(log_txt + "\n")
|
|
|
|
|
|
def retrieve(chat_assistant: ChatAssistant, message: str, history: list):
|
|
|
history = history[-MAX_HISTORY_CONVERSATION:]
|
|
|
conversation = "".join(f"User: {user_msg}\nBot: {bot_msg}\n" for user_msg, bot_msg in history)
|
|
|
query_for_rag = conversation + f"User: {message}\nBot:"
|
|
|
|
|
|
rag_query = chat_assistant.get_response(request_retrieve_prompt.format(input=query_for_rag))
|
|
|
rag_query = rag_query[rag_query.lower().rfind("[") + 1: rag_query.rfind("]")]
|
|
|
|
|
|
if "NO NEED" not in rag_query:
|
|
|
retrieve_results = vretrieve(rag_query, vectorstore, docs, k=4, metric="mmr", threshold=0.7)
|
|
|
else:
|
|
|
retrieve_results = []
|
|
|
|
|
|
retrieved_docs = "\n".join([f"Document {i+1}:\n" + doc.page_content for i, doc in enumerate(retrieve_results)])
|
|
|
log(f"%% RAG query %%: {rag_query}")
|
|
|
log(f"%% Retrieved documents %%:\n{retrieved_docs}")
|
|
|
return retrieved_docs
|
|
|
|
|
|
|
|
|
|
|
|
def chatbot_logic(message: str, history: list, selected_model_key: str, customer_data: str):
|
|
|
"""
|
|
|
Handles the main logic for receiving a message, performing RAG, and generating a response.
|
|
|
"""
|
|
|
model_id, model_provider = AVAILABLE_MODELS[selected_model_key]
|
|
|
|
|
|
log(f"%% Current time %%: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
|
log(f"%% User message %%: {message}")
|
|
|
log(f"%% Using Model %%: {model_id} ({model_provider})")
|
|
|
|
|
|
try:
|
|
|
chat_assistant = ChatAssistant(model_id, model_provider)
|
|
|
except Exception as e:
|
|
|
yield f"Error: Could not initialize the model. Please check the ID and provider. Details: {e}"
|
|
|
return
|
|
|
|
|
|
action = chat_assistant.get_response(determine_action.format(input=message), sys)
|
|
|
action = action[action.lower().rfind("[") + 1: action.rfind("]")]
|
|
|
log(f"%% Action %%: {action}")
|
|
|
|
|
|
if "RAG" in action:
|
|
|
|
|
|
retrieved_docs = retrieve(chat_assistant, message, history)
|
|
|
final_prompt = judge_prompt.format(customer=customer_data, law_docs=retrieved_docs, post=message)
|
|
|
elif "CHAT" in action:
|
|
|
final_prompt = "".join(f"User: {user_msg}\nBot: {bot_msg}\n" for user_msg, bot_msg in history)
|
|
|
|
|
|
response = ""
|
|
|
for token in chat_assistant.get_streaming_response(final_prompt, sys):
|
|
|
response += token
|
|
|
yield response
|
|
|
log(f"%% Bot response %%: {response}")
|
|
|
log("=" * 50 + "\n\n")
|
|
|
|
|
|
|
|
|
def start_new_chat():
|
|
|
"""Clears the chatbot and input box to start a new conversation."""
|
|
|
return None, ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
customer_data = json.loads(CUSTOMER)
|
|
|
|
|
|
|
|
|
professional_theme = gr.themes.Base(
|
|
|
primary_hue=gr.themes.colors.blue,
|
|
|
secondary_hue=gr.themes.colors.sky,
|
|
|
).set(
|
|
|
button_primary_background_fill_hover='*primary_600',
|
|
|
)
|
|
|
|
|
|
with gr.Blocks(theme=professional_theme) as chatbot_ui:
|
|
|
|
|
|
customer_state = gr.State(customer_data)
|
|
|
|
|
|
gr.Markdown("# VNPT Guardian")
|
|
|
gr.Markdown("### Your Guardian AI Assistant")
|
|
|
|
|
|
with gr.Row(equal_height=True):
|
|
|
|
|
|
with gr.Column(scale=1, min_width=300):
|
|
|
model_selector = gr.Dropdown(
|
|
|
label="Select Model",
|
|
|
choices=list(AVAILABLE_MODELS.keys()),
|
|
|
value=DEFAULT_MODEL_KEY,
|
|
|
)
|
|
|
|
|
|
new_chat_btn = gr.Button("New Chat", variant="secondary")
|
|
|
|
|
|
|
|
|
edit_info_btn = gr.Button("Edit Customer Info", variant="secondary")
|
|
|
|
|
|
with gr.Accordion("Customer Details", open=False) as edit_form:
|
|
|
customer_name_input = gr.Textbox(label="Name")
|
|
|
customer_birth_input = gr.Number(label="Birth/Establish Year", precision=0)
|
|
|
customer_pos_input = gr.Textbox(label="Description", lines=3)
|
|
|
customer_email_input = gr.Textbox(label="Email")
|
|
|
save_info_btn = gr.Button("Save Changes", variant="primary")
|
|
|
|
|
|
|
|
|
gr.Markdown(
|
|
|
"--- \n"
|
|
|
"**Note:** Your conversations are saved for quality assurance."
|
|
|
)
|
|
|
|
|
|
|
|
|
with gr.Column(scale=4):
|
|
|
chatbot = gr.Chatbot(
|
|
|
label="Chat Window",
|
|
|
height=600,
|
|
|
bubble_full_width=False
|
|
|
)
|
|
|
|
|
|
msg_input = gr.Textbox(
|
|
|
label="Your Message",
|
|
|
placeholder="Type your question here and press Enter...",
|
|
|
)
|
|
|
|
|
|
|
|
|
def respond(message, chat_history, selected_model_key):
|
|
|
"""Wrapper function to connect chatbot_logic with Gradio's state."""
|
|
|
chat_history = chat_history or []
|
|
|
bot_message_stream = chatbot_logic(message, chat_history, selected_model_key, customer_data)
|
|
|
chat_history.append([message, ""])
|
|
|
for token in bot_message_stream:
|
|
|
chat_history[-1][1] = token
|
|
|
yield chat_history
|
|
|
|
|
|
|
|
|
def show_edit_form(current_data):
|
|
|
"""Populates the form with current data and makes it visible."""
|
|
|
return {
|
|
|
edit_form: gr.update(open=True),
|
|
|
customer_name_input: current_data['name'],
|
|
|
customer_birth_input: current_data['birth'],
|
|
|
customer_pos_input: current_data['current_position'],
|
|
|
customer_email_input: current_data['email'],
|
|
|
}
|
|
|
|
|
|
def save_customer_info(current_data, name, birth, position, email):
|
|
|
"""Updates the customer data state and hides the form."""
|
|
|
current_data['name'] = name
|
|
|
current_data['birth'] = int(birth)
|
|
|
current_data['current_position'] = position
|
|
|
current_data['email'] = email
|
|
|
|
|
|
gr.Info("Customer information saved successfully!")
|
|
|
|
|
|
|
|
|
return {
|
|
|
customer_state: current_data,
|
|
|
edit_form: gr.update(open=False)
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
msg_input.submit(
|
|
|
respond,
|
|
|
[msg_input, chatbot, model_selector],
|
|
|
[chatbot]
|
|
|
).then(
|
|
|
lambda: gr.update(value=""), None, [msg_input], queue=False
|
|
|
)
|
|
|
|
|
|
|
|
|
new_chat_btn.click(
|
|
|
start_new_chat,
|
|
|
inputs=None,
|
|
|
outputs=[chatbot, msg_input],
|
|
|
queue=False
|
|
|
)
|
|
|
|
|
|
|
|
|
edit_info_btn.click(
|
|
|
fn=show_edit_form,
|
|
|
inputs=[customer_state],
|
|
|
outputs=[
|
|
|
edit_form,
|
|
|
customer_name_input,
|
|
|
customer_birth_input,
|
|
|
customer_pos_input,
|
|
|
customer_email_input
|
|
|
],
|
|
|
queue=False
|
|
|
)
|
|
|
|
|
|
save_info_btn.click(
|
|
|
fn=save_customer_info,
|
|
|
inputs=[
|
|
|
customer_state,
|
|
|
customer_name_input,
|
|
|
customer_birth_input,
|
|
|
customer_pos_input,
|
|
|
customer_email_input
|
|
|
],
|
|
|
outputs=[customer_state, edit_form],
|
|
|
queue=False
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
chatbot_ui.launch(debug=True, share=True)
|
|
|
|