Data Agent 的第一版做完后,我想把它重新读一遍。
这个项目是我指挥 Coding Agent 一步步做出来的。需求和取舍由我提出,大量代码由 AI 完成。页面能操作,查询能执行,也留下了测试和试用记录;但要解释一次请求经过哪些模块、某条规则为什么放在那里,仍需要沿着代码核对。
写这个系列,就是为了把这些细节弄清楚。四篇依次讲整体设计、语义与个人积累、查询执行与恢复,最后讨论验证和开发复盘。这一篇用一个问题贯穿全程,把关键记录、调用和失败情况展开。
本文对应合成平台 MVP。表、数据和业务说明均从零构造,查询引擎使用 SQLite;Pi、Rust 服务、MySQL、Milvus 和 Mem0 已有实际接入与本地验证,真实模型试用使用 DeepSeek Flash。真实数据平台、正式身份认证和生产容量仍待验证。下面的业务约定只属于这套演示资料。
一句“查净收入”,先确定五件事
假设用户问:
查一下 2026 年 1 月各渠道的净收入,金额用元。
生成 SQL 前,要把这句话翻译成可计算的条件。至少有五件事需要依据:
| 问题 | 样例里的约定 | 理解错了会怎样 |
|---|---|---|
| 一行代表什么 | 一个订单商品行,主键为 (order_id, line_id) | 把行数当成订单数,多商品订单会重复计数 |
| 用哪个时间 | 净收入按当前行首笔成功付款的 paid_at 归属 | 用 order_date 会把下单月当成收入月 |
| 怎样处理退款 | 累计付款减累计退款;退款扣回原付款期间 | 只筛选 1 月退款,会漏掉 2 月发生、应扣回 1 月的退款 |
| 金额是什么单位 | 付款、退款已分摊到行,单位为整数分 | 再乘商品数量会重复计算;不换算会把分当元 |
| 统计哪些数据 | 默认排除 is_test=1;使用当前快照 | 测试交易会混入结果,快照之后的事件则根本不在数据里 |
这些答案来自表结构、加工 SQL 和业务说明。字段名只能提供线索。例如 order_status='refunded' 表示整单全退,它不能代表“这行出现过退款”。某行可能全退,整单却仍是部分退款。
加工 SQL 也决定了金额为什么可以直接相加。raw_payments 中一行是一笔付款或退款事件,同一订单商品行可以有多笔事件。加工先筛选截止快照之前的成功事件,按 (order_id, line_id) 汇总,再连接订单行。如果查询时又把事件明细连接回来,然后累加已经汇总的行金额,就会把同一笔收入算多遍。
还有一个容易藏在“1 月”后面的条件:快照截止 2026 年 2 月 4 日 00:00:00 UTC,不含截止时刻。1 月付款、2 月初退款,在这份快照中会减少 1 月净收入。因此本次问题的完整含义是“截至这份快照、归属于 1 月付款的净收入”。若要查 1 月实际发生的现金流入流出,就要改按付款事件时间计算。
这份约定可以写成下面的教学 SQL。它用于解释口径,不是某次模型回答的逐字摘录:
SELECT
channel,
SUM(paid_amount_cents - refunded_amount_cents) / 100.0
AS net_revenue_yuan
FROM demo_order_detail
WHERE is_test = 0
AND paid_at >= '2026-01-01T00:00:00Z'
AND paid_at < '2026-02-01T00:00:00Z'
GROUP BY channel
ORDER BY channel;
在仓库的合成数据上执行,得到:
| 渠道 | 净收入(元) |
|---|---|
| app | 16 |
| store | 8 |
| web | 100 |
左闭右开的区间避免漏掉月底最后一秒之后的小数时间,也避免把 2 月 1 日算入 1 月。/ 100.0 保留元的小数表达。公式没有再次限制退款月份,因为退款累计值已经反映快照之前的事件。
这些金额可以独立计算,模型却还要回答“为什么这样算”。业务说明还明确列出了未模拟的税费、运费、货币兑换和跨月分次付款等边界。字段叫 paid_amount_cents,不等于系统已经知道税费是否包含在内。这个缺口应跟着解释保留下来。
业务语义怎样从资料变成可用记录
系统有三个日常入口:工作台负责提问和结果,语义管理负责公共表、字段、指标与文档,“我的积累”保存个人偏好和分析方法。净收入的定义属于共享语义,不能只留在某一次聊天里。
先冻结本次分析能看到的材料
平台适配器提供表结构、原始注释、加工 SQL 和血缘。预填任务从目标对象出发,收集上游来源,再补充相关业务文档。当前装配器有明确上限:沿血缘最多八层、最多收集 32 份来源,再取至多 32 份相关文档;材料正文合计不超过 24,576 字节。
这些限制控制一次模型调用的体积。分配正文额度时,短材料先释放不用的份额,其余材料分摊剩余额度,避免一份长 SQL 把后面的业务文档完全挤掉。截断沿 UTF-8 字符边界进行,并记录 body_limit、原长度和已纳入长度。上游不可读、来源不完整、血缘超过范围也分别写入缺口。模型拿到的材料因此有明确的“读到了多少”。
准备完成后,任务保存来源 ID、版本、正文与覆盖情况。模型运行期间有人改了业务文档,旧分析不能悄悄以新文档的名义落库。提交前还要核对来源和目标版本;版本不再匹配,本次输出就不能成为当前建议。材料装配器可以看到这些边界。
分析和依据复核各调用一次
字段预填使用固定两阶段:先分析,再对分析结果做依据复核。第一阶段产出建议、缺口和引用;通过结构、目标覆盖和引用检查后,才进入第二次模型调用。最终结果仍要经过程序检查,再保存为建议。
这条流程没有自主规划循环。它的任务范围已知,就是给指定对象预填语义。聊天中的 Pi 则需要根据问题选择下一步调查动作。两者共用模型预算约束,但调度方式不同。
validated 只表示输出符合结构和引用规则。例如引文确实来自某个固定版本,目标条目没有遗漏。它不能证明模型从那句原文推出的业务含义正确。引用了真实加工 SQL,也可能把一段中间计算误说成最终表允许出现的值;后面的质量测量就要检查这种错误。

