打开网易新闻 查看精彩图片

LangChain是一个知名的开源编排框架,用于使用大型语言模型 (LLM) 开发应用程序。它最初是一个开源项目,后来在获得大量关注后转变为一家初创公司,并获得了红杉领投的2500万美元A轮融资。

Octomind是一家用AI重塑端到端测试的软件公司,在今年4月获得了由Cherry Ventures领投的480万美元种子轮融资。

近期,Octomind的深度学习工程师Fabian Both发布一篇博客,称其不再使用 LangChain来构建AI代理。该文章获得了大量关注。

在 Octomind,我们使用拥有多个 LLM 的 AI 代理在 Playwright 中自动创建和修复端到端测试。直到几个月前,我们还在使用LangChain 框架来实现这一点。

在这篇文章中,我将分享我们在使用 LangChain 时遇到的困难,以及为什么用模块化构建块取代其僵化的高级抽象可以简化我们的代码库,并使我们的团队更快乐、更高效。

背景故事

我们在生产中使用 LangChain 超过 12 个月,从 2023 年初开始,并于 2024 年将其移除。

LangChain 似乎是我们在 2023 年的最佳选择。它拥有令人印象深刻的组件和工具列表,人气飙升。它承诺“让开发人员在一个下午内从一个想法变成工作代码”。但随着我们的要求变得越来越复杂,问题开始浮出水面,使 LangChain 成为摩擦源,而不是生产力。

随着其不灵活性开始显现,我们很快就开始深入研究 LangChain 的内部结构,以改善系统的底层行为。但由于 LangChain 有意从您那里抽象出如此多的细节,因此编写我们需要的底层代码通常并不容易或不可能。

早期框架的风险

人工智能和大语言模型是瞬息万变的领域,每周都有新概念和新想法出现。因此,当围绕多种新兴技术创建像 LangChain 这样的框架时,设计经得起时间考验的抽象是非常困难的。

我确信,如果我在他们建立 LangChain 这样的框架时也尝试过,我肯定不会做得更好。事后很容易指出错误,这篇文章的目的不是不公平地批评 LangChain 的核心开发人员或他们的贡献者。每个人都在尽最大努力。

精心设计抽象很难- 即使需求很容易理解。但是,当您在这种不断变化的状态下建模组件(例如代理)时,仅将抽象用于较低级别的构建块是更安全的做法。

LangChain 抽象的问题

当我们的简单需求与 LangChain 的使用假设相符时,LangChain 起初很有用。但是,它的高级抽象很快使我们的代码更难理解,维护起来也更令人沮丧。当我们的团队开始花费与构建功能一样多的时间来理解和调试 LangChain 时,这不是一个好兆头。

通过将英语单词翻译成意大利语这个简单的例子可以证明 LangChain 的抽象方法存在的问题。

以下是仅使用 OpenAI 包的 Python 示例:

1from openai import OpenAI 2
3client = OpenAI(api_key=" " ) 4text = "hello!" 5language = "Italian" 6
7messages = [
8 {"role": "system", "content": "You are an expert translator"}, 9 {"role": "user", "content": f"Translate the following from English into {language}"}, 10 {"role": "user", "content": f"{text}"}, 11]
12
13response = client.chat.completions.create(model="gpt-4o", messages=messages) 14result = response.choices[0].message.content

这是简单易懂的代码,包含一个类和一个函数调用。其余的是标准 Python。

让我们将其与 LangChain 的版本进行对比:

1from langchain_openai import ChatOpenAI 2from langchain_core.output_parsers import StrOutputParser 3from langchain_core.prompts import ChatPromptTemplate 4
5os.environ["OPENAI_API_KEY"] = " " 6text = "hello!" 7language = "Italian" 8
9
10prompt_template = ChatPromptTemplate.from_messages(
11 [("system", "You are an expert translator"), 12 ("user", "Translate the following from English into {language}"), 13 ("user", "{text}")] 14)
15
16parser = StrOutputParser()
17chain = prompt_template | model | parser
18result = chain.invoke({"language": language, "text": text})

代码大致做了同样的事情,但是相似之处也仅此而已。

我们现在有三个类和四个函数调用。但最令人担忧的是引入了三个新的抽象:

  • 提示模板:为 LLM 提供提示

  • 输出解析器:处理 LLM 的输出

  • Chains:LangChain 的“LCEL 语法”覆盖了 Python 的 `|` 运算符

LangChain 所取得的成就只是增加了代码的复杂性,而没有带来任何明显的好处。

对于早期原型来说,此代码可能还不错。但对于生产用途,必须合理理解每个组件,这样它才不会在实际使用条件下意外崩溃。您必须遵守给定的数据结构并围绕这些抽象设计应用程序

