智能问数智能体方案.md 24 KB

智能问数 · 智能体化方案(harness + loop)

目标:移除经营分析页右下角问数机器人弹窗,新增独立「智能问数」页面;问数能力从 「单发 NL2Query」升级为类 Codex / WorkBuddy 的数据智能体 —— 带 harness(编排契约)

  • loop(工具循环)架构,专注数据处理与分析:交叉比对、同比环比验证、数据体检、 同行对标。基于现有「本地数据湖 + 钉钉 AI 表格」双层存储寻最优解,参考 ai-fde 的副驾(copilot-delegation)与 agent runtime 模式。

一、审计结论

1.1 现状链路

层 位置 现状
前端 public/reports.js:1292-1470 + public/index.html:768+ 右下角 FAB 弹窗,仅 reports/analysis 视图显示;localStorage 按主体存 20 条;ECharts 渲染 +「应用到报表」跳转数据中台
路由 server/routes/reports.js:22 POST /api/reports/ask 单发同步接口
问数 nl-query.js(334 行) 两段式:①语义层 prompt → 模型输出查询 JSON → sanitizeQuery 钳制 → 本地湖执行;②Top≤10 行 digest 回传模型写一句结论,失败降级模板句
语义层 napos-service.js REPORT_COLUMNS 19 列注册表:key/label/同义词/口径说明/预留列标记
数据湖 reports-store.js JSON 按月分片,行键 date\|scope\|chainId\|shopId;scope=company/chain/shop/summary;全字段落库(每行 raw 含环比差值 _diff、同行均值/排名 _mall_avg(_rank)、上期对比 _last_period,约 60+ 字段)
云端 server/services/aitable.js 钉钉 AI 表格三表(门店明细/连锁汇总/公司汇总),pushLake/pullLake 水位同步
模型 server/context.js makeChatCfg OpenAI 兼容 /v1/chat/completions,deepseek-v4.1-flash(默认)/ glm-5.3-flash,stream:false,文本内嵌 JSON(extractJson),未用原生 function calling

1.2 现状局限(升级动因)

  1. 单发无循环:一次规划定死查询,答不了「先看整体再下钻到异常店再看异常日」的复合分析;追问只靠 history 里拼接的文字摘要。
  2. 无验证层:结论句直接采信一次查询结果。shop 行全零而 chain 行正常的已知 bug(挂起中)就是例证 —— 没有任何环节做层间一致性核对;比率指标若被简单算术平均(而非加权)也不会被发现。
  3. 无工具抽象:取数逻辑写死在 nl-query 里,湖里最值钱的数据(raw 的 _diff/_mall_avg/_last_period、syncs 同步日志、bench 同行对标、跨主体 crossData)全都触达不到。
  4. 无过程可见性:用户只能看到最终一句话 + 图,模型选了什么区间/门店/指标,只有「应用到报表」后反向可见;出了错无法定位。
  5. 无护栏:无轮次/调用/时长预算,无循环侦测,无取消。
  6. 无审计:对话只在浏览器 localStorage,服务端零留痕,换电脑即失。

1.3 必须保住的既有资产(方案全部复用)

  • summarizeRows 的加权口径:进店率按曝光加权、下单率按进店加权 —— 这是防「算术平均比率」错误的关键,agent 一切聚合必须走它,不允许模型自己算。
  • sanitizeQuery 的钳制哲学:日期钳在湖内区间(T-1 结算)、指标白名单、limit 上限。
  • 语义层注册表 REPORT_COLUMNS + 同义词表。
  • 全字段落库的 raw 字段:环比/上期/同行排名的源侧真值,验证层直接比对,不用重算。
  • /api/reports/bench(同行对标)、ensureCrossData(跨主体对比)、report-html.js(HTML 周报)、aggregateReport/queryReport。
  • syncState/syncSnapshot 的任务状态机模式、syncs.json 的留痕模式 —— agent 运行态照此办理。
  • 零依赖纪律:package.json 仅图像编解码两个依赖,LLM 调用手写 HTTP。智能体全程延续零依赖。

