WEBSITE ĐANG PHÁT TRIỂN

Từ Episerver Find đến AI-powered search - con đường migration

Bạn đang dùng Episerver Find và muốn cải thiện search quality với AI? Đừng rewrite toàn bộ. Có một con đường từng bước - từ keyword search → hybrid search → semantic search - mà không cần phá vỡ hệ thống đang chạy ổn.

Khi khách hàng phàn nàn về search

Cuối năm 2024, tôi nhận được complaint từ một khách hàng đang dùng hệ thống e-commerce mà team tôi build và maintain. Hệ thống search dùng Episerver Find - hoạt động tốt nhiều năm.

Nhưng khách hàng nói: "Search ngày càng kém hơn so với Amazon. Khách vào tìm 'giày thể thao bền' không ra gì. Tìm tên thương hiệu sai chính tả thì không có kết quả."

Tôi ngồi test thử. Họ đúng. Episerver Find mặc định là keyword search - không có semantic understanding, không có typo tolerance tốt, không hiểu intent của user.

Câu hỏi là: làm thế nào để cải thiện mà không rewrite toàn bộ hệ thống?


Quay lại chuyện kỹ thuật: tại sao keyword search không đủ

Episerver Find về bản chất là một wrapper trên Elasticsearch với opinionated configuration. Khi bạn gọi:

var results = SearchClient.Instance
    .Search<ProductContent>()
    .For("giày thể thao bền")
    .GetContentResult();

Nó chuyển thành một Elasticsearch query tìm documents có chứa các từ "giày", "thể thao", "bền". Rất đơn giản - và cũng rất giới hạn.

Vấn đề 1: Semantic gap. User muốn "giày bền" nhưng product description dùng từ "độ bền cao". Keyword search không match.

Vấn đề 2: Typo tolerance. "Nike" thay vì "Nike" → không ra kết quả.

Vấn đề 3: Intent mismatch. "Giày cho chạy bộ buổi sáng" là một intent cụ thể, nhưng keyword search chỉ thấy "giày", "chạy", "bộ", "buổi", "sáng" - không hiểu intent.

AI-powered search giải quyết những vấn đề này bằng cách understand semantic meaning, không chỉ match keywords.


Lộ trình migration 3 bước

Bước 1: Cải thiện Episerver Find với Better Configuration

Trước khi nghĩ đến AI, hãy tận dụng tối đa những gì Elasticsearch/Episerver Find đã có.

// Cấu hình Episerver Find với fuzzy matching và synonyms
SearchClient.Instance
    .Search<ProductContent>()
    .For("giay the thao", x => x
        .UseFuzzyMatching(Fuzziness.EditDistance(1))  // typo tolerance
        .InField(p => p.Name, boost: 3.0)
        .InField(p => p.Description, boost: 1.0)
        .InField(p => p.Categories.Select(c => c.Name), boost: 2.0)
    )
    .Filter(x => x.PublishedStatus.Match(true))
    .OrderByDescending(x => x.Relevance())
    .GetContentResult();

Thêm synonyms dictionary:

// elasticsearch_synonyms.txt
giầy, giày
nịt, belt
ao, áo
quan, quần
nike, nike, naik
adidas, adidas, adidass

Cấu hình fuzzy matching đúng có thể cải thiện typo tolerance đáng kể mà không cần thay đổi architecture.

Kết quả thực tế: Chúng tôi làm bước này trước và search quality cải thiện khoảng 20-30% chỉ với configuration.


Bước 2: Hybrid Search - Kết hợp Keyword + Vector

Đây là bước thêm semantic understanding mà không cần bỏ Episerver Find.

Ý tưởng: Khi user search, bạn chạy song song:

  1. Keyword search qua Episerver Find (như cũ)
  2. Vector similarity search qua Elasticsearch dense_vector field
  3. Merge và re-rank kết quả
public class HybridSearchService
{
    private readonly IClient _episeverFind;
    private readonly ElasticClient _elasticClient;
    private readonly IEmbeddingService _embeddingService;

    public async Task<IEnumerable<ProductContent>> SearchAsync(string query)
    {
        // Parallel execution
        var keywordTask = SearchWithEpiserver(query);
        var vectorTask = SearchWithVectors(query);

        await Task.WhenAll(keywordTask, vectorTask);

        // Reciprocal Rank Fusion để merge kết quả
        return MergeWithRRF(
            keywordTask.Result,
            vectorTask.Result,
            keywordWeight: 0.6,
            vectorWeight: 0.4
        );
    }

    private async Task<List<(ProductContent, double)>> SearchWithVectors(string query)
    {
        // Convert query thành embedding
        var queryVector = await _embeddingService.GetEmbeddingAsync(query);

        var response = await _elasticClient.SearchAsync<ProductDocument>(s => s
            .Index("products")
            .Query(q => q
                .ScriptScore(ss => ss
                    .Query(qq => qq.MatchAll())
                    .Script(sc => sc
                        .Source("cosineSimilarity(params.queryVector, 'description_vector') + 1.0")
                        .Params(p => p.Add("queryVector", queryVector))
                    )
                )
            )
            .Size(20)
        );

        return response.Hits
            .Select(h => (MapToProductContent(h.Source), h.Score ?? 0.0))
            .ToList();
    }
}

Lưu ý quan trọng: Vector field cần được thêm vào Elasticsearch index. Với Episerver Find, bạn có thể dùng một Elasticsearch index riêng cho vector search, không cần sửa index của Find.


Bước 3: AI Re-ranking

Sau khi có kết quả từ hybrid search, bước cuối cùng là dùng AI model để re-rank dựa trên relevance thực sự.

public class SearchReranker
{
    private readonly IChatClient _llmClient; // Claude, GPT-4o, hoặc local model

