From a4158929ccd496dff2d538e9f3f83a645273f079 Mon Sep 17 00:00:00 2001 From: letai2001 Date: Mon, 13 Oct 2025 01:32:17 +0700 Subject: [PATCH] up --- .env | 8 +- __pycache__/main.cpython-313.pyc | Bin 187 -> 187 bytes image-1.png | Bin 0 -> 114507 bytes logs/api_2025-10-12.log | 257 ++++ logs/api_2025-10-13.log | 1053 +++++++++++++++++ outputs/file_20251012_183627.txt | 1 + outputs/file_20251013_011905.txt | 1 + src/api/__pycache__/chat_api.cpython-313.pyc | Bin 4742 -> 4870 bytes src/api/chat_api.py | 3 + .../__pycache__/llm_client.cpython-313.pyc | Bin 1709 -> 3866 bytes .../prompt_builder.cpython-313.pyc | Bin 5212 -> 6399 bytes .../__pycache__/rag_pipeline.cpython-313.pyc | Bin 2077 -> 4561 bytes .../__pycache__/retriever.cpython-313.pyc | Bin 3653 -> 3653 bytes src/chatbot/function_tools/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 171 bytes .../__pycache__/base_tool.cpython-313.pyc | Bin 0 -> 1075 bytes .../function_executor.cpython-313.pyc | Bin 0 -> 4595 bytes .../__pycache__/google_tool.cpython-313.pyc | Bin 0 -> 7490 bytes .../__pycache__/router.cpython-313.pyc | Bin 0 -> 4629 bytes .../__pycache__/txt_tool.cpython-313.pyc | Bin 0 -> 2632 bytes src/chatbot/function_tools/base_tool.py | 23 + .../function_tools/function_executor.py | 101 ++ src/chatbot/function_tools/google_tool.py | 149 +++ src/chatbot/function_tools/router.py | 87 ++ src/chatbot/function_tools/txt_tool.py | 55 + src/chatbot/llm_client.py | 68 +- src/chatbot/prompt_builder.py | 84 +- src/chatbot/rag_pipeline.py | 87 +- src/core/__pycache__/config.cpython-313.pyc | Bin 3224 -> 4311 bytes src/core/config.py | 10 + .../__pycache__/test_searchgg.cpython-313.pyc | Bin 0 -> 384 bytes utils/test_searchgg.py | 4 + 32 files changed, 1932 insertions(+), 59 deletions(-) create mode 100644 image-1.png create mode 100644 logs/api_2025-10-12.log create mode 100644 logs/api_2025-10-13.log create mode 100644 outputs/file_20251012_183627.txt create mode 100644 outputs/file_20251013_011905.txt create mode 100644 src/chatbot/function_tools/__init__.py create mode 100644 src/chatbot/function_tools/__pycache__/__init__.cpython-313.pyc create mode 100644 src/chatbot/function_tools/__pycache__/base_tool.cpython-313.pyc create mode 100644 src/chatbot/function_tools/__pycache__/function_executor.cpython-313.pyc create mode 100644 src/chatbot/function_tools/__pycache__/google_tool.cpython-313.pyc create mode 100644 src/chatbot/function_tools/__pycache__/router.cpython-313.pyc create mode 100644 src/chatbot/function_tools/__pycache__/txt_tool.cpython-313.pyc create mode 100644 src/chatbot/function_tools/base_tool.py create mode 100644 src/chatbot/function_tools/function_executor.py create mode 100644 src/chatbot/function_tools/google_tool.py create mode 100644 src/chatbot/function_tools/router.py create mode 100644 src/chatbot/function_tools/txt_tool.py create mode 100644 utils/__pycache__/test_searchgg.cpython-313.pyc create mode 100644 utils/test_searchgg.py diff --git a/.env b/.env index 277e49e..438f2b3 100644 --- a/.env +++ b/.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" \ No newline at end of file +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 diff --git a/__pycache__/main.cpython-313.pyc b/__pycache__/main.cpython-313.pyc index c461d4da989e337ec4fa2d266ba7563b77cff8d9..1f02bce6277e486c86b7461775d10f9863af5fc9 100644 GIT binary patch delta 19 ZcmdnZxSNstGcPX}0}%N4zMjav0RS}P1+D-9 delta 19 ZcmdnZxSNstGcPX}0}$l&J)6kA0RT0E1;+pY diff --git a/image-1.png b/image-1.png new file mode 100644 index 0000000000000000000000000000000000000000..ed3ced8152ac053783fe34ca7e84ac0862bf953a GIT binary patch literal 114507 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%0A#x*#?bo{RIkw7YG#V@8w=gf=UmIJr+D`zCh%hk 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