1.4 从 ai-fde 借的模式(映射)

ai-fde 本方案
copilot-delegation.ts 双模式:direct(点名工具直调,成本 ~1/20)vs agent(完整 harness) 「快问」保留 nl-query 单发;「深度分析」走 loop。双模式并存,同一页面切换
runtime.ts 主循环 + 循环侦测(回注重复提醒而非硬杀) loop.js 同款:相同 tool+args 重复 → 注入提醒 → 再犯才强制收敛
harness-modes(auto/plan/research/…,模式定温度/预算/确认闸门) 收敛为两模式:快问(低成本)/ 深度分析(loop),温度 0.2
tool-registry 清单求交防幻觉工具名 + tool-guard 风险级 tools/registry.js 同款:模型输出工具名先与清单求交,再过参数 sanitize
harness-trace 审计事件(tool.completed outcome / agent.error,成败都留痕)+ turn-assessment 零 token 规则打分 trace.js 事件流 + 落盘;「数字溯源 post-check」沿用零 token 规则哲学
委派永不抛出、失败折叠进结构化结果 + EventLog run 状态机失败折叠为 {status:'failed', error},前端可读

二、目标与非目标

目标

  1. 独立「智能问数」页(经营分析分组第 4 项),会话式、过程可见、可取消、可回放。
  2. harness + loop 智能体:规划 → 调工具取数 → 校验 → 结论,多步复合分析。
  3. 验证层为一等公民:同比环比验证、层间交叉比对、恒等式校验、数据体检、同行对标,产出结构化 verdict 卡。
  4. 内置 tools + skills 双层:tools 是可执行取数/校验动作;skills 是注入 prompt 的分析打法(playbook)。
  5. 双层存储用满:本地湖为计算层(快、全字段),钉钉 AI 表格为档案层(跨机共享)—— 增加湖↔云比对工具。

非目标

  • 不引入任何 npm 依赖、不换框架、不上向量库/SQL。
  • 不做通用代码执行沙箱(模型永不直接算数,聚合只走本地确定性函数)。
  • 不动生成/采集/任务中心等既有模块。
  • 本期不做流式打字机(沿用轮询;见 §9 开放问题)。

三、总体架构

┌─ 前端 public/ask.js + index.html(view=ask) ─────────────────────────┐
│ 会话列表 │ 对话流:用户消息 / 工具步骤折叠条 / verdict 卡 / 图表表格卡 │
│          │ 底部 composer:模式(快问/深度) · 发送 · 停止              │
└──────────────┬──────────────────────────────────────────────────────┘
               │ POST /api/agent/runs → {runId};GET /api/agent/runs/:id 轮询
┌─ server/agent ─▼────────────────────────────────────────────────────┐
│ index.js   运行注册表(syncState 模式:running/steps/cancel)        │
│ loop.js    while 循环:prompt→解析→护栏→执行→观察→(提醒/收敛)        │
│ llm.js     chatCompletion(自 nl-query 抽出共用)+ JSON 解析与修复   │
│ guard.js   工具清单求交、参数 sanitize、预算(轮次/调用/时长)、循环侦测│
│ trace.js   事件流 + runs 落盘 + 数字溯源 post-check(零 token 规则)  │
│ tools/     registry + lake_overview/query_report/compare_periods/   │
│            make_chart/cross_check/data_quality/peer_benchmark/…     │
│ skills/    周期对比 · 数据体检 · 同行对标 · 下钻归因(prompt playbook)│
└──────────────┬──────────────────────────────────────────────────────┘
               │
    ┌──────────▼──────────┐   ┌──────────────────────────────┐
    │ 本地数据湖 reports-  │   │ 钉钉 AI 表格 aitable.js       │
    │ store(计算缓存层,  │   │ (在线主档案/共享层,三表)    │
    │ raw 全字段落库)     │   │  P3: aitable_verify 湖↔云比对 │
    └─────────────────────┘   └──────────────────────────────┘

四、Harness + Loop 设计