根据当前源码绘制。两次模型调用之间和正式保存前都有检查;图中的“通过”表示相应程序检查通过。 查看原图
原始事实、建议和人工值分别保留
以 paid_amount_cents 的含义为例,一条语义记录中会保留几层内容:
| 字段 | 保存内容 | 重新分析时的处理 |
|---|---|---|
source_facts | 来源 ID、版本、位置及原始依据 | 更新为本次通过检查的来源事实 |
suggestion | 模型建议、缺口、分析状态及依据 | 保存新建议供核对 |
human_override | 人工修订及修改信息 | 保留已有人工修改 |
effective_value | 当前读取时采用的值 | 有人工覆盖时继续使用人工值;否则采用新建议 |
保存实现中的关键分支很短:
entry["source_facts"] = new["source_facts"].clone();
entry["suggestion"] = new["suggestion"].clone();
if entry["human_override"].is_null() {
entry["effective_value"] = new["effective_value"].clone();
entry["review_state"] = json!("unverified");
} else {
entry["review_state"] = json!("needs_review");
}
假设维护者已经补充“按成功付款累计,金额为分;税费范围未知”,重新预填时模型又给了不同解释,人工有效值继续保留,新建议留下供复核。人工标记也不等于业务已验证,它说明的是内容来源。
清除人工覆盖是显式操作。清除后才回到建议或来源基础值。正式对象、新版本和索引待办在同一个数据库事务内保存,避免正文更新了却没有留下索引任务。
跨用户纠错还有另一层区分:普通用户提出的纠错先是个人草稿,明确提交后负责人才能看到。负责人“接受”只改变建议处理状态;核对、编辑并保存后,正式语义才产生新版本。这里的语义编辑权属于 Data Agent,数据平台的读取和执行权限仍独立检查。
检索命中以后,为什么还要回 MySQL
对净收入问题,Agent 通常需要找到指标定义、订单行表、金额字段和业务说明。当前检索把名称与关键词匹配、向量候选结合使用。
词法通道对名称命中给更高权重,也会检查正文;中文查询使用二元词等分词结果。精确对象 ID 或精确名称优先。其余候选按两通道名次融合,当前分值可简写为:
score = 1 / (20 + lexical_rank) + 1 / (20 + vector_rank)
名次从零开始,某通道没命中时该项为零。使用名次而非直接相加向量距离和关键词分,避免把不同尺度的数值混在一起。当返回上限至少为六时,选取过程还会尽量保留表、字段、指标、文档、关联、术语的类型覆盖,防止结果全被一类对象占满。具体排序没有使用另一个模型判断相关性。
正文向量化按 360 个字符切窗口,步长 280,重叠部分有助于保留跨边界的语句。索引片段标识包含对象、版本、条目、位置和模型版本。文档编辑时的业务章节与检索窗口是两种边界,不能把一个窗口当作完整章节。
Milvus 返回的仍然只是候选。服务端根据候选 ID 回 MySQL 核对空间、启用状态、正式版本和来源依赖,随后从正式正文重建命中摘录。位置和内容指纹不匹配时,不能直接把向量库中的旧文字交出去。
例如“净收入”文档从 v7 改成 v8,旧向量暂时还没更新。v7 即使排在第一,也不能借这个排名继续充当当前口径。停用文档同样如此:停止使用要在正式读取时成立,不能等待后台删完向量才生效。

