Extend AI Related Posts via Filters — Init Live Search

This guide shows how to extend the AI Related Posts module of Init Live Search using its filter system. You will learn how to add new candidate pools, enrich scoring signals, adjust weights, and fine-tune the final ranking without modifying core code. The document focuses on practical implementation, performance, and maintainability.

Extend AI Related Posts via Filters — Init Live Search

Architecture Overview

  • Candidate Pool: default set includes latest posts and random posts from the same category.
  • Signals: features used for scoring such as category, tag, comment, views, title bigrams, recency, time gap.
  • Weights: normalized weights (sum = 1) applied to each signal.
  • MMR Diversification: reduces redundancy with Max Marginal Relevance.
  • Cache: results cached with an algo version key for smooth invalidation.
  • Filters: extension points, all prefix-consistent and non-breaking.

Supported Filters

  • init_plugin_suite_live_search_ai_candidates: add or remove candidates.
  • init_plugin_suite_live_search_ai_signals: add or override signals.
  • init_plugin_suite_live_search_ai_weights: adjust weights.
  • init_plugin_suite_live_search_ai_score: tweak the final score per candidate.
  • init_plugin_suite_live_search_ai_selected: post-process the selected list.
  • init_plugin_suite_live_search_ai_half_life_recency: half-life for recency signal.
  • init_plugin_suite_live_search_ai_half_life_gap: half-life for time-gap signal.
  • init_plugin_suite_live_search_ai_mmr_lambda: lambda factor for MMR relevance/diversity.

Example Extension: Candidates, Signals, and Weights

The snippet below demonstrates adding candidates from same_keyword (meta) and series (taxonomy), enriching the scoring signals, and adjusting weights accordingly.

// + Add candidates: same series (random) + same_keyword (meta_query)
// + Merge with existing candidates
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 (light) -----
    $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, light)
    $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);

Step-by-Step Guide

  1. Open the theme/plugin file where you want to add the filters.
  2. Paste the example code into a suitable PHP file (e.g. an extension module or functions.php).
  3. Ensure the series taxonomy and same_keyword meta field exist and contain data.
  4. Adjust posts_per_page limits to balance quality and performance.
  5. Tune the weights in ..._ai_weights filter to achieve the desired ranking.

Performance and Cache

  • Prime caches: core calls _prime_post_caches and update_object_term_cache to avoid N+1 queries.
  • Pool limits: keep reasonable candidate limits for meta/tax queries to avoid DB load.
  • TTL: results are cached via transient with TTL configurable using ..._ai_cache_ttl filter.
  • Algo version: bump $algo_ver when making significant logic changes for smooth cache invalidation.

Advanced Ranking Tweaks

  • Use init_plugin_suite_live_search_ai_score to adjust final scores, e.g., boosting posts with high conversions.
  • Use init_plugin_suite_live_search_ai_selected to exclude or reorder specific posts after ranking.
  • Adjust init_plugin_suite_live_search_ai_mmr_lambda to balance relevance vs. diversity.

Testing and Measurement

  • Create test sets with posts sharing series, keywords, and categories to observe ranking changes.
  • Compare CTR/Time on Page before and after enabling custom filters.
  • Monitor logs and performance metrics for candidate pool size and processing time.

Implementation Notes

  • Always check the existence of taxonomy/meta before querying.
  • Return only IDs and set no_found_rows to reduce query overhead.
  • Exclude $post_id from candidates and keep only published posts.
  • Normalize and deduplicate with array_unique and intval.

Conclusion

With its open filter system and cache-friendly design, AI Related Posts in Init Live Search enables flexible extension while maintaining high performance. Start with the example in this guide, then fine-tune signals and weights to match your content strategy.

Comments


  • No comments yet.

Init Toolbox

Press Ctrl + \ on desktop, or swipe left anywhere on mobile.

Login