在 Tenten,我們使用多個大型語言模型(LLM)的 AI 代理,自動創建及修復 Rag 的端到端測試。直到幾個月前,我們還在使用 LangChain 框架

在這篇文章中,我將分享我們在使用 LangChain 遇到的困難,以及為何用模塊化的建構塊取代其僵化的高層抽象後,我們的代碼庫變得更簡潔,團隊也變得更快樂且更高效。

背景故事

我們在 2023 年初開始在生產環境中使用 LangChain,持續了超過 12 個月,並於 2024 年移除了它。

在 2023 年,LangChain 看似是我們的最佳選擇。它擁有令人印象深刻的組件和工具列表,且受歡迎程度飆升。它承諾能“讓開發者在一個下午內從想法變成可運行的代碼”。但隨著我們的需求變得更複雜,LangChain 逐漸成為我們生產力的阻力。

隨著其不靈活性開始顯現,我們很快發現自己不得不深入研究 LangChain 的內部機制,以改進系統的低層行為。但由於 LangChain 故意抽象了許多細節,我們經常無法編寫所需的低層代碼。

成為早期框架的風險

AI 和 LLM 是迅速變化的領域,每週都有新的概念和想法出現。因此,當一個框架如 LangChain 建立在多個新興技術之上時,設計能經得起時間考驗的抽象是非常困難的。

如果當時由我來建立像 LangChain 這樣的框架,我也不會做得更好。事後看問題很容易,本文的目的不是不公平地批評 LangChain 的核心開發者或貢獻者。每個人都在盡力而為。

設計出色的抽象是困難的 - 即使需求已經很明確。但當你在這樣一個變化不定的狀態下建模組件時(如代理),使用僅限於低層建構塊的抽象是更安全的選擇。

LangChain 的抽象問題

起初,當我們的簡單需求與其使用假設一致時,LangChain 是有幫助的。但其高層抽象很快讓我們的代碼變得難以理解和令人沮喪地維護。當我們的團隊花在理解和調試 LangChain 上的時間與開發功能的時間相當時,這並不是一個好兆頭。

LangChain 的抽象問題可以用這個將英文字翻譯成意大利語的簡單例子來說明。

使用 OpenAI 包的 Python 範例:

from openai import OpenAI client = OpenAI(api_key="<your_api_key>") text = "hello!" language = "Italian" messages = [ {"role": "system", "content": "You are an expert translator"}, {"role": "user", "content": f"Translate the following from English into {language}"}, {"role": "user", "content": f"{text}"}, ] response = client.chat.completions.create(model="gpt-4o", messages=messages) result = response.choices[0].message.content

這段代碼簡單易懂,只包含一個類和一個函數調用。其餘都是標準的 Python 語法。

讓我們對比一下 LangChain 的版本:

from langchain_openai import ChatOpenAI from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate os.environ["OPENAI_API_KEY"] = "<your_api_key>" text = "hello!" language = "Italian" prompt_template = ChatPromptTemplate.from_messages( [("system", "You are an expert translator"), ("user", "Translate the following from English into {language}"), ("user", "{text}")] ) parser = StrOutputParser() chain = prompt_template | model | parser result = chain.invoke({"language": language, "text": text})

這段代碼大致做了同樣的事情,但相似之處也僅此而已。

我們現在有三個類和四個函數調用。但最令人擔憂的是引入了三個新的抽象:

  • 提示模板:提供給 LLM 的提示
  • 輸出解析器:處理 LLM 的輸出
  • 鏈:LangChain 的“LCEL 語法”,重載了 Python 的 | 操作符

LangChain 所做的只是增加了代碼的複雜性,卻沒有任何明顯的好處。

這段代碼可能適用於早期的原型開發。但在生產環境中,每個組件都必須被合理地理解,以便在真實世界的使用情況下不會意外崩潰。你必須遵循給定的數據結構,並圍繞這些抽象設計應用程序。

讓我們再看一個 Python 中的抽象比較,這次是從 API 獲取 JSON。

使用內置的 http 包:

import http.client import json conn = http.client.HTTPSConnection("api.example.com") conn.request("GET", "/data") response = conn.getresponse() data = json.loads(response.read().decode()) conn.close()

使用 requests 包:

import requests response = requests.get("/data") data = response.json()

