5653 字
28 分钟
把测试平台的经验迁移到问数与智能客服:我的 AI 应用开发实战复盘

这篇是我对自己几个 AI 项目的系统复盘。主项目是 RuleTest-Agent(一个 AI 驱动的自动化测试平台),另外两个是医疗领域 RAG 端侧问答系统、工业视觉+控制闭环缺陷检测系统。我会顺着这些经验,一条条迁移到我接下来想做的「AI 问数(NL2SQL)」和「智能客服」两个方向上——因为我越做越确信:这些场景的工程内核是同构的。


第一部分:RuleTest-Agent 深挖#

为什么要做「多智能体 + 确定性 Pipeline」双引擎,而不是全部用 Agent 自由决策?#

这个项目是一个 AI 驱动的自动化测试平台,覆盖三个能力:API 文档解析生成测试用例、接口白盒分支覆盖测试、Java 大型类的单测自动生成。

双引擎是我们踩过坑之后的架构决策。最初全部走 Coordinator + 多 Worker 的 ReAct 模式,LLM 自己决定流程,开放式对话任务没问题,但结构化任务暴露了两个致命缺陷:不可重放不可验收——同一个任务跑两次路径可能不一样,失败了只能从头重跑,成本和时间都不可控。

所以我们把「控制流」做了切分:开放任务控制流在 LLM 手里(Coordinator 分解任务 → Phase 调度 → Worker 执行);可验收的结构化任务控制流收回到 Python 代码里,LLM 退化为流水线中的单步「函数调用」。Pipeline 每一步输入输出落盘,支持断点续跑,失败从指定步骤恢复。

这个经验直接迁移到问数和客服场景:问数本质也是结构化任务(意图识别 → Schema 定位 → SQL 生成 → 执行 → 校验 → 解释),适合 Pipeline 化保证可控;而客服的多轮自由对话更适合 Agent 模式。什么时候让 LLM 掌控流程、什么时候只让它当「函数」,是 LLM 应用工程化最核心的判断。

「换被测系统核心代码零改动」是怎么做到的?这个设计思想能用到智能客服上吗?#

两层机制。第一层是配置驱动:超时、重试、URL、业务关键词这类一律从 skill 的 config.yaml 读取,代码里只允许「读不到时的兜底默认值」。第二层是插件化 Skill 注入:核心代码只承载通用编排和执行骨架,具体领域知识——流程提示词、专用工具、业务规则——全部放在 .testagent/skills/ 目录下的 SKILL.md + config.yaml + tools/ 里,引擎启动时扫描注入。新增一种测试能力,理论上不碰核心代码,加一个 skill 目录就行。

这对智能客服是直接可复用的:客服系统最大的工程痛点就是换一个业务线(银行/保险/电商)知识、话术、流程全变。如果把领域 FAQ、业务流程、工单字段映射都做成 skill 插件 + 配置,核心对话引擎就能跨业务线复用,交付新客户的成本从「改代码」降到「写配置」。

分支覆盖测试里「以覆盖率为反馈多轮迭代」,这个闭环具体怎么设计的?LLM 生成的东西不可靠怎么办?#

闭环是:静态分析提取调用链和分支条件(tree-sitter 解析 + 向量库做代码定位)→ LLM 针对未覆盖分支生成用例 → 真实执行 → JaCoCo 拿到覆盖率报告 → 把「哪些分支没覆盖」作为结构化反馈喂回 LLM → 下一轮针对性补。关键是反馈不是笼统的「覆盖率不够」,而是精确到「第几行的哪个条件分支的 false 路径没走到」。

可靠性上我的核心原则是:永远不信任 LLM 的单次输出,用确定性手段验收。生成的测试代码必须真实编译、真实跑、覆盖率必须由 JaCoCo 实测,而不是 LLM 自己声称。我们还踩过一个真实的坑:测试报告统计被陈旧的 surefire XML 污染,导致「误报失败」,最后是靠「跑测试前清理陈旧报告 + 剔除空壳测试类后权威重测」修掉的——这让我深刻体会到,AI 系统的可信度不在模型,而在验收环节的工程严谨性