让我们看看 Python 中的另一个抽象比较,这次是从 API 中获取 JSON。

使用内置的 http 包:

1import http.client 2import json 3
4conn = http.client.HTTPSConnection("api.example.com") 5conn.request("GET", "/data") 6response = conn.getresponse()
7data = json.loads(response.read().decode())
8conn.close()

使用请求包:

1import requests 2
3response = requests.get("https://api.example.com/data") 4data = response.json()

胜者显而易见。这就是好的抽象的感觉。

当然,这些都是微不足道的例子。但我的观点是,好的抽象可以简化你的代码,并减少理解代码所需的认知负担。

LangChain 试图通过隐藏细节来用更少的代码做更多的事情,让你的生活更轻松。但是,当以简单性和灵活性为代价时,抽象就失去了价值。

LangChain 还习惯于在其他抽象之上使用抽象,因此您经常被迫以嵌套抽象的方式思考,以了解如何正确使用 API。这不可避免地导致您理解大量堆栈跟踪并调试您未编写的内部框架代码,而不是实现新功能。

LangChain 对我们开发团队的影响

我们的应用程序大量使用 AI 代理来执行不同类型的任务,例如测试用例发现、Playwright 测试生成和自动修复。

当我们想要从具有单个顺序代理的架构转变为更复杂的架构时,LangChain 是限制因素。例如,生成子代理并让它们与原始代理交互。或者多个专业代理相互交互。

在另一个例子中,我们需要根据业务逻辑和 LLM 的输出动态更改代理可以访问的工具的可用性。但 LangChain 不提供从外部观察代理状态的方法,这导致我们缩小了实施范围以适应 LangChain 代理可用的有限功能。

一旦我们删除了它,我们就不再需要将我们的需求转化为适合 LangChain 的解决方案。我们只需编写代码即可。

那么,如果不是 LangChain,你应该使用什么框架?也许你根本不需要框架。

您需要一个用于构建 AI 应用程序的框架吗?

LangChain 早期通过提供 LLM 功能帮助了我们,让我们可以专注于构建应用程序。但事后看来,长期来看,如果没有框架,我们会过得更好。

LangChain 的组件列表很长,给人的印象是构建基于 LLM 的应用程序很复杂。但大多数应用程序所需的核心组件通常是:

  • LLM 通信客户端

  • 函数调用函数/工具

  • RAG 矢量数据库

  • 用于跟踪、评估等的可观察性平台。

其余的要么是这些组件的辅助程序(例如,矢量数据库的分块和嵌入),要么是常规应用程序任务,例如通过数据持久性和缓存管理文件和应用程序状态。

如果你在没有框架的情况下开始你的人工智能开发之旅,那么你需要花更长的时间来整合你自己的工具箱,并且需要更多的前期学习和研究。但这是值得的,也是对你和你的应用程序未来的一项值得的投资,因为你正在学习你将要操作的领域的基础知识。

在大多数情况下,LLM 的使用将简单而直接。您将主要编写顺序代码、迭代提示以及提高输出的质量和可预测性。大多数任务都可以通过简单的代码和相对较少的外部包集合来实现。

即使使用代理,您也不太可能做太多事情,除了在预定的顺序流程中使用业务逻辑来处理代理状态及其响应的简单代理间通信。您不需要框架来实现这一点。

虽然代理领域正在迅速发展,出现令人兴奋的可能性和有趣的用例,但我们建议在代理使用模式固化的同时,暂时保持简单。

利用构建模块保持快速和精益

假设您没有将垃圾代码投入生产,那么团队创新和迭代的速度是成功的最重要指标。人工智能领域的许多发展都是由实验和原型设计驱动的。

但框架通常设计为基于完善的使用模式来强制执行结构- 这是 LLM 驱动的应用程序目前尚不具备的。必须将新想法转化为框架特定的代码,这会限制您的迭代速度。

构建块方法倾向于使用简单的低级代码和精心选择的外部包,保持架构精简,以便开发人员可以专注于他们正在尝试解决的问题。

构建块是指您认为简单的东西,可以全面理解,并且不太可能改变。例如,矢量数据库。它是一种已知的模块化组件,具有一组基本功能,因此可以轻松更换和替换。您的代码库需要精简且适应性强,以最大限度地提高您的学习速度和从每个迭代周期中获得的价值。

我希望我已经深思熟虑并公正地描述了我们在使用 LangChain 时面临的挑战,以及为什么完全摆脱框架对我们的团队大有裨益。

我们当前的策略是使用具有最少抽象的模块化构建块,这使得我们现在能够更快、更轻松地进行开发。

| |