【Bug已解决】Assistant reprocesses previous prompt. 解决方案

【Bug已解决】Assistant reprocesses previous prompt. 解决方案

原始报错线索:Assistant reprocesses previous prompt.(助手把上一条 prompt 又处理了一遍,用户明明只发了一次,却得到两次响应)。


一、现象长什么样

用户发一条消息,期待一个回复,结果:

  1. 助手回了两次相同或几乎相同的内容;
  2. 或某些「动作」被执行了两次(发两封邮件、建两个任务);
  3. 用户确认只点了一次发送;
  4. 往往发生在「网络重发 / UI 重绘 / 事件冒泡」场景下------同一条输入被系统从不同路径递送了两次。 根因是输入消费没有幂等保护,且上一次处理的事件/状态残留,被当成了「新输入」再次处理

二、背景:为什么一条输入会被处理两次

输入从「用户敲击」到「被处理」,往往经过多层:

复制代码
UI 事件 → 网络传输 → 队列 → 处理器

任何一层若「重复递送」且下游不防重,就会双处理:

  • UI 层:按钮连点、事件冒泡;
  • 网络层:超时重试把同一条请求发两次;
  • 队列层:消费确认失败,消息被重新投递;
  • 状态层:上一次处理的「最后一条输入」没清,重启后又被读出来当新输入。

三、为什么重复处理:根因

3.1 输入无唯一 ID / 指纹

系统无法区分「这是同一条重发的」还是「这是新的」,于是都处理。

3.2 消费不幂等

处理器对同一条输入执行了副作用(发消息、建任务),重复执行就有重复副作用。

3.3 状态残留

「上一条 prompt」存在某处状态里,被误当成新输入再次喂给处理器。

3.4 重试无去重

网络重试把同一条带相同内容的请求发两次,下游照单全收。

四、最小可运行复现(无去重双处理)

下面演示「同内容输入被处理两次」:

python 复制代码
def process_prompt_bad(prompt, state):
    # 错误:直接处理,不检查是否重复
    state["last_response"] = f"回复: {prompt}"
    state["responses"].append(state["last_response"])
    return state["last_response"]
if __name__ == "__main__":
    state = {"responses": []}
    process_prompt_bad("你好", state)
    process_prompt_bad("你好", state)    # 同内容又来一次 -> 双处理
    print("响应数:", len(state["responses"]))   # 2,重复了

无去重,相同内容被处理两次(根因 3.1/3.2)。

五、解决方案一:输入指纹去重

给每条输入算指纹(内容哈希 + 可选序号),已处理过的直接跳过:

python 复制代码
import hashlib
class DeduplicatingProcessor:
    def __init__(self):
        self.seen = set()
    def fingerprint(self, prompt, user_id):
        # 内容 + 用户 + 时间窗(可选)构成指纹
        raw = f"{user_id}:{prompt}"
        return hashlib.sha256(raw.encode()).hexdigest()[:16]
    def process(self, prompt, user_id, handler):
        fp = self.fingerprint(prompt, user_id)
        if fp in self.seen:
            return None, "duplicate"     # 去重,不重复处理
        self.seen.add(fp)
        return handler(prompt), "ok"
if __name__ == "__main__":
    p = DeduplicatingProcessor()
    h = lambda x: f"回复:{x}"
    print(p.process("你好", "u1", h))   # ('回复:你好', 'ok')
    print(p.process("你好", "u1", h))   # (None, 'duplicate') 拦截

指纹去重,同内容二次到达被拦(解决 3.1,呼应第 110/120 篇幂等)。

六、解决方案二:消费幂等(带唯一请求 ID)

从输入端就带「请求 ID」,下游用 ID 保证「同一 ID 只产生一次副作用」:

python 复制代码
class IdempotentHandler:
    def __init__(self):
        self.processed_ids = set()
    def handle(self, request_id, prompt, side_effect):
        if request_id in self.processed_ids:
            return "already_done"        # 幂等:不重复副作用
        # 执行副作用(发消息/建任务)
        result = side_effect(prompt)
        self.processed_ids.add(request_id)
        return result
def send_message(text):
    return f"已发送: {text}"
if __name__ == "__main__":
    h = IdempotentHandler()
    print(h.handle("req-001", "你好", send_message))   # 已发送
    print(h.handle("req-001", "你好", send_message))   # already_done(重试安全)
    print(h.handle("req-002", "你好", send_message))   # 已发送(不同 ID)