迁移到问数场景就是同一套思路:SQL 生成后先 EXPLAIN 校验语法和表权限、限制扫描行数、对比执行结果与意图的一致性,错了带着数据库报错信息回炉重试,而不是直接把第一次生成的 SQL 给用户。

支持多家 LLM 提供商,实际切换过吗?切换中遇到什么问题?#

真实切换过,而且有一次是被迫的踩坑。我们抽象了统一的模型客户端层,支持 DashScope、OpenAI 兼容接口、Anthropic、Gemini、Ollama,上层代码不感知具体厂商。

有一次为了降成本切到一家更便宜的模型,实测下来生成质量明显下滑、连接错误频发、整体耗时反而拉长了几倍,最后切回了原模型。这次踩坑还顺带暴露了我们 OpenAI 兼容分支没有注入 timeout 的 bug——第三方兼容网关响应慢时请求会无限挂起,我补上了超时注入。

我的结论有两条:一是模型选型必须用自己业务的真实任务做基准测试,价格表上的便宜不等于综合成本低;二是适配层必须把超时、重试、流式中断这些「脏活」做扎实,因为第三方兼容网关的行为千差万别。问数和客服都是高频调用场景,模型路由(简单问题走小模型、复杂走大模型)+ 健壮的适配层会是成本控制的关键。

实时流式输出这条链路是怎么设计的?中间断了怎么办?#

我们是三段式:Python Agent 子进程通过 HTTP Hook 把事件推回 Node 后端 → 后端双通道分发(SSE 队列 + Socket.IO 广播)→ 前端 Vue 接收渲染。几个关键设计:

  1. 推送是辅助信号,失败静默不阻塞主流程——Agent 该干活干活,推送挂了任务不能挂;
  2. SSE 30 秒无消息发 heartbeat,防止代理层掐掉长连接;
  3. 流式消息同时聚合持久化为完整消息,前端 Pinia 维护流式缓存,用户中途刷新页面或切换会话回来,内容能恢复,不是「刷新就丢」;
  4. 安全上子进程的密钥走环境变量注入而不是命令行参数(命令行在 ps 里可见),回推请求带签名头校验。

这套东西对智能客服几乎是同构的:客服的逐字流式输出、多轮会话的断线恢复、客服后台的实时监控推送,链路设计完全一致,我可以直接复用这些经验。


第二部分:业务迁移——AI 问数#

如果让我从零搭建一个 AI 问数系统(自然语言查数据库),我会怎么设计?#

我会用 RuleTest 验证过的「确定性 Pipeline + LLM 单步调用」架构,拆成六步:

  1. 意图理解与澄清:判断是查数、追问还是闲聊;指代消解(「上个月呢?」补全成完整问题);歧义主动反问而不是猜。
  2. Schema 召回:表多时不能全塞 prompt,用向量 + 关键词混合检索召回相关表和字段——这和我在 RuleTest 里用 BM25 + 向量混合检索做记忆召回、用 ChromaDB 做代码定位是同一套技术。
  3. SQL 生成:prompt 里带表结构、字段枚举值、业务口径定义(「销售额」到底含不含退款,必须用指标字典统一口径)和少量相似问题示例。
  4. 校验执行:EXPLAIN 预检、只读权限、行数限制、超时控制;报错带着错误信息回炉,最多 N 轮。
  5. 结果呈现:数据 + 自动选图表类型 + 自然语言解读。
  6. 反馈闭环:用户点「不对」的案例进入 badcase 库,沉淀为后续的少样本示例。

核心理念还是那句话:每一步可落盘、可重放、可单独评估,出了问题能定位到是召回错了还是生成错了,而不是一个黑盒。

问数最怕「数出来是错的,用户还信了」。怎么保证准确性?#

