用户只会说它胡说:AI 服务的一线报障怎么接

发布时间:2026/10/25 7:32:26
用户只会说它胡说:AI 服务的一线报障怎么接

说明:本文讨论的是 AI 服务的一线报障受理流程,属于工程运维话题,不涉及具体模型版本与价格。AI 领域版本迭代极快,凡涉及版本号、价格、可用性,请以你阅读时的官方页面为准。文中代码为结构示意,请按自己的技术栈调整后再上生产。

一、"它胡说"这句话里没有信息

周一早上九点四十,支持群里进来一句:“你们这个 AI 胡说八道。”

后面通常没有别的了。你回一句"能具体说下吗",对方发来一张截图,截图里是一段看起来挺正常的回答。再问"你问的是什么",他说"就那个报销的事"。到这一步你花了二十分钟,手上仍然是零个可查询的条件。

这一章只说一件事:报障受理的第一步不是判断对错,是把一句自然语言变成能塞进检索框的条件。

用户给的,和他以为给了的

用户描述里通常只有三样东西:一个结论、一种情绪、一个可能并不存在的例子。

“胡说"在用户那里等价于"输出和我的预期不符”,而"预期"没法查。同一句"它乱答",可能对应他问的问题本身有歧义,可能对应材料里根本没有答案,也可能对应他换了入口、而那个入口的配置不一样。这几种在受理单上长得一样,在日志里一行都对不上。

情绪是有用的信号,但不是判据。"很生气"说明影响面,不说明该查哪张表。

例子最值钱,可用户给的往往是转述。他写"它说报销要找财务",原文可能是"报销申请需经财务部门复核后提交"。这两句丢进全文检索,命中率不是一个量级。所以要原文:我发了什么、它回了什么。

用户说的工程师要的差在哪
“它胡说”一段原文问答对结论对不上可检索的文本
“就刚才”带时区的时间戳相对时间无法对齐日志
“你们那个网页”entry 枚举值口语名与字段名不同源
“一直这样”重放三次的结果印象替代不了记录

工程师要的是可查询条件

工程师能开工的前提,是手上有四个能直接用的东西:时间、会话、输入、入口。

  • 时间落到分钟级,把窗口从"全部历史"缩到"那一次"
  • 会话 ID 是唯一能把人带进日志的钩子,没有它只能靠时间加关键字碰运气
  • 原始输入决定能不能重放,转述过的输入重放不出同一个问题
  • 入口决定你看哪一套日志、哪一份配置

四样凑齐,第一动作可以是查;缺一样就退化成"你再试一次给我看看",响应时间从十分钟拉成一天。

缺口不该由用户来补

用户不知道你会记会话 ID,也不知道日志只保留一段时间。要求他提供不知道存在的东西,是把系统设计的债转嫁给用户,收回来的还是一堆没法用的描述。

正确的补法在三个地方:界面上放一个"复制本次会话"的按钮;报障入口自动带上时间、入口、请求 ID;表单只问系统拿不到的东西。都是一次性投入,做完每一单省十几分钟。

这一章可以带走的一句:用户没有义务提供工程信息,但系统有义务在他开口的时候顺手把信息收下来。

二、一线要收的最小信息包

"最小"这两个字有约束力:多一个字段,用户多一分放弃率;少一个字段,工程师多一次来回。下面六个是压不下去的下限。

六个字段,各自的作用

会话 ID 或任务 ID。它是索引键,不是给人看的。要求是能直接粘进检索框:长度对、字符集对、没被人为截断。

发生时间,带时区。精确到分钟就够,但必须能和你日志的时间轴对齐。跨时区团队最容易在这翻车:用户说"昨天下午三点",日志按 UTC 记,一减就是八小时。

原始输入。原文,不是摘要。概括会丢掉标点、空格、换行和附件名,而这些恰好影响上下文拼接。

期望与实际的差。写成两句话:“我以为它会做 X”“它给了 Y”。写成"答案不对"等于没写,因为"不对"没有方向。

是否可复现。分级的主要输入。三值就够:每次、偶尔、只出现过一次。“还没试"要单独留一项,不要塞进"只出现过一次”。