4.1 回合协议(文本 JSON,非原生 function calling)

沿用 extractJson 已被验证的路子(两个 flash 模型、temperature 0 下稳定),结构化为单一动作对象:

{"thought": "先看整体趋势,再定位异常门店",
 "action": {"tool": "query_report", "args": {"mode": "date", "from": "20261001", "to": "20261007", "metrics": ["grossAmt","validOrderCnt"]}}}

结束回合:

{"thought": "数据齐了,二店环比下滑主因是曝光下降",
 "final": {"answer": "markdown 文本",
           "chart": {"type": "line", "x": "date", "y": ["grossAmt"]},
           "applied": {…同现有 applied 形状,供「应用到报表」…},
           "verdicts": ["v1", "v3"], "sources": ["t1", "t2", "t4"]}}
  • 解析失败:带错误信息回注重模型修复一次,再失败即终止该轮(折叠为可读失败)。
  • 工具名与注册表清单求交,未注册名 → 回注「工具不存在,可用清单:…」修正一次(ai-fde 防幻觉同款)。

4.2 主循环(loop.js)

准备:系统提示(角色 + 语义层 + 工具清单 + skills 打法 + 输出契约 + 今天/湖区间)
     + 会话历史(压缩后)+ skills 注入
while (true):
  1 调 LLM(快问直连路径除外)
  2 解析动作 → guard 校验(清单求交 / sanitize / 预算)
  3 执行工具 → 观察(rows 裁剪 Top≤20 + summary;全量留在 artifacts)
  4 观察 appended;循环侦测:同 tool+规范化args 连续重复 → 注入提醒;第三次强制 finish
  5 final 或触发预算上限(maxTurns/maxToolCalls/超时)→ 强制 finish 或折叠失败
收尾:trace 落盘、数字溯源 post-check、状态机置 done/failed/cancelled

4.3 护栏与预算(guard.js,常量起步零配置)

  • maxTurns=10、maxToolCalls=16、单次 LLM 45s、单 run 墙钟 120s(照抄 nl-query 超时量级)。
  • 循环侦测用「提醒注入」,不硬杀(ai-fde runtime 注释里的 Codex/Claude Code 同款思路)。
  • 取消:run 状态机带 cancelRequested,工具间隙检查(照抄 requestSyncCancel)。
  • 工具只读:本期全部工具均为只读(湖查询/校验/图表规格),无写操作 —— 不需要 ai-fde 的风险级审批链,guard 简化为参数钳制。

4.4 双模式(ai-fde direct/agent 同构)

  • 快问:直接走现有 nl-query.ask(2 次 LLM 调用),回答快、成本 1/3。适合「昨天营业额多少」「上周 Top5」。
  • 深度分析:走 loop(3~10 次调用)。适合「环比为什么跌」「哪些店数据有问题」「对标同行谁掉队了」。
  • P1 前端显式 toggle,默认深度分析;P3 加零成本规则启发自动分流(问题长度 + 指标词命中 + 是否含「为什么/对比/变化」类词)。

4.5 运行态与留痕(trace.js)

  • 运行注册表:server/agent/index.js 持有 Map<runId, state>,state 形状仿 syncSnapshot(status/steps/error/startedAt/finishedAt)。
  • 接口:
    • POST /api/agent/runs(body: entity, question, mode, sessionId)→ {runId},立即返回
    • GET /api/agent/runs?entity= 会话与历史 run 列表
    • GET /api/agent/runs/:id → 进度 + 事件数组(前端 1s 轮询,模式与任务中心一致)
    • POST /api/agent/runs/:id/cancel
    • GET /api/agent/sessions?entity=
  • 落盘:data/agent/<entityKey>/runs/<runId>.json(事件数组:llm.call/tool.completed{outcome,durationMs}/verdict/final),sessions.json 索引 cap 30。成败都留痕(ai-fde EventLog 哲学:失败无留痕时问题不会有任何信号)。
  • 历史压缩(compaction):新回合只携带旧回合的「final 结论 + 关键数字 + 用过的区间/门店」,旧观察全部丢弃 —— 防上下文随轮次膨胀(ai-fde compaction 的简化版)。