复用项目已有的 GPT Image 概念插画。两个索引集合负责找候选,MySQL 保存正式记录。
交给模型的第一次结果以短目录和摘录为主。工具会给出 content_page.complete 和 next_offset;不完整时,模型可用 read_knowledge 按同一版本继续读取。来源原文另用 read_source 补读。摘要短不是问题,把没读完的材料说成全文才是问题。
当前候选检索有数量上限,会返回 bounded、candidate_limit 等覆盖提示。合成规模检查可以验证候选处理和版本过滤,尚不能证明真实千表环境中的召回率。检索漏掉了关键文档,后面的模型再认真也可能建立在不完整资料上。
Pi 怎样调查,Rust 怎样接住工具调用
先看这次问题由哪些进程负责:
| 部分 | 对这次请求负责什么 |
|---|---|
| React / TypeScript | 接收输入,展示文字、SQL、状态和结果,保存未发送草稿等界面状态 |
| Rust API 与 Worker | 保存会话、任务和查询,核对权限、版本、执行依据与预算,跟踪平台状态 |
| Node.js / Pi SDK | 理解问题、调查、澄清、选择工具、生成回答,维持会话和原生上下文压缩 |
| Python / Mem0 | 提取与检索个人记忆候选,不决定共享指标定义 |
| 平台适配器 | 提供目录资料、SQL 检查、执行状态和结果;当前目标是合成 SQLite 平台 |

