大模型疯狂重复输出 3 小时没人发现:我搭了一套主动巡检 + 三级熔断体系
"你好呀!我!你刚!你!你!你!你!你!你!你你!你!你!你!你!你!你!你!你!你!你!你!你!你!你!你!你!你!你!你!你!你!你!......"一个"你"字,循环输出了几百次。用户坐在车里,看着屏幕上的字一个一个往外蹦,像卡碟了一样。
没有告警,没有截断,没有兜底。接口返回 200,延迟正常,状态码正常——模型"活着",但在说废话。
这件事让我彻底意识到:三方大模型最恶心的故障不是挂了,是"活着但在说废话"。传统监控只看状态码和延迟,对这种"软故障"完全瞎了。
这篇文章,我把后来搭的整套主动巡检 + 分级触发 + 兜底熔断体系完整写出来,代码可以直接抄。

先搞清楚:哪些输出算"重复"
动手之前必须先定义清楚检测目标,不然写出来的规则要么漏检要么误杀。
我踩了两周坑之后,把三方模型的重复输出归成了 6 类:
这 6 类对应的检测策略不一样,不能一把尺子量所有。
核心检测引擎:4 个量化指标
所有检测逻辑最终落到 4 个可计算的数字上:
def compute_repeat_metrics(text: str, history: str = "") -> dict:"""计算文本重复度四维指标- text: 当前模型输出- history: 上一轮模型回答(用于上下文复述检测)"""# 指标1:n-gram 重复度(最精准,核心指标)trigrams = [text[i:i+3] for i in range(len(text)-2)]tri_repeat = 1 - len(set(trigrams)) / max(len(trigrams), 1)# 指标2:滑动窗口字符重复率window, size = text[-120:], 120char_repeat = 1 - len(set(window)) / max(len(window), 1)# 指标3:上下文余弦相似度(检测复述)ctx_sim = cosine_similarity(text, history) if history else 0.0# 指标4:循环周期检测(固定片段出现次数)cycle_count = detect_cycle_pattern(text)return {"trigram_repeat": tri_repeat, # >0.6 判定异常"char_repeat": char_repeat, # >0.35 判定异常"context_sim": ctx_sim, # >0.85 判定复述"cycle_count": cycle_count # >=4 判定循环}
这 4 个指标覆盖了所有 6 类重复场景。n-gram 重复度是主力,其余三个做交叉验证降低误判。
第一层防线:实时流式拦截——边输出边检测
这是用户感知最小、止损最前置的一层。
模型还在往外吐 token 的时候,检测引擎就已经在跑了。命中规则,立刻截断,用户看到的只是一句正常的收尾话术。
class StreamRepeatDetector:"""流式输出实时重复检测器"""def __init__(self, window_size=120, tri_threshold=4):self.buffer = ""self.window_size = window_sizeself.tri_threshold = tri_threshold # 3-gram 连续循环阈值def feed(self, token: str) -> bool:"""每收到一个 token 调用一次,返回 True 表示命中重复"""self.buffer += tokenif len(self.buffer) < self.window_size:return Falsewindow = self.buffer[-self.window_size:]# 规则1:3-gram 连续循环 ≥4 次if self._check_trigram_cycle(window):return True# 规则2:窗口内重复率 >35%if 1 - len(set(window)) / len(window) > 0.35:return True# 规则3:单词语连续重复 >6 次if self._check_word_stuck(window):return Truereturn Falsedef _check_trigram_cycle(self, text: str) -> bool:trigrams = [text[i:i+3] for i in range(len(text)-2)]from collections import Counterreturn Counter(trigrams).most_common(1)[0][1] >= self.tri_thresholddef _check_word_stuck(self, text: str) -> bool:# 检测最后一个词是否连续出现超过6次words = text.split()if len(words) < 6:return Falsereturn len(set(words[-6:])) == 1
命中截断后,系统自动补一句柔和收尾:
"以上就是本次解答内容,如果还有其他问题随时问我~"
用户完全无感知。但后台已经记了一笔:该模型故障次数 +1。
第二层防线:主动巡检——不等用户投诉
靠用户投诉发现问题,等于把质量保障外包给了你的用户。
这是我从那次 3 小时无告警事故里学到的最痛的一课。主动巡检分 4 条链路:
链路 1:线上实时指标监控(小时级)
按模型维度单独统计,每家三方模型独立看板:
- 每千次请求的重复触发次数
- 各重复类型占比(循环短句 / 段落复读 / 上下文复述)
- 分场景故障率(闲聊 / 问答 / 文案 / 座舱对话)
- 时段波动(凌晨高负载时重复率是否上涨)
核心告警逻辑:
当某厂商模型近 1 小时重复报错频次环比上涨 50%,系统自动标记该模型服务异常,推送告警。
链路 2:每日离线批量评测(凌晨自动跑)
每天凌晨低峰,用标准化测试集批量调用所有三方模型,自动打分。
测试集专门设计来诱导触发重复:
跑完自动输出各家模型重复率日报,对比基线阈值,超标即告警。
链路 3:灰度小流量探测
新模型版本上线前,切 5% 流量灰度验证。灰度期间重点盯重复指标,劣化则立刻停止放量、回滚旧版本。
链路 4:用户反馈闭环
用户点"回答重复"按钮 → 系统聚合 → 绑定对应三方模型 → 单模型单日重复类反馈超阈值 → 自动纳入异常清单。
被动反馈,转化为主动发现。
三级触发条件:从轻到重,梯度处置
检测发现问题之后怎么处置?不能一有重复就熔断,也不能等死了才切。我设了三级阈值:
级别 1:预警(仅通知,不动流量)
满足任意一条:
- 单模型 10 分钟内重复请求占比 ≥8%
- 离线评测当日重复率较基线上涨 ≥30%
- 单场景重复频次环比上升 40%
动作:企微/钉钉推送告警,人工核查。
级别 2:限流降权(切走部分流量)
满足任意一条:
- 半小时内重复占比 ≥15%
- n-gram 循环报错持续上涨无回落
- 同一重复 bug 集中爆发
- 连续 15 分钟重复占比 ≥25%
- 流式输出大面积字符卡顿刷屏
- 离线评测重复率超基线 2 倍
- 客服侧集中收到大量重复投诉
动作:该模型调用权重从 40% 降至 10%,其他模型承接,观测 30 分钟,回落则恢复。
级别 3:熔断下线(彻底切走)
满足任意一条硬条件:
动作:标记不可用,全量切备用模型。
8% 预警、15% 限流、25% 熔断——三个数字,是拿线上事故换来的。
兜底机制:4 层拦截,杜绝恶劣体验
触发之后怎么兜?层层递进:
兜底 1:流式截断(最前置)
命中重复 → 终止拉取后续 token → 补全柔和收尾话术 → 用户无感知。
兜底 2:跨模型重试(请求级)
def call_with_fallback(query, context, model_priority=["modelA", "modelB", "modelC"]):"""单请求跨模型重试,最多切2个模型"""for i, model in enumerate(model_priority[:3]):response = call_llm(model, query, context)metrics = compute_repeat_metrics(response, context.get("last_answer", ""))if metrics["trigram_repeat"] < 0.6 and metrics["context_sim"] < 0.85:return response # 正常,直接返回log_repeat_event(model, metrics) # 记录异常# 所有模型均异常,走静态话术兜底return get_static_fallback(query)
首次异常换同厂商备用接口重试,依旧重复则切第二优先级模型,最多切 2 次。
兜底 3:全局静态话术(终极兜底)
所有三方模型全挂 → 关闭动态生成 → 调用本地预置话术库应答 → 待指标回落后自动切回。
兜底 4:上下文清理(多轮复述专属)
连续 2 轮检测到上下文复述 → 自动裁剪过长历史 → 重置 prompt 并追加防重复约束。
前置 Prompt 约束:从源头减少重复
每次调用三方模型,系统默认塞入防重复指令:
约束要求:1. 禁止语句循环、段落重复、首尾内容一致2. 长文本保证句式多变,杜绝同质化堆砌3. 对话收尾简洁,不无限输出客套话术4. 问题宽泛时多角度精简作答,不循环凑字数
这层不能根治问题,但能把重复概率压低 30%-40%。属于成本最低的预防手段。
整套体系跑起来之后的效果
上线这套体系一个月,几个核心数字:
最让我安心的是:再也没出现过"模型复读了 3 小时没人知道"这种事。
三方大模型最恶心的故障不是宕机,是"活着但在说废话"。你的监控如果只看状态码和延迟,等于没监控。
2026 年多模型调度已经是标配,但大部分团队的容灾还停留在"接口超时重试"这一层。重复内容这种"软故障",不主动巡检、不分级熔断、不做流式截断,迟早会在某个周六晚上给你来一次 3 小时静默事故。
你们接三方模型遇到过重复输出的坑吗?怎么发现的?评论区聊聊,我猜大部分人第一次都是被用户投诉才知道的。
这篇有用的话,转给正在做多模型调度的同事——他们可能正在裸奔。