入口。网页、客户端、API、批处理。入口写错,后面所有查询都白做。

每个字段的合格线

字段定义里最容易漏的是"什么算填对"。只写"必填"没用,用户会填一个字形正确、内容错误的字符串,然后在受理单里躺三天。

字段合格线(能直接用于查询)不合格的典型样子
会话 ID可直接粘贴检索、长度与字符集符合规则只截了后六位、截图有反光
发生时间带时区的 ISO 时间,误差 ±5 分钟“昨天下午”“刚刚”“上周”
原始输入用户实际发送的完整文本,可复制一句概括、语音转文字后的残句
期望与实际的差两句话,X 和 Y 都可判定“答得不对”“胡说八道”
是否可复现四值枚举之一,重放动作真实做过空着、或者填"应该是每次"
入口枚举值,和日志里的 entry 字段同名“网页版”“就是你们那个”

表单里的入口选项要和日志里的entry一一对应。写"网页版"、记web,中间就得有人翻译一次,翻译就会出错。

收不到的时候怎么兜

第一类,用户把会话删了。退一步收"输出里的一段原文",用那段原文做全文反查,通常能定位到唯一一条响应。要跟用户说明为什么需要,否则他会觉得你在刁难。

第二类,走的是 API,没有界面。这类用户一般能给请求 ID 或调用时间,以请求 ID 为主键,时间只作辅助。

第三类,不敢贴原文,里面可能有客户名、金额、证件号。要给可操作的替代:只贴结构、脱敏后贴、或者授权工程师按会话 ID 查看。授权要落在产品里,不能落在群聊里的"我同意"。

把六个字段固化成受理单:

⚠️ 代码待验证

ticket:id:t-20261024-0007entry:web# 与日志 entry 字段同名session_id:s_9f2c7a41# 用户可复制,直接进检索框happened_at:"2026-10-24T09:38:00+08:00"# 必须带时区raw_input:|帮我看下这张差旅报销单能不能过,附件是行程单和发票。expected:逐条列出不符合项actual_quote:|您的报销单符合规定,可以提交。reproducible:true# 四值:true / false / once / untestedself_check:replay:reproduced# reproduced / not_reproduced / changedother_entry:not_triedshrunk_input:reproduced

每一行都要能翻译成一条查询条件;翻译不出来的字段,不该出现在表单里。

三、做成一句话模板:让用户填对

表单不是问卷,是检索条件的生成器

判断一个字段该不该留,只有一个标准:它会不会进入查询条件。不会的删掉。

“您的公司规模”"您是哪个部门"这类字段在工单系统里很常见,对统计有用,对受理没用。它让用户觉得在走流程,填写质量跟着下降,最后连该认真填的也敷衍了。

还有一条更硬的:系统能自己拿到的东西,一律不要问用户。浏览器 UA、入口、请求 ID、会话 ID、提交时间、用户标识,提交那一刻全都拿得到。把它们做成输入框,等于把自动化外包给最不可靠的一环。按这条砍一刀,十几项的表单通常剩三四个必填。

第一屏只放三个框

用户的耐心只够填三个框。第一屏放这三样:

  1. 你发了什么(多行文本,必填)
  2. 它回了什么(多行文本,必填,只要一两句原话)
  3. 什么时候(默认当前时间,可改,必填)

其余放进"补充信息"折叠区。代价是首轮信息不完整,收益是提交率:一个没提交的报障等于不存在。

字段定义写成配置,改起来不用发版:

⚠️ 代码待验证

{"form_id":"ai_incident_intake","version":3,"fields":[{"name":"raw_input","type":"textarea","required":true,"min_len":8,"label":"你发给它的内容","hint":"粘贴原文,不要概括"},{"name":"actual_quote","type":"textarea","required":true,"min_len":4,"label":"它回答的原话","hint":"一两句即可,不要截图"},{"name":"happened_at","type":"datetime","required":true,"auto_fill":"now"},{"name":"entry","type":"select","required":true,"auto_fill":"detect","options":["web","app","api","batch"]},{"name":"expected","type":"text","required":false,"max_len":120,"label":"你原本预期它做什么"},{"name":"reproducible","type":"radio","required":false,"options":["every_time","sometimes","once","untested"]}]}