五、工具集设计(tools/)

工具 职责 复用/说明
lake_overview 主体/门店清单、湖内日期范围、各月行数、最近同步时间与成败 入口必调:先知道有什么数据再问;多机场景提示「先云端拉取」
query_report 明细/按店/按日查询 + 排序 + TopN + 区间合计 内核 = queryReport/aggregateReport/summarizeRows(加权口径);参数走 sanitizeQuery 同源钳制
compare_periods 环比/同比:本期 vs 基期(等长平移,支持任意偏移) 门店集合对齐(开/闭店列出)、缺失日对齐说明、比率加权 delta
cross_check 层间一致性:每日 sum(shop)≈chain 行、sum(chain)≈company 行;恒等式:收入+支出=营业额、营业额/订单≈客单价;日期连续性 容差可配;直接命中「shop 全零」这类已踩的坑(见 §8)
data_quality 体检:全零行、缺失日、预留列 null、syncs 最近失败/告警 同步步骤里已有 nonZero===0 → warn 的先例,升级为工具
raw_inspect 取某行 raw 的指定字段(_diff/_last_period/_mall_avg/_rank) 源侧真值交叉验证:本地算的环比 vs 取数接口自带的环比差值
peer_benchmark 同行对标:门店×天 自身值/同行均值/排名,跨期排名变化 内聚 /api/reports/bench 逻辑为服务函数供两端复用
make_chart 独立工具 P1 实施调整:图表规格随 final 输出、由 guard.clampChart 按语义层白名单钳制;图表数据取自本运行最后一次查询产物(rows/curByDate/curByShop),模型不传数据只传规格 复用 sanitizeQuery 的 chart 钳制思路
aitable_verify(P3) 湖↔云:行数比对 + 按行键抽样值 diff 只读拉取(记录接口无限流),复用 aitable.js pullLake 的读路径
cross_entity(P3) 跨主体(跨校)本期/基期对比 内聚 ensureCrossData 逻辑

工具出参统一带 digest(给模型的压缩观察)与 data(给前端的完整产物)两路。

六、技能集设计(skills/,prompt playbook,非工具)

skill 打法(注入的步骤约束) 阶段
周期对比分析 overview → 本期+基期 query → compare_periods → cross_check 抽查 → 图表 → 结论必答「变化量/变化率/主因店」 P2
数据体检 data_quality 全量 + cross_check 全量 → 出体检报告(分级问题清单 + 指引:如全零行建议跑 scripts/diagnose-napos.js) P2
同行对标 peer_benchmark → 排名恶化店下钻逐日 → raw_inspect 看同行均值是否整体下移 P3
下钻归因 TopN → 锁定异常店逐日 → 定位拐点日 → 区分自身问题 vs 大盘问题(_diff/_mall_avg) P3

skills 触发:用户点页面快捷 chip 或模型在规划回合自选(最终回答的 final.verdicts/sources 契约不变)。


七、验证层(本方案的差异化核心)

7.1 verdict 体系

每个校验产出结构化判定:

{"id":"v1","kind":"identity|cross-layer|freshness|period|peer",
 "target":"2026-10 全部门店","expect":"sum(shop)=chain 行","actual":"差 0.4%",
 "status":"pass|warn|fail","note":"…"}

前端渲染为 verdict 卡(绿/黄/红),结论文本必须引用 verdict id —— 「数据可不可信」和「结论是什么」并排呈现。

7.2 数字溯源 post-check(零 token,仿 turn-assessment 哲学)

final 返回后,本地规则校验:从 answer 抽取数值(金额/单量/百分比),逐个在 sources 对应工具观察的数值集合中做容差匹配;匹配不到的标注 ⚠ 未溯源 展示(不阻断,但可见)。防编造的底线,纯字符串/数值匹配,零额外模型成本。

