智能预处理与 Agent 编排:先把逻辑讲清楚,再让 AI 真正提速

Intelligent Preprocess Agent Orchestration Cover

这篇文章想复盘的,不只是一次预处理能力升级。

我更想讨论一个更底层的问题:

为什么很多团队一边说“让 AI 提效”,一边又被 AI 拖慢节奏?

我现在的答案很直接:

不是 AI 不够强,而是任务边界和编排逻辑先天就没讲清楚。

这次升级真正解决的,不是“多识别几个脏表”

做数据分析助手时,预处理很容易被理解成一个局部功能。

比如:

  • 识别宽表
  • 删除合计行
  • 处理特殊缺失值
  • 规范列名里的单位

表面看,这些都像小事。

但一旦系统进入 Agent 链路,问题就不再只是“识别到了没有”,而会立刻升级成另一组系统问题:

  • 这件事由谁判断
  • 判断完什么时候生效
  • 是自动应用还是先让用户确认
  • 用户确认前,后续分析到底能不能继续
  • 下一轮分析是否要沿用这次的处理结果

也就是说,预处理一旦进入 Agent 模式,它就不再只是一个数据清洗函数。

它变成了一个有状态的分支节点。

而这次升级,本质上是在把这个分支节点从“能跑”改造成“可解释、可确认、可复用、可回滚”。

这条链路其实经历了三次明显的改版

如果把这次演化过程拆开看,大致可以分成三步。

版本一:规则检测器 + 本地 fix

最早的思路其实很工程化:

  • 写一组规则检测器
  • 识别宽表、表头偏移、合计行、重复列名等问题
  • 命中后调用确定性 fix 函数

这套思路的优点很明显:

  • 可控
  • 可测
  • 低幻觉
  • 出错时容易定位

但它的上限也很明显。

规则检测器擅长识别“格式像什么”,却不擅长判断“在当前任务下该不该处理”。

比如:

  • 宽表一定要转长表吗
  • 列名里的单位应该自动提取,还是只做提示
  • 表头偏移是元数据行,还是业务说明的一部分

这时候,纯规则系统开始显得僵硬。

版本二:规则检测 + LLM 评估 + 试做复核

于是第二版开始让 LLM 参与决策。

流程变成了:

  • 先用规则检测器找候选问题
  • 再让 LLM 评估每个问题是 auto / ask / inform / skip
  • 对部分问题先试做一版
  • 再用一次 LLM 复核试做结果是否可靠

这一版已经比最早期强很多。

它开始具备一种真正有产品意义的能力:

把结构问题翻译成产品动作。

也就是:

  • 哪些问题系统可以直接处理
  • 哪些问题必须打断用户确认
  • 哪些问题只需要提示,不要自动动数据

但这版仍然有两个核心缺点。

第一,链路太绕。

识别一次、评估一次、试做一次、复核一次,成本很高。

第二,状态语义不够干净。

最危险的问题是:

待确认 fix 和已确认 fix 的边界不够硬。

如果 pending 也会被后续分析偷偷吃进去,那用户眼里看到的是“待确认”,系统内部却已经把它当成“已生效”。

这种系统最容易出现“看似可交互,实则状态不可信”的问题。

版本三:单次 LLM 诊断 + JSON Schema 约束 + 确定性执行

这次升级最终落地的,是第三版思路:

  • 不再把 LLM 当成自由发挥的清洗器
  • 让它只负责识别问题、生成受约束参数、决定处理级别
  • 所有输出都必须落在 issue_type + params + decision + confidence 这个结构里
  • 真正动数据的,仍然是确定性的 fix 函数

也就是说:

LLM 负责判断,代码负责执行。

这看上去像一句很朴素的话。

但它其实是整次升级里最关键的边界。

因为只有这样,系统才能同时得到两种好处:

  • 保留 LLM 的语义判断能力
  • 保留工程系统的确定性和可验证性

这次升级真正落下来的技术元素是什么

如果只讲“要把逻辑理清”,这篇文章会显得很空。

真正让这次升级落地的,其实是一组非常具体的技术元素。

我最后把整条链路收敛成了下面这个结构:

User Query