auto_fill的两个字段仍要显示,只是不让改:让用户看见系统已经知道他在哪个入口,比藏起来更让人放心。

校验:该拦的拦,该提示的提示

校验的尺度分三档,全都硬拦会把用户赶走。

情况处置理由
输入为空、时间在未来硬拦,不许提交提交了也查不了,是无效工单
输入少于 8 个字软提示,可继续有人真的只发了一个词
出现"胡说"“不对”“有问题”追加必填项:引用一句原话这类词没有方向,必须补方向
时间早于日志保留期硬拦,并说明保留天数免得用户白填,也免得多一轮解释
会话 ID 格式不符软提示,允许留空有些用户确实拿不到

第三行是这套表单里最关键的一条:它是条件必填。平时"引用原话"是选填,用户一旦用了那些没信息量的词,它变成必填。用户不觉得被拦,只觉得系统在帮他把话说清楚。

实现上就是一个返回错误列表的函数,不要抛异常——错误要能一次全显示,而不是让用户改一条提交一次:

⚠️ 代码待验证

fromdatetimeimportdatetime,timedelta,timezone VAGUE_WORDS=("胡说","不对","有问题","乱答","答非所问")SUMMARY_HINTS=("它说","大概","类似","好像","意思是")defvalidate(payload:dict,log_retention_days:int=30)->list[str]:errors=[]raw=(payload.get("raw_input")or"").strip()ifnotraw:errors.append("请输入你发给它的内容")eliflen(raw)<8:errors.append("输入很短,确认一下是不是只发了一个词")elifsum(winrawforwinSUMMARY_HINTS)>=2:# 转述的典型特征:连着出现多个"它说/好像"这类词errors.append("这段看起来是转述,请粘贴你实际发送的原文")quote=(payload.get("actual_quote")or"").strip()ifany(winraw+quoteforwinVAGUE_WORDS)andnotquote:errors.append("请引用一句它的原话,这样我们才能定位到具体回答")ts=parse_ts(payload.get("happened_at"))iftsisNone:errors.append("时间格式不对,需要带时区")elifts>datetime.now(timezone.utc)+timedelta(minutes=5):errors.append("时间在未来,检查一下时区")elifts<datetime.now(timezone.utc)-timedelta(days=log_retention_days):errors.append(f"这个时间已经超出日志保留期({log_retention_days}天)")returnerrors

最后一个分支不是拦住用户,是提前告诉他这条查不到了——否则他会提交,然后等一个"查不到"的回复。

四、自助诊断:三个动作,让用户先排除一半

三个动作,按成本排序

自助诊断不是让用户替工程师干活,是让他在两分钟内把三种最可能的分岔走掉。

动作用户要做什么结果怎么记能分掉哪一类
原样重放点一下"再问一次"稳定复现 / 偶发 / 换了个错法稳定性与内容问题
换一个入口同一句在另一个入口再问复现 / 不复现 / 没权限入口之间的配置差异
缩小输入去掉附件、去掉前半段、缩短指令仍出错 / 变正常与输入长度或形态相关

顺序不能颠倒:重放最便宜,一秒做完;缩小输入最贵,用户得重新组织问题。让用户先做贵的,他多半直接放弃。

三个动作各自的读法

原样重放。关键是"原样":同一段输入、同一个入口、不改一个字。所以产品上要提供按钮,不要让用户手工复制粘贴——手工重放十有八九会顺手改一两个字。结果只有三种:每次都错,和输入内容强相关;偶尔错,和采样或并发有关;换了个错法,说明这段输入踩在边界上。

换入口。同一份能力在不同入口上往往差着系统提示、超时、附件解析开关、检索是否开启。换入口能复现,说明现象不是某个入口独有;换入口不复现,说明差异在入口配置里。这一条能挡掉很多"你们模型不行"的误报。