分四层防:

  1. 口径前置:大部分「算错」其实是口径理解错。指标定义(含不含税、自然月还是财月)必须做成结构化的指标字典注入 prompt,不让模型自由发挥。
  2. 确定性校验:SQL 必须真实通过语法检查和试执行才返回——这是我在 RuleTest 里「LLM 产出必须经编译和实测验收」原则的直接平移。
  3. 可解释性兜底:返回结果时同步展示「我理解你要查的是 X,使用了表 Y,统计口径是 Z」,让用户有能力发现理解偏差,这比单纯提高准确率更能建立信任。
  4. 置信度分级:召回分数低或生成多次不一致时,宁可反问澄清,也不输出一个看起来很自信的错误答案。

我在 RuleTest 里修过一个「报告误报」的 bug,深刻体会是:用户对系统的信任是被一次「自信的错误」摧毁的,所以「承认不确定」必须是系统的一等能力。

我没有直接做过 NL2SQL,怎么证明能快速上手?#

我不回避这点,但我想拆解一下 NL2SQL 工程上由什么构成:自然语言理解和生成靠 LLM + 提示词工程,我在 RuleTest 里写过大量结构化提示词(让 LLM 稳定输出可编译的 Java 测试代码,约束强度比 SQL 高);Schema 召回靠向量检索,我在医疗 RAG 项目里完整做过 BioBERT 语义向量 + 向量库的检索链路,在 RuleTest 里做过 tree-sitter + ChromaDB 的代码语义索引;生成结果校验靠确定性工程,这是我整个 RuleTest 项目的核心方法论。

所以 NL2SQL 对我来说不是新领域,是已有能力在新载体上的组合——把「生成 Java 测试代码并用覆盖率验收」换成「生成 SQL 并用执行结果验收」,架构同构。真正需要补的是业务侧的数仓建模和指标口径知识,这部分跟业务方对齐就能补上,工程框架第一周就能搭起来。


第三部分:业务迁移——智能客服#

智能客服最大的风险是大模型幻觉,客户问退款政策答错了要赔钱的。怎么治理?#

我在医疗 RAG 项目里专门解决过这个问题,医疗场景对幻觉的容忍度比客服更低。我的治理是三层:

  1. 检索层:回答必须基于检索到的知识片段,检索分数低于阈值直接走「未找到相关政策,为您转人工」,而不是让模型自由生成。医疗项目里我针对专业术语微调了向量模型来提高高信息密度文本的检索精度,客服场景同理——业务术语(「保费」「免赔额」)的检索质量决定了答案上限。
  2. 生成层:prompt 强约束「仅依据以下资料回答,资料中没有的明确说不知道」,回答带引用来源,可追溯到具体政策条款和版本。
  3. 验收层:高风险意图(退款、赔付、投诉)单独识别出来,走更严格的模板化回答或强制人工审核,低风险闲聊才让模型自由发挥。上线后用 badcase 回流持续评估,幻觉率作为核心指标监控。

本质上和我做测试平台的哲学一致:不消除模型的不确定性,而是用工程手段把不确定性圈在可接受的范围内

多轮对话里用户突然改主意、话题跳转,上下文管理怎么做?#

我在 RuleTest 里设计过三层记忆体系,可以直接映射到客服场景:

  • Working Memory(当前会话上下文):维护本轮对话窗口,超长时做摘要压缩而不是粗暴截断——重点保留用户的核心诉求和已确认的槽位(订单号、手机号),丢掉寒暄。
  • Collaborative Memory(中间结果共享):在客服里对应「会话状态」——当前在办什么业务、办到哪一步、还缺什么信息。话题跳转时旧任务状态挂起而非丢弃,用户说「先帮我查下物流」再回来办退款,已填的信息还在。
  • Global Memory(长期记忆):跨会话的用户画像和历史工单,BM25 + 向量混合检索召回,用户第二次进线不用从头说一遍。

话题跳转的识别我会单独做一个轻量意图判断步骤:每轮先判断是「延续当前任务」还是「新开任务」,这一步用小模型或规则做,便宜且稳定,不和主回答混在一个 prompt 里——这也是我在 RuleTest 里反复验证的原则:职责拆得越细,每一步越可控


第四部分:RAG 与检索#

