傳統搜索系統基於關鍵字匹配,在面向:遊戲攻略、技術圖譜、知識庫等業務場景時,缺少對用戶問題理解和答案二次處理能力。
本文探索使用大語言模型(Large Language Model, LLM),通過其對自然語言理解和生成的能力,揣摩用戶意圖,並對原始知識點進行彙總、整合,生成更貼切的答案。
關於基本思路,驗證效果和擴展方向,可以參考正文的介紹。
需求描述
打造 特定領域知識(Domain-specific Knowledge) 問答 系統,具體需求有:
-
通過自然語言問答的形式,和用戶交互,同時支持中文和英文。 -
理解用戶不同形式的問題,找到與之匹配的答案。可以對答案進行二次處理,比如將關聯的多個知識點進行去重、彙總等。 -
支持上下文。有些問題可能比較複雜,或者原始知識不能覆蓋,需要從歷史會話中提取信息。 -
準確。不要出現似是而非或無意義[1]的回答。
從大語言模型(Large Language Model, LLM)[2]角度而言,上面的需求是在兩階段訓練模式下,面向下游場景進行適配的問題。基礎模型(Foundation Model[3]),面向特定領域不能直接應用,因爲領域知識不在預訓練的數據集中,比如:
-
較新的內容。同一個知識點不斷變更:修改、刪除、添加。如何反饋當前最新的最全面的知識。比如對於 ChatGpt 而言,訓練數據全部來自於 2021.09 之前。 -
未公開的、未聯網的內容。 大語言模型兩階段訓練示意圖
方案分析
基於 LLM 搭建問答系統的解決方案有以下幾種:
-
Fine-Tuning -
基於 Prompt Engineering[4],比如 Few-Shot 方式。 -
與普通搜索結合,使用基礎模型對搜索結果加工。
Fine-Tuning
使用下游特定領域的知識對基礎模型進行微調,改變神經網絡中參數的權重。業界已經不少 chatgpt 的平替方案都支持微調,比如:
-
清華大學於 2023.03 提出的 ChatGLM[5]支持中英雙語,具有 62 億參數,可以在消費級顯卡上部署,INT4 量化級別下最低只需要 6GB 顯存。 -
Alpaca[6] 是在 Meta 提出的 LLaMA 7B 模型基礎上微調的結果。原生的 Alpaca 對中文的支持並不好,不過已經業界也做了些擴充中文詞表的開源方案[7]。
微調方式比較適合特化的任務或風格,但也存在一些問題:
-
沒有解決事實性問答可靠性的問題。 -
消耗的資源量雖然相對大模型預訓練減少,但還是不容小覷的。比如 Alpaca 的微調,據作者介紹他們使用 8 個 顯存 80GB A100 ,花費了 3 個小時。如果領域支持頻繁更新,且需要需要較高的實時性,顯然是無法滿足要求的。 -
需要構建特定領域微調的訓練語料,可以參考Dataset Engineering for LLM finetuning[8]。如果想要獲得較好的結果,高質量訓練數據集的構建需要精心設計,開銷也是不容忽視的。 -
微調的結果不一定符合預期。在 ChatGLM-6B 微調實踐中可以發現,使用 ADGEN 數據集微調後,模型對“廣告詞生成”任務的確變好,但其他任務的回答均不如原始模型。
基於 Prompt
將特定領域的知識作爲輸入消息提供給模型。類似於短期記憶,容量有限但是清晰。舉個例子給 ChatGPT 發送請求,將特定的知識放在請求中,讓 ChatGPT 對消息中蘊含的知識進行分析,並返回處理結果。
-
優勢。正確性和精度高。 -
劣勢。一次可以處理的文本量有限制,如果知識庫較大,無論從可行性還是效率而言都是不合適的。Chatgpt 的限制[9]如下表所示:
Model | Maximum text length |
---|---|
gpt-3.5-turbo | 4,096(~5 pages) |
gpt-4 | 8,192(~10 pages) |
gpt-4-32k | 32,768(~40 pages) |
與搜索結合
Fine-Tuning 和基於 Prompt 方式均存在缺陷,比如效率低下、數據不夠精確、不能支持大規模數據量等問題。這裏提出第三種方法,嘗試克服這些困難,基本思想是:
-
使用傳統搜索技術構建基礎知識庫查詢。好處在於: -
問答可控性更高一些 -
無論是數據規模、查詢效率、更新方式都可以滿足常見知識庫應用場景的需要 -
技術棧成熟,探索風險低 -
使用 LLM 作爲用戶和搜索系統件溝通的介質,發揮其強大的自然語言處理能力:對用戶請求進行糾錯、提取關鍵點等預處理實現 “理解”;對輸出結果在保證正確性的基礎上二次加工,比如——概括、分析、推理等。
整個方案設計如下圖所示由兩部分組成:
-
LLM。主要功能有以下幾點: -
完成對用戶問題的預處理。糾正語法錯誤,提取關鍵點,通過交互方式引導用戶補充問題足夠多的信息等。 -
對本地搜索系統的原始答案進行二次處理。比如內容過多,可以進行概括;也可以進行簡單推理。 -
提供上下文交互的能力。一個常見的例子是 “比較”,比如遊戲中販售道具,倚天劍和屠龍刀。原始知識系統只會提供兩件兵器的基礎屬性,但不會提供各屬性的對比和總體評價。提問的過程可以是:
-
玩家諮詢倚天劍的屬性 -
玩家諮詢屠龍刀的屬性 -
玩家要求比較倚天劍和屠龍刀。這裏 LLM 已經獲取兩件兵器的屬性,使用既有的推理能力進行對比。在同一個會話過程中,可以讓 LLM 從會話歷史中提取信息並分析。
-
Lexical-based search。通過歸一化、拼寫糾錯、擴展、翻譯等方式對查詢請求中的詞進行替換。性能好、可控性強,儘管存在一些語義鴻溝問題,但仍被廣泛的應用在現有的搜索引擎架構中。 -
Graph-based search。以圖的形式描述知識點以及相互間的關係,然後通過圖搜索算法尋找與查詢請求匹配的結果。 -
Embedding-based search。將文字形式的查詢請求,編碼爲數值向量的形式,體現潛在的關係。該文[11]介紹了 Word Embedding 的一些技術實現。
方案實現
本文的實現,參考了 OpenAI 提供的樣例,主要理由是 ChatGPT 對外提供了良好的 API 以及中英文支持。但從框架角度而言,不會綁死在 OpenAI 上,每一個具體實現都可以由業務結合自己的需求進行替換。上節提到的方案落地到 ChatGPT 上特化爲:
說明如下:
-
使用 OpenAI 的 Embedding 接口將專業領域知識轉化爲向量,連同原始材料一併保存在 Redis 中。 -
用戶提問的搜索處理: -
使用 OpenAI API 對用戶的問題進行 Embedding,獲得向量。 -
使用問題向量在 Redis 中搜索,找到與之最匹配的若干記錄。將這些記錄的原始材料返回。 -
使用 OpenAI 的 Completion API 對這些原始材料進行加工完善,並將最終結果返回。
下面對上述過程展開描述。
領域知識入庫
該過程的主要目的是:將原始知識庫分拆爲若干知識點,並生成與之對應的字典:
-
key 是知識點 Embedding 之後生成的向量 -
value 是知識點的原始記錄
該字典的作用是用戶提問時,通過 Embedding 之後的向量比對,實現問題和答案的匹配。具體過程涉及以下幾點,如圖所示:
-
數據源可能來自於網絡(遊戲已經對外的攻略)、本地文本文件(技術文檔、設計稿)或者數據庫(業務自己維護的 UGC,比如用戶帖子、評論等)。採用合適的方式收集這些數據並整理爲純文本的格式。這裏提供一個 python 庫textract[12],支持從多種類型文件中提取文字信息,普通文本文件自不必說,其它各種常用格式文件也都支持,比如:Microsoft 全家桶 docx, xlsx;圖像 gif, jpg 等;音頻文件 mp3, ogg 等。 -
生成分詞器 tokenizer,將文本分成一個個詞元,保證各個詞元擁有相對完整和獨立的語義,以供後續任務比如 Embedding 使用。tiktoken[13]是一種 Byte Pair Encoding(BPE)[14] 分詞器,有多種編碼方法可選,如:r50k_base, p50k_base, cl100k_base 等。面向 OpenAI 的 gpt-4, gpt-3.5-turbo 和 text-embedding-ada-002 模型通常使用 cl100k_base 編碼方法。 -
分片。將原始知識庫拆分爲若干個獨立、較短的知識點。每個知識點會作爲問答的最小記錄,與問題進行匹配。在實際使用過程中有以下幾點建議:
-
原始內容在編寫、組織時最好原子化、正交化。對於樹狀結構的知識點,可以按層級關係表示,最好不要混爲一談。比如倚天劍可能基礎屬性,也有適合的打法,偏向的英雄天賦,那麼三者應該獨立描述,而不要混雜在一起。 -
可以在原始語料中設計明確的分片標記,簡化處理過程。對於 html、markdown 等類型的文檔而言,天然結構化處理會簡單一些。 -
基本的分片方式。粒度從細到粗可以使用,標點符號、段落、章節等進行區分。分片粒度過細,知識點會比較零碎影響了相互間的關係;分片粒度過粗,在匹配時可能會攜帶冗餘信息,另外對 Embedding、處理、索引的效率也有影響。 -
分片要使用 tokenizer,原始文本經過分詞然後再進行 embedding,分片大小需要考慮分詞之後生成的 token 數量。基本目標是:分片不能破壞知識點的完整性,生成的分片對應的 token 數量應該在預設範圍內,不要過小或過大。
openai.Embedding.create
接口。-
RediSearch 提供的 Vector Similarity[16] ,支持使用向量字段和向量相似性查詢。它可以加載、索引和查詢存儲在 Redis 哈希或 JSON 文檔(通過與 RedisJSON 模塊集成)中的向量。Vector Similarity 提供了實時向量索引、實時向量更新/刪除、K-最近鄰(KNN)搜索和範圍過濾等功能。 -
pgvector[17]基於 PostgresQL,提供了類似的向量索引支持。和 Redis 的基本功能差不多,在向量距離計算方面,也提供了:L2、點積和 COSINE 這三種方法。使用 Redis 比較簡單高效,接口和文檔非常豐富,如果沒有特別要求可以直接使用。
搜索
搜索的核心流程包含兩步:
-
將用戶的問題通過 OpenAI API openai.Embedding.create
進行 embedding 得到向量。 -
向 redis 發起查詢獲得與之最匹配(距離最近、相似度最高)的若干答案。
除此外,也可以利用 LLM 對用戶的問題進行預處理,常見的方式有:
-
簡化概括用戶的問題 -
利用思維鏈(Chain-Of-Thought, COT)提示的能力,提供範本,讓 LLM 按樣例和用戶進行交互,將問題逐步完善,直到獲取足夠的信息爲止。以遊戲 NBA2K 爲例,球員的打法在不同比賽模式中是不同的,比如王朝 5v5 和街頭 3v3 就不一樣。用戶可能並未意識到這一點,希望 LLM 能夠自動和玩家交互直到獲得:問題和比賽模式兩個信息,再進行處理。細節可以參考知乎的文章ChatGPT 系列教程—提問篇:Prompt 的高級概念[18],本文不再贅述。這裏僅提供一個示例,告知 ChatGPT 使用案例中的樣式和玩家交互: 你是一個遊戲客服。你需要在和用戶的交流過稱中提取一個問題和比賽模式這兩個信息。請按照下面的方式一步步思考:- 玩家問了一個問題 - 如果問題中沒有包含比賽模式,你需要請他提供比賽模式信息,比如可以問他:你指的是哪一種比賽模式?- 一旦你獲得了比賽模式,那麼說:好的,開始爲您查找。例子:User:請問科比的打法是什麼?Assistant:你指的是哪一種比賽模式?User: 王朝模式 Assistant: 好的,開始爲您查找。
對於 ChatGPT 而言,上述這種預設對話行爲來引導用戶的方式稱之爲 ChatCompletion
,可以 openai.ChatCompletion.create
api,將多輪會話的上下文整合起來,對提問和回答過程提供更加強大、靈活的定製能力。比如:
-
要求用戶的提問必須提供足夠的指定類型的信息。 -
爲用戶提供的多輪信息進行進行總結。
交互式會話中, 提供了三種不同的角色(role):user,system,assistant。
-
user 代表用戶,記錄用戶的提問 -
system 用於向 chatgpt 發出指令,定義其應答行爲 -
assistant 代表 chatgpt 返回的結果
具體可以參考 ChatGPT API Transition Guide | OpenAI Help Center[19]
結果整合
結果整合的主要作用是將本地搜索系統返回的結果進行二次加工,比如發揮 LLM 的:
-
總結、概括 -
格式整理 -
去重、翻譯 -
從會話歷史中,提取上下文,進行分析處理等能力
實現的方法還是基於 ChatCompletion
,方式很多,業務完全可以結合場景自由發揮。這裏提供一個在 NBA2K Online2 中實現的方式:
-
首先交互式提問完成後,對會話歷史進行總結,讓 ChatGPT 用一句話概括,須包含問題和賽季信息。 -
基於該問題,進行 Embedding 並在本地搜索,搜索的結果可能有多條,也可能沒找到。如果找不到則返回特殊語言標記,比如:本地知識庫找不到。 -
以 ChatCompletion 的方式和 OpenAI 交互,讓 ChatGPT 基於本地搜索結果和歷史會話,進行總結整理。需要以 system 的身份,注入 Prompt 定義 ChatGPT 的應答行爲。具體方法是:
-
本地 search result 有效,可能有多條最貼近的知識點,則整理總結作爲最終結果。 -
本地 search result 返回特殊標記,比如:本地知識庫找不到,則基於 conversation history 分析;如果還是找不到則提示找不到。
-
基於用戶的請求 user_query 觸發本地搜索,獲得答案 search result -
將答案以 system role 的身份插入 conversation history 中,要求對於用戶發出的請求 user_query 使用 search result 回覆 -
將包含本地搜索答案的 system 指令和用戶問題依次推入 conversation history -
交給 chatgpt 的 ChatCompletion 處理:
應用效果
上述方案在測試過程中,以 NBA2K Online2 官網的攻略信息[20]爲基礎進行嘗試,基於 OpenAI API,搭建簡單的 CLI 的應用。效果如下所示(爲了簡化過程,一律省略多輪交互問答的過程)。
基礎能力
對問題在本地進行搜索,找到多條匹配語料,然後自動整合使用無序列表的形式返回。
-
問題:關於詹姆斯的打法。
-
本地 Redis 搜索命中的關聯度最大的若干條答案:
本地知識庫搜索命中的原始材料 -
整合後的反饋:
使用 LLM 整合概括的結果
基於會話歷史的問答
知識庫中僅保存了:奧拉朱旺、科比、詹姆斯三人各自的打法信息,並沒有直接提供三者的比較。所以如果僅通過一個問題要求比較三人的打法差異,是無法在 Redis 中直接匹配命中的。但是可以使用會話歷史,當本地無法命中時,讓 ChatGPT 基於過往的信息自動進行整合,如下所示:
總結
本文針對特定領域知識問答系統的問題,進行方案比較和選型。不難發現:傳統的搜索模式、LLM 的 Fine-Tuning、Prompt Engineer 等方式均存在不同程度的缺陷。經過分析比較後,決定探索 LLM +搜索 的方式進行處理,並在 NBA2K Online2 攻略應用場景進行驗證。該方法:
-
將本地知識通過傳統搜索框架進行處理,並作爲答案的基礎數據源。這保證了答案的精準和可靠。 -
同時基於 Prompt Engineering 激發 LLM 的自然語言理解、生成和簡單推理能力,對用戶的問題預處理、對原始答案進行加工。從而提供了更加智能和友好的交互方式。
在實踐過程中,選擇 ChatGPT 作爲 LLM 的經典實現,使用 RediSearch 提供的 Vector Similarity 作爲問題答案的匹配索引框架。但 LLM+搜索的方式在框架上是非常通用的,不侷限於上述選擇,業務完全可以基於自身場景使用其他基礎模型和搜索方案。另外業務在和 LLM 交互過程中也可以定製更加靈活、智能的提示詞來引導交互過程。本文的細節實踐僅供參考,希望可以起到拋磚引玉的效果。
參考資料
[2]大語言模型(Large Language Model, LLM): https://research.aimultiple.com/large-language-models/
[3]Foundation Model: https://en.wikipedia.org/wiki/Foundation_models
[4]Prompt Engineering: https://www.allabtai.com/prompt-engineering-tips-zero-one-and-few-shot-prompting/
[5]ChatGLM: https://github.com/THUDM/ChatGLM-6B
[6]Alpaca: https://github.com/tatsu-lab/stanford_alpaca
[7]擴充中文詞表的開源方案: https://link.juejin.cn/?target=https://arxiv.org/pdf/2304.08177v1.pdf
[8]Dataset Engineering for LLM finetuning: https://www.flowrite.com/blog/dataset-engineering-llm-finetuning
[9]Chatgpt 的限制: https://github.com/openai/openai-cookbook/blob/main/examples/Question_answering_using_embeddings.ipynb?spm=wolai.workspace.0.0.357e11a2XSQ9wO&file=Question_answering_using_embeddings.ipynb
[10]Search: Query Matching via Lexical, Graph, and Embedding Methods: https://eugeneyan.com/writing/search-query-matching/
[11]該文: https://medium.com/intelligentmachines/word-embedding-and-one-hot-encoding-ad17b4bbe111
[12]textract: https://textract.readthedocs.io/en/stable/
[13]tiktoken: https://github.com/openai/tiktoken
[14]Byte Pair Encoding(BPE): https://en.wikipedia.org/wiki/Byte_pair_encoding
[15]vector database: https://learn.microsoft.com/en-us/semantic-kernel/concepts-ai/vectordb
[16]Vector Similarity: https://redis.io/docs/stack/search/reference/vectors/
[17]pgvector: https://github.com/pgvector/pgvector
[18]ChatGPT 系列教程—提問篇:Prompt 的高級概念: https://zhuanlan.zhihu.com/p/623395924
[19]ChatGPT API Transition Guide | OpenAI Help Center: https://www.wolai.com/9fBmz1E4WZWbHJZQYKHTji.md
[20]攻略信息: https://nba2k2.qq.com/act/a20200520apph5/app/index.html#/ol2/news/6366
作者:simon