構建由大型語言模型(LLM)驅動的 Go 應用程序
本文由 Eli Bendersky 於 2024 年 9 月 12 日發表在 https://go.dev/blog
隨着過去一年大型語言模型(LLM)及其相關工具(如嵌入模型)的能力顯著增長,越來越多的開發者開始考慮將 LLM 集成到他們的應用程序中。
由 於 LLM 通常需要專用硬件和大量的計算資源,它們最常見的形式是作爲提供 API 訪問的網絡服務。這就是領先的 LLM 如 OpenAI 或 Google Gemini 的 API 的工作方式;即使是運行你自己的 LLM 工具,如 Ollama[1],也會將 LLM 包裝在 REST API 中供本地使用。此外,利用 LLM 的開發者通常還需要輔助工具,如向量數據庫,這些通常也作爲網絡服務部署。
換句話說,LLM 驅動的應用程序很像其他現代雲原生應用程序:它們需要對 REST 和 RPC 協議、併發性和性能有出色的支持。這些恰好是 Go 擅長的領域,使它成爲編寫 LLM 驅動應用程序的絕佳語言。
這篇博客文章通過一個簡單的例子,展示瞭如何使用 Go 爲 LLM 驅動的應用程序工作。它首先描述了演示應用程序要解決的問題,然後展示了幾個變體的應用程序,它們都完成了相同的任務,但使用了不同的包來實現。這篇帖子的所有演示代碼都可以在線獲取 [2]。
用於問答的 RAG 服務器
LLM 驅動應用程序的一種常見技術是 RAG - Retrieval Augmented Generation[3]。RAG 是爲特定領域的交互定製 LLM 知識庫的最可擴展方式之一。
我們將用 Go 構建一個 RAG 服務器。這是一個 HTTP 服務器,爲用戶提供兩個操作:
-
向知識庫添加文檔
-
向 LLM 提問有關這個知識庫的問題
在典型的現實世界場景中,用戶會向服務器添加一批文檔,然後開始提問。例如,一家公司可以用內部文檔填充 RAG 服務器的知識庫,並使用它爲內部用戶提供 LLM 驅動的問答能力。
以下是顯示我們服務器與外部世界交互的圖:
除了用戶發送 HTTP 請求(上述兩個操作)之外,服務器還與以下內容交互:
-
一個嵌入模型,用於計算提交的文檔和用戶問題的向量嵌入 [4]。
-
一個向量數據庫,用於存儲和高效檢索嵌入。
-
一個 LLM,用於根據從知識庫收集的上下文提出問題。具體來說,服務器向用戶公開兩個 HTTP 端點:
-
/add/: POST {"documents": [{"text": "..."}, {"text": "..."}, ...]}: 向服務器提交一系列文本文檔,以便添加到其知識庫中。對於此請求,服務器:
-
使用嵌入模型爲每個文檔計算向量嵌入。
-
將文檔及其向量嵌入存儲在向量數據庫中。
-
/query/: POST {"content": "..."}: 向服務器提交一個問題。對於此請求,服務器:
-
使用嵌入模型計算問題的向量嵌入。
-
使用向量數據庫的相似性搜索找到知識庫中最相關文檔。
-
使用簡單的提示工程將問題重新表述爲在步驟(2)中找到的最相關文檔作爲上下文,並將其發送給 LLM,將答案返回給用戶。
我們的演示使用的服務包括:
-
Google Gemini API 用於 LLM 和嵌入模型。
-
Weaviate[5] 作爲本地託管的向量數據庫;Weaviate 是一個用 Go 實現 [6] 的開源向量數據庫。
-
應該很容易用其他等效服務替換這些。事實上,這就是第二個和第三個服務器變體的全部內容!我們將從直接使用這些工具的第一個變體開始。
直接使用 Gemini API 和 Weaviate
Gemini API 和 Weaviate 都有方便的 Go SDK(客戶端庫),我們的第一個服務器變體直接使用這些。這個變體的完整代碼在這個目錄 [7] 中。
我們不會在這篇博客文章中複製整個代碼,但在閱讀時請注意以下幾點:
-
結構:代碼結構對於任何寫過 Go HTTP 服務器的人來說都很熟悉。Gemini 和 Weaviate 的客戶端庫被初始化,客戶端存儲在傳遞給 HTTP 處理程序的狀態值中。
-
路由註冊:使用 Go 1.22 中引入的路由增強 [8] 功能,我們的服務器的 HTTP 路由很容易設置:
mux := http.NewServeMux() mux.HandleFunc("POST /add/", server.addDocumentsHandler) mux.HandleFunc("POST /query/", server.queryHandler)
-
併發性:我們的服務器的 HTTP 處理程序通過網絡訪問其他服務並等待響應。這對 Go 來說不是問題,因爲每個 HTTP 處理程序都在自己的 goroutine 中併發運行。這個 RAG 服務器可以處理大量併發請求,每個處理程序的代碼都是線性和同步的。
-
批量 API:由於 /add/ 請求可能提供大量文檔添加到知識庫,服務器利用嵌入(embModel.BatchEmbedContents)和 Weaviate DB(rs.wvClient.Batch)的批量 API 以提高效率。
使用 LangChainGo
我們的第二個 RAG 服務器變體使用 LangChainGo 來完成相同的任務。
LangChain[9] 是一個流行的 Python 框架,用於構建 LLM 驅動的應用程序。LangChainGo[10] 是它的 Go 框架。該框架有一些工具,可以將應用程序構建爲模塊化組件,並支持許多 LLM 提供商和向量數據庫的通用 API。這允許開發者編寫可以與任何提供商一起工作的代碼,並很容易地更改提供商。
這個變體的完整代碼在這個目錄 [11] 中。在閱讀代碼時,你會注意到兩件事:
-
首先,它比之前的變體稍短。LangChainGo 負責包裝向量數據庫的完整 API 到通用接口中,初始化和處理 Weaviate 所需的代碼更少。
-
其次,LangChainGo API 使得切換提供商相當容易。假設我們想用另一個向量數據庫替換 Weaviate;在我們之前的變體中,我們將不得不重寫所有與向量數據庫接口的代碼以使用新的 API。有了像 LangChainGo 這樣的框架,我們就不再需要這樣做了。只要 LangChainGo 支持我們感興趣的新向量數據庫,我們應該能夠替換我們服務器中的幾行代碼,因爲所有數據庫都實現了一個通用接口:
type VectorStore interface { AddDocuments(ctx context.Context, docs []schema.Document, options ...Option) ([]string, error) SimilaritySearch(ctx context.Context, query string, numDocuments int, options ...Option) ([]schema.Document, error) }
使用 Genkit for Go
今年早些時候,Google 爲 Go 引入了 Genkit[12] - 一個構建 LLM 驅動應用程序的新開源框架。Genkit 與 LangChain 有一些共同的特點,但在其他方面有所不同。
像 LangChain 一樣,它提供了可以由不同提供商(作爲插件)實現的通用接口,從而使從一個提供商切換到另一個提供商變得更簡單。然而,它並不試圖規定不同的 LLM 組件如何交互;相反,它專注於生產特性,如提示管理和工程,以及集成開發工具的部署。
我們的第三個 RAG 服務器變體使用 Genkit for Go 來完成相同的任務。它的完整代碼在這個目錄 [13] 中。
這個變體與 LangChainGo 非常相似 - 使用 LLM、嵌入器和向量數據庫的通用接口,而不是直接提供 API,使得從一個切換到另一個變得更容易。此外,使用 Genkit 將 LLM 驅動的應用程序部署到生產環境要容易得多;我們沒有在我們的變體中實現這一點,但如果你感興趣,可以閱讀文檔 [14]。
總結 - Go 用於 LLM 驅動的應用程序
這篇文章中的示例只是構建 Go 中 LLM 驅動應用程序的可能性的一小部分。它展示瞭如何用相對較少的代碼構建一個功能強大的 RAG 服務器;最重要的是,由於 Go 的一些基本特性,這些示例具有相當程度的生產準備度。
與 LLM 服務合作通常意味着向網絡服務發送 REST 或 RPC 請求,等待響應,根據該響應向其他服務發送新請求等等。Go 在所有這些方面都表現出色,爲管理併發性和處理網絡服務的複雜性提供了出色的工具。
此外,Go 作爲雲原生語言的卓越性能和可靠性使其成爲實現 LLM 生態系統更基本構建塊的自然選擇。一些例子包括 Ollama、LocalAI[15]、Weaviate 或 Milvus[16] 等項目。
參考資料
[1]
ollama: https://ollama.com/
[2]
go.dev ragserver demo: https://github.com/golang/example/tree/master/ragserver
[3]
RAG: https://en.wikipedia.org/wiki/Retrieval-augmented_generation
[4]
vector embedding: https://en.wikipedia.org/wiki/Sentence_embedding
[5]
Weaviate: https://weaviate.io/
[6]
weaviate github: https://github.com/weaviate/weaviate
[7]
ragserver demo: https://github.com/golang/example/tree/master/ragserver/ragserver
[8]
routing-enhancements: https://go.dev/blog/routing-enhancements
[9]
LangChain: https://www.langchain.com/
[10]
langchaingo: https://github.com/tmc/langchaingo
[11]
ragserver langchaingo: https://github.com/golang/example/tree/master/ragserver/ragserver-langchaingo
[12]
Genkit: https://developers.googleblog.com/en/introducing-genkit-for-go-build-scalable-ai-powered-apps-in-go/
[13]
ragserver-genkit: https://github.com/golang/example/tree/master/ragserver/ragserver-genkit
[14]
genkit start doc: https://firebase.google.com/docs/genkit-go/get-started-go
[15]
localai: https://localai.io/
[16]
milvus: https://zilliz.com/what-is-milvus
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/W5OpauBtdhsUu6AEqeP53w