Xây Semantic Search Cho Blog Markdown Laravel Bằng Embeddings
Giới Thiệu
Keyword search hoạt động tốt khi người dùng gõ đúng từ khóa có trong bài viết. Nhưng với blog kỹ thuật, độc giả thường diễn đạt vấn đề bằng nhiều cách khác nhau. Họ có thể tìm "tối ưu query", trong khi bài viết dùng cụm "query plan" hoặc "index strategy". Đây là chỗ semantic search phát huy tác dụng.
Với blog Markdown file-based, semantic search có một lợi thế lớn: dữ liệu sạch, cấu trúc ổn định và pipeline indexing tương đối đơn giản.
Mục lục
- Kiến trúc tối thiểu cho semantic search
- Chunking và metadata quan trọng thế nào
- Ranking ngoài cosine similarity
- Flow search và pipeline indexing thực dụng
- Cách đánh giá chất lượng search
- Những pitfall thường gặp
Kiến Trúc Tối Thiểu
Một flow đủ tốt cho blog cá nhân:
- Đọc toàn bộ file Markdown.
- Parse frontmatter và body.
- Chunk nội dung theo đoạn hoặc heading.
- Gọi embeddings API để lấy vector.
- Lưu vector kèm metadata vào bảng hoặc vector store.
- Khi search, embedding câu hỏi rồi so độ tương đồng.
namespace App\Services\Search;
class ChunkPostContent
{
public function handle(string $markdown): array
{
$sections = preg_split('/\n##\s+/', $markdown);
return collect($sections)
->map(fn (string $section) => trim($section))
->filter(fn (string $section) => mb_strlen($section) > 120)
->values()
->all();
}
}
Không cần bắt đầu bằng pipeline quá phức tạp. Với blog vài trăm bài, chunk theo heading đã đủ hữu ích.
Metadata Quan Trọng Không Nên Bỏ Qua
Mỗi vector nên đi kèm:
- slug bài viết
- title
- locale
- heading hoặc section title
- published date
- tags
Metadata giúp bạn lọc kết quả tốt hơn và hiển thị snippet phù hợp hơn. Nếu chỉ lưu vector trần, bạn sẽ sớm gặp khó khi ranking hoặc debug.
Chunking Là Quyết Định Quan Trọng Hơn Bạn Nghĩ
Nhiều tutorial về semantic search nói rất nhiều về vector store nhưng lại nói rất ít về chunking. Trong thực tế, chunking thường ảnh hưởng chất lượng retrieval nhiều hơn việc bạn đổi sang một model embedding mới hơn.
Nếu chunk quá lớn:
- mỗi vector mang quá nhiều ý
- kết quả search dễ trả về section chung chung
- ranking khó biết đoạn nào thực sự liên quan
Nếu chunk quá nhỏ:
- mất ngữ cảnh
- kết quả dễ rơi vào các mảnh câu khó đọc
- số lượng vectors tăng nhanh, làm indexing và ranking nặng hơn
Với blog kỹ thuật dạng Markdown, một chiến lược khá thực dụng là:
- ưu tiên chunk theo heading
##hoặc### - gộp các đoạn quá ngắn với đoạn kế tiếp
- giữ chunk đủ dài để có nghĩa độc lập, nhưng không ôm cả bài
Những Trường Metadata Rất Đáng Có
Ngoài các trường cơ bản, bạn nên cân nhắc thêm:
excerptngắn để hiển thị search resultheading_levelđể phân biệt section chính/phụword_countđể loại bỏ các chunk quá mỏngis_draftđể chắc chắn không index nhầm bài nháp
Các trường này không chỉ giúp ranking mà còn giúp debug vì bạn nhìn ra ngay chunk nào đang xuất hiện trong kết quả.
Ranking Không Chỉ Là Cosine Similarity
Similarity score là điểm bắt đầu, không phải điểm cuối. Với blog kỹ thuật, nên cộng thêm một số tín hiệu khác:
- bài mới hơn có thể được cộng nhẹ
- tiêu đề khớp intent thì tăng điểm
- tag liên quan thì tăng điểm
- bài draft hoặc quá ngắn thì loại bỏ
Semantic search tốt là semantic search có kiểm soát, không phải để vector score quyết định tất cả.
Một Flow Search Thực Tế Hơn
Thay vì chỉ lấy top 5 chunks theo similarity score rồi hiển thị, bạn có thể đi theo flow này:
- embedding câu hỏi
- lấy top 20 chunks gần nhất
- group theo
post_slug - cộng thêm điểm cho title match, tag match, freshness
- chọn 5 bài tốt nhất
- trong mỗi bài, hiển thị chunk phù hợp nhất làm snippet
Flow này thường hữu ích hơn việc để nhiều chunk từ cùng một bài chiếm hết kết quả.
Khi Nào Nên Reindex?
Một số thời điểm hợp lý để reindex:
- khi publish bài mới
- khi sửa nội dung bài đã publish
- khi đổi chunking strategy
- khi đổi prompt tiền xử lý hoặc cách normalize text
- khi đổi model embeddings
Đừng đợi đến khi search quality giảm rõ mới nghĩ đến reindex. Nhiều vấn đề về retrieval thực ra đến từ index cũ không phản ánh nội dung mới.
Đánh Giá Chất Lượng Search Bằng Cách Nào?
Bạn không cần một hệ thống ML evaluation quá nặng. Với blog cá nhân, chỉ cần tạo một tập 20-30 truy vấn mẫu như:
cách tối ưu query mysqlsemantic search blog laravelqueue retry ai jobs
Sau đó tự kiểm tra:
- bài đúng có xuất hiện trong top 3 không?
- snippet có thực sự hữu ích không?
- kết quả có bị lặp cùng một bài nhiều lần không?
Đây là cách đánh giá rất rẻ nhưng đủ giúp bạn biết hệ thống đang tốt lên hay tệ đi.
Một Pipeline Indexing Thực Dụng Hơn
Khi bắt đầu triển khai thật, bạn thường sẽ cần nhiều bước hơn ví dụ tối giản ở trên:
- load file Markdown
- parse frontmatter
- loại bỏ các phần không nên index như code fence quá dài nếu cần
- chunk theo heading
- normalize whitespace và giữ metadata
- gửi embeddings theo batch
- upsert vector records
Điểm quan trọng là pipeline này nên idempotent. Nếu reindex lại một bài, kết quả cuối cùng phải giống nhau và không tạo duplicate records.
Snippet Generation Cũng Ảnh Hưởng Trải Nghiệm Tìm Kiếm
Kể cả khi retrieval đúng, trải nghiệm tìm kiếm vẫn có thể tệ nếu snippet không đúng trọng tâm. Một vài nguyên tắc khá hữu ích:
- ưu tiên snippet từ chunk có score cao nhất
- cắt snippet theo câu hoặc đoạn ngắn, đừng cắt giữa code block quá nhiều
- hiển thị heading đi kèm để người đọc biết kết quả nằm ở đâu trong bài
Nhiều khi người dùng đánh giá chất lượng search không phải ở ranking tuyệt đối, mà ở việc snippet có khiến họ bấm vào kết quả hay không.
Khi Nào Semantic Search Chưa Đáng Làm?
- số bài còn quá ít
- keyword search hiện tại đã đáp ứng đủ
- người đọc chủ yếu tìm bằng title hoặc tag rõ ràng
- bạn chưa sẵn sàng duy trì pipeline indexing
AI feature tốt là feature giải quyết vấn đề có thật. Đừng thêm semantic search chỉ vì nó nghe hiện đại.
Những Pitfall Thường Gặp
- index luôn cả bài draft hoặc note nội bộ
- dùng cùng một chunking strategy cho mọi loại nội dung
- không lưu đủ metadata nên khó debug ranking
- chỉ nhìn similarity score mà không đánh giá theo intent thực của người dùng
Nếu tránh được bốn điểm này, phiên bản đầu tiên của semantic search thường đã đủ hữu ích.
FAQ
Có cần vector database riêng không?
Không nhất thiết. Với blog nhỏ hoặc vừa, lưu vector và metadata trong bảng riêng rồi ranking ở application layer có thể đã đủ cho giai đoạn đầu.
Có nên dùng toàn bộ bài viết để tạo một vector duy nhất không?
Thường không. Một vector cho cả bài quá thô và dễ làm mất những section rất mạnh nhưng nằm trong bài dài.
Key takeaways:
- Semantic search phù hợp với blog kỹ thuật vì nội dung sạch và người đọc hay dùng nhiều cách diễn đạt khác nhau.
- Chunking và metadata thường ảnh hưởng chất lượng retrieval mạnh hơn bạn nghĩ.
- Ranking tốt cần thêm title match, tag match và freshness thay vì chỉ dựa vào vector score.
- Bạn có thể đánh giá search quality bằng một bộ truy vấn mẫu nhỏ trước khi overbuild hệ thống.
- Indexing pipeline nên idempotent và tránh index nhầm draft hoặc chunk quá mỏng.
Indexing Nên Chạy Khi Nào?
Vì blog của bạn là Markdown-first, thời điểm hợp lý nhất là khi file mới được publish hoặc cập nhật. Có thể làm bằng command hoặc queue job:
php artisan blog:reindex-search --locale=vi
Batch reindex giúp dễ kiểm soát chi phí hơn so với reindex mọi thứ trên mỗi deploy.
Kết Luận
Semantic search là một tính năng AI rất hợp với blog kỹ thuật vì tác động rõ ràng, dữ liệu đầu vào sạch và có thể triển khai tăng dần. Bắt đầu nhỏ với chunk đơn giản, metadata đầy đủ và ranking có thêm business signals là đủ để tạo khác biệt đáng kể so với keyword search thuần túy.