架构图来自仓库当前实现。已有真实模型试用主要使用 Flash,工作台也支持选择 Pro。
Rust API 和 Worker 复用同一个业务库。HTTP 路由、Pi 工具请求与后台作业进入各自的具名用例,再由用例组合业务模块。模块保存自己的状态,用例确定跨模块事务边界。例如执行查询既要读消息、锁分析任务,又要确认 SQL,不能让三个入口各写一套稍有区别的规则。
Node.js 的会话入口调用 Pi 的 createAgentSession,传入模型、思考等级、会话管理器、受控资源和工具,再调用 session.prompt。取消使用 session.abort,上下文压缩使用 Pi 原生能力。应用没有再建一个规划器决定“第一步必须检索、第二步必须生成 SQL”。
以当前工具说明为例,模型可以:
| 工具 | 用途及限制 |
|---|---|
search_knowledge | 查共享语义、本人记忆和当前已选 Skill 的目录;命中内容作为资料 |
read_knowledge / read_source | 按版本分页读取正式正文或来源,检查是否已读完整 |
update_analysis_task | 登记消息属于哪个任务,保存条件、修订或澄清问题 |
validate_sql | 检查 SQL 与目标能力,不执行 |
request_query | 保存完整 SQL、参数、目标、口径和依据,供用户查看 |
execute_query | 根据当前用户的明确执行意图申请执行,接受宿主校验 |
get_query | 读取已有查询状态和结果,不创建或重跑查询 |
对“退款率”,模型可能先读定义,发现有订单退款率和退款金额比例,再保存澄清问题并询问分母;用户回答后继续同一个任务。对定义已经明确的净收入,则可以调查后直接生成草稿。选择哪个工具由模型判断,工具能造成什么状态变化由程序限制。
一次工具调用经过 Node.js 时,先按 packages/contracts 中的同源 Schema 校验参数,然后把 SDK 调用 ID、工具名称、参数、会话检查点和宿主绑定的运行身份送给 Rust。Rust 先登记稳定操作身份,再执行业务用例。模型无法靠自己填一个 user_id 就切换归属,也不能在普通工具里获得任意文件读写或数据库执行能力。
工具有确定回执以后,结果回到 Pi,模型可以继续调查或结束回答。流式文字则通过 message_update 中的 text_delta 单独提交给宿主,前端能先展示过程。最终交付还会检查最近一条助手消息的停止原因:输出被截断、模型报错或已取消,都不能当作成功回答。
最后,Rust 核对已保存的文字片段与最终全文一致,把最终回答、检查点、消息消费状态和租约释放放进一次事务。由此可以区分“界面已经显示了一部分字”和“这一轮已经正式完成”。相关边界分别在 Pi 会话入口和 最终交付用例。
跨进程和跨语言会增加运维与排错成本。这里换来的是职责边界:模型保留调查空间,正式业务状态有明确的写入者。Schema 能检查形状、程序能检查版本,但“净收入是否理解正确”依然要看内容证据。
从 SQL 草稿到执行,绑定的到底是什么
第一条问题处理完,用户看到的是 SQL、参数和口径说明。接下来两句话会产生不同动作:
只看 app。
只看 app,再执行。
前一句修订条件,后一句还要求执行。Pi 负责理解这层差别。Rust 不再做一遍自然语言分类,而是检查这次执行请求的依据和目标是否成立。
条件版本和 SQL 版本各自变化
对话、分析任务、查询和 Agent 运行是不同记录。对话容纳长期交流;任务保存一次分析的目标、条件与修订;查询保存某份具体 SQL;运行表示 Agent 此刻处理一条输入的执行过程。
条件由“全渠道”改成“app”时,任务产生新的 condition_version,旧待执行稿失效。模型修订 SQL 后,保存新的查询及 draft_version。如果条件不变,只纠正 JOIN 写法,也要明确指出被替换的旧查询,防止用户仍能执行那份已知有问题的稿子。
这里采用了一个比较保守的顺序:先让被纠正的旧稿失效,再做新 SQL 的外部检查。若新 SQL 检查失败,界面应提示修订没有完成,旧稿也不会因此恢复执行权。否则用户明明已经指出错误,系统却可能因为新稿失败而继续允许执行旧稿。
可把一次修订理解成下面的示意记录;其中编号用于讲解:
| 时刻 | 任务条件 | 查询草稿 | 能否确认执行 |
|---|---|---|---|
| 首次展示 | v1:1 月、全渠道 | qA / 草稿 v1 | 等待用户明确要求 |
| 用户改为 app | v2:1 月、app | qA 已失效 | 不能执行旧稿 |
| 新 SQL 检查完成 | v2:1 月、app | qB / 草稿 v2 | 可根据当前有效授权执行 |
全文引述把授权连回真实消息
execute_query 携带 query_id、draft_version、condition_version 和 instruction_quote。最后一个字段必须是当前用户完整原话,不能只摘其中的“执行”两个字。
执行入口检查消息仍可处理、引述与完整正文匹配,并排除系统查询结果通知。随后检查消息已归属正确任务,查询属于当前对话和任务,上下文依赖仍然有效。确认时继续核对 SQL 草稿版本、任务条件版本、检查状态和取消状态。
全文匹配解决的是出处与截断问题。“先别执行,只解释一下”不能被裁成“执行”当作授权依据。但它不能证明模型理解一定正确:模型如果误读了完整原话,单靠字符串匹配仍然不够。当前行为还依赖工具说明、模型判断和相应负例测试,需要如实保留这个边界。

