当抽象弊大于利时:在生产中使用LangChain的经验教训以及我们应该做什么?
在 Octomind,我们使用具有多个 LLM 的 AI 智能体(agents)来自动创建和修复 Playwright 中的端到端测试。直到几个月前,我们还使用 LangChain 框架做到了这一点。
在这篇文章中,我将分享我们在LangChain上如何痛苦地挣扎,以及为什么用模块化构建块(building block)替换其僵化的高级抽象可以简化我们的代码库,使团队更快乐,更有效率。
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的版本进行对比:
代码大致相同,但相似之处就到此为止。
我们现在有三个类和四个函数调用。但最令人担忧的是引入了三个新的抽象:
提示模板(Prompt_template):向 LLM 提供提示
输出解析器(Output parsers):处理 LLM 的输出
链(Chains):LangChain 的“LCEL 语法”覆盖了 Python 的“|”运算符
LangChain所取得的成就只是增加了代码的复杂性,而没有明显的好处。
此代码可能适用于早期原型。但对于生产使用,必须合理理解每个组件,这样它就不会在实际使用条件下意外地崩溃(blow up)。您必须遵守给定的数据结构,并围绕这些抽象来设计应用程序。
让我们看一下 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应用开发,我们将自己的工具箱集成起来需要更长的时间,并且需要更多的前期学习和研究。但这是值得花费的时间,也是对自己及其应用程序的未来有价值的投资,因为这是AI应用开发领域的基础知识。
在大多数情况下,我们对 LLM 的使用将简单明了。我们主要负责编写顺序代码、迭代提示以及提高输出的质量和可预测性。大多数任务都可以通过简单的代码和相对较小的外部软件包集成来实现。
即使使用智能体(agents),我们也不太可能在预定的顺序流中执行简单的智能体到智能体之间的通信,并使用业务逻辑来处理智能体状态及其响应。所以,不需要一个框架来实现这一点。
虽然智能体应用正在迅速发展,具有令人兴奋的可能性和有趣的用例,但我们建议在智能体使用模式巩固的同时保持简单。
6. 使用构建块保持快速和精益
假设我们没有将垃圾代码交付到生产环境,那么团队创新和迭代的速度是衡量成功的最重要指标。AI领域的许多发展都是由实验和原型驱动的。
但框架通常是基于完善的使用模式来实现其特定结构的设计,而 LLM 驱动的应用程序尚不具备这种模式。必须将新想法转化为特定于框架的代码,这限制了迭代速度。
构建块方法更喜欢使用精心挑选的外部软件包的简单低层次代码,使架构保持精简,以便开发人员可以将注意力集中在试图解决的问题上。
构建块是我们感到可以全面理解且不太可能更改的简单内容。例如,向量数据库。它是一种已知类型的、具有一组基本功能的模块化组件,因此可以轻松更换和更换。我们的代码库需要精益且适应性强,以最大限度地提高学习速度和从每个迭代周期中获得的价值。
我希望这里能深思熟虑地、公平地描述我们在LangChain上所面临的挑战,以及为什么完全摆脱框架对我们的团队非常有益。
我们目前的策略是使用具有最小抽象的模块化构建块,这使我们能够更快地开发,减少摩擦。
原文:https://www.octomind.dev/blog/why-we-no-longer-use-langchain-for-building-our-ai-agents