勝者顯而易見。這就是良好抽象的感覺。

當然,這些都是簡單的例子。但我的重點是,良好的抽象簡化了代碼並減少了理解它所需的認知負荷。

LangChain 試圖通過隱藏細節來使你的生活更輕鬆,從而用更少的代碼完成更多工作。但當這以簡單性和靈活性為代價時,抽象就失去了價值。

LangChain 還有一個習慣,就是在其他抽象之上使用抽象,所以你經常需要考慮嵌套抽象,以正確理解如何使用 API。這不可避免地導致理解龐大的堆棧跟蹤,並調試你沒有編寫的內部框架代碼,而不是實現新功能。

LangChain 對我們開發團隊的影響

我們的應用程序大量使用 AI 代理來執行不同類型的任務,例如測試用例發現、Playwright 測試生成和自動修復。

當我們想從單一的順序代理架構轉向更複雜的架構時,LangChain 成為了限制因素。例如,生成子代理並讓它們與原代理交互,或多個專業代理之間的交互。

在另一個實例中,我們需要根據業務邏輯和 LLM 的輸出動態更改代理可訪問的工具。但 LangChain 並不提供外部觀察代理狀態的方法,導致我們不得不縮小實施範圍,以適應 LangChain 代理的有限功能。

移除 LangChain 後,我們不再需要將需求轉換為 LangChain 適用的解決方案。我們只需編寫代碼。

那麼,如果不是 LangChain,我們應該使用什麼框架?或許你根本不需要框架。

你需要框架來構建 AI 應用嗎?

LangChain 早期幫助我們提供了 LLM 功能,讓我們可以專注於構建應用程序。但事後看來,長期而言,我們會更好地不使用框架。

LangChain 的龐大組件列表給人一種構建 LLM 驅動應用程序很複雜的印象。但大多數應用程序需要的核心組件通常是:

  • LLM 通訊客戶端
  • 用於函數調用的函數/工具
  • 用於 RAG 的向量數據庫
  • 用於追蹤和評估的可觀察性平台

其餘的要麼是這些組件的輔助工具(例如,向量數據庫的分塊和嵌入),要麼是管理文件和應用程序狀態的常規應用程序任務,例如數據持久化和緩存。

如果你從無框架開始 AI 開發之旅,是的,組建自己的工具箱會花費更長的時間,需要更多的前期學習和研究。但這是值得花費的時間和有價值的投資,因為你正在學習你將要運作領域的基本知識。

大多數情況下,你對 LLM 的使用將是簡單且直接的。你主要會編寫順序代碼,迭代提示,並改進輸出質量和可預測性。大多數任務可以通過簡單的代碼和相對較小的外部包集合來實現。

即使使用代理,你很可能不會超出簡單的代理到代理通信,在預定的順序流程中使用業務邏輯來處理代理狀態及其響應。你不需要框架來實現這些功能。

儘管代理空間正在迅速發展,充滿了令人興奮的可能性和有趣的用例,我們建議在代理使用模式確立之前保持簡單。

使用建構塊保持快速和精簡

假設你不會將糟糕的代碼投放到生產環境中,團隊能夠創新和迭代的速度是成功的最重要指標。在 AI 領域中,許多開發是由實驗和原型驅動的。

但框架通常是為了基於已確立的使用模式強制結構而設計的——而這正是 LLM 驅動應用程序目前所缺乏的。將新想法轉換為框架特定的代碼限制了你的迭代速度。

建構塊方法偏好簡單的低層代碼和精心選擇的外部包,保持你的架構精簡,讓開發者能專注於解決他們面臨的問題。

建構塊是指一些你認為已經全面理解且不太可能改變的簡單組件。例如,向量數據庫。這是一種已知類型的模塊組件,具有基本功能,因此可以輕鬆更換和替換。你的代碼庫需要精簡且可適應,以最大化你的學習速度和每個迭代周期的價值。

. . .

希望我能夠深入且公正地描述我們在使用 LangChain 時遇到的挑戰,以及為什麼完全遠離框架對我們團隊有著巨大的好處。

我們目前採用的使用模塊化建構塊和最少抽象的策略,使我們現在能夠更快速且無摩擦地開發。

Ewan Mak linkedin headshot
Ewan Mak - AI Solutions Architect ot Tenten

Learn more about our LLM Developer insights

Share this post