跳转至

追问取图依赖已发布报告,失败侧无法恢复图片

2026 年 9 月 15 日生产排查发现:一路候选成功发布了报告,用户接着发了一句普通文字追问,另一路候选报"找不到图片"。事发时产品在双候选对照期间,系统同时实验两个模型配置,同一张照片由两路各自观察、各自出解读。

定位

排查从生产只读核对开始:只查生产 PostgreSQL、API 持久日志和 runtime 容器日志,不动代码、配置和数据库。先固定现场版本——生产内核是 0.6.4,服务器上的取图与队列源文件 SHA256 和本地一致,读本地源码就是在读线上行为。环境原因同步排除:API、runtime、路由、数据库健康,容器近 7 天没有 OOM;磁盘余量不足是另一个独立的容量风险,解释不了这个报错。

然后对照三组记录:两个失败请求的追问内容、两侧的发布状态、资源读取记录。两个失败请求的追问都是纯文本,没有携带资源;失败一侧的会话里,已发布报告数为零。

先排除了上传丢失:成功那一路能从会话的受控资源里恢复图片,说明图还在,读取能力也正常。上传没有问题,差异就只可能落在两侧的取图路径上。

两条路径的机制不一样。失败那一路的追问取图,查询锚在"本会话已发布报告里的观察图片"上:

SELECT DISTINCT r.resource_id, r.kind, r.name, r.mime_type, r.size_bytes, r.scope
FROM incense_published_readings p
JOIN agent_resources r ON r.resource_id = p.observation_resource_id
    AND r.user_id = p.user_id AND r.session_id = p.session_id
WHERE p.user_id = $1 AND p.session_id = $2 AND lower(r.scope) = 'session'

代码要求这个查询恰好返回一行:失败侧从来没发布过报告,结果是零行,整个追问直接报错;同一会话里两张不同图片会返回两行,同样报错。这条链路默认"能继续追问的会话必然已有成功报告"。这个查询是 9 月 5 日为修"对照分流后追问丢图"加上的,把图锚在已发布报告上是为了复用它的受控资源和已有观察;单候选生产里这个前提一直成立,双候选对照里一路可能失败,前提就不成立了。用户这次只发了文字,也绕不开这条路径:恢复逻辑有意放在对照分流之后,不带资源的追问靠它恢复上下文,不会因此触发新一轮对照。

另一路能继续,是因为它的候选会话在请求准备阶段已经从原会话的受控资源复制了图片,请求自带资源,直接跳过这个查询。日志也能对上:近 7 天比较降级日志里,follow-up image no rows 这个错误出现过 2 次。

修复与验证

修复把恢复路径改为直接查当前用户与会话的受控资源:

SELECT resource_id, kind, name, mime_type, size_bytes, scope
FROM agent_resources
WHERE user_id = $1 AND session_id = $2 AND lower(scope) = 'session'
ORDER BY updated_at DESC, created_at DESC, resource_id ASC

不再要求先发布报告。同时守三条边界:存在多张图片时不擅自取第一张,恢复要有明确的资源对应关系,只有明确单图时才自动复用观察;不把恢复扩大到任意链接或跨用户资源;两路候选的观察结果按候选、模型与连接语义隔离,不能把一侧的观察结果拷给另一侧用。

验证分几层。观察隔离用真实识图调用核对,确认调用落在正确一侧、数据库重载后仍保持隔离。本地真实首轮与无资源引用的追问分别约 20 秒和约 4 秒通过。最终发布后在公网验证了三项:无资源字段的追问约 12 秒完成;两个历史失败会话的继续处理各约 4 秒完成;运行时日志确认调用走的是预期模型,整轮重试为零,线上追问复用了已观察结果,没有新增识图调用。

最终上线的是调整后的单模型路径,旧的双候选结构没有随之做完整回归;历史双连接环境下的故障注入脚本在新链路下不再生效,需要另行调整。