安全过滤把拒答与业务失败分开记录的实现方法
来源:17golang原创
时间:2026-09-20 04:21:42 310浏览 收藏
接入安全过滤后,最容易出现的误判是:用户没有拿到正常答案,系统就把这次请求记成“业务失败”。实际情况可能完全不同。安全策略拒答代表请求被策略分支拦截;业务失败代表输入或业务状态不满足条件;超时、限流和网络错误则属于上游调用失败。三者的处理动作不同,日志却经常只剩一个 failed。
我更建议在应用层把它们拆成稳定的结果类型,再分别决定用户提示、重试策略和告警级别。示例会参考安全过滤与审核的常见做法,官方资料可从 https://platform.openai.com/docs/guides/safety-best-practices 和 https://platform.openai.com/docs/api-reference/moderations 查看。
- 拒答是策略结果,不等于程序故障,也不应默认自动重试。
- 业务失败要保留可定位的业务码;传输失败才进入有限重试。
- 日志事件同时服务用户提示、指标聚合和后续人工复核,字段应稳定而克制。
先把“没有答案”拆成三条应用层路径
分类时不要从展示文案反推原因,而要从调用链上游的结构化结果开始。安全过滤通常会给出是否命中和类别信息;业务校验有自己的错误码;网络或服务端问题则表现为调用错误、状态码或超时。应用层只负责把这些事实归一化,不要把“拒答”改写成“服务异常”。
| 结果类型 | 用户提示 | 是否重试 | 主要指标 |
|---|---|---|---|
| policy_refusal | 换一个合规请求 | 默认不重试 | 策略命中率 |
| business_rejected | 补充或修正业务输入 | 按业务规则决定 | 业务失败率 |
| upstream_error | 稍后再试 | 限次、退避 | 超时/限流/5xx |

安全过滤分支要先于业务重试判断
如果代码先进入通用重试器,再判断安全结果,拒答可能被重复发送,既浪费调用额度,也让日志看起来像服务不稳定。正确顺序是先完成安全结果归类;命中策略时只返回安全的用户提示,记录必要的类别和关联 ID,不把原始敏感内容写进普通业务日志。
// Result 表示应用层对一次 AI 请求的稳定归类。
type Result struct {
Kind string // policy_refusal、business_rejected 或 upstream_error
Code string // 给指标和前端使用的稳定错误码
Retryable bool // 只有明确允许时才进入重试器
Message string // 面向用户的安全提示,不放内部策略细节
}
func classify(filter FilterResult, callErr error, inputErr error) Result {
// 策略命中优先返回,避免拒答被误送进通用重试。
if filter.Refused {
return Result{Kind: "policy_refusal", Code: "SAFETY_REFUSED", Message: "请调整请求内容后再试"}
}
// 业务错误由调用方修正,不能用网络重试解决。
if inputErr != nil {
return Result{Kind: "business_rejected", Code: "INPUT_INVALID", Message: "请检查输入条件"}
}
// 只有上游暂时性错误才标记可重试,次数和退避仍由外层控制。
if callErr != nil {
return Result{Kind: "upstream_error", Code: "MODEL_UNAVAILABLE", Retryable: true, Message: "服务暂时不可用"}
}
return Result{Kind: "ok", Code: "OK", Message: "处理完成"}
}
这里的关键不是字段数量,而是优先级:安全结果已经明确时,不再用后面的通用错误覆盖它。实际项目还应把过滤服务本身的超时和服务端错误单独归入 upstream_error,不要因为它发生在过滤阶段就误标成拒答。
日志字段要同时服务排障与隐私边界
统一事件建议至少包含 request_id、kind、code、retryable、latency_ms 和 source。source 可以区分 input-filter、output-filter、business 或 model-call,便于看出失败发生在哪一段。策略类别可按需要做低基数枚举;原始提示词、完整模型输出和敏感分类细节不应无条件进入普通日志。

告警也要按类型分开:policy_refusal 更适合做趋势和抽样复核,突然升高才触发策略排查;upstream_error 才是服务可用性告警的直接输入;business_rejected 则应该反馈给产品或业务规则负责人。三种数量混在一张“失败率”图上,会让值班人员做出错误动作。
哪些情况可以重试,哪些情况必须停止
可以把重试决策固定成小表,避免不同接口各写一套判断:
| 场景 | 处理 | 原因 |
|---|---|---|
| 策略拒答 | 停止自动重试,给出合规提示 | 重复相同输入不会改变策略结果 |
| 参数或权限不满足 | 返回业务码,要求修正输入 | 重试不会修复业务条件 |
| 短暂超时、限流、5xx | 指数退避并限制次数 | 可能是暂时性上游故障 |
| 响应解析失败 | 记录原始错误摘要并停止 | 先确认协议或供应商响应是否变化 |
上线前可以用四条检查清单收尾:拒答是否有独立的 kind;业务失败是否能定位到稳定的 code;重试器是否只接受 Retryable=true;普通日志是否避开原始敏感内容。只要这四点成立,安全过滤就不会再被当成模糊的“调用失败”。
常见问题
拒答是否应该返回 HTTP 4xx?
不必一概而论。对外 API 可以使用稳定的业务响应码,并在内部用 policy_refusal 区分;是否映射为 4xx 要看客户端契约,不能让网关把它误记成服务故障。
过滤服务超时算拒答吗?
不算。没有拿到明确的策略结果时,应记录为过滤链路的 upstream_error,并按超时策略处理。
日志里要保存完整拒答文本吗?
通常不需要。保存稳定的类型、类别摘要和请求关联 ID 就够排障;完整内容应遵循最小化和访问控制原则。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
199 收藏
-
173 收藏
-
301 收藏
-
398 收藏
-
科技周边 · 人工智能 | 7小时前 | 错误处理 · mcp · 工具调用 · AI工程 · MCP工具错误 isError structuredContent CallToolResult JSON-RPC错误208 收藏
-
359 收藏
-
171 收藏
-
234 收藏
-
380 收藏
-
191 收藏
-
272 收藏
-
251 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习