!%5 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ℑYj5Z)D!VG{!8cOXlMFt_sNB~v*8=mU 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"}} + +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ế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ế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ế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... diff --git a/logs/api_2025-10-13.log b/logs/api_2025-10-13.log new file mode 100644 index 0000000..17a6e5f --- /dev/null +++ b/logs/api_2025-10-13.log @@ -0,0 +1,1053 @@ +2025-10-13 00:25:41,494 [INFO] numexpr.utils - NumExpr defaulting to 12 threads. +2025-10-13 00:26:13,383 [ERROR] rag_api - ❌ Lỗi khi khởi tạo RAGPipeline +Traceback (most recent call last): + File "C:\Users\SLG PC\Documents\rag_app_2\src\api\chat_api.py", line 58, in startup_event + from src.chatbot.rag_pipeline import RAGPipeline + File "C:\Users\SLG PC\Documents\rag_app_2\src\chatbot\rag_pipeline.py", line 11, in + from src.core.config import ALLOW_WEB_SEARCH, SECOND_PASS +ImportError: cannot import name 'ALLOW_WEB_SEARCH' from 'src.core.config' (C:\Users\SLG PC\Documents\rag_app_2\src\core\config.py) +2025-10-13 00:27:27,530 [INFO] numexpr.utils - NumExpr defaulting to 12 threads. +2025-10-13 00:27:44,775 [INFO] httpx - HTTP Request: GET http://localhost:6333 "HTTP/1.1 200 OK" +2025-10-13 00:27:44,786 [INFO] httpx - HTTP Request: GET http://localhost:6333/collections/text_chunks "HTTP/1.1 200 OK" +2025-10-13 00:27:44,948 [INFO] sentence_transformers.SentenceTransformer - Load pretrained SentenceTransformer: Alibaba-NLP/gte-multilingual-base +2025-10-13 00:27:51,759 [INFO] src.chatbot.llm_client - 🔮 LLMClient khởi tạo với model: models/gemini-2.0-flash-001 +2025-10-13 00:27:51,760 [INFO] function_router - 🔧 Đã đăng ký tool: create_txt +2025-10-13 00:27:51,760 [INFO] rag_api - ✅ RAGPipeline đã khởi tạo thành công. +2025-10-13 00:28:23,823 [INFO] rag_api - 📥 Câu hỏi: nhóm nhạc stray kids có bao nhiêu thành viên? +2025-10-13 00:28:30,180 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK" +2025-10-13 00:29:28,993 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 00:29:51,355 [INFO] src.chatbot.llm_client - 💬 Gemini output: Tôi không có thông tin về số lượng thành viên của nhóm nhạc Stray Kids. Bạn có muốn tôi tìm kiếm trên Google không?... +2025-10-13 00:30:50,288 [INFO] function_executor - ⚠️ Không có action → fallback Google Search +2025-10-13 00:31:23,120 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 00:31:27,574 [INFO] src.chatbot.llm_client - 💬 Gemini output: Stray Kids là một nhóm nhạc nam Hàn Quốc được thành lập bởi JYP Entertainment thông qua chương trình truyền hình thực tế... +2025-10-13 00:31:37,895 [INFO] root - 📤 Gửi prompt tới LLM: ### Vai trò hệ thống: +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. Nếu người dùng chỉ hỏi, hãy trả lời tự nhiên. Nếu người dùng yêu cầu hành động hoặc thông tin ngoài ngữ cảnh, hãy dùng công cụ phù hợp. + +### Dữ liệu ngữ cảnh (retrieved from database): +Dưới đây là các thông tin có liên quan: + +(Đoạn 1 - score=0.654) +Thời Đại Thiếu Niên Đoàn + +Thời Đại Thiếu Niên Đoàn (, , tên thường gọi: TNT) là nhóm nhạc nam Trung Quốc gồm 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. Bảy thành viên trước khi ra mắt đều là thực tập sinh thế hệ thứ hai trực thuộc TF Entertainment, sau thành công của thế hệ thứ nhất là TFBoys. Nhóm được thành lập chính thức từ chương trình thực tế sống còn "Đài Phong Lột Xác Chiến" do TF Entertainment cải tổ lại. Chương trình kết thúc và nhóm chính thức được thành lập vào ngày 25 tháng 8 năm 2019. +Ngày 23 tháng 11 năm 2019 nhóm tổ chức buổi ra mắt và ra mắt với bài hát mới "Thông Báo Toàn Trường". +Fandom chính thức "Bạo Mễ Hoa" (tiếng Trung: 爆米花). Màu tiếp ứng là: Vàng, Đen. Khẩu hiệu tiếp ứng: Phá Thiên Hạ, Định Phong Vân, Thời Đại Thiếu Niên Tinh Kiên Hành.(tiếng Trung:破天下,定风云,时代少年并肩行). +Thành viên. +Tất cả các thành viên của Thời Đại Thiếu Niên Đoàn đều là mang quốc tịch Trung Quốc. Mã Gia Kỳ, người giành được hạng 1 trong chương trình thực tế ra mắt của cả nhóm, vừa là trưởng nhóm vừa đảm nhiệm vai trò vị trí trung tâm. +Lưu ý. + Các nội dung sau đây mang tính tham khảo, không tự ý sửa đổi, nếu sửa đổi sẽ được cho là phá hoại. Mong không sửa đổi dưới mọi hình thức, chỉ bổ sung khi thiếu, hoàn thành lại khi sai. Vì một số sửa đổi của một số bạn nên chúng tôi có thể không hoàn thiện nó như lúc đầu, vì vậy sửa đổi không cần thiết chính là phá hoại trang. + +(Đoạn 2 - score=0.611) +Boney M. + +Boney M. là một nhóm nhạc được thành lập vào năm 1976 bởi nhà sản xuất âm nhạc người Tây Đức Frank Farian. Bốn thành viên gốc của ban nhạc là Liz Mitchell và Marcia Barrett (Jamaica), Maizie Williams (Montserrat) và Bobby Farrell (Aruba). Boney M. thành công vào cuối thập niên 1970, đứng đầu nhiều bảng xếp hạng với số lượng đĩa bán lớn với những ca khúc nổi tiếng như Daddy Cool, Sunny, Rasputin... Kể từ thập niên 1980, ban nhạc thay đổi thành viên thường xuyên. Thành viên nam duy nhất Bobby Farrell qua đời vào cuối năm 2010 ở Saint Petersburg vì trụy tim. +Bảng xếp hạng. +Đĩa đơn. +"-" nghĩa là không có thông tin hoặc không được xếp hạng + +### Công cụ có thể sử dụng: + +Hiện bạn có thể sử dụng các công cụ sau: +1. search_google(query: str, max_results: int = 5) + → Tìm kiếm thông tin mới nhất trên Internet (dành cho các câu hỏi không có trong ngữ cảnh). +2. 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 lưu lại kết quả). + +Nếu người dùng yêu cầu hành động, hãy trả về đúng JSON hợp lệ: +{"action": "", "params": {"query" hoặc "text": ""}} + +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: +nhóm nhạc stray kids có bao nhiêu thành viên? + +### Hướng dẫn cho AI: +- Nếu thông tin trong ngữ cảnh đủ → trả lời bình thường, ngắn gọn, tự nhiên. +- Nếu ngữ cảnh không đủ → gọi công cụ search_google với câu hỏi gốc. +- Nếu người dùng yêu cầu hành động (như tạo/lưu file) → dùng create_txt. +- Khi trả về JSON, không thêm giải thích hay văn bản nào khác. +- Trả duy nhất một JSON hợp lệ khi gọi công cụ. +- Không bịa thêm hoặc suy diễn nếu thông tin không có. + +### Trả lời: +... +2025-10-13 00:31:42,136 [INFO] rag_api - ✅ Đã trả lời: nhóm nhạc stray kids có bao nhiêu thành viên?... +2025-10-13 00:34:20,287 [INFO] rag_api - 📥 Câu hỏi: nhóm nhạc stray kids có bao nhiêu thành viên? +2025-10-13 00:34:24,356 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK" +2025-10-13 00:34:50,766 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 00:34:55,409 [INFO] src.chatbot.llm_client - 💬 Gemini output: Tôi không có thông tin về số lượng thành viên của nhóm nhạc Stray Kids. Bạn có muốn tôi tìm kiếm trên Google không?... +2025-10-13 00:41:46,324 [INFO] numexpr.utils - NumExpr defaulting to 12 threads. +2025-10-13 00:42:03,781 [INFO] httpx - HTTP Request: GET http://localhost:6333 "HTTP/1.1 200 OK" +2025-10-13 00:42:03,810 [INFO] httpx - HTTP Request: GET http://localhost:6333/collections/text_chunks "HTTP/1.1 200 OK" +2025-10-13 00:42:03,977 [INFO] sentence_transformers.SentenceTransformer - Load pretrained SentenceTransformer: Alibaba-NLP/gte-multilingual-base +2025-10-13 00:42:11,346 [INFO] src.chatbot.llm_client - 🔮 LLMClient khởi tạo với model: models/gemini-2.0-flash-001 +2025-10-13 00:42:11,346 [INFO] function_router - 🔧 Đã đăng ký tool: create_txt +2025-10-13 00:42:11,346 [INFO] rag_api - ✅ RAGPipeline đã khởi tạo thành công. +2025-10-13 00:54:18,166 [INFO] rag_api - 📥 Câu hỏi: nhóm nhạc stray kids có bao nhiêu thành viên? +2025-10-13 00:54:22,660 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK" +2025-10-13 00:54:45,794 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 00:55:05,481 [INFO] src.chatbot.llm_client - 💬 Gemini output: Tôi không có thông tin về số lượng thành viên của nhóm nhạc Stray Kids.... +2025-10-13 00:56:10,159 [INFO] numexpr.utils - NumExpr defaulting to 12 threads. +2025-10-13 00:56:28,160 [INFO] httpx - HTTP Request: GET http://localhost:6333 "HTTP/1.1 200 OK" +2025-10-13 00:56:28,167 [INFO] httpx - HTTP Request: GET http://localhost:6333/collections/text_chunks "HTTP/1.1 200 OK" +2025-10-13 00:56:28,179 [INFO] sentence_transformers.SentenceTransformer - Load pretrained SentenceTransformer: Alibaba-NLP/gte-multilingual-base +2025-10-13 00:56:35,841 [INFO] src.chatbot.llm_client - 🔮 LLMClient khởi tạo với model: models/gemini-2.0-flash-001 +2025-10-13 00:56:35,841 [INFO] function_router - 🔧 Đã đăng ký tool: create_txt +2025-10-13 00:56:35,842 [INFO] rag_api - ✅ RAGPipeline đã khởi tạo thành công. +2025-10-13 00:56:43,560 [INFO] rag_api - 📥 Câu hỏi: nhóm nhạc stray kids có bao nhiêu thành viên? +2025-10-13 00:56:47,602 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK" +2025-10-13 00:58:25,888 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 00:58:43,454 [INFO] src.chatbot.llm_client - 💬 Gemini output: Tôi không có thông tin về số lượng thành viên của nhóm nhạc Stray Kids.... +2025-10-13 01:02:59,777 [INFO] numexpr.utils - NumExpr defaulting to 12 threads. +2025-10-13 01:03:17,011 [INFO] httpx - HTTP Request: GET http://localhost:6333 "HTTP/1.1 200 OK" +2025-10-13 01:03:17,019 [INFO] httpx - HTTP Request: GET http://localhost:6333/collections/text_chunks "HTTP/1.1 200 OK" +2025-10-13 01:03:17,187 [INFO] sentence_transformers.SentenceTransformer - Load pretrained SentenceTransformer: Alibaba-NLP/gte-multilingual-base +2025-10-13 01:03:25,027 [INFO] src.chatbot.llm_client - 🔮 LLMClient khởi tạo với model: models/gemini-2.0-flash-001 +2025-10-13 01:03:25,027 [INFO] function_router - 🔧 Đã đăng ký tool: create_txt +2025-10-13 01:03:25,027 [INFO] rag_api - ✅ RAGPipeline đã khởi tạo thành công. +2025-10-13 01:03:29,615 [INFO] rag_api - 📥 Câu hỏi: nhóm nhạc stray kids có bao nhiêu thành viên? +2025-10-13 01:03:36,705 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK" +2025-10-13 01:03:38,581 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 01:03:51,722 [INFO] src.chatbot.llm_client - 💬 Gemini output: {"action": "search_google", "params": {"query": "Stray Kids có bao nhiêu thành viên?"}}... +2025-10-13 01:03:55,916 [INFO] src.chatbot.llm_client - 🤖 Function call JSON: {'action': 'search_google', 'params': {'query': 'Stray Kids có bao nhiêu thành viên?'}} +2025-10-13 01:04:37,209 [INFO] root - 📤 Gửi prompt tới LLM: ### Vai trò hệ thống +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. + +### Ngữ cảnh (retrieved from database) +Dưới đây là các thông tin có liên quan: + +(Đoạn 1 - score=0.654) +Thời Đại Thiếu Niên Đoàn + +Thời Đại Thiếu Niên Đoàn (, , tên thường gọi: TNT) là nhóm nhạc nam Trung Quốc gồm 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. Bảy thành viên trước khi ra mắt đều là thực tập sinh thế hệ thứ hai trực thuộc TF Entertainment, sau thành công của thế hệ thứ nhất là TFBoys. Nhóm được thành lập chính thức từ chương trình thực tế sống còn "Đài Phong Lột Xác Chiến" do TF Entertainment cải tổ lại. Chương trình kết thúc và nhóm chính thức được thành lập vào ngày 25 tháng 8 năm 2019. +Ngày 23 tháng 11 năm 2019 nhóm tổ chức buổi ra mắt và ra mắt với bài hát mới "Thông Báo Toàn Trường". +Fandom chính thức "Bạo Mễ Hoa" (tiếng Trung: 爆米花). Màu tiếp ứng là: Vàng, Đen. Khẩu hiệu tiếp ứng: Phá Thiên Hạ, Định Phong Vân, Thời Đại Thiếu Niên Tinh Kiên Hành.(tiếng Trung:破天下,定风云,时代少年并肩行). +Thành viên. +Tất cả các thành viên của Thời Đại Thiếu Niên Đoàn đều là mang quốc tịch Trung Quốc. Mã Gia Kỳ, người giành được hạng 1 trong chương trình thực tế ra mắt của cả nhóm, vừa là trưởng nhóm vừa đảm nhiệm vai trò vị trí trung tâm. +Lưu ý. + Các nội dung sau đây mang tính tham khảo, không tự ý sửa đổi, nếu sửa đổi sẽ được cho là phá hoại. Mong không sửa đổi dưới mọi hình thức, chỉ bổ sung khi thiếu, hoàn thành lại khi sai. Vì một số sửa đổi của một số bạn nên chúng tôi có thể không hoàn thiện nó như lúc đầu, vì vậy sửa đổi không cần thiết chính là phá hoại trang. + +(Đoạn 2 - score=0.611) +Boney M. + +Boney M. là một nhóm nhạc được thành lập vào năm 1976 bởi nhà sản xuất âm nhạc người Tây Đức Frank Farian. Bốn thành viên gốc của ban nhạc là Liz Mitchell và Marcia Barrett (Jamaica), Maizie Williams (Montserrat) và Bobby Farrell (Aruba). Boney M. thành công vào cuối thập niên 1970, đứng đầu nhiều bảng xếp hạng với số lượng đĩa bán lớn với những ca khúc nổi tiếng như Daddy Cool, Sunny, Rasputin... Kể từ thập niên 1980, ban nhạc thay đổi thành viên thường xuyên. Thành viên nam duy nhất Bobby Farrell qua đời vào cuối năm 2010 ở Saint Petersburg vì trụy tim. +Bảng xếp hạng. +Đĩa đơn. +"-" nghĩa là không có thông tin hoặc không được xếp hạng + +### Công cụ có thể sử dụng + +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": "", "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. + + +### Câu hỏi người dùng +nhóm nhạc stray kids có bao nhiêu thành viên? + +### Hướng dẫn cho AI + +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": ""}} +3️⃣ Nếu người dùng yêu cầu lưu/tạo file → TRẢ VỀ JSON: + {"action": "create_txt", "params": {"text": ""}} +4️⃣ Nếu bạn không chắc chắn, cũng hãy gọi search_google thay vì suy đoán. + + +### Đị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 +... +2025-10-13 01:04:40,222 [INFO] rag_api - ✅ Đã trả lời: nhóm nhạc stray kids có bao nhiêu thành viên?... +2025-10-13 01:08:53,091 [INFO] rag_api - 📥 Câu hỏi: nhóm nhạc stray kids có bao nhiêu thành viên? +2025-10-13 01:08:55,089 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK" +2025-10-13 01:09:00,657 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 01:09:05,722 [INFO] src.chatbot.llm_client - 💬 Gemini output: {"action": "search_google", "params": {"query": "Stray Kids có bao nhiêu thành viên?"}}... +2025-10-13 01:09:07,758 [INFO] src.chatbot.llm_client - 🤖 Function call JSON: {'action': 'search_google', 'params': {'query': 'Stray Kids có bao nhiêu thành viên?'}} +2025-10-13 01:09:53,333 [INFO] root - 📤 Gửi prompt tới LLM: ### Vai trò hệ thống +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. + +### Ngữ cảnh (retrieved from database) +Dưới đây là các thông tin có liên quan: + +(Đoạn 1 - score=0.654) +Thời Đại Thiếu Niên Đoàn + +Thời Đại Thiếu Niên Đoàn (, , tên thường gọi: TNT) là nhóm nhạc nam Trung Quốc gồm 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. Bảy thành viên trước khi ra mắt đều là thực tập sinh thế hệ thứ hai trực thuộc TF Entertainment, sau thành công của thế hệ thứ nhất là TFBoys. Nhóm được thành lập chính thức từ chương trình thực tế sống còn "Đài Phong Lột Xác Chiến" do TF Entertainment cải tổ lại. Chương trình kết thúc và nhóm chính thức được thành lập vào ngày 25 tháng 8 năm 2019. +Ngày 23 tháng 11 năm 2019 nhóm tổ chức buổi ra mắt và ra mắt với bài hát mới "Thông Báo Toàn Trường". +Fandom chính thức "Bạo Mễ Hoa" (tiếng Trung: 爆米花). Màu tiếp ứng là: Vàng, Đen. Khẩu hiệu tiếp ứng: Phá Thiên Hạ, Định Phong Vân, Thời Đại Thiếu Niên Tinh Kiên Hành.(tiếng Trung:破天下,定风云,时代少年并肩行). +Thành viên. +Tất cả các thành viên của Thời Đại Thiếu Niên Đoàn đều là mang quốc tịch Trung Quốc. Mã Gia Kỳ, người giành được hạng 1 trong chương trình thực tế ra mắt của cả nhóm, vừa là trưởng nhóm vừa đảm nhiệm vai trò vị trí trung tâm. +Lưu ý. + Các nội dung sau đây mang tính tham khảo, không tự ý sửa đổi, nếu sửa đổi sẽ được cho là phá hoại. Mong không sửa đổi dưới mọi hình thức, chỉ bổ sung khi thiếu, hoàn thành lại khi sai. Vì một số sửa đổi của một số bạn nên chúng tôi có thể không hoàn thiện nó như lúc đầu, vì vậy sửa đổi không cần thiết chính là phá hoại trang. + +(Đoạn 2 - score=0.611) +Boney M. + +Boney M. là một nhóm nhạc được thành lập vào năm 1976 bởi nhà sản xuất âm nhạc người Tây Đức Frank Farian. Bốn thành viên gốc của ban nhạc là Liz Mitchell và Marcia Barrett (Jamaica), Maizie Williams (Montserrat) và Bobby Farrell (Aruba). Boney M. thành công vào cuối thập niên 1970, đứng đầu nhiều bảng xếp hạng với số lượng đĩa bán lớn với những ca khúc nổi tiếng như Daddy Cool, Sunny, Rasputin... Kể từ thập niên 1980, ban nhạc thay đổi thành viên thường xuyên. Thành viên nam duy nhất Bobby Farrell qua đời vào cuối năm 2010 ở Saint Petersburg vì trụy tim. +Bảng xếp hạng. +Đĩa đơn. +"-" nghĩa là không có thông tin hoặc không được xếp hạng + +### Công cụ có thể sử dụng + +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": "", "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. + + +### Câu hỏi người dùng +nhóm nhạc stray kids có bao nhiêu thành viên? + +### Hướng dẫn cho AI + +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": ""}} +3️⃣ Nếu người dùng yêu cầu lưu/tạo file → TRẢ VỀ JSON: + {"action": "create_txt", "params": {"text": ""}} +4️⃣ Nếu bạn không chắc chắn, cũng hãy gọi search_google thay vì suy đoán. + + +### Đị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 +... +2025-10-13 01:09:57,125 [INFO] rag_api - ✅ Đã trả lời: nhóm nhạc stray kids có bao nhiêu thành viên?... +2025-10-13 01:10:29,207 [INFO] numexpr.utils - NumExpr defaulting to 12 threads. +2025-10-13 01:10:46,580 [INFO] httpx - HTTP Request: GET http://localhost:6333 "HTTP/1.1 200 OK" +2025-10-13 01:10:46,586 [INFO] httpx - HTTP Request: GET http://localhost:6333/collections/text_chunks "HTTP/1.1 200 OK" +2025-10-13 01:10:46,774 [INFO] sentence_transformers.SentenceTransformer - Load pretrained SentenceTransformer: Alibaba-NLP/gte-multilingual-base +2025-10-13 01:10:53,326 [INFO] src.chatbot.llm_client - 🔮 LLMClient khởi tạo với model: models/gemini-2.0-flash-001 +2025-10-13 01:11:01,495 [INFO] function_router - 🔧 Đã đăng ký tool: create_txt — Tạo file TXT. Tham số: {text: str, filename?: str} +2025-10-13 01:11:01,496 [INFO] function_router - 🔧 Đã đăng ký tool: search_google — Tìm thông tin mới nhất trên Internet bằng Google Custom Search API. +2025-10-13 01:11:01,496 [INFO] function_router - ✅ FunctionRouter khởi tạo, có 2 công cụ sẵn sàng. +2025-10-13 01:11:01,496 [INFO] rag_api - ✅ RAGPipeline đã khởi tạo thành công. +2025-10-13 01:11:05,835 [INFO] rag_api - 📥 Câu hỏi: nhóm nhạc stray kids có bao nhiêu thành viên? +2025-10-13 01:11:10,310 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK" +2025-10-13 01:11:12,316 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 01:11:22,121 [INFO] src.chatbot.llm_client - 💬 Gemini output: {"action": "search_google", "params": {"query": "Stray Kids có bao nhiêu thành viên?"}}... +2025-10-13 01:11:24,072 [INFO] src.chatbot.llm_client - 🤖 Function call JSON: {'action': 'search_google', 'params': {'query': 'Stray Kids có bao nhiêu thành viên?'}} +2025-10-13 01:11:59,380 [INFO] function_router - ⚙️ Thực thi tool 'search_google' với params={'query': 'Stray Kids có bao nhiêu thành viên?'} +2025-10-13 01:12:09,600 [INFO] function_router - ✅ Tool 'search_google' thực thi thành công. +2025-10-13 01:12:51,036 [INFO] root - 📤 Gửi prompt tới LLM: ### Vai trò hệ thống +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. + +### Ngữ cảnh (retrieved from database) +Dưới đây là các thông tin có liên quan: + +(Đoạn 1 - score=0.654) +Thời Đại Thiếu Niên Đoàn + +Thời Đại Thiếu Niên Đoàn (, , tên thường gọi: TNT) là nhóm nhạc nam Trung Quốc gồm 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. Bảy thành viên trước khi ra mắt đều là thực tập sinh thế hệ thứ hai trực thuộc TF Entertainment, sau thành công của thế hệ thứ nhất là TFBoys. Nhóm được thành lập chính thức từ chương trình thực tế sống còn "Đài Phong Lột Xác Chiến" do TF Entertainment cải tổ lại. Chương trình kết thúc và nhóm chính thức được thành lập vào ngày 25 tháng 8 năm 2019. +Ngày 23 tháng 11 năm 2019 nhóm tổ chức buổi ra mắt và ra mắt với bài hát mới "Thông Báo Toàn Trường". +Fandom chính thức "Bạo Mễ Hoa" (tiếng Trung: 爆米花). Màu tiếp ứng là: Vàng, Đen. Khẩu hiệu tiếp ứng: Phá Thiên Hạ, Định Phong Vân, Thời Đại Thiếu Niên Tinh Kiên Hành.(tiếng Trung:破天下,定风云,时代少年并肩行). +Thành viên. +Tất cả các thành viên của Thời Đại Thiếu Niên Đoàn đều là mang quốc tịch Trung Quốc. Mã Gia Kỳ, người giành được hạng 1 trong chương trình thực tế ra mắt của cả nhóm, vừa là trưởng nhóm vừa đảm nhiệm vai trò vị trí trung tâm. +Lưu ý. + Các nội dung sau đây mang tính tham khảo, không tự ý sửa đổi, nếu sửa đổi sẽ được cho là phá hoại. Mong không sửa đổi dưới mọi hình thức, chỉ bổ sung khi thiếu, hoàn thành lại khi sai. Vì một số sửa đổi của một số bạn nên chúng tôi có thể không hoàn thiện nó như lúc đầu, vì vậy sửa đổi không cần thiết chính là phá hoại trang. + +(Đoạn 2 - score=0.611) +Boney M. + +Boney M. là một nhóm nhạc được thành lập vào năm 1976 bởi nhà sản xuất âm nhạc người Tây Đức Frank Farian. Bốn thành viên gốc của ban nhạc là Liz Mitchell và Marcia Barrett (Jamaica), Maizie Williams (Montserrat) và Bobby Farrell (Aruba). Boney M. thành công vào cuối thập niên 1970, đứng đầu nhiều bảng xếp hạng với số lượng đĩa bán lớn với những ca khúc nổi tiếng như Daddy Cool, Sunny, Rasputin... Kể từ thập niên 1980, ban nhạc thay đổi thành viên thường xuyên. Thành viên nam duy nhất Bobby Farrell qua đời vào cuối năm 2010 ở Saint Petersburg vì trụy tim. +Bảng xếp hạng. +Đĩa đơn. +"-" nghĩa là không có thông tin hoặc không được xếp hạng + +### Công cụ có thể sử dụng + +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": "", "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. + + +### Câu hỏi người dùng +nhóm nhạc stray kids có bao nhiêu thành viên? + +### Hướng dẫn cho AI + +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": ""}} +3️⃣ Nếu người dùng yêu cầu lưu/tạo file → TRẢ VỀ JSON: + {"action": "create_txt", "params": {"text": ""}} +4️⃣ Nếu bạn không chắc chắn, cũng hãy gọi search_google thay vì suy đoán. + + +### Đị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 +... +2025-10-13 01:12:54,158 [INFO] rag_api - ✅ Đã trả lời: nhóm nhạc stray kids có bao nhiêu thành viên?... +2025-10-13 01:16:26,490 [INFO] numexpr.utils - NumExpr defaulting to 12 threads. +2025-10-13 01:16:29,505 [INFO] httpx - HTTP Request: GET http://localhost:6333 "HTTP/1.1 200 OK" +2025-10-13 01:16:29,509 [INFO] httpx - HTTP Request: GET http://localhost:6333/collections/text_chunks "HTTP/1.1 200 OK" +2025-10-13 01:16:29,517 [INFO] sentence_transformers.SentenceTransformer - Load pretrained SentenceTransformer: Alibaba-NLP/gte-multilingual-base +2025-10-13 01:17:13,121 [INFO] src.chatbot.llm_client - 🔮 LLMClient khởi tạo với model: models/gemini-2.0-flash-001 +2025-10-13 01:17:13,121 [INFO] function_router - 🔧 Đã đăng ký tool: create_txt — Tạo file TXT. Tham số: {text: str, filename?: str} +2025-10-13 01:17:13,121 [INFO] function_router - 🔧 Đã đăng ký tool: search_google — Tìm thông tin mới nhất trên Internet bằng Google Custom Search API. +2025-10-13 01:17:13,122 [INFO] function_router - ✅ FunctionRouter khởi tạo, có 2 công cụ sẵn sàng. +2025-10-13 01:17:13,122 [INFO] rag_api - ✅ RAGPipeline đã khởi tạo thành công. +2025-10-13 01:17:51,802 [INFO] rag_api - 📥 Câu hỏi: nhóm nhạc stray kids có bao nhiêu thành viên? +2025-10-13 01:17:52,167 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK" +2025-10-13 01:17:52,170 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 01:17:53,204 [INFO] src.chatbot.llm_client - 💬 Gemini output: {"action": "search_google", "params": {"query": "Stray Kids có bao nhiêu thành viên?"}}... +2025-10-13 01:17:53,205 [INFO] src.chatbot.llm_client - 🤖 Function call JSON: {'action': 'search_google', 'params': {'query': 'Stray Kids có bao nhiêu thành viên?'}} +2025-10-13 01:17:53,205 [INFO] function_executor - 🔧 LLM yêu cầu thực thi tool: search_google({'query': 'Stray Kids có bao nhiêu thành viên?'}) +2025-10-13 01:17:53,205 [INFO] function_router - ⚙️ Thực thi tool 'search_google' với params={'query': 'Stray Kids có bao nhiêu thành viên?'} +2025-10-13 01:17:54,620 [INFO] function_router - ✅ Tool 'search_google' thực thi thành công. +2025-10-13 01:17:54,621 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 01:17:55,895 [INFO] src.chatbot.llm_client - 💬 Gemini output: Stray Kids là nhóm nhạc nam Hàn Quốc được thành lập bởi JYP Entertainment thông qua chương trình truyền hình thực tế cùn... +2025-10-13 01:17:55,896 [INFO] root - 📤 Gửi prompt tới LLM: ### Vai trò hệ thống +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. + +### Ngữ cảnh (retrieved from database) +Dưới đây là các thông tin có liên quan: + +(Đoạn 1 - score=0.654) +Thời Đại Thiếu Niên Đoàn + +Thời Đại Thiếu Niên Đoàn (, , tên thường gọi: TNT) là nhóm nhạc nam Trung Quốc gồm 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. Bảy thành viên trước khi ra mắt đều là thực tập sinh thế hệ thứ hai trực thuộc TF Entertainment, sau thành công của thế hệ thứ nhất là TFBoys. Nhóm được thành lập chính thức từ chương trình thực tế sống còn "Đài Phong Lột Xác Chiến" do TF Entertainment cải tổ lại. Chương trình kết thúc và nhóm chính thức được thành lập vào ngày 25 tháng 8 năm 2019. +Ngày 23 tháng 11 năm 2019 nhóm tổ chức buổi ra mắt và ra mắt với bài hát mới "Thông Báo Toàn Trường". +Fandom chính thức "Bạo Mễ Hoa" (tiếng Trung: 爆米花). Màu tiếp ứng là: Vàng, Đen. Khẩu hiệu tiếp ứng: Phá Thiên Hạ, Định Phong Vân, Thời Đại Thiếu Niên Tinh Kiên Hành.(tiếng Trung:破天下,定风云,时代少年并肩行). +Thành viên. +Tất cả các thành viên của Thời Đại Thiếu Niên Đoàn đều là mang quốc tịch Trung Quốc. Mã Gia Kỳ, người giành được hạng 1 trong chương trình thực tế ra mắt của cả nhóm, vừa là trưởng nhóm vừa đảm nhiệm vai trò vị trí trung tâm. +Lưu ý. + Các nội dung sau đây mang tính tham khảo, không tự ý sửa đổi, nếu sửa đổi sẽ được cho là phá hoại. Mong không sửa đổi dưới mọi hình thức, chỉ bổ sung khi thiếu, hoàn thành lại khi sai. Vì một số sửa đổi của một số bạn nên chúng tôi có thể không hoàn thiện nó như lúc đầu, vì vậy sửa đổi không cần thiết chính là phá hoại trang. + +(Đoạn 2 - score=0.611) +Boney M. + +Boney M. là một nhóm nhạc được thành lập vào năm 1976 bởi nhà sản xuất âm nhạc người Tây Đức Frank Farian. Bốn thành viên gốc của ban nhạc là Liz Mitchell và Marcia Barrett (Jamaica), Maizie Williams (Montserrat) và Bobby Farrell (Aruba). Boney M. thành công vào cuối thập niên 1970, đứng đầu nhiều bảng xếp hạng với số lượng đĩa bán lớn với những ca khúc nổi tiếng như Daddy Cool, Sunny, Rasputin... Kể từ thập niên 1980, ban nhạc thay đổi thành viên thường xuyên. Thành viên nam duy nhất Bobby Farrell qua đời vào cuối năm 2010 ở Saint Petersburg vì trụy tim. +Bảng xếp hạng. +Đĩa đơn. +"-" nghĩa là không có thông tin hoặc không được xếp hạng + +### Công cụ có thể sử dụng + +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": "", "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. + + +### Câu hỏi người dùng +nhóm nhạc stray kids có bao nhiêu thành viên? + +### Hướng dẫn cho AI + +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": ""}} +3️⃣ Nếu người dùng yêu cầu lưu/tạo file → TRẢ VỀ JSON: + {"action": "create_txt", "params": {"text": ""}} +4️⃣ Nếu bạn không chắc chắn, cũng hãy gọi search_google thay vì suy đoán. + + +### Đị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 +... +2025-10-13 01:17:55,897 [INFO] rag_api - ✅ Đã trả lời: nhóm nhạc stray kids có bao nhiêu thành viên?... +2025-10-13 01:18:20,842 [INFO] rag_api - 📥 Câu hỏi: Thời Đại Thiếu Niên Đoàn có mấy thành viên? +2025-10-13 01:18:21,040 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK" +2025-10-13 01:18:21,042 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 01:18:21,937 [INFO] src.chatbot.llm_client - 💬 Gemini output: 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ê... +2025-10-13 01:18:21,938 [INFO] function_executor - ⚠️ Không có action → fallback Google Search +2025-10-13 01:18:23,297 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 01:18:24,857 [INFO] src.chatbot.llm_client - 💬 Gemini output: Thời Đại Thiếu Niên Đoàn (TNT) là nhóm nhạc nam Trung Quốc gồm 7 thành viên: Mã Gia Kỳ, Đinh Trình Hâm, Tống Á Hiên, Lưu... +2025-10-13 01:18:24,857 [INFO] root - 📤 Gửi prompt tới LLM: ### Vai trò hệ thống +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. + +### Ngữ cảnh (retrieved from database) +Dưới đây là các thông tin có liên quan: + +(Đoạn 1 - score=0.929) +Thời Đại Thiếu Niên Đoàn + +Thời Đại Thiếu Niên Đoàn (, , tên thường gọi: TNT) là nhóm nhạc nam Trung Quốc gồm 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. Bảy thành viên trước khi ra mắt đều là thực tập sinh thế hệ thứ hai trực thuộc TF Entertainment, sau thành công của thế hệ thứ nhất là TFBoys. Nhóm được thành lập chính thức từ chương trình thực tế sống còn "Đài Phong Lột Xác Chiến" do TF Entertainment cải tổ lại. Chương trình kết thúc và nhóm chính thức được thành lập vào ngày 25 tháng 8 năm 2019. +Ngày 23 tháng 11 năm 2019 nhóm tổ chức buổi ra mắt và ra mắt với bài hát mới "Thông Báo Toàn Trường". +Fandom chính thức "Bạo Mễ Hoa" (tiếng Trung: 爆米花). Màu tiếp ứng là: Vàng, Đen. Khẩu hiệu tiếp ứng: Phá Thiên Hạ, Định Phong Vân, Thời Đại Thiếu Niên Tinh Kiên Hành.(tiếng Trung:破天下,定风云,时代少年并肩行). +Thành viên. +Tất cả các thành viên của Thời Đại Thiếu Niên Đoàn đều là mang quốc tịch Trung Quốc. Mã Gia Kỳ, người giành được hạng 1 trong chương trình thực tế ra mắt của cả nhóm, vừa là trưởng nhóm vừa đảm nhiệm vai trò vị trí trung tâm. +Lưu ý. + Các nội dung sau đây mang tính tham khảo, không tự ý sửa đổi, nếu sửa đổi sẽ được cho là phá hoại. Mong không sửa đổi dưới mọi hình thức, chỉ bổ sung khi thiếu, hoàn thành lại khi sai. Vì một số sửa đổi của một số bạn nên chúng tôi có thể không hoàn thiện nó như lúc đầu, vì vậy sửa đổi không cần thiết chính là phá hoại trang. + +(Đoạn 2 - score=0.666) +Tiểu Thời Đại + +Tiểu Thời Đại hay Tiểu Thời Đại 1.0 (小时代) là phim điện ảnh của đạo diễn kiêm biên kịch Quách Kính Minh dựa trên tiểu thuyết cùng tên của chính anh. +Bộ phim thành công về mặt thương mại và nhận được những phản ứng tích cực từ giới chuyên môn. Phần hai có tựa đề "Tiểu Thời Đại 2" được quay song song cùng phần một của bộ phim và dựa trên phần hai của bộ tiểu thuyết đã ra mắt vào ngày 8 tháng 8 năm 2013. "Tiểu Thời Đại 3" cũng đã nhanh chóng khởi quay và ra mắt công chúng vào ngày 17 tháng 7 năm 2014. +Nội dung. +Bộ phim dựa trên bộ truyện có tên là "Tiểu Thời Đại "với 3 phần" 1.0, 2.0, 3.0" xoay quanh tình bạn của bốn cô gái gồm Lâm Tiêu, Cố Lý, Nam Tương và Đường Uyển Như sống giữa thành phố Thượng Hải phồn hoa nhưng đầy rẫy tham vọng, toan tính, khát khao của tuổi trẻ. +Bốn cô gái là bạn cùng lớp thời trung học và bạn cùng phòng khi lên đại học. Trong trường, họ phải đối mặt với áp lực nặng nề, làm quen với cuộc sống sinh viên nội trú, gặp rắc rối với những chuyện tình cảm xảy ra liên tục. Sau khi tốt nghiệp, họ dần thay đổi và vướng vào những mối quan hệ phức tạp khác trong cuộc sống và dần trở nên nghi kị và hiểu lầm lẫn nhau. +Ra mắt và phản hồi. +Doanh thu phòng vé. +Dù kinh phí sản xuất của bộ phim chưa đến 50 triệu nhân dân tệ, được chia làm 2 phần, mỗi phần vỏn vẹn 25 triệu, nhưng ngay ngày đầu tiên công chiếu, "Tiểu Thời Đại" đã đạt doanh thu hơn 73 triệu Nhân dân tệ +Bộ phim đã thu về US$79.7 triệu tại các phòng vé ở Trung Quốc. +Sau ngày ra mắt, doanh thu phòng vé của Tiểu Thời Đại 3 đạt 150 triệu tệ (~ 525 tỷ đồng), lập kỷ lục phim điện ảnh 2D nội địa ăn khách nhất Trung Quốc. + +### Công cụ có thể sử dụng + +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": "", "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. + + +### Câu hỏi người dùng +Thời Đại Thiếu Niên Đoàn có mấy thành viên? + +### Hướng dẫn cho AI + +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": ""}} +3️⃣ Nếu người dùng yêu cầu lưu/tạo file → TRẢ VỀ JSON: + {"action": "create_txt", "params": {"text": ""}} +4️⃣ Nếu bạn không chắc chắn, cũng hãy gọi search_google thay vì suy đoán. + + +### Đị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 +... +2025-10-13 01:18:24,858 [INFO] rag_api - ✅ Đã trả lời: Thời Đại Thiếu Niên Đoàn có mấy thành viên?... +2025-10-13 01:19:03,866 [INFO] rag_api - 📥 Câu hỏi: Thời Đại Thiếu Niên Đoàn có mấy thành viên? bạn viết ra file txt +2025-10-13 01:19:04,099 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK" +2025-10-13 01:19:04,100 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 01:19:05,452 [INFO] src.chatbot.llm_client - 💬 Gemini output: 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ê... +2025-10-13 01:19:05,452 [INFO] src.chatbot.llm_client - 🤖 Function call JSON: {'action': 'create_txt', 'params': {'text': '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.'}} +2025-10-13 01:19:05,452 [INFO] function_executor - 🔧 LLM yêu cầu thực thi tool: create_txt({'text': '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.'}) +2025-10-13 01:19:05,453 [INFO] function_router - ⚙️ Thực thi tool 'create_txt' với params={'text': '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.'} +2025-10-13 01:19:05,454 [INFO] function_router - ✅ Tool 'create_txt' thực thi thành công. +2025-10-13 01:19:05,454 [INFO] root - 📤 Gửi prompt tới LLM: ### Vai trò hệ thống +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. + +### Ngữ cảnh (retrieved from database) +Dưới đây là các thông tin có liên quan: + +(Đoạn 1 - score=0.829) +Thời Đại Thiếu Niên Đoàn + +Thời Đại Thiếu Niên Đoàn (, , tên thường gọi: TNT) là nhóm nhạc nam Trung Quốc gồm 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. Bảy thành viên trước khi ra mắt đều là thực tập sinh thế hệ thứ hai trực thuộc TF Entertainment, sau thành công của thế hệ thứ nhất là TFBoys. Nhóm được thành lập chính thức từ chương trình thực tế sống còn "Đài Phong Lột Xác Chiến" do TF Entertainment cải tổ lại. Chương trình kết thúc và nhóm chính thức được thành lập vào ngày 25 tháng 8 năm 2019. +Ngày 23 tháng 11 năm 2019 nhóm tổ chức buổi ra mắt và ra mắt với bài hát mới "Thông Báo Toàn Trường". +Fandom chính thức "Bạo Mễ Hoa" (tiếng Trung: 爆米花). Màu tiếp ứng là: Vàng, Đen. Khẩu hiệu tiếp ứng: Phá Thiên Hạ, Định Phong Vân, Thời Đại Thiếu Niên Tinh Kiên Hành.(tiếng Trung:破天下,定风云,时代少年并肩行). +Thành viên. +Tất cả các thành viên của Thời Đại Thiếu Niên Đoàn đều là mang quốc tịch Trung Quốc. Mã Gia Kỳ, người giành được hạng 1 trong chương trình thực tế ra mắt của cả nhóm, vừa là trưởng nhóm vừa đảm nhiệm vai trò vị trí trung tâm. +Lưu ý. + Các nội dung sau đây mang tính tham khảo, không tự ý sửa đổi, nếu sửa đổi sẽ được cho là phá hoại. Mong không sửa đổi dưới mọi hình thức, chỉ bổ sung khi thiếu, hoàn thành lại khi sai. Vì một số sửa đổi của một số bạn nên chúng tôi có thể không hoàn thiện nó như lúc đầu, vì vậy sửa đổi không cần thiết chính là phá hoại trang. + +(Đoạn 2 - score=0.678) +Tiểu Thời Đại + +Tiểu Thời Đại hay Tiểu Thời Đại 1.0 (小时代) là phim điện ảnh của đạo diễn kiêm biên kịch Quách Kính Minh dựa trên tiểu thuyết cùng tên của chính anh. +Bộ phim thành công về mặt thương mại và nhận được những phản ứng tích cực từ giới chuyên môn. Phần hai có tựa đề "Tiểu Thời Đại 2" được quay song song cùng phần một của bộ phim và dựa trên phần hai của bộ tiểu thuyết đã ra mắt vào ngày 8 tháng 8 năm 2013. "Tiểu Thời Đại 3" cũng đã nhanh chóng khởi quay và ra mắt công chúng vào ngày 17 tháng 7 năm 2014. +Nội dung. +Bộ phim dựa trên bộ truyện có tên là "Tiểu Thời Đại "với 3 phần" 1.0, 2.0, 3.0" xoay quanh tình bạn của bốn cô gái gồm Lâm Tiêu, Cố Lý, Nam Tương và Đường Uyển Như sống giữa thành phố Thượng Hải phồn hoa nhưng đầy rẫy tham vọng, toan tính, khát khao của tuổi trẻ. +Bốn cô gái là bạn cùng lớp thời trung học và bạn cùng phòng khi lên đại học. Trong trường, họ phải đối mặt với áp lực nặng nề, làm quen với cuộc sống sinh viên nội trú, gặp rắc rối với những chuyện tình cảm xảy ra liên tục. Sau khi tốt nghiệp, họ dần thay đổi và vướng vào những mối quan hệ phức tạp khác trong cuộc sống và dần trở nên nghi kị và hiểu lầm lẫn nhau. +Ra mắt và phản hồi. +Doanh thu phòng vé. +Dù kinh phí sản xuất của bộ phim chưa đến 50 triệu nhân dân tệ, được chia làm 2 phần, mỗi phần vỏn vẹn 25 triệu, nhưng ngay ngày đầu tiên công chiếu, "Tiểu Thời Đại" đã đạt doanh thu hơn 73 triệu Nhân dân tệ +Bộ phim đã thu về US$79.7 triệu tại các phòng vé ở Trung Quốc. +Sau ngày ra mắt, doanh thu phòng vé của Tiểu Thời Đại 3 đạt 150 triệu tệ (~ 525 tỷ đồng), lập kỷ lục phim điện ảnh 2D nội địa ăn khách nhất Trung Quốc. + +### Công cụ có thể sử dụng + +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": "", "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. + + +### Câu hỏi người dùng +Thời Đại Thiếu Niên Đoàn có mấy thành viên? bạn viết ra file txt + +### Hướng dẫn cho AI + +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": ""}} +3️⃣ Nếu người dùng yêu cầu lưu/tạo file → TRẢ VỀ JSON: + {"action": "create_txt", "params": {"text": ""}} +4️⃣ Nếu bạn không chắc chắn, cũng hãy gọi search_google thay vì suy đoán. + + +### Đị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 +... +2025-10-13 01:19:05,454 [INFO] rag_api - ✅ Đã trả lời: Thời Đại Thiếu Niên Đoàn có mấy thành viên? bạn vi... +2025-10-13 01:19:25,271 [INFO] rag_api - 📥 Câu hỏi: Thời Đại Thiếu Niên Đoàn có mấy thành viên? +2025-10-13 01:19:25,426 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK" +2025-10-13 01:19:25,428 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 01:19:26,166 [INFO] src.chatbot.llm_client - 💬 Gemini output: 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ê... +2025-10-13 01:19:26,166 [INFO] function_executor - ⚠️ Không có action → fallback Google Search +2025-10-13 01:19:27,499 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 01:19:29,078 [INFO] src.chatbot.llm_client - 💬 Gemini output: Thời Đại Thiếu Niên Đoàn (TNT) là nhóm nhạc nam Trung Quốc gồm 7 thành viên: Mã Gia Kỳ, Đinh Trình Hâm, Tống Á Hiên, Lưu... +2025-10-13 01:19:29,078 [INFO] root - 📤 Gửi prompt tới LLM: ### Vai trò hệ thống +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. + +### Ngữ cảnh (retrieved from database) +Dưới đây là các thông tin có liên quan: + +(Đoạn 1 - score=0.929) +Thời Đại Thiếu Niên Đoàn + +Thời Đại Thiếu Niên Đoàn (, , tên thường gọi: TNT) là nhóm nhạc nam Trung Quốc gồm 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. Bảy thành viên trước khi ra mắt đều là thực tập sinh thế hệ thứ hai trực thuộc TF Entertainment, sau thành công của thế hệ thứ nhất là TFBoys. Nhóm được thành lập chính thức từ chương trình thực tế sống còn "Đài Phong Lột Xác Chiến" do TF Entertainment cải tổ lại. Chương trình kết thúc và nhóm chính thức được thành lập vào ngày 25 tháng 8 năm 2019. +Ngày 23 tháng 11 năm 2019 nhóm tổ chức buổi ra mắt và ra mắt với bài hát mới "Thông Báo Toàn Trường". +Fandom chính thức "Bạo Mễ Hoa" (tiếng Trung: 爆米花). Màu tiếp ứng là: Vàng, Đen. Khẩu hiệu tiếp ứng: Phá Thiên Hạ, Định Phong Vân, Thời Đại Thiếu Niên Tinh Kiên Hành.(tiếng Trung:破天下,定风云,时代少年并肩行). +Thành viên. +Tất cả các thành viên của Thời Đại Thiếu Niên Đoàn đều là mang quốc tịch Trung Quốc. Mã Gia Kỳ, người giành được hạng 1 trong chương trình thực tế ra mắt của cả nhóm, vừa là trưởng nhóm vừa đảm nhiệm vai trò vị trí trung tâm. +Lưu ý. + Các nội dung sau đây mang tính tham khảo, không tự ý sửa đổi, nếu sửa đổi sẽ được cho là phá hoại. Mong không sửa đổi dưới mọi hình thức, chỉ bổ sung khi thiếu, hoàn thành lại khi sai. Vì một số sửa đổi của một số bạn nên chúng tôi có thể không hoàn thiện nó như lúc đầu, vì vậy sửa đổi không cần thiết chính là phá hoại trang. + +(Đoạn 2 - score=0.666) +Tiểu Thời Đại + +Tiểu Thời Đại hay Tiểu Thời Đại 1.0 (小时代) là phim điện ảnh của đạo diễn kiêm biên kịch Quách Kính Minh dựa trên tiểu thuyết cùng tên của chính anh. +Bộ phim thành công về mặt thương mại và nhận được những phản ứng tích cực từ giới chuyên môn. Phần hai có tựa đề "Tiểu Thời Đại 2" được quay song song cùng phần một của bộ phim và dựa trên phần hai của bộ tiểu thuyết đã ra mắt vào ngày 8 tháng 8 năm 2013. "Tiểu Thời Đại 3" cũng đã nhanh chóng khởi quay và ra mắt công chúng vào ngày 17 tháng 7 năm 2014. +Nội dung. +Bộ phim dựa trên bộ truyện có tên là "Tiểu Thời Đại "với 3 phần" 1.0, 2.0, 3.0" xoay quanh tình bạn của bốn cô gái gồm Lâm Tiêu, Cố Lý, Nam Tương và Đường Uyển Như sống giữa thành phố Thượng Hải phồn hoa nhưng đầy rẫy tham vọng, toan tính, khát khao của tuổi trẻ. +Bốn cô gái là bạn cùng lớp thời trung học và bạn cùng phòng khi lên đại học. Trong trường, họ phải đối mặt với áp lực nặng nề, làm quen với cuộc sống sinh viên nội trú, gặp rắc rối với những chuyện tình cảm xảy ra liên tục. Sau khi tốt nghiệp, họ dần thay đổi và vướng vào những mối quan hệ phức tạp khác trong cuộc sống và dần trở nên nghi kị và hiểu lầm lẫn nhau. +Ra mắt và phản hồi. +Doanh thu phòng vé. +Dù kinh phí sản xuất của bộ phim chưa đến 50 triệu nhân dân tệ, được chia làm 2 phần, mỗi phần vỏn vẹn 25 triệu, nhưng ngay ngày đầu tiên công chiếu, "Tiểu Thời Đại" đã đạt doanh thu hơn 73 triệu Nhân dân tệ +Bộ phim đã thu về US$79.7 triệu tại các phòng vé ở Trung Quốc. +Sau ngày ra mắt, doanh thu phòng vé của Tiểu Thời Đại 3 đạt 150 triệu tệ (~ 525 tỷ đồng), lập kỷ lục phim điện ảnh 2D nội địa ăn khách nhất Trung Quốc. + +### Công cụ có thể sử dụng + +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": "", "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. + + +### Câu hỏi người dùng +Thời Đại Thiếu Niên Đoàn có mấy thành viên? + +### Hướng dẫn cho AI + +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": ""}} +3️⃣ Nếu người dùng yêu cầu lưu/tạo file → TRẢ VỀ JSON: + {"action": "create_txt", "params": {"text": ""}} +4️⃣ Nếu bạn không chắc chắn, cũng hãy gọi search_google thay vì suy đoán. + + +### Đị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 +... +2025-10-13 01:19:29,079 [INFO] rag_api - ✅ Đã trả lời: Thời Đại Thiếu Niên Đoàn có mấy thành viên? ... +2025-10-13 01:20:01,389 [INFO] numexpr.utils - NumExpr defaulting to 12 threads. +2025-10-13 01:20:19,002 [INFO] httpx - HTTP Request: GET http://localhost:6333 "HTTP/1.1 200 OK" +2025-10-13 01:20:19,007 [INFO] httpx - HTTP Request: GET http://localhost:6333/collections/text_chunks "HTTP/1.1 200 OK" +2025-10-13 01:20:19,170 [INFO] sentence_transformers.SentenceTransformer - Load pretrained SentenceTransformer: Alibaba-NLP/gte-multilingual-base +2025-10-13 01:20:25,807 [INFO] src.chatbot.llm_client - 🔮 LLMClient khởi tạo với model: models/gemini-2.0-flash-001 +2025-10-13 01:20:25,807 [INFO] function_router - 🔧 Đã đăng ký tool: create_txt — Tạo file TXT. Tham số: {text: str, filename?: str} +2025-10-13 01:20:25,808 [INFO] function_router - 🔧 Đã đăng ký tool: search_google — Tìm thông tin mới nhất trên Internet bằng Google Custom Search API. +2025-10-13 01:20:25,808 [INFO] function_router - ✅ FunctionRouter khởi tạo, có 2 công cụ sẵn sàng. +2025-10-13 01:20:25,808 [INFO] rag_api - ✅ RAGPipeline đã khởi tạo thành công. +2025-10-13 01:20:33,300 [INFO] rag_api - 📥 Câu hỏi: Thời Đại Thiếu Niên Đoàn có mấy thành viên? +2025-10-13 01:20:37,732 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK" +2025-10-13 01:20:40,358 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 01:20:57,112 [INFO] src.chatbot.llm_client - 💬 Gemini output: 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ê... +2025-10-13 01:21:18,879 [INFO] function_executor - ⚠️ Không có action → fallback Google Search +2025-10-13 01:25:19,920 [INFO] numexpr.utils - NumExpr defaulting to 12 threads. +2025-10-13 01:25:23,027 [INFO] httpx - HTTP Request: GET http://localhost:6333 "HTTP/1.1 200 OK" +2025-10-13 01:25:23,031 [INFO] httpx - HTTP Request: GET http://localhost:6333/collections/text_chunks "HTTP/1.1 200 OK" +2025-10-13 01:25:23,038 [INFO] sentence_transformers.SentenceTransformer - Load pretrained SentenceTransformer: Alibaba-NLP/gte-multilingual-base +2025-10-13 01:25:29,724 [INFO] src.chatbot.llm_client - 🔮 LLMClient khởi tạo với model: models/gemini-2.0-flash-001 +2025-10-13 01:25:29,724 [INFO] function_router - 🔧 Đã đăng ký tool: create_txt — Tạo file TXT. Tham số: {text: str, filename?: str} +2025-10-13 01:25:29,725 [INFO] function_router - 🔧 Đã đăng ký tool: search_google — Tìm thông tin mới nhất trên Internet bằng Google Custom Search API. +2025-10-13 01:25:29,725 [INFO] function_router - ✅ FunctionRouter khởi tạo, có 2 công cụ sẵn sàng. +2025-10-13 01:25:29,725 [INFO] rag_api - ✅ RAGPipeline đã khởi tạo thành công. +2025-10-13 01:25:35,666 [INFO] rag_api - 📥 Câu hỏi: Thời Đại Thiếu Niên Đoàn có mấy thành viên? +2025-10-13 01:25:35,852 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK" +2025-10-13 01:25:35,855 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 01:25:37,093 [INFO] src.chatbot.llm_client - 💬 Gemini output: 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ê... +2025-10-13 01:25:37,093 [INFO] root - 📤 Gửi prompt tới LLM: ### Vai trò hệ thống +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. + +### Ngữ cảnh (retrieved from database) +Dưới đây là các thông tin có liên quan: + +(Đoạn 1 - score=0.929) +Thời Đại Thiếu Niên Đoàn + +Thời Đại Thiếu Niên Đoàn (, , tên thường gọi: TNT) là nhóm nhạc nam Trung Quốc gồm 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. Bảy thành viên trước khi ra mắt đều là thực tập sinh thế hệ thứ hai trực thuộc TF Entertainment, sau thành công của thế hệ thứ nhất là TFBoys. Nhóm được thành lập chính thức từ chương trình thực tế sống còn "Đài Phong Lột Xác Chiến" do TF Entertainment cải tổ lại. Chương trình kết thúc và nhóm chính thức được thành lập vào ngày 25 tháng 8 năm 2019. +Ngày 23 tháng 11 năm 2019 nhóm tổ chức buổi ra mắt và ra mắt với bài hát mới "Thông Báo Toàn Trường". +Fandom chính thức "Bạo Mễ Hoa" (tiếng Trung: 爆米花). Màu tiếp ứng là: Vàng, Đen. Khẩu hiệu tiếp ứng: Phá Thiên Hạ, Định Phong Vân, Thời Đại Thiếu Niên Tinh Kiên Hành.(tiếng Trung:破天下,定风云,时代少年并肩行). +Thành viên. +Tất cả các thành viên của Thời Đại Thiếu Niên Đoàn đều là mang quốc tịch Trung Quốc. Mã Gia Kỳ, người giành được hạng 1 trong chương trình thực tế ra mắt của cả nhóm, vừa là trưởng nhóm vừa đảm nhiệm vai trò vị trí trung tâm. +Lưu ý. + Các nội dung sau đây mang tính tham khảo, không tự ý sửa đổi, nếu sửa đổi sẽ được cho là phá hoại. Mong không sửa đổi dưới mọi hình thức, chỉ bổ sung khi thiếu, hoàn thành lại khi sai. Vì một số sửa đổi của một số bạn nên chúng tôi có thể không hoàn thiện nó như lúc đầu, vì vậy sửa đổi không cần thiết chính là phá hoại trang. + +(Đoạn 2 - score=0.666) +Tiểu Thời Đại + +Tiểu Thời Đại hay Tiểu Thời Đại 1.0 (小时代) là phim điện ảnh của đạo diễn kiêm biên kịch Quách Kính Minh dựa trên tiểu thuyết cùng tên của chính anh. +Bộ phim thành công về mặt thương mại và nhận được những phản ứng tích cực từ giới chuyên môn. Phần hai có tựa đề "Tiểu Thời Đại 2" được quay song song cùng phần một của bộ phim và dựa trên phần hai của bộ tiểu thuyết đã ra mắt vào ngày 8 tháng 8 năm 2013. "Tiểu Thời Đại 3" cũng đã nhanh chóng khởi quay và ra mắt công chúng vào ngày 17 tháng 7 năm 2014. +Nội dung. +Bộ phim dựa trên bộ truyện có tên là "Tiểu Thời Đại "với 3 phần" 1.0, 2.0, 3.0" xoay quanh tình bạn của bốn cô gái gồm Lâm Tiêu, Cố Lý, Nam Tương và Đường Uyển Như sống giữa thành phố Thượng Hải phồn hoa nhưng đầy rẫy tham vọng, toan tính, khát khao của tuổi trẻ. +Bốn cô gái là bạn cùng lớp thời trung học và bạn cùng phòng khi lên đại học. Trong trường, họ phải đối mặt với áp lực nặng nề, làm quen với cuộc sống sinh viên nội trú, gặp rắc rối với những chuyện tình cảm xảy ra liên tục. Sau khi tốt nghiệp, họ dần thay đổi và vướng vào những mối quan hệ phức tạp khác trong cuộc sống và dần trở nên nghi kị và hiểu lầm lẫn nhau. +Ra mắt và phản hồi. +Doanh thu phòng vé. +Dù kinh phí sản xuất của bộ phim chưa đến 50 triệu nhân dân tệ, được chia làm 2 phần, mỗi phần vỏn vẹn 25 triệu, nhưng ngay ngày đầu tiên công chiếu, "Tiểu Thời Đại" đã đạt doanh thu hơn 73 triệu Nhân dân tệ +Bộ phim đã thu về US$79.7 triệu tại các phòng vé ở Trung Quốc. +Sau ngày ra mắt, doanh thu phòng vé của Tiểu Thời Đại 3 đạt 150 triệu tệ (~ 525 tỷ đồng), lập kỷ lục phim điện ảnh 2D nội địa ăn khách nhất Trung Quốc. + +### Công cụ có thể sử dụng + +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": "", "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. + + +### Câu hỏi người dùng +Thời Đại Thiếu Niên Đoàn có mấy thành viên? + +### Hướng dẫn cho AI + +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": ""}} +3️⃣ Nếu người dùng yêu cầu lưu/tạo file → TRẢ VỀ JSON: + {"action": "create_txt", "params": {"text": ""}} +4️⃣ Nếu bạn không chắc chắn, cũng hãy gọi search_google thay vì suy đoán. + + +### Đị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 +... +2025-10-13 01:25:37,094 [INFO] rag_api - ✅ Đã trả lời: Thời Đại Thiếu Niên Đoàn có mấy thành viên? ... +2025-10-13 01:25:42,772 [INFO] rag_api - 📥 Câu hỏi: Thời Đại Thiếu Niên Đoàn có mấy thành viên? +2025-10-13 01:25:42,954 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK" +2025-10-13 01:25:42,955 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 01:25:43,924 [INFO] src.chatbot.llm_client - 💬 Gemini output: 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ê... +2025-10-13 01:25:43,924 [INFO] root - 📤 Gửi prompt tới LLM: ### Vai trò hệ thống +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. + +### Ngữ cảnh (retrieved from database) +Dưới đây là các thông tin có liên quan: + +(Đoạn 1 - score=0.929) +Thời Đại Thiếu Niên Đoàn + +Thời Đại Thiếu Niên Đoàn (, , tên thường gọi: TNT) là nhóm nhạc nam Trung Quốc gồm 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. Bảy thành viên trước khi ra mắt đều là thực tập sinh thế hệ thứ hai trực thuộc TF Entertainment, sau thành công của thế hệ thứ nhất là TFBoys. Nhóm được thành lập chính thức từ chương trình thực tế sống còn "Đài Phong Lột Xác Chiến" do TF Entertainment cải tổ lại. Chương trình kết thúc và nhóm chính thức được thành lập vào ngày 25 tháng 8 năm 2019. +Ngày 23 tháng 11 năm 2019 nhóm tổ chức buổi ra mắt và ra mắt với bài hát mới "Thông Báo Toàn Trường". +Fandom chính thức "Bạo Mễ Hoa" (tiếng Trung: 爆米花). Màu tiếp ứng là: Vàng, Đen. Khẩu hiệu tiếp ứng: Phá Thiên Hạ, Định Phong Vân, Thời Đại Thiếu Niên Tinh Kiên Hành.(tiếng Trung:破天下,定风云,时代少年并肩行). +Thành viên. +Tất cả các thành viên của Thời Đại Thiếu Niên Đoàn đều là mang quốc tịch Trung Quốc. Mã Gia Kỳ, người giành được hạng 1 trong chương trình thực tế ra mắt của cả nhóm, vừa là trưởng nhóm vừa đảm nhiệm vai trò vị trí trung tâm. +Lưu ý. + Các nội dung sau đây mang tính tham khảo, không tự ý sửa đổi, nếu sửa đổi sẽ được cho là phá hoại. Mong không sửa đổi dưới mọi hình thức, chỉ bổ sung khi thiếu, hoàn thành lại khi sai. Vì một số sửa đổi của một số bạn nên chúng tôi có thể không hoàn thiện nó như lúc đầu, vì vậy sửa đổi không cần thiết chính là phá hoại trang. + +(Đoạn 2 - score=0.666) +Tiểu Thời Đại + +Tiểu Thời Đại hay Tiểu Thời Đại 1.0 (小时代) là phim điện ảnh của đạo diễn kiêm biên kịch Quách Kính Minh dựa trên tiểu thuyết cùng tên của chính anh. +Bộ phim thành công về mặt thương mại và nhận được những phản ứng tích cực từ giới chuyên môn. Phần hai có tựa đề "Tiểu Thời Đại 2" được quay song song cùng phần một của bộ phim và dựa trên phần hai của bộ tiểu thuyết đã ra mắt vào ngày 8 tháng 8 năm 2013. "Tiểu Thời Đại 3" cũng đã nhanh chóng khởi quay và ra mắt công chúng vào ngày 17 tháng 7 năm 2014. +Nội dung. +Bộ phim dựa trên bộ truyện có tên là "Tiểu Thời Đại "với 3 phần" 1.0, 2.0, 3.0" xoay quanh tình bạn của bốn cô gái gồm Lâm Tiêu, Cố Lý, Nam Tương và Đường Uyển Như sống giữa thành phố Thượng Hải phồn hoa nhưng đầy rẫy tham vọng, toan tính, khát khao của tuổi trẻ. +Bốn cô gái là bạn cùng lớp thời trung học và bạn cùng phòng khi lên đại học. Trong trường, họ phải đối mặt với áp lực nặng nề, làm quen với cuộc sống sinh viên nội trú, gặp rắc rối với những chuyện tình cảm xảy ra liên tục. Sau khi tốt nghiệp, họ dần thay đổi và vướng vào những mối quan hệ phức tạp khác trong cuộc sống và dần trở nên nghi kị và hiểu lầm lẫn nhau. +Ra mắt và phản hồi. +Doanh thu phòng vé. +Dù kinh phí sản xuất của bộ phim chưa đến 50 triệu nhân dân tệ, được chia làm 2 phần, mỗi phần vỏn vẹn 25 triệu, nhưng ngay ngày đầu tiên công chiếu, "Tiểu Thời Đại" đã đạt doanh thu hơn 73 triệu Nhân dân tệ +Bộ phim đã thu về US$79.7 triệu tại các phòng vé ở Trung Quốc. +Sau ngày ra mắt, doanh thu phòng vé của Tiểu Thời Đại 3 đạt 150 triệu tệ (~ 525 tỷ đồng), lập kỷ lục phim điện ảnh 2D nội địa ăn khách nhất Trung Quốc. + +### Công cụ có thể sử dụng + +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": "", "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. + + +### Câu hỏi người dùng +Thời Đại Thiếu Niên Đoàn có mấy thành viên? + +### Hướng dẫn cho AI + +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": ""}} +3️⃣ Nếu người dùng yêu cầu lưu/tạo file → TRẢ VỀ JSON: + {"action": "create_txt", "params": {"text": ""}} +4️⃣ Nếu bạn không chắc chắn, cũng hãy gọi search_google thay vì suy đoán. + + +### Đị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 +... +2025-10-13 01:25:43,925 [INFO] rag_api - ✅ Đã trả lời: Thời Đại Thiếu Niên Đoàn có mấy thành viên? ... +2025-10-13 01:26:06,376 [INFO] rag_api - 📥 Câu hỏi: Tân binh toàn năng là chương trình gì? +2025-10-13 01:26:06,549 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK" +2025-10-13 01:26:06,550 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 01:26:07,216 [INFO] src.chatbot.llm_client - 💬 Gemini output: {"action": "search_google", "params": {"query": "Tân binh toàn năng là chương trình gì?"}}... +2025-10-13 01:26:07,216 [INFO] src.chatbot.llm_client - 🤖 Function call JSON: {'action': 'search_google', 'params': {'query': 'Tân binh toàn năng là chương trình gì?'}} +2025-10-13 01:26:07,216 [INFO] function_executor - 🔧 LLM yêu cầu thực thi tool: search_google({'query': 'Tân binh toàn năng là chương trình gì?'}) +2025-10-13 01:26:07,217 [INFO] function_router - ⚙️ Thực thi tool 'search_google' với params={'query': 'Tân binh toàn năng là chương trình gì?'} +2025-10-13 01:26:09,723 [INFO] function_router - ✅ Tool 'search_google' thực thi thành công. +2025-10-13 01:26:09,724 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 01:26:11,886 [INFO] src.chatbot.llm_client - 💬 Gemini output: "Tân binh toàn năng" là một chương trình truyền hình thực tế theo format "sống còn" nhằm tìm kiếm và đào tạo nhóm nhạc n... +2025-10-13 01:26:11,886 [INFO] root - 📤 Gửi prompt tới LLM: ### Vai trò hệ thống +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. + +### Ngữ cảnh (retrieved from database) +Dưới đây là các thông tin có liên quan: + +(Đoạn 1 - score=0.597) +Tổng động viên + +Tổng động viên là một khái niệm quân sự chỉ một hành động vừa mang tính kêu gọi vừa mang tính mệnh lệnh trong tình hình quốc gia đó chuyển sang tình trạng chiến tranh. Đối tượng của lệnh Tổng động viên bao gồm tất cả những thanh niên có độ tuổi từ 18 đến 30 tuổi. Tại một số quốc gia đối tượng của Tổng động viên chỉ gói gọn trong những binh sĩ, hạ sĩ quan, sĩ quan dự bị đã giải ngũ. Về sau có thể do yêu cầu của tình hình đất nước đối tượng của Tổng động viên được mở rộng đến cả những thanh niên chưa từng qua huấn luyện quân sự như học sinh sinh viên, công nhân, công chức, nông dân... +Người có quyền ra quyết định Tổng động viên thường là Tổng tư lệnh quân đội. Tại nhiều nước người ra quyết định Tổng động viên là Tổng thống đối với những quốc gia theo chế độ Tư bản chủ nghĩa và Chủ tịch nước hoặc Chủ tịch Hội đồng Nhà nước đối với những quốc gia theo chế độ Xã hội Chủ nghĩa do Đảng Cộng sản đứng đầu. + +(Đoạn 2 - score=0.594) +Chương trình Ranger + +Chương trình Ranger là một loạt các phi vụ không gian không người lái của Hoa Kỳ trong những năm 1960 mà mục tiêu là để có được những hình ảnh cận cảnh đầu tiên về bề mặt của Mặt Trăng. Phi thuyền Ranger được thiết kế để có hình ảnh của bề mặt mặt trăng, truyền những hình ảnh về Trái đất cho đến khi tàu vũ trụ đã bị phá hủy khi tác động. Một loạt các rủi ro, tuy nhiên, đã dẫn đến sự thất bại của sáu chuyến bay đầu tiên. Tại một thời điểm, chương trình được gọi là "phóng đi và hy vọng". Quốc hội Hoa Kỳ đã phát động một cuộc điều tra vào "vấn đề của quản lý" tại trụ sở NASA và Phòng thí nghiệm Sức đẩy phản lực. Sau hai tái tổ chức của các cơ quan, Ranger 7 trở thành hình ảnh trong tháng 7 năm 1964, tiếp theo là hai nhiệm vụ thành công hơn. +Ranger được thiết kế ban đầu, bắt đầu từ năm 1959, trong ba giai đoạn riêng biệt, được gọi là "khối". Mỗi khối có mục tiêu nhiệm vụ khác nhau và thiết kế hệ thống dần dần nâng cao hơn. Các nhà thiết kế phi vụ JPL lên kế hoạch ra mắt nhiều trong mỗi khối, để tối đa hóa các kinh nghiệm kỹ thuật và giá trị khoa học của nhiệm vụ này và để đảm bảo ít nhất một chuyến bay thành công. Tổng số nghiên cứu, phát triển, ra mắt, và hỗ trợ chi phí cho các dòng Ranger của tàu vũ trụ (Ranger 1 đến 9) là khoảng 170 triệu đô la Mỹ. + +### Công cụ có thể sử dụng + +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": "", "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. + + +### Câu hỏi người dùng +Tân binh toàn năng là chương trình gì? + +### Hướng dẫn cho AI + +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": ""}} +3️⃣ Nếu người dùng yêu cầu lưu/tạo file → TRẢ VỀ JSON: + {"action": "create_txt", "params": {"text": ""}} +4️⃣ Nếu bạn không chắc chắn, cũng hãy gọi search_google thay vì suy đoán. + + +### Đị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 +... +2025-10-13 01:26:11,886 [INFO] rag_api - ✅ Đã trả lời: Tân binh toàn năng là chương trình gì? ... +2025-10-13 01:28:39,563 [INFO] rag_api - 📥 Câu hỏi: Ca sĩ Mahola là ai? +2025-10-13 01:28:39,736 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK" +2025-10-13 01:28:39,737 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 01:28:40,838 [INFO] src.chatbot.llm_client - 💬 Gemini output: Zolani Mahola là một ca sĩ, diễn viên người Nam Phi, ca sĩ chính của nhóm nhạc Freshlyground. Cô sinh ngày 19 tháng 7 nă... +2025-10-13 01:28:40,838 [INFO] root - 📤 Gửi prompt tới LLM: ### Vai trò hệ thống +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. + +### Ngữ cảnh (retrieved from database) +Dưới đây là các thông tin có liên quan: + +(Đoạn 1 - score=0.811) +Zolani Mahola + +Zolani Mahola (sinh ngày 19 tháng 7 năm 1981) là một ca sĩ, diễn viên, người Nam Phi, ca sĩ chính của nhóm nhạc Nam Phi Freshlyground. +Tiểu sử. +Mahola được sinh ra tại thành phố Port Elizabeth, thủ phủ của tỉnh Eastern Cape, Nam Phi. Cô lớn lên gần phía Đông London ở Bhisho, và theo học trường trung học Trinity (sau này đã sáp nhập vào trường St Priic's Priory). +Cô làm nhân viên tiếp tân tại Makro vào cuối tuần và ngày nghỉ học. Cô ở lại với cha và em gái của mình từ năm 1999 đến năm 2001. Năm 2011, Zolani và Freshlyground đã có một buổi hòa nhạc miễn phí trên phố Ntshekisa như một phần trong chuyến lưu diễn kỷ niệm 10 năm hoạt động của họ. +Sau khi học kịch tại Đại học Cape Town, cô được chọn đóng vai chính trong bộ phim truyền hình Tsha-Tsha, phát sóng trên SABC 1. Mahola lồng tiếng cho nhân vật Zoë trong bộ phim hoạt hình Zambezia (2012), và đã thu âm bài hát "Get Up" cho bản nhạc nền của phim hoạt hình này. +Vào tháng 3 năm 2013, Mahola đã công bố lần mang thai đầu tiên của mình. Vào ngày 8 tháng 8 năm 2013, Mahola sinh một đứa con trai tên là Zazi Bastion Mahola-Klemp với Nicholas. +Sự nghiệp âm nhạc. +Mahola và sáu nhạc sĩ khác đã thành lập Freshlyground ở Cape Town vào năm 2002. +Sau khi Freshlyground xuất bản album thứ hai của họ, Nomvula, vào năm 2004, The Sunday Times mô tả Mahola là một trong những ca sĩ trẻ tốt nhất và đầy cảm hứng nhất của Nam Phi. +Cô đến Cape Town, Durban và Pretoria với ca sĩ nhạc pop người Anh Robbie Williams vào năm 2006, Freshlyground biểu diễn như một vai diễn hỗ trợ của Williams. +Cô hát trong album phòng thu thứ ba của The Parlotones, Stardust Galaxies (2009). +Tại World Cup 2010, Mahola, với Freshlyground và Shakira, biểu diễn "Waka Waka (This Time for Africa)" trong lễ khai mạc và bế mạc. "Waka Waka (This Time for Africa)" là bài hát chính thức của FIFA World Cup năm 2010. +Tại Lễ trao giải Phụ nữ của năm 2011, được tổ chức vào ngày 25 tháng 7 tại Johannesburg, Mahola là một trong tám người phụ nữ được vinh danh bởi hạng mục người phụ nữ quyến rũ Nam Phi. + +(Đoạn 2 - score=0.631) +LMFAO + +LMFAO là một bộ đôi song ca electro hop người Mỹ thành lập vào năm 2006 tại Los Angeles, California, bao gồm rapper đồng thời là DJs Redfoo (tên thật là Stefan Kendal Gordy, sinh 3 tháng 9 năm 1975) và SkyBlu (tên thật là Skyler Husten Gordy, sinh 23 tháng 8 năm 1986). Họ là con trai và cháu trai của nhà sáng lập hãng đĩa thu âm Motown Berry Gordy, và hai người có mối quan hệ cháu và chú. Âm nhạc của họ thống nhất 2 chủ đề chính là tiệc tùng và rượu chè, và nhóm thường tự cho phong cách âm nhạc của mình là "party rock". Cái tên LMFAO là một chữ viết tắt từ "Laughing My Fucking Ass Out" và được phát âm từng chữ một. Nhóm hiện đã tan rã. +Giải thưởng và đề cử. +Hip-Hop Nation Awards +Grammy Award + +### Công cụ có thể sử dụng + +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": "", "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. + + +### Câu hỏi người dùng +Ca sĩ Mahola là ai? + +### Hướng dẫn cho AI + +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": ""}} +3️⃣ Nếu người dùng yêu cầu lưu/tạo file → TRẢ VỀ JSON: + {"action": "create_txt", "params": {"text": ""}} +4️⃣ Nếu bạn không chắc chắn, cũng hãy gọi search_google thay vì suy đoán. + + +### Đị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 +... +2025-10-13 01:28:40,839 [INFO] rag_api - ✅ Đã trả lời: Ca sĩ Mahola là ai? ... +2025-10-13 01:30:04,067 [INFO] rag_api - 📥 Câu hỏi: Bóng đá Malaysia sắp nhận án phạt nào ? +2025-10-13 01:30:04,243 [INFO] httpx - HTTP Request: POST http://localhost:6333/collections/text_chunks/points/search "HTTP/1.1 200 OK" +2025-10-13 01:30:04,244 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 01:30:05,060 [INFO] src.chatbot.llm_client - 💬 Gemini output: {"action": "search_google", "params": {"query": "bóng đá Malaysia nhận án phạt"}}... +2025-10-13 01:30:05,060 [INFO] src.chatbot.llm_client - 🤖 Function call JSON: {'action': 'search_google', 'params': {'query': 'bóng đá Malaysia nhận án phạt'}} +2025-10-13 01:30:05,060 [INFO] function_executor - 🔧 LLM yêu cầu thực thi tool: search_google({'query': 'bóng đá Malaysia nhận án phạt'}) +2025-10-13 01:30:05,061 [INFO] function_router - ⚙️ Thực thi tool 'search_google' với params={'query': 'bóng đá Malaysia nhận án phạt'} +2025-10-13 01:30:08,333 [INFO] function_router - ✅ Tool 'search_google' thực thi thành công. +2025-10-13 01:30:08,333 [INFO] src.chatbot.llm_client - 🧠 Gửi prompt tới Gemini... +2025-10-13 01:30:11,917 [INFO] src.chatbot.llm_client - 💬 Gemini output: Bóng đá Malaysia đang đối mặt với án phạt nặng từ FIFA và AFC do gian dối trong quá trình nhập tịch cầu thủ. Cụ thể, Liê... +2025-10-13 01:30:11,917 [INFO] root - 📤 Gửi prompt tới LLM: ### Vai trò hệ thống +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. + +### Ngữ cảnh (retrieved from database) +Dưới đây là các thông tin có liên quan: + +(Đoạn 1 - score=0.682) +Đại dịch COVID-19 tại Malaysia + +Đại dịch COVID-19 đã được xác nhận đã lan sang Malaysia vào ngày 25 tháng 1 năm 2020. Tính đến ngày 31 tháng 7 năm 2023, Malaysia có 5,120,581 ca nhiễm COVID-19 và 37,164 ca tử vong được xác nhận. Sau sự gia tăng của các ca nhiễm vào tháng 3 năm 2020, Vua Yang di-Pertuan Agong đã bày tỏ mối quan tâm lớn nhất của mình đối với bước nhảy vọt số lượng các ca nhiễm bệnh. Các biện pháp tiếp theo sau đó đã được Thủ tướng Malaysia công bố để chống lại sự lây lan của virus trong nước thông qua một chương trình truyền hình trực tiếp trên toàn quốc vào ngày 13 tháng 3 năm 2020. +Với sự lây lan của virus vào tất cả các bang và lãnh thổ liên bang của Malaysia vào ngày 16 tháng 3 năm 2020, chính phủ Malaysia tuyên bố rằng họ đã quyết định thực hiện khóa cửa một phần trên toàn quốc (được gọi là Lệnh Kiểm soát Di chuyển) từ ngày 18 tháng 3 đến ngày 31 tháng 3 năm 2020 để hạn chế sự bùng phát số ca dương tính với virus này trong nước. +Đặt tên. +Bộ Y tế gọi căn bệnh này là "coronavirus mới 2019". Một số phương tiện truyền thông gọi căn bệnh này là "coronavirus Vũ Hán". Vào thời điểm dịch bệnh bùng phát, truyền thông Malaysia gọi nó là "radang paru-paru Wuhan" trong tiếng Mã Lai. Sau đó, một số phương tiện truyền thông đã đổi tên thành "radang paru-paru koronavirus baru" trong tiếng Malay. + +(Đoạn 2 - score=0.647) +Sân vận động Sultan Ibrahim + +Sân vận động Sultan Ibrahim () là một sân vận động bóng đá ở Iskandar Puteri, Johor, Malaysia. Sân được đặt tên để vinh danh người đứng đầu hiện tại của bang, Sultan Ibrahim ibni Almarhum Sultan Iskandar. +Kể từ năm 2020, đây là sân nhà của Johor Darul Ta'zim thuộc Malaysia Super League. Sân vận động này đã thay thế Sân vận động Larkin, nơi đã từng là sân nhà của Johor Darul Ta'zim và tất cả các đội bóng ở Johor trước đây kể từ năm 1964. Tổng chi phí xây dựng ước tính khoảng 200 triệu MYR. Sân vận động có sức chứa 40.000 khán giả và được khánh thành vào ngày 22 tháng 2 năm 2020. +Thiết kế. +Lấy cảm hứng từ lá chuối, ý tưởng và thiết kế cuối cùng đã được Tunku Ismail trình làng trong một buổi lễ được tổ chức tại Iskandar Puteri, Johor. Sân vận động được xây dựng trên khu đất rộng 140.000 mét vuông với tổng diện tích xây dựng là 70.000 mét vuông và có sức chứa 40.000 người. Đèn LED màu đỏ, xanh lam và trắng (màu của hiệu kỳ Johor) được lắp đặt bên ngoài sân vận động để chiếu sáng vào ban đêm. Sân cũng là nơi đặt trụ sở của câu lạc bộ, và có trung tâm tập luyện và một cửa hàng lưu niệm. +Một thử nghiệm đường hầm gió tích hợp cho thiết kế của sân vận động đã được thực hiện bởi Guangdong Provincial Academy of Building Research Group, một công ty con của Guangdong Construction Engineering Group Co Ltd. +Xây dựng. +Với chi phí xây dựng sân vận động mới ước tính khoảng 200 triệu RM, Sultan của Johor đã cấp kinh phí cần thiết cho công việc xây dựng. +Country Garden Pacificview Sdn Bhd đã được giao trách nhiệm xây dựng sân vận động. +Vào tháng 1 năm 2017, câu lạc bộ cho biết công việc xây dựng sân vận động đã bị trì hoãn trong vài tháng và sẽ được hoàn thành từ tháng 7 đến cuối năm 2018 sau những điều chỉnh, thay đổi về vị trí, kỹ thuật và thiết kế của sân vận động do Tunku Ismail thực hiện. + +### Công cụ có thể sử dụng + +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": "", "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. + + +### Câu hỏi người dùng + Bóng đá Malaysia sắp nhận án phạt nào ? + +### Hướng dẫn cho AI + +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": ""}} +3️⃣ Nếu người dùng yêu cầu lưu/tạo file → TRẢ VỀ JSON: + {"action": "create_txt", "params": {"text": ""}} +4️⃣ Nếu bạn không chắc chắn, cũng hãy gọi search_google thay vì suy đoán. + + +### Đị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 +... +2025-10-13 01:30:11,918 [INFO] rag_api - ✅ Đã trả lời: Bóng đá Malaysia sắp nhận án phạt nào ? ... diff --git a/outputs/file_20251012_183627.txt b/outputs/file_20251012_183627.txt new file mode 100644 index 0000000..2bbeb78 --- /dev/null +++ b/outputs/file_20251012_183627.txt @@ -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. \ No newline at end of file diff --git a/outputs/file_20251013_011905.txt b/outputs/file_20251013_011905.txt new file mode 100644 index 0000000..e2c26df --- /dev/null +++ b/outputs/file_20251013_011905.txt @@ -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. \ No newline at end of file diff --git a/src/api/__pycache__/chat_api.cpython-313.pyc b/src/api/__pycache__/chat_api.cpython-313.pyc index cc4538c241586aa697aa38f446bc0b8a5e48ac31..2d46938600bcf869fdae7aaaf66261bc23b41877 100644 GIT binary patch delta 409 zcmZouZByg@%*)Hg00bdVUT0j`$eYQ*#R=p!1M%l;n;SUt87D916ldkpXE0@&JeN~e zkT;mmlnJDPfgzY*k1daB@@fuwMuExvoMJ4&jKPAF^|?G5g(g>W$ubI0p3Y^@EE38x zxtdFoSu|K|@>ecJM)Aqw+^?-~u@~o;7A2<^-{L4OPECm~Pfc>uWGhkt8e3!pByRC0 z=jWBAR+Pji=a=S{6d8jgOhAO`W_g|%MlMGn+ZTw7!zZ`$nlp+{-pgyl`hkr>Omgxc zUR~82;69(8ILe?xvDWA;Z<_cXFj6O3u4=-x+*dsRb&OSkE$`dYEJ&eH=ETJXm#P_ zS^Or9mYdJ>b1`ay+*4!?B0y3_${<1qL`VaPTO2mI`6;D2sdhzio0$Z?86{;I{TOFN Ye`WyDpP3n$gg-EWm>=9GrwJ(l0HK*_ZU6uP delta 301 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 KzYqx1`TqbQjlIzT delta 939 zcmX|9O-~b16uocWOgl651KQFw$VU|fN3kjbAqvK76jPw66BY(cB`_^DW!k)H6E+Gl z2HlWIUWn+5KVaCBxDbAURZJu!lDN^Zs}h4B8{cb%yEymUd*_}v@8rE~eC77+x!o$T z7=QeAsv9qQLN|I@wFEloXaGnhqnJQ&m~&x3j7da_Ifx?+Avgv)t^-}{61WBcai)+a z?Q$BYPf<6zOy8i6u%lDdopo?uB(g3OC8cPptqcwhUCpEoGr#MknxLV1stZrl6lgeJ zIKu>J2zNMSMl^{&#Ap2qr`Q%PBa;JHYxzPeg zVU!w^9P$ef(68dlxB#|Z-F3<<+sgq&z z%j*~E@MOVEh4aQtKBC%Ch%kZCeq%aqrVmZJOEXmsvvJKqxDR%9$}kNwo;MN`Su@W} zv_-ydOIDtwbGCAAX2Qtj(^=D&awKi$HIaDP9trSenHW|sYg&el3`=C4Fbh&$9v$e~ zzQdy<2R$#Bu*Mk7K}nXBAie1d%x<-Jt`4mZJ?nYix7hd5-}=tqy6HbrbVb()gJ0X; z&>jd%aNAW=3|v`1x87XzUH{@ax!^~6T@=agTKI1a$c zh;^F5JZ!aguJ?WiN$Oa^C7#ywpFEX3iWFS7O2E>Jwa*hND{&D`wuP)kFM3+0T&%Rc z7MX}lWQoDROr}%h@NX(-wy}>Y+ZBs@*?J`7BZNMI@)?@HLC?OU L1_gfrgYxD-0t(4%9%>aurqH@(|E_M^5E z+?poR#1O1&Oo_p?G^A1?EmmT=3z1|?;}4AxO(f_K7KuMJ#*JY3VWPgXcUSI!sF&RA z?Cd-5JoCKIGxLN0@1~;LUN1$!Z~L#mutdTm$AeJ*yM|i~QlRRq$ABP+~1S*gzIm3qnc2WD@KCyN^ARQsh zLZWuVUBXdInKqlZ#k3Qvyx)4NOlm=M{7jNH+8eHQ_tSE zv&R9gtA~Wv-Q$GTv!#G{^|4=hhms4C8S)o5d@Vk3T7HpIr^{lkZ2`LQB46PMQM>?`}zf;KNj^LRH9<BA%t$e^)A|}Ux2y{upHw1F%qcFG!$$gEfiejnQURx zW7=!>Mm0oHn*)duqqH3DlZRx#937Ia|H7cOEk^C6QJLIDonG<+l82OrIeU|UDKL|Uu5(&&Ik#l-s}`~O5$HZ| zZOkzhHhcGf0K89T52~!0Uy+%baV7S!g=}jUnFTEE%q($WJkSXgH#r9dVJ@Er1+e<` zFai}E%F=*pYw1J#g$&3d)I1mq&e|K9sU% zC8`w{?gQ9m1Wp+qb}pc2c2ml&m4Kypj1(9Jf1iRdIB1hI3OI>s%vdIU^kyz>nM*4y zYXFeD_NA>z1^oh}7K@lya4e)8f;&63k|%r5JT#MaSf(!}c6lDbKNmI7JbTYSIrf6G z;w7kBOm~h2senxk!xlvkGy%7^ShCh%A`3!H>Wf4rM&uIsq`mGGl~`1|?n*#G60+om zV1%?EOST3(p(_nSGeX>`T=QM!rmK~k=4-#He0t1lI!bCBS3IlDb)FK(nA0rRww2a5 zlwBdpOu}AHnl8dtI8q3Guhn;txJ|v0ArcbEh`=Iz#F(S8TNnH6=;-Q;}zynL>~Byj&y z*&XX?wv*eO6_b)lz+-XerSc1VtnL?E?&1;g5!jdLA_y1J8E}fN+Mi|BRc(08`rw7$ cbx(6#hyXb)Vu!Y=yr%dNNml$$;MNNI2mO-hw*UYD delta 1267 zcmYjQU2NM_6!uMQH+2)YT}ytNHoaR5UAnY%Do7}xX;Z3cWn-GOlm{lQ%aSUNNbKU+ z)yhjIAw&?2sk%ID8k@RR)6h`WRN2I4Yofx_#KRC&ATa?CwFEru$*@5m;asO3vE*~k zJ?EZ#&-u=G|K0oRZt+PVATaP1KKVmRb*_rj9#0+-#S`hsO8UHYQ^s;$D%j=+vxIm~ zRZ@~|eu&Gqxr}qRd0oz9O|ouLRgkf!Dqzh@wsC`|+2#t)+QyPB;rk1=`5we!g^RX% zxiE^4+QwHpe!G!P<2UGuE4WB6(-T;-mgcc+)eCqiZQUy1tn{FkA@|xx#)t5nwYZ%L zx0&bR*82;JbydcaVjEv(aao_oMcp=*p(x3^St7FcGrSwxJj*zKCbnmj;bVg1VFL${ z<7HX{Kl*wcu^wk1BNQXu{0nGZ`Nj~Raj#VHu#fuHlkxUesuXwgDw(6MUi>Om7`HU7V3e1+q{04!v9# z+s>gx0?I6ZifGsW_?eE_qpsv{!QKle*Y_Uz{>``6gD0wO8EFn}k>F(Fg|*U&Rp%DSdQ2j+PgD-1pZ5YQGc7jVwHE9YTv8mexjAEJ>< z07=d^z5w(j1s|A90(j~F*|ZHIQ0|HP0Coo0 z0=uy0O5l{zL}s#cM^``H+2Cmt%eRICGp2i9X=duPi!s!;3fBQ0=)@^?T~ahLoIGsD zlOy@d_`>w4;H=OhYo!Nsz}fVwlW)_wPN*ZgLDlX}z+s95D0hG&k(IrSj9 z5{@36q&_zg0PAPpYb7P87iadVhhYh1Vaibuju#>HC+k7%rjJ3fO}>Co@Gpi+H~Bxb Cp3Thw diff --git a/src/chatbot/__pycache__/rag_pipeline.cpython-313.pyc b/src/chatbot/__pycache__/rag_pipeline.cpython-313.pyc index 532bbdabd8da6a115660360d03dcd5dd0af604db..6ddc3459beeeb45a0ad0f3fe32190a914b3344ce 100644 GIT binary patch literal 4561 zcmb7HYiu0V6}~e&`&FsfhZ2`K7$Lljg)~O_5d)Q)&_)T5ieUdJXpx{&f7n{6mr6?1K*MF#6UZ=OBw&qi-8>4Z5-0=-Y=pgI??%^kE-| zHls$wJL(Yc+({RA#1)ZBp6xk`P_zr7NJS&sSyBa8@h)5)sR{*3gmq1%dM8zquZj3! zO?RY*_mH13*X58w_X6GbZF;OCRuQO74FfY9>Kho9lTut(B<}Os7daSnZQTBpB5HC% z;rgR-n2G^uAd!g0C2j%;$D-nKpd3Dt=txFYmBZ2mh_hc3eF7Gp*(-~hZs}8IbY@sq zwE(3%hldaL$7M+|1|dnqvNSDWnDLC_#8gsyJSE4+4MK&v_r8;on9>qBK z&%kGm*dlgvFuW~d?RN0?I`lMy9lRsrjIiAngU=ap@odD+yWj)vh&AHrrin~9%ex8x z(-ijd9_)*G13ulg?l8af0+(1a5p)qE=C;6h=ciCzKx5@O)7)VcFVn`zCPrB|OQel5 z<}`cjX3G*|<{6Y730Ah75-}8@Mgk5@2)YxSroav|_9hK$2*--%z&1j(#N$(#?5I~x zl>9=FOyf{Gqe}4!m1xfCd-{7KVO7FvgdZN@M*Aas6Jlx#%&taoG$uroN#UW0ip7XH z8P$#@G=rKf`Cl+OqdNpaR%A^O^iAsyGiXwMu!~BfVHVxm(s-#p&HU1PYg^l;M4I`? zQGcs?>-ir5$#JWpGuzOWW+{+34|5`{8))a081O4(>0c@B+<1QZd2SNa{dp<~3l2?Y-U1{$ zmR~*_<=&sqFTJe=xsm+RABpyHHhf1eqO}7$BRkx&YTz9L96c!~_(4>=^O5&L- z6wR_U; z0z+-0a+G`4Q$Q9kRj?u2JL`qM&*-~n-34UxPz9f-f+--6llkT=JE|O@zR??c)e6w& z`7M2I%qxDV-gEl;b~wHy_@`U7L{d1eSHLZmI3XC3E=938sk>B3Oeo_5xv<1CRDwu3 z%(p?XL71Zu5I2^41>(^vp4vq#R*+Z^8zs>YS$9J@T4M&fVgo5+p$#SE+CYF|cwHca zl>5IqLL}%mlrV&O7_Pis7R3>Egeo^o!;rU_0I{2DDNPfof}Dj>oDU*}{SG;+DW|x? zE>kjk4WY4WbCPJnne?1f9g0@BV9fsPHOoH|&;wlF(JAjK$Qm$@qM3R=sT zgd45~_eg-kt-u9Y9t&B4kVt9Cl%{(VSdPg`G+w-7F<1xb41poMopdb_hz4fiZDi;! z-M$7;>7J;OY62{&skoLRvCXoD%a<1ab@Lo|IFX3=Z~?1siz?~~3F~f%VzRIRH~P44 zD}k831bw=LkV)JJyqK({+f*a|F$nA zSc#ll>1SMIx6+U6Zb+ix1dCJn`(%MmQZ*4N&$mDu!$Se7Bv0_j zBx_Phf;o%+=Bqw;`poGyU-PQ3dGW~=U-OD@cbWkVuJW%{Hm_DTuT^%gR(9TK%~tlO zZ9whI6p*HN&e+C2rt z)Us(;!HYIGU3_l-IgqLiuGMzmXiU3vRh!awkoVP{OPon8NLg=l+VYvp`(n?6_?zyv z>Xy~&mMi@ABUg{yII`BUd$nVCZb#RRfj8V)|DiYBR~kRCy=QxO`UCIz({uRZ^!)U~ z#IH}k?oJP^_z!*NtIKiCYuv6?Zr9D$4`T1dK2%no`bl!))|tvJ z`&lLvtbsbf7=C8>p5c>*myQG~Nam|VrzFUHSwWaejHlwH?-n4WqVdvl*;G!S8OndlZ-(ePtrNNy>-pU`Rq0eo4LMIowz10PFi zkTnQb5hj_Mkt|^HhA=Afj#1N4{Q`YOGuG~cyUNO3Q40u~cdL!&!^10~C;s%L(O-!O z|0Yv~7S#FB%5Y=_KYJSmtn7kVAkCE~qj~qR(X1R#7;OQeetLk0<^VlJnuCza8lzGA zHktbo{WXy`n2Smh{m7Y3MP)@0QvERO6XkIZ03itvS6SrZkSWG8zu&_h$hTb> zlZ2OX@yv~*H4+d%yl#S}*G-Uwx@bImi!Z5~^^

UL;k}>(b(rrjoqDF|9u|Og-I~P2a}EK^UANF3 zj}~3C7e`}^Eq z!U&BRcluP;vgH$B-Pvbn59AyNXZvz&)w%XF?Q_qZeQ>t_6ULctn^PAanSb^p=6*72 Uof8)V^UBA}{RJ0d>I|L!1D54!QUCw| literal 2077 zcmZVrq-uX<%~g0|)jZ2u4qZKp~K}5WMvak&Tn>-f~^I?wzx{ zXbqD3VveXmVj7zelcEo5;sYj{VEdp+eejW{n3fGBTue-OYwZKCezSXbJ?oe3H}n0@ z%=fpO9UU?PkZ%1w`3FYmFHST8XdgBgV3|bnDGn!A9IHjI!OPopD&0I?1w#yZuG z{N4tmZBaXq2o3u#H3dFp;50lEL|?k2lc`G4EgLzR01T77;V{#>rY2BYt~2Ad}PY;b3lGED`h<>1t>UE;9%!Z)N` zU;N74QhLfCn-M)cZrYBAPnx>piN~$!jNrvuXK|u+OCUP_b2B}NSAb&;V)H0$W|4}R z0BH>=0uz-`KVn#kDWV!yuqr8GHKK;q=;2TpbPr2BeV6fNfyI;vlS|Qz?8Vzc`g8|q z>5b(0r|`6h@NK>WXck>;$(o5SpmK|Mkssnio0MDoaItysR*`^h%c6ub!nusZBB1NV zSTkQN%IVIkpC&pH=RCN|j#mBzTw0EWH&I(Bl#CBm4tw zW>Ep0N+qUN^kQ31KNN7ny5$$@WYGqmOI81!x96Np|17e&hOTa%ed@H2?PfovTV>RC+q{8ygK2v2z>Wa7LAEp5tx8k8vw#u)4q6lRZBZb*`pUNV{O z@HuEzC}q5HJrTx|Cz_VSctJ4kj}b2Dq11LX$JD9jz~q{69mA&dd*t864+Z0Y5Fc?* zb{5db=%0`n-n${k!t$KHj$rw2&|mHr@4fh&c<<#Ot!JF;^8#1g3xB|rW?GcGX&_J3 z7w%YO!t@99S#7dX)<_vjLNm#g$)%sH(lM78-3T9_ll8@$X3k9j9h8+nNpt5iJG=;` zkW2`K48qQf zHb;dQ_WuJxaHGtys};*Oyl@r%3J%-Pt$3D$B8$EjB=IQYQ;`oUdjin9Cqb5w@=GcPX}0}vEqb5w@=GcPX}0}#ZuJln`^#RmXCl?AZ? diff --git a/src/chatbot/function_tools/__init__.py b/src/chatbot/function_tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/chatbot/function_tools/__pycache__/__init__.cpython-313.pyc b/src/chatbot/function_tools/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dab2fc78d90428ff396eecd35516d3169b4038b9 GIT binary patch literal 171 zcmey&%ge<81U7ZAGeGoX5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~iCvsFxJacWU< zOt6o;LV$COOMY@`ZfaghaZFKSdVFF*LA+5+aZz$iazvPcP&En78s_p*(PqA)R~E22VD=M z7tuZJ!P|;>SVUA1g!bTJ#ecxtoD3dByi}^-(Kl^ZG?4d^_ug-Q@6F3#a?(Zc+`Iif z`fVcgMLQc$VQdTzfU$yn^~1kGSa@+}a1&-1aSA2%Mnk+xsEIFYwaP zbF>$f{bJx8G5yjGbj3b8LPXs4%Y35gdK0R2nu_pJnzp{#5X|m?aL*+&$ohzfGE$<-O;)Dxut~xfg~3?O>KB_``DEyPM%oly{%Scrow3 z$1=)$FDSmYzKY{^rxmutMB-?DwT;6YA7&oVtwkO~n=%!Xs)9}pf5t#19=;P~;Pl36nvc_;D@4k|_k>$N7 ztmYoXbzu84fhcWbk@uEyb@4joP2ph))G-v@AC@iyM3m;;cMQ+XU4X(%9NNU!pygq! z5?_VEJ;Q=#crl8HHE$TGe<$bG*YpP1B5W-vXLOK&1w$CC*_q?PWf5`__;Y9R#hKtt z$}&AK5pdchw9_Gn0>N3pA}SlH3>LD4>FEpyCxX8-taWax;&{Aj^Tuu0#$PtWX`%>X z3H3Lja%BJ};*%iiUPYA%(HB67atUdt^I1#V6+*6Nv^9=Q5wZ|-AzN`0CMkrAgv_Um z5MBlpWx-x6$e~G+N~#G82$~Awbka6Lgbp%7rQ~q~HbG};8hly{O_MttLKli- zAnv0drd25QD`?BEr85uqezJG=9kktBnteR|$*z7bO!rNvP+2`ZK%fQ|a<&fihY_g8 zt9!PxXoGS6myH+=kNN*-RXQ>1{v_WGd3w9VRuFy5FpRHg?@xQ$s5}tA5U3&cA8*b> AA^-pY literal 0 HcmV?d00001 diff --git a/src/chatbot/function_tools/__pycache__/function_executor.cpython-313.pyc b/src/chatbot/function_tools/__pycache__/function_executor.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7fe9585623e56b72a768388677d09a9439f16a58 GIT binary patch literal 4595 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 literal 0 HcmV?d00001 diff --git a/src/chatbot/function_tools/__pycache__/google_tool.cpython-313.pyc b/src/chatbot/function_tools/__pycache__/google_tool.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a8643a19da6197212d0d501e0029f13c0bb52897 GIT binary patch literal 7490 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 Pb&{Gs_a#y*s&xJrKPl~* literal 0 HcmV?d00001 diff --git a/src/chatbot/function_tools/__pycache__/router.cpython-313.pyc b/src/chatbot/function_tools/__pycache__/router.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..deb97cf54c3a2fecf21c9ce34a24b4736fd04993 GIT binary patch literal 4629 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 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ụ + """ + ... diff --git a/src/chatbot/function_tools/function_executor.py b/src/chatbot/function_tools/function_executor.py new file mode 100644 index 0000000..b1097bd --- /dev/null +++ b/src/chatbot/function_tools/function_executor.py @@ -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": "", + "action_result": "", + "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 diff --git a/src/chatbot/function_tools/google_tool.py b/src/chatbot/function_tools/google_tool.py new file mode 100644 index 0000000..baefd93 --- /dev/null +++ b/src/chatbot/function_tools/google_tool.py @@ -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

