Tổng quan kiến trúc
- Candidate Pool: tập hợp bài viết ứng viên, mặc định gồm latest và random theo category.
- Signals: các đặc trưng để chấm điểm như category, tag, comment, views, title bigrams, recency, time gap.
- Weights: trọng số chuẩn hoá (tổng bằng 1) cho mỗi tín hiệu.
- MMR Diversification: giảm trùng lặp nội dung bằng Max Marginal Relevance.
- Cache: kết quả được cache theo algo version bằng transient.
- Filters: điểm mở rộng chính, tất cả đều giữ nguyên nguyên tắc “prefix-consistent”.
Danh sách filter hỗ trợ
init_plugin_suite_live_search_ai_candidates: thêm/bớt ứng viên.init_plugin_suite_live_search_ai_signals: bổ sung hoặc ghi đè tín hiệu.init_plugin_suite_live_search_ai_weights: điều chỉnh trọng số.init_plugin_suite_live_search_ai_score: tinh chỉnh điểm cuối cùng cho từng ứng viên.init_plugin_suite_live_search_ai_selected: hậu xử lý danh sách đã chọn.init_plugin_suite_live_search_ai_half_life_recency: bán rã cho độ mới.init_plugin_suite_live_search_ai_half_life_gap: bán rã cho chênh lệch thời gian.init_plugin_suite_live_search_ai_mmr_lambda: hệ số MMR giữa độ liên quan và đa dạng.
Ví dụ mở rộng: candidates + signals + weights
Đoạn mã dưới đây minh hoạ cách bổ sung nguồn ứng viên từ same_keyword (meta) và series (taxonomy), đồng thời thêm điểm số tương ứng và hiệu chỉnh trọng số.
// + Thêm ứng viên: cùng series (random) + cùng same_keyword (meta_query)
// + Giữ nguyên các candidate có sẵn rồi hợp nhất lại
add_filter('init_plugin_suite_live_search_ai_candidates', function($candidates, $post_id, $post_type){
$candidates = is_array($candidates) ? $candidates : [];
// ----- SAME_KEYWORD -----
$same_kw_raw = function_exists('get_field')
? (string) get_field('same_keyword', $post_id)
: (string) get_post_meta($post_id, 'same_keyword', true);
if ($same_kw_raw !== '') {
$tokens = array_filter(array_map('trim', preg_split('/[,;|]+/u', $same_kw_raw)));
if (!empty($tokens)) {
$meta_query = ['relation' => 'OR'];
foreach ($tokens as $t) {
$meta_query[] = [
'key' => 'same_keyword',
'value' => $t,
'compare' => 'LIKE',
];
}
$kw_pool = get_posts([
'post_type' => $post_type,
'post_status' => 'publish',
'posts_per_page' => 100,
'post__not_in' => [$post_id],
'fields' => 'ids',
'meta_query' => $meta_query,
'no_found_rows' => true,
]);
$candidates = array_merge($candidates, (array)$kw_pool);
}
}
// ----- SERIES (nhẹ) -----
$series_terms = wp_get_post_terms($post_id, 'series', ['fields' => 'ids']);
if (!empty($series_terms) && !is_wp_error($series_terms)) {
$series_pool = get_posts([
'post_type' => $post_type,
'post_status' => 'publish',
'posts_per_page' => 30,
'post__not_in' => [$post_id],
'fields' => 'ids',
'orderby' => 'rand',
'tax_query' => [[
'taxonomy' => 'series',
'field' => 'term_id',
'terms' => $series_terms,
]],
'no_found_rows' => true,
]);
$candidates = array_merge($candidates, (array)$series_pool);
}
return array_values(array_unique(array_map('intval', $candidates)));
}, 10, 3);
add_filter('init_plugin_suite_live_search_ai_signals', function($signals, $post_id, $candidate_id){
// SAME_KEYWORD
$get_kw = function($pid){
$raw = function_exists('get_field') ? (string) get_field('same_keyword', $pid)
: (string) get_post_meta($pid, 'same_keyword', true);
return $raw !== '' ? array_filter(array_map('trim', preg_split('/[,;|]+/u', $raw))) : [];
};
$src = $get_kw($post_id);
$dst = $get_kw($candidate_id);
$kw_score = 0.0;
if ($src && $dst) {
$inter = array_intersect($src, $dst);
$kw_score = count($inter) / max(count($src), 1);
}
$signals['same_keyword'] = $kw_score;
// SERIES (binary, nhưng nhẹ)
$src_series = wp_get_post_terms($post_id, 'series', ['fields' => 'ids']);
$dst_series = wp_get_post_terms($candidate_id, 'series', ['fields' => 'ids']);
$signals['series'] = (!empty(array_intersect($src_series, $dst_series))) ? 1.0 : 0.0;
return $signals;
}, 10, 3);
add_filter('init_plugin_suite_live_search_ai_weights', function($weights){
$weights['tag'] = 0.25;
$weights['series'] = 0.20;
$weights['title_bigrams'] = 0.15;
$weights['same_keyword'] = 0.15;
$weights['category'] = 0.08;
$weights['views'] = 0.07;
$weights['comment'] = 0.05;
$weights['freshness'] = 0.05;
return $weights;
}, 10, 1);
Hướng dẫn từng bước
- Cài đặt hoặc mở theme/plugin nơi bạn muốn thêm filter.
- Sao chép đoạn mã ví dụ vào file PHP phù hợp (ví dụ: một module mở rộng hoặc
functions.php). - Đảm bảo taxonomy
seriesvà metasame_keywordtồn tại và có dữ liệu. - Tinh chỉnh giới hạn
posts_per_pageđể cân bằng giữa chất lượng và hiệu năng. - Điều chỉnh trọng số trong filter
..._ai_weightsđể đạt thứ hạng mong muốn.
Hiệu năng và cache
- Prime caches: core đã gọi
_prime_post_cachesvàupdate_object_term_cacheđể tránh N+1. - Giới hạn pool: chọn ngưỡng hợp lý cho candidate pool từ meta/tax để không nặng DB.
- TTL: kết quả cuối cùng được cache bởi transient với TTL có thể chỉnh bằng filter
..._ai_cache_ttl. - Algo version: thay đổi logic lớn hãy tăng
$algo_verđể invalidation mượt.
Tinh chỉnh xếp hạng nâng cao
- Dùng
init_plugin_suite_live_search_ai_scoređể can thiệp điểm sau khi tính, ví dụ đẩy nhẹ bài có conversion cao. - Dùng
init_plugin_suite_live_search_ai_selectedđể loại trừ các bài trùng chủ đề đặc biệt ở bước cuối. - Điều chỉnh
init_plugin_suite_live_search_ai_mmr_lambdađể cân bằng giữa độ liên quan và đa dạng.
Kiểm thử và đo lường
- Tạo bộ test gồm các bài có cùng series, cùng keyword, khác category để quan sát thứ hạng.
- So sánh CTR/Time on Page trước và sau khi bật filter tùy chỉnh.
- Giám sát log/monitoring cho số lượng candidate và thời gian xử lý trung bình.
Các lưu ý triển khai
- Luôn kiểm tra quyền tồn tại của taxonomy/meta trước khi truy vấn.
- Ưu tiên trả về ID và đặt
no_found_rowsđể giảm overhead truy vấn. - Loại trừ
$post_idkhỏi tập ứng viên và chỉ giữ bàipublish. - Chuẩn hoá và loại bỏ trùng lặp bằng
array_uniquevàintval.
Kết luận
Với hệ thống filter mở và cache thân thiện, AI Related Posts của Init Live Search cho phép bạn mở rộng logic liên quan linh hoạt mà vẫn giữ hiệu năng cao. Hãy bắt đầu bằng ví dụ trong mục này, sau đó tinh chỉnh tín hiệu và trọng số để phù hợp nội dung của bạn.
Bình luận