    public async Task<IEnumerable<ProductContent>> RerankAsync(
        string userQuery,
        IEnumerable<ProductContent> candidates)
    {
        // Dùng cross-encoder model để score từng candidate
        var scoringTasks = candidates.Select(async product =>
        {
            var relevanceScore = await ScoreRelevance(userQuery, product);
            return (product, relevanceScore);
        });

        var scored = await Task.WhenAll(scoringTasks);

        return scored
            .OrderByDescending(x => x.relevanceScore)
            .Select(x => x.product)
            .Take(10);
    }

    private async Task<double> ScoreRelevance(string query, ProductContent product)
    {
        // Với production, dùng dedicated cross-encoder model
        // Ví dụ: ms-marco-MiniLM-L-6-v2 chạy local
        // Không dùng GPT-4o/Claude cho từng result - quá tốn kém
        return await _crossEncoderModel.ScoreAsync(query, product.Description);
    }
}

Lưu ý về chi phí: Cross-encoder model nên chạy on-premise hoặc là model nhỏ. Đừng dùng GPT-4o/Claude để re-rank từng product - với 20 results và 100k queries/ngày, chi phí token sẽ rất cao.


Kinh nghiệm từ dự án thực

Chúng tôi đã implement lộ trình này trong 4 tháng:

  • Tháng 1: Better Episerver Find configuration (synonyms, fuzzy, boosting)
  • Tháng 2-3: Add vector field vào product index, implement embedding pipeline
  • Tháng 4: Hybrid search và A/B testing

Kết quả sau 4 tháng:

  • Search zero-results rate giảm từ 15% xuống 4%
  • Conversion rate từ search tăng 23%
  • User satisfaction (khảo sát) tăng đáng kể

Điều quan trọng nhất: Chúng tôi không cần rewrite Episerver Find. Hệ thống cũ vẫn chạy, chúng tôi chỉ thêm layer mới.


Triết lý

Có một pattern tôi thấy ở nhiều team khi nghĩ đến "AI search": muốn rewrite everything ngay lập tức.

Tôi không nghĩ đó là cách đúng. Hệ thống đang hoạt động tốt - dù không perfect - là valuable. Migration từng bước giúp bạn:

  1. Validate từng improvement với real data
  2. Rollback dễ nếu có vấn đề
  3. Team học dần thay vì bị overwhelm

"Make it work, then make it better" - vẫn còn đúng trong thời AI.


Bạn đã gặp tình huống này chưa?

Nếu bạn đang dùng Episerver Find hoặc bất kỳ keyword search nào và muốn upgrade lên AI search - bạn đang ở giai đoạn nào? Keyword enhancement, hybrid, hay full semantic? Tôi rất muốn nghe kinh nghiệm của bạn 👇


/Son Do - believe in basic

#1percentbetter #AIArchitecture #Episerver #SemanticSearch #VectorSearch #dotnet


Bài viết liên quan

Xem thêm
AI Integration — Góc nhìn Architect

LLM Wiki: Khi AI Không Còn Phải Đọc Codebase Từ Đầu Mỗi Ngày

LLM Wiki là pattern biến codebase thành "living knowledge base" – AI đọc một lần, nhớ mãi, kiến thức tích lũy theo thời gian. Khác với RAG truyền thống, LLM Wiki compound knowledge: mỗi lần hỏi đều thông minh hơn lần trước. Tôi nhớ lần đầu tiên tiếp cận một codebase e-commerce 5 năm tuổi. Hệ thống đang chạy ổn định, team cũ đã nghỉ gần hết, và tôi được giao nhiệm vụ thêm một tính năng tưởng đơn giản: hiển thị trạng thái tồn kho real-time trên trang sản phẩm. Ba ngày sau, tôi vẫn còn ngồi đọc code. Không phải vì code xấu. Mà vì không ai – kể cả những dev còn lại trong team – có thể giải thích được tại sao InventoryService lại gọi thẳng vào một endpoint của OrderService thay vì dùng shared database. Confluence có một trang wiki viết năm 2021 với nội dung "TODO: update this section." File README chỉ có hướng dẫn setup môi trường local. Đó là lần đầu tôi thực sự hiểu cái gọi là tribal knowledge – kiến thức nằm trong đầu người, không phải trong code.

AI Integration — Góc nhìn Architect

Prompt Engineering 2026: Không phải 1 góc nhìn — mà 40 góc nhìn song song

Năm 2026, AI agents đang bùng nổ và nhiều người vội vã tuyên bố "prompt engineering đã chết". Sai hoàn toàn. Chain of Thought, flipping roles — những kỹ thuật cơ bản đó vẫn còn sống, chỉ là giờ chúng ta không chạy 1 luồng nữa, mà chạy song song 40 luồng cùng lúc. Bài này là câu chuyện về sự tiến hóa đó. Hôm rồi tôi ngồi cà phê với một ông bạn — tôi gọi anh là Khoa cho tiện — senior developer 6 năm, đang chuyển sang làm AI engineer ở một startup khá hot trong nước. Anh mở màn bằng một câu khiến tôi suýt đổ cà phê: "Anh ơi, prompt engineering giờ chết rồi. Giờ phải học AI agents mới là đúng hướng." Tôi nhìn anh, hỏi: "Chết theo nghĩa nào?" Anh giải thích: "Thì giờ người ta build hệ thống multi-agent rồi, AI tự lo với nhau, mình chỉ cần thiết kế workflow là xong. Ai còn ngồi viết prompt thủ công nữa?" Tôi im lặng một lúc, rồi hỏi ngược: "Thế mấy cái agent đó nó tự nhiên biết làm việc không? Hay vẫn cần ai đó chỉ cho nó cách nghĩ?" Anh Khoa dừng lại. Ừ nhỉ.