Hướng dẫn · 7 phút đọc

Vì sao tìm kiếm hybrid của AgentKits Memory phải giải quyết tiếng Nhật hai lần

AgentKits Memory kết hợp BM25 và tìm kiếm vector, nhưng với truy vấn tiếng Nhật, Trung, Hàn, nửa từ khóa cần thêm một quyết định nữa.

Sản phẩm liên quan: AgentKits Tất cả sản phẩm Giải pháp

Vì sao tìm kiếm hybrid của AgentKits Memory phải giải quyết tiếng Nhật hai lần

Hai loại truy vấn, hai kiểu thất bại khác nhau

Hỏi bộ nhớ của một trợ lý lập trình AI về “authentication pattern” (mẫu xác thực), bạn muốn nó tìm ra mục ghi “dùng JWT với refresh token” dù không từ nào trong câu hỏi trùng khớp theo nghĩa đen — đây là bài toán về ý nghĩa, và tìm kiếm từ khóa vốn dở ở việc này. Hỏi chính bộ nhớ đó về chuỗi chính xác ModuleNotFoundError: No module named 'networkx', bạn muốn đúng một mục chứa lỗi đó, không phải năm mục “có vẻ liên quan đến Python” — đây là bài toán về tính đồng nhất, và tìm kiếm vector vốn dở ở việc này, vì độ tương đồng embedding làm nhòe các chuỗi chính xác vào một vùng lân cận gồm những văn bản “gần giống”.

AgentKits Memory — lớp bộ nhớ bền vững, local-first cho trợ lý lập trình AI mà chúng tôi đã từng viết — không chọn một trong hai. HybridSearchEngine của nó chạy cả tìm kiếm từ khóa SQLite FTS5 lẫn tìm kiếm vector bằng embedding cục bộ trên mọi truy vấn, rồi kết hợp hai điểm số lại. Đây không phải là một quyết định theo cảm tính — nó khớp với những gì các nghiên cứu về truy xuất thông tin liên tục tìm thấy mỗi khi đo riêng hai cách tiếp cận này. Trên bộ benchmark tìm kiếm thương mại điện tử WANDS, một cấu hình hybrid được tinh chỉnh đạt NDCG 0,7497, so với 0,6983 của riêng BM25 và 0,6953 của riêng tìm kiếm vector — mỗi bên thắng một số truy vấn và thua ở những truy vấn khác, và việc kết hợp cả hai tốt hơn là chọn một bên. (Denser AI)

Cơ chế kết hợp hoạt động thế nào

Cách triển khai là một phép trộn có trọng số đơn giản, không phải hộp đen: mặc định điểm từ khóa nhân 0,3 cộng điểm ngữ nghĩa nhân 0,7, cả hai đều được chuẩn hóa về khoảng 0–1 trước khi cộng lại. Điểm xếp hạng BM25 lấy từ hàm bm25() có sẵn của FTS5; độ tương đồng ngữ nghĩa được tính từ khoảng cách cosine giữa embedding của truy vấn được tạo cục bộ và embedding của các mục đã lưu — không gọi API embedding trên cloud, sử dụng mô hình đa ngôn ngữ multilingual-e5-small dạng ONNX như README đã ghi rõ. Khi chỉ một bên tìm thấy kết quả khớp, mục đó vẫn giữ nguyên điểm số riêng lẻ của mình thay vì bị trừ điểm vì bên còn lại im lặng.

Trọng số mặc định nghiêng 70/30 về phía ngữ nghĩa thể hiện một cược thực sự: vì phần lớn các mục trong hệ thống bộ nhớ này là văn xuôi — quyết định, tóm tắt — chứ không phải mã sản phẩm, nên ý nghĩa thường quan trọng hơn cách diễn đạt chính xác — nhưng ngưỡng sàn 30% dành cho từ khóa chính là thứ ngăn một chuỗi lỗi hay tên hàm chính xác bị lạc mất trong không gian embedding, đúng là kiểu thất bại đã được ghi nhận ở tìm kiếm chỉ dựa trên vector.

Vấn đề CJK ẩn bên trong “từ khóa”

Đây là phần không nằm trong những lời khuyên RAG chung chung: “tìm kiếm từ khóa” giả định rằng bạn có thể xác định một từ kết thúc ở đâu và từ tiếp theo bắt đầu từ đâu, còn văn bản tiếng Nhật, tiếng Trung, tiếng Hàn thì không cho bạn điều đó một cách miễn phí — giữa các từ không có khoảng trắng. Tokenizer chuẩn unicode61 của FTS5, được xây dựng dựa trên ranh giới từ, đơn giản là không thể phân đoạn văn bản CJK thành thứ gì có thể tìm kiếm được. Câu trả lời của AgentKits Memory là mặc định dùng tokenizer trigram của FTS5, đánh chỉ mục theo các chuỗi 3 ký tự chồng lấp nhau thay vì theo từ — một chiến lược được thiết kế riêng cho trường hợp bạn không thể xác định đáng tin cậy ranh giới từ ngay từ đầu.