医疗 RAG 项目用了 BioBERT 做向量,为什么不直接用通用 embedding 模型?检索召回不准时怎么调优?#

通用 embedding 在医疗这种高信息密度、强专业性的文本上有明显短板——「心梗」和「心肌梗死」是同义词、「阿司匹林」和「乙酰水杨酸」是同一种药,通用模型的向量空间里这些不一定挨得近,召回就会漏。BioBERT 是在生物医学语料上预训练的,对医学术语的语义表达天然更准,我又针对我们的术语做了微调,进一步压缩了同义术语的向量距离。

召回不准我是分维度排查的:先看是分块问题还是向量问题——chunk 切太大噪声多、切太小语义断裂,我做过分块粒度的对比实验;再看是不是单一向量召回的固有缺陷,纯语义检索对精确的专有名词、编号反而不如关键词,所以我倾向向量 + BM25 混合检索,这个在 RuleTest 的记忆系统里也是同一套方案;最后是重排,召回阶段宽召回、再用更重的模型精排取 top-k。问数和客服场景这套调优方法完全通用。

RAG 系统里「检索召回了但模型答错了」和「压根没召回到」是两类问题,怎么定位是哪一类?#

这正是我做 RuleTest 时反复强调的「每一步可单独评估」的价值。我会把 RAG 链路拆成可观测的两段分别打点:

  • 检索段评估:把召回的片段单独拎出来看,标准答案对应的知识到底在不在 top-k 里。如果不在,是检索/召回的问题——去调 embedding、分块、混合检索权重。
  • 生成段评估:如果正确知识明明在召回结果里、模型却答错或答漏,那是生成段的问题——去调 prompt、上下文顺序(「lost in the middle」,关键信息别放中间)、或者召回片段太多冲淡了重点。

工程上我会让每次问答都落盘 query、召回片段、最终回答,badcase 能一眼看出断点在哪。这和我在 RuleTest 里定位「覆盖率塌方到底是 import 炸了还是生成逻辑错了」是完全一样的排障思路——先把黑盒拆成可观测的段,再定位

知识库会频繁更新(政策改了、产品下架了),怎么保证 RAG 答的是最新的、不答过期内容?#

这是我在医疗项目里专门处理过的「知识时效性」问题。几个手段:

  1. 增量更新而非全量重建:向量库支持按文档 ID 增删改,政策更新只重建受影响的那批向量,这和我在 RuleTest 单测的「增量模式」(在已有基线上增量提升)是同一思路。
  2. 元数据带版本和生效时间:每个知识片段带 versioneffective_datestatus 元数据,检索时按生效时间过滤,下架/过期的直接排除在召回之外。
  3. 冲突时新版本优先:同一主题召回到多个版本,按时间戳取最新,并在回答里标注依据的政策版本号,可追溯。
  4. 过期兜底:检索不到有效版本时明确说「该政策可能已调整,请以最新公告为准/转人工」,绝不拿旧版本硬答。

核心还是那句——宁可承认不知道,不给一个自信的过期答案


第五部分:LLM 工程与 Agent#

我对 Function Calling / 工具调用的理解,以及在 RuleTest 里工具是怎么注册和管理的#

工具调用本质是让 LLM 把「自然语言意图」翻译成「结构化的函数调用参数」,由我们的代码真正执行,再把结果喂回模型。它是 Agent 能「动手」而不只是「动嘴」的关键。

RuleTest 里我做了统一的工具注册层,工具包括文件读写、Shell、Web、Glob、Grep 这些通用能力,外加各 skill 自带的领域工具。注册层负责把工具的 schema(名字、参数、描述)暴露给 LLM,并统一处理执行、异常、超时。设计上我坚持两点:一是工具描述就是给模型的提示词,描述写不清模型就会乱调,这部分要反复打磨;二是领域工具放 skill 插件、通用工具放核心层,不让领域工具污染骨架。

迁移到问数/客服就很自然:问数里「执行 SQL」「查指标字典」「画图表」都是工具;客服里「查订单」「提交工单」「查物流」都是工具。把业务系统的 API 包装成规范的工具集、写好让模型能准确调用的描述,是 Agent 落地业务的核心工作量所在。

