🔥从"打补丁"到"换思路":一次企业级 AI Agent 的架构拐点

在做企业级 AI Agent 时,我踩过一个非常典型的坑。

一开始我以为只是个"小逻辑问题"。后来我发现,那是一次架构认知的分水岭

这篇文章,讲的不是"消息补全"。讲的是一个更重要的问题:
当规则开始打补丁时,你是不是已经选错了工具?

问题很简单:AI 听不懂 "...这个呢"

我们在做一个企业内部智能运维助手 LUI Agent。能力很清晰:

  • 查询域名状态
  • 查询 Pod 数
  • 搜索内部文档
  • ......

在实现多轮对话时,出现了一个极其常见的问题:

text 复制代码
用户:查询域名 bbb.com 的状态
AI:该域名 QPS 为 120,P99 为 45ms...

用户:yyy.com 这个呢
AI:???

第二句话------

"yyy.com 这个呢"

从人类视角看,毫无歧义。

但从工具调用视角看,这是一个不完整的句子

下游网关服务根本不知道用户要干什么。

所以我们需要一个能力:

在调用工具前,把"不完整的问题"补全为完整问题。

第一反应:规则一定能搞定(踩坑之路)

作为一个开发者,我的第一反应非常自然:这不就是模式匹配吗?

于是写了第一版规则:

ts 复制代码
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

css 复制代码
b.com也查一下

好,加一个"也 + 动词"规则。

Case 3

复制代码
yyy.com呢

好,再扩展域名 + 呢。

Case 4

复制代码
这个应用有几个pod

以"这个"开头,但其实是完整问题。误判。

Case 5

复制代码
这个功能很好用

被误判为"不完整问题"。假阳性。


那一刻我突然意识到:

我已经开始写"例外规则"了。

而当你开始写例外规则的时候,你已经失去了规则系统的简洁性。

规则不再是"设计",它开始变成"修修补补"。(越来越不好维护!)

真正的问题:这不是字符串问题

我突然意识到一个更本质的问题:我在用规则解决一个语义问题。

  1. "这个呢" 不是句法问题,是指代问题。
  2. 不是字符串匹配问题,是上下文理解问题。

这本质上是一个语义理解任务。而我在用规则解决语义,这就像用 if/else 写一个自然语言理解系统,注定会崩。

相反,正是 LLM 天生擅长的领域。

意图识别

换思路:让 LLM 做意图补全

我加了一层"消息预处理"。在真正调用 agent 工具前,让 LLM 判断:

  • 当前问题是否完整?
  • 是否是追问?
  • 是否需要结合历史补全?

核心逻辑:

javascript 复制代码
/**
 * 预处理消息:使用 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 起了很大作用:

md 复制代码
你是一个意图分析助手。判断用户当前输入是否需要根据对话历史补全。

## 任务
1. 判断当前输入是否是一个完整、独立的问题
2. 如果是完整问题,直接返回原文
3. 如果是追问、指代、省略句式(如"这个呢"、"xxx也查一下"、"状态如何"),结合历史补全为完整问题

效果对比:规则 vs 语义

对话历史:

makefile 复制代码
用户: 查询域名 xxx.com 的状态
助手: 该域名 QPS 为 120,响应时间 P99 为 45ms...

用户输入: yyy.com这个呢
LLM 补全: 查询域名 yyy.com 的状态 ✅

对话历史:

makefile 复制代码
用户: 这个应用有几个pod
助手: 当前应用 yyy.com 有 3 个 Pod...

用户输入: 这个应用呢(切换了应用)
LLM 补全: 这个应用有几个pod ✅

对话历史:

makefile 复制代码
用户: 查询 xxx.com 的 QPS
助手: xxx.com 的 QPS 为 50...

用户输入: yyy.com 也查一下
LLM 补全: 查询 yyy.com 的 QPS ✅

完美!LLM 能够理解语义,自动处理各种追问句式。

  • 没有新增规则。
  • 没有顺序依赖。
  • 没有边界爆炸。

它理解了语义。

但 LLM 不是银弹

LLM 问题也随之而来:

  • 延迟增加 500ms ~ 1s。
  • Token 成本增加。
  • 输出不可 100% 可控。

所以,我没有"全盘 LLM 化"。而是做了一个分层架构。

混合架构:规则前置,LLM兜底

在实际项目中,采用了"规则快速拦截 + LLM 深度分析"的混合策略:

js 复制代码
// 意图分析流程
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 的现实架构。

更隐蔽的一次教训:历史丢失

后来,我还踩了一个更隐蔽的坑。

日志显示:

js 复制代码
API historyLength: 10
MasterAgent historyLength: 6

丢了 4 条。原因:

js 复制代码
JSON.stringify(undefined) // -> undefined

某些结构化消息没有 content 字段,被我写的代码逻辑给过滤掉了。

修复方式: 直接 stringify 化

js 复制代码
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 开发方向的工作,后续我会持续分享这两方面的文章。欢迎大家随时来交流~~

相关推荐
2分钟速写快排13 分钟前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
linqiw20 分钟前
Embabel 上手:Java Agent 是怎么干活的
agent
passerby60611 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角2 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!2 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰2 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
Interview Aid1122 小时前
Walmart Global Tech SDE 三轮面经|基础、并发、压力面
面试·职场和发展
大模型真好玩3 小时前
DeepSeek Harness 入门很简单(三)——DeepSeek Harness接入工具、MCP、Skill
人工智能·agent·deepseek
mayaairi4 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js