【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 篇重试安全,共同体现「任何会被重复递送的输入,都必须有去重与幂等保护」。
相关推荐
枫叶丹49 小时前
从聊天到委派:AI Agent 如何推进长期任务
人工智能·chatgpt·agent·codex
梦想的颜色1 天前
UniApp 请求层终极封装:统一拦截、错误处理、多端兼容,结合 Claude Code 高效落地
服务器·uniapp·uni.request·codex·claudecode·ai 辅助开发·uniapp 请求封装
todoitbo1 天前
把发票台账接进 Codex:KingbaseES MCP 的一次只读风险排查实践
数据库·oracle·codex·kingbasees·mcp
VIP_CQCRE1 天前
用 Ace Data Cloud 接入 Codex:让 AI 编程工具配置更简单
openai·ai编程·开发工具·codex·acedatacloud
梦想的颜色3 天前
2026 硬核横评:DeepSeek‑V4‑Flash VS OpenAI Codex (GPT‑5.6‑Codex) 代码模型全方位对比
gpt·codex·deepseek·ai 代码大模型·大模型 api 选型
黑科技软件3 天前
Windows 11 下 Codex 桌面端卡顿解决方案:自动绑定大核心,优化 CPU 调度教程
codex·codex卡顿·codex windows很卡
梦想的颜色4 天前
Codex 精准 高并发点赞系统终极解决方案|前端防抖幂等 + Redis 抗并发 + 异步落库
codex·接口幂等·前端防抖·高并发点赞·redis 点赞架构·ai 生成业务·后端性能优化
机建狂魔4 天前
Codex 接入第三方模型 API 实战:以 Mimo 为例
java·服务器·数据库·ai·ai编程·codex
175063319454 天前
Codex 的 Linux bubblewrap 沙箱无法创建 UID 用户命名空间
codex