缩小输入。三种砍法:去掉附件、去掉前半段对话、把长文档换成一段摘要。三种结果:短输入正常,和长度或截断有关;短输入也错,和内容有关;怎么砍都正常,和当时的并发或时间有关。

这里有个容易忽略的点:"缩小后正常"不等于问题消失了。它只说明触发条件包含长度。受理单上要如实记这个结果,而不是记成"用户误报"。

结果要能回到受理单

自助做完了,结果必须变成结构化字段回传,否则自助只是把问题又推回给用户,还多花了他两分钟。

三个字段就够,每个都是三值枚举:

  • replay:reproduced / not_reproduced / changed
  • other_entry:reproduced / not_reproduced / not_tried
  • shrunk_input:reproduced / normal / not_tried

这三个字段直接决定分级。replay=reproduced且shrunk_input=normal的单子,比一个什么都没试过的值钱得多,接手的人第一眼就知道往哪查。

自助也有边界。涉及计费、权限、数据删除的现象,不要让用户自己试——他每试一次都可能产生真实副作用。这类直接进人工,别省那两分钟。

五、分级与升级路径

三级,判据写死

分级的难点不在分级本身,在"什么算严重"这句话没有共识。所以判据必须是可判定的条件,不能是形容词。

级别判据(满足任一)响应时限谁接
一级核心入口有请求但无任何输出;或 30 分钟内 3 个以上不同用户报同一现象15 分钟内有人回应当班工程师 + 负责人
二级单用户、可复现、有替代路径可用当日工作时间内当班工程师
三级单次、不可复现、用户已自行绕过每周归并时统一看受理人登记即可

"核心入口有请求但无任何输出"和"答得不对"是两件事。前者是服务没回,后者是回了但内容不合预期。放进同一个级别,真正该立刻处理的那类会被淹没。

"30 分钟内 3 个以上不同用户"有两个限定词:不同用户排除同一个人反复提交,30 分钟内排除跨越一周的偶发。少任何一个,这条判据都会误报。

判据写成代码比写成文档可靠,因为代码不会在不同人的嘴里变成不同意思:

⚠️ 代码待验证

fromdatetimeimportdatetime,timedelta REQUIRED=("entry","happened_at","reproducible","has_output")LEVEL_1,LEVEL_2,LEVEL_3="L1","L2","L3"defgrade(ticket:dict,same_phenomenon_users:int,now:datetime|None=None)->tuple[str,str]:missing=[kforkinREQUIREDifticket.get(k)isNone]ifmissing:# 缺字段不猜级别,直接打回补信息raiseValueError(f"受理单缺字段:{missing}")now=nowordatetime.now()ifticket["entry"]=="core"andticket["has_output"]isFalse:returnLEVEL_1,"核心入口有请求无输出"ifsame_phenomenon_users>=3andticket["happened_at"]>=now-timedelta(minutes=30):returnLEVEL_1,"30 分钟内多用户同一现象"ifticket["reproducible"]isTrue:returnLEVEL_2,"单用户可复现"returnLEVEL_3,"单次不可复现"

那个raise是有意为之。缺字段时按默认值猜一个级别,等于把一个信息不完整的单子伪装成完整的,后面所有人都被这个假级别骗。

允许跳级,不允许随手降级

往上跳要容易:任何人都能标一级,代价是必须填一段理由。这条不是防滥用,是防沉默——宁可多几个误报,也不要一个被压在三级里的真故障。

往下调要难:只能由接手人看过之后调,并写清原因。最常见的坏习惯是"先标三级,等有空再看",这一压往往就是两天。

还有一条:时限从提交时刻算,不从接单时刻算。从接单算的话,一直没人接的单子永远不会超时,时限形同虚设。

超时之后自动往上走

一级 15 分钟没人回应,自动通知上一层;二级到当日结束还没人接,次日自动升为一级并附上"已积压一天"。这套动作是自动的,不依赖任何人记得。

只有"超时未回应"才升,"回应了但没解决"不升:前者是流程问题,后者是技术问题,处理的人手不一样。

