到目前為止,我們的搜尋引擎已經可以把資料載入(Day 20)、解析、索引(Day 21),甚至在 Day 22 嘗試了一些基本的文字查詢。
很多時候,我們並不是在「精確地查一個詞」,而是在「讓系統懂我們想問什麼」。
例如使用 JEOPARDY! 資料集時,一個使用者可能輸入:
“Which US president was assassinated in 1865?”
這個問題其實背後包含多層意圖:
這就不是單純的 keyword search 可以解決的事,而是需要一種**多階段查詢(multi-stage query)**設計。
以我們的 JEOPARDY! 搜尋服務為例,可以拆成以下階段:
{
"bool": {
"must": [
{ "match": { "question": "US president" }},
{ "match": { "question": "killed" }},
{ "match": { "question": "1865" }}
]
}
}
下面是一個簡化的 Go handler,展示如何把上述流程包成 pipeline:
func multiStageSearchHandler(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("q")
if q == "" {
http.Error(w, "missing query parameter: q", http.StatusBadRequest)
return
}
ctx := r.Context()
// Step 1: 基本語意分析(之後可以改用 OpenAI embeddings)
keywords := expandSynonyms(q)
// Step 2: 組合查詢
esQuery := buildElasticsearchQuery(keywords)
// Step 3: 向 Elasticsearch 查詢
results, err := esClient.Search(ctx, esQuery)
if err != nil {
log.Printf("ES search error: %v", err)
http.Error(w, "search failed", http.StatusInternalServerError)
return
}
// Step 4: 結果回傳
json.NewEncoder(w).Encode(results)
}
多階段查詢的核心,其實是把「人的模糊語意」翻譯成「電腦能理解的結構化條件」。
這樣的分層架構帶來幾個明顯好處:
模組 | 功能 | 關聯 |
---|---|---|
資料載入(Day 20) | 把 Jeopardy CSV 轉成可查詢的 NDJSON | 為查詢提供原料 |
索引建立(Day 21) | 建立 ES 結構 | 為複雜查詢鋪路 |
基本查詢(Day 22) | 可搜尋 question 與 answer 欄位 | 驗證基礎連線與功能 |
多階段查詢(Day 23) | 加入語意層次與條件組合 | 讓搜尋更貼近「人」 |
如果把搜尋引擎比喻成一位聰明的問答助理,前幾天的工作是在教他「資料在哪裡」與「怎麼找」。
今天的任務,則是在教他「怎麼聽懂人話」。