Preprocessing Branch
├─ build_df_context(df)
├─ llm_diagnose_and_evaluate()
├─ schema / 引用 / dry-run 校验
├─ 状态分流:auto / ask / inform
└─ 写入 session state

Confirmed Fixes Only

Analysis Artifact
├─ preprocess_fingerprint
├─ plan_analysis()
└─ execute_analysis()

这套结构里,重要的不是“多了一层”。

而是每一层都只做一件事。

1. 用 Schema 把 LLM 从“自由文本生成器”收敛成“受约束诊断器”

这次预处理升级最核心的技术动作,不是换了哪个 prompt。

而是把模型输出强行压进一套稳定契约:

  • issue_type
  • decision
  • params
  • confidence
  • user_message

再加上 JSON Schema 和列名、行号、dry-run 校验。

这样做的意义是:

模型可以判断,但不能随意发明系统里不存在的动作。

它不再有机会输出一个后端根本接不住的“聪明答案”。

2. 用确定性 fixer 保住系统的可测试性

我没有让 LLM 直接“修改 DataFrame”。

真正执行数据变换的,仍然是硬编码的 fix 函数。

比如:

  • 删合计行
  • 宽表转长表
  • 特殊缺失值归一
  • 转置恢复

这样做的好处非常工程化:

  • 可单测
  • 可回放
  • 可 debug
  • 可解释

这也是为什么我一直强调:

LLM 负责决定“要不要做”,代码负责决定“怎么做”。

3. 用显式状态机替代隐式约定

这次升级里我最重视的,其实不是识别率。

而是状态机。

如果没有显式状态,系统就会开始依赖默认约定。

而默认约定往往是最危险的东西。

现在这条链路里,至少有三类状态是清楚的:

  • confirmed fixes
  • pending confirmations
  • suggested fixes

这三类状态分别对应不同的产品动作和不同的数据流权限。

一旦状态机被写进代码,很多以前看起来“容易说不清”的事,都会自动变清楚。

4. 用 preprocess_fingerprint 保证 planexecute 看的是同一版数据

这是一个非常典型的架构细节。

如果 plan_analysis()execute_analysis() 各自重新读文件、重新做预处理,那么系统理论上随时可能出现:

  • 规划阶段基于版本 A
  • 执行阶段基于版本 B

这种问题不会天天发生。

但一旦发生,用户看到的就是最难排查的“偶发错乱”。

所以我补了共享 artifact 解析路径,并把关键输入折叠成 preprocess_fingerprint

这件事的价值不是性能优化,而是:

把“同一份数据版本”从默认假设变成显式约束。

5. 用兼容归一化把系统从“切换新协议”改成“平滑迁移”

预处理 issue 契约升级以后,一个很现实的问题是:

  • 流式 SSE 已经开始发新字段
  • 历史消息里还存着旧字段

如果不做兼容层,用户刷新一次页面,系统就会像换了一个脑子。

所以这次还有一个不太显眼、但非常关键的技术动作:

把历史 payload 统一归一化成同一套 issue 结构。

很多系统并不是死在新功能做不出来。

而是死在“新旧世界并存时没有过渡层”。

6. 用“真脏表工厂”而不是纯 mock 测试预处理

这次我后来专门补了一层真实场景回归。

它不是只 mock 一些 issue payload。

而是会真实生成:

  • 宽表 + 合计行的 CSV
  • 列名带单位 + 特殊缺失值的 CSV
  • 表头偏移的 XLSX
  • 转置后的 CSV

然后走真实读盘、真实 prepare()、真实 fix 重放。

这层测试的意义很大。

因为预处理最怕的一件事,就是:

你以为自己在测真实表格,其实只是在测一组自己定义的数据结构。

真正难的不是识别问题,而是把状态归属讲清楚

如果只看功能表,预处理升级像是在做“识别能力增强”。

但真正决定系统质量的,反而不是识别本身,而是状态怎么流动。

这次我最后理顺的,是下面这几个边界。

如果再往上抽一层,这其实就是几条很典型的架构原则:

  • 契约先于实现
  • 显式状态优于隐式约定
  • 单一职责优于混合责任
  • confirmed data path 必须和 proposed data path 分离

