🔥从"打补丁"到"换思路":一次企业级 AI Agent 的架构拐点
在做企业级 AI Agent 时,我踩过一个非常典型的坑。
一开始我以为只是个“小逻辑问题”。后来我发现,那是一次架构认知的分水岭。
这篇文章,讲的不是“消息补全”。讲的是一个更重要的问题:
当规则开始打补丁时,你是不是已经选错了工具?
问题很简单:AI 听不懂 “...这个呢”
我们在做一个企业内部智能运维助手 LUI Agent。能力很清晰:
- 查询域名状态
- 查询 Pod 数
- 搜索内部文档
- ......
在实现多轮对话时,出现了一个极其常见的问题:
用户:查询域名 bbb.com 的状态
AI:该域名 QPS 为 120,P99 为 45ms...
用户:yyy.com 这个呢
AI:???
第二句话——
“yyy.com 这个呢”
从人类视角看,毫无歧义。
但从工具调用视角看,这是一个不完整的句子。
下游网关服务根本不知道用户要干什么。
所以我们需要一个能力:
在调用工具前,把“不完整的问题”补全为完整问题。
第一反应:规则一定能搞定(踩坑之路)
作为一个开发者,我的第一反应非常自然:这不就是模式匹配吗?
于是写了第一版规则:
private isIncompleteMessage(message: string): boolean {
const trimmed = message.trim();
if (trimmed.length < 8) return true;
if (/呢[??]?$/.test(trimmed)) return true;
if (/吗[??]?$/.test(trimmed)) return true;
if (/^(这个|那个|这|那)/.test(trimmed)) return true;
return false;
}
看起来很优雅:
- 短消息?拦截。
- 追问句式?拦截。
- 指代开头?拦截。
覆盖三大类问题。我当时甚至觉得设计得挺漂亮。
然后,规则开始失控
测试几轮后,问题很快暴露:
Case 1
yyy.com这个呢
长度 19,不符合 < 8。
规则顺序导致被判定为“完整问题”。
我开始加补丁。
Case 2
b.com也查一下
好,加一个“也 + 动词”规则。
Case 3
yyy.com呢
好,再扩展域名 + 呢。
Case 4
这个应用有几个pod
以“这个”开头,但其实是完整问题。误判。
Case 5
这个功能很好用
被误判为“不完整问题”。假阳性。
那一刻我突然意识到:
我已经开始写“例外规则”了。
而当你开始写例外规则的时候,你已经失去了规则系统的简洁性。
规则不再是“设计”,它开始变成“修修补补”。(越来越不好维护!)
真正的问题:这不是字符串问题
我突然意识到一个更本质的问题:我在用规则解决一个语义问题。
- “这个呢” 不是句法问题,是指代问题。
- 不是字符串匹配问题,是上下文理解问题。
这本质上是一个语义理解任务。而我在用规则解决语义,这就像用 if/else 写一个自然语言理解系统,注定会崩。
相反,正是 LLM 天生擅长的领域。
意图识别
换思路:让 LLM 做意图补全
我加了一层“消息预处理”。在真正调用 agent 工具前,让 LLM 判断:
- 当前问题是否完整?
- 是否是追问?
- 是否需要结合历史补全?
核心逻辑:
/**
* 预处理消息:使用 LLM 判断并补全不完整的问题
*/
private async preprocessMessage(
message: string,
history: BaseMessage[],
agentId: string,
requestId?: string
): Promise<string> {
// 没有历史对话,无需补全
if (history.length === 0) {
return message;
}
const llm = getLLM();
// 取最近的对话历史
const recentHistory = history.slice(-6).map(msg => {
const role = msg instanceof HumanMessage ? '用户' : '助手';
return `${role}: ${String(msg.content).substring(0, 200)}`;
}).join('\n');
const prompt = `你是一个意图分析助手。判断用户当前输入是否需要根据对话历史补全。
## 对话历史
${recentHistory}
## 用户当前输入
${message}
## 任务
1. 判断当前输入是否是一个完整、独立的问题
2. 如果是完整问题,直接返回原文
3. 如果是追问、指代、省略句式(如"这个呢"、"xxx也查一下"、"状态如何"),结合历史补全为完整问题
## 输出
只返回最终的问题(补全后或原文),不要任何解释。`;
const response = await llm.invoke(prompt);
const completed = typeof response.content === 'string'
? response.content.trim()
: message;
// 记录是否进行了补全
if (completed !== message) {
this.logger.info('消息已补全', { original: message, completed });
}
return completed || message;
}
核心 Prompt 起了很大作用:
你是一个意图分析助手。判断用户当前输入是否需要根据对话历史补全。
## 任务
1. 判断当前输入是否是一个完整、独立的问题
2. 如果是完整问题,直接返回原文
3. 如果是追问、指代、省略句式(如"这个呢"、"xxx也查一下"、"状态如何"),结合历史补全为完整问题
效果对比:规则 vs 语义
对话历史:
用户: 查询域名 xxx.com 的状态
助手: 该域名 QPS 为 120,响应时间 P99 为 45ms...
用户输入: yyy.com这个呢
LLM 补全: 查询域名 yyy.com 的状态 ✅
对话历史:
用户: 这个应用有几个pod
助手: 当前应用 yyy.com 有 3 个 Pod...
用户输入: 这个应用呢(切换了应用)
LLM 补全: 这个应用有几个pod ✅
对话历史:
用户: 查询 xxx.com 的 QPS
助手: xxx.com 的 QPS 为 50...
用户输入: yyy.com 也查一下
LLM 补全: 查询 yyy.com 的 QPS ✅
完美!LLM 能够理解语义,自动处理各种追问句式。
- 没有新增规则。
- 没有顺序依赖。
- 没有边界爆炸。
它理解了语义。
但 LLM 不是银弹
LLM 问题也随之而来:
- 延迟增加 500ms ~ 1s。
- Token 成本增加。
- 输出不可 100% 可控。
所以,我没有“全盘 LLM 化”。而是做了一个分层架构。
混合架构:规则前置,LLM兜底
在实际项目中,采用了"规则快速拦截 + LLM 深度分析"的混合策略:
// 意图分析流程
async analyzeIntent(message: string, history: BaseMessage[]) {
// 1. 规则快速拦截(< 1ms)
const quickResult = this.quickIntercept(message);
if (quickResult.confident) {
return quickResult;
}
// 2. LLM 深度分析(500ms - 3s)
const llmResult = await this.llmAnalyze(message, history);
return llmResult;
}
// 规则快速拦截
private quickIntercept(message: string) {
// 问候语
if (/^(你好|hi|hello|嗨)/i.test(message)) {
return { agentId: 'general', confident: true };
}
// 身份询问
if (/你是谁|你叫什么/.test(message)) {
return { agentId: 'general', confident: true };
}
// 导航意图(明确的跳转词)
if (/^(跳转|打开|进入|去)(到)?/.test(message)) {
return { agentId: 'navigation', confident: true };
}
// 不确定,交给 LLM
return { confident: false };
}
规则适合:
- 问候语(例如:你好、你是)
- 明确跳转(例如:打开**、跳转**)
- 格式校验
- 固定关键词
LLM 适合:
- 指代
- 追问
- 模糊表达
- 语义补全
规则保证速度,LLM 保证理解。这才是企业级 Agent 的现实架构。
更隐蔽的一次教训:历史丢失
后来,我还踩了一个更隐蔽的坑。
日志显示:
API historyLength: 10
MasterAgent historyLength: 6
丢了 4 条。原因:
JSON.stringify(undefined) // -> undefined
某些结构化消息没有 content 字段,被我写的代码逻辑给过滤掉了。
修复方式: 直接 stringify 化
function getMessageContent(msg) {
if (typeof msg.content === 'string') return msg.content;
const { role, timestamp, ...rest } = msg;
return JSON.stringify(rest);
}
让 LLM 自己理解结构化数据。
这件事让我学到一个重要认知:
不要低估 LLM 对结构化信息的理解能力。
信息别丢,比格式完美更重要。
总结
这不是一个“补全功能优化”的故事,这是一个架构边界判断问题:
- 规则系统适合确定性边界
- 语义系统适合模糊边界
当你发现:
- 规则在不断打补丁
- 误判越来越多
- 例外规则越来越复杂
那很可能 —— 你在用规则解决语义问题。
很多人做 Agent,沉迷 Prompt。但真正重要的不是 Prompt 写多长。而是学会判断:
什么时候该用规则;
什么时候该交给语义(LLM 意图识别)。
如果你也在做 Agent,你现在的系统,是规则在膨胀?还是语义在进化?
![]()
note: 我最近一直在做 前端转全栈、前端转 AI Agent 开发方向的工作,后续我会持续分享这两方面的文章。欢迎大家随时来交流~~