根据聊天执行入口绘制。业务文档、工具结果和后台完成通知都不能作为新的用户执行授权。 查看原图
首次确认时,查询授权、消息已应用标记和预算归属原子提交。重复请求拿到原查询,不会重新执行,也不会把已有操作转到一份新预算上。用户如果明确要求“重新跑一次”,就需要创建新的查询记录,保留两次执行的区别。
SQL 的只读性和平台权限还要单独检查。当前 SQLite 适配器实际使用只读连接、EXPLAIN 与 authorizer 等机制,不能只看语句是不是以 SELECT 开头。未来接入真实平台,需重新实现其方言、身份与执行协议;本地 SQLite 的通过不能证明真实 Datasight 权限已经正确接入。

复用已有 GPT Image 流程插画。用户可以返回修改条件,已提交查询继续按自己的任务和版本跟踪。
查询在后台运行,晚到的结果怎样不串题
用户要求执行后,查询先进入队列,再由 Worker 提交给平台。平台可能返回运行中、成功、失败,也可能因超时暂时无法判断提交是否成立。
这时用户仍可以继续输入。假设 1 月净收入查询 A 还在运行,用户又提出 2 月问题 B。系统需要同时保存两件事:当前正在讨论 B;A 仍有一条未完成的平台查询。
同一 Pi 会话的推进串行化,避免两条模型运行同时写会话树。平台查询独立运行,所以等待 A 不必占住整个对话。A 完成后,系统按原查询、原任务、原条件版本发出结果唤醒,再让 Pi 解释结果。后台通知不算新的用户要求,也不能授予另一条 SQL 执行权。
最终交付还会核对任务版本。旧版本的结果可以作为那次查询的历史结果展示,但不能把已经修订的任务推进成“当前问题已回答”。预算也沿用原执行归属,不能因为结果回来晚了,就借 B 的新消息继续支付 A 的模型调用。

根据当前实现绘制。查询 A、B 是讲解用编号;同一 Pi 会话串行推进,平台查询可独立运行。 查看原图
超时以后,先查证有没有做成
查询提交超时有两种可能:平台没收到请求,或者平台已经执行,只是回执没回来。把它们都当失败再提交一次,会造成重复执行。
因此状态里有 submission_unknown。Worker 根据稳定查询身份先查证平台状态,保留未知,或在确认条件满足时有限重试。取消也分开保存 cancel_state 与 execution_state:“已发出停止请求”只说明本地提出了取消,不说明平台已经终止。
Agent 工具具有类似问题。Rust 把 SDK 调用 ID 映射到稳定 operation_id。进程恢复后,新 run_id 证明当前运行的执行资格,原副作用仍使用原操作身份。同一个操作、同一份参数可接回原回执;同 ID 配不同参数则冲突。未知操作不能通过换 ID 变成一笔看似全新的动作。
会话日志要回到正确的叶子
Pi 的完整会话树、工具历史和压缩记录保存在原生 JSONL;MySQL 保存该会话文件、session ID 与精确 leaf 的引用。恢复时打开对应日志,再切回已经提交的 leaf,不能简单拿“文件最后一行”当作已提交状态。
一个具体的崩溃窗口是:Pi 已把新条目写进日志,Rust 还没提交最终回答。磁盘上有条目,不代表业务事务已经承认这一轮完成。保存精确 leaf,才能把 SDK 历史与业务提交位置对齐。日志恢复实现直接使用 Pi 的 session 和 branch 接口。
压缩摘要也属于需要检查的历史。系统记录已经交给模型的知识、来源、记忆和 Skill 的版本依赖,按同一会话累积;不会因为最近一页没出现某条资料就删除依赖。因为那条资料可能仍在压缩摘要里。
停用资产、撤权或改版后,新输入可以重建上下文;对于同一输入的恢复,如果旧上下文已经失效,则拒绝继续,避免一边忘掉旧工具记录、一边把副作用重做。这里保证的是失效内容不再进入后续模型调用,不意味着物理删除全部历史日志。
每次实际模型调用还有预算预留和结算。未知用量继续占用预留,不按零费用处理;会话恢复、任务拆分或结果唤醒也不能重置额度。这些记录使故障处理有账可查,但恢复依然依赖 MySQL 和 Pi 日志共同存在,只备份向量库远远不够。
查询结果、个人记忆和 Skill 怎样留下来
查询成功后,工作台可以预览、复制最终 SQL、下载 CSV,并继续追问。当前表格预览前 10 行,合成平台最多保存 1,000 行,超过保存上限会提示截断。下载读取该查询已经保存的结果,不会隐式重新跑 SQL。

