生产环境该用 LangChain 还是手搓?一个金融级团队的选型真相

目录

一、凌晨三点的告警

二、先问对问题:你到底在选什么?

三、正面交锋:七个维度的深度对比

四、LangChain 的暗坑:那些文档没写的行为

五、手搓的底气:核心代码其实不到 100 行

六、预留模型升级口子:适配器模式的威力

七、折中方案:很多企业的实际做法

生产环境:手搓内核

开发环境:用 LangChain

八、什么时候才考虑直接用 LangChain 上生产?

九、写在最后:技术选型的底层逻辑


一、凌晨三点的告警

想象一个场景:凌晨三点,你被手机告警吵醒。线上的 AI Agent 突然不工作了,客户投诉堆积如山。你爬起来看日志,发现报错栈穿过 RunnableBinding → RunnableSerializable → AgentExecutor → ChatModel → BaseLLM 整整六层框架内部代码,真正的业务错误被包装得面目全非。

你翻开 LangChain 的 GitHub Issues,发现三天前的一次小版本升级改了 AgentExecutor 的默认行为——它开始"智能地"吞掉 ToolCall 的异常,静默返回一个空结果。文档没有写这个变更,Changelog 里只有一句含糊的 "improved error handling"。

这不是故事会。这是 2025-2026 年间无数个生产团队真实经历过的场景。

当你追求的是 "跑通 demo",LangChain 是天使;当你追求的是 "凌晨三点不报警",它可能变成噩梦。

二、先问对问题:你到底在选什么?

很多人把这个问题简化为"LangChain 好不好用"。但真正的选型逻辑不是"工具好不好",而是"我的业务场景需要什么"。让我们先明确你的约束条件:

  • 稳定性优先级极高——金融业务、线上生产环境,一次故障可能意味着真金白银的损失
  • 未来要升级/切换模型——今天是 GPT-4o,明天可能换 DeepSeek、通义千问或私有部署的开源模型
  • ToolCall / Agent 链路——不是简单的聊天,而是有工具调用、多轮推理的复杂链路
  • Java 后端背景团队——对 Python 生态不熟,对 Spring 那套"声明式 + 注解"的企业级开发更有体感

带着这些约束,我们来看两个方案的真实差距。

三、正面交锋:七个维度的深度对比

不讲故事,先上硬核对比。每一个维度都是实际项目中踩过的坑、流过的血。

维度手搓原生代码LangChain
稳定性链路完全透明,异常全部自己接管。没有框架的隐式重试、吞异常、静默降级。出问题栈信息干净,排查线上故障快。黑盒较多。内部有大量默认行为:自动重试、异常捕获后吞错、缓存、回调链。很多行为文档不全,版本升级极易出现隐性行为变更。
模型切换自己抽象 LLMClient 接口,不同模型各写一个适配器。上层业务完全无感切换,新增模型只新增一个类。也支持多模型,但 Chain/Agent 逻辑强绑定框架对象。换模型不仅换 client,部分 prompt、解析器也要适配框架 API。
ToolCall / Agent完全掌控:是否调用工具、参数校验、最大轮次、超时、截断、状态持久化全部自己写。可和现有数据库、业务表无缝对接。框架封装好的黑盒。想做定制校验、特殊业务拦截,需要继承重写大量内部类。版本一改,重写的代码直接失效。
升级维护框架依赖极少,只依赖 http 客户端 + 模型官方 SDK。业务逻辑完全掌握在团队手里。大版本破坏性变更频繁(0.1→0.2→0.3 很多 API 直接删除)。锁版本也会遇到依赖包冲突。
开发效率前期慢:需自己实现输出解析、tool 调度、上下文截断、token 统计。写一套抽象层约 1-3 周。原型极快,几行代码跑通 Agent。但业务越复杂,定制成本指数上升,后期改黑盒代价远大于手写。
可观测性日志、埋点、metrics 全部自己定义,贴合公司监控体系。每一步请求、返回、tool 入参出参精准打点。内置回调体系,但埋点格式固定。想对接公司内部监控需适配回调。很多内部异常不完整透出。
故障排查链路清晰,出问题看自己代码即可。可以复现每一步请求报文。报错栈嵌套多层框架内部代码,真实业务错误被包装。线上排错成本高。

