Atoms平台是如何实现多模型请求幂等控制的呢?核心做法是由服务端生成SHA-256请求指纹(user_id+session_id+标准化prompt+排序后的model_list)来完成唯一识别。同时,平台结合Redis原子锁与Promise缓存的协同去重机制,在结果聚合阶段再按照预设优先级选择首个成功响应,从而有效避免重复请求带来的资源浪费。

在Atoms平台同时调用多个大模型(如GPT-4o、Claude-3.5、Qwen-Max)时,如果用户频繁重试、前端自动重新调度,或网关触发二次转发,就很容易让同一语义请求并发分发到多个模型实例,进而造成重复推理、Token重复计费以及会话上下文混乱这三类典型问题。
识别重复请求的唯一标识
Atoms并不依赖客户端传入的随机ID,而是由服务端统一生成请求指纹。因为只有基于原始输入和调用链路特征来生成唯一标识,才能真正覆盖多模型并发调用的幂等场景。
具体做法是将user_id + session_id + normalized_prompt(去除空格、换行、注释,并标准化JSON字段顺序) + model_list.sort().join("|") 这四项拼接后执行SHA-256哈希,最终得到32字节二进制指纹。
【model_list必须排序后拼接】否则像gpt-4o|qwen-max与qwen-max|gpt-4o会被判定为两个不同请求,直接导致请求幂等失效。
服务端请求锁与缓存协同
方法一:Redis原子锁 + Promise缓存双机制
请求到达后,先通过Lua脚本执行SETNX命令尝试加锁,key为atoms:dedup:{fingerprint},过期时间设置为max(model_timeout)+10s(例如最长模型超时90s,则锁时长设为100s)。
如果加锁成功,就继续发起各模型调用;如果加锁失败,则立即返回303 See Other响应,并在Header中携带Location: /v1/dedup/wait?token={fingerprint},引导前端通过轮询等待最终结果。
方法二:内存级短时缓存(仅限单机部署)
可使用LRUMap缓存最近5000个请求指纹及其Promise引用,TTL设置为60秒。该方案适合开发环境或小流量的Atoms实例,但【不可用于K8s多副本集群】,否则由于缓存不一致,极易导致幂等机制失效。
多模型结果聚合阶段去重
第一步:等待所有模型返回结果或超时,收集results = [{model: "gpt-4o", output: "...", status: "success"}, ...]
第二步:对每个result计算output_hash = sha256(trim(output)),然后将那些status === "error"且output_hash与其他成功结果一致的条目剔除。这是因为部分模型可能因网络抖动返回空响应,但其他模型实际上已经成功生成了正确结果。
第三步:按照预设优先级(如gpt-4o > claude-3.5 > qwen-max)选择首个status === "success"的结果作为最终输出,其余结果直接丢弃且不入库。
第四步:向Redis发布事件PUBLISH atoms:dedup:done {fingerprint},唤醒所有等待该token的客户端连接。