这些原则听起来很抽象。

但一旦落到预处理链里,它们就会变成非常具体的系统规则。

1. autoaskinform 分别属于什么状态

这三个词如果只存在于 prompt 里,系统很快就会乱。

必须把它们落成真正的状态语义:

  • auto:直接进入 confirmed fixes
  • ask:进入 pending confirmations,阻塞后续分析
  • inform:进入 suggested fixes,不阻塞,只有用户点 apply 才进入 confirmed

这件事一旦讲清楚,前后端动作也自然就清楚了:

  • confirm
  • revert
  • apply

如果这层不先定义,AI 很容易写出“能交互但逻辑互相打架”的实现。

2. 待确认 fix 不能参与正式分析

这是这次升级里最需要强行纠偏的一点。

在旧链路里,pending 有机会被一起重放到数据引擎里。

这意味着:

  • UI 说“请确认”
  • 后端却已经拿它去做正式分析

这是很典型的“提示词语义”和“系统语义”不一致。

所以我把规则改得非常硬:

只有 confirmed fixes 才能进入分析链。

待确认 fix 只存在于卡片和 session state 里,不得偷偷生效。

3. 历史消息和流式消息必须共享同一套 issue 契约

这一点也很容易被忽略。

如果实时 SSE 发的是一套字段:

  • issue_type
  • user_message
  • params
  • confidence

但历史消息里存的还是旧字段:

  • type
  • summary
  • fix_params
  • llm_confidence

那用户刷新一次页面,就会得到两套世界观。

所以这次我做了两件事:

  • 前端切到新字段契约
  • 后端在读历史消息时做兼容归一化

这不是“兼容旧数据”这么简单。

这是在保证:

系统对同一个状态的解释,在时间维度上保持一致。

4. 同一份文件不能在 planexecute 阶段重复做两次预处理

这也是典型的 Agent 编排问题。

如果 plan_analysis()execute_analysis() 各自重新加载文件、重新预处理一次,就会带来两个风险:

  • 重复计算
  • 计划阶段和执行阶段看到的不是同一份数据版本

所以这次我在数据引擎里补了共享 artifact 解析路径。

它会把:

  • 原始文件
  • 当前 preprocess config
  • 当前 query

共同折叠成一个可复用的预处理产物。

再配合 preprocess_fingerprint,就能让 planexecute 至少知道自己看到的是不是同一版数据。

这类设计不是 AI 自己会“顺手想到”的。

它必须先由人把系统不变量讲清楚。

为什么 Agent 编排还得人来做

这次升级之后,我反而更确定了一件事:

Agent 编排最难的部分,不是写代码,而是定义责任。

AI 很擅长做这些事:

  • 按明确枚举补 schema
  • 按确定接口补 API
  • 写 fix 函数
  • 改前端 action 流
  • 扩测试覆盖

但 AI 不擅长替你做这些决定:

  • 哪些状态应该长期保存
  • 哪些状态只是一次性建议
  • 哪些动作会阻塞后续分析
  • 哪些结果必须显式失败
  • 哪些中间态可以缓存,哪些绝不能缓存

换句话说:

AI 擅长实现边界,人的工作是先画出边界。

如果边界没画清楚,就会出现一种很常见的低效:

  • 你让 AI “优化预处理”
  • 它帮你补了一堆代码
  • 但 pending 和 confirmed 继续混着用
  • 前端 action 和后端语义继续错位
  • 测试越写越多,逻辑反而越乱

这时候不是 AI 没有产出。

而是它产出的是一堆建立在错误前提上的高质量代码。

高质量地做错事,往往比低质量地做错事更危险。

为什么含糊不清的任务会拖慢 AI,而不是加速 AI

这是我这次最大的感受。

很多人以为,给 AI 一个宽泛任务,能换来“更大的自主性”。

比如:

  • “把预处理做智能一点”
  • “把 Agent 编排优化一下”
  • “让流程更自然”

这类话听起来像方向,实际上对工程实现几乎没有约束力。

当任务含糊时,AI 往往会发生三件事:

第一,它会自动脑补需求。

