[博客翻译]抽象过度时,弊大于利:使用LangChain在生产中的教训和替代做法


原文地址:https://www.octomind.dev/blog/why-we-no-longer-use-langchain-for-building-our-ai-agents


在 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})

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

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

  1. 提示模板:向 LLM 提供提示
  2. 输出解析器:处理 LLM 的输出
  3. 链: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