#!/usr/bin/env python3 # -*- coding: utf-8 -*- # PHẦN 1: CÀI ĐẶT THƯ VIỆN # ============================================================ import os import sys import warnings warnings.filterwarnings('ignore') print('='*70) print('🏛️ AI HÀNH CHÍNH CÔNG VIP - CÀI ĐẶT THƯ VIỆN') print('='*70) print() # Tạo thư mục (đường dẫn tương đối cho Hugging Face Spaces) DATA_DIR = os.path.dirname(os.path.abspath(__file__)) os.makedirs(os.path.join(DATA_DIR, 'data'), exist_ok=True) os.makedirs(os.path.join(DATA_DIR, 'models'), exist_ok=True) os.makedirs(os.path.join(DATA_DIR, 'audio_output'), exist_ok=True) # Cài đặt thư viện - tự động cài đặt từ requirements.txt trên Hugging Face Spaces print('📦 Thư viện sẽ được cài đặt tự động từ requirements.txt') print('✅ Sẵn sàng!') print() ADMIN_PASSWORD = 'TOIDEPTRAI' # Import libraries import torch import gc import json import numpy as np import pandas as pd from datetime import datetime # Check GPU print('🔍 Kiểm tra GPU:') print(f' - CUDA Available: {torch.cuda.is_available()}') if torch.cuda.is_available(): print(f' - GPU Name: {torch.cuda.get_device_name(0)}') print(f' - GPU Memory: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB') # Set memory efficient torch.cuda.empty_cache() else: print(' - ⚠️ Không có GPU, sẽ chạy bằng CPU (chậm hơn)') print() print('='*70) # ====================================================================== # ============================================================ # PHẦN 2: TẢI DATASET # ============================================================ print('='*70) print('📥 TẢI DATASET') print('='*70) print() # Dataset: Thủ tục hành chính try: from datasets import load_dataset print('🔄 Đang tải Dataset: Thủ tục hành chính...') ds1 = load_dataset('hirine/dataset-thu-tuc-hanh-chinh-5733-samples', split='train') df1 = pd.DataFrame(ds1) print(f' ✅ Dataset: {len(df1):,} samples') except Exception as e: print(f' ⚠️ Lỗi tải Dataset: {e}') print(' 🔄 Tạo dữ liệu mẫu...') df1 = pd.DataFrame({ 'id': range(100), 'title': ['Khai sinh', 'Kết hôn', 'Làm CCCD', 'Tách hộ khẩu', 'Đổi bằng lái', 'Khai tử', 'Hộ chiếu', 'Lý lịch tư pháp', 'Sổ đỏ', 'Sang tên'] * 10, 'text': ['Thủ tục hành chính cần giấy tờ...' * 5] * 100 }) print(f' ✅ Đã tạo dữ liệu mẫu: {len(df1)} records') df1.to_csv(os.path.join(DATA_DIR, 'data/ds1.csv'), index=False) print() # Tổng kết print(f'📊 Tổng samples: {len(df1):,}') print() print('='*70) # ====================================================================== # ============================================================ # PHẦN 3: DATA DỊCH VỤ CÔNG (40+ THỦ TỤC) # ============================================================ print('='*70) print('📥 TẢI DATA DỊCH VỤ CÔNG') print('='*70) print() # Dữ liệu Dịch vụ công đầy đủ DICHVUCONG_DATA = { # NHÂN SỰ - HỘ TỊCH - TƯ PHÁP "KHAI_SINH": {"code":"KHAI_SINH","name":"Đăng ký khai sinh trực tiếp","docs":"Giấy chứng sinh, CCCD cha mẹ, Giấy chứng nhận kết hôn","coquan":"UBND xã/phường","time":"1-3 ngày","lephi":"Miễn phí","note":"Làm trong 60 ngày kể từ khi sinh","steps":"1. Đến UBND nơi cư trú. 2. Nộp hồ sơ. 3. Nhận kết quả.","category":"NHAN_SU"}, "KHAISINH_LIEN_THONG": {"code":"KS_LT","name":"Liên thông: Khai sinh - Thường trú - Cấp thẻ BHYT","docs":"Giấy chứng sinh điện tử, CCCD cha mẹ","coquan":"Cổng Dịch vụ công Quốc gia","time":"1-3 ngày làm việc","lephi":"Miễn phí","note":"Thực hiện online 100%, nhận 3 kết quả cùng lúc","steps":"1. Truy cập dichvucong.gov.vn. 2. Chọn nhóm dịch vụ Liên thông. 3. Nhập mã giấy chứng sinh. 4. Chờ phê duyệt.","category":"NHAN_SU"}, "KHAI_SINH_QUA_HAN": {"code":"KHAI_SINH_QUA_HAN","name":"Khai sinh quá hạn","docs":"Giấy chứng sinh, CCCD, Đơn giải trình lý do chậm trễ","coquan":"UBND xã/phường","time":"3-7 ngày","lephi":"Miễn phí","note":"Quá 60 ngày cần đơn giải trình","steps":"1. Viết đơn giải trình. 2. Xin xác nhận tổ dân phố (nếu cần). 3. Nộp hồ sơ tại UBND.","category":"NHAN_SU"}, "KHAI_TU": {"code":"KHAI_TU","name":"Đăng ký khai tử","docs":"Giấy báo tử, CCCD người chết","coquan":"UBND xã/phường","time":"1-3 ngày","lephi":"Miễn phí","note":"Làm trong 60 ngày kể từ khi chết","steps":"1. Lấy giấy báo tử tại Y tế/Công an. 2. Đến UBND nơi người chết cư trú.","category":"NHAN_SU"}, "KET_HON": {"code":"KET_HON","name":"Đăng ký kết hôn (Trong nước)","docs":"CCCD cả 2 bên, Giấy xác nhận tình trạng hôn nhân (nếu cư trú khác nơi đăng ký)","coquan":"UBND xã/phường","time":"1-3 ngày","lephi":"Miễn phí","note":"Cả 2 bên phải có mặt","steps":"1. Chuẩn bị giấy xác nhận độc thân. 2. Cả 2 cùng đến UBND ký vào sổ hộ tịch.","category":"NHAN_SU"}, "LY_HON": {"code":"LY_HON","name":"Ly hôn thuận tình","docs":"Đơn xin ly hôn, Giấy kết hôn gốc, CCCD, Khai sinh con (nếu có)","coquan":"Tòa án nhân dân quận/huyện","time":"2-4 tháng","lephi":"300.000đ","note":"Cả hai bên cùng đồng ý và ký tên","steps":"1. Nộp đơn tại Tòa án. 2. Hòa giải. 3. Tòa án ra quyết định công nhận.","category":"NHAN_SU"}, "KET_HON_NGOAI": {"code":"KET_HON_NGOAI","name":"Kết hôn có yếu tố nước ngoài","docs":"Hộ chiếu, Giấy xác nhận tình trạng hôn nhân hợp pháp hóa lãnh sự","coquan":"UBND quận/huyện","time":"5-15 ngày","lephi":"Dưới 1.000.000đ","note":"Cần phỏng vấn tại tư pháp","steps":"1. Hợp pháp hóa giấy tờ nước ngoài. 2. Nộp tại UBND quận. 3. Phỏng vấn và ký giấy.","category":"NHAN_SU"}, "SAO_KHAI_SINH": {"code":"SAO_KS","name":"Cấp bản sao trích lục khai sinh","docs":"CCCD người yêu cầu","coquan":"UBND nơi lưu trữ sổ gốc","time":"Ngay trong ngày","lephi":"8.000đ/bản","note":"Có thể xin bản sao điện tử trên DVC","steps":"1. Nộp tờ khai. 2. Đóng phí. 3. Nhận bản sao trích lục.","category":"NHAN_SU"}, "XAC_NHAN_DOC_THAN": {"code":"XN_DOC_THAN","name":"Xác nhận tình trạng hôn nhân (Độc thân)","docs":"CCCD, Quyết định ly hôn (nếu có)","coquan":"UBND xã/phường nơi thường trú","time":"1-3 ngày","lephi":"15.000đ","note":"Giấy có giá trị 6 tháng để kết hôn hoặc vay vốn","steps":"1. Nộp đơn tại UBND. 2. Cán bộ kiểm tra sổ hộ tịch. 3. Cấp giấy.","category":"NHAN_SU"}, "LY_LICH_TU_PHAP": {"code":"LLTP","name":"Cấp lý lịch tư pháp (Số 1 và Số 2)","docs":"CCCD gắn chip","coquan":"Sở Tư pháp hoặc qua app VNeID","time":"7-10 ngày","lephi":"200.000đ (miễn phí cho một số đối tượng)","note":"Hiện tại làm qua VNeID rất nhanh","steps":"1. Truy cập VNeID hoặc DVC. 2. Chọn cấp LLTP. 3. Thanh toán online. 4. Nhận kết quả bản điện tử/giấy.","category":"HANH_CHINH"}, "CHUNG_THUC_BAN_SAO": {"code":"CHUNG_THUC","name":"Công chứng/Chứng thực bản sao (Sao y bản chính)","docs":"Bản chính giấy tờ, Bản photo","coquan":"UBND xã/phường hoặc Phòng Công chứng","time":"Lấy ngay","lephi":"2.000đ/trang","note":"Trình bản chính để đối chiếu","steps":"1. Xuất trình bản chính. 2. Cán bộ ký đóng dấu vào bản photo.","category":"HANH_CHINH"}, # CCCD - HỘ KHẨU "CCCD_CAP": {"code":"CCCD_CAP","name":"Cấp CCCD lần đầu","docs":"Giấy khai sinh, Sổ hộ khẩu","coquan":"Công an quận/huyện","time":"7-15 ngày","lephi":"Miễn phí","note":"Từ 14 tuổi","steps":"1. Chuẩn bị giấy khai sinh, sổ hộ khẩu. 2. Đến Công an quận/huyện. 3. Làm thủ tục cấp CCCD.","category":"CCCD_HK"}, "CCCD_DOI": {"code":"CCCD_DOI","name":"Đổi CCCD","docs":"CCCD cũ, Giấy khai sinh","coquan":"Công an quận/huyện","time":"7-15 ngày","lephi":"Miễn phí","note":"Đổi khi hư hỏng/sai thông tin","steps":"1. Mang CCCD cũ. 2. Đến Công an nơi thường trú. 3. Làm thủ tục đổi.","category":"CCCD_HK"}, "VNEID_DINH_DANH_2": {"code":"VNEID_L2","name":"Đăng ký tài khoản VNeID Mức độ 2","docs":"CCCD gắn chip, Số điện thoại chính chủ","coquan":"Công an xã/phường","time":"3-5 ngày duyệt","lephi":"Miễn phí","note":"Thay thế thẻ vật lý khi đi máy bay, hành chính","steps":"1. Đến trực tiếp Công an. 2. Quét chip CCCD. 3. Chụp ảnh chân dung. 4. Chờ Bộ Công an phê duyệt.","category":"CCCD_HK"}, "CCCD_CAP_LAI": {"code":"CCCD_CAP_LAI","name":"Cấp lại CCCD khi mất","docs":"Giấy khai sinh, Sổ hộ khẩu, Đơn xin cấp lại","coquan":"Công an quận/huyện","time":"7-15 ngày","lephi":"Miễn phí","note":"Báo mất và xin cấp lại","steps":"1. Làm đơn báo mất. 2. Đến Công an. 3. Xin cấp lại CCCD.","category":"CCCD_HK"}, "HO_KHAU_TACH": {"code":"HO_KHAU_TACH","name":"Tách sổ hộ khẩu","docs":"Sổ hộ khẩu, CCCD, Đơn xin tách, Giấy tờ nơi ở mới","coquan":"Công an quận/huyện","time":"3-7 ngày","lephi":"Miễn phí","note":"Cần đồng ý chủ hộ","steps":"1. Viết đơn xin tách hộ khẩu. 2. Xin chữ ký chủ hộ. 3. Đến Công án làm thủ tục.","category":"CCCD_HK"}, "HO_KHAU_NHAP": {"code":"HO_KHAU_NHAP","name":"Nhập sổ hộ khẩu","docs":"Sổ hộ khẩu, CCCD, Đơn xin nhập, Giấy tờ nơi ở","coquan":"Công an quận/huyện","time":"3-7 ngày","lephi":"Miễn phí","note":"Cần đồng ý chủ hộ mới","steps":"1. Viết đơn xin nhập. 2. Xin đồng ý chủ hộ mới. 3. Đến Công an làm thủ tục.","category":"CCCD_HK"}, "TAM_TRU": {"code":"TAM_TRU","name":"Đăng ký tạm trú","docs":"CCCD, Đơn xin tạm trú, Giấy tờ nơi ở","coquan":"Công an xã/phường","time":"3-5 ngày","lephi":"Miễn phí","note":"Tối đa 2 năm","steps":"1. Lấy giấy xác nhận của chủ nhà. 2. Viết đơn xin tạm trú. 3. Đến Công an xã/phường.","category":"CCCD_HK"}, "TRU_SO": {"code":"TRU_SO","name":"Đăng ký trú quán","docs":"CCCD, Đơn xin trú quán, Xác nhận tổ dân phố","coquan":"Công an xã/phường","time":"3-5 ngày","lephi":"Miễn phí","note":"Cho người không có hộ khẩu","steps":"1. Xin xác nhận tổ dân phố. 2. Viết đơn xin trú quán. 3. Đến Công an.","category":"CCCD_HK"}, "TAM_VANG": {"code":"TAM_VANG","name":"Đăng ký tạm vắng","docs":"CCCD, Sổ hộ khẩu, Đơn xin","coquan":"Công an","time":"1-3 ngày","lephi":"Miễn phí","note":"Khi đi khỏi nơi thường trú trên 30 ngày","steps":"1. Làm đơn xin báo tạm vắng. 2. Nộp tại Công an nơi thường trú.","category":"CCCD_HK"}, # BẰNG LÁI "BANG_LAI_A1": {"code":"BANG_LAI_A1","name":"Cấp đổi bằng lái A1","docs":"CCCD, Giấy khai sinh (nếu dưới 18), 1 ảnh 3x4","coquan":"Sở GTVT","time":"7-10 ngày","lephi":"Miễn phí","note":"Từ 18 tuổi, đổi bằng không cần thi lại","steps":"1. Chuẩn bị CCCD, ảnh. 2. Đến Sở GTVT hoặc trung tâm sát hạch. 3. Nộp hồ sơ và nhận bằng mới.","category":"GIAO_THONG"}, "BANG_LAI_A1_CAP_MOI": {"code":"BANG_LAI_A1_CAP_MOI","name":"Cấp mới bằng lái A1","docs":"CCCD, Giấy khai sinh, Học lý, Giấy khám sức khỏe","coquan":"Sở GTVT","time":"15-20 ngày","lephi":"110.000đ","note":"Phải thi sát hạch","steps":"1. Học lý thuyết và sa hình. 2. Đăng ký thi. 3. Thi sát hạch. 4. Nhận bằng khi đỗ.","category":"GIAO_THONG"}, "BANG_LAI_A2": {"code":"BANG_LAI_A2","name":"Cấp bằng lái A2","docs":"CCCD, Học lý, Giấy khám sức khỏe","coquan":"Sở GTVT","time":"15-20 ngày","lephi":"150.000đ","note":"Xe mô tô trên 175cc","steps":"1. Học lý thuyết và sa hình A2. 2. Đăng ký thi. 3. Thi và nhận bằng.","category":"GIAO_THONG"}, "BANG_LAI_B1": {"code":"BANG_LAI_B1","name":"Cấp bằng lái B1","docs":"CCCD, Học lý, Giấy khám sức khỏe","coquan":"Sở GTVT","time":"20-25 ngày","lephi":"350.000đ","note":"Ô tô dưới 9 chỗ","steps":"1. Học lý thuyết, sa hình, đường trường. 2. Đăng ký thi B1. 3. Thi và nhận bằng.","category":"GIAO_THONG"}, "BANG_LAI_B2": {"code":"BANG_LAI_B2","name":"Cấp bằng lái B2","docs":"CCCD, Học lý, Giấy khám sức khỏe","coquan":"Sở GTVT","time":"20-25 ngày","lephi":"400.000đ","note":"Ô tô kinh doanh","steps":"1. Học lý thuyết, sa hình, đường trường. 2. Đăng ký thi B2. 3. Thi và nhận bằng.","category":"GIAO_THONG"}, "BANG_LAI_DOI": {"code":"BANG_LAI_DOI","name":"Đổi bằng lái","docs":"GPLX cũ, CCCD, 1 ảnh 3x4","coquan":"Sở GTVT","time":"7-10 ngày","lephi":"135.000đ","note":"Đổi khi hết hạn","steps":"1. Chuẩn bị bằng cũ, CCCD, ảnh. 2. Đến Sở GTVT. 3. Đổi bằng mới.","category":"GIAO_THONG"}, "BANG_LAI_CAP_LAI": {"code":"BANG_LAI_CAP_LAI","name":"Cấp lại bằng lái","docs":"CCCD, 1 ảnh 3x4, Đơn xin cấp lại","coquan":"Sở GTVT","time":"7-10 ngày","lephi":"135.000đ","note":"Khi bị mất","steps":"1. Làm đơn xin cấp lại. 2. Đến Sở GTVT. 3. Nhận bằng mới.","category":"GIAO_THONG"}, "GPLX_ONLINE": {"code":"GPLX_ONL","name":"Đổi Giấy phép lái xe trực tuyến (Mức độ 4)","docs":"GPLX cũ, Giấy khám sức khỏe điện tử, Ảnh chân dung file số","coquan":"Cổng DVC Cục đường bộ","time":"5 ngày","lephi":"115.000đ","note":"Nhận bằng tại nhà qua bưu điện","steps":"1. Khám sức khỏe tại BV có kết nối dữ liệu. 2. Kê khai trên DVC. 3. Thanh toán tiền. 4. Bưu điện giao bằng.","category":"GIAO_THONG"}, "DANG_KY_XE": {"code":"DK_XE","name":"Đăng ký xe máy/ô tô mới","docs":"Hóa đơn GTGT, Tờ khai lệ phí trước bạ, Giấy xuất xưởng","coquan":"Công an quận/huyện","time":"1 ngày","lephi":"Tùy loại xe và khu vực","note":"Phải đóng thuế trước bạ trước khi đăng ký","steps":"1. Đóng thuế trước bạ. 2. Đến công an bấm biển số. 3. Nhận biển số ngay, nhận cavet sau 2 ngày.","category":"GIAO_THONG"}, "THU_HOI_BIEN_SO": {"code":"THU_HOI","name":"Thu hồi biển số, đăng ký xe (Khi bán xe)","docs":"Cavet xe, Biển số xe, Hợp đồng mua bán","coquan":"Cơ quan Công an nơi quản lý xe","time":"2 ngày","lephi":"Miễn phí","note":"Biển số sẽ được giữ lại làm biển định danh cho chủ cũ","steps":"1. Chủ xe nộp đăng ký và biển số. 2. Nhận giấy chứng nhận thu hồi.","category":"GIAO_THONG"}, "PHAT_NGUOI": {"code":"PHAT_NGUOI","name":"Nộp phạt vi phạm giao thông (Phạt nguội)","docs":"Mã số quyết định xử phạt","coquan":"Cổng Dịch vụ công Quốc gia","time":"Xử lý ngay","lephi":"Theo biên bản","note":"Có thể tra cứu bằng biển số xe","steps":"1. Tra cứu mã vi phạm. 2. Thanh toán qua ngân hàng/ví điện tử. 3. Hệ thống tự động xóa lỗi.","category":"GIAO_THONG"}, "DANG_KIEM": {"code":"DANG_KIEM","name":"Đăng kiểm xe cơ giới","docs":"Cavet xe, Đăng kiểm cũ, Bảo hiểm dân sự","coquan":"Trạm đăng kiểm","time":"1-2 giờ","lephi":"~300.000đ + Phí đường bộ","note":"Nên đặt lịch qua app TTDK","steps":"1. Đặt lịch. 2. Đưa xe đến kiểm định. 3. Dán tem mới.","category":"GIAO_THONG"}, # HỘ CHIẾU (2) "HO_CHIEU_CAP": {"code":"HO_CHIEU_CAP","name":"Cấp hộ chiếu","docs":"CCCD, Sổ hộ khẩu, 4 ảnh 4x6","coquan":"Phòng xuất nhập cảnh","time":"7-15 ngày","lephi":"200.000đ","note":"Hộ chiếu gắn chip 10 năm","steps":"1. Chuẩn bị CCCD, hộ khẩu, ảnh. 2. Làm đơn xin cấp hộ chiếu. 3. Nộp tại Phòng XNC. 4. Nhận hộ chiếu.","category":"HANH_CHINH"}, "HO_CHIEU_GIA_HAN": {"code":"HO_CHIEU_GIA_HAN","name":"Gia hạn hộ chiếu","docs":"Hộ chiếu cũ, CCCD, 1 ảnh 4x6","coquan":"Phòng xuất nhập cảnh","time":"5-10 ngày","lephi":"200.000đ","note":"Trước 6 tháng hết hạn","steps":"1. Kiểm tra hạn hộ chiếu. 2. Chuẩn bị hồ sơ gia hạn. 3. Nộp tại Phòng XNC.","category":"HANH_CHINH"}, # LÝ LỊCH (1) "LY_LICH_TU_PHAP": {"code":"LLTP","name":"Cấp lý lịch tư pháp","docs":"CCCD, Đơn xin cấp LLTP","coquan":"Sở Tư pháp","time":"3-5 ngày","lephi":"Miễn phí","note":"Kiểm tra án tích","steps":"1. Làm đơn xin cấp lý lịch tư pháp. 2. Nộp tại Sở Tư pháp. 3. Chờ 3-5 ngày và nhận kết quả.","category":"HANH_CHINH"}, # NHÀ ĐẤT (2) "SO_DO": {"code":"SO_DO","name":"Cấp Sổ đỏ lần đầu","docs":"Giấy tờ chứng minh quyền sử dụng đất, Trích lục bản đồ","coquan":"Văn phòng đăng ký đất đai","time":"20-30 ngày","lephi":"Tiền sử dụng đất + Lệ phí cấp sổ","note":"Quy trình phức tạp, cần xác minh nguồn gốc đất","steps":"1. Nộp hồ sơ tại bộ phận 1 cửa. 2. Niêm yết công khai. 3. Thực hiện nghĩa vụ tài chính. 4. Nhận sổ.","category":"NHADAT"}, "SANG_TEN": {"code":"SANG_TEN","name":"Sang tên Sổ đỏ (Chuyển nhượng nhà đất)","docs":"Hợp đồng công chứng, Sổ đỏ gốc, CCCD hai bên","coquan":"Văn phòng đăng ký đất đai","time":"10-15 ngày","lephi":"Thuế TNCN 2%, Lệ phí trước bạ 0.5%","note":"Phải công chứng hợp đồng trước","steps":"1. Ký hợp đồng tại phòng công chứng. 2. Nộp hồ sơ đăng ký biến động. 3. Đóng thuế. 4. Nhận sổ đã sang tên.","category":"NHADAT"}, "QUY_HOACH_DAT": {"code":"QH_DAT","name":"Tra cứu thông tin quy hoạch đất đai","docs":"Phiếu yêu cầu, CCCD","coquan":"Phòng Tài nguyên và Môi trường","time":"2 ngày","lephi":"Theo quy định","note":"Để biết đất có dính dự án, lộ giới không","steps":"1. Gửi yêu cầu. 2. Cơ quan chức năng cung cấp văn bản trả lời.","category":"NHADAT"}, "CAP_PHEP_XAY_DUNG": {"code":"PHEP_XD","name":"Cấp phép xây dựng nhà ở riêng lẻ","docs":"Bản vẽ thiết kế, Sổ đỏ sao y, Đơn xin cấp phép","coquan":"UBND quận/huyện","time":"15 ngày","lephi":"~100.000đ","note":"Bắt buộc phải có trước khi khởi công","steps":"1. Thuê đơn vị thiết kế bản vẽ. 2. Nộp hồ sơ tại Quận. 3. Cán bộ kiểm tra thực địa. 4. Cấp phép.","category":"XAYDUNG"}, # GIAO THÔNG XE (2) "DANG_KY_XE": {"code":"DANG_KY_XE","name":"Đăng ký xe ô tô","docs":"CCCD, Giấy chứng nhận quyền sở hữu xe, Giấy bảo hiểm","coquan":"Phòng đăng ký xe","time":"1-3 ngày","lephi":"1.500.000đ-3.000.000đ","note":"Trong 30 ngày kể từ khi mua","steps":"1. Chuẩn bị giấy tờ xe. 2. Mua bảo hiểm xe. 3. Đến Phòng đăng ký xe. 4. Nộp hồ sơ và nhận biển số.","category":"GIAO_THONG"}, "CHUYEN_NHUONG_XE": {"code":"CHUYEN_NHUONG_XE","name":"Sang tên chủ sở hữu xe","docs":"CCCD cả 2 bên, Sổ đăng ký xe, Hợp đồng chuyển nhượng","coquan":"Phòng đăng ký xe","time":"2-5 ngày","lephi":"500.000đ-1.000.000đ","note":"Cả 2 cùng đến làm","steps":"1. Làm hợp đồng chuyển nhượng có công chứng. 2. Cả 2 bên đến Phòng đăng ký xe. 3. Làm thủ tục chuyển nhượng.","category":"GIAO_THONG"}, "SANG_TEN_XE_CUNG_TINH": {"code":"ST_CT","name":"Sang tên xe trong cùng tỉnh/thành phố","docs":"Hợp đồng mua bán công chứng, Chứng nhận thu hồi biển số, CCCD chủ mới","coquan":"Công an Quận/Huyện","time":"2-5 ngày","lephi":"Thuế trước bạ (1% - 2%) + phí đổi cavet","note":"Chủ cũ phải làm thủ tục thu hồi biển số trước","steps":"1. Công chứng hợp đồng mua bán. 2. Chủ cũ nộp biển số/cavet. 3. Chủ mới đóng thuế trước bạ. 4. Chủ mới đăng ký tại Công an.","category":"GIAO_THONG"}, "SANG_TEN_XE_KHAC_TINH": {"code":"ST_KT","name":"Sang tên xe đi tỉnh khác (Di chuyển nguyên chủ)","docs":"Hồ sơ gốc của xe, Chứng nhận thu hồi, CCCD","coquan":"Công an nơi đến","time":"5-7 ngày","lephi":"Theo biểu phí khu vực đến","note":"Phải rút hồ sơ gốc (thu hồi) tại tỉnh cũ","steps":"1. Làm thủ tục thu hồi tại tỉnh cũ. 2. Cầm hồ sơ gốc đến Công an tỉnh mới. 3. Đóng thuế trước bạ tại nơi mới. 4. Bấm biển số mới.","category":"GIAO_THONG"}, "DOI_MAU_SON_XE": {"code":"MAU_SON","name":"Đổi màu sơn xe (Thay đổi màu sắc xe)","docs":"Cavet xe, CCCD, Đơn đề nghị thay đổi màu sơn","coquan":"Phòng/Đội CSGT","time":"1-2 ngày","lephi":"30.000đ - 50.000đ","note":"Phải làm thủ tục TRƯỚC khi sơn lại xe","steps":"1. Mang xe đến CSGT. 2. Cán bộ kiểm tra xe hiện tại. 3. Viết đơn xin đổi màu sơn. 4. Đi sơn xe. 5. Đến cấp lại cavet mới với màu sơn mới.","category":"GIAO_THONG"}, "CAP_LAI_BIEN_SO_MAT": {"code":"BS_MAT","name":"Cấp lại biển số xe bị mất/hỏng","docs":"CCCD, Cavet xe, Đơn trình báo mất biển số","coquan":"Phòng CSGT nơi đăng ký","time":"7 ngày làm việc","lephi":"150.000đ","note":"Biển số cấp lại vẫn là biển số định danh cũ của bạn","steps":"1. Nộp đơn báo mất tại Công an. 2. Kê khai trên DVC. 3. Chờ dập lại biển số mới theo số cũ.","category":"GIAO_THONG"}, "DANG_KY_XE_MAY_DIEN": {"code":"XE_DIEN","name":"Đăng ký biển số xe máy điện","docs":"Hóa đơn GTGT, Phiếu kiểm tra chất lượng xuất xưởng, CCCD","coquan":"Công an xã/phường","time":"1 ngày","lephi":"Tùy khu vực (từ 50.000đ)","note":"Xe máy điện bắt buộc phải có biển số mới được lưu thông","steps":"1. Đóng thuế trước bạ. 2. Khai báo trên cổng DVC Bộ Công an. 3. Mang xe đến công an xã bấm biển.","category":"GIAO_THONG"}, # XÂY DỰNG (1) "CAP_PHEP_XAY_DUNG": {"code":"PHEP_XD","name":"Cấp phép xây dựng nhà ở","docs":"Đơn xin, Giấy chứng nhận quyền sử dụng đất, Bản vẽ quy hoạch","coquan":"Phòng Đô thị","time":"15-20 ngày","lephi":"50.000đ-200.000đ","note":"Cần quy hoạch","steps":"1. Lấy bản vẽ quy hoạch 1/500. 2. Thiết kế bản vẽ xây dựng. 3. Làm đơn xin cấp phép. 4. Nộp tại Phòng Đô thị.","category":"XAYDUNG"}, "THONG_BAO_KHOI_CONG": {"code": "KHOI_CONG", "name": "Thông báo khởi công xây dựng", "docs": "GPXD, Hợp đồng bảo hiểm, Thông tin nhà thầu", "coquan": "UBND Xã/Phường", "time": "3 ngày trước khi làm", "lephi": "Miễn phí", "note": "Bắt buộc phải báo trước khi đào móng", "steps": "1. Chuẩn bị GPXD. 2. Nộp đơn thông báo tại phường.", "category": "XAYDUNG"}, "GIA_HAN_GPXD": {"code": "GH_GPXD", "name": "Gia hạn Giấy phép xây dựng", "docs": "Bản gốc GPXD, Đơn xin gia hạn", "coquan": "Cơ quan cấp phép cũ", "time": "5 ngày", "lephi": "15.000đ", "note": "Phải làm trước khi hết hạn 12 tháng", "steps": "1. Nộp đơn khi gần hết hạn. 2. Đóng dấu gia hạn vào bản gốc.", "category": "XAYDUNG"}, "PHA_DO_CONG_TRINH": {"code": "PHA_DO", "name": "Cấp phép phá dỡ công trình", "docs": "Phương án phá dỡ, Đơn xin phá dỡ", "coquan": "UBND Quận/Huyện", "time": "7-10 ngày", "lephi": "Miễn phí", "note": "Đảm bảo an toàn cho các nhà liền kề", "steps": "1. Lập phương án che chắn. 2. Nộp thông báo phá dỡ.", "category": "XAYDUNG"}, # KINH DOANH (1) "CAP_PHEP_KINH_DOANH": {"code":"PHEP_KD","name":"Cấp giấy phép kinh doanh","docs":"Đơn xin cấp phép kinh doanh, CCCD, Địa chỉ kinh doanh","coquan":"Phòng Đăng ký kinh doanh hoặc UBND","time":"3-5 ngày","lephi":"Miễn phí","note":"Kinh doanh cá thể miễn phí","steps":"1. Làm đơn xin cấp phép kinh doanh. 2. Chuẩn bị CCCD, địa chỉ kinh doanh. 3. Nộp tại Phòng ĐKKD hoặc UBND.","category":"KINHDOANH"}, # Y TẾ (2) "BHYT_CAP_MOI": {"code": "BHYT_CAP", "name": "Đăng ký đóng, cấp thẻ BHYT đối với người chỉ tham gia BHYT", "docs": "CCCD, Tờ khai tham gia BHXH, BHYT (Mẫu TK1-TS)", "coquan": "Cơ quan Bảo hiểm xã hội / Đại lý thu", "time": "5 ngày làm việc", "lephi": "Theo mức đóng quy định", "note": "Đóng theo hộ gia đình sẽ được giảm trừ mức đóng từ người thứ 2", "steps": "1. Điền tờ khai Mẫu TK1-TS. 2. Đóng tiền cho Đại lý thu/Bưu điện. 3. Nhận thẻ BHYT mới.", "category": "YTE"}, "BHYT_BHXH_DOI": {"code": "BHYT_DOI", "name": "Cấp lại, đổi, điều chỉnh thông tin trên sổ BHXH, thẻ BHYT", "docs": "Tờ khai (Mẫu TK1-TS), Sổ BHXH/Thẻ BHYT cũ, Giấy tờ minh chứng thay đổi", "coquan": "Bảo hiểm xã hội", "time": "Trong ngày (nếu không đổi thông tin) / 3-5 ngày (có đổi thông tin)", "lephi": "Miễn phí", "note": "Có thể thực hiện thủ tục trực tuyến qua ứng dụng VssID hoặc Cổng DVC", "steps": "1. Kê khai thông tin cần điều chỉnh. 2. Nộp hồ sơ kèm minh chứng. 3. Nhận thẻ/sổ mới.", "category": "YTE"}, "BHYT_THANH_TOAN": {"code": "BHYT_TT", "name": "Thanh toán trực tiếp chi phí KCB BHYT / Cấp giấy chứng nhận không cùng chi trả", "docs": "Thẻ BHYT, CCCD, Hóa đơn viện phí gốc, Bản sao bệnh án/Giấy ra viện", "coquan": "Cơ quan BHXH cấp huyện/tỉnh", "time": "40 ngày", "lephi": "Miễn phí", "note": "Áp dụng lấy lại tiền khi khám trái tuyến hoặc cấp GCN miễn đồng chi trả (tham gia 5 năm liên tục)", "steps": "1. Tập hợp hóa đơn đỏ viện phí. 2. Nộp hồ sơ tại BHXH. 3. Nhận tiền thanh toán qua tài khoản.", "category": "YTE"}, "BH_THAI_SAN": {"code": "THAI_SAN", "name": "Giải quyết hưởng chế độ thai sản", "docs": "Giấy chứng sinh/khai sinh của con, Giấy ra viện (nếu có)", "coquan": "Cơ quan BHXH (qua Công ty)", "time": "6-10 ngày làm việc", "lephi": "Miễn phí", "note": "Cần đóng đủ 6 tháng BHXH trong vòng 12 tháng trước khi sinh", "steps": "1. Nộp giấy tờ sinh con cho Công ty. 2. Công ty lập danh sách nộp lên BHXH. 3. Nhận tiền thai sản.", "category": "YTE"}, "BH_OM_DAU": {"code": "OM_DAU", "name": "Giải quyết hưởng chế độ ốm đau", "docs": "Giấy ra viện (nội trú) hoặc Giấy chứng nhận nghỉ việc hưởng BHXH (ngoại trú)", "coquan": "Cơ quan BHXH (qua Công ty)", "time": "6 ngày làm việc", "lephi": "Miễn phí", "note": "Phải nộp hồ sơ cho công ty trong vòng 45 ngày từ lúc đi làm lại", "steps": "1. Xin giấy nghỉ ốm theo chuẩn Bộ Y tế. 2. Nộp cho kế toán. 3. BHXH chuyển tiền trợ cấp.", "category": "YTE"}, "BH_DSPHSK": {"code": "DSPHSK", "name": "Giải quyết hưởng trợ cấp nghỉ dưỡng sức phục hồi sức khỏe (DSPHSK)", "docs": "Danh sách đề nghị giải quyết hưởng chế độ (Mẫu 01B-HSB do công ty lập)", "coquan": "Cơ quan BHXH (qua Công ty)", "time": "6-10 ngày làm việc", "lephi": "Miễn phí", "note": "Dành cho NLĐ đã hết thời gian nghỉ ốm đau, thai sản mà sức khỏe còn yếu", "steps": "1. Công đoàn và Công ty xem xét số ngày nghỉ. 2. Công ty lập mẫu nộp BHXH. 3. Nhận tiền.", "category": "YTE"}, "BH_TNLD_LAN1": {"code": "TNLD_L1", "name": "Giải quyết hưởng chế độ Tai nạn lao động, Bệnh nghề nghiệp (TNLĐ, BNN) lần đầu", "docs": "Biên bản điều tra TNLĐ, Giấy ra viện, Biên bản giám định tỷ lệ suy giảm KNLĐ", "coquan": "Cơ quan BHXH", "time": "6 ngày làm việc", "lephi": "Miễn phí", "note": "Mức hưởng phụ thuộc vào tỷ lệ giám định suy giảm khả năng lao động", "steps": "1. Công ty lập biên bản tai nạn. 2. Đưa đi giám định y khoa. 3. Nộp hồ sơ nhận trợ cấp.", "category": "YTE"}, "BH_TNLD_TIEP": {"code": "TNLD_TIEP", "name": "Giải quyết hưởng chế độ TNLĐ, BNN đối với trường hợp tiếp tục bị TNLĐ/BNN", "docs": "Hồ sơ vụ tai nạn mới, Biên bản giám định tổng hợp tỷ lệ suy giảm KNLĐ", "coquan": "Cơ quan BHXH", "time": "6 ngày làm việc", "lephi": "Miễn phí", "note": "Áp dụng khi đang hưởng trợ cấp tai nạn cũ mà tiếp tục bị tai nạn mới", "steps": "1. Lập hồ sơ tai nạn mới. 2. Giám định tổng hợp phần trăm thương tật. 3. Nộp hồ sơ hưởng mức mới.", "category": "YTE"}, "BH_TNLD_TAIPHAT": {"code": "TNLD_TP", "name": "Giải quyết chế độ TNLĐ, BNN do thương tật, bệnh tật tái phát", "docs": "Hồ sơ y tế điều trị tái phát, Biên bản giám định lại thương tật", "coquan": "Cơ quan BHXH", "time": "6 ngày làm việc", "lephi": "Miễn phí", "note": "Dành cho người đã hưởng TNLĐ nay bị đau lại do vết thương cũ", "steps": "1. Đi viện điều trị đợt tái phát. 2. Xin giám định lại tỷ lệ thương tật. 3. Nộp xin điều chỉnh trợ cấp.", "category": "YTE"}, # GIÁO DỤC (1) "HOC_BONG": {"code":"HOC_BONG","name":"Đăng ký xét học bổng","docs":"Đơn xin học bổng, Bảng tổng kết, Học bạ","coquan":"Trường học hoặc Phòng GD-ĐT","time":"7-10 ngày","lephi":"Miễn phí","note":"Theo từng loại học bổng","steps":"1. Làm đơn xin xét học bổng. 2. Chuẩn bị hồ sơ (bảng tổng kết, học bạ). 3. Nộp tại trường hoặc Phòng GD-ĐT.","category":"GIAODUC"}, "CN_BANG_DH_NN": {"code": "CN_BANG_DH", "name": "Công nhận bằng Cử nhân, Thạc sĩ, Tiến sĩ do nước ngoài cấp", "docs": "Bản sao văn bằng + bảng điểm, Bản dịch công chứng, Hộ chiếu/Minh chứng thời gian học ở nước ngoài", "coquan": "Cục Quản lý chất lượng (Bộ GD&ĐT)", "time": "20 ngày làm việc", "lephi": "500.000đ/văn bằng", "note": "Thực hiện trực tuyến 100% trên Cổng DVC Bộ GD&ĐT", "steps": "1. Tạo hồ sơ online. 2. Nộp lệ phí trực tuyến. 3. Cục thẩm định hồ sơ. 4. Nhận Giấy công nhận bản điện tử/giấy.", "category": "GIAODUC"}, "CN_BANG_PT_NN": {"code": "CN_BANG_PT", "name": "Công nhận bằng tốt nghiệp THCS, THPT do nước ngoài cấp", "docs": "Bản sao văn bằng, Bảng điểm, Bản dịch công chứng, Giấy tờ tùy thân", "coquan": "Sở Giáo dục và Đào tạo địa phương", "time": "20 ngày làm việc", "lephi": "250.000đ/văn bằng", "note": "Dành cho học sinh học ở nước ngoài muốn về VN học tiếp hoặc xét tuyển", "steps": "1. Nộp hồ sơ trên cổng DVC của Sở. 2. Đóng phí. 3. Thẩm định. 4. Nhận Giấy công nhận.", "category": "GIAODUC"}, "DAC_CACH_THPT": {"code": "DAC_CACH", "name": "Xét đặc cách tốt nghiệp THPT", "docs": "Đơn đề nghị, Hồ sơ nhập viện/Giấy ra viện (nếu ốm đau), Biên bản xác nhận của Hội đồng thi", "coquan": "Sở Giáo dục và Đào tạo", "time": "Chậm nhất 07 ngày sau buổi thi cuối cùng", "lephi": "Miễn phí", "note": "Chỉ áp dụng khi thí sinh bị tai nạn, ốm đau đột xuất trước hoặc trong kỳ thi", "steps": "1. Nộp đơn và hồ sơ bệnh án cho Trưởng Điểm thi/Trường THPT. 2. Trường nộp lên Sở GD&ĐT. 3. Sở ra quyết định đặc cách.", "category": "GIAODUC"}, "PHUC_KHAO_THPT": {"code": "PHUC_KHAO", "name": "Phúc khảo bài thi tốt nghiệp THPT", "docs": "Đơn đề nghị phúc khảo bài thi (theo mẫu)", "coquan": "Nơi đăng ký dự thi (Trường THPT hoặc Phòng GD&ĐT)", "time": "15 ngày kể từ ngày hết hạn nhận đơn", "lephi": "Miễn phí (hoặc theo quy định của Sở)", "note": "Thí sinh phải nộp đơn trong vòng 10 ngày kể từ khi công bố điểm thi", "steps": "1. Viết đơn nộp tại trường cấp 3. 2. Trường chuyển dữ liệu lên Sở. 3. Tổ chức chấm lại. 4. Công bố điểm phúc khảo.", "category": "GIAODUC"}, "XET_TN_THAC_SI": {"code": "TN_THS", "name": "Xét tốt nghiệp và cấp bằng Thạc sĩ", "docs": "Đơn xin xét tốt nghiệp, Bảng điểm toàn khóa, Chứng chỉ ngoại ngữ, Quyết định bảo vệ luận văn", "coquan": "Phòng Đào tạo Sau đại học (Cơ sở GD Đại học)", "time": "Theo kế hoạch xét tốt nghiệp của trường", "lephi": "Lệ phí cấp bằng (tùy trường)", "note": "Học viên phải nộp lưu chiểu luận văn trước khi xét", "steps": "1. Nộp đơn xin xét tốt nghiệp. 2. Hội đồng trường họp xét duyệt. 3. Hiệu trưởng ký quyết định. 4. Cấp bằng và bảng điểm.", "category": "GIAODUC"}, "XET_CAP_TIEN_SI": {"code": "CAP_TS", "name": "Xét cấp bằng Tiến sĩ", "docs": "Hồ sơ bảo vệ luận án cấp viện/trường, Giấy biên nhận nộp lưu chiểu Thư viện Quốc gia", "coquan": "Cơ sở GD Đại học / Viện nghiên cứu", "time": "Theo đợt xét tốt nghiệp", "lephi": "Lệ phí cấp bằng (tùy trường)", "note": "Bắt buộc phải nộp bản cứng luận án vào Thư viện Quốc gia Việt Nam", "steps": "1. Hoàn thiện hồ sơ sau khi bảo vệ thành công. 2. Nộp lưu chiểu. 3. Hội đồng chức danh xét duyệt. 4. Cấp bằng.", "category": "GIAODUC"}, "TUYEN_SINH_10": {"code": "TS_10", "name": "Tuyển sinh vào lớp 10 trung học phổ thông", "docs": "Đơn dự tuyển, Học bạ THCS, Giấy CN tốt nghiệp THCS tạm thời, Giấy chứng nhận ưu tiên (nếu có)", "coquan": "Sở Giáo dục và Đào tạo / Trường THPT", "time": "Theo lịch tuyển sinh của tỉnh/thành phố", "lephi": "Lệ phí thi (khoảng 150.000đ - 300.000đ)", "note": "Học sinh lớp 9 thường được đăng ký nguyện vọng trực tuyến tại trường THCS đang học", "steps": "1. Đăng ký nguyện vọng trực tuyến. 2. Nhận thẻ dự thi. 3. Tham gia kỳ thi. 4. Xem điểm chuẩn và nộp hồ sơ nhập học.", "category": "GIAODUC"}, "BO_TUC_THCS": {"code": "BT_THCS", "name": "Tiếp nhận đối tượng học bổ túc trung học cơ sở", "docs": "Đơn xin học, Bản sao Giấy khai sinh, Học bạ (nếu đã học dở dang), CCCD", "coquan": "Trung tâm Giáo dục thường xuyên / Cơ sở GDTX cấp huyện", "time": "Theo kỳ tuyển sinh (thường vào tháng 8-9)", "lephi": "Miễn phí (hoặc theo quy định hỗ trợ của địa phương)", "note": "Dành cho người đã quá tuổi học THCS chính quy (từ 15 tuổi trở lên)", "steps": "1. Mua/Tải hồ sơ tại Trung tâm GDTX. 2. Điền thông tin và nộp lại kèm học bạ cũ. 3. Giám đốc Trung tâm ra quyết định tiếp nhận.", "category": "GIAODUC"}, "DK_THI_THPT": {"code": "THI_THPT", "name": "Đăng ký dự thi tốt nghiệp trung học phổ thông", "docs": "Phiếu đăng ký dự thi, CCCD, Học bạ THPT, Giấy chứng nhận ưu tiên (nếu có)", "coquan": "Trường THPT (với thí sinh lớp 12) hoặc Sở GD&ĐT (với thí sinh tự do)", "time": "Theo lịch của Bộ GD&ĐT (thường vào tháng 4-5)", "lephi": "Lệ phí xét tuyển (tùy số lượng nguyện vọng ĐH)", "note": "Thí sinh lớp 12 thực hiện đăng ký 100% bằng hình thức trực tuyến (online)", "steps": "1. Nhận tài khoản từ trường. 2. Đăng nhập hệ thống của Bộ GD&ĐT. 3. Điền thông tin, chọn môn thi, nguyện vọng. 4. In phiếu, ký xác nhận.", "category": "GIAODUC"}, # KHÁC (2) "XIN_VIEC": {"code":"XIN_VIEC","name":"Đăng ký tìm việc làm","docs":"CCCD, Hồ sơ xin việc","coquan":"Trung tâm dịch vụ việc làm","time":"1-2 ngày","lephi":"Miễn phí","note":"","steps":"1. Chuẩn bị hồ sơ xin việc (CCCD, sơ yếu lý lịch). 2. Đến Trung tâm dịch vụ việc làm. 3. Đăng ký và được giới thiệu việc làm.","category":"KHAC"}, "TRO_CAP": {"code":"TRO_CAP","name":"Đăng ký trợ cấp xã hội","docs":"Đơn xin trợ cấp, CCCD, Sổ hộ khẩu","coquan":"UBND xã/phường","time":"7-10 ngày","lephi":"Miễn phí","note":"","steps":"1. Làm đơn xin trợ cấp xã hội. 2. Xin xác nhận hoàn cảnh khó khăn. 3. Nộp tại UBND xã/phường.","category":"KHAC"}, } print(f'✅ Dataset Dịch vụ công: {len(DICHVUCONG_DATA)} thủ tục') print() # Lưu data with open(os.path.join(DATA_DIR, 'data/dichvucong.json'), 'w', encoding='utf-8') as f: json.dump(DICHVUCONG_DATA, f, ensure_ascii=False, indent=2) print('✅ Đã lưu data/dichvucong.json') print() print('='*70) # ====================================================================== # ============================================================ # PHẦN 4: BUILD DOCUMENTS & RAG SYSTEM # ============================================================ print('='*70) print('📦 XÂY DỰNG VECTOR DATABASE & RAG SYSTEM') print('='*70) print() from langchain_huggingface.embeddings import HuggingFaceEmbeddings from langchain_core.documents import Document from rank_bm25 import BM25Okapi # Build documents print('🔄 Building documents...') all_docs = [] # From Dataset for i, row in df1.iterrows(): # Truncate text to avoid Pinecone metadata size limit truncated_text = row['text'][:10000] # Limit to 10,000 characters text = f"THỦ TỤC: {row['title']}\nNỘI DUNG: {truncated_text}" all_docs.append(Document(page_content=text, metadata={"source": "ds1", "id": i})) print(f' - Dataset: {len([d for d in all_docs if d.metadata["source"] == "ds1"])} docs') # From Dịch vụ công for code, proc in DICHVUCONG_DATA.items(): text = f"""MÃ: {proc['code']} THỦ TỤC: {proc['name']} HỒ SƠ: {proc['docs']} CƠ QUAN: {proc['coquan']} THỜI GIAN: {proc['time']} LỆ PHÍ: {proc['lephi']} LƯU Ý: {proc['note']} QUY TRÌNH: {proc['steps']}""" all_docs.append(Document(page_content=text, metadata={"source": "dvc", "code": code})) print(f' - Dịch vụ công: {len([d for d in all_docs if d.metadata["source"] == "dvc"])} docs') print(f'\n✅ Total documents: {len(all_docs):,}') print() # Initialize Embeddings print('🔄 Loading embeddings model...') device = 'cuda' if torch.cuda.is_available() else 'cpu' embeddings = HuggingFaceEmbeddings( model_name='keepitreal/vietnamese-sbert', model_kwargs={'device': device}, encode_kwargs={'normalize_embeddings': True} ) print('✅ Embeddings loaded!') print() # Configure Pinecone print('🔄 Configuring Pinecone...') # Import Pinecone libraries from pinecone import Pinecone, PineconeApiException from langchain_pinecone import PineconeVectorStore import time # Nhập PINECONE_API_KEY của bạn # Lấy token tại: https://app.pinecone.io/keys # Trên Hugging Face Spaces, set PINECONE_API_KEY trong Secrets PINECONE_API_KEY = os.environ.get('PINECONE_API_KEY', '') index_name = 'ai-hanh-chinh-rag' dimension = 768 # keepitreal/vietnamese-sbert dimension # Global variables vector_db = None pc = None pinecone_available = False # Hàm kết nối Pinecone với retry def connect_pinecone(max_retries=3): global pc, vector_db, pinecone_available if not PINECONE_API_KEY: print('⚠️ Cần thiết lập PINECONE_API_KEY trong Secrets của Hugging Face Space!') print(' 1. Vào: https://app.pinecone.io/keys') print(' 2. Tạo API Key') print(' 3. Vào Settings → Repository secrets → Thêm: PINECONE_API_KEY') return False for attempt in range(max_retries): try: print(f' 🔑 Đang kết nối Pinecone (lần {attempt + 1}/{max_retries})...') # Initialize Pinecone client pc = Pinecone(api_key=PINECONE_API_KEY) # Kiểm tra index đã tồn tại chưa existing_indexes = [index['name'] for index in pc.list_indexes().get('indexes', [])] if index_name not in existing_indexes: print(f' 📝 Đang tạo index mới: {index_name}...') pc.create_index( name=index_name, dimension=dimension, metric='cosine', spec={'serverless': {'cloud': 'aws', 'region': 'us-east-1'}} ) # Chờ index sẵn sàng print(' ⏳ Chờ index sẵn sàng...') time.sleep(10) else: print(f' ✅ Index {index_name} đã tồn tại') # Kết nối vector store vector_db = PineconeVectorStore( index_name=index_name, embedding=embeddings, pinecone_api_key=PINECONE_API_KEY ) # Kiểm tra số vectors trong index index = pc.Index(index_name) stats = index.describe_index_stats() vector_count = stats.get('total_vector_count', 0) print(f' 📊 Index hiện có: {vector_count:,} vectors') # Nếu index rỗng, thêm documents if vector_count == 0: print(f' 📤 Đang thêm {len(all_docs):,} documents vào Pinecone...') ids = [f"doc_{doc.metadata.get('source', 'unk')}_{i}" for i, doc in enumerate(all_docs)] # Thêm theo batch để tránh lỗi batch_size = 100 for i in range(0, len(all_docs), batch_size): batch_docs = all_docs[i:i+batch_size] batch_ids = ids[i:i+batch_size] vector_db.add_documents(documents=batch_docs, ids=batch_ids) print(f' ✅ Đã thêm {min(i+batch_size, len(all_docs))}/{len(all_docs)} documents') print(' ✅ Hoàn tất thêm documents vào Pinecone!') pinecone_available = True print('✅ Pinecone ready!') return True except PineconeApiException as e: print(f' ⚠️ Pinecone API Error: {e}') if attempt < max_retries - 1: print(f' 🔄 Thử lại sau 2 giây...') time.sleep(2) else: print(' ❌ Không thể kết nối Pinecone sau {max_retries} lần thử') return False except Exception as e: print(f' ❌ Lỗi kết nối Pinecone: {e}') if attempt < max_retries - 1: print(f' 🔄 Thử lại sau 2 giây...') time.sleep(2) else: print(' ❌ Không thể kết nối Pinecone sau {max_retries} lần thử') return False pinecone_available = False return False # Kết nối Pinecone connect_pinecone() print() # Create BM25 print('🔄 Creating BM25 index...') corpus = [doc.page_content for doc in all_docs] tokenized_corpus = [doc.split() for doc in corpus] bm25 = BM25Okapi(tokenized_corpus) print('✅ BM25 ready!') print() print('='*70) # ====================================================================== # ============================================================ # PHẦN 5: TRAIN CLASSIFIER PHÂN LOẠI TÌNH HUỐNG # ============================================================ print('='*70) print('🎯 TRAIN CLASSIFIER PHÂN LOẠI TÌNH HUỐNG') print('='*70) print() from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.ensemble import RandomForestClassifier # Danh sách tình huống từ DICHVUCONG_DATA SITUATIONS = list(DICHVUCONG_DATA.keys()) # Tạo training data từ DICHVUCONG_DATA X_train = [] y_train = [] for code, proc in DICHVUCONG_DATA.items(): # Tạo các câu hỏi mẫu cho mỗi thủ tục questions = [ f"Làm {proc['name'].lower()} cần gì?", f"{proc['name']} ở đâu?", f"Thủ tục {proc['name']} như thế nào?", proc['name'].lower(), f"Cách làm {proc['name'].lower()}", ] for q in questions: X_train.append(q) y_train.append(code) # Thêm các câu hỏi chung general_questions = [ "cho tôi hỏi", "tôi muốn hỏi", "ai giúp tôi với", "cần làm gì", "ở đâu", "bao lâu", "bao nhiêu tiền" ] for q in general_questions: X_train.append(q) y_train.append('hoi') print(f'📊 Training data: {len(X_train)} samples') print(f' - Situations: {len(SITUATIONS)}') # Train TF-IDF vectorizer vec = TfidfVectorizer(max_features=1000) X_train_vec = vec.fit_transform(X_train) # Train classifier clf = RandomForestClassifier(n_estimators=50, max_depth=10, random_state=42) clf.fit(X_train_vec, y_train) print('✅ Classifier trained successfully!') print() print('='*70) # ====================================================================== # ============================================================ # PHẦN 5.1: HYBRID SEARCH FUNCTION # ============================================================ def hybrid_search(query, k_final=5): """Hybrid Search: Pinecone + BM25 với error handling""" # Danh sách kết quả dense_results = [] sparse_results = [] # 1. DENSE SEARCH từ Pinecone if pinecone_available and vector_db is not None: try: # Tìm kiếm với score (cosine similarity) results = vector_db.similarity_search_with_score(query, k=10) # Pinecone trả về (doc, score) với score là distance (càng nhỏ càng tốt) # Chuyển thành similarity (càng lớn càng tốt) for doc, distance in results: if distance < 0: # Nếu score là similarity (đã chuẩn) similarity = distance else: # Nếu score là distance, chuyển thành similarity similarity = 1 - distance dense_results.append({ 'doc': doc, 'score': similarity, 'source': 'pinecone' }) except Exception as e: print(f'⚠️ Pinecone search error: {e}, dùng fallback local docs...') # Fallback: dùng local documents nếu Pinecone lỗi dense_results = [] # 2. SPARSE SEARCH từ BM25 (local) try: tokenized_query = query.split() bm25_scores = bm25.get_scores(tokenized_query) # Lấy top 10 kết quả top_indices = np.argsort(bm25_scores)[::-1][:10] for idx in top_indices: if idx < len(all_docs): sparse_results.append({ 'doc': all_docs[idx], 'score': float(bm25_scores[idx]), 'source': 'bm25' }) except Exception as e: print(f'⚠️ BM25 search error: {e}') sparse_results = [] # 3. KẾT HỢP (Merge) kết quả combined = {} # Thêm kết quả từ Pinecone (ưu tiên cao hơn) for item in dense_results: doc_id = item['doc'].page_content[:100] combined[doc_id] = item # Thêm kết quả từ BM25 (nếu chưa có) for item in sparse_results: doc_id = item['doc'].page_content[:100] if doc_id not in combined: combined[doc_id] = item else: # Nếu đã có từ Pinecone, cộng thêm điểm BM25 combined[doc_id]['score'] += item['score'] * 0.3 # 4. SẮP XẾP và trả về top k_final sorted_results = sorted(combined.values(), key=lambda x: x['score'], reverse=True)[:k_final] # Trả về list documents return [item['doc'] for item in sorted_results] # Hàm lấy documents trực tiếp từ Pinecone def search_pinecone_only(query, k=5): """Tìm kiếm chỉ dùng Pinecone (RAG thuần túy)""" if not pinecone_available or vector_db is None: print('⚠️ Pinecone không khả dụng, dùng local documents...') return all_docs[:k] try: results = vector_db.similarity_search(query, k=k) return results except Exception as e: print(f'⚠️ Lỗi Pinecone: {e}') return all_docs[:k] # Hàm lấy context từ RAG cho LLM def get_rag_context(query, max_docs=5): """Lấy context từ RAG system để đưa vào prompt LLM""" docs = hybrid_search(query, k_final=max_docs) context_parts = [] for i, doc in enumerate(docs, 1): text = doc.page_content.strip() if len(text) > 800: text = text[:800] + "..." context_parts.append(f"[Tài liệu {i}]: {text}") return "\n\n".join(context_parts) print('✅ Hybrid Search function ready!') print(f' - Pinecone: {"✅ Connected" if pinecone_available else "❌ Not available"}') print(f' - BM25: ✅ Ready') print(f' - Total documents: {len(all_docs):,}') print() print('='*70) # ====================================================================== # ============================================================ # PHẦN 6: LOAD LLM (QWEN 2.5 3B) # ============================================================ print('='*70) print('🤖 LOAD LLM (QWEN 2.5 3B INSTRUCT)') print('='*70) print() from transformers import AutoModelForCausalLM, AutoTokenizer MODEL_ID = 'Qwen/Qwen2.5-3B-Instruct' print(f'Model: {MODEL_ID}') # Detect device HAS_GPU = torch.cuda.is_available() device_str = "GPU" if HAS_GPU else "CPU" print(f'🖥️ Device: {device_str}') if HAS_GPU: print('🔄 Loading model với 4-bit quantization (GPU mode)...') from transformers import BitsAndBytesConfig bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_use_double_quant=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16 ) tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained( MODEL_ID, quantization_config=bnb_config, device_map="auto", low_cpu_mem_usage=True, trust_remote_code=True ) quant_type = "4-bit NF4" else: print('🔄 Loading model với float16 (CPU mode - không dùng quantization)...') tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained( MODEL_ID, torch_dtype=torch.float16, device_map="auto", low_cpu_mem_usage=True, trust_remote_code=True ) quant_type = "Float16 (CPU)" print('✅ LLM loaded successfully!') print(f' - Device: {device_str}') print(f' - Quantization: {quant_type}') print() print('='*70) # ====================================================================== # ============================================================ # PHẦN 7: SETUP VOICE AI (WHISPER + EDGE-TTS) - LAZY LOAD # ============================================================ print('='*70) print('🎤 SETUP VOICE AI (LAZY MODE)') print('='*70) print() import whisper import edge_tts import asyncio # Flag để lazy load Whisper whisper_model = None whisper_device = 'cuda' if torch.cuda.is_available() else 'cpu' def get_whisper_model(): """Lazy load Whisper chỉ khi cần""" global whisper_model if whisper_model is None: print('🔄 Loading Whisper model (lazy load)...') whisper_model = whisper.load_model('tiny', device=whisper_device) # Dùng 'tiny' thay 'base' để nhanh hơn print('✅ Whisper ready!') return whisper_model def speech_to_text(audio_path): """Convert speech to text""" try: if audio_path and os.path.exists(audio_path): model = get_whisper_model() result = model.transcribe(audio_path, language='vi', fp16=False) return result["text"].strip() except Exception as e: print(f'Whisper error: {e}') return "" async def text_to_speech(text, voice='vi-VN-HoaiMyNeural'): """Convert text to speech""" try: text = str(text)[:1500].replace('*','').replace('#','').replace('_','') timestamp = datetime.now().strftime('%H%M%S') output_path = os.path.join(DATA_DIR, f"audio_output/audio_{timestamp}.mp3") communicate = edge_tts.Communicate(text, voice) await communicate.save(output_path) return output_path except Exception as e: print(f'TTS error: {e}') return None print('✅ Voice AI ready (Whisper sẽ load khi cần)!') print() print('='*70) # ====================================================================== # ============================================================ # PHẦN 12.1: CÀI ĐẶT THƯ VIỆN NÂNG CAO # ============================================================ print('='*70) print('🚀 NÂNG CẤP AI PRO - IMPORT THƯ VIỆN') print('='*70) print() # Thư viện bổ sung sẽ được cài đặt từ requirements.txt # - sentencepiece>=0.1.99 # - protobuf>=3.20.0 print('✅ Sẵn sàng!') print() # Import bổ sung from functools import lru_cache from typing import List, Dict, Tuple import re import hashlib import time print('='*70) # ====================================================================== # ============================================================ # PHẦN 12.2: PROMPT ENGINE NÂNG CAO (PERSONA-DRIVEN) # ============================================================ print('='*70) print('🎯 PROMPT ENGINE NÂNG CAO') print('='*70) print() # Định nghĩa các persona cho AI AI_PERSONAS = { "thuong": { "name": "Chị Thuong - Cán bộ tư vấn thân thiện", "tone": "thân thiện, gần gũi, dùng từ ngữ bình dân", "greeting": ["Chào bác/cháu ạ,", "Dạ vâng,", "Bác/cháu hỏi đúng người rồi ạ,"], "closing": ["Có gì bác/chứ cứ hỏi thêm nhé!", "Chúc bác/cháu làm thủ tục thuận lợi ạ!"], "style": "conversational" }, "chuyen": { "name": "Anh Chuyen - Chuyên viên hành chính công", "tone": "chuyên nghiệp, ngắn gọn, súc tích", "greeting": ["Dạ, về vấn đề", "Theo quy định,", "Về thủ tục"], "closing": ["Trân trọng!", "Thông tin trên để tham khảo ạ."], "style": "professional" }, "chi_tiet": { "name": "Cô Chi Tiet - Cán bộ hướng dẫn chi tiết", "tone": "chi tiết, từng bước, cẩn thận", "greeting": ["Dạ để em hướng dẫn chi tiết cho bác/cháu ạ,", "Em sẽ giải thích kỹ càng ạ,"], "closing": ["Bác/cháu làm theo như trên là được ạ!", "Nếu chưa hiểu chỗ nào cứ hỏi thêm ạ!"], "style": "detailed" } } # Query Patterns để hiểu ý định người dùng QUERY_PATTERNS = { "can_gi": ["cần gì", "giấy tờ gì", "chuẩn bị gì", "hồ sơ gì", "documents"], "o_day": ["ở đâu", "nơi nào", "địa chỉ", "đ到哪里", "đến đâu"], "bao_lau": ["bao lâu", "mất bao lâu", "thời gian", "how long", "bao nhiêu ngày"], "bao_nhieu": ["bao nhiêu", "bao tiền", "phí", "lệ phí", "cost", "giá"], "the_quy_trinh": ["quy trình", "cách làm", "làm thế nào", "làm sao", "hướng dẫn", "how to"], "nguoi_nuoc_ngoai": ["người nước ngoài", "ngoài", "không phải VN", "foreigner"], "qua_han": ["qua hạn", "quá hạn", "muộn", "sau thời hạn", "overdue"], "mat": ["mất", "thất lạc", "đánh rơi", "không thấy", "lost"], "doi": ["đổi", "làm lại", "cập nhật", "thay đổi", "renew", "change"], "moi": ["lần đầu", "mới", "chưa từng làm", "first time", "new"], } # Query Expansion - Mở rộng câu hỏi def expand_query(query: str) -> List[str]: """Mở rộng câu hỏi để tìm kiếm tốt hơn""" expanded_queries = [query] query_lower = query.lower() # Thêm từ đồng nghĩa for pattern, keywords in QUERY_PATTERNS.items(): for kw in keywords: if kw in query_lower: # Thêm các từ khóa liên quan for related_kw in keywords: if related_kw not in query_lower: expanded_queries.append(query_lower.replace(kw, related_kw)) return list(set(expanded_queries)) # Detect intent from query def detect_intent(query: str) -> Dict[str, any]: """Phát hiện ý định người dùng từ câu hỏi""" query_lower = query.lower() intents = { "need_docs": False, "need_location": False, "need_time": False, "need_fee": False, "need_process": False, "is_foreign": False, "is_overdue": False, "is_lost": False, "is_renewal": False, "is_new": False } for pattern, keywords in QUERY_PATTERNS.items(): for kw in keywords: if kw in query_lower: intents[pattern] = True return intents # Dynamic Response Templates RESPONSE_TEMPLATES = { " khai_sinh": { "short": "Dạ để khai sinh cho bé, bác/cháu cần mang: Giấy khai sinh, CCCD của cả cha mẹ, và sổ hộ khẩu. Ra UBND xã/phường nơi cư trú là được ạ!", "detailed": """Dạ về khai sinh cho bé, em hướng dẫn chi tiết ạ: 📋 **HỒ SƠ CẦN CHUẨN BỊ:** 1. Giấy khai sinh (điền đầy đủ thông tin) 2. CCCD/CMND của cả cha và mẹ 3. Sổ hộ khẩu của cha mẹ 🏢 **NƠI LÀM:** UBND xã/phường nơi cư trú ⏰ **THỜI GIAN:** 1-3 ngày 💰 **LỆ PHÍ:** Miễn phí ⚠️ **LƯU Ý:** Làm trong 60 ngày kể từ khi sinh để tránh phạt nhé!""" } } print('✅ Prompt Engine đã sẵn sàng!') print(f' - Personas: {len(AI_PERSONAS)}') print(f' - Query Patterns: {len(QUERY_PATTERNS)}') print() print('='*70) # ====================================================================== # ============================================================ # PHẦN 12.3: CONTEXT MEMORY & CHAIN OF THOUGHT # ============================================================ print('='*70) print('🧠 CONTEXT MEMORY & CHAIN OF THOUGHT') print('='*70) print() # Conversation Memory with Context Tracking class ConversationMemory: def __init__(self, max_history=10): self.history = [] # Lưu lịch sử chat self.context = {} # Lưu ngữ cảnh (tình huống, giấy tờ đã có, etc.) self.max_history = max_history self.persona = "thuong" # Persona mặc định def add_message(self, role: str, content: str, metadata: dict = None): """Thêm tin nhắn vào lịch sử""" self.history.append({ "role": role, "content": content, "metadata": metadata or {}, "timestamp": time.time() }) # Giữ lịch sử trong giới hạn if len(self.history) > self.max_history * 2: self.history = self.history[-self.max_history * 2:] def update_context(self, key: str, value: any): """Cập nhật ngữ cảnh""" self.context[key] = value def get_context_summary(self) -> str: """Tóm tắt ngữ cảnh hiện tại""" if not self.context: return "" summary_parts = [] if "situation" in self.context: summary_parts.append(f"Vấn đề: {self.context['situation']}") if "has_docs" in self.context: summary_parts.append(f"Đã có giấy tờ: {self.context['has_docs']}") if "location" in self.context: summary_parts.append(f"Khu vực: {self.context['location']}") return " | ".join(summary_parts) if summary_parts else "" def get_relevant_history(self, current_query: str) -> str: """Lấy lịch sử liên quan đến câu hỏi hiện tại""" if not self.history: return "" # Lấy 3-4 tin nhắn gần nhất recent = self.history[-6:] relevant = [] for msg in recent: if msg["role"] == "user": relevant.append(f"Người dùng: {msg['content']}") elif msg["role"] == "assistant": # Chỉ lấy tóm tắt content = msg['content'][:200] relevant.append(f"AI: {content}...") return "\n".join(relevant) def set_persona(self, persona: str): """Thay đổi persona""" if persona in AI_PERSONAS: self.persona = persona def get_persona(self) -> dict: """Lấy persona hiện tại""" return AI_PERSONAS.get(self.persona, AI_PERSONAS["thuong"]) def detect_situation_from_history(self) -> str: """Phát hiện tình huống từ lịch sử""" for msg in reversed(self.history): if msg.get("metadata", {}).get("situation"): return msg["metadata"]["situation"] return "" def clear(self): """Xóa lịch sử và ngữ cảnh""" self.history = [] self.context = {} # Global memory instance conversation_memory = ConversationMemory() # Chain of Thought Prompting def build_chain_of_thought_prompt(query: str, situation: str, context: str, retrieved_docs: list, intents: dict) -> str: """Xây dựng prompt với Chain of Thought để suy luận từng bước""" persona = conversation_memory.get_persona() # Bước 1: Xác định vấn đề step1 = f"""BƯỚC 1 - XÁC ĐỊNH VẤN ĐỀ: Người dùng đang hỏi về: {situation.replace('_', ' ').upper()} Câu hỏi cụ thể: "{query}" """ # Bước 2: Phân tích ý định intent_list = [k for k, v in intents.items() if v] step2 = f"""BƯỚC 2 - PHÂN TÍCH Ý ĐỊNH: Người dùng muốn biết: {', '.join(intent_list) if intent_list else 'thông tin chung'} """ # Bước 3: Lấy thông tin từ tài liệu step3 = """BƯỚC 3 - TRA CỨU THÔNG TIN: Dựa trên tài liệu và quy định hiện hành: """ for i, doc in enumerate(retrieved_docs[:3], 1): doc_text = doc.page_content.strip()[:300] step3 += f"\n[i] {doc_text}..." # Bước 4: Tổng hợp câu trả lời step4 = f"""BƯỚC 4 - TỔNG HỢP TRẢ LỜI: Hãy trả lời theo phong cách: {persona['tone']} Lưu ý: - Dùng các mẫu câu: {', '.join(persona['greeting'][:2])} - Kết thúc bằng: {', '.join(persona['closing'][:2])} - Tránh lặp lại từ ngữ, thay đổi cách diễn đạt - Nếu người dùng hỏi ngắn gọn, trả lời ngắn gọn - Nếu người dùng hỏi chi tiết, trả lời đầy đủ """ if context: step4 += f"\n- Ngữ cảnh trước đó: {context}" return step4 # Dynamic Temperature based on query complexity def calculate_temperature(query: str, situation: str) -> float: """Tính độ sáng tạo phù hợp dựa trên độ phức tạp câu hỏi""" # Câu hỏi đơn giản, cần chính xác cao simple_patterns = ["cần gì", "ở đâu", "bao lâu", "bao nhiêu"] for pattern in simple_patterns: if pattern in query.lower(): return 0.1 # Thấp, tập trung vào chính xác # Câu hỏi phức tạp, cần linh hoạt complex_patterns = ["nhưng", "tuy nhiên", "trong trường hợp", "vậy nếu", "làm sao khi"] for pattern in complex_patterns: if pattern in query.lower(): return 0.4 # Cao hơn, linh hoạt hơn # Câu hỏi tình huống đặc biệt if "qua_han" in situation or "mat" in situation or "doi" in situation: return 0.3 # Trung bình return 0.2 # Mặc định print('✅ Context Memory & CoT đã sẵn sàng!') print() print('='*70) # ====================================================================== # ============================================================ # PHẦN 12.4: AI PROCESS PRO - PHIÊN BẢN NÂNG CẤP # ============================================================ # Response Diversity - Tránh lặp lại import random def get_diverse_greeting(persona_key: str) -> str: """Lấy lời chào ngẫu nhiên để đa dạng hóa""" persona = AI_PERSONAS.get(persona_key, AI_PERSONAS["thuong"]) return random.choice(persona["greeting"]) def get_diverse_closing(persona_key: str) -> str: """Lấy lời kết ngẫu nhiên""" persona = AI_PERSONAS.get(persona_key, AI_PERSONAS["thuong"]) return random.choice(persona["closing"]) # Advanced RAG với Re-ranking def advanced_hybrid_search(query: str, k_final: int = 3) -> list: """Tìm kiếm nâng cao với re-ranking - Tối ưu tốc độ""" # Query expansion - giới hạn để tăng tốc expanded_queries = expand_query(query) # Tìm kiếm với các query mở rộng - giảm từ 3 xuống 2 all_results = {} for expanded_q in expanded_queries[:2]: # Giảm từ 3 xuống 2 để nhanh hơn docs = hybrid_search(expanded_q, k_final=5) # Giảm từ 8 xuống 5 for doc in docs: doc_id = doc.page_content[:50] if doc_id not in all_results: all_results[doc_id] = {"doc": doc, "score": 1.0} else: all_results[doc_id]["score"] += 0.5 # Re-ranking dựa trên relevance query_words = set(query.lower().split()) for doc_id, item in all_results.items(): doc_words = set(item["doc"].page_content.lower().split()) overlap = len(query_words & doc_words) item["score"] += overlap * 0.1 # Sort và trả về sorted_results = sorted(all_results.values(), key=lambda x: x["score"], reverse=True) return [item["doc"] for item in sorted_results[:k_final]] # Smart Response Builder def build_smart_response(query: str, situation: str, docs: list, intents: dict, proc_info: str = "") -> str: """Xây dựng câu trả lời thông minh, tự nhiên""" persona = conversation_memory.get_persona() persona_key = conversation_memory.persona # Xác định độ dài câu trả lời dựa trên câu hỏi query_length = len(query.split()) if query_length <= 5 and any([intents["need_docs"], intents["need_location"]]): # Câu hỏi ngắn, trả lời ngắn gọn response_style = "short" else: response_style = "detailed" # Lấy thông tin thủ tục proc_data = None for code, proc in DICHVUCONG_DATA.items(): if situation.replace("_", "").upper() in code or code in situation.upper(): proc_data = proc break # Xây dựng câu trả lời parts = [] # Lời chào ngẫu nhiên greeting = get_diverse_greeting(persona_key) parts.append(greeting) # Phần thân câu trả lời if response_style == "short" and proc_data: # Trả lời ngắn gọn if intents["need_docs"]: parts.append(f"\nĐể {proc_data['name'].lower()}, bác/cháu cần chuẩn bị:") docs_list = proc_data['docs'].split(',') for doc in docs_list: parts.append(f"- {doc.strip()}") if intents["need_location"]: parts.append(f"\nRa làm ở: {proc_data['coquan']}") if intents["need_time"]: parts.append(f"\nThời gian: {proc_data['time']}") if intents["need_fee"]: parts.append(f"\nLệ phí: {proc_data['lephi']}") if intents["need_process"]: parts.append(f"\nCách làm: {proc_data['steps'][:100]}...") else: # Trả lời chi tiết if proc_data: parts.append(f"\nem sẽ hướng dẫn về **{proc_data['name']}** ạ:") if intents["need_docs"] or not any(intents.values()): parts.append(f"\n📋 **Giấy tờ cần chuẩn bị:**") docs_list = proc_data['docs'].split(',') for i, doc in enumerate(docs_list, 1): parts.append(f"{i}. {doc.strip()}") if intents["need_location"] or not any(intents.values()): parts.append(f"\n🏢 **Nơi làm thủ tục:** {proc_data['coquan']}") if intents["need_time"] or not any(intents.values()): parts.append(f"\n⏰ **Thời gian:** {proc_data['time']}") if intents["need_fee"] or not any(intents.values()): parts.append(f"\n💰 **Lệ phí:** {proc_data['lephi']}") if proc_data.get('note'): parts.append(f"\n⚠️ **Lưu ý:** {proc_data['note']}") if intents["need_process"] or not any(intents.values()): parts.append(f"\n📝 **Quy trình:**") steps = proc_data['steps'].split('.') for step in steps: step = step.strip() if step and len(step) > 3: parts.append(f"- {step}") else: # Không có dữ liệu thủ tục cụ thể parts.append(f"\nvề vấn đề bác/cháu hỏi, em xin được tư vấn ạ:") # Dùng thông tin từ retrieved docs for i, doc in enumerate(docs[:2], 1): doc_text = doc.page_content.strip()[:200] parts.append(f"\nDựa trên tài liệu [{i}]: {doc_text}...") # Lời kết ngẫu nhiên closing = get_diverse_closing(persona_key) parts.append(f"\n\n{closing}") return "\n".join(parts) # AI Process PRO - Hàm chính def ai_process_pro(query: str, use_memory: bool = True) -> dict: """ AI Process PRO với tất cả các cải tiến: - Context Memory - Chain of Thought - Dynamic Temperature - Response Diversity - Advanced RAG """ try: start_time = time.time() # Clean memory torch.cuda.empty_cache() gc.collect() # Bước 1: Phân tích câu hỏi intents = detect_intent(query) # Bước 2: Phân loại tình huống try: situation = clf.predict(vec.transform([query]))[0] except: # Fallback: detect từ query patterns for pattern, keywords in QUERY_PATTERNS.items(): for kw in keywords: if kw in query.lower(): situation = pattern break else: continue break else: situation = 'hoi' # Bước 3: Advanced RAG retrieval docs = advanced_hybrid_search(query, k_final=3) # Giảm từ 5 xuống 3 # Bước 4: Lấy thông tin thủ tục proc_info = "" proc_data = None for code, proc in DICHVUCONG_DATA.items(): if situation.replace("_", "").upper() in code or code in situation.upper(): proc_data = proc proc_info = f""" THỦ TỤC: {proc['name']} HỒ SƠ: {proc['docs']} CƠ QUAN: {proc['coquan']} THỜI GIAN: {proc['time']} LỆ PHÍ: {proc['lephi']} LƯU Ý: {proc['note']} QUY TRÌNH: {proc['steps']} """ break # Bước 5: Lấy ngữ cảnh từ memory context_summary = "" relevant_history = "" if use_memory: conversation_memory.update_context("situation", situation) context_summary = conversation_memory.get_context_summary() relevant_history = conversation_memory.get_relevant_history(query) # Bước 6: Tính toán động temperature temperature = calculate_temperature(query, situation) # Bước 7: Xây dựng prompt với Chain of Thought cot_prompt = build_chain_of_thought_prompt( query=query, situation=situation, context=context_summary, retrieved_docs=docs, intents=intents ) # Bước 8: Full prompt cho LLM full_prompt = f"""Bạn là cán bộ tư vấn thủ tục hành chính công tại UBND. Hãy hỗ trợ người dân. {cot_prompt} CÂU HỎI: {query} {proc_info} LỊCH SỬ ĐỌC CHUẨN KHI CÓ: {relevant_history} HÃY TRẢ LỜI NGƯỜI DÂNG:""" messages = [ {"role": "system", "content": "Bạn là cán bộ UBND tận tâm, luôn hỗ trợ người dân nhiệt tình. Trả lời bằng tiếng Việt, ngôn ngữ tự nhiên, gần gũi như cán bộ thực tế đang tư vấn."}, {"role": "user", "content": full_prompt} ] text_prompt = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) # Bước 9: Generate response với dynamic temperature - dùng model.generate trực tiếp inputs = tokenizer(text_prompt, return_tensors="pt").to(model.device) input_length = inputs.input_ids.shape[1] # Lấy độ dài của prompt đầu vào outputs = model.generate( **inputs, max_new_tokens=512, # Tăng lên một chút để tránh bị cắt chữ temperature=temperature, top_p=0.9, repetition_penalty=1.15, do_sample=temperature > 0.2 ) # CHỈ giải mã những token mới (phần sau input_length) response_text = tokenizer.decode(outputs[0][input_length:], skip_special_tokens=True) # Clean response if "<|im_start|>assistant\n" in response_text: response_text = response_text.split("<|im_start|>assistant\n")[-1] if "<|im_end|>" in response_text: response_text = response_text.split("<|im_end|")[0] llm_response = response_text.strip() # Bước 10: Post-process để đảm bảo tự nhiên # Loại bỏ tiếng Trung, ký tự lạ clean_lines = [] for line in llm_response.split('\n'): # Filter Chinese characters if any('\u4e00' <= char <= '\u9fff' for char in line): continue # Filter unwanted phrases unwanted_keywords = ["Infrastructure", "Last Updated", "Step 1 -", "BƯỚC 1", "BƯỚC 2", "BƯỚC 3", "BƯỚC 4"] if any(phrase in line for phrase in unwanted_keywords): continue clean_lines.append(line) final_response = '\n'.join(clean_lines).strip() # Fallback nếu response quá ngắn if len(final_response) < 50: # Dùng smart response builder thay vì prompt mặc định final_response = build_smart_response(query, situation, docs, intents, proc_info) if "persons" in final_response.lower(): final_response = final_response.replace("Persons", "hành chính về trật tự xã hội") final_response = final_response.replace("persons", "hành chính về trật tự xã hội") # Fallback cuối cùng if len(final_response) < 30: final_response = "Dạ bác/cháu hỏi đúng người rồi ạ! Hiện tại em chưa có đầy đủ thông tin về trường hợp này. Bác/cháu vui lòng mang giấy tờ tùy thân ra trực tiếp UBND Phường/Xã để được hỗ trợ chi tiết hơn ạ!" # Lưu vào memory if use_memory: conversation_memory.add_message("user", query, {"situation": situation}) conversation_memory.add_message("assistant", final_response, {"situation": situation}) # Clean memory torch.cuda.empty_cache() gc.collect() process_time = time.time() - start_time return { "situation": situation, "response": final_response, "docs_count": len(docs), "process_time": round(process_time, 2), "intents": intents, "temperature": temperature } except Exception as e: print(f'AI Process PRO Error: {e}') import traceback traceback.print_exc() # Fallback với smart response try: fallback_response = build_smart_response( query, conversation_memory.detect_situation_from_history() or 'hoi', [], detect_intent(query), "" ) return { "situation": "fallback", "response": fallback_response, "docs_count": 0, "process_time": 0.5 } except: return { "situation": "error", "response": "Dạ xin lỗi, hệ thống đang bận. Bác/cháu vui lòng thử lại sau hoặc đến trực tiếp UBND để được hỗ trợ ạ!", "docs_count": 0, "process_time": 0 } print('✅ AI Process PRO đã sẵn sàng!') print() print('Cải tiến:') print(' - Context Memory: Nhớ lịch sử chat') print(' - Chain of Thought: Suy luận từng bước') print(' - Dynamic Temperature: Tự điều chỉnh độ sáng tạo') print(' - Response Diversity: Tránh lặp lại câu trả lời') print(' - Advanced RAG: Tìm kiếm chính xác hơn') print() print('='*70) # ====================================================================== # ============================================================ # PHẦN 12.5: GRADIO INTERFACE # ============================================================ import gradio as gr print('='*70) print('🖥️ KHỞI TẠO GRADIO INTERFACE') print('='*70) print() # Admin functions admin_procedures = list(DICHVUCONG_DATA.values()) def get_procedures_list(): lines = ["| Mã | Tên thủ tục | Cơ quan |"] lines.append("|---|---|---|") for p in admin_procedures[:20]: # Limit to 20 for display lines.append(f"| {p['code']} | {p['name']} | {p['coquan']} |") return "\n".join(lines) def admin_add(code, name, docs, coquan, time_val, lephi, note, steps): global admin_procedures new_proc = { 'code': code, 'name': name, 'docs': docs, 'coquan': coquan, 'time': time_val, 'lephi': lephi, 'note': note, 'steps': steps } admin_procedures.append(new_proc) # For simplicity, we are not updating DICHVUCONG_DATA or Pinecone here directly # In a real application, you would update the backend data source return f"✅ Đã thêm: {code} - {name}", get_procedures_list() # Chat function với AI Process PRO def process_chat_pro(audio, text, history, persona="thuong"): try: if history is None: history = [] # Cập nhật persona conversation_memory.set_persona(persona) # Xử lý input query = '' if audio and not os.path.isdir(audio): query = speech_to_text(audio) elif text and text.strip(): query = text.strip() if len(query) < 3: return history, None, '' # Dùng AI Process PRO result = ai_process_pro(query, use_memory=True) # Format response response = f""" {result['response']} --- _⏱️ Xử lý: {result['process_time']}s | 📄 Tài liệu: {result['docs_count']} | 🌡️ Temp: {result['temperature']}_""" history.append({"role": "user", "content": query}) history.append({"role": "assistant", "content": response}) # TTS audio_out = None try: loop = asyncio.get_event_loop() except: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: audio_out = loop.run_until_complete(text_to_speech(result['response'])) except: pass return history, audio_out, '' except Exception as e: import traceback traceback.print_exc() error_msg = f"Lỗi: {str(e)}" return history, None, error_msg def clear_chat_pro(): conversation_memory.clear() return [], None, '' def change_persona(new_persona): conversation_memory.set_persona(new_persona) persona_names = { "thuong": "Chị Thuong - Thân thiện", "chuyen": "Anh Chuyen - Chuyên nghiệp", "chi_tiet": "Cô Chi Tiet - Chi tiết" } return f"✅ Đã chuyển sang: {persona_names.get(new_persona, new_persona)}" # Build PRO interface custom_css = """ /* Reset font & nền */ .gradio-container { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important; background-color: #f8f9fa !important; } /* Ẩn footer mặc định */ footer {display: none !important;} /* Thanh Topbar chuyên nghiệp (Gradient Xanh Navy) */ .admin-topbar { background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); padding: 20px 30px; display: flex; align-items: center; margin: -20px -20px 20px -20px; /* Tràn viền */ box-shadow: 0 4px 12px rgba(0,0,0,0.1); color: white; } .admin-icon { font-size: 45px; margin-right: 20px; background: rgba(255,255,255,0.1); padding: 10px; border-radius: 12px; } .admin-brand h1 { color: #ffffff !important; font-size: 24px !important; font-weight: 600 !important; margin: 0 !important; letter-spacing: 1px; text-transform: uppercase; } .admin-brand p { color: #e0e0e0 !important; font-size: 14px !important; margin: 5px 0 0 0 !important; font-weight: 300 !important; } /* Tinh chỉnh Nút bấm */ button.primary { background-color: #0056b3 !important; color: white !important; border-radius: 6px !important; font-weight: 500 !important; border: none !important; transition: all 0.3s ease; } button.primary:hover { background-color: #004494 !important; box-shadow: 0 4px 8px rgba(0,86,179,0.3) !important; } /* Tùy chỉnh Khung Chat */ .main-chatbot { border: 1px solid #dee2e6 !important; border-radius: 8px !important; background-color: #ffffff !important; box-shadow: 0 2px 15px rgba(0,0,0,0.03) !important; } /* Tiêu đề Box */ .section-title { color: #1e3c72; font-weight: 600; margin-bottom: 10px; border-bottom: 2px solid #e9ecef; padding-bottom: 5px; } """ # Khởi tạo giao diện với Theme base màu xanh thanh lịch with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", neutral_hue="slate"), css=custom_css) as demo: # 1. TOPBAR CHUYÊN NGHIỆP (Không dùng logo/tên thật) gr.HTML("""
🏛️

