1. Hạn chế của cách hiển thị liên quan truyền thống
- Tags không đồng nhất: cùng một chủ đề có thể bị đặt nhiều tag khác nhau, hoặc trùng tag giữa các bài không liên quan.
- Thiếu chính xác nội dung: 2 bài viết có cùng tag nhưng viết về các hướng hoàn toàn khác nhau.
- Phụ thuộc thủ công: nếu người viết quên gắn tag hoặc category thì hệ thống liên quan sẽ không hoạt động.
2. Giải pháp: Phân tích ngữ nghĩa với Embedding
Thay vì phụ thuộc vào các chỉ số thủ công, bạn có thể sử dụng embedding – kỹ thuật chuyển văn bản thành vector số, từ đó tính toán mức độ tương đồng giữa các bài viết.
| Công cụ | Ưu điểm | Miễn phí? |
|---|---|---|
| OpenAI Embedding API | Đơn giản, hỗ trợ đa ngôn ngữ, trả về vector có độ chính xác cao | Không |
| Sentence-BERT | Miễn phí, chạy local được, nhiều model tốt cho tiếng Việt | Có |
| Hugging Face Transformers | Open source, nhiều tùy biến nâng cao, có GPU hỗ trợ | Có |
3. Mô hình dữ liệu cơ bản
CREATE TABLE wp_post_embeddings (
post_id BIGINT PRIMARY KEY,
embedding TEXT NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
4. Ví dụ hàm gọi OpenAI để lấy embedding
function get_embedding_from_openai($text) {
$api_key = 'YOUR_OPENAI_KEY';
$data = array(
'input' => $text,
'model' => 'text-embedding-ada-002'
);
$args = array(
'headers' => array(
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $api_key,
),
'body' => json_encode($data),
'timeout' => 15
);
$response = wp_remote_post('https://api.openai.com/v1/embeddings', $args);
if (is_wp_error($response)) return false;
$body = json_decode(wp_remote_retrieve_body($response), true);
return isset($body['data'][0]['embedding']) ? maybe_serialize($body['data'][0]['embedding']) : false;
}
5. Tự động cập nhật embedding khi lưu bài viết
add_action('save_post', function($post_id) {
$post = get_post($post_id);
if ($post->post_status !== 'publish') return;
$content = $post->post_title . ' ' . wp_trim_words(strip_tags($post->post_content), 100);
$embedding = get_embedding_from_openai($content);
if (!$embedding) return;
global $wpdb;
$wpdb->replace('wp_post_embeddings', [
'post_id' => $post_id,
'embedding' => $embedding
]);
});
6. Hàm truy vấn các bài viết liên quan
Do WordPress/MySQL không hỗ trợ xử lý vector natively, bạn nên:
- Lấy vector của bài hiện tại
- So sánh cosine similarity bằng PHP hoặc API ngoài
- Trả về danh sách post ID liên quan
function cosine_similarity($v1, $v2) {
$dot = 0; $mag1 = 0; $mag2 = 0;
foreach ($v1 as $i => $v) {
$dot += $v * $v2[$i];
$mag1 += $v * $v;
$mag2 += $v2[$i] * $v2[$i];
}
return $dot / (sqrt($mag1) * sqrt($mag2));
}
function get_related_posts_by_vector($post_id, $limit = 5) {
global $wpdb;
$row = $wpdb->get_row($wpdb->prepare(
"SELECT embedding FROM wp_post_embeddings WHERE post_id = %d", $post_id
));
if (!$row) return [];
$current_vector = maybe_unserialize($row->embedding);
$candidates = $wpdb->get_results("SELECT post_id, embedding FROM wp_post_embeddings WHERE post_id != $post_id");
$scores = [];
foreach ($candidates as $c) {
$vector = maybe_unserialize($c->embedding);
$scores[$c->post_id] = cosine_similarity($current_vector, $vector);
}
arsort($scores);
$top_ids = array_slice(array_keys($scores), 0, $limit);
return get_posts(['post__in' => $top_ids, 'orderby' => 'post__in']);
}
7. Tích hợp hiển thị trên single.php
<?php
$related = get_related_posts_by_vector(get_the_ID(), 4);
if ($related):
?>
<h3>Bài viết liên quan</h3>
<ul>
<?php foreach ($related as $post): setup_postdata($post); ?>
<li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php endforeach; wp_reset_postdata(); ?>
</ul>
<?php endif; ?>
Nâng cao: Bạn có thể kết hợp caching hoặc dùng plugin Redis Object Cache để lưu kết quả truy vấn gần nhất, tránh tính toán lại mỗi lượt xem.
Kết luận
Việc hiển thị bài viết liên quan dựa trên phân tích ngữ nghĩa giúp tăng cường trải nghiệm người đọc, giảm tỷ lệ thoát và giữ chân người dùng lâu hơn trên site. Đây là một bước nâng cấp hợp lý nếu bạn hướng tới sự thông minh và hiệu quả thay vì phụ thuộc hoàn toàn vào tag thủ công.
Bình luận