7.3 同比环比验证方法论(compare_periods 内置,模型无权偏离)

  1. 基期 = 等长平移(环比)或上年同期(同比,湖内无数据时如实说明)。
  2. 门店集合对齐:两期都在线的店才算可比集,开/闭店单列。
  3. 比率指标一律加权(曝光/进店为权),禁止算术平均 —— 与 summarizeRows 同源实现。
  4. 缺失日显式列出(复用趋势图表 dataQualityNote 的空缺标注逻辑)。
  5. 交叉验证:本地计算的 delta 与该行 raw._diff(源侧环比差值)比对,超容差 → warn。

八、与已知数据问题的关系

「shop 行全零、chain 行正常」的挂起 bug 是本方案的活教材:cross_check 的层间一致性会把这类问题在每次问数时自动暴露为 fail verdict,而不是等人工发现数字不对。配套两点:

  • 数据体检 skill 的报告对已知问题给出处置指引(诊断脚本 scripts/diagnose-napos.js)。
  • 体检 fail 时,涉及该数据的结论自动加「数据质量受影响」角标,避免用户把平台 bug 误读为经营事实。

九、前端页面(public/ask.js + index.html view=ask)—— 已按 FDE 副驾式布局实施

  • 导航:经营分析分组第 4 项「智能问数」(data-view="ask"),页面全高(100vh)。
  • 布局(2026-10-17 用户以 ai-fde「FDE 副驾」截图定版):
    • 左会话栏:品牌头(图标+标题+「+」新建)、会话搜索框(按标题过滤)、会话列表(标题 + 最近一问预览 + 悬停删除);
    • 右舞台:顶部「新对话/会话标题」+ 主体徽章 + 一行能力说明;空态为居中大标题「给问数一个目标」+ 说明 + 推荐 chips;
    • 底部大输入卡:textarea(回车发送 / Shift+Enter 换行 / 自动伸缩),控制行含主体下拉、深度分析/快问 seg、停止、圆形渐变发送钮。
  • 对话流元素:用户消息右气泡;工具步骤折叠条(工具名/参数摘要/耗时/结果行数,可展开看观察与原始参数);verdict 卡(P2);图表卡(ECharts,复用现有配色与格式化);「应用到报表」按钮(复用 window.shanReportsApplyQuery 跳数据中台明细 tab)。
  • 样式遵循既有约定:配色/字体沿用应用设计令牌(暖色 + 橙 accent),组件复用 seg-control/btn/panel 体系;help 小字克制(输入卡 placeholder 一行 + 空态说明一句)。
  • 移除:index.html 机器人 markup、reports.js 的 chat 区块(state/render/bindings 约 170 行)、app.js 视图可见性里的机器人逻辑、style.css 的 .chatbot-*。旧 localStorage 对话不迁移(价值低,新页服务端留痕)。

十、分阶段落地

P1 骨架(先移除、立骨架、跑通闭环)—— 2026-10-17 已完成,smoke 86/86

  1. 移除机器人(§9 清单),/api/reports/ask 与 nl-query.js 保留(快问路径继续用);reports.js 以 window.shanReportsApplyQuery 承接「应用到报表」。
  2. server/agent/:llm.js(chatCompletion 自 nl-query 抽出共用)、loop.js、guard.js、trace.js、index.js + 4 个核心工具(lake_overview/query_report/compare_periods/data_quality);测试隔离环境变量 GENPIC_LAKE_DIR(reports-store)与 GENPIC_AGENT_DIR(trace)。
  3. 路由(§4.5 五条,挂 server/routes/agent.js);前端 public/ask.js + viewAsk 页面 + nav「智能问数」。
  4. smoke:NAPOS_AGENT_MOCK=1 确定性假模型在隔离湖上端到端跑通 loop(四工具序列/环比数字/体检判定/用量/会话索引),加解析/钳制/循环侦测纯函数断言。