看完这张表,你可能会说:"LangChain 也不是一无是处啊?" 没错。关键在于你用什么标准衡量它。

四、LangChain 的暗坑:那些文档没写的行为

如果你还在犹豫,下面这些是真实生产项目中踩过的坑,每一个都是"文档没写、代码没写、但线上会犯"的隐式行为:

坑 1:悄悄的消息截断
LangChain 内部会在某些情况下自动截断、合并消息。你的代码看不到这一步,但线上偶发输出异常——因为历史消息被"智能"删减了,上下文丢了关键信息。

坑 2:ToolCall 静默失败
不同模型厂商的 function calling 格式有微小差异。LangChain 的解析逻辑是框架内部封装的,遇到格式差异时不会报错,而是静默返回 None,你的 Agent 会"认为没有工具可调用"然后自己编一个答案。日志里什么都看不出来。

坑 3:版本升级,行为变更
同样的 prompt + 同样的模型,升级 LangChain 版本后返回结果行为发生变化。文档没写这个变更,Changelog 里只有一句 "improved error handling"。你的测试用例全绿,但线上输出变了。

坑 4:依赖树爆炸
pip install langchain 拉进来上百个间接依赖。任何一个子依赖出 bug 或版本冲突,都可能影响你的系统。生产环境运维成本直线上升。

这些坑的共同特点是:它们不是 bug,是"设计如此"。LangChain 的设计哲学是"智能默认值"——帮你做决策。但当你需要为每一行报错负责时,"帮你做决策"等于"帮你埋地雷"。

五、手搓的底气:核心代码其实不到 100 行

"手搓"听起来很重,但 Agent 的核心循环其实非常简单。看一下这个极简实现:

# ============ 适配器层:切换模型只改这里 ============
from abc import ABC, abstractmethod

class LLMProvider(ABC):
    """统一接口:所有模型适配器实现这个接口"""
    @abstractmethod
    def chat(self, messages: list[dict], tools: list = None) -> dict:
        """返回统一结构:{content, tool_calls, usage, raw}"""
        pass

class OpenAIProvider(LLMProvider):
    def __init__(self, model="gpt-4o", api_key=None):
        from openai import OpenAI
        self.client = OpenAI(api_key=api_key)
        self.model = model

    def chat(self, messages, tools=None):
        resp = self.client.chat.completions.create(
            model=self.model, messages=messages, tools=tools
        )
        msg = resp.choices[0].message
        return {"content": msg.content, "tool_calls": msg.tool_calls}

class DashScopeProvider(LLMProvider):  # 通义千问
    def __init__(self, model="qwen-max", api_key=None):
        import dashscope; dashscope.api_key = api_key
        self.model = model

    def chat(self, messages, tools=None):
        resp = dashscope.Generation.call(model=self.model, messages=messages)
        return {"content": resp.output.text, "tool_calls": None}

# ============ Agent 核心循环:不到 50 行 ============
def run_agent(query: str, provider: LLMProvider, tools: list, max_iter=10):
    messages = [{"role": "user", "content": query}]
    for i in range(max_iter):
        result = provider.chat(messages, tools)
        if not result["tool_calls"]:
            return result["content"]  # 模型给出最终答案
        # 执行工具调用
        messages.append({"role": "assistant", "tool_calls": result["tool_calls"]})
        for tc in result["tool_calls"]:
            output = execute_tool(tc, tools)  # 你的业务逻辑
            messages.append({"role": "tool", "content": output})
    return "达到最大轮次限制"

就这么简单。整个 Agent 核心循环不到 50 行,加上适配器层不到 100 行。你拥有每一行代码的完全控制权——最大轮次、超时、参数校验、状态持久化,全部你说了算。

对比一下:用 LangChain 实现同样的功能,你需要理解 AgentExecutorRunnableBaseToolOutputParser 等十几个类的交互关系,而这些类的行为在不同版本间还会变化。

六、预留模型升级口子:适配器模式的威力

你提到"后面升级模型也要预留口子"。手搓的适配器模式天然解决这个问题:

flowchart TB
    subgraph BizLayer["业务层(完全不变)"]
        A["Agent 核心循环
run_agent()"]
        B["工具调度
execute_tool()"]
    end

    subgraph AdapterLayer["适配器层(切换模型只改这里)"]
        C["LLMProvider 接口
chat() 统一签名"]
    end

    subgraph Models["模型实现(可随时新增)"]
        D1["OpenAIProvider
GPT-4o / GPT-4.1"]
        D2["DashScopeProvider
通义千问"]
        D3["DeepSeekProvider
DeepSeek-V3"]
        D4["ClaudeProvider
Claude 3.5"]
        D5["... 新增模型
只需加一个类"]
    end

    A --> C
    B --> C
    C --> D1
    C --> D2
    C --> D3
    C --> D4
    C -.-> D5
        

适配器架构:业务层与模型完全解耦,新增模型只需加一个 Provider 类

这个架构带来三个实际的好处:

  • 新增模型:只需写一个 XXXProvider 类实现 chat() 方法,业务代码一行不改
  • A/B 测试:同一个业务同时调用新旧模型,对比输出质量,灰度切换
  • 降级容灾:主模型挂了,配置中心一键切到备用模型适配器,上层业务零改动

LangChain 也能做多模型,但你会被框架对象"绑架"——切换模型时,Chain、Agent、OutputParser 都可能需要跟着适配,口子预留得不够干净。

七、折中方案:很多企业的实际做法

真实世界里,不是非黑即白。很多成熟团队的做法是混合模式:

生产环境:手搓内核
  • 自己实现 Agent 循环、tool 调度
  • 自己实现 LLM 适配器层
  • 状态落业务数据库
  • 日志/埋点对接公司监控
开发环境:用 LangChain
  • 快速验证 prompt 效果
  • 跑通 Agent 思路原型
  • 参考源码逻辑,抄到自研内核
  • 放在 dev 依赖,不进生产镜像

这是最务实的路线:用 LangChain 的"快"来加速验证,用手搓的"稳"来保障生产。验证通过的 prompt、解析逻辑、tool 定义,复制迁移到自研内核,不直接 import 框架代码。

LangChain 的定位应该是"开发期的脚手架",不是"运行时的内核"

八、什么时候才考虑直接用 LangChain 上生产?

为了公平,以下场景是可以考虑直接用 LangChain 上生产的——但需要同时满足全部条件

  1. 业务允许一定容错,非核心链路,允许偶发异常
  2. 几乎不需要深度定制 Agent/Tool 逻辑,全部使用框架默认行为
  3. 团队愿意锁死框架版本,永远不做大版本升级
  4. 业务迭代慢,不会频繁新增模型、新增工具

你们的情况——追求高稳定性、未来要升级切换模型、大概率金融业务——一条都不满足。

九、写在最后:技术选型的底层逻辑

说到底,技术选型的核心问题不是"LangChain 好不好",而是"谁为你生产环境的每一个异常负责"。

当你用框架时,是框架的维护者在帮你做决策。他们做的决策是面向"大多数场景"的,不是面向你的金融级稳定性要求的。当框架的"智能默认值"和你的业务需求冲突时,你要么忍受,要么深入框架源码去重写——而后者的成本,往往比一开始就自己写还高。

手搓 100 行核心代码,换来的是:

  • 每一行报错你都能看懂——因为是你写的
  • 每一次模型切换只需改一个类——因为适配器是你设计的
  • 每一个默认行为都是你决定的——没有"智能"的惊喜
  • 框架升级永远不会影响你——因为你只依赖官方 SDK

这 100 行代码的投入,换来的是凌晨三点可以安稳睡觉的底气。这可能是你今年做过的最划算的技术投资。

最后说一句有温度的话:选型没有对错,只有"是否匹配你的约束"。如果你的团队还在起步阶段,业务允许试错,用 LangChain 快速跑起来也完全合理。但如果你的系统已经有了用户、有了 SLA、有了凌晨三点的告警——请把手搓当作一个严肃的选项。

这不是对 LangChain 的否定,是对你业务的尊重。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

only-qi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值