HỆ THỐNG TRỢ LÝ ẢO TƯ VẤN HÀNH CHÍNH

Giải đáp thông tin thủ tục - Nhanh chóng, Chính xác, Hỗ trợ 24/7

""") with gr.Tabs(): # Chat Tab with gr.Tab("💬 Trợ lý AI"): with gr.Row(): with gr.Column(scale=7): chat = gr.Chatbot(height=550, label='Hệ thống tư vấn tự động', elem_classes="main-chatbot") audio_out = gr.Audio(label='🔊 Nghe phản hồi', autoplay=True) with gr.Column(scale=3): gr.HTML("
Tra cứu thông tin
") msg_in = gr.Textbox(label='Nhập câu hỏi của bạn', lines=3, placeholder='VD: Điều kiện để cấp lại căn cước công dân là gì?') audio_in = gr.Audio(sources=['microphone'], type='filepath', label='🎤 Tìm kiếm bằng giọng nói') with gr.Row(): btn_send = gr.Button('GỬI CÂU HỎI', variant='primary') btn_clear = gr.Button('Làm mới') # --- Ô HƯỚNG DẪN NHỎ THÊM VÀO ĐÂY --- gr.HTML("""
💡 Cách dùng nhanh:
""") gr.Markdown("---") gr.Markdown("**📌 Gợi ý tra cứu nhanh:**") gr.Examples( examples=[ [None, 'Làm bằng lái xe máy cần giấy tờ gì?'], [None, 'Khai sinh quá hạn phải làm sao?'], [None, 'Đăng ký xe ô tô mới ở đâu?'], [None, 'Tách hộ khẩu cần những gì?'], [None, 'Làm hộ chiếu bao nhiêu tiền?'], ], inputs=[audio_in, msg_in] ) # (Các sự kiện click giữ nguyên) btn_send.click(process_chat_pro, [audio_in, msg_in, chat], [chat, audio_out, msg_in]) msg_in.submit(process_chat_pro, [audio_in, msg_in, chat], [chat, audio_out, msg_in]) btn_clear.click(clear_chat_pro, [], [chat, audio_out, msg_in]) with gr.Tab("⚙️ Admin Panel"): gr.Markdown("### 🔐 Xác thực quyền quản trị") with gr.Row(): input_pass = gr.Textbox( label="Nhập mật khẩu để mở khóa tính năng Admin", type="password", placeholder="nhap mat khau", scale=4 ) btn_login = gr.Button("Xác thực 🔑", scale=1) # Group chứa nội dung Admin - Mặc định ẩn (visible=False) admin_content = gr.Group(visible=False) with admin_content: gr.Markdown("---") gr.Markdown("### ➕ Thêm thủ tục mới (AI sẽ học ngay)") with gr.Row(): with gr.Column(): admin_code = gr.Textbox(label="Mã thủ tục") admin_name = gr.Textbox(label="Tên thủ tục") admin_docs = gr.Textbox(label="Hồ sơ cần chuẩn bị", lines=2) admin_coquan = gr.Textbox(label="Cơ quan tiếp nhận") with gr.Column(): admin_time = gr.Textbox(label="Thời gian") admin_lephi = gr.Textbox(label="Lệ phí") admin_note = gr.Textbox(label="Lưu ý", lines=2) admin_steps = gr.Textbox(label="Quy trình", lines=3) btn_add = gr.Button("➕ THÊM VÀO HỆ THỐNG", variant='primary', size='lg') add_output = gr.Textbox(label="Kết quả xử lý") procedures_list = gr.Markdown(value=get_procedures_list()) # --- Logic xử lý ẩn hiện và mật khẩu --- def check_admin(password): if password == ADMIN_PASSWORD: # Nếu đúng: Hiện nội dung admin, ẩn ô nhập mật khẩu và hiện thông báo thành công gr.Info("Đăng nhập thành công! Quyền quản trị đã được mở.") return gr.update(visible=True), gr.update(visible=False) else: # Nếu sai: Giữ ẩn nội dung và hiện cảnh báo raise gr.Error("Mật khẩu không chính xác. Vui lòng thử lại!") return gr.update(visible=False), gr.update(visible=True) # Sự kiện khi nhấn nút Xác thực btn_login.click( check_admin, inputs=[input_pass], outputs=[admin_content, input_pass] ) # Sự kiện khi nhấn nút THÊM (giữ nguyên logic cũ của bạn) btn_add.click( admin_add, [admin_code, admin_name, admin_docs, admin_coquan, admin_time, admin_lephi, admin_note, admin_steps], [add_output, procedures_list] ) # Memory Tab (mới) with gr.Tab("🧠 Memory Manager"): gr.Markdown("### 📊 Quản lý bộ nhớ hội thoại") memory_info = gr.Textbox(label="Ngữ cảnh hiện tại", interactive=False, lines=3) memory_history = gr.Textbox(label="Lịch sử chat", interactive=False, lines=10) with gr.Row(): btn_refresh = gr.Button("🔄 Làm mới") btn_clear_mem = gr.Button("🗑️ Xóa memory", variant='stop') def refresh_memory(): ctx = conversation_memory.get_context_summary() hist = "\n".join([f"{m['role']}: {m['content'][:100]}..." for m in conversation_memory.history[-5:]]) return ctx or "Không có ngữ cảnh", hist or "Không có lịch sử" btn_refresh.click(refresh_memory, [], [memory_info, memory_history]) btn_clear_mem.click(clear_chat_pro, [], [chat, audio_out, msg_in]) # ====================================================================== # ============================================================ # PHẦN 12.7: LAUNCH APP PRO # ============================================================ print('='*70) print('🚀 KHỞI ĐỘNG ỨNG DỤNG AI PRO') print('='*70) print() print('='*70) print(' Đang tạo public link...') print(' Link sẽ xuất hiện bên dưới sau vài giây...') print('='*70) print() # Launch với demo_pro thay vì demo demo.launch( share=True, server_name='0.0.0.0', show_error=True )