完整版资料清单:本文用到的受理单模板、分级判据表和术语对照都整理在里面了,扫码即可获取:

六、工程师接手后的第一条动作

把"胡说"翻译成三个查询条件

接手后的第一件事不是看模型答得对不对,是确认用户说的那一次到底是哪一次。

拿三个条件去定位:时间窗(用户报的时间前后各 15 分钟)、会话 ID(精确匹配)、入口(枚举值)。三个里至少两个要落在索引上,否则查询会退化成全表扫描。

这一步必须放在最前面,原因是受理单上的时间是"用户记得的时间",不是"请求发生的时间"。人会记错半小时,会把"昨天晚上"说成"昨天下午",会把自己在另一个入口的操作记成这个入口。先把那一次请求找出来,后面的讨论才有共同对象。

这一步还常带来一个意外发现:用户说的那次,根本不在他填的入口里。到这儿问题就从"内容不对"变成了"受理单填错入口",处置动作完全不同。

一条能跑的查询

查询本身没技巧,技巧在条件上。下面这条带上租户条件,是因为多租户下session_id有可能重复——两个租户各自生成了一样的短 ID,不带租户就会拉出别人的会话,既查错也越权。

⚠️ 代码待验证

-- 按时间窗拉一条会话的全部请求-- tenant 条件不能省:session_id 在多租户下可能重复SELECTr.request_id,r.turn_index,r.entry,r.created_at,r.status,r.finish_reason,r.input_chars,r.output_charsFROMrequest_log rWHEREr.tenant_id=:tenant_idANDr.session_id=:session_idANDr.created_at>=:from_tsANDr.created_at<:to_tsORDERBYr.created_atLIMIT200;

ORDER BY created_at是为了还原对话顺序:少了顺序,你会看到一堆并列的请求,判断不出上下文是怎么被拼起来的。LIMIT 200是保险——长时间挂着的会话里上千轮请求并不罕见,不设上限会把结果集撑爆。

受理单上由接手人填的四个字段

受理单分两半:用户填的那半,和接手人填的那半。后一半只有四个字段,但每个都能被追问。

字段填什么为什么必须有
首查条件实际用了哪几个条件、命中几条让别人能原样复现你的检索
定位到的请求request_id 与 turn_index把讨论对象钉死在一个请求上
信息完整度六个字段里缺哪几个缺字段的单子结论要打折看
下一步归属转给谁、还是退回补信息只写归属,不写结论

第四个字段要克制。受理人写"应该是模型幻觉"“估计是检索没召回”,看起来有帮助,实际是在没有证据时给接手人预设方向。

“查到 0 条"也是有效结果。它说明三件事之一:入口填错、时间窗错、或者这次请求根本没落日志。三种的下一步完全不同,所以要当成正常分支记下来,而不是当成"没查到,再问问”。

七、把重复报障变成规则

一周一次归并,按现象不按人

单看一条报障,什么规律都看不出来。攒一周再看,重复的部分会自己浮出来。

归并的三个维度是:现象、入口、输入形态。不按用户归并,因为同一个用户可能报三件不同的事,三个用户也可能报同一件事。

“现象"要归一化。用户写"胡说”“答非所问”“牛头不对马嘴”,归并时要落到同一个标签上。这一步靠人做,不要指望关键词自动聚类——中文里同一个意思的说法太多,自动聚出来的组通常没法看。

阈值建议是 5:同一类一周内到 5 条才升级成"待处理类",不到的留在池子里,下周继续累计。低于阈值的强行归类,会得到一堆只出现过一两次的类,改完表单也没人用。

归并之后要改三样东西

归并结果如果只写进周报,等于没做。它必须回写到三个地方,而且最好是同一个人改:

  1. 表单选项:把高频现象做成下拉项。用户点一下,分类就带上了,比让他用自然语言描述准确得多。
  2. 自助话术:这一类如果有稳定的自助解法,写进自助诊断的提示语。比如"这类问题多数和附件解析有关,可以先去掉附件试一次"。
  3. 知识库条目:一条现象、一条判据、一条处置动作,三行写完。不要写长文,长文没人看,也没法在受理时快速对照。