ReAct 模式是什么?它有什么局限?怎么应对它「想太多/绕圈子」的问题?#

ReAct 就是 Reasoning + Acting 交替:模型先想(这一步该干嘛)、再调工具、看结果、再想下一步,循环到完成。好处是灵活、能处理开放任务;局限也很明显——不可控:可能绕圈子重复调同一个工具、可能在错误方向上越走越远、token 和耗时不可预测,还可能陷入死循环。

我在 RuleTest 里的应对就是前面说的双引擎思想:能确定的流程绝不交给 ReAct。具体到还得用 ReAct 的开放任务,我加了几道护栏——最大步数限制防死循环、目标评估器每轮判断是否已达成/是否在原地打转、Phase 调度把大任务切成有验收标准的小阶段而不是让一个 Agent 一口气跑到底。

这个判断对客服特别重要:客服不能让 Agent 自由 ReAct 几十轮把用户绕晕,常见业务(查单、退款)应该是确定性流程 + 关键节点调模型,只有真正开放的咨询才放开 ReAct,而且要有步数和兜底转人工的护栏。

提示词工程的实战经验:怎么让 LLM 稳定输出我要的结构化结果#

我在 RuleTest 里最硬核的提示词工作,是让 LLM 稳定输出能直接编译通过的 Java + Mockito 测试代码——这个约束强度比输出 JSON 高得多,差一个 import、Mock 写法不对就编不过。我的实战经验有几条:

  1. 给配方而不是给目标:不是说「写个测试」,而是把「什么依赖该用什么 Mock 写法」「静态方法怎么 MockedStatic」做成结构化的配方步骤写进提示词,模型照着填空,稳定性大幅提升。
  2. 少样本示例 > 长篇描述:给两三个高质量的输入输出示例,比写一大段规则管用。
  3. 结构化约束输出:强制 JSON Schema / 固定格式,方便代码解析,解析失败带错误回炉重试。
  4. 失败案例驱动迭代:把模型常犯的错收集成 badcase,针对性在提示词里加约束(「禁止用 XXX 写法」),这是个持续打磨的过程。

问数里让模型稳定输出合法 SQL、客服里稳定输出「答案+引用+置信度」的结构,用的是同一套方法论。


第六部分:工程落地#

「覆盖率 90%+」「分支覆盖 75~90%」是怎么测出来的?如果上线后效果没达预期,怎么排查和迭代?#

这些数字都是真实被测系统上 JaCoCo / 覆盖率工具实测出来的,不是模型自己声称的——我前面提过,我整个项目最坚持的原则就是 LLM 产出必须经确定性手段验收。我们在多个真实 Java 项目(不同复杂度的 Service 类)上跑过横向对比,还摸清了能力边界:纯函数式的类能到 80% 以上,而 HttpServletRequest / session / Excel 这类有状态依赖密集的类会明显偏低,这个边界认知本身就是实测的产物。

效果没达预期我的排查路径是固定的:先拆段定位,再针对性迭代。比如单测覆盖率上不去,我会先看是生成失败(编译不过)、还是生成成功但没覆盖到关键分支、还是某类方法(IO/Excel 巨兽)模型根本啃不动。定位清楚是哪一段的问题,再决定是改提示词、加工具、还是把这类方法识别出来单独处理。我还踩过覆盖率「塌方」的坑,最后查出是重构时漏搬了模块级依赖导致运行时 NameError,而不是模型变差了——这件事让我养成习惯:指标异动先怀疑工程链路,再怀疑模型

这套「实测验收 + 拆段定位 + 数据驱动迭代」放到问数的准确率、客服的解决率上完全一样:定基线、拆漏斗、找断点、灰度迭代,而不是拍脑袋调。

把测试平台的经验迁移到问数与智能客服:我的 AI 应用开发实战复盘
https://liuhuanblog.top/posts/ai-app-dev-project-deep-dive/
作者
liuhuan
发布于
2026-06-11
许可协议
CC BY-NC-SA 4.0