有一种新的技术债,它不是来自偷工减料,而是来自伸手去拿房间里最炫酷的工具。
可以称之为简历驱动的 AI 工程:选择某个智能体框架、向量数据库或多模型编排层,是因为它写在简历上很好看,而不是因为问题需要它。结果在演示里能跑通。但它也更慢、更贵、更难调试,而且在本来不需要的地方变得不确定。
演示 vs. 值班传呼机
在教程里,复杂度是免费的。你启动一个智能体,接入一个向量存储,然后看着它用十份样本文档做出点聪明的事。
在生产环境里,每一个活动部件都有成本:延迟、token 开销、一种新的故障模式、一个凌晨 3 点还得有人去理解的新东西。问题不是“LLM 能不能做这件事?”(通常能)。而是“LLM 是不是能可靠地做这件事的最简单方案?”
有三个地方答案往往是否定的。这些场景是示意性的,但如果你在 AI 项目里待过一阵子,它们看起来会很眼熟。
故障模式 1:一个正则表达式就能搞定,却要调用模型
一个团队需要从收到的邮件里提取发票号。发票号遵循固定格式:INV-加上八位数字。他们把每封邮件都发给 LLM,用提示词要求它提取这个号码。
98% 的情况下它能工作。另外 2% 的情况下,模型“贴心地”重新格式化了号码,或者抓取了一个采购订单号。每次调用都要花钱,还增加几百毫秒延迟。
import re
INVOICE_RE = re.compile(r"\bINV-\d{8}\b")
def extract_invoice_ids(text: str) -> list[str]:
return INVOICE_RE.findall(text)
确定性、可测试、实际上免费,而且运行只需微秒。把模型留给模式处理不了的杂乱情况,只在正则表达式找不到任何东西时才路由给它。
故障模式 2:SQL 就能搞定,却要用向量搜索
“给我看客户 4417 在过去 30 天内所有仍未支付的订单。”
这不是一个语义问题。这是一个过滤条件。然而常见的是,这类查询被嵌入、推过向量存储,再由 LLM 总结 top-k 个片段来回答,而这些片段可能包含、也可能不包含每一个匹配的订单。
SELECT id, total, created_at
FROM orders
WHERE customer_id = 4417
AND status = 'unpaid'
AND created_at >= NOW() - INTERVAL '30 days';
精确、完整、可索引、可审计。当你匹配的是含义时(“与此投诉相似的工单”),向量搜索很棒。但当你匹配的是事实时,它就是错误的工具。
失败模式 3:本该用决策树的地方却用了自主智能体
一个支持工作流:如果客户是企业版套餐且问题涉及账单,就转给客户管理团队;如果是 bug,就开一张工单;否则,发送 FAQ 链接。
这就是四个分支。有人却把它做成了一个自主智能体,带有工具访问、规划循环和记忆存储。现在路由变成了概率性的,偶尔会循环,而且没人能解释为什么工单 #8812 被分到了错误的团队。
def route(customer, issue):
if customer.plan == "enterprise" and issue.type == "billing":
return "account_management"
if issue.type == "bug":
return "open_ticket"
return "send_faq"
如果你能在白板上画出这套逻辑,那你大概不需要一个智能体在每次请求时重新发现它。
重新审视
很多“AI 系统”其实只是普通软件,只不过在一个本不需要 LLM 的步骤上硬加了一个 LLM。模型不是问题。把它当作默认选项而非例外,才是问题。
一份实用的决策清单
在添加框架、模型调用或智能体之前,先问:
- 输入是结构化的,还是输出是固定格式的?从解析、正则表达式或模式校验开始。
- 这个问题关乎事实还是关乎含义?事实交给 SQL。含义可以交给嵌入。
- 逻辑能被枚举出来吗?如果可以,就把分支写出来。智能体适用于那些你确实无法枚举的开放式任务。
- 出错时会发生什么?如果答案是“悄无声息的坏数据”,那你需要的是确定性。
- 一年后谁来维护它?每个框架都是一项依赖,总得有人去升级、理解和调试。
这一切并不意味着“永远不要用 AI”。把它用在真正存在歧义的地方:非结构化文本、模糊匹配、生成。只要让它成为你有意选择的工具,而不是条件反射式的选择。
最好的工程师不是拥有最复杂技术栈的人。而是那些在出问题时,系统仍然简单到足以被理解的人。
你见过(或自己搭建过)的最过度设计的 AI 方案是什么,本来可以用一个很无聊的东西替代?