三样必须同步。最常见的破败样子是:表单里多了一个选项,知识库里没有对应条目,接手人看到这个选项也不知道该干什么,最后还是回去问用户。

什么不该归并

第一类是时间上高度集中的。一小时内进来的 20 条,很可能是同一次故障的 20 个用户。归并成 1 条,等于把影响面从 20 改成 1,级别也跟着从一级掉到三级。这类要合的是"处置动作",不是"计数"——20 条一起处理,但 20 这个数字必须留着。

第二类是处置动作不同的。两条描述几乎一样,但一条要改配置、一条要补数据,它们就不是同一类。归并的粒度是"处置动作相同",不是"关键词相同"。

统计脚本可以自动做完候选分组,剩下的判断留给人:

⚠️ 代码待验证

fromcollectionsimportdefaultdictdefcluster(tickets:list[dict],min_count:int=5)->tuple[list[dict],list[dict]]:buckets=defaultdict(list)fortintickets:key=(t["phenomenon"],t["entry"],t["input_shape"])buckets[key].append(t)ready,pending=[],[]forkey,itemsinbuckets.items():row={"phenomenon":key[0],"entry":key[1],"input_shape":key[2],"tickets":len(items),"users":len({i["user_id"]foriinitems}),"ticket_ids":[i["id"]foriinitems],}(readyiflen(items)>=min_countelsepending).append(row)ready.sort(key=lambdar:r["tickets"],reverse=True)returnready,pending

输出里特意带了users:条数多但用户数只有 1,说明是同一个人在反复提交,不该进"待处理类",该去看他为什么反复提交。

完整版资料清单:本文用到的受理单模板、分级判据表和术语对照都整理在里面了,扫码即可获取:

附表 A:关键取舍一览

取舍本文结论判断依据位置
受理第一步做什么先转成查询条件,不判断对错没有条件就没有可查对象第一章
信息缺口谁来补系统补,不追问用户用户不知道哪些东西存在第一章
表单首屏只放三个必填框提交率优先于首轮完整度第三章
模糊描述怎么处理条件必填"引用原话"硬拦会赶走用户,放任则无法定位第三章
自助动作顺序重放 → 换入口 → 缩小输入按用户操作成本排序第四章
分级判据写法可判定条件,不写形容词"严重"没有共识,无法执行第五章
降级权限只有接手人能降,且写原因防止"先压三级再说"第五章
时限从哪算从提交时刻算从接单算则无人接单永不超时第五章
接手第一条动作先确认是哪一次请求用户记的时间不等于请求时间第六章
受理人写不写结论只写归属,不写结论无证据的预设会带偏后续第六章
归并按什么聚类现象 + 入口 + 输入形态按用户聚类会把不同问题混在一起第七章
密集报障怎么处理合处置不合计数合计数会把影响面算小第七章

附表 B:术语速查表

术语含义
最小信息包让工程师能开始检索的最少字段集合,本文定为六个
会话 ID一次连续对话的标识,是定位日志的主键
入口用户实际使用的接入方式,如网页、客户端、API、批处理
可查询条件能直接落进检索或数据库条件的值,如时间窗、精确 ID
原样重放同一输入、同一入口、不改一字地再执行一次
分级判据用来判定报障级别的可判定条件,不含形容词
超时升级超过响应时限仍无人回应时,自动提升级别并通知上一层
现象归一化把不同说法归到同一个现象标签,通常由人完成

写在最后:这篇用到的资料

写这篇文章时,我把几个模型的官方文档、参数表和实测记录都对了一遍,顺手整理成几份配套的东西:

  • 大模型学习路线图:从 LLM 基础到 Agent 开发,各阶段该学什么、用什么资料
  • 大模型全套教程:按主题分好的视频与文档清单
  • 大模型实战好书:24 本,附每本适合的阶段

资料是我自己整理的,放在下面这个码上,扫码即可获取:

添加时备注「大模型」,优先通过。

拿到之后建议先看学习路线图那一份,先定位自己在哪个阶段,再决定学什么,比一上来就啃框架效率高得多。