第二,它会把局部最优当成系统最优。

第三,它会在没有明确状态机的前提下,把不同语义揉在一起。

最后你得到的,不是“高效自动化”。

而是:

  • 一轮又一轮返工
  • 一层又一层兼容补丁
  • 越来越重的上下文负担

所以真正高效的做法,从来不是一句“你自己发挥”。

而是先把这些问题想清楚:

  1. 系统里有哪些状态
  2. 每个状态由谁产生
  3. 每个状态何时生效
  4. 哪些状态允许跨轮复用
  5. 哪些状态绝对不能默默流入后续分析

当这五个问题清楚以后,AI 的效率会突然变高。

因为它终于不需要替你猜系统设计了。

它只需要帮你实现系统设计。

这也是我现在越来越认同的一句话:

Agent 编排不是提示词工程,首先是系统设计。

提示词只是其中一层。

真正的骨架,是:

  • 状态机
  • 协议
  • 缓存
  • 幂等
  • 回归样本

这些东西不先定下来,AI 越强,返工速度反而越快。

这次升级后,我对“AI 提效”的理解反而更保守了

以前我也会更乐观地想:

只要模型够强、上下文够长、工具够多,系统自然会越来越聪明。

但这次做完以后,我的结论反而更保守,也更实用:

AI 不是替代架构思考,而是放大架构思考。

如果你的逻辑是清楚的,AI 会把效率放大很多。

如果你的逻辑是含糊的,AI 也会把这种含糊放大很多。

所以真正的提效顺序应该是:

  1. 人先把状态、边界、责任讲清楚
  2. 把枚举、schema、动作协议和测试目标定清楚
  3. 再把这些明确任务交给 AI 并行推进

这样 AI 才是加速器。

否则,它更像一个会高速放大歧义的执行器。

写给所有正在用 AI 做工程的人

如果你想让 AI 真正帮你提效,我现在最推荐的不是“学更多 prompt 技巧”。

而是先学会做三件事。

1. 把问题写成状态机,而不是写成愿望

不要说“让体验更顺”。

要说:

  • ask 阻塞
  • inform 不阻塞
  • confirm 进入 confirmed
  • revert 从 pending 移除

只有这样,AI 写出来的东西才会稳。

2. 把能力写成受约束的契约,而不是模糊描述

不要说“让模型返回结构化结果”。

要说:

  • issue_type
  • params
  • decision
  • confidence

再配上 schema、校验器和 deterministic executor。

这时候模型才是在你的系统里工作,而不是在系统外自由发挥。

如果再往技术上说得更硬一点,这一步至少应该包括:

  • 明确枚举
  • 参数 schema
  • 引用校验
  • dry-run 验证
  • 失败回退策略

只有把这些层补齐,模型输出才真正配叫“工程输入”。

3. 先定义回归样本,再谈智能升级

这次我专门补了一组“真脏表工厂”测试,而不是只写 mock。

因为现实世界里的表格问题,从来不是抽象的。

它们往往就是:

  • 宽表加合计行
  • 单位混在列名里
  • Excel 头两行是说明文字
  • 一整张表是转置过来的

如果没有这类真实样本,任何“智能预处理”都很容易只是在 demo 上显得聪明。

更进一步说,真正高价值的测试不是“函数能跑”。

而是:

  • 一份真实脏表进入系统
  • 一组状态在链路里流转
  • 最终只有该生效的 fix 生效

这才是 Agent 系统真正该验证的东西。

结语

这次智能预处理升级,表面上是在重构一个数据清洗分支。

但对我来说,它更像是一次对 Agent 工程方法的重新确认:

真正决定效率的,不是你把多少任务交给 AI,而是你在交给 AI 之前,到底有没有先把逻辑理清。

AI 当然能写很多代码。

但能不能把这些代码变成稳定、可信、可演化的系统,最后仍然取决于:

  • 你有没有先想清楚架构
  • 你有没有先讲清楚边界
  • 你有没有把模糊任务压缩成可验证任务

当这些前提成立时,AI 确实会非常快。

当这些前提不成立时,AI 不会救你。

它只会更快地把混乱实现出来。