作者 | Fabian Both
编辑 | 伊风
出品 | 51CTO技术栈(微信号:blog51cto)
为什么我们不再使用LangChain来构建我们的AI代理AI agents?
在Octomind,我们使用多个大型语言模型(LLM)的AI代理来自动创建和修复Playwright中的端到端测试。直到几个月前,我们还在使用LangChain框架。
在这篇文章中,我将分享我们使用LangChain时遇到的困难,并解释为什么用模块化的构建块替换其僵化的高级抽象后,我们的代码库变得更简洁,使我们的团队更快乐、更高效。
1.背景故事
我们在生产中使用LangChain超过12个月,从2023年初开始,然后在2024年将其移除。
2023年,LangChain似乎是我们的最佳选择。它有一个令人印象深刻的组件和工具列表,并且其受欢迎程度迅速上升。它承诺“让开发人员可以在一个下午内从想法变为可运行的代码。”但随着我们的需求变得更加复杂,问题开始浮现,LangChain变成了摩擦而不是生产力的来源。
随着其不灵活性开始显现,我们很快发现自己在深入研究LangChain内部,以改进我们系统的底层行为。但由于LangChain故意抽象了许多细节,因此通常不容易或不可能编写我们所需的底层代码。
2.早期框架的危险
AI和LLM是快速变化的领域,每周都有新概念和想法涌现。因此,当一个框架如LangChain围绕多个新兴技术创建时,设计能经受时间考验的抽象是非常困难的。
我相信,如果我在他们创建LangChain时尝试构建一个类似的框架,我也不会做得更好。事后看错误是很容易的,这篇文章的目的是不公平地批评LangChain的核心开发人员或其贡献者。每个人都在尽力而为。
设计良好的抽象很难,即使需求是明确的。但当你在这样一个变化多端的状态下建模组件(如代理)时,仅对低级构建块使用抽象是更安全的方式。
3.LangChain抽象带来的问题
在我们简单的需求与其使用假设一致时,LangChain最初是好用的。但其高度的抽象很快使我们的代码变得难以理解与维护。当我们的团队开始花费与构建功能一样多的时间来理解和调试LangChain时,这不是一个好兆头。
LangChain的抽象方法的问题可以通过这个将英文单词翻译成意大利语的简单示例来展示。
这是一个仅使用OpenAI包的Python示例:
图片
这是简单且易于理解的代码,包含一个类和一个函数调用。其余是标准的Python。
让我们对比一下LangChain的版本:
图片
代码大致相同,但相似之处仅此而已。
我们现在有三个类和四个函数调用。但最令人担忧的是引入了三个新的抽象:
- 提示模板:提供给LLM的提示
- 输出解析器:处理LLM的输出
- 链:LangChain的“LCEL语法”覆盖Python的 `|` 运算符
所有LangChain所做的就是增加了代码的复杂性而没有明显的好处。
这段代码可能适用于早期阶段的原型。但对于生产使用,每个组件都必须合理理解,以确保在实际使用条件下不会意外爆炸。你必须遵守给定的数据结构,并围绕这些抽象设计你的应用程序。
让我们再看看一个Python中的抽象对比,这次是从API获取JSON。
使用内置的http包:
图片
使用requests包:
图片
胜利者显而易见。这就是一个好的抽象的感觉。
当然,这些是简单的例子。但我的观点是,好的抽象简化了你的代码并减少了理解它所需的认知负担。
LangChain试图通过隐藏细节,用更少的代码做更多的事情来让你的生活更轻松。但当这以牺牲简单性和灵活性为代价时,抽象就失去了价值。
LangChain还习惯于在其他抽象之上使用抽象,因此你常常被迫以嵌套抽象的方式思考如何正确使用API。这不可避免地导致理解巨大的堆栈跟踪并调试你没有编写的内部框架代码,而不是实现新功能。
4.LangChain对我们开发团队的影响
我们的应用程序大量使用AI代理来执行不同类型的任务,例如测试用例发现、Playwright测试生成和自动修复。
当我们想从单一的顺序代理架构转向更复杂的架构时,LangChain成了限制因素。例如,生成子代理并让它们与原始代理交互,或者多个专家代理相互交互。
在另一个实例中,我们需要根据业务逻辑和LLM的输出动态更改我们的代理可以访问的工具的可用性。但LangChain并没有提供一种方法来外部观察代理的状态,导致我们不得不缩小实现的范围以适应LangChain代理的有限功能。
一旦我们移除它,我们就不再需要将我们的需求转换为LangChain适当的解决方案。我们只需编码即可。
因此,如果不是LangChain,你应该使用什么框架?也许你根本不需要框架。
5.你需要一个框架来构建AI应用程序吗?
LangChain在早期帮助了我们,因为它提供了LLM功能,使我们能够专注于构建应用程序。但事后看来,从长远来看,我们会更好地不用框架。
LangChain的长组件列表给人的印象是,构建一个LLM驱动的应用程序很复杂。但大多数应用程序需要的核心组件通常是:
- 一个用于LLM通信的客户端
- 用于函数调用的函数/工具
- 一个用于RAG的向量数据库
- 一个用于跟踪、评估等的可观察性平台
其余的是围绕这些组件的帮助工具(例如,用于向量数据库的分块和嵌入),或者是常规的应用程序任务,例如通过数据持久性和缓存管理文件和应用程序状态。
如果你在没有框架的情况下开始你的AI开发之旅,是的,组建你自己的工具箱会花费更长的时间,并且需要更多的前期学习和研究。但这是值得的时间投资,因为你正在学习你将要操作的领域的基本知识。
在大多数情况下,你对LLM的使用将是简单而直接的。你主要会编写顺序代码,迭代提示,并改进输出的质量和可预测性。大多数任务可以通过简单的代码和相对较少的外部包来完成。
即使使用代理,也不太可能做超出简单的代理到代理通信,在预定的顺序流程中处理代理状态和它们的响应。你不需要框架来实现这一点。
虽然代理领域正在迅速发展,有许多令人兴奋的可能性和有趣的用例,但我们建议在代理使用模式稳定下来之前,保持简单。
6.使用构建块保持快速和精简
假设你没有向生产发布垃圾代码,那么团队创新和迭代的速度是成功的最重要指标。AI领域的许多开发是由实验和原型驱动的。
但框架通常是为基于已建立的使用模式强制结构而设计的——LLM驱动的应用程序尚未有这些模式。将新想法转换为框架特定的代码,限制了你的迭代速度。
构建块方法更喜欢使用简单的低级代码和精心选择的外部包,保持你的架构精简,使开发人员可以将注意力集中在他们试图解决的问题上。
构建块是指你觉得已经全面理解且不太可能改变的简单组件。例如,一个向量数据库。它是一种已知类型的模块化组件,具有一组基础功能,因此可以很容易地被替换掉。你的代码库需要保持精简和适应性,以最大化你的学习速度和每个迭代周期的价值。
. . .
我希望我能恰当地和公平地描述我们在使用LangChain时遇到的挑战,以及为什么完全放弃框架对我们团队是非常有益的。
我们目前使用模块化构建块和最小化抽象的策略使我们现在能够更快地开发,并减少了摩擦。