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:
- Keyword search qua Episerver Find (như cũ)
- Vector similarity search qua Elasticsearch dense_vector field
- 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:
- Validate từng improvement với real data
- Rollback dễ nếu có vấn đề
- 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