0、null、未采集:AI Agent 反馈系统最容易踩的语义坑

做 Agent 反馈系统时,一个诱惑是把所有缺失指标补成 0:SQL 好聚合,图表不留空,前端也少写判断。但这个"方便"会同时破坏基线、增量和优化结论。
工程上应保留三个状态:真实零值、字段未知、没有采集证据。它们看起来都"没有数",业务含义却完全不同。
工程场景与决策冲突
数据团队希望 Schema 稳定,产品团队希望仪表盘完整,采集端却面对平台字段缺失、登录状态和解析漂移。正确取舍不是强行补数,而是让数据模型显式承载不确定性。
方案拆解与关键权衡

| 状态 | 含义 | 能否参与增量 |
|---|---|---|
0 |
已采集,真实值为零 | 可以 |
null |
已采集,但字段不可得或不可靠 | 不可以 |
| 未采集 | 没有本次观察证据 | 不存在该快照 |
把 null 保留下来会增加查询判断,却换来一个重要保证:系统不会把证据缺失解释成表现差。
实现链路与最小示例
快照模型
python
@dataclass(frozen=True)
class Snapshot:
article_id: str
revision: int
captured_at: datetime
views: int | None
likes: int | None
collects: int | None
comments: int | None
采集运行结果单独记录 completed/failed/skipped。只有 completed 才生成快照;失败不能生成一条全零数据冒充成功。
基线和增量

第一次快照只建立 baseline。后续比较时,空值必须传播:
python
def compare(now, before):
if now is None or before is None:
return None
if now < before:
raise MetricRecalculated
return now - before
不要用 int(value or 0),因为它同时吞掉合法的 0 和缺失的 None。
报告层
报告必须输出不同句子:
views_delta == 0:当前窗口没有新增阅读;views_delta is None:当前窗口没有可比较的阅读增量;- 没有第二个快照:首次采集,只建立基线。
同样,所有互动字段都是 None 时,只能说"互动不可比较",不能说"没有互动"。
证据、限制与自动化边界
真实实现还需要三道绑定:公开页标题与清单一致、同一内容 ID 不改绑其他文章、revision 不回退。否则即使数值类型正确,也可能把别人的数据接到当前序列。
平台不提供展现时,应保留 impressions=None,不能用阅读量代替。累计指标出现下降时,先记录异常并停止解释,避免把平台重算当成负增长。
RuyiBookCourse 对生产 Agent KPI 的要求是先定义多维指标并建立基线;项目测试则证明了更细的工程语义:首次快照不计算增量、收藏未知时增量保持空、全部互动未知时报告不得声称获得互动。
可复用检查清单
- 数字字段允许
None,但禁止负数; - 采集运行状态与指标快照分开;
- 失败或跳过不生成全零快照;
- 首次快照只建基线;
- 空值逐字段传播到增量;
- 累计指标下降触发异常;
- 文章 ID、标题、内容 ID、revision 绑定;
- 不用阅读冒充展现;
- 文案区分"真实为零"和"无法比较"。
收束
一个可信的反馈系统,不是每个单元格都有数字,而是每个数字都能说明它来自哪次真实观察。让 null 保持 null,是数据工程对决策最基本的诚实。
发布前门禁
- 工程冲突和取舍明确
- 示例与真实测试一致
- 没有虚构平台指标
- 本轮未认领实验