如何用 Go 來構建 LLM 應用
隨着大型語言模型(LLM)及其相關工具(如嵌入模型)在過去一年中能力顯著提升,越來越多的開發者考慮將 LLM 集成到他們的應用程序中。
由於 LLM 通常需要專用硬件和大量計算資源,它們通常作爲網絡服務打包,提供 API 供訪問。這就是 OpenAI 和 Google Gemini 等領先 LLM 的 API 工作方式;即使是像 Ollama 這樣的自託管 LLM 工具,也將 LLM 封裝在 REST API 中以供本地使用。此外,利用 LLM 的開發者通常還需要補充工具,如向量數據庫,這些數據庫大多也作爲網絡服務部署。
換句話說,LLM 驅動的應用程序與其他現代雲原生應用程序非常相似:它們需要對 REST 和 RPC 協議、併發性和性能的出色支持。這正是 Go 語言擅長的領域,使其成爲編寫 LLM 驅動應用程序的理想選擇。
這篇博客文章通過一個使用 Go 構建簡單 LLM 驅動應用程序的示例進行說明。文章首先描述了演示應用程序所解決的問題,然後展示了幾種實現相同任務但使用不同包的應用程序變體。所有演示代碼均可在線獲取。
RAG 服務器用於問答
一種常見的 LLM 驅動應用技術是 RAG(檢索增強生成)。RAG 是爲特定領域交互定製 LLM 知識庫最具可擴展性的方法之一。
我們將構建一個 RAG 服務器,這是一個 HTTP 服務器,爲用戶提供
兩項操作:
-
向知識庫添加文檔
-
向 LLM 提問關於該知識庫的問題
在典型的現實場景中,用戶會向服務器添加一組文檔,然後提出問題。例如,一家公司可以將內部文檔填充到 RAG 服務器的知識庫中,以便爲內部用戶提供 LLM 驅動的問答功能。
以下是展示我們服務器與外部世界交互的圖示:
除了用戶發送 HTTP 請求(上述兩項操作),服務器還與以下組件進行交互:
-
嵌入模型,用於計算提交文檔和用戶問題的向量嵌入。
-
向量數據庫,用於高效存儲和檢索嵌入。
-
LLM,根據從知識庫收集的上下文提問。
具體來說,服務器向用戶公開兩個 HTTP 端點:
/add/: POST {"documents": [{"text": "..."}, {"text": "..."}, ...]}
:提交一系列文本文檔,以添加到其知識庫中。
對於此請求,服務器:
-
使用嵌入模型計算每個文檔的向量嵌入。
-
將文檔及其向量嵌入存儲在向量數據庫中。
/query/: POST {"content": "..."}
:向服務器提交一個問題。對於此請求,服務器:
-
使用嵌入模型計算問題的向量嵌入。
-
使用向量數據庫的相似性搜索找到與問題最相關的知識庫中的文檔。
-
使用簡單的提示工程,將步驟(2)中找到的最相關文檔作爲上下文重新表述問題,並將其發送給 LLM,返回答案給用戶。
我們演示中使用的服務包括:
-
Google Gemini API 用於 LLM 和嵌入模型。
-
Weaviate 作爲本地託管的向量數據庫;Weaviate 是用 Go 實現的開源向量數據庫。
用其他等效服務替換這些服務應該非常簡單。事實上,這正是服務器第二和第三個變體所涉及的內容!我們將從第一個變體開始,該變體直接使用這些工具。
直接使用 Gemini API 和 Weaviate
Gemini API 和 Weaviate 都有方便的 Go SDK(客戶端庫),我們的第一個服務器變體直接使用這些。該變體的完整代碼在此目錄中。
我們不會在這篇博客文章中重現整個代碼,但在閱讀時請注意以下幾點:
結構:代碼結構對任何編寫過 Go HTTP 服務器的人來說都很熟悉。Gemini 和 Weaviate 的客戶端庫被初始化,並且客戶端存儲在傳遞給 HTTP 處理程序的狀態值中。
路由註冊:使用 Go 1.22 引入的路由增強功能,設置我們服務器的 HTTP 路由非常簡單:
mux := http.NewServeMux()
mux.HandleFunc("POST /add/", server.addDocumentsHandler)
mux.HandleFunc("POST /query/", server.queryHandler)
併發性:我們服務器的 HTTP 處理程序會通過網絡與其他服務進行交互,並等待響應。這對 Go 來說不是問題,因爲每個 HTTP 處理程序都在自己的 goroutine 中併發運行。這個 RAG 服務器可以處理大量併發請求,每個處理程序的代碼都是線性和同步的。
批量 API:由於/add/
請求可能提供大量要添加到知識庫中的文檔,因此服務器利用_批量 API_ 來提高效率,包括嵌入(embModel.BatchEmbedContents
)和 Weaviate 數據庫(rs.wvClient.Batch
)。
使用 LangChain for Go
我們的第二個 RAG 服務器變體使用 LangChainGo 來完成相同任務。LangChain 是一個流行的 Python 框架,用於構建 LLM 驅動應用程序。LangChainGo 是其 Go 版本。該框架有一些工具,可以將應用程序構建爲模塊化組件,並支持許多 LLM 提供商和向量數據庫,提供統一 API。這使得開發者可以編寫適用於任何提供商且能夠輕鬆更換提供商的代碼。
該變體的完整代碼在此目錄中。閱讀代碼時你會注意到兩點:
首先,它比前一個變體短一些。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
今年早些時候,谷歌推出了 Genkit for Go——一個用於構建 LLM 驅動應用的新開源框架。Genkit 與 LangChain 有一些相似之處,但在其他方面有所不同。
像 LangChain 一樣,它提供可以由不同提供商(作爲插件)實現的通用接口,從而簡化了從一個到另一個提供商之間切換。然而,它並不試圖規定不同 LLM 組件如何交互;相反,它專注於生產特性,如提示管理和工程,以及集成開發工具進行部署。
我們的第三個 RAG 服務器變體使用 Genkit for Go 來完成相同任務。它的完整代碼在此目錄中。
這個變體與 LangChainGo 相似——使用通用接口而不是直接提供商 API,使得從一個切換到另一個更容易。此外,使用 Genkit 將 LLM 驅動應用程序部署到生產環境要容易得多;我們在這個變體中沒有實現這一點,但如果你感興趣,可以查看文檔。
總結 - Go 用於 LLM 驅動應用程序
本文中的示例僅展示了在 Go 中構建 LLM 驅動應用程序的一部分可能性。它展示瞭如何用相對較少的代碼構建強大的 RAG 服務器;最重要的是,由於某些基本 Go 特性,這些示例具有顯著程度的生產就緒性。
與 LLM 服務一起工作通常意味着發送 REST 或 RPC 請求到網絡服務,等待響應,然後根據響應發送新請求到其他服務等等。Go 在這些方面表現出色,爲管理併發性和處理網絡服務複雜性提供了極好的工具。
此外,作爲雲原生語言,Go 出色的性能和可靠性使其成爲實現 LLM 生態系統更基本構建塊的自然選擇。一些示例項目包括 Ollama、LocalAI、Weaviate 或 Milvus。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Hmq76Hn-7ywJAW555uF_LA