This commit is contained in:
parent
feee56dc56
commit
a4158929cc
8
.env
8
.env
|
|
@ -12,4 +12,10 @@ EMBED_DIM=768
|
|||
QDRANT_URL=http://localhost:6333
|
||||
QDRANT_COLLECTION=text_chunks
|
||||
GEMINI_API_KEY = "AIzaSyDWqNUBKhaZjbFI8CW52_hKr46JtWABkGU"
|
||||
GEMINI_MODEL = "models/gemini-2.0-flash-001"
|
||||
GEMINI_MODEL = "models/gemini-2.0-flash-001"
|
||||
SERPER_API_KEY = "14ea1e5de7b68084d3e41a4efe204108a8fa76c6fab062b00fa1912a97d3c28b"
|
||||
GOOGLE_API_KEY=AIzaSyCOtXgt7M6qKlj5srF8hF_7iyvZbBrVnhA
|
||||
GOOGLE_CX=9379e94eed0f145b4
|
||||
TRUSTED_DOMAINS = "wikipedia.org,chinhphu.vn,vnexpress.net,bbc.com,nhandan.vn,tuoitre.vn,thanhnien.vn,zingnews.vn,vov.vn,vietnamnet.vn"
|
||||
ALLOW_WEB_SEARCH=true
|
||||
SECOND_PASS=true
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
|
|
@ -0,0 +1,257 @@
|
|||
2025-10-12 18:14:03,660 [INFO] numexpr.utils - NumExpr defaulting to 12 threads.
|
||||
2025-10-12 18:15:29,187 [INFO] httpx - HTTP Request: GET http://localhost:6333 "HTTP/1.1 200 OK"
|
||||
2025-10-12 18:15:29,199 [INFO] httpx - HTTP Request: GET http://localhost:6333/collections/text_chunks "HTTP/1.1 200 OK"
|
||||
2025-10-12 18:15:29,211 [INFO] sentence_transformers.SentenceTransformer - Load pretrained SentenceTransformer: Alibaba-NLP/gte-multilingual-base
|
||||
2025-10-12 18:15:43,063 [INFO] src.chatbot.llm_client - 🔮 LLMClient khởi tạo với model: models/gemini-2.0-flash-001
|
||||
2025-10-12 18:15:43,063 [INFO] function_router - 🔧 Đã đăng ký tool: create_txt
|
||||
2025-10-12 18:15:43,064 [INFO] rag_api - ✅ RAGPipeline đã khởi tạo thành công.
|
||||
2025-10-12 18:15:46,719 [INFO] rag_api - 📥 Câu hỏi: Đại học Hanover có bao nhiêu khoa? Có bao nhiêu nhân viên?
|
||||
2025-10-12 18:16:03,855 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK"
|
||||
2025-10-12 18:19:21,649 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini...
|
||||
2025-10-12 18:19:27,724 [INFO] src.chatbot.llm_client - 💬 Gemini output: Đại học Hanover có 9 khoa. Đội ngũ cán bộ của trường bao gồm 1.120 nhân viên, trong đó có 340 giáo sư và 1.560 nhân viên...
|
||||
2025-10-12 18:19:40,751 [INFO] root - 📤 Gửi prompt tới LLM: ### Vai trò hệ thống:
|
||||
Bạn là trợ lý AI chuyên nghiệp, hiểu tiếng Việt, có khả năng trả lời tự nhiên, chính xác và ngắn gọn. Bạn có thể vừa trả lời, vừa thực hiện hành động theo yêu cầu người dùng. Nếu người dùng chỉ đặt câu hỏi, hãy trả lời tự nhiên. Nếu người dùng yêu cầu hành động, hãy trả về **đúng JSON hợp lệ**.
|
||||
|
||||
### Dữ liệu ngữ cảnh:
|
||||
Dưới đây là các thông tin có liên quan:
|
||||
|
||||
(Đoạn 1 - score=0.886)
|
||||
Đại học Hanover
|
||||
|
||||
Đại học Hanover, chính thức là Gottfried Wilhelm Leibniz Universität Hannover hoặc Luh, là một trường đại học nằm ở Hanover, Đức. Trường được thành lập vào năm 1831 và là tổ chức đào tạo đại học lớn thứ hai ở Niedersachsen. Đại học Leibniz Hannover là một thành viên của TU9, một hiệp hội của chín Viện Công nghệ hàng đầu tại Đức.
|
||||
Lịch sử.
|
||||
Trường đại học này được thành lập vào năm 1831 là một trường cao đẳng thương mại. Trường đã bắt đầu nghiên cứu toán học, kiến trúc, kỹ thuật, lịch sử tự nhiên, vật lý, hóa học, vẽ, công nghệ, nghiên cứu và kế toán. Năm 1879 trường đã được nâng cấp thành Trường Cao đẳng Công nghệ Hoàng gia, năm 1898 nó đã được trao quyền đào tạo tiến sĩ.
|
||||
Lĩnh vực của trường đại học này, từ đầu của nó, tập trung vào khoa học và công nghệ. Trong thế kỷ 20, các ngành nghệ thuật và nhân văn đã được bổ sung, và trường đã được sáp nhập thêm Trường Cao đẳng Sư phạm trước đó là một trường độc lập.
|
||||
Khoa.
|
||||
Trường có 9 khoa với hơn 150 cấp độ độ đầu tiên toàn thời gian và các khóa học trình độ bán thời gian, khiến cho trường này là trường đại học lớn thứ hai của giáo dục đại học tổ chức ở Lower Saxony. Đội ngũ cán bộ trường đại học này bao gồm 1.120 nhân viên, bao gồm 340 giáo sư, 1.560 nhân viên trong các chức năng hành chính, và có thêm 900 người được tài trợ của bên thứ ba.
|
||||
|
||||
(Đoạn 2 - score=0.630)
|
||||
Viện Thiên văn học của Đại học Hawaii
|
||||
|
||||
Viện Thiên văn học của Đại học Hawaii (, viết tắt: IfA) là một đơn vị nghiên cứu trong hệ thống Đại học Hawaii, do Günther Hasinger làm giám đốc. Trụ sở chính của IfA đặt tại 2680 Woodlawn Drive ở Honolulu, Hawaii, , trong khuôn viên Đại học Hawaii tại Mānoa. Các cơ sở khác đặt tại Pukalani, Maui và Hilo trên đảo Hawaiʻi (Đảo Lớn). IfA tuyển dụng hơn 150 nhà thiên văn học và tình nguyện viên. Các nhà thiên văn IfA thực hiện nghiên cứu vật thể, sao, thiên hà và Hệ Mặt Trời.
|
||||
Viện Thiên văn học được thành lập năm 1967 để nghiên cứu và quản lý các khu phức hợp quan sát tại Haleakalā, Maui và Đài Quan sát Mauna Kea trên đỉnh Mauna Kea. Nó có khoảng 55 giảng viên và hơn 300 nhân viên.
|
||||
|
||||
(Đoạn 3 - score=0.607)
|
||||
Đại học Khoa học Đời sống Warszawa
|
||||
|
||||
Đại học Khoa học Đời sống Warszawa (, SGGW) là trường đại học nông nghiệp lớn nhất Ba Lan, được thành lập năm 1816 tại Warszawa. Trường có hơn 2.600 nhân viên bao gồm hơn 1.200 nhà giáo dục học thuật. Từ năm 2005, trường đại học này là một thành viên của tổ chức Euroleague cho Khoa học sự sống (ELLS) được thành lập năm 2001. SGGW cung cấp khoảng 37 lĩnh vực nghiên cứu khác nhau, 13 khoa Khoa học Nông nghiệp, Khoa học Kinh tế, Nhân văn, Kỹ thuật cũng như Khoa học Đời sống.
|
||||
Khuôn viên.
|
||||
Khuôn viên trường nằm ở quận cực nam của Warszawa, Ursynów. Khuôn viên có một phần lịch sử, với một cung điện từ thế kỷ 18, và một phần hiện đại nơi có hầu hết các tòa nhà văn phòng khoa và ký túc xá. Trên khuôn viên chính rộng 70 ha, có 12 ký túc xá, thư viện hiện đại, trung tâm thể thao (có sân tennis, phòng thể thao và hồ bơi) một trung tâm ngôn ngữ, phòng khám thú y.
|
||||
|
||||
(Đoạn 4 - score=0.591)
|
||||
Hán học
|
||||
|
||||
Hán học (chữ Hán: 漢學) hay Trung Quốc học (chữ Hán: 中國學) là ngành khoa học chuyên nghiên cứu về Trung Quốc, bao gồm lịch sử, chính trị, xã hội, triết học, kinh tế, thậm chí nghiên cứu cả về cộng đồng người Hoa ở nước ngoài. Đây là khái niệm do người nước ngoài đặt ra, tiếng Anh gọi môn khoa học này là Sinology hay Chinese Studies, còn người Trung Quốc gọi khoa học nghiên cứu về Trung Quốc là Quốc học 國學.
|
||||
Lịch sử.
|
||||
Ban đầu Hán học chỉ nghiên cứu về văn hoá cổ đại Trung Quốc, chủ yếu nghiên cứu cổ văn, triết học, văn học, hầu như không bao quát hết xã hội Trung Quốc hiện đại. Sau Chiến tranh thế giới thứ hai, Hán học mới bắt đầu nghiên cứu đến Trung Quốc hiện đại.
|
||||
Hán học thường được chia làm hai thời kỳ là "Hán học cổ đại" và "Hán học hiện đại" …
|
||||
|
||||
### Công cụ có thể sử dụng:
|
||||
|
||||
Hiện bạn có thể sử dụng công cụ sau:
|
||||
1. create_txt(text: str) — Tạo file .txt chứa nội dung văn bản.
|
||||
|
||||
Nếu người dùng yêu cầu hành động (như tạo, lưu, ghi vào file),
|
||||
hãy trả về đúng JSON theo mẫu sau:
|
||||
{"action": "create_txt", "params": {"text": "<nội dung cần lưu>"}}
|
||||
|
||||
Nếu người dùng chỉ hỏi thông tin, hãy trả lời văn bản tự nhiên, không JSON.
|
||||
|
||||
|
||||
### Câu hỏi của người dùng:
|
||||
Đại học Hanover có bao nhiêu khoa? Có bao nhiêu nhân viên?
|
||||
|
||||
### Hướng dẫn cho AI:
|
||||
- Nếu người dùng chỉ hỏi → trả lời tự nhiên, ngắn gọn, chính xác.
|
||||
- Nếu người dùng yêu cầu lưu/tạo file → hãy dùng create_txt.
|
||||
- Khi trả về JSON, không thêm giải thích, chỉ trả JSON hợp lệ duy nhất.
|
||||
- Nếu không đủ dữ liệu, hãy nói rõ ràng rằng bạn chưa có thông tin chính xác.
|
||||
- Không bịa thêm, không suy diễn.
|
||||
|
||||
### Trả lời:
|
||||
...
|
||||
2025-10-12 18:19:45,211 [INFO] rag_api - ✅ Đã trả lời: Đại học Hanover có bao nhiêu khoa? Có bao nhiêu nh...
|
||||
2025-10-12 18:20:13,978 [INFO] rag_api - 📥 Câu hỏi: Đại học Hanover có bao nhiêu khoa? Có bao nhiêu nhân viên? Lưu thông tin vào file txt
|
||||
2025-10-12 18:20:16,935 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK"
|
||||
2025-10-12 18:21:46,603 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini...
|
||||
2025-10-12 18:22:08,879 [INFO] src.chatbot.llm_client - 💬 Gemini output: Đại học Hanover có 9 khoa. Số lượng nhân viên là 1.120, bao gồm 340 giáo sư, 1.560 nhân viên hành chính, và 900 người đư...
|
||||
2025-10-12 18:22:21,101 [INFO] src.chatbot.llm_client - 🤖 Function call JSON: {'action': 'create_txt', 'params': {'text': 'Đại học Hanover có 9 khoa. Số lượng nhân viên là 1.120, bao gồm 340 giáo sư, 1.560 nhân viên hành chính, và 900 người được tài trợ.'}}
|
||||
2025-10-12 18:22:34,885 [WARNING] function_executor - Không thể parse JSON từ LLM output: 'dict' object has no attribute 'find'
|
||||
2025-10-12 18:22:47,884 [INFO] root - 📤 Gửi prompt tới LLM: ### Vai trò hệ thống:
|
||||
Bạn là trợ lý AI chuyên nghiệp, hiểu tiếng Việt, có khả năng trả lời tự nhiên, chính xác và ngắn gọn. Bạn có thể vừa trả lời, vừa thực hiện hành động theo yêu cầu người dùng. Nếu người dùng chỉ đặt câu hỏi, hãy trả lời tự nhiên. Nếu người dùng yêu cầu hành động, hãy trả về **đúng JSON hợp lệ**.
|
||||
|
||||
### Dữ liệu ngữ cảnh:
|
||||
Dưới đây là các thông tin có liên quan:
|
||||
|
||||
(Đoạn 1 - score=0.818)
|
||||
Đại học Hanover
|
||||
|
||||
Đại học Hanover, chính thức là Gottfried Wilhelm Leibniz Universität Hannover hoặc Luh, là một trường đại học nằm ở Hanover, Đức. Trường được thành lập vào năm 1831 và là tổ chức đào tạo đại học lớn thứ hai ở Niedersachsen. Đại học Leibniz Hannover là một thành viên của TU9, một hiệp hội của chín Viện Công nghệ hàng đầu tại Đức.
|
||||
Lịch sử.
|
||||
Trường đại học này được thành lập vào năm 1831 là một trường cao đẳng thương mại. Trường đã bắt đầu nghiên cứu toán học, kiến trúc, kỹ thuật, lịch sử tự nhiên, vật lý, hóa học, vẽ, công nghệ, nghiên cứu và kế toán. Năm 1879 trường đã được nâng cấp thành Trường Cao đẳng Công nghệ Hoàng gia, năm 1898 nó đã được trao quyền đào tạo tiến sĩ.
|
||||
Lĩnh vực của trường đại học này, từ đầu của nó, tập trung vào khoa học và công nghệ. Trong thế kỷ 20, các ngành nghệ thuật và nhân văn đã được bổ sung, và trường đã được sáp nhập thêm Trường Cao đẳng Sư phạm trước đó là một trường độc lập.
|
||||
Khoa.
|
||||
Trường có 9 khoa với hơn 150 cấp độ độ đầu tiên toàn thời gian và các khóa học trình độ bán thời gian, khiến cho trường này là trường đại học lớn thứ hai của giáo dục đại học tổ chức ở Lower Saxony. Đội ngũ cán bộ trường đại học này bao gồm 1.120 nhân viên, bao gồm 340 giáo sư, 1.560 nhân viên trong các chức năng hành chính, và có thêm 900 người được tài trợ của bên thứ ba.
|
||||
|
||||
(Đoạn 2 - score=0.584)
|
||||
Viện Thiên văn học của Đại học Hawaii
|
||||
|
||||
Viện Thiên văn học của Đại học Hawaii (, viết tắt: IfA) là một đơn vị nghiên cứu trong hệ thống Đại học Hawaii, do Günther Hasinger làm giám đốc. Trụ sở chính của IfA đặt tại 2680 Woodlawn Drive ở Honolulu, Hawaii, , trong khuôn viên Đại học Hawaii tại Mānoa. Các cơ sở khác đặt tại Pukalani, Maui và Hilo trên đảo Hawaiʻi (Đảo Lớn). IfA tuyển dụng hơn 150 nhà thiên văn học và tình nguyện viên. Các nhà thiên văn IfA thực hiện nghiên cứu vật thể, sao, thiên hà và Hệ Mặt Trời.
|
||||
Viện Thiên văn học được thành lập năm 1967 để nghiên cứu và quản lý các khu phức hợp quan sát tại Haleakalā, Maui và Đài Quan sát Mauna Kea trên đỉnh Mauna Kea. Nó có khoảng 55 giảng viên và hơn 300 nhân viên.
|
||||
|
||||
(Đoạn 3 - score=0.573)
|
||||
Đại học Khoa học Đời sống Warszawa
|
||||
|
||||
Đại học Khoa học Đời sống Warszawa (, SGGW) là trường đại học nông nghiệp lớn nhất Ba Lan, được thành lập năm 1816 tại Warszawa. Trường có hơn 2.600 nhân viên bao gồm hơn 1.200 nhà giáo dục học thuật. Từ năm 2005, trường đại học này là một thành viên của tổ chức Euroleague cho Khoa học sự sống (ELLS) được thành lập năm 2001. SGGW cung cấp khoảng 37 lĩnh vực nghiên cứu khác nhau, 13 khoa Khoa học Nông nghiệp, Khoa học Kinh tế, Nhân văn, Kỹ thuật cũng như Khoa học Đời sống.
|
||||
Khuôn viên.
|
||||
Khuôn viên trường nằm ở quận cực nam của Warszawa, Ursynów. Khuôn viên có một phần lịch sử, với một cung điện từ thế kỷ 18, và một phần hiện đại nơi có hầu hết các tòa nhà văn phòng khoa và ký túc xá. Trên khuôn viên chính rộng 70 ha, có 12 ký túc xá, thư viện hiện đại, trung tâm thể thao (có sân tennis, phòng thể thao và hồ bơi) một trung tâm ngôn ngữ, phòng khám thú y.
|
||||
|
||||
(Đoạn 4 - score=0.545)
|
||||
Đại học Eötvös Loránd
|
||||
|
||||
Đại học Eötvös Loránd (phiên âm: Ết-vêx Lô-ran), thành lập năm 1635, là trường đại học lớn nhất ở Hungary. Trường tọa lạc tại Thủ đô Budapest.
|
||||
Lịch sử.
|
||||
Trường được Đức Tổng Giám mục và nhà thần học Péter Pázmány thành lập năm 1635 tại Nagyszombat (Trnava, Slovakia hiện nay). Các tu sĩ Dòng Tên đã nắm quyền lãnh đạo trường. Ban đầu, trường chỉ có hai trường thành viên (Trường Nghệ thuật và Trường Thần học). Đại học Luật đã được bổ sung năm 1667 và Đại học Y khoa đã được bắt đầu năm 1769. Sau đợt giải thể của trật tự dòng Tên, trường đại học đã được chuyển đến Buda (ngày nay là một phần của Budapest) vào năm 1777 phù hợp với ý định của người sáng lập. Trường đã được di chuyển đến vị trí cuối cùng của nó trong Pest (cũng là một phần của Budapest) năm 1784 …
|
||||
|
||||
### Công cụ có thể sử dụng:
|
||||
|
||||
Hiện bạn có thể sử dụng công cụ sau:
|
||||
1. create_txt(text: str) — Tạo file .txt chứa nội dung văn bản.
|
||||
|
||||
Nếu người dùng yêu cầu hành động (như tạo, lưu, ghi vào file),
|
||||
hãy trả về đúng JSON theo mẫu sau:
|
||||
{"action": "create_txt", "params": {"text": "<nội dung cần lưu>"}}
|
||||
|
||||
Nếu người dùng chỉ hỏi thông tin, hãy trả lời văn bản tự nhiên, không JSON.
|
||||
|
||||
|
||||
### Câu hỏi của người dùng:
|
||||
Đại học Hanover có bao nhiêu khoa? Có bao nhiêu nhân viên? Lưu thông tin vào file txt
|
||||
|
||||
### Hướng dẫn cho AI:
|
||||
- Nếu người dùng chỉ hỏi → trả lời tự nhiên, ngắn gọn, chính xác.
|
||||
- Nếu người dùng yêu cầu lưu/tạo file → hãy dùng create_txt.
|
||||
- Khi trả về JSON, không thêm giải thích, chỉ trả JSON hợp lệ duy nhất.
|
||||
- Nếu không đủ dữ liệu, hãy nói rõ ràng rằng bạn chưa có thông tin chính xác.
|
||||
- Không bịa thêm, không suy diễn.
|
||||
|
||||
### Trả lời:
|
||||
...
|
||||
2025-10-12 18:22:50,804 [INFO] rag_api - ✅ Đã trả lời: Đại học Hanover có bao nhiêu khoa? Có bao nhiêu nh...
|
||||
2025-10-12 18:23:12,573 [INFO] rag_api - 📥 Câu hỏi: Đại học Hanover có bao nhiêu khoa? Có bao nhiêu nhân viên? Lưu thông tin vào file txt
|
||||
2025-10-12 18:23:14,740 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK"
|
||||
2025-10-12 18:23:26,449 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini...
|
||||
2025-10-12 18:23:35,323 [INFO] src.chatbot.llm_client - 💬 Gemini output: Đại học Hanover có 9 khoa. Đội ngũ cán bộ của trường bao gồm 1.120 nhân viên, bao gồm 340 giáo sư, 1.560 nhân viên trong...
|
||||
2025-10-12 18:23:56,910 [INFO] src.chatbot.llm_client - 🤖 Function call JSON: {'action': 'create_txt', 'params': {'text': 'Đại học Hanover có 9 khoa. Đội ngũ cán bộ của trường bao gồm 1.120 nhân viên, bao gồm 340 giáo sư, 1.560 nhân viên trong các chức năng hành chính, và có thêm 900 người được tài trợ của bên thứ ba.'}}
|
||||
2025-10-12 18:27:48,814 [WARNING] function_executor - Không thể parse JSON từ LLM output: 'dict' object has no attribute 'find'
|
||||
2025-10-12 18:29:06,767 [INFO] numexpr.utils - NumExpr defaulting to 12 threads.
|
||||
2025-10-12 18:29:38,852 [INFO] numexpr.utils - NumExpr defaulting to 12 threads.
|
||||
2025-10-12 18:29:56,031 [INFO] httpx - HTTP Request: GET http://localhost:6333 "HTTP/1.1 200 OK"
|
||||
2025-10-12 18:29:56,039 [INFO] httpx - HTTP Request: GET http://localhost:6333/collections/text_chunks "HTTP/1.1 200 OK"
|
||||
2025-10-12 18:29:56,050 [INFO] sentence_transformers.SentenceTransformer - Load pretrained SentenceTransformer: Alibaba-NLP/gte-multilingual-base
|
||||
2025-10-12 18:30:02,939 [INFO] src.chatbot.llm_client - 🔮 LLMClient khởi tạo với model: models/gemini-2.0-flash-001
|
||||
2025-10-12 18:30:02,939 [INFO] function_router - 🔧 Đã đăng ký tool: create_txt
|
||||
2025-10-12 18:30:02,939 [INFO] rag_api - ✅ RAGPipeline đã khởi tạo thành công.
|
||||
2025-10-12 18:30:05,771 [INFO] rag_api - 📥 Câu hỏi: Đại học Hanover có bao nhiêu khoa? Có bao nhiêu nhân viên? Lưu thông tin vào file txt
|
||||
2025-10-12 18:30:10,939 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK"
|
||||
2025-10-12 18:30:27,710 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini...
|
||||
2025-10-12 18:30:32,763 [INFO] src.chatbot.llm_client - 💬 Gemini output: Đại học Hanover có 9 khoa. Đội ngũ cán bộ của trường bao gồm 1.120 nhân viên, bao gồm 340 giáo sư, 1.560 nhân viên trong...
|
||||
2025-10-12 18:30:37,214 [INFO] src.chatbot.llm_client - 🤖 Function call JSON: {'action': 'create_txt', 'params': {'text': 'Đại học Hanover có 9 khoa. Đội ngũ cán bộ của trường bao gồm 1.120 nhân viên, trong đó có 340 giáo sư, 1.560 nhân viên hành chính và 900 nhân viên được tài trợ.'}}
|
||||
2025-10-12 18:30:54,917 [INFO] function_router - ⚙️ Gọi tool 'create_txt' với params={'text': 'Đại học Hanover có 9 khoa. Đội ngũ cán bộ của trường bao gồm 1.120 nhân viên, trong đó có 340 giáo sư, 1.560 nhân viên hành chính và 900 nhân viên được tài trợ.'}
|
||||
2025-10-12 18:30:54,920 [INFO] function_router - ✅ Tool 'create_txt' thực thi thành công
|
||||
2025-10-12 18:31:01,729 [INFO] root - 📤 Gửi prompt tới LLM: ### Vai trò hệ thống:
|
||||
Bạn là trợ lý AI chuyên nghiệp, hiểu tiếng Việt, có khả năng trả lời tự nhiên, chính xác và ngắn gọn. Bạn có thể vừa trả lời, vừa thực hiện hành động theo yêu cầu người dùng. Nếu người dùng chỉ đặt câu hỏi, hãy trả lời tự nhiên. Nếu người dùng yêu cầu hành động, hãy trả về **đúng JSON hợp lệ**.
|
||||
|
||||
### Dữ liệu ngữ cảnh:
|
||||
Dưới đây là các thông tin có liên quan:
|
||||
|
||||
(Đoạn 1 - score=0.818)
|
||||
Đại học Hanover
|
||||
|
||||
Đại học Hanover, chính thức là Gottfried Wilhelm Leibniz Universität Hannover hoặc Luh, là một trường đại học nằm ở Hanover, Đức. Trường được thành lập vào năm 1831 và là tổ chức đào tạo đại học lớn thứ hai ở Niedersachsen. Đại học Leibniz Hannover là một thành viên của TU9, một hiệp hội của chín Viện Công nghệ hàng đầu tại Đức.
|
||||
Lịch sử.
|
||||
Trường đại học này được thành lập vào năm 1831 là một trường cao đẳng thương mại. Trường đã bắt đầu nghiên cứu toán học, kiến trúc, kỹ thuật, lịch sử tự nhiên, vật lý, hóa học, vẽ, công nghệ, nghiên cứu và kế toán. Năm 1879 trường đã được nâng cấp thành Trường Cao đẳng Công nghệ Hoàng gia, năm 1898 nó đã được trao quyền đào tạo tiến sĩ.
|
||||
Lĩnh vực của trường đại học này, từ đầu của nó, tập trung vào khoa học và công nghệ. Trong thế kỷ 20, các ngành nghệ thuật và nhân văn đã được bổ sung, và trường đã được sáp nhập thêm Trường Cao đẳng Sư phạm trước đó là một trường độc lập.
|
||||
Khoa.
|
||||
Trường có 9 khoa với hơn 150 cấp độ độ đầu tiên toàn thời gian và các khóa học trình độ bán thời gian, khiến cho trường này là trường đại học lớn thứ hai của giáo dục đại học tổ chức ở Lower Saxony. Đội ngũ cán bộ trường đại học này bao gồm 1.120 nhân viên, bao gồm 340 giáo sư, 1.560 nhân viên trong các chức năng hành chính, và có thêm 900 người được tài trợ của bên thứ ba.
|
||||
|
||||
### Công cụ có thể sử dụng:
|
||||
|
||||
Hiện bạn có thể sử dụng công cụ sau:
|
||||
1. create_txt(text: str) — Tạo file .txt chứa nội dung văn bản.
|
||||
|
||||
Nếu người dùng yêu cầu hành động (như tạo, lưu, ghi vào file),
|
||||
hãy trả về đúng JSON theo mẫu sau:
|
||||
{"action": "create_txt", "params": {"text": "<nội dung cần lưu>"}}
|
||||
|
||||
Nếu người dùng chỉ hỏi thông tin, hãy trả lời văn bản tự nhiên, không JSON.
|
||||
|
||||
|
||||
### Câu hỏi của người dùng:
|
||||
Đại học Hanover có bao nhiêu khoa? Có bao nhiêu nhân viên? Lưu thông tin vào file txt
|
||||
|
||||
### Hướng dẫn cho AI:
|
||||
- Nếu người dùng chỉ hỏi → trả lời tự nhiên, ngắn gọn, chính xác.
|
||||
- Nếu người dùng yêu cầu lưu/tạo file → hãy dùng create_txt.
|
||||
- Khi trả về JSON, không thêm giải thích, chỉ trả JSON hợp lệ duy nhất.
|
||||
- Nếu không đủ dữ liệu, hãy nói rõ ràng rằng bạn chưa có thông tin chính xác.
|
||||
- Không bịa thêm, không suy diễn.
|
||||
|
||||
### Trả lời:
|
||||
...
|
||||
2025-10-12 18:31:04,946 [INFO] rag_api - ✅ Đã trả lời: Đại học Hanover có bao nhiêu khoa? Có bao nhiêu nh...
|
||||
2025-10-12 18:36:06,726 [INFO] numexpr.utils - NumExpr defaulting to 12 threads.
|
||||
2025-10-12 18:36:09,883 [INFO] httpx - HTTP Request: GET http://localhost:6333 "HTTP/1.1 200 OK"
|
||||
2025-10-12 18:36:09,916 [INFO] httpx - HTTP Request: GET http://localhost:6333/collections/text_chunks "HTTP/1.1 200 OK"
|
||||
2025-10-12 18:36:09,923 [INFO] sentence_transformers.SentenceTransformer - Load pretrained SentenceTransformer: Alibaba-NLP/gte-multilingual-base
|
||||
2025-10-12 18:36:16,771 [INFO] src.chatbot.llm_client - 🔮 LLMClient khởi tạo với model: models/gemini-2.0-flash-001
|
||||
2025-10-12 18:36:16,771 [INFO] function_router - 🔧 Đã đăng ký tool: create_txt
|
||||
2025-10-12 18:36:16,771 [INFO] rag_api - ✅ RAGPipeline đã khởi tạo thành công.
|
||||
2025-10-12 18:36:25,890 [INFO] rag_api - 📥 Câu hỏi: Đại học Hanover có bao nhiêu khoa? Có bao nhiêu nhân viên? Lưu thông tin vào file txt
|
||||
2025-10-12 18:36:26,245 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK"
|
||||
2025-10-12 18:36:26,250 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini...
|
||||
2025-10-12 18:36:27,947 [INFO] src.chatbot.llm_client - 💬 Gemini output: Đại học Hanover có 9 khoa. Đội ngũ cán bộ của trường bao gồm 1.120 nhân viên, trong đó có 340 giáo sư, 1.560 nhân viên h...
|
||||
2025-10-12 18:36:27,948 [INFO] src.chatbot.llm_client - 🤖 Function call JSON: {'action': 'create_txt', 'params': {'text': 'Đại học Hanover có 9 khoa. Đội ngũ cán bộ của trường bao gồm 1.120 nhân viên, trong đó có 340 giáo sư, 1.560 nhân viên hành chính và 900 người được tài trợ từ bên thứ ba.'}}
|
||||
2025-10-12 18:36:27,949 [INFO] function_router - ⚙️ Gọi tool 'create_txt' với params={'text': 'Đại học Hanover có 9 khoa. Đội ngũ cán bộ của trường bao gồm 1.120 nhân viên, trong đó có 340 giáo sư, 1.560 nhân viên hành chính và 900 người được tài trợ từ bên thứ ba.'}
|
||||
2025-10-12 18:36:27,952 [INFO] function_router - ✅ Tool 'create_txt' thực thi thành công
|
||||
2025-10-12 18:36:27,952 [INFO] root - 📤 Gửi prompt tới LLM: ### Vai trò hệ thống:
|
||||
Bạn là trợ lý AI chuyên nghiệp, hiểu tiếng Việt, có khả năng trả lời tự nhiên, chính xác và ngắn gọn. Bạn có thể vừa trả lời, vừa thực hiện hành động theo yêu cầu người dùng. Nếu người dùng chỉ đặt câu hỏi, hãy trả lời tự nhiên. Nếu người dùng yêu cầu hành động, hãy trả về **đúng JSON hợp lệ**.
|
||||
|
||||
### Dữ liệu ngữ cảnh:
|
||||
Dưới đây là các thông tin có liên quan:
|
||||
|
||||
(Đoạn 1 - score=0.818)
|
||||
Đại học Hanover
|
||||
|
||||
Đại học Hanover, chính thức là Gottfried Wilhelm Leibniz Universität Hannover hoặc Luh, là một trường đại học nằm ở Hanover, Đức. Trường được thành lập vào năm 1831 và là tổ chức đào tạo đại học lớn thứ hai ở Niedersachsen. Đại học Leibniz Hannover là một thành viên của TU9, một hiệp hội của chín Viện Công nghệ hàng đầu tại Đức.
|
||||
Lịch sử.
|
||||
Trường đại học này được thành lập vào năm 1831 là một trường cao đẳng thương mại. Trường đã bắt đầu nghiên cứu toán học, kiến trúc, kỹ thuật, lịch sử tự nhiên, vật lý, hóa học, vẽ, công nghệ, nghiên cứu và kế toán. Năm 1879 trường đã được nâng cấp thành Trường Cao đẳng Công nghệ Hoàng gia, năm 1898 nó đã được trao quyền đào tạo tiến sĩ.
|
||||
Lĩnh vực của trường đại học này, từ đầu của nó, tập trung vào khoa học và công nghệ. Trong thế kỷ 20, các ngành nghệ thuật và nhân văn đã được bổ sung, và trường đã được sáp nhập thêm Trường Cao đẳng Sư phạm trước đó là một trường độc lập.
|
||||
Khoa.
|
||||
Trường có 9 khoa với hơn 150 cấp độ độ đầu tiên toàn thời gian và các khóa học trình độ bán thời gian, khiến cho trường này là trường đại học lớn thứ hai của giáo dục đại học tổ chức ở Lower Saxony. Đội ngũ cán bộ trường đại học này bao gồm 1.120 nhân viên, bao gồm 340 giáo sư, 1.560 nhân viên trong các chức năng hành chính, và có thêm 900 người được tài trợ của bên thứ ba.
|
||||
|
||||
### Công cụ có thể sử dụng:
|
||||
|
||||
Hiện bạn có thể sử dụng công cụ sau:
|
||||
1. create_txt(text: str) — Tạo file .txt chứa nội dung văn bản.
|
||||
|
||||
Nếu người dùng yêu cầu hành động (như tạo, lưu, ghi vào file),
|
||||
hãy trả về đúng JSON theo mẫu sau:
|
||||
{"action": "create_txt", "params": {"text": "<nội dung cần lưu>"}}
|
||||
|
||||
Nếu người dùng chỉ hỏi thông tin, hãy trả lời văn bản tự nhiên, không JSON.
|
||||
|
||||
|
||||
### Câu hỏi của người dùng:
|
||||
Đại học Hanover có bao nhiêu khoa? Có bao nhiêu nhân viên? Lưu thông tin vào file txt
|
||||
|
||||
### Hướng dẫn cho AI:
|
||||
- Nếu người dùng chỉ hỏi → trả lời tự nhiên, ngắn gọn, chính xác.
|
||||
- Nếu người dùng yêu cầu lưu/tạo file → hãy dùng create_txt.
|
||||
- Khi trả về JSON, không thêm giải thích, chỉ trả JSON hợp lệ duy nhất.
|
||||
- Nếu không đủ dữ liệu, hãy nói rõ ràng rằng bạn chưa có thông tin chính xác.
|
||||
- Không bịa thêm, không suy diễn.
|
||||
|
||||
### Trả lời:
|
||||
...
|
||||
2025-10-12 18:36:27,952 [INFO] rag_api - ✅ Đã trả lời: Đại học Hanover có bao nhiêu khoa? Có bao nhiêu nh...
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1 @@
|
|||
Đại học Hanover có 9 khoa. Đội ngũ cán bộ của trường bao gồm 1.120 nhân viên, trong đó có 340 giáo sư, 1.560 nhân viên hành chính và 900 người được tài trợ từ bên thứ ba.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Thời Đại Thiếu Niên Đoàn có 7 thành viên: Đinh Trình Hâm, Mã Gia Kỳ, Trương Chân Nguyên, Tống Á Hiên, Hạ Tuấn Lâm, Nghiêm Hạo Tường, Lưu Diệu Văn.
|
||||
Binary file not shown.
|
|
@ -86,11 +86,14 @@ async def chat_endpoint(req: ChatRequest):
|
|||
try:
|
||||
result = rag_pipeline.run(req.query, top_k=req.top_k)
|
||||
logging.info(f"📤 Gửi prompt tới LLM: {result['prompt']}...")
|
||||
|
||||
response = {
|
||||
"query": req.query,
|
||||
"answer": result["answer"],
|
||||
"context_count": len(result.get("context_used", [])),
|
||||
"context_used": result.get("context_used", []),
|
||||
"sources": result.get("sources", []), # ✅ mới
|
||||
"used_web": result.get("used_web", False), # ✅ mới
|
||||
}
|
||||
|
||||
logger.info(f"✅ Đã trả lời: {req.query[:50]}...")
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class BaseTool(ABC):
|
||||
"""
|
||||
Interface (chuẩn chung) cho mọi công cụ (tool).
|
||||
Mọi tool con đều phải kế thừa và implement hàm execute().
|
||||
"""
|
||||
|
||||
# Tên duy nhất của tool (LLM và router sẽ dùng để gọi)
|
||||
name: str = "base_tool"
|
||||
|
||||
# Mô tả ngắn, để LLM biết công cụ này làm gì (sẽ hiển thị trong prompt)
|
||||
description: str = "No description."
|
||||
|
||||
@abstractmethod
|
||||
def execute(self, **kwargs) -> str:
|
||||
"""
|
||||
Phương thức bắt buộc.
|
||||
- Nhận tham số (kwargs) từ hệ thống hoặc LLM
|
||||
- Trả về kết quả sau khi thực thi công cụ
|
||||
"""
|
||||
...
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
FunctionExecutor — giải mã output LLM & kích hoạt tool nếu cần.
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from .router import FunctionRouter
|
||||
from src.chatbot.function_tools.google_tool import GoogleSearchTool
|
||||
|
||||
logger = logging.getLogger("function_executor")
|
||||
|
||||
|
||||
def _strip_code_fences(s: str) -> str:
|
||||
if not isinstance(s, str):
|
||||
return s
|
||||
s = s.strip()
|
||||
if s.startswith("```"):
|
||||
s = s.strip("`")
|
||||
# nếu có ```json ... ``` thì bỏ dòng đầu/cuối
|
||||
if s.startswith("json"):
|
||||
s = s[4:]
|
||||
return s.strip()
|
||||
|
||||
|
||||
class FunctionExecutor:
|
||||
def __init__(self):
|
||||
self.router = FunctionRouter()
|
||||
self.google_tool = GoogleSearchTool()
|
||||
|
||||
def parse_action(self, text: Any) -> Optional[Dict[str, Any]]:
|
||||
"""Parse JSON action từ LLM output (hỗ trợ string/dict, có code-fence)."""
|
||||
if not text:
|
||||
return None
|
||||
|
||||
if isinstance(text, dict):
|
||||
return text if "action" in text else None
|
||||
|
||||
if isinstance(text, str):
|
||||
try:
|
||||
s = _strip_code_fences(text)
|
||||
start = s.find("{")
|
||||
end = s.rfind("}") + 1
|
||||
if start == -1 or end == 0:
|
||||
return None
|
||||
data = json.loads(s[start:end])
|
||||
return data if isinstance(data, dict) and "action" in data else None
|
||||
except Exception as e:
|
||||
logger.warning(f"Không thể parse JSON từ LLM output: {e}")
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
def execute_if_needed(self, llm_output: Any, original_query: str = "", allow_web_search: bool = True) -> Dict:
|
||||
"""
|
||||
Trả về dict chuẩn cho pipeline:
|
||||
{
|
||||
"text": "<văn bản gốc của LLM (nếu có)>",
|
||||
"action_result": "<kết quả tool action nếu có hoặc None>",
|
||||
"web": {"summary_text":..., "items":[...]} | None
|
||||
}
|
||||
"""
|
||||
result: Dict[str, Any] = {"text": "", "action_result": None, "web": None}
|
||||
|
||||
# Base text
|
||||
result["text"] = llm_output if isinstance(llm_output, str) else json.dumps(llm_output, ensure_ascii=False)
|
||||
|
||||
# 1️⃣ Nếu LLM yêu cầu action → parse JSON
|
||||
data = self.parse_action(llm_output)
|
||||
if data:
|
||||
action = data.get("action")
|
||||
params = data.get("params", {})
|
||||
try:
|
||||
logger.info(f"🔧 LLM yêu cầu thực thi tool: {action}({params})")
|
||||
exec_out = self.router.execute_tool(action, **params)
|
||||
|
||||
# 🧠 Nếu tool là search_google → gán vào result["web"]
|
||||
if action == "search_google" and isinstance(exec_out, dict):
|
||||
result["web"] = exec_out
|
||||
result["action_result"] = f"✅ Đã tìm kiếm Google với truy vấn: {params.get('query', '')}"
|
||||
else:
|
||||
result["action_result"] = exec_out
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
msg = f"⚠️ Tool '{action}' lỗi: {e}"
|
||||
logger.exception(msg)
|
||||
result["action_result"] = msg
|
||||
return result
|
||||
|
||||
# 2️⃣ Nếu không có action → fallback Google (nếu bật cờ và có query)
|
||||
# if allow_web_search and original_query:
|
||||
# logger.info("⚠️ Không có action → fallback Google Search")
|
||||
# web = self.google_tool.execute(original_query, max_results=3, fetch_pages=True)
|
||||
# result["web"] = web
|
||||
# return result
|
||||
|
||||
# 3️⃣ Nếu không có gì cả
|
||||
return result
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
GoogleSearchTool (Custom Search API + Smart Filter + Page fetch)
|
||||
----------------------------------------------------------------
|
||||
- Tìm bằng Google CSE
|
||||
- Lọc domain uy tín + khử trùng lặp theo domain
|
||||
- Tải nội dung trang (HTML) và rút trích đoạn <p>
|
||||
- Chuẩn hóa output: trả về cả summary_text và items (để two-pass và sources)
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from src.chatbot.function_tools.base_tool import BaseTool
|
||||
from src.core.config import GOOGLE_API_KEY, GOOGLE_CX, TRUSTED_DOMAINS
|
||||
|
||||
logger = logging.getLogger("google_search_tool")
|
||||
|
||||
|
||||
class GoogleSearchTool(BaseTool):
|
||||
name = "search_google"
|
||||
description = "Tìm thông tin mới nhất trên Internet bằng Google Custom Search API."
|
||||
|
||||
def __init__(self):
|
||||
if not GOOGLE_API_KEY or not GOOGLE_CX:
|
||||
raise ValueError("❌ Thiếu GOOGLE_API_KEY hoặc GOOGLE_CX trong config.")
|
||||
self.api_key = GOOGLE_API_KEY
|
||||
self.cx = GOOGLE_CX
|
||||
self.trusted_domains = set(TRUSTED_DOMAINS)
|
||||
|
||||
# ----------------- helpers -----------------
|
||||
def _domain(self, url: str) -> str:
|
||||
return urlparse(url).netloc.lower()
|
||||
|
||||
def _is_trusted(self, url: str) -> bool:
|
||||
dom = self._domain(url)
|
||||
return any(trust in dom for trust in self.trusted_domains)
|
||||
|
||||
def _summarize(self, text: str, limit: int = 280) -> str:
|
||||
if not text:
|
||||
return ""
|
||||
text = " ".join(text.split())
|
||||
if len(text) <= limit:
|
||||
return text
|
||||
cut = text.rfind(".", 0, limit)
|
||||
if cut < int(limit * 0.6):
|
||||
cut = limit
|
||||
return text[:cut].rstrip() + " …"
|
||||
|
||||
def _fetch_page_text(self, url: str, limit_chars: int = 1800) -> str:
|
||||
try:
|
||||
headers = {"User-Agent": "Mozilla/5.0 (RAGBot)"}
|
||||
r = requests.get(url, headers=headers, timeout=10)
|
||||
if r.status_code != 200 or not r.text:
|
||||
return ""
|
||||
soup = BeautifulSoup(r.text, "html.parser")
|
||||
paragraphs = [p.get_text(" ", strip=True) for p in soup.find_all("p")]
|
||||
text = " ".join(paragraphs)
|
||||
text = " ".join(text.split())
|
||||
if len(text) > limit_chars:
|
||||
text = text[:limit_chars] + " …"
|
||||
return text
|
||||
except Exception as e:
|
||||
logger.warning(f"Fetch page failed {url}: {e}")
|
||||
return ""
|
||||
|
||||
# ----------------- main -----------------
|
||||
def execute(self, query: str, max_results: int = 5, fetch_pages: bool = True) -> Dict:
|
||||
"""
|
||||
Return:
|
||||
{
|
||||
"summary_text": "<chuỗi gộp đã format cho hiển thị nhanh>",
|
||||
"items": [
|
||||
{"title":..., "snippet":..., "link":..., "domain":..., "page_text":...},
|
||||
...
|
||||
]
|
||||
}
|
||||
"""
|
||||
try:
|
||||
url = "https://www.googleapis.com/customsearch/v1"
|
||||
params = {
|
||||
"key": self.api_key,
|
||||
"cx": self.cx,
|
||||
"q": query,
|
||||
"num": max_results * 2, # xin nhiều hơn chút để lọc
|
||||
"hl": "vi",
|
||||
}
|
||||
resp = requests.get(url, params=params, timeout=10)
|
||||
if resp.status_code != 200:
|
||||
return {
|
||||
"summary_text": f"⚠️ Google API lỗi {resp.status_code}: {resp.text}",
|
||||
"items": []
|
||||
}
|
||||
|
||||
data = resp.json()
|
||||
raw_items = data.get("items", [])
|
||||
|
||||
# Lọc domain uy tín + khử trùng lặp theo domain
|
||||
picked: List[Dict] = []
|
||||
seen_domains = set()
|
||||
for it in raw_items:
|
||||
link = it.get("link") or ""
|
||||
if not link:
|
||||
continue
|
||||
dom = self._domain(link)
|
||||
if not self._is_trusted(link):
|
||||
continue
|
||||
if dom in seen_domains:
|
||||
continue
|
||||
seen_domains.add(dom)
|
||||
|
||||
item = {
|
||||
"title": (it.get("title") or "").strip(),
|
||||
"snippet": self._summarize(it.get("snippet") or ""),
|
||||
"link": link,
|
||||
"domain": dom,
|
||||
"page_text": ""
|
||||
}
|
||||
|
||||
if fetch_pages:
|
||||
item["page_text"] = self._fetch_page_text(link)
|
||||
|
||||
picked.append(item)
|
||||
if len(picked) >= max_results:
|
||||
break
|
||||
|
||||
if not picked:
|
||||
return {
|
||||
"summary_text": f"❌ Không tìm thấy nguồn uy tín cho truy vấn: '{query}'",
|
||||
"items": []
|
||||
}
|
||||
|
||||
# Gộp summary_text để hiển thị/nhét prompt dễ
|
||||
blocks = []
|
||||
for i, it in enumerate(picked, 1):
|
||||
page_part = f"\n📄 {self._summarize(it['page_text'], 700)}" if it["page_text"] else ""
|
||||
blocks.append(
|
||||
f"{i}. {it['title']}\n{it['snippet']}{page_part}\n🔗 {it['link']}"
|
||||
)
|
||||
summary_text = f"🔍 Kết quả Google (đã lọc nguồn) cho: '{query}'\n\n" + "\n\n".join(blocks)
|
||||
|
||||
return {"summary_text": summary_text, "items": picked}
|
||||
|
||||
except Exception as e:
|
||||
return {"summary_text": f"⚠️ Lỗi khi tìm kiếm Google: {e}", "items": []}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
FunctionRouter — Bộ điều phối công cụ (function tools)
|
||||
------------------------------------------------------
|
||||
- Quản lý và điều hướng các công cụ mà LLM có thể gọi.
|
||||
- Khi LLM trả JSON {"action": "<tên_tool>", "params": {...}},
|
||||
router sẽ xác định đúng tool và gọi thực thi tương ứng.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict
|
||||
from .base_tool import BaseTool
|
||||
from .txt_tool import TxtTool
|
||||
from .google_tool import GoogleSearchTool # ✅ Thêm import Google search
|
||||
|
||||
logger = logging.getLogger("function_router")
|
||||
|
||||
|
||||
class FunctionRouter:
|
||||
"""
|
||||
Router trung tâm, chịu trách nhiệm:
|
||||
- Đăng ký tool vào registry (theo tên)
|
||||
- Điều phối thực thi tool
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# Danh mục các tool khả dụng (tên -> instance)
|
||||
self.tools: Dict[str, BaseTool] = {}
|
||||
|
||||
# 🔧 Đăng ký các tool mặc định
|
||||
self.register_tool(TxtTool())
|
||||
self.register_tool(GoogleSearchTool())
|
||||
|
||||
logger.info(f"✅ FunctionRouter khởi tạo, có {len(self.tools)} công cụ sẵn sàng.")
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Đăng ký tool mới
|
||||
# ------------------------------------------------------------
|
||||
def register_tool(self, tool: BaseTool):
|
||||
"""Đăng ký 1 tool mới vào router"""
|
||||
if not isinstance(tool, BaseTool):
|
||||
raise TypeError("Tool phải kế thừa BaseTool.")
|
||||
if not hasattr(tool, "name"):
|
||||
raise ValueError("Tool phải có thuộc tính name.")
|
||||
if not callable(getattr(tool, "execute", None)):
|
||||
raise ValueError("Tool phải có hàm execute().")
|
||||
|
||||
# Ghi đè nếu trùng tên
|
||||
if tool.name in self.tools:
|
||||
logger.warning(f"⚠️ Tool '{tool.name}' đã tồn tại, ghi đè instance mới.")
|
||||
self.tools[tool.name] = tool
|
||||
logger.info(f"🔧 Đã đăng ký tool: {tool.name} — {tool.description}")
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Lấy danh sách tool (cho debug / hiển thị)
|
||||
# ------------------------------------------------------------
|
||||
def list_tools(self) -> Dict[str, str]:
|
||||
"""Trả về danh sách tên và mô tả của tất cả công cụ"""
|
||||
return {name: t.description for name, t in self.tools.items()}
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Thực thi tool dựa trên action
|
||||
# ------------------------------------------------------------
|
||||
def execute_tool(self, action: str, **params) -> str:
|
||||
"""
|
||||
Gọi tool theo tên hành động (action).
|
||||
Ví dụ:
|
||||
router.execute_tool("create_txt", text="Xin chào!")
|
||||
router.execute_tool("search_google", query="tin tức hôm nay")
|
||||
|
||||
Trả về: Chuỗi kết quả hoặc message lỗi.
|
||||
"""
|
||||
tool = self.tools.get(action)
|
||||
if not tool:
|
||||
msg = f"❌ Không tìm thấy tool '{action}' trong router."
|
||||
logger.error(msg)
|
||||
return msg
|
||||
|
||||
try:
|
||||
logger.info(f"⚙️ Thực thi tool '{action}' với params={params}")
|
||||
result = tool.execute(**params)
|
||||
logger.info(f"✅ Tool '{action}' thực thi thành công.")
|
||||
return result
|
||||
except Exception as e:
|
||||
msg = f"❌ Lỗi khi thực thi tool '{action}': {e}"
|
||||
logger.exception(msg)
|
||||
return msg
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
TxtTool — công cụ tạo file văn bản .txt
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
from .base_tool import BaseTool
|
||||
|
||||
|
||||
def _slugify(s: str) -> str:
|
||||
"""Chuẩn hóa tên file an toàn (không dấu, không ký tự lạ)."""
|
||||
s = (s or "").lower().strip()
|
||||
s = re.sub(r"[^a-z0-9._-]+", "-", s)
|
||||
s = re.sub(r"-{2,}", "-", s).strip("-")
|
||||
return s or "output"
|
||||
|
||||
|
||||
class TxtTool(BaseTool):
|
||||
"""Tool tạo file TXT từ nội dung văn bản."""
|
||||
|
||||
# Định danh của tool (LLM sẽ dùng để gọi)
|
||||
name = "create_txt"
|
||||
|
||||
# Mô tả — dùng cho LLM biết công cụ này làm gì
|
||||
description = "Tạo file TXT. Tham số: {text: str, filename?: str}"
|
||||
|
||||
def execute(self, text: str, filename: str | None = None) -> str:
|
||||
"""
|
||||
Tạo file txt chứa nội dung được truyền vào.
|
||||
Trả về thông báo thành công (và đường dẫn file).
|
||||
"""
|
||||
# 1️⃣ Đảm bảo thư mục outputs tồn tại
|
||||
os.makedirs("outputs", exist_ok=True)
|
||||
|
||||
# 2️⃣ Nếu không chỉ định tên, tạo theo timestamp
|
||||
if not filename:
|
||||
filename = f"file_{datetime.now():%Y%m%d_%H%M%S}.txt"
|
||||
|
||||
# 3️⃣ Chuẩn hóa tên file (loại bỏ ký tự lạ)
|
||||
filename = _slugify(filename)
|
||||
if not filename.endswith(".txt"):
|
||||
filename += ".txt"
|
||||
|
||||
path = os.path.join("outputs", filename)
|
||||
|
||||
# 4️⃣ Ghi nội dung vào file
|
||||
try:
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write((text or "")[:20000]) # giới hạn 20k ký tự cho an toàn
|
||||
except Exception as e:
|
||||
return f"❌ Lỗi khi ghi file: {e}"
|
||||
|
||||
# 5️⃣ Trả kết quả
|
||||
return f"✅ File TXT đã được tạo: {path}"
|
||||
|
|
@ -1,24 +1,78 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
LLMClient
|
||||
---------
|
||||
Tầng giao tiếp với mô hình ngôn ngữ Gemini (Google Generative AI).
|
||||
Hỗ trợ cả phản hồi văn bản tự nhiên và function calling (JSON).
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
import google.generativeai as genai
|
||||
import logging
|
||||
from typing import Any, Optional
|
||||
|
||||
from src.core.config import GEMINI_API_KEY, GEMINI_MODEL
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LLMClient:
|
||||
def __init__(self, api_key: str = GEMINI_API_KEY, model: str = GEMINI_MODEL):
|
||||
if not api_key:
|
||||
raise ValueError("Thiếu GEMINI_API_KEY trong config hoặc .env")
|
||||
raise ValueError("❌ Thiếu GEMINI_API_KEY trong config hoặc .env")
|
||||
|
||||
genai.configure(api_key=api_key)
|
||||
self.model = genai.GenerativeModel(model)
|
||||
logger.info(f"🔮 LLMClient khởi tạo với model: {model}")
|
||||
|
||||
def generate(self, prompt: str) -> str:
|
||||
# ----------------------------------------------------------
|
||||
def _try_extract_json(self, text: str) -> Optional[dict]:
|
||||
"""
|
||||
Gửi prompt vào Gemini và trả về nội dung text.
|
||||
Cố gắng trích JSON từ đầu ra LLM.
|
||||
- Loại bỏ text thừa, chỉ giữ đoạn {...} đầu tiên.
|
||||
- Nếu JSON chứa 'action', coi là function call hợp lệ.
|
||||
"""
|
||||
try:
|
||||
response = self.model.generate_content(prompt)
|
||||
return response.text.strip() if response.text else ""
|
||||
match = re.search(r"\{.*\}", text, re.DOTALL)
|
||||
if not match:
|
||||
return None
|
||||
|
||||
json_str = match.group(0).strip()
|
||||
parsed = json.loads(json_str)
|
||||
|
||||
if isinstance(parsed, dict) and "action" in parsed:
|
||||
return parsed
|
||||
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"[GeminiLLMClient] Lỗi khi gọi Gemini API: {e}")
|
||||
return ""
|
||||
logger.warning(f"⚠️ Không thể parse JSON: {e}")
|
||||
return None
|
||||
|
||||
# ----------------------------------------------------------
|
||||
def generate(self, prompt: str) -> Any:
|
||||
"""
|
||||
Gửi prompt vào Gemini và trả về:
|
||||
- dict (nếu là function call)
|
||||
- str (nếu là câu trả lời tự nhiên)
|
||||
"""
|
||||
try:
|
||||
logger.info("🧠 Gửi prompt tới Gemini...")
|
||||
response = self.model.generate_content(prompt)
|
||||
|
||||
text = response.text.strip() if response.text else ""
|
||||
logger.info(f"💬 Gemini output: {text[:120]}...")
|
||||
|
||||
# ✅ Cố gắng parse JSON (function call)
|
||||
parsed = self._try_extract_json(text)
|
||||
if parsed:
|
||||
logger.info(f"🤖 Function call JSON: {parsed}")
|
||||
return parsed
|
||||
|
||||
# 🔤 Nếu không phải JSON → trả lời tự nhiên
|
||||
return text
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"❌ Lỗi khi gọi Gemini API: {e}")
|
||||
return f"Lỗi LLM: {str(e)}"
|
||||
|
|
|
|||
|
|
@ -18,43 +18,33 @@ class PromptBuilder:
|
|||
context_label: str = "Dưới đây là các thông tin có liên quan:"
|
||||
):
|
||||
self.system_prompt = system_prompt or (
|
||||
"Bạn là trợ lý AI chuyên nghiệp, hiểu tiếng Việt, "
|
||||
"có khả năng trả lời tự nhiên, chính xác và ngắn gọn. "
|
||||
"Chỉ sử dụng thông tin trong phần ngữ cảnh để trả lời. "
|
||||
"Nếu không đủ dữ liệu, hãy nói 'Tôi chưa có thông tin để trả lời chính xác' "
|
||||
"và không phỏng đoán hoặc suy luận thêm."
|
||||
"Bạn là trợ lý AI thông minh, hiểu tiếng Việt, có khả năng trả lời tự nhiên, "
|
||||
"chính xác và ngắn gọn. Bạn được phép sử dụng các công cụ có sẵn để tìm kiếm "
|
||||
"hoặc thực hiện hành động nếu cần."
|
||||
)
|
||||
self.max_context_len = max_context_len
|
||||
self.context_label = context_label
|
||||
|
||||
# -----------------------------------------------------
|
||||
def _smart_truncate(self, text: str, limit: int) -> str:
|
||||
"""Cắt ngắn văn bản theo giới hạn ký tự, ưu tiên dừng ở dấu câu."""
|
||||
text = text.strip()
|
||||
if len(text) <= limit:
|
||||
return text
|
||||
|
||||
cut = text.rfind(".", 0, limit)
|
||||
if cut < int(limit * 0.6): # không tìm thấy dấu chấm hợp lý
|
||||
if cut < int(limit * 0.6):
|
||||
cut = limit
|
||||
|
||||
return text[:cut].rstrip() + " …"
|
||||
|
||||
# -----------------------------------------------------
|
||||
# -----------------------------------------------------
|
||||
def build_context_block(self, retrieved_docs: List[Dict]) -> str:
|
||||
"""
|
||||
Ghép các đoạn văn bản được truy xuất từ Qdrant thành block context.
|
||||
- Luôn đảm bảo có ít nhất một đoạn context.
|
||||
- Cắt ngắn tự động để không vượt quá giới hạn max_context_len.
|
||||
"""
|
||||
"""Ghép các đoạn văn bản được truy xuất từ Qdrant thành block context."""
|
||||
if not retrieved_docs:
|
||||
return "(Không có dữ liệu ngữ cảnh)"
|
||||
|
||||
sorted_docs = sorted(retrieved_docs, key=lambda x: x.get("score", 0), reverse=True)
|
||||
|
||||
context_blocks = []
|
||||
total_len = 0
|
||||
budget = self.max_context_len - len(self.context_label) - 50 # trừ phần tiêu đề
|
||||
context_blocks, total_len = [], 0
|
||||
budget = self.max_context_len - len(self.context_label) - 50
|
||||
|
||||
for i, doc in enumerate(sorted_docs, start=1):
|
||||
text = (doc.get("text") or "").strip()
|
||||
|
|
@ -76,7 +66,6 @@ class PromptBuilder:
|
|||
if total_len >= budget:
|
||||
break
|
||||
|
||||
# Nếu vì lý do nào đó chưa có đoạn nào, lấy đoạn đầu tiên
|
||||
if not context_blocks:
|
||||
first = sorted_docs[0]
|
||||
header = f"(Đoạn 1 - score={first.get('score', 0):.3f})\n"
|
||||
|
|
@ -85,30 +74,59 @@ class PromptBuilder:
|
|||
|
||||
return f"{self.context_label}\n\n" + "\n\n".join(context_blocks)
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Xây prompt hoàn chỉnh
|
||||
# -----------------------------------------------------
|
||||
def build_prompt(self, user_query: str, retrieved_docs: List[Dict]) -> str:
|
||||
"""
|
||||
Tạo prompt hoàn chỉnh cho LLM.
|
||||
"""
|
||||
"""Tạo prompt hoàn chỉnh cho LLM, ép trả JSON nếu thiếu dữ liệu."""
|
||||
context_block = self.build_context_block(retrieved_docs)
|
||||
|
||||
prompt = f"""### Vai trò hệ thống:
|
||||
tools_description = """
|
||||
Bạn có thể sử dụng các công cụ sau:
|
||||
|
||||
1) search_google(query: str, max_results: int = 5)
|
||||
→ Dùng khi ngữ cảnh không đủ hoặc không có thông tin để trả lời chính xác.
|
||||
|
||||
2) create_txt(text: str)
|
||||
→ Dùng khi người dùng yêu cầu lưu hoặc tạo file văn bản.
|
||||
|
||||
Khi cần dùng công cụ, bạn PHẢI trả về **JSON hợp lệ duy nhất** theo mẫu:
|
||||
{"action": "<tên_công_cụ>", "params": {...}}
|
||||
|
||||
Ví dụ:
|
||||
{"action": "search_google", "params": {"query": "Stray Kids có bao nhiêu thành viên?"}}
|
||||
{"action": "create_txt", "params": {"text": "Nội dung cần lưu"}}
|
||||
|
||||
Không bao giờ thêm chữ, ký tự hay lời giải thích nào ngoài JSON.
|
||||
"""
|
||||
|
||||
rules = """
|
||||
Quy tắc ra quyết định:
|
||||
1️⃣ Nếu dữ liệu trong ngữ cảnh (context) chứa đủ chi tiết để trả lời chính xác → Trả lời văn bản tự nhiên, KHÔNG JSON.
|
||||
2️⃣ Nếu ngữ cảnh thiếu, mơ hồ, hoặc không có dữ liệu liên quan → TRẢ VỀ JSON:
|
||||
{"action": "search_google", "params": {"query": "<câu hỏi người dùng>"}}
|
||||
3️⃣ Nếu người dùng yêu cầu lưu/tạo file → TRẢ VỀ JSON:
|
||||
{"action": "create_txt", "params": {"text": "<nội dung cần lưu>"}}
|
||||
4️⃣ Nếu bạn không chắc chắn, cũng hãy gọi search_google thay vì suy đoán.
|
||||
"""
|
||||
|
||||
prompt = f"""### Vai trò hệ thống
|
||||
{self.system_prompt}
|
||||
|
||||
### Dữ liệu ngữ cảnh:
|
||||
### Ngữ cảnh (retrieved from database)
|
||||
{context_block}
|
||||
|
||||
### Câu hỏi của người dùng:
|
||||
### Công cụ có thể sử dụng
|
||||
{tools_description}
|
||||
|
||||
### Câu hỏi người dùng
|
||||
{user_query}
|
||||
|
||||
### Hướng dẫn cho AI:
|
||||
- Trả lời ngắn gọn, rõ ràng, đúng trọng tâm.
|
||||
- Dựa hoàn toàn vào thông tin trong ngữ cảnh.
|
||||
- Nếu thông tin không có trong ngữ cảnh, hãy nói rõ ràng rằng bạn chưa có dữ liệu để trả lời chính xác.
|
||||
- Không bịa thêm, không suy diễn.
|
||||
### Hướng dẫn cho AI
|
||||
{rules}
|
||||
|
||||
### Trả lời:
|
||||
### Định dạng đầu ra
|
||||
- Nếu đủ thông tin → trả lời tự nhiên, ngắn gọn, chính xác.
|
||||
- Nếu thiếu thông tin → trả về JSON như hướng dẫn trên, KHÔNG văn bản khác.
|
||||
|
||||
### Trả lời
|
||||
"""
|
||||
return prompt
|
||||
|
|
|
|||
|
|
@ -1,39 +1,90 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
RAGPipeline
|
||||
-----------
|
||||
Kết nối các module:
|
||||
- Retriever (Qdrant)
|
||||
- PromptBuilder
|
||||
- LLMClient
|
||||
Đầu vào: user_query (string)
|
||||
Đầu ra: câu trả lời (string)
|
||||
RAGPipeline — RAG + Function Calling + Google fallback + Two-pass refine
|
||||
"""
|
||||
from typing import Dict, Any, List
|
||||
|
||||
from typing import List, Dict, Any
|
||||
from src.chatbot.llm_client import LLMClient
|
||||
from src.chatbot.retriever import Retriever
|
||||
from src.chatbot.prompt_builder import PromptBuilder
|
||||
from src.chatbot.function_tools.function_executor import FunctionExecutor
|
||||
from src.core.config import ALLOW_WEB_SEARCH, SECOND_PASS
|
||||
|
||||
|
||||
class RAGPipeline:
|
||||
def __init__(self):
|
||||
self.retriever = Retriever()
|
||||
self.prompt_builder = PromptBuilder()
|
||||
self.llm = LLMClient()
|
||||
self.llm = LLMClient()
|
||||
self.function_executor = FunctionExecutor()
|
||||
|
||||
def run(self, user_query: str, top_k: int = 5) -> Dict[str, Any]:
|
||||
"""Trả về cả câu trả lời và context dùng"""
|
||||
def _build_refine_prompt(self, user_query: str, web_summary: str, items: List[Dict]) -> str:
|
||||
# Tạo block nguồn gọn để LLM trích dẫn
|
||||
links = [it.get("link", "") for it in items if it.get("link")]
|
||||
sources_text = "\n".join([f"- {url}" for url in links])
|
||||
|
||||
return f"""### Dữ liệu web (đã thu thập):
|
||||
{web_summary}
|
||||
|
||||
### Nguồn:
|
||||
{sources_text}
|
||||
|
||||
### Câu hỏi gốc:
|
||||
{user_query}
|
||||
|
||||
### Yêu cầu:
|
||||
- Tóm tắt chính xác và súc tích (3–6 câu), ngắn gọn, dễ hiểu.
|
||||
- Không bịa đặt. Nếu thông tin không chắc chắn, nêu rõ mức độ chắc chắn.
|
||||
- Trích dẫn 1–3 nguồn ở cuối, dạng: "Nguồn: <link1>, <link2>"
|
||||
"""
|
||||
|
||||
def run(self, user_query: str, top_k: int = 5, allow_web_search: bool = ALLOW_WEB_SEARCH, second_pass: bool = SECOND_PASS) -> Dict[str, Any]:
|
||||
# 1) RAG retrieve
|
||||
docs = self.retriever.search(user_query, top_k=top_k)
|
||||
|
||||
# 2) Build prompt từ context
|
||||
prompt = self.prompt_builder.build_prompt(user_query, docs)
|
||||
answer = self.llm.generate(prompt)
|
||||
|
||||
# 3) Gọi LLM (pass 1)
|
||||
llm_answer = self.llm.generate(prompt)
|
||||
|
||||
# 4) Thực thi tool nếu có / fallback Google
|
||||
exec_result = self.function_executor.execute_if_needed(
|
||||
llm_output=llm_answer,
|
||||
original_query=user_query,
|
||||
allow_web_search=allow_web_search
|
||||
)
|
||||
|
||||
# Chuẩn bị output mặc định
|
||||
final_answer = exec_result.get("text") or ""
|
||||
sources = []
|
||||
|
||||
# Nếu có web result → two-pass refine (nếu bật)
|
||||
web = exec_result.get("web")
|
||||
if web and isinstance(web, dict) and web.get("items"):
|
||||
sources = [it.get("link") for it in web["items"] if it.get("link")]
|
||||
if second_pass:
|
||||
refine_prompt = self._build_refine_prompt(user_query, web.get("summary_text", ""), web["items"])
|
||||
final_answer = self.llm.generate(refine_prompt)
|
||||
|
||||
# Nếu có action_result (VD: create_txt), nối thêm thông báo ngắn
|
||||
action_result = exec_result.get("action_result")
|
||||
if action_result:
|
||||
final_answer = f"{final_answer}\n\n🛠️ Tool: {action_result}"
|
||||
|
||||
# 5) Trả đầy đủ cho API
|
||||
return {
|
||||
"answer": answer,
|
||||
"answer": final_answer.strip(),
|
||||
"context_used": docs,
|
||||
"prompt": prompt,
|
||||
"sources": sources, # ✅ link nguồn (nếu có)
|
||||
"used_web": bool(web), # ✅ có dùng web hay không
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pipeline = RAGPipeline()
|
||||
query = "Bạn biết Mahola là ai không?"
|
||||
response = pipeline.run(query)
|
||||
print("Câu hỏi:", query)
|
||||
print("Trả lời:", response)
|
||||
q = "Acid propionic là gì? Hôm nay có cập nhật gì mới không?"
|
||||
out = pipeline.run(q)
|
||||
print("Answer:\n", out["answer"])
|
||||
print("Sources:", out["sources"])
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -48,12 +48,22 @@ load_dotenv()
|
|||
DATA_RAW = Path(os.getenv("DATA_RAW", "./data/data_raw10k")).resolve()
|
||||
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "")
|
||||
GEMINI_MODEL = os.getenv("GEMINI_MODEL", "models/gemini-2.0-flash-001")
|
||||
SERPER_API_KEY = os.getenv("SERPER_API_KEY", "14ea1e5de7b68084d3e41a4efe204108a8fa76c6fab062b00fa1912a97d3c28b")
|
||||
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY", "AIzaSyCOtXgt7M6qKlj5srF8hF_7iyvZbBrVnhA")
|
||||
GOOGLE_CX = os.getenv("GOOGLE_CX", "9379e94eed0f145b4")
|
||||
|
||||
# ==== Embedding model (SentenceTransformers) ====
|
||||
EMBED_MODEL = os.getenv("EMBED_MODEL", "Alibaba-NLP/gte-multilingual-base")
|
||||
EMBED_DIM = int(os.getenv("EMBED_DIM", "768"))
|
||||
EMBED_DEVICE = os.getenv("EMBED_DEVICE", "cuda") # "cuda" | "cpu" | "auto"
|
||||
EMBED_BATCH_SIZE = int(os.getenv("EMBED_BATCH_SIZE", "64"))
|
||||
_TRUSTED = os.getenv(
|
||||
"TRUSTED_DOMAINS",
|
||||
"wikipedia.org,chinhphu.vn,vnexpress.net,bbc.com,nhandan.vn,tuoitre.vn,thanhnien.vn,zingnews.vn,vov.vn,vietnamnet.vn"
|
||||
)
|
||||
TRUSTED_DOMAINS = [d.strip().lower() for d in _TRUSTED.split(",") if d.strip()]
|
||||
ALLOW_WEB_SEARCH = os.getenv("ALLOW_WEB_SEARCH", "true").lower() == "true"
|
||||
SECOND_PASS = os.getenv("SECOND_PASS", "true").lower() == "true"
|
||||
|
||||
# ==== Semantic chunking ====
|
||||
# Ngưỡng cosine similarity để tách chủ đề (thấp hơn ngưỡng => tách chunk)
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -0,0 +1,4 @@
|
|||
from src.chatbot.function_tools.google_tool import GoogleSearchTool
|
||||
|
||||
tool = GoogleSearchTool()
|
||||
print(tool.execute("Messi là ai?", max_results=2))
|
||||
Loading…
Reference in New Issue