??新智元報(bào)道??
??新智元報(bào)道??
【新智元導(dǎo)讀】最近,Hacker News熱榜上出現(xiàn)了一篇「聲討」LangChain的技術(shù)文章,得到了評(píng)論區(qū)網(wǎng)友的一致呼應(yīng)。去年還火遍L(zhǎng)LM圈的LangChain,為什么口碑逆轉(zhuǎn)了?
?2023年是屬于LLM初創(chuàng)公司的一年,也是屬于LangChain的一年。
這個(gè)發(fā)布于2022年10月的開源框架可以支持開發(fā)者構(gòu)建由LLM驅(qū)動(dòng)的應(yīng)用程序,目前依舊是社區(qū)中一種不可忽視的開發(fā)范式。
更具體地說,基于LLM構(gòu)建應(yīng)用程序的過程有點(diǎn)像在搭積木。即使模型本身的能力已經(jīng)很強(qiáng)大了,我們依舊需要其他的組件和工具才能更好發(fā)揮其潛力。
比如聊天模型、提示模板、文本嵌入模型、文本分割器、文檔加載器、檢索器、向量存儲(chǔ)等,這些工具的不同的搭配組合能夠構(gòu)建出各種的應(yīng)用鏈,滿足RAG、Agent、存儲(chǔ)&索引、信息提取等不同的應(yīng)用需求。
舉個(gè)例子,你想用GPT-4開發(fā)一個(gè)旅行顧問機(jī)器人,為用戶提供行程方面的規(guī)劃和建議。如果只依靠GPT-4在訓(xùn)練時(shí)學(xué)到的知識(shí),沒有實(shí)時(shí)查詢最新的航班、酒店、景區(qū)信息,提供的建議就不可能準(zhǔn)確實(shí)用。
借助LangChain框架,這個(gè)機(jī)器人就能鏈接到各種API和外部數(shù)據(jù)庫(kù),并記住用戶的旅行偏好,甚至能根據(jù)用戶的對(duì)話歷史提供個(gè)性化建議。
這樣聽起來,LangChain是一個(gè)非常強(qiáng)大的工具,流行起來也是理所應(yīng)當(dāng)。
然而,最近一個(gè)技術(shù)團(tuán)隊(duì)的博文登上了HN熱榜,描述了他們從「入坑」LangChain到「幡然醒悟」,最終決定拋棄這個(gè)熱門框架的過程。
「為什么我們不再使用LangChain構(gòu)建AI agents——當(dāng)抽象弊大于利時(shí):在生產(chǎn)中使用LangChain的教訓(xùn)以及我們應(yīng)該做什么」
底下的評(píng)論也紛紛附和,表示這個(gè)框架有種「代碼糟糕」的感覺,而且把使用LangChain描述為一條「充滿雷區(qū)的道路」。
「LangChain的抽象就是死亡的定義。」
從大受追捧到「人人喊打」,LangChain到底有什么樣的問題?
問題浮現(xiàn)
這個(gè)技術(shù)團(tuán)隊(duì)在生產(chǎn)中使用LangChain已經(jīng)超過12個(gè)月,開始于2023年初。
當(dāng)時(shí),LangChain似乎是最佳選擇,因?yàn)樗鼡碛幸幌盗辛钊擞∠笊羁痰慕M件和工具,并且承諾開發(fā)者「用一個(gè)下午將想法轉(zhuǎn)變?yōu)榭蓤?zhí)行的代碼」,流行程度飆升。
然而,隨著需求逐漸變得復(fù)雜,LangChain的不靈活性開始顯現(xiàn)出來,開始阻礙生產(chǎn)力、成為摩擦的源頭。
團(tuán)隊(duì)不得不深入研究框架的內(nèi)部結(jié)構(gòu),以改善系統(tǒng)的底層行為。但因?yàn)長(zhǎng)angChain有意通過抽象屏蔽細(xì)節(jié),編寫底層代碼的嘗試通常也不可行,或至少是十分復(fù)雜。
究其根源,作者認(rèn)為是LangChain的抽象程度過高。在開發(fā)的初期階段,較為簡(jiǎn)單的需求與框架的假設(shè)相一致,因此配合得很好。
但高級(jí)抽象很快使之后的代碼變得難以理解、維護(hù),團(tuán)隊(duì)花費(fèi)在理解和調(diào)試LangChain上的時(shí)間越來越多,幾乎趕上了真正構(gòu)建功能所用的時(shí)間。
舉個(gè)具體的例子,上代碼:用OpenAI的包,將英語單詞翻譯為意大利語(沒錯(cuò),就是GPT-4o發(fā)布會(huì)demo的功能)
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
這段代碼很好理解,包含一個(gè)OpenAI類的實(shí)例client以及一個(gè)函數(shù)調(diào)用,其余都是標(biāo)準(zhǔn)的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})
涉及到三個(gè)類、四個(gè)函數(shù)調(diào)用。但最令人擔(dān)憂的是,一個(gè)如此簡(jiǎn)單的任務(wù)需要引入三個(gè)抽象概念——
提示模板(prompt template):為L(zhǎng)LM提供提示
輸出解析器(output parser):處理LLM的輸出
鏈(chain):LangChain的「LCEL語法」覆蓋了Python的「|」運(yùn)算符
這似乎徒增代碼復(fù)雜性,卻沒有任何額外的好處。
LangChain似乎更適合早期原型,但不適合實(shí)際的生產(chǎn)使用。
對(duì)于后者而言,開發(fā)人員必須理解每一個(gè)組件,才能保證代碼不會(huì)在真實(shí)的使用場(chǎng)景中意外崩潰,但LangChain限制他們必須遵守給定的數(shù)據(jù)結(jié)構(gòu)和抽象概念,這無疑是額外的負(fù)擔(dān)。
再舉一個(gè)例子,這次是從API獲取JSON。
要使用Python內(nèi)置的http包可以這樣寫:
用requests包的寫法則更加簡(jiǎn)潔:
import?requests
response = requests.get("/data")
data = response.json()
這感覺上就是好的抽象。雖然是微不足道的小例子,但足以說明一個(gè)觀點(diǎn)——好的抽象可以簡(jiǎn)化代碼,并能讓人快速理解。
LangChain的初衷是好的,它希望隱藏細(xì)節(jié),讓開發(fā)人員用更少的代碼完成更多的功能。但如果代價(jià)是失去開發(fā)的簡(jiǎn)潔和靈活,這種抽象就失去了價(jià)值。
此外,LangChain還習(xí)慣于「嵌套抽象」,在一個(gè)抽象概念之上再使用抽象。
這不僅讓學(xué)習(xí)API的過程更加復(fù)雜,開發(fā)人員還不得不面對(duì)大量的堆棧跟蹤信息,并調(diào)試那些自己不熟悉的內(nèi)部框架代碼。
以這個(gè)技術(shù)團(tuán)隊(duì)自己的開發(fā)為例,他們的應(yīng)用程序使用大量AI agent執(zhí)行不同類型的任務(wù),比如測(cè)試用例發(fā)現(xiàn)、Playwright測(cè)試生成和自動(dòng)修復(fù)。
當(dāng)他們想要從只有單個(gè)順序代理的架構(gòu)轉(zhuǎn)向更復(fù)雜架構(gòu)時(shí),例如,生成sub-agnet并與原始agent交互,或者多個(gè)專業(yè)agent彼此交互,LangChain就成為了限制因素。
另一個(gè)示例中,需要根據(jù)業(yè)務(wù)邏輯和LLM的輸出,動(dòng)態(tài)更改agent可訪問工具的可用性。但LangChain沒有提供從外部觀察agent狀態(tài)的方法,導(dǎo)致他們不得不縮小實(shí)現(xiàn)范圍,以適應(yīng)LangChain對(duì)agent可用功能的限制。
下決心刪除LangChain之后,技術(shù)團(tuán)隊(duì)仿佛得到了真正的「解脫」。不僅工作高效了,內(nèi)耗也少了。
一旦刪除了它,我們就可以不用先將需求轉(zhuǎn)化為適合LangChain的解決方案。我們只寫代碼就可以了。
拋棄LangChain,下一個(gè)框架用什么?
事后,團(tuán)隊(duì)仔細(xì)反思復(fù)盤了這個(gè)問題。他們認(rèn)為,長(zhǎng)期來看,不使用框架是更好的選擇。
LangChain提供了一長(zhǎng)串組件,讓人感覺LLM驅(qū)動(dòng)的應(yīng)用程序很復(fù)雜,但其實(shí)并不是。核心組件只有幾樣——
用于LLM通信的客戶端
函數(shù)或調(diào)用函數(shù)的工具
用于RAG的向量數(shù)據(jù)庫(kù)
用于追蹤、評(píng)估等功能的可觀察平臺(tái)
其余的組件,要么是以上核心組件的輔助(比如向量數(shù)據(jù)庫(kù)的分塊和嵌入),要么只是完成常規(guī)應(yīng)用程序的任務(wù)(比如使用數(shù)據(jù)持久化和緩存,以管理文件和應(yīng)用程序狀態(tài))。
如果不使用任何框架,毫無疑問會(huì)增加前期用于學(xué)習(xí)、調(diào)研的工作量,開發(fā)者需要更長(zhǎng)時(shí)間來組建自己的工具箱。
但「磨刀不誤砍柴工」,這些時(shí)間是值得的。在即將進(jìn)入的領(lǐng)域打下基礎(chǔ),這對(duì)你本人和應(yīng)用程序的未來都是良好的投資。
而且,很多情況下,使用LLM的流程都是非常簡(jiǎn)單直接的。開發(fā)人員主要編寫順序代碼、迭代提示,并提高輸出的質(zhì)量和可預(yù)測(cè)性。絕大多數(shù)任務(wù)都可以通過簡(jiǎn)潔的代碼和較小的外部包集合來實(shí)現(xiàn)。
即使用到了agent,也不一定需要框架才能實(shí)現(xiàn)。在處理業(yè)務(wù)邏輯時(shí),一般只需要在預(yù)定順序流中進(jìn)行agent之間的通信,處理它們的狀態(tài)和響應(yīng),超出這個(gè)范圍的工作內(nèi)容并不多。
雖然agent領(lǐng)域正在迅速發(fā)展,并帶來許多令人興奮的用例和可能性,但在代理的使用模式逐漸固化的過程中,我們還是應(yīng)該遵循簡(jiǎn)潔原則。
構(gòu)建基本塊,「輕裝疾行」
假設(shè)技術(shù)團(tuán)隊(duì)沒有在生產(chǎn)中混入垃圾代碼,那么創(chuàng)新和迭代的速度是衡量成功的最重要指標(biāo),因?yàn)锳I領(lǐng)域的許多發(fā)展都是由實(shí)驗(yàn)和原型設(shè)計(jì)驅(qū)動(dòng)的。
這意味著,代碼庫(kù)需要盡可能精簡(jiǎn)且適應(yīng)性強(qiáng),才能最大限度提升開發(fā)人員的學(xué)習(xí)速度,每個(gè)迭代周期才能產(chǎn)生更多價(jià)值。
然而,「框架」的概念與此并不相容,它通常是人為設(shè)計(jì)出一種代碼結(jié)構(gòu),為了匹配根據(jù)既有的使用模式。
但LLM驅(qū)動(dòng)的應(yīng)用還在發(fā)展階段,沒有固定的使用模式。當(dāng)你不得不將創(chuàng)新的想法「翻譯」為特定于某個(gè)框架的代碼時(shí),就限制了迭代速度。
因此,相比于使用框架,更好的辦法是構(gòu)建基本塊(builing blocks),通過簡(jiǎn)潔的底層代碼和精心挑選的外部依賴包,保持架構(gòu)的精簡(jiǎn),從而讓開發(fā)人員專注于真正需要解決的問題。
「構(gòu)建基本塊」意味著簡(jiǎn)潔、可被完全理解,且不易變動(dòng)。最典型的例子就是矢量數(shù)據(jù)庫(kù),它屬于已知類型的模塊化組件,只有基本功能,因此可以輕松被更換或取代。
因此,作者所在團(tuán)隊(duì)目前的策略是,完全不使用任何框架,用盡可能少的抽象進(jìn)行模塊化構(gòu)建,從而讓開發(fā)過程更快、更流暢。
雖然LangChain的槽點(diǎn)如此之多,但作者還是選擇不過分苛責(zé)。某種程度上,這些缺陷都是無法避免的。
在AI和LLM這樣快速變化的領(lǐng)域,每周都會(huì)涌現(xiàn)新的概念和想法。因此,想要在如此多新型技術(shù)中間創(chuàng)建LangChain這樣的框架,并設(shè)計(jì)出經(jīng)得起時(shí)間考驗(yàn)的抽象,是非常困難的。
作者非常坦誠(chéng)地承認(rèn),如果當(dāng)初是自己去構(gòu)建LangChain,也不會(huì)做得比現(xiàn)在更好。當(dāng)一個(gè)「事后諸葛」指出錯(cuò)誤總是容易的,這篇博文的目的并不是批評(píng)任何LangChain的開發(fā)人員或貢獻(xiàn)者,因?yàn)槊總€(gè)人都在盡力而為。
即使能很好地理解需求,構(gòu)建精心設(shè)計(jì)的抽象也是很困難的。因此在不斷變動(dòng)的條件下對(duì)組件(比如agent)進(jìn)行建模時(shí),更安全的選擇是僅對(duì)底層模塊使用抽象。
發(fā)表評(píng)論