+- 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": "", + "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": []} diff --git a/src/chatbot/function_tools/router.py b/src/chatbot/function_tools/router.py new file mode 100644 index 0000000..0ecee9c --- /dev/null +++ b/src/chatbot/function_tools/router.py @@ -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": "", "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 diff --git a/src/chatbot/function_tools/txt_tool.py b/src/chatbot/function_tools/txt_tool.py new file mode 100644 index 0000000..83ef405 --- /dev/null +++ b/src/chatbot/function_tools/txt_tool.py @@ -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}" diff --git a/src/chatbot/llm_client.py b/src/chatbot/llm_client.py index e6c1909..3c8e180 100644 --- a/src/chatbot/llm_client.py +++ b/src/chatbot/llm_client.py @@ -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)}" diff --git a/src/chatbot/prompt_builder.py b/src/chatbot/prompt_builder.py index d88a58f..9305ca2 100644 --- a/src/chatbot/prompt_builder.py +++ b/src/chatbot/prompt_builder.py @@ -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": "", "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": ""}} +3️⃣ Nếu người dùng yêu cầu lưu/tạo file → TRẢ VỀ JSON: + {"action": "create_txt", "params": {"text": ""}} +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 diff --git a/src/chatbot/rag_pipeline.py b/src/chatbot/rag_pipeline.py index 81ba8a2..65a5a1d 100644 --- a/src/chatbot/rag_pipeline.py +++ b/src/chatbot/rag_pipeline.py @@ -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: , " +""" + + 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) \ No newline at end of file + 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"]) diff --git a/src/core/__pycache__/config.cpython-313.pyc b/src/core/__pycache__/config.cpython-313.pyc index 9b01531e1b2043cf0ccf2f215e92657bde88e100..ac1a5e2429b94156588abc0d01c7beb6fb959220 100644 GIT binary patch delta 1309 zcmZWoPfXiZ9DWXA3WPWb=1&s-|0g9l2_$jcP81hH$qG%H*8-i8GMhNW1DxzRz~BOB zE|B^_x6=+gZ0eztdg*bKI`vAHZA3oow8OMrrd8UsX@~h4h$?2u-}}Aq`+lE)&$6Go z&-=(XHd`amuU~)uEhjijq|5ZVxitEh`hak$w+O2VHK|ErNKFtPyAw8tP1p+nB3JFD z4?5Iui59}48{GC**j5vrhFI4QUFtu?80UtbnnbNqDepzb+Xy?t#KoQhOF%LC^_v3o z3;=nM8Y`W6T1GGJ8GV99Hvo?=cvEUzR~<>yA`5^O^y+;wK<#)WTPN(gdamKJz^B)tb8v$1ohJ zIeIW-ABEl;(`!6{y8*|ps9JlUA3)y&0`oaX2GB4JdH@<(Hz}rz!)Jka z;>Dk)Tf_kgW2s~;mFAN1^u5?)Z8{hd`Jfoih>Q@42BM+Nq!z|!G-}xYITxayq2q3(w8qtt5*t}Qc+)uNGfRcoB3T4 zp*Gy?!r;LP_1VolU%Wm{57v7zTug+kF#yyb8rd6oFfXAI83q4|0YvMlS>D~sXSO%R zDa7epEGXS3m9Yxps*^f(KXIRRjGc|=E6&0p`8KmuaW3mj6L}mzil1zp4MZwU%wdE2 zi8XOhwc3u^BerU8t(y1V)Ymnc4lTc1fS-9Te<{C&-_u;BIq@CMec^n){LS*~#}#Vu zBzVe{7?nM}UAp}vHCU#(a&zL)UZq-3*(dB-m;bA_3Kc!4rpnY*g_{0)u1vjm1{_t= zaV#B4Pi>#=R7m=q43)`HrGbpR94?d7hgS6$>oeEie*^)QB$OuoY_Gaqahv(o&3+yK E1L%cI2mk;8 delta 458 zcmcbvI75=}GcPX}0}v>Fdz^8JZz7)r(f8~1b|YL*D{JQ3xZWmzRk!zS%68IS3Fn(rn*QNWE8g~ z+-MOHpI0ha8m>YVB*HHfEDIMA0~=>97c37Gf@>GoX9!jZWGs>hRxFYXRw|MTRxXkb zRwiAtTjO<7b$`WkjX{LK;jmMO>TZlX-=wLks*)^ih|-O#>vb0k|x{o`%e}TSj`Gn F3;>UMQ91ws diff --git a/src/core/config.py b/src/core/config.py index d9105d7..e053ecf 100644 --- a/src/core/config.py +++ b/src/core/config.py @@ -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) diff --git a/utils/__pycache__/test_searchgg.cpython-313.pyc b/utils/__pycache__/test_searchgg.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c83e2152cef4334fd8908c573a6d718bc5425e64 GIT binary patch literal 384 zcmYjMJ4*vW5Z=AZ<DKwGmre zJO6`!KoAQTQixTs5%LGzOCmVM%zTd-W@gpu8Zfan`5ru&`c;6xaF@-jXPSF}0D?!L z2RZZrhBeS!%G|)u?grZWM)*Zbgc;rWeP+l?VU#m2d2}=WMb_hS7Z=C~f zn{iz#c