另一条订单明细查询的实际截图,用来展示结果界面;它不是前面按渠道汇总的三行结果。
假如用户接着说“以后金额默认用元”,这条偏好可以跨会话保留。“这次用元”则仍是当前要求。共享指标继续以分定义,展示偏好不改公共口径,用户当次明确指定的条件优先。
当前 Mem0 从完整原始消息中提取候选。候选先处于 pending,不能马上进入正常召回。Rust 核验并把正式正文、用户、空间、版本和状态存入 MySQL 后,再推进个人记忆索引到 ready。如果数据库保存失败,向量库里一个相似度很高的 pending 候选也不能假装已经保存成功。
人工编辑记忆正文时,索引采用 infer=False,不再让模型重新改写人工内容。检索先按用户、空间和提交状态过滤,再核对当前映射与版本,最后仍回 Rust 的正式记录。正文对了而适用范围被扩写,也会导致误用,所以正式 body 和 scope 使用同一份最终提取文字;名称只作显示标签。
另一类积累是分析方法。例如“核对付款期间与退款归属,检查一对多关联,再生成净收入 SQL”,可以保存为 Skill。Skill 需要用户明确选用,Pi 才能按需读取对应正文、说明和文本模板。个人 Skill 可直接使用,也可以明确发布为当前空间的独立公共副本;公共副本以后独立维护,不带走私人聊天、记忆或来源备注。