Lựa chọn mặc định đó không phải là không có đánh đổi, và mã nguồn cũng không giấu điều này. Việc đánh chỉ mục theo n-gram ký tự cho tiếng Nhật được tài liệu về truy xuất thông tin ghi nhận là dễ sinh ra “kết quả giả” — những từ không liên quan tình cờ chia sẻ chung một n-gram và bị khớp nhầm — trong khi lựa chọn thay thế, phân đoạn từ đúng nghĩa bằng phân tích hình thái học, lại mang một vấn đề ngược lại: từ điển của bộ phân đoạn luôn không đầy đủ, nên nó âm thầm bỏ sót bất cứ điều gì nó chưa từng học. (Tài liệu Whoosh về đánh chỉ mục n-gram; các khảo sát chung về tokenize tiếng Nhật cũng chỉ ra rõ đánh đổi này theo cả hai chiều.) HybridSearchEngine của AgentKits Memory viết mã để xử lý việc này thay vì chọn mù quáng một bên: nó thương lượng tokenizer tốt nhất mà bản dựng SQLite cục bộ thực sự hỗ trợ (trigram, rồi đến porter, rồi đến unicode61, được kiểm tra trực tiếp lúc khởi tạo), và với truy vấn CJK ngắn hơn 3 ký tự — quá ngắn để tạo thành một trigram — nó chuyển sang quét LIKE đơn giản thay vì trả về rỗng. Với những đội muốn phân đoạn từ tiếng Nhật thực sự thay vì thỏa hiệp bằng n-gram, gói phần mềm này cung cấp backend lindera-sqlite tùy chọn như một hướng nâng cấp rõ ràng, thay vì áp đặt sự đánh đổi đó lên tất cả mọi người theo mặc định.

Phần tiết kiệm phải được bảo vệ, không chỉ giành được rồi thôi

Chạy hai công cụ tìm kiếm và dung hòa hai chiến lược tokenizer sẽ là công sức kỹ thuật lãng phí nếu kết quả bị đổ thẳng toàn văn vào cửa sổ ngữ cảnh của mô hình. Đó chính là lý do tồn tại của TokenEconomicsTracker đi kèm và thiết kế 3 lớp của công cụ tìm kiếm (compact → timeline → full): lớp một chỉ trả về khoảng 50 token cho mỗi kết quả — id, phân tích điểm số, đoạn trích 100 ký tự, số token ước tính — để trợ lý có thể xem qua mười ứng viên với chi phí bằng một lần lấy toàn văn một mục, trước khi quyết định mục nào đáng lấy đầy đủ. README ghi nhận mẫu hình tiết lộ dần (progressive disclosure) này tiết kiệm khoảng 70% token so với việc lấy mọi thứ ngay từ đầu, và ở một số cấu hình còn được ghi nhận lên tới 87%. Dù con số nào, điểm mấu chốt vẫn vậy: việc kết hợp hai tín hiệu truy xuất chỉ thực sự đáng giá nếu lớp nằm phía trên nó không lập tức tiêu hết phần tiết kiệm đó.

Nói thẳng ra

Không có điều nào ở trên khiến tìm kiếm của AgentKits Memory trở thành “đã giải quyết xong” theo nghĩa cuối cùng nào cả — tokenize bằng trigram là một sự thỏa hiệp đã được lựa chọn và ghi nhận rõ ràng như vậy, chứ không phải là tuyên bố nó ngang bằng một bộ phân tích hình thái học đúng nghĩa. Điều được thể hiện ở đây là một kiểu kỷ luật thiết kế cụ thể: một công cụ tìm kiếm hybrid kết hợp điểm từ khóa và điểm ngữ nghĩa đã là kiến trúc RAG cơ bản, ai cũng làm, vào năm 2026 — nhưng việc thừa nhận rằng “từ khóa” không phải là một thao tác được định nghĩa giống hệt nhau giữa tiếng Anh và văn bản CJK, và xây dựng hẳn một chuỗi dự phòng tường minh thay vì mặc định rằng unicode61 phù hợp với tất cả mọi người — đó là phần chỉ lộ ra khi bạn thực sự cố làm cho tìm kiếm hoạt động được với một truy vấn viết bằng tiếng Nhật.


AgentKits Memory có sẵn trên GitHub và trên npm với tên gói @aitytech/agentkits-memory. Có câu hỏi về cách nó phù hợp với quy trình làm việc của bạn? Liên hệ tại [email protected].

Khám phá mã nguồn mở

Chúng tôi xây dựng và duy trì các công cụ mã nguồn mở cho nhà phát triển. Xem trên GitHub.

Xem trên GitHub

Bài viết liên quan