首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >日报每天7:30自动进邮箱:4段链路里第3段最容易崩|PRIVO AI智能操作助手 三合一·网页+桌面+IDE

日报每天7:30自动进邮箱:4段链路里第3段最容易崩|PRIVO AI智能操作助手 三合一·网页+桌面+IDE

原创
作者头像
用户12722577
发布于 2026-11-06 16:57:35
发布于 2026-11-06 16:57:35
1170
举报

07:30,日报邮件准时进了邮箱。正文第一行写着「昨日成交 0 单」,附件表格里是 1843 行明细。两个数字来自同一条链路,相差 1843。这不是模型算错,是上游 SQL 的时间窗口比下游晚了一天——链路跑通了,但没人校验过它跑对了。

这是我把日报做成定时任务后第一个月的真实事故。后来我把它拆成四段,每段单独设检查点,才敢让它无人值守。下面写下四段的失败模式、幂等策略和几个必须避开的坑。

四段链路,崩溃点集中在第三段

段

职责

常见失败

幂等策略

实测耗时

1 采集

拉业务库增量 + 第三方 API

上游延迟写入,拉到半天的数据

按业务日期分区覆盖写

40–90 s

2 清洗

去重、时区归一、字段对齐

时区混用导致跨天错位

落地后做行数与金额双校验

10–20 s

3 生成

模型把结构化数据写成叙述

数字与表格不一致(幻觉)

抽取正文数字回表比对,不一致则回填

25–60 s

4 投递

邮件 + 群机器人

超时、重复推送

以 (日期, 版本号) 做幂等键

5–15 s

第三段是最脆的一环,因为它把「可校验的表格」变成了「不可直接校验的自然语言」。前两段出错会报异常,第四段出错用户会投诉,只有第三段出错是静默的:数字看着合理,其实是编的。

采集:窗口边界比调度频率重要

日报最常见的错误是把「今天」当成「昨天」。我第一次写窗口时用了 created_at >= CURDATE(),跑出来永远是 0 单,因为凌晨跑任务时当天还没有数据。改成按业务日期取前一日,并显式声明时区:

代码语言:sql
复制
-- 增量窗口:按业务日期取前一日,显式绑定时区,避免容器 UTC 与业务 UTC+8 错位
SELECT
    DATE(CONVERT_TZ(o.created_at, '+00:00', '+08:00')) AS biz_date,
    COUNT(*)                        AS order_cnt,
    SUM(o.pay_amount) / 100.0       AS amount_yuan
FROM orders o
WHERE o.created_at >= CONVERT_TZ(:biz_date, '+08:00', '+00:00')
  AND o.created_at <  CONVERT_TZ(DATE_ADD(:biz_date, INTERVAL 1 DAY), '+08:00', '+00:00')
  AND o.status IN ('PAID', 'SHIPPED', 'DONE')
GROUP BY biz_date;

关键在 CONVERT_TZ 的两个方向:入库是 UTC,统计是 UTC+8,写反了就会前后各偏 8 小时,表现为「每天固定少 8 小时的订单」。这个 bug 我盯了两天才发现,因为它只在 00:00–08:00 的订单上体现。

采集段还要处理「补单」——支付成功但回调延迟落库的订单,会晚几小时才出现。我的做法是每天重跑前 7 天的窗口,覆盖写入而不是追加,这样补单自然被吸收,代价是每天多跑 7 次查询(约 15 秒,可接受)。

生成:让模型写叙述,但不让它写数字

把 1843 行明细直接丢给模型是不可行的:token 成本高,且模型对长表格的聚合几乎必错。我的做法是先用 SQL 算出 12 个指标(单量、GMV、客单价、退款率、Top5 品类、环比),再把这张 12 行的小表交给模型,只让它写解释性段落。

即便如此,仍有数字漂移。我统计了 60 次生成结果:正文里出现数字 41 个,与源表不一致的有 6 个,错误率约 15%,集中在「百分比取整」和「环比方向」上——它会把 +3.2% 写成「下降 3.2%」。

所以第三段必须加一道回填校验:正则抽出正文里所有数字,逐个到指标字典里查,查不到或对不上就用模板句替换。

代码语言:python
复制
import re

NUM = re.compile(r"-?\d+(?:\.\d+)?%?")

def verify_and_fill(text, metrics):
    """正文数字必须能在指标字典里找到,否则该句降级为模板句。"""
    bad = []
    for m in NUM.finditer(text):
        token = m.group()
        if token.rstrip("%") not in metrics["tokens"]:
            bad.append(token)
    if bad:
        return metrics["fallback_text"], bad   # 交回模板,记录告警
    return text, []

这段代码很土,但它把「静默出错」变成了「显式降级」。跑了两周,命中 4 次,其中 3 次是模型把 1843 写成 1834——肉眼几乎发现不了。

投递:幂等键是最后一道闸

第四段看似简单,实际最容易造成信任崩塌:重复推送三次日报,用户就不再看了。我用 (biz_date, version) 做幂等键,同一版本只允许成功投递一次;重试时 version 递增,正文里会带「第 2 版」标记,读者能看出这是修正。

这里踩过一个坑:邮件发送超时不等于没发出去。第一次重试导致同一封日报发了两遍,因为 SMTP 在 30 秒后超时,但服务端其实已经接收。后来改成发送前先查投递日志表,超时后延迟 90 秒再查一次状态,仍未确认才重发。

我的失误

最大的失误不是技术,是范围。第一版我让它连「异常归因」一起写——为什么单量掉了 20%。模型给出的原因看起来头头是道(天气、竞品活动、投放衰减),但没有一条有数据支撑,等于把猜测包装成了结论。现在我把它砍掉了,日报只描述事实和变化,归因留给人在会上判断。

这套链路现在跑在 PRIVO 的定时任务里,四段各自独立成一次执行,失败只重跑单段,不会把整条链拖垮;生成段的回填校验也是我后来补进去的,最初那版直接让模型自由发挥,才有了开头那个 0 单的日报。

如果只能记一条经验:把不可校验的环节夹在两个可校验的环节之间,并且给每个环节一个能对上的数字。链路能跑通不算什么,跑通之后数字能对上,才算能用。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 四段链路,崩溃点集中在第三段
  • 采集:窗口边界比调度频率重要
  • 生成:让模型写叙述,但不让它写数字
  • 投递:幂等键是最后一道闸
  • 我的失误
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档