复用项目已有 GPT Image 概念插画。个人方法无需先公开才能使用。
存储分工随这些边界确定:
| 记录 | 保存位置 | 恢复或读取时需要什么 |
|---|---|---|
| 共享语义、个人资产、任务、SQL、预算 | MySQL | 当前状态、版本、归属和依赖 |
| 共享语义与个人记忆的向量 | Milvus 的独立集合 | 候选回源;索引可由正式内容重建 |
| Pi 会话树、工具历史、压缩记录 | Pi 原生 JSONL | 与 MySQL 中 session/leaf 引用对应 |
| Mem0 历史与技术回执 | 本地 SQLite | SDK 操作连续性;正式个人资产仍在 MySQL |
| 业务数据和查询结果 | 平台;当前为合成 SQLite | 平台查证状态,按结果引用读取 |
“记住用户偏好用元”和“记住那次查询已提交但回执未知”都需要持久化,保存规则却完全不同。前者可以修改和停用,后者关系到是否会重复执行。把它们分开,后续查错时才能找到负责这条事实的记录。
跑通以后,还要检查回答对不对
2026 年 10 月 12 日,为这篇文章在当前代码上重新运行了固定场景。结果需要先说清楚:正式测量在第三个场景中止,没有得到新的 16 题整体准确率。
本次怎样测
先通过全 16 场景的协议检查,再用真实 DeepSeek Flash/high 跑 S01 问答、P01 字段预填两个小样。小样使用独立账本,最多 24 次调用、US$0.50;正式轮另有最多 200 次调用、US$5 的固定账本。小样不并入正式得分,不用后来的成功覆盖原来的失败。
这里使用原业务验收入口:真实 Flash、可执行合成 SQLite、词法检索和内置个人记忆路径。它没有接入当前完整栈中的 Mem0、Qwen,也仍按原题面使用显式确认接口。因此它可以与历史同题结果对照,不能据此声称完整工作台所有路径都测过。
| 检查 | 本次结果 | 能说明什么 |
|---|---|---|
| 16 场景协议检查 | 全部通过,0 次真实模型调用 | 固定策略可以走通受控工具和宿主链路 |
| 真实小样 S01、P01 | 两题机器检查通过,7 次调用 | 接线、预填采集和账本可用;内容仍有错误 |
| 正式轮 S01、S02 | 机器检查通过 | S01 内容复核发现遗漏;S02 的 SQL 与解释通过复核 |
| 正式轮 S03 | 时间条件断言失败 | 原稿与 app 修订稿已保存,完整场景未完成 |
| 正式轮其余 13 题 | 未运行 | 不能计为通过,也不能据此计算内容准确率 |
为什么 SQL 算对了,场景仍然失败
S03 的题目是先查 1 月净收入,再补充“只看 app,其他条件保持”。模型给出了两份 SQL,也让旧稿失效、新稿等待确认,但持久条件写成了:
任务条件:2026-01-01 至 2026-01-31,timezone=UTC
SQL 参数:[2026-01-01T00:00:00Z, 2026-02-01T00:00:00Z)
原验收要求任务条件保存完整 UTC 时间戳,开始时间必须是 2026-01-01T00:00:00Z。实际为 2026-01-01,断言失败后 runner 停止了后续场景。
中止后,只读提取两份已保存 SQL,用独立 SQLite 核算器验证:全渠道为 12400 分,app 为 1600 分,两份 SQL 各五个数据变体也通过。这说明不能把本次失败简单写成“净收入算错”。问题出在任务条件与 SQL 参数采用了不同的日期表达,尚未满足原验收契约;条件结束写 1 月 31 日,SQL 的右开边界却是 2 月 1 日。
后续应核对条件字段的语义,决定怎样统一表达。本轮没有因为模型这样输出就放宽断言,也没有继续执行 S03 后面的旧版确认拒绝、新查询确认和结果解释。保存状态显示旧稿失效,并不能替这些未执行步骤算通过。
机器通过仍不等于解释正确
正式轮 S01 对成功付款、快照筛选和补零解释正确,却把“已付款的整单里能否有付款为零的商品行”说成资料未说明。加工 SQL 已分别按行补零、按整单累计金额决定状态,可以推导出这种组合在结构上允许;需要查数的是样例中是否实际存在。内容复核因此没有把 S01 算作完整通过。S02 的整月已知支付客户数为 5,SQL、匿名客户边界和七个独立数据变体则通过复核。
小样 P01 的再次预填在解释金额字段时写了:
与
order_id、line_id一起构成主键(order_id, line_id)
DDL 的主键只有订单号和行号,金额列不在其中。结构检查、引用检查和人工保护都通过了,这句话却仍然有错。两阶段预填完成后,最终文字仍要逐条核对。
历史测量也出现过类似差距。2026 年 10 月 5 日固定 16 场景的流程全部通过,内容复核为 12/16:把测试标记误当成零金额成因、把已经运行的查询说成待确认、遗漏税费与运费未知范围等。原报告继续保留,75% 仍只属于那一次历史测量。
本次小样加正式轮共 27 次真实调用,全部结算、未知预留为零,按配置价格估算 US$0.208708;这不是服务商账单。正式产物与当前 233 份输入的指纹一致,原始输出、失败日志和独立核算分别保留。模型、提示和验收要求没有为获得更好分数而修改。
本文的三行金额只是教学 SQL 的独立结果;模型回答、语义预填、聊天授权和页面操作各有自己的验证范围。独立答案文件不进入模型知识、索引或 Skill,未完成的测量也不能在写文章时变成一个看起来完整的分数。
第二篇会继续展开语义编辑、个人记忆提取和 Skill 采用:分别检查正式内容怎样生效,适用范围如何保留,以及改版后旧资料怎样退出下一轮上下文。四篇的阅读顺序放在系列目录。
本文实现依据固定在 Data Agent 的 f482a51 提交。架构图与产品截图来自仓库;封面、查询流程、知识召回、Skill 复用插画由 GPT Image 在此前生成,本次复用。三张实现细节图依据源码用 SVG 绘制并导出 PNG。示意编号和示例状态不冒充真实运行日志,图片中的业务资料均为合成内容。