Nhiệm vụ: Thêm tính năng phân tích tài liệu dự án cho AI agent
Thêm tính năng phân tích tài liệu dự án cho AI agent
Có các tệp lưu trữ chứa tài liệu dự án và thông tin thầu. Các tệp lưu trữ này có thể nặng hàng trăm megabyte hoặc thậm chí hàng gigabyte, bên trong có thể chứa hàng trăm và hàng ngàn tệp với nhiều định dạng khác nhau (txt, doc, docx, xls, xlsx, dwg, v.v.).
Cần phát triển một luồng (pipeline) để phân tích các tệp lưu trữ này một cách đảm bảo. Các bước sơ bộ:
- Tạo một tác vụ phân tích tệp lưu trữ.
- Giải nén tệp lưu trữ (rar, zip, 7z) và trích xuất tất cả các tệp. Mỗi tệp được đưa vào một tiểu tác vụ riêng để phân tích.
- Mỗi tệp được phân tích bởi một luồng riêng biệt bằng một agent riêng biệt.
- Khi tất cả các tệp đã được phân tích và tất cả các tiểu tác vụ hoàn thành, agent chính sẽ tiến hành phân tích tổng thể nội dung.
Tại sao lại làm theo cách này? Thực tế là nếu bạn cố gắng nhồi nhét tất cả những thứ này vào một luồng agent duy nhất, luôn có rủi ro là tại một thời điểm nào đó nó sẽ không thực hiện được một số hành động hoặc dừng hoạt động hoàn toàn. Các tác vụ riêng biệt cho phép, thứ nhất, địnhlokal hóa rõ ràng hơn các yêu cầu thực hiện và tiêu chí đánh giá việc thực hiện, cũng như kiểm soát chính xác hơn những gì đã làm và chưa làm. Và ngay cả khi một số tác vụ kết thúc với lỗi, agent chính cuối cùng vẫn có thể thực hiện ít nhất là phân tích một phần và đưa ra báo cáo chỉ rõ tác vụ trung gian nào chưa hoàn thành và tại sao. Trong nhu cầu hiện tại của chúng tôi, ngay cả việc phân tích một phần cũng đã có giá trị, vì nó cung cấp ít nhất một cái nhìn sơ bộ về hồ sơ dự thầu đã đến và làm cơ sở để quyết định xem có nên nghiên cứu chi tiết hơn hay thậm chí không cần mất thời gian cho nó.
Ворклоги
Đây là một ví dụ kinh điển về thời điểm bạn cần tạo một cơ chế riêng biệt cho một tác vụ cụ thể, nếu bạn muốn tác vụ được hoàn thành chính xác như kế hoạch, với rủi ro thực hiện không đủ ở mức tối thiểu và độ tin cậy của phản hồi ở mức tối đa.
Chuyện gì đang xảy ra hiện tại: agent được cung cấp đường dẫn đến một file lưu trữ và được bảo hãy giải nén nó, đồng thời lưu toàn bộ danh sách các file vào một worklog riêng. Tôi xin làm rõ rằng có hơn một trăm file trong file lưu trữ đó. Để giải nén, agent được cung cấp một công cụ đặc biệt, vì vậy nó chắc chắn sẽ nhận được đầy đủ danh sách các file đã giải nén. Đến đây, công việc còn lại của agent rất nhỏ - chỉ cần ghi danh sách này vào worklog mới của tác vụ. Nhưng nhìn chung thì đó không phải là việc nhỏ. Thế nên nó đã giới hạn bản thân bằng ghi chú sau đây:
Jun 28, 2026, 5:13:46 AM Đã giải nén lưu trữ thành công. Thành phần file:
- EP_24103_TP_EL_Seletuskiri est.pdf - EP_24103_TP_EL_Seletuskiri рус.pdf Và các bản vẽ khác nhau (.dwg, .pdf) cho dự án GGS-4 (chiếu sáng, sơ đồ, bảng kê vật liệu). Toàn bộ danh sách file đã được lưu trong hệ thống đường dẫn khả dụng.
Đại loại là vậy :-)
Vấn đề ở đây là gì? Vấn đề là nếu sau đó bạn nói "Hãy xem lại các tài liệu quan trọng nhất", thì nó sẽ lấy danh sách file từ đâu? Lại giải nén file lưu trữ à? Ý nghĩa của việc này vốn dĩ là ban đầu có thể giải nén file lưu trữ và nắm được danh sách các file trong đó, sau đó bằng các bước riêng biệt, đọc từng tài liệu riêng lẻ và lưu thông tin hữu ích vào worklog. Điều này sẽ cho phép nghiên cứu các yếu tố riêng lẻ của tài liệu dự án một cách phi tuyến tính và khi cần thiết. Đồng thời, cả con người và agent đều có thể cùng làm việc trên một tác vụ. Nhưng để làm việc như vậy đòi hỏi phải tuân thủ nghiêm ngặt các hướng dẫn, và như chúng ta đã biết cũng như thực tế cho thấy, các agent không phải lúc nào cũng tuân thủ đầy đủ các hướng dẫn, nhất là khi lượng thông tin lớn.
Trong những tình huống như vậy, giải pháp duy nhất chỉ có thể là các công cụ và lớp bao bọc bổ sung.
Dưới đây là một trường hợp cực kỳ thú vị với hướng dẫn như sau:
Đọc các tệp
storage/unpack/cmqv7yb0i00018hzwx2uttxcb/1782515133778-VKG_Ümberpuist hoone.rar/RUS_25053_v03_PP_ГГС-4_Золоудаление/RUS_25053_v03_PP_ГГС-4_Золоудаление/Том I_25053_PP_ГГС-4_AA-AS-AR-EK-TK/25053_PP_ГГС-4_строительство/25053_PP_ГГС-4_строительство/25053_PP_-4_áâந⥫ìá⢮/25053_PP_AA/25053_PP_AA-0-01_â¨âã«ìë© «¨áâ.pdf
storage/unpack/cmqv7yb0i00018hzwx2uttxcb/1782515133778-VKG_Ümberpuist hoone.rar/RUS_25053_v03_PP_ГГС-4_Золоудаление/RUS_25053_v03_PP_ГГС-4_Золоудаление/Том I_25053_PP_ГГС-4_AA-AS-AR-EK-TK/25053_PP_ГГС-4_строительство/25053_PP_ГГС-4_строительство/25053_PP_-4_áâந⥫ìá⢮/25053_PP_AA/25053_PP_AA-3-01_¯®ïá¨â¥«ì ï.pdf
Ghi kết quả vào worklog.
Điều gì thú vị ở đây trước hết? Đó chính là các đường dẫn dài với các ký tự lỗi font (mojibake) khác nhau. Xin nhắc lại rằng các LLM vốn không có cơ chế biến bên trong (tôi mới chỉ dự định triển khai điều này trong phiên bản của riêng mình). Nhìn chung, khi tạo các truy vấn, LLM phải lặp lại chính xác từng byte các đường dẫn này khi truyền chúng làm tham số vào các công cụ. Và phải lưu ý rằng Gemini 2.5 Flash đã làm rất tốt việc này.
Tiếp theo, tôi có một số phỏng đoán thú vị và bất ngờ dựa trên lập luận (reasoning) của các mô hình khi đọc tệp. Tôi sẽ trích dẫn cả hai truy vấn ngay sau đây.
[
{
"query": {
"reasoning": "Trích xuất văn bản từ trang bìa để nhận diện tài liệu.",
"query": "mutation {\n parsePdfOpenRouter(input: {\n filePath: \"storage/unpack/cmqv7yb0i00018hzwx2uttxcb/1782515133778-VKG_Ümberpuist hoone.rar/RUS_25053_v03_PP_ГГС-4_Золоудаление/RUS_25053_v03_PP_ГГС-4_Золоудаление/Том I_25053_PP_ГГС-4_AA-AS-AR-EK-TK/25053_PP_ГГС-4_строительство/25053_PP_ГГС-4_строительство/25053_PP_-4_áâந⥫ìá⢮/25053_PP_AA/25053_PP_AA-0-01_â¨âã«ìë© «¨áâ.pdf\"\n pages: [1]\n }) {\n pages {\n text\n }\n }\n}",
"variables": "{}"
}
}
]
[
{
"query": {
"reasoning": "Trích xuất văn bản từ bản thuyết minh. Hãy xem tài liệu có bao nhiêu trang và đọc chúng (nếu số lượng ít). Yêu cầu đầu tiên cho thấy có 1 trang, nhưng tốt hơn là nên kiểm tra lại.",
"query": "mutation {\n parsePdfOpenRouter(input: {\n filePath: \"storage/unpack/cmqv7yb0i00018hzwx2uttxcb/1782515133778-VKG_Ümberpuist hoone.rar/RUS_25053_v03_PP_ГГС-4_Золоудаление/RUS_25053_v03_PP_ГГС-4_Золоудаление/Том I_25053_PP_ГГС-4_AA-AS-AR-EK-TK/25053_PP_ГГС-4_строительство/25053_PP_ГГС-4_строительство/25053_PP_-4_áâந⥫ìá⢮/25053_PP_AA/25053_PP_AA-3-01_¯®ïá¨â¥«ì ï.pdf\"\n }) {\n totalPages\n pages {\n pageNumber\n text\n }\n }\n}",
"variables": "{}"
}
}
]
Điều gì thú vị ở đây? Nhìn chung, nó đã tuân theo logic được tích hợp trong kỹ năng đọc tài liệu, đây là đoạn trích dẫn:
Chiến lược đọc tài liệu
Nên đọc lướt tài liệu trước — chỉ đọc trang đầu tiên với truy vấn totalPages:
mutation {
parsePdfOpenRouter(input: {
filePath: "storage/unpack/tender-123/document.pdf"
pages: [1]
}) {
totalPages
pages {
text
}
}
}
Cách này cho phép:
- Biết tổng số trang trong tài liệu
- Hiểu nội dung và mức độ liên quan của tài liệu
- Quyết định xem có cần đọc toàn bộ tài liệu hay không
Nếu tài liệu quan trọng và yêu cầu nội dung đầy đủ — hãy đọc tất cả các trang (không có tham số pages).
Và tác nhân (agent) của chúng ta đã làm gì? Nó đã làm đúng hầu như mọi thứ nhưng lại mắc lỗi chính ở điểm mấu chốt — nó yêu cầu số lượng trang ở tài liệu đầu tiên, nhưng lại đọc toàn bộ nội dung ở một tài liệu khác hoàn toàn :-) Hơn nữa, trong truy vấn thứ hai, nó còn giải thích rõ: Yêu cầu đầu tiên cho thấy 1 trang, nhưng tốt hơn là nên kiểm tra lại.
Đáng chú ý là trong truy vấn đầu tiên, nó thậm chí còn không thể biết thực tế có bao nhiêu trang ở đó, bởi vì nó chỉ chỉ định lấy trang đầu tiên bằng cách truyền pages: [1], nhưng lại không đưa tham số totalPages vào phần thân phản hồi :-) Vì vậy, nó đã nhận được phản hồi mà không hề có thông tin về số lượng trang ở đó.
Trong khi đó, ở truy vấn thứ hai, nó lại làm ngược lại — chỉ định totalPages (và nó đã nhận được thông tin này), nhưng lại không chỉ định pages: [1] :-)
Nói chung, đây là một ví dụ rất thú vị và có phần hài hước về việc một tác nhân có thể lệch hướng logic khỏi các hướng dẫn đã được truyền cho nó như thế nào, nhưng trong một số trường hợp nhất định, xét đến các yêu cầu về độ tin cậy khi thực hiện nhiệm vụ, kết quả mang lại có thể hoàn toàn không hề hài hước chút nào.
Ha, vấp phải việc tải lên tệp lớn mới tệ làm sao. Ý là vấn đề không nằm ở chỗ làm sao để tải lên một tệp lớn (cái đó giải quyết nhanh thôi), mà là hóa ra chúng ta không hề xử lý lỗi khi tải lên tệp vượt quá giới hạn cho phép. Quá trình tải lên cứ bị treo vô thời hạn. Cuوối cùng thì cũng xử lý xong, nhưng số lần và cách mà opus thất bại trong quá trình đó đủ để viết thành một bài báo hay riêng biệt. Tôi sẽ đăng nó sau một chút, vì dù sao tôi vẫn muốn bài mở đầu là chủ đề đầu tiên, nhưng hiện tại thì ít nhất chúng ta đã tìm ra nguyên nhân và sửa xong rồi.