P2 验证层 —— 2026-10-17 全部完成

  • ✅ 工具齐装(9 个):lake_overview / query_report / compare_periods / data_quality / cross_check / raw_inspect / peer_benchmark / cross_entity(跨主体对比)/ aitable_verify(湖↔云只读比对,aitable 服务新增 snapshotForVerify,未启用时优雅降级为 warn verdict);
  • ✅ 内置技能:skills.js 四大技能(周期对比分析/数据体检/同行对标/下钻归因),打法常驻 prompt,composer「技能」下拉点名(run.skill),GET /api/agent/skills;
  • ✅ 编排时间线 UI:运行头「时间 + 状态·总耗时·N 个工具·自动分流·技能」+ 逐步骤(思考·Turn N 带耗时与 in/out tokens;工具带耗时/载荷字节/行数);
  • ✅ verdict 体系:校验类工具产出结构化判定(quality/identity/cross-layer/freshness/coverage/cloud),loop 归档到 run.verdicts(v1..vn),前端 verdict 卡(绿/黄/红左边条);
  • ✅ 数字溯源 post-check:provenance.js 零 token 规则(数值抽取剔除日期/TopN 噪声,容差匹配含 %/万/亿换算),未溯源数字前端黄条警示不阻断。

P3 —— 2026-10-17 完成

  • ✅ 快问自动分流:mode=auto(前端默认档),服务端规则(>18 字或含分析类词 → 深度分析,否则快问),run.autoPicked 标记并在时间线头展示「自动→快问/深度分析」;
  • ✅ aitable_verify、cross_entity(见上);
  • ✅ HTML 分析报告:build_report 工具(复用 report-html.js 即时生成,零重复实现),final 携带 reportUrl,前端「查看分析报告」新窗口打开可打印导出 PDF;快问模式同样附带当期报告链接;
  • ✅ 会话管理:新建/搜索/删除/轮询续显(切页运行不中断,回来自动续接轮询)。

自检记录(2026-10-17,用户要求)

  • ✅ 修复:问数页运行中切换会话/新建会话会让 composer 卡在「停止」态(busy 未复位);
  • ✅ 修复:切页回来运行中的运行不会恢复轮询(现 openSession 检测 running 运行并续接);
  • ✅ 修复:agent 路由重复实例化 aitable 服务(server.js 已有 ctx.aitable,改为复用);
  • ✅ 修复:reports.js 残留死变量 lastAskApply;
  • ✅ 确认:cloud 行键映射 scope 已转回本地枚举,湖↔云比对逻辑成立;无 console.log/debugger 残留;无 ask-thought 等死样式引用。

预估:P1 约 6~8 个新文件 + 3 处移除;P2/P3 均为增量工具/技能注册,不动骨架。


十一、测试与回归

  • 纯函数单测进 smoke:动作解析(含围栏/脏输出)、guard 钳制、compare_periods(fixture 行构造:比率加权、闭店对齐)、cross_check(合成全零/恒等式破坏 fixture 必须出 fail)、溯源 post-check。
  • mock 模型端到端:NAPOS_AGENT_MOCK 固化工具调用序列 → 断言 run 状态机、事件留痕、取消、预算强制收敛。
  • 红线不变:smoke 绝不发真实 LLM 请求、不触碰 DELETE/启动类端点(沿用现有约定)。

十二、风险与开放问题

风险 对策
flash 模型多轮 JSON 输出不稳 temperature 0.2、单一 JSON 动作契约、失败回注修复一次、maxTurns 兜底、mock 锁行为进 smoke
loop 成本高于单发(3~10 次调用) 快问双模式并存 + 预算护栏 + 观察 digest 压缩(控制输入 token)
钉钉侧限流 aitable_verify 只读、抽样比对,不批量拉全表
多机湖不同步导致答旧数据 lake_overview 展示最近同步时间,提示先「云端拉取」
每次问数都暴露既有全零 bug 引发困惑 体检报告给处置指引 + 结论加数据质量角标(§8)
开放:是否要流式(SSE)体验 P1 用轮询(与任务中心一致、smoke 友好);SSE 可后置为纯增强,接口形状不变
开放:会话是否上云(钉钉)共享 本期仅本地落盘;跨机共享待 aitable 场景成熟后再议