在 AI千集,我们使用配备多个大型语言模型的 AI 代理自动创建和修复 Playwright 的端到端测试。直到几个月前,我们使用的是 LangChain 框架。
在这篇文章中,我将分享我们在使用 LangChain 时遇到的困难,以及为什么用模块化构建块替换其僵化的高级抽象简化了我们的代码库,使团队更加快乐和高效。
背景故事
我们从 2023 年初开始在生产环境中使用 LangChain,直到 2024 年才移除。
在 2023 年,LangChain 对我们来说似乎是最佳选择。它有一个令人印象深刻的组件和工具列表,人气飙升。它承诺“让开发者在一下午的时间内从一个想法变成可运行的代码 ”。但随着我们的需求变得更加复杂,LangChain 逐渐成为摩擦的来源,而不是生产力的源泉。
当其不灵活性开始显现时,我们很快发现自己深入 LangChain 的内部,以改进我们系统的低级行为。但由于 LangChain 故意将许多细节抽象化,经常很难或不可能编写我们需要的低级代码。
早期框架的风险
AI 和 LLMs 是快速发展的领域,每周都有新的概念和想法出现。因此,当像 LangChain 这样的框架围绕多个新兴技术建立时,设计能够经受住时间考验的抽象是非常困难的。
如果我在他们创建 LangChain 的时候尝试构建类似的框架,我不认为我能做得更好。事后批评很容易,这篇文章的目的不是不公平地批评 LangChain 的核心开发者或其贡献者。每个人都在尽最大努力。
设计良好的抽象是困难的 ——即使需求已充分理解。但当你在如此不稳定的状态下(例如,代理)建模组件时,仅使用低级构建块的抽象更为安全。
LangChain 抽象的问题
LangChain 在我们的简单需求与它的使用假设相匹配时起初是有帮助的。但其高级抽象很快就使我们的代码更难理解,维护起来也令人沮丧。当我们的团队开始花费与构建功能同样多的时间来理解和调试 LangChain 时,这不是一个好兆头。
LangChain 抽象方法的问题可以通过以下将英语单词翻译成意大利语的简单示例来说明。
以下是使用仅 OpenAI 包的 Python 示例:
from openai import OpenAI
client = OpenAI(api_key="<你的_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"] = "<你的_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 Agent 的有限功能。
一旦我们移除了它,我们就不再需要将需求转化为 LangChain 适用的解决方案。我们可以直接编码了。
那么,如果不是 LangChain,你应该使用哪个框架呢?可能你根本不需要框架。
建立 AI 应用需要框架吗?
LangChain 早期通过提供 LLM 功能帮助我们,使我们可以专注于构建我们的应用。但回顾过去,长期来看,没有框架会更好。
LangChain 的长列表组件给人一种建立 LLM 驱动的应用很复杂的感觉。但实际上,大多数应用需要的核心组件通常是:
- 用于 LLM 通信的客户端
- 用于函数调用的功能/工具
- 用于 RAG 的向量数据库
- 用于追踪、评估等的可观测性平台
其余的是围绕这些组件的辅助工具(如向量数据库的分块和嵌入),或者常规应用任务,如通过数据持久化和缓存管理文件和应用状态。
如果你开始没有框架的 AI 开发之旅,是的,建立自己的工具箱会花更长时间,并且需要更多的前期学习和研究。但这是花得值得的时间和对您和应用未来的投资,因为你正在学习领域的基础知识。
在大多数情况下,你对 LLMs 的使用将是简单直接的 。你大多会编写顺序代码,迭代提示,提高输出的质量和可预测性。大多数任务可以通过简单的代码和相对较小的外部包集合来完成。
即使使用代理,也不太可能进行复杂的操作,如预定义顺序流程中的代理之间简单通信,以及处理代理状态和响应的业务逻辑。你不需要框架来实现这个。
虽然代理领域正在快速发展,带来了令人兴奋的可能性和有趣的用例,但我们建议在代理使用模式稳定下来之前保持简单。
使用构建块保持快速和精干
假设你没有将垃圾代码部署到生产环境,团队可以创新和迭代的速度是成功最重要的指标。AI 领域的大量开发由实验和原型驱动。
但框架通常旨在根据已确立的使用模式强制结构 ——LLM 驱动的应用目前还没有。将新想法翻译成框架特定的代码限制了你迭代的速度。
构建块方法倾向于使用简单低级代码和精心选择的外部包,保持架构精干,让开发者能够专注于他们试图解决的问题。
一个构建块是简单到你充分理解且不太可能改变的东西。例如,向量数据库。它是一种已知的具有基本功能集的模块化组件,可以轻松替换 。你的代码库需要精干且适应性强,以最大化学习速度和每次迭代周期的价值。
. . .
我希望我仔细而公正地描述了我们在 LangChain 遇到的挑战,以及为什么完全远离框架对我们团队来说是极大的好处。
我们当前采用模块化构建块和最小抽象的战略现在使我们能够更快地开发,减少摩擦。