请求 ID 幂等,网络重试(同 ID)不重复副作用。

七、解决方案三:状态不残留上一条输入

「上一条 prompt」不该成为「下次自动处理的输入源」。处理完即清,或明确区分「历史」与「待处理队列」:

python 复制代码
class InputQueue:
    def __init__(self):
        self.pending = []        # 仅待处理
        self.history = []        # 已处理历史,不参与再处理
    def enqueue(self, prompt):
        self.pending.append(prompt)
    def consume_one(self, handler):
        if not self.pending:
            return None
        prompt = self.pending.pop(0)     # 取出即移除,不残留
        result = handler(prompt)
        self.history.append(prompt)      # 进历史,但历史不会被再消费
        return result
if __name__ == "__main__":
    q = InputQueue()
    q.enqueue("你好")
    print(q.consume_one(lambda x: f"回复:{x}"))   # 回复:你好
    print("待处理剩余:", q.pending)                # [] 不残留
    print(q.consume_one(lambda x: x))            # None(不会把历史当新输入)

待处理队列「取出即移除」,历史与待处理分离,杜绝残留重处理。

八、跨层防重策略

  • UI 层:按钮防连点(debounce / disable until response);
  • 网络层:重试带相同请求 ID,下游幂等;
  • 队列层:消费确认(ack)后才删消息,失败重投但下游幂等;
  • 处理器层:指纹去重 + ID 幂等(第五、六节);
  • 可观测:记录「重复拦截次数」。

九、排查清单

「助手重复处理上一条 prompt」,按下面排查:

  1. 输入有无指纹/请求 ID?能否区分重复(第五、六节);
  2. 消费是否幂等?重复执行是否有重复副作用(第六节);
  3. 上一条输入是否残留?待处理与历史是否分离(第七节);
  4. UI 是否防连点
  5. 网络重试是否带相同 ID
  6. 队列是否确认消费(ack 机制);
  7. 日志是否记重复拦截数
  8. 是否只在特定场景(重发/重绘)出现?定位递送层。

十、小结

「助手重复处理上一条 prompt」的根因是输入无去重、消费不幂等、且上一条输入状态残留被再处理。通用修复:

  1. 指纹去重:内容哈希构成指纹,已处理直接跳过(第五节,呼应第 110/120 篇);
  2. 请求 ID 幂等:带唯一 ID,同 ID 只产生一次副作用,重试安全;
  3. 状态不残留:待处理队列「取出即移除」,历史与待处理分离;
  4. 跨层防重 :UI 防连点 + 网络带 ID + 队列 ack + 处理器幂等(第八节)。 一句话:一条输入只要「带唯一身份 + 下游幂等消费 + 处理完即从待办移除」,就绝不会被处理两次。把去重和幂等做成输入链路的标配,助手就不会再把你的「你好」回答两遍------这与第 110 篇幂等记账、第 120 篇幂等恢复、第 129 篇重试安全,共同体现「任何会被重复递送的输入,都必须有去重与幂等保护」。
相关推荐
Mr数据杨2 小时前
【Codex】用学生生活日常模块记录校园生活过程
django·生活·codex·项目开发
Mr数据杨2 小时前
【Codex】用角色管理模块控制不同岗位的数据与操作权限
django·codex·项目开发
Mr数据杨3 小时前
【Codex】用用户管理模块维护后台账号与登录权限
django·codex·项目开发
Mr数据杨5 小时前
【Codex】用学生支持周报模块跟踪个性化支持进展
android·java·javascript·django·codex·项目开发
零号栈帧5 小时前
Codex 手机控制台——AgentDeck
windows·cloudflare·codex·pwa·ai agent
Mr数据杨6 小时前
【Codex】用登录日志模块审计用户登录与访问行为
django·codex·项目开发
孤狼GPT9 小时前
Codex额度快用完时,要不要换Luna?什么任务适合降模型,什么任务绝对别降?
chatgpt·codex·luna·chatgpt plus·chatgpt pro·codex额度
程序员徐公1 天前
从 0 到 1,Codex 重置雷达小程序我是怎么实现的
codex·codex 重置雷达·codex 重置雷达小程序
深念Y2 天前
约束工程:如何让 AI 没法跑偏
人工智能·ai·软件工程·codex·opencode·ccsiwtch