Spring AI生产治理、SLO、成本与事故排查
Spring AI 接口能够返回一句回答,只证明模型链路基本连通,不代表可以上线。商业 AI 系统同时面对概率性输出、秒级延迟、按 Token 计费、第三方限流、Prompt Injection、RAG 越权、Tool 写操作、流式中断和模型版本漂移。治理目标不是让模型永不失败,而是让失败可以被限制、发现、解释、降级和恢复。
生产治理需要覆盖五个平面:流量与容量、可靠性、质量与评估、安全与权限、成本与运营。任何一项缺失,都可能出现“接口 200 但答案不可用”的假健康。
学习目标
完成本页后,你应该能够:
- 为 AI 接口分别定义可用性、延迟、质量、安全和成本 SLO。
- 把一次请求拆成鉴权、RAG、Rerank、模型、Tool 和后处理时间预算。
- 设计请求数、并发、Token 和金额四类配额。
- 区分可重试、不可重试和结果未知错误,避免重试雪崩。
- 正确使用超时、退避、抖动、Bulkhead、Circuit Breaker、排队和降级。
- 设计模型路由和能力兼容的 Fallback。
- 设计同步、流式和异步 AI 接口的错误语义。
- 建立模型、Prompt、RAG索引、Embedding、Tool和安全策略的版本矩阵。
- 设计离线评估、灰度、线上反馈和回滚门槛。
- 记录可排障又不泄密的日志、指标和 Trace。
- 分析成本突增、质量下降、429、延迟、越权和Tool误执行事故。
一、AI生产可用不等于HTTP 200
一个请求可能 HTTP 200,但仍然失败:
- 模型编造不存在的制度。
- JSON 不能解析。
- 引用不支持答案。
- RAG 召回了其他租户资料。
- Tool 查询了错误订单。
- 回答耗时 40 秒,用户早已离开。
- 输入 2 万 Token,成本超过业务价值。
需要拆分 SLI:
| 维度 | 示例SLI | 说明 |
|---|---|---|
| 技术可用性 | 请求获得受控成功/拒答比例 | 不把模型胡编算成功 |
| TTFT | 首个Token时间P95 | 影响流式体感 |
| 总耗时 | 完整响应时间P95/P99 | 影响完成率和资源占用 |
| 质量 | 正确性、Faithfulness、格式通过率 | 依场景评估 |
| 安全 | 越权率、注入突破率、敏感泄露率 | 高风险通常硬门槛 |
| Tool正确性 | 参数/权限/幂等/结果成功率 | 写操作尤其严格 |
| 成本 | 每成功任务Token和金额 | 不能只看请求平均 |
| 拒答 | 应答/应拒答准确率 | 错误拒答和错误回答都要看 |
二、一次生产请求的治理链
flowchart TD
A["认证请求"] --> B["参数、大小、内容类型校验"]
B --> C["租户、场景和数据权限"]
C --> D["用户/租户/全局配额与并发门禁"]
D --> E["风险分级和场景路由"]
E --> F["RAG权限过滤、召回和重排"]
F --> G["Prompt与上下文Token预算"]
G --> H["模型路由、超时、Bulkhead和重试"]
H --> I{"是否请求Tool"}
I -->|"是"| J["工具权限、参数、确认和幂等"]
J --> H
I -->|"否"| K["结构化解析、引用和安全校验"]
K --> L["响应、拒答、降级或人工审核"]
L --> M["日志、指标、Trace、成本和反馈"]三、先为场景分级
| 风险级别 | 场景 | 可接受策略 |
|---|---|---|
| 低 | 文案改写、内部头脑风暴 | 可直接生成,标记AI内容 |
| 中 | 企业制度问答、客服建议 | RAG引用、拒答、抽样审核 |
| 高 | 医疗建议、法律结论、支付/删除Tool | 强规则、人工确认、最小工具权限、完整审计 |
同一个模型不能使用一套治理策略处理所有场景。高风险请求即使模型“看起来很确定”也不能跳过人工/规则门槛。
四、请求时间预算怎样拆
总延迟近似:
T_total = T_auth
+ T_rewrite
+ T_embedding
+ T_retrieve
+ T_rerank
+ T_model
+ ΣT_tool
+ T_validate例:交互式知识问答总预算 12 秒:
| 阶段 | 预算示例 |
|---|---|
| 鉴权/配额 | 100ms |
| Query改写 | 800ms |
| Embedding+检索 | 500ms |
| Rerank | 700ms |
| 模型TTFT | 2.5s |
| 模型完整生成 | 6s |
| 校验/引用 | 500ms |
| 网络抖动余量 | 900ms |
预算只是示例。外层超时应大于内部阶段预算之和,但不能无限大:
内部HTTP连接超时 < 单阶段超时 < 服务总超时 < 网关超时 < 客户端超时如果 Nginx 30 秒断开,而应用和模型仍执行 120 秒,会产生无用户接收的成本和资源浪费。
五、同步、流式和异步怎么选
| 模式 | 适用 | 风险 |
|---|---|---|
| 同步 | 分类、抽取、短回答 | 线程/连接等待,长尾明显 |
| SSE流式 | 长回答、聊天 | 中途错误、断连、代理缓冲 |
| 异步Job | 大文档分析、批量评估 | 最终一致、任务状态和取消 |
同步
成功前不返回,错误可用标准 HTTP 状态/业务码表达,但需要严格总超时。
流式
HTTP Header 已发送后,中途失败不能再改为 500。需要发送结构化 error event,并记录 partial output 是否展示/保存。
异步
提交返回 202 + jobId,Worker 执行,客户端轮询/Webhook。任务表要有幂等 requestId、状态、租约、重试和取消语义。
六、四种限额不能混为一个QPS
6.1 请求速率
防止单位时间请求过多。
6.2 并发数
AI 请求慢,即使 QPS 不高也可能占满连接、线程和 Provider 并发。
6.3 Token额度
同样一个请求可能是 100 Token 或 100000 Token。只限 QPS 无法控制成本和上下文压力。
6.4 金额/业务额度
按用户、租户、场景和日期控制预算,保留管理配额和应急余量。
flowchart TD
A["请求进入"] --> B{"用户速率是否超限"}
B -->|"是"| C["429或排队"]
B -->|"否"| D{"租户并发是否有槽位"}
D -->|"否"| E["快速失败/短队列"]
D -->|"是"| F{"估算Token和金额是否超预算"}
F -->|"是"| G["拒绝、压缩上下文或降级模型"]
F -->|"否"| H["预留额度并调用"]
H --> I["按实际Usage结算差额"]七、配额数据怎样并发更新
不要“先查余额,再更新余额”,并发请求可能一起通过。
条件更新示例:
UPDATE ai_tenant_quota
SET reserved_tokens = reserved_tokens + ?,
row_version = row_version + 1,
updated_at = CURRENT_TIMESTAMP
WHERE tenant_id = ?
AND quota_date = ?
AND used_tokens + reserved_tokens + ? <= token_limit;更新行数为 1 才获得额度。请求结束后按实际 Token 结算;崩溃未结算的 Reservation 需要过期回收和对账。
云 Provider Usage 可能缺失或延迟,应用还应保存估算值和账单对账。
八、本地Semaphore为什么不等于全局并发限制
private final Semaphore semaphore = new Semaphore(20);单实例有效;部署 10 个 Pod 后全局可能达到 200。全局配额需要 Redis/数据库/网关或集中 AI Gateway,且要处理租约过期和节点崩溃。
本地 Semaphore 仍可作为每实例 Bulkhead,防止模型调用占满全部线程。
九、Bulkhead解决什么
将不同场景/Provider隔离:
知识库问答池
合同抽取池
低优先级批处理池
高风险Tool池批量评估流量不应耗尽在线客服并发。Bulkhead 可以是独立线程池、连接池、队列、Pod或Provider配额。
十、什么错误可以重试
| 错误 | 是否自动重试 | 原因 |
|---|---|---|
| 400参数/模型不支持 | 否 | 请求本身错误 |
| 401/403 | 否 | 密钥/权限错误,重试放大流量 |
| 404模型不存在 | 否 | 配置错误 |
| 409/Provider特定冲突 | 视语义 | 先理解错误码 |
| 429 | 条件重试 | 尊重Retry-After和总预算 |
| 5xx | 有限重试 | 可能瞬时故障 |
| 连接超时 | 有限重试 | 可能尚未发送成功 |
| 读取超时 | 谨慎 | Provider可能已生成并计费 |
| 输出Schema失败 | 可有限修复重试 | 需不同Prompt/修复策略 |
| Tool写操作超时 | 默认不盲重试 | 外部可能已成功,先按幂等键确认 |
十一、指数退避和抖动
delay = min(maxDelay, baseDelay * 2^attempt) + randomJitter还要限制:
- 最大尝试次数。
- 单请求总截止时间。
- 全局重试预算,例如重试流量不超过正常流量10%。
- Retry-After。
- 可重试错误白名单。
没有抖动时,大量请求会在同一时间再次冲击 Provider。
十二、重试为什么会重复计费
读取超时不代表 Provider 没生成。第一次可能已经完成并计费,客户端只是没收到响应;重试会再次生成。
记录 clientRequestId/providerRequestId,Provider支持幂等键时使用;不支持时把重复成本纳入预算,并避免对长生成无脑重试。
十三、Circuit Breaker是什么
当 Provider 持续失败时,继续请求只会占用资源和放大故障。Circuit Breaker 状态:
flowchart TD
A["CLOSED正常调用"] -->|"失败率/慢调用超过阈值"| B["OPEN快速失败"]
B -->|"等待窗口到期"| C["HALF_OPEN放少量探测"]
C -->|"探测成功"| A
C -->|"探测失败"| B断路器按 Provider/模型/区域区分,不能一个小模型失败就熔断全部 AI 能力。
十四、降级不是随便换个模型
模型 Fallback 必须满足场景能力:
| 场景 | 降级可行性 |
|---|---|
| 普通摘要 | 可切更便宜模型或模板摘要 |
| JSON抽取 | 备用模型必须通过Schema评估 |
| Tool Calling | 备用模型必须支持相同Tool Schema |
| 多模态OCR | 文本模型不能直接替代图片能力 |
| 高风险医疗问答 | 更适合拒答/人工,不应降级到低质量模型 |
每个路由版本要记录 primary/fallback、能力、价格、区域和评估分。
十五、模型路由
flowchart TD
A["识别场景、风险、语言和输入模态"] --> B{"是否需要Tool/JSON/多模态"}
B -->|"是"| C["过滤不支持能力的模型"]
B -->|"否"| D["候选模型集合"]
C --> D
D --> E["按质量门槛过滤"]
E --> F["按延迟、成本、区域和配额选择"]
F --> G["记录routeVersion和实际模型"]模型路由属于后端策略,不接受用户直接传任意模型名绕过预算和数据区域。
十六、Prompt版本不是唯一版本
一次回答由版本矩阵决定:
applicationVersion
modelProvider/modelName/modelRevision
modelOptions
promptVersion
advisorChainVersion
embeddingModelVersion
chunkStrategyVersion
ragIndexVersion
retrieval/rerankConfigVersion
toolSchemaVersion/toolImplementationVersion
safetyPolicyVersion事故复现必须拿到整个矩阵。只说“Prompt没改”无法证明系统没变。
十七、Prompt注册表示例
CREATE TABLE ai_prompt_release (
prompt_key VARCHAR(128) NOT NULL,
prompt_version VARCHAR(64) NOT NULL,
content_hash VARCHAR(128) NOT NULL,
content_location VARCHAR(500) NOT NULL,
model_route_version VARCHAR(64) NOT NULL,
evaluation_run_id VARCHAR(64) NOT NULL,
release_status VARCHAR(32) NOT NULL,
released_by VARCHAR(64),
released_at TIMESTAMP,
PRIMARY KEY (prompt_key, prompt_version)
);真实 Prompt 内容可能包含内部规则,不应无权限暴露到前端或普通日志。
十八、结构化输出必须校验
flowchart TD
A["模型输出JSON文本"] --> B["JSON语法解析"]
B --> C["Schema/Bean Validation"]
C --> D["业务规则校验"]
D --> E{"是否通过"}
E -->|"是"| F["进入业务流程"]
E -->|"否"| G["修复重试、人工或失败"]模型输出:
{"amount": -100, "currency": "ABC"}JSON 合法但业务非法。必须校验金额非负、币种白名单、ID是否存在、用户是否有权限。
十九、Tool Calling生产边界
Tool 参数全部视为不可信输入:
- userId/tenantId从认证上下文注入,不信模型参数。
- 查询做数据权限和字段最小化。
- 写操作有requestId、幂等、二次确认和审计。
- 金额使用BigDecimal。
- 超时后先查询执行结果,不能无条件重试。
- Tool返回内容先脱敏再交给模型。
Tool详细原理见:Spring AI Tool Calling。
二十、RAG缓存Key为什么必须包含权限和版本
危险 Key:
hash(question)用户 A 的答案可能被返回给无权限用户 B。
安全 Key 至少考虑:
tenantId
permissionScopeVersion/roleSetHash
scene
normalizedQuestion
promptVersion
modelRouteVersion
ragIndexVersion
retrievalConfigVersion
safetyPolicyVersion
language高风险实时业务不应只依赖语义缓存。缓存值还要有引用、生成时间、资料版本和安全结果。
二十一、什么可以缓存
| 对象 | 是否适合 | 注意 |
|---|---|---|
| 公共低风险FAQ | 适合 | 版本化和TTL |
| Embedding | 适合 | Key含模型和规范化文本Hash |
| 文档解析结果 | 适合 | Key含文件Hash和解析器版本 |
| Rerank结果 | 条件适合 | Key含候选集、模型和ACL |
| 用户私有RAG答案 | 谨慎 | 严格权限Key与删除同步 |
| 账户余额/库存 | 不适合普通AI缓存 | 走实时Tool |
| Tool写操作结果 | 不是普通缓存 | 使用幂等记录 |
二十二、日志怎样既可排查又不泄密
推荐记录:
requestId, traceId, tenantScopeHash, userScopeHash,
scene, riskLevel, routeVersion,
provider, model, promptVersion, advisorVersion,
ragIndexVersion, retrievedChunkIds,
toolNames, toolCallIds, toolResultCodes,
inputTokens, outputTokens, estimatedCost,
queueMs, retrieveMs, rerankMs, ttftMs, totalMs,
finishReason, responseValidationResult,
safetyResult, refusalReason, errorCode不记录:API Key、密码、完整身份证/病历、完整 Prompt、完整 Tool 结果和可逆敏感映射。
需要保留原文用于质量复现时,放在访问受控、加密、有限保留期的评估/审计存储,不是普通应用日志。
二十三、Trace怎样拆Span
flowchart TD
A["ai.request"] --> B["auth.quota"]
A --> C["query.rewrite"]
A --> D["embedding"]
A --> E["vector.retrieve"]
A --> F["rerank"]
A --> G["chat.model"]
G --> H["tool.call"]
A --> I["output.validate"]Span 属性使用低敏感、低基数分类。requestId 在 Trace/日志中可查,但不要把 Prompt 正文放 Span Attribute。
二十四、核心指标
流量和容量
- requests/s、concurrent requests、queue depth。
- input/output Token/s。
- Provider/模型并发和配额剩余。
延迟
- queue、retrieval、rerank、TTFT、generation、tool、total P50/P95/P99。
错误
- 4xx、401/403、429、5xx、timeout、cancel、parse/schema、tool error。
- retry attempts、circuit state、fallback count。
质量和安全
- evaluation score、refusal accuracy、citation validity。
- ACL violation、prompt injection detected、sensitive output blocked。
成本
- Token/金额按tenant、scene、model、promptVersion聚合。
- 每成功业务任务成本,不只每请求成本。
指标 Label 不放 userId/requestId/conversationId。
二十五、Spring AI可观测性边界
Spring AI 可以接入 Spring Observability/Micrometer,帮助产生模型、Embedding、VectorStore 等 Observation。仍需确认:
- 当前版本默认启用哪些 Observation。
- Prompt/Completion是否默认不导出,避免误开启敏感内容。
- Provider Token Usage是否完整。
- 自定义 Advisor/Tool/Rerank是否有独立 Span。
- 采样后是否仍能保留事故请求。
框架指标不能替代业务质量指标。
二十六、健康检查怎样设计
不要让 Liveness 依赖外部模型:Provider 短暂故障会让所有 Pod 被重启,形成重启风暴。
Readiness 也要谨慎。如果全部实例因共享 Provider 不可用而 NotReady,入口没有任何后端。更常见做法:应用保持 Ready,AI 接口根据 Circuit/Provider状态返回受控降级,健康详情供告警而非直接摘除全部服务。
二十七、流式响应治理
事件格式示例:
event: meta
data: {"requestId":"...","model":"..."}
event: delta
data: {"text":"第一段"}
event: error
data: {"code":"MODEL_TIMEOUT","retryable":true}
event: done
data: {"finishReason":"STOP","totalTokens":1234}必须考虑:
- 客户端断开取消上游模型订阅。
- 已输出部分文本是否通过安全检查。
- Tool Call期间前端显示什么状态。
- Nginx
proxy_buffering、idle timeout 和心跳。 - 重连不能自动再次执行写 Tool。
- done 未到达时如何结算和记录 partial response。
二十八、异步任务表
CREATE TABLE ai_job (
job_id VARCHAR(64) PRIMARY KEY,
request_id VARCHAR(64) NOT NULL UNIQUE,
tenant_id VARCHAR(64) NOT NULL,
scene VARCHAR(64) NOT NULL,
job_status VARCHAR(32) NOT NULL,
lease_owner VARCHAR(128),
lease_until TIMESTAMP,
retry_count INT NOT NULL DEFAULT 0,
route_version VARCHAR(64) NOT NULL,
input_ref VARCHAR(500) NOT NULL,
output_ref VARCHAR(500),
error_code VARCHAR(128),
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);大文本不直接塞任务行;使用受控对象存储引用,带权限、加密和生命周期。
二十九、离线评估与上线门槛
flowchart TD
A["冻结候选版本矩阵"] --> B["运行普通、边界、拒答、权限和攻击集"]
B --> C["按场景/风险分组评分"]
C --> D["比较当前生产基线"]
D --> E{"质量、安全、延迟、成本是否达标"}
E -->|"否"| F["拒绝发布并归因"]
E -->|"是"| G["小流量Shadow/Canary"]
G --> H["线上质量和安全门禁"]
H --> I["扩大流量或回滚"]不能只看平均分:普通问题提高 2%,但越权样例从 0 变 1 次,应阻止上线。
三十、Shadow和Canary区别
Shadow
复制真实请求给候选版本,但结果不返回用户。要确保数据合规和成本可控,写 Tool 必须禁用或模拟。
Canary
少量真实流量使用候选版本并返回结果。按 tenant/用户稳定分流,避免同一会话版本漂移。
三十一、回滚单位是什么
回滚不能只改模型名。应回滚版本矩阵指针:
modelRouteVersion
promptVersion
advisorVersion
ragIndexVersion
retrievalConfigVersion
toolSchemaVersion
safetyPolicyVersionTool 已产生的写操作、用户已看到的错误答案和外部通知不能通过配置回滚自动撤销,需要补偿和事故处置。
三十二、成本核算
单请求成本近似:
Cost = inputTokens * inputUnitPrice
+ outputTokens * outputUnitPrice
+ embeddingTokens * embeddingPrice
+ rerankRequests * rerankPrice
+ tool/搜索/向量库资源
+ 计算与网络成本更有意义指标:
- 每个成功抽取文档成本。
- 每个自助解决客服问题成本。
- 每个有效 Tool 任务成本。
- 每个正确高风险回答成本。
低成本但错误率高不是优化。
三十三、成本突增常见原因
- 输入历史无限增长。
- RAG TopK/Chunk突然变大。
- 输出 maxTokens 失控。
- 重试导致重复生成。
- Agent循环终止条件失效。
- Tool结果过大反复回传。
- 新模型价格或Tokenizer不同。
- 恶意用户/Key泄露。
- 评估/批任务误走生产高价路由。
三十四、成本预算代码结构
public record AiBudget(
int maxInputTokens,
int maxOutputTokens,
int maxToolCalls,
Duration deadline,
BigDecimal maxEstimatedCost) {
}
public record AiRequestContext(
String requestId,
String tenantId,
String userId,
String scene,
String riskLevel,
AiBudget budget) {
}JDK 8 使用普通不可变类。Budget 应由服务端场景策略生成,不接受前端任意增大。
三十五、受治理调用Service骨架
@Service
public class GovernedChatService {
private final ChatClient chatClient;
private final AiQuotaService quotaService;
private final AiPolicyService policyService;
private final AiAuditService auditService;
public GovernedChatService(
ChatClient chatClient,
AiQuotaService quotaService,
AiPolicyService policyService,
AiAuditService auditService) {
this.chatClient = chatClient;
this.quotaService = quotaService;
this.policyService = policyService;
this.auditService = auditService;
}
public AiAnswer answer(
AiRequestContext context,
String userInput) {
long startNanos = System.nanoTime();
policyService.validateInput(context, userInput);
TokenEstimate estimate = policyService.estimateTokens(
context,
userInput);
QuotaReservation reservation = quotaService.reserve(
context,
estimate);
try {
ChatResponse response = chatClient.prompt()
.user(userInput)
.options(policyService.chatOptions(context))
.call()
.chatResponse();
ValidatedAnswer validated = policyService
.validateResponse(context, response);
Usage usage = Usage.from(response, estimate);
quotaService.settle(reservation, usage);
auditService.success(
context,
usage,
elapsedMillis(startNanos),
validated.auditMetadata());
return AiAnswer.from(validated);
} catch (RuntimeException ex) {
quotaService.markUncertain(reservation);
auditService.failure(
context,
classify(ex),
elapsedMillis(startNanos));
throw ex;
}
}
}markUncertain 是因为超时后 Provider 可能已计费;后续账单/Usage 对账再结算。代码中的 Usage.from 等是项目治理抽象,需要按当前 Spring AI Metadata API 实现。
三十六、异常不能统一catch后返回一句繁忙
至少分类业务码:
AI_INPUT_REJECTED
AI_QUOTA_EXCEEDED
AI_PROVIDER_UNAUTHORIZED
AI_PROVIDER_RATE_LIMITED
AI_PROVIDER_TIMEOUT
AI_PROVIDER_UNAVAILABLE
AI_OUTPUT_INVALID
AI_RAG_INSUFFICIENT
AI_RAG_ACCESS_DENIED
AI_TOOL_CONFIRMATION_REQUIRED
AI_TOOL_FAILED
AI_SAFETY_BLOCKED前端可以据此显示重试、补充资料、确认操作或联系管理员。日志保存内部根因,响应不暴露 Key、Provider敏感信息和系统 Prompt。
三十七、事故一:回答质量突然下降
flowchart TD
A["确认下降开始时间和场景"] --> B["对齐发布/配置/Provider变更"]
B --> C["提取失败样例和完整版本矩阵"]
C --> D["拆检索、Prompt、模型、Tool和后处理"]
D --> E["用生产基线重放相同样例"]
E --> F{"能否复现并定位"}
F -->|"能"| G["回滚对应版本指针"]
F -->|"不能"| H["检查Provider漂移、数据和随机性"]
G --> I["补回归样例和复盘"]
H --> I不要只说“模型偶发”。先比较 Prompt Hash、RAG Index、Embedding、Model Revision、Options和Tool结果。
三十八、事故二:延迟和429同时升高
检查:
- 入站请求/Token速率和并发。
- 本地队列等待和Bulkhead占用。
- Provider Retry-After和配额。
- 重试流量占比。
- 上下文/输出Token变化。
- 是否新模型吞吐更低。
- RAG/Rerank是否变慢导致请求重叠更多。
立即动作:限制新流量、关闭非关键重试、缩短输出、切合格备用路由、暂停批任务。单纯扩容应用 Pod 不能增加第三方 Provider 配额。
三十九、事故三:成本突然升高
按 tenant、user、scene、model、promptVersion、routeVersion 分解:
请求数增加?
每请求输入Token增加?
输出Token增加?
重试/Agent循环增加?
模型单价变化?
缓存命中下降?
Key是否泄露?先止损:收紧配额、禁用异常租户/Key、暂停低优先级任务、回滚版本。不要直接清空全部日志,证据用于账单申诉和安全调查。
四十、事故四:RAG越权泄露
- 立即停止相关场景/索引流量。
- 保存授权快照、Filter、Chunk ID、缓存 Key、Prompt发送范围。
- 判断是否发送到第三方模型、日志或评估平台。
- 修复 metadata/Filter/缓存 ACL。
- 清理泄露数据和受影响缓存,轮换必要凭据。
- 通知安全/合规和受影响方,按制度处置。
- 将案例加入权限硬门槛评估集。
四十一、事故五:Tool执行了错误写操作
检查 toolCallId、requestId、操作者、权限决策、Schema、确认记录、下游幂等结果和事务状态。
先暂停该写 Tool,不暂停全部只读 AI 能力;按业务补偿或人工纠正,不能只删除聊天消息。修复后增加参数边界、二次确认和回归样例。
四十二、事故六:流式响应大量中断
分层检查:
- 浏览器网络和主动取消。
- CDN/Ingress/Nginx SSE 缓冲、空闲超时。
- 应用连接池和事件循环是否阻塞。
- Provider流式连接和模型队列。
- 安全/Advisor是否把Flux聚合成完整响应。
- 心跳是否存在。
- 取消是否真正传播到上游。
四十三、上线检查清单
版本和质量
- [ ] 记录完整版本矩阵和内容Hash。
- [ ] 离线评估按场景/风险达标。
- [ ] 安全、权限、格式为硬门槛。
- [ ] Shadow/Canary与回滚指针已演练。
可靠性
- [ ] 每阶段有超时和总Deadline。
- [ ] 重试白名单、退避、抖动和预算明确。
- [ ] Bulkhead、队列、Circuit和Fallback通过压测。
- [ ] SSE取消、心跳和中途错误测试完成。
- [ ] 异步任务租约、幂等、取消和对账完成。
安全
- [ ] API Key在Secret系统并可轮换。
- [ ] RAG ACL在检索前下推。
- [ ] Tool参数、权限、确认和幂等完整。
- [ ] Prompt/Tool日志脱敏、加密和保留期明确。
- [ ] Prompt Injection和敏感输出测试通过。
成本和观测
- [ ] 用户/租户/场景请求、并发、Token和金额配额。
- [ ] Usage估算、结算和账单对账。
- [ ] 分段Trace、低基数指标和质量反馈。
- [ ] 成本、429、质量和安全告警有负责人/Runbook。
四十四、常见误区
| 误区 | 准确结论 | 后果 |
|---|---|---|
| HTTP 200就是成功 | 还要质量、格式、安全、引用和业务结果 | 假健康 |
| 只限QPS即可 | 慢请求、Token和并发仍可打满资源 | 成本/线程耗尽 |
| 超时都重试 | 读取超时可能已生成/执行 | 重复计费/写操作 |
| 扩Pod能解决429 | Provider配额不随Pod增加 | 重试雪崩 |
| 小模型可无损降级 | 能力、Tool、JSON、质量不同 | 隐性错误 |
| defaultSystem能保证安全 | Prompt不是权限边界 | 越权/注入 |
| 流式会降低总计算成本 | 主要改善TTFT | 成本误判 |
| 缓存只用问题Hash | 忽略租户、ACL和版本 | 数据泄露/旧答 |
| 只记录Prompt版本可复现 | 回答由完整版本矩阵决定 | 无法复盘 |
| 平均评估分高就上线 | 高风险退化会被平均掩盖 | 安全事故 |
| 回滚配置能撤销Tool副作用 | 外部写操作需补偿 | 业务事实错误 |
四十五、面试标准回答
Spring AI从Demo到生产要补什么
要先按场景分风险,补认证、租户和RAG/Tool数据权限;按用户/租户做请求、并发、Token和金额配额;为检索、模型和工具拆超时预算,配置有限重试、退避抖动、Bulkhead、Circuit和能力兼容降级;对输出做Schema、业务、引用和安全校验;记录完整版本矩阵、Token、分段耗时和质量指标,并用评估集、灰度、回滚、成本告警和事故Runbook闭环。
AI接口怎样设计超时和重试
先设置请求总Deadline,再给鉴权、检索、Rerank、模型和Tool分配子预算,外层超时大于内部总预算但不能无限等待。只对白名单瞬时错误有限重试,429尊重Retry-After,使用指数退避和抖动,并限制总重试流量。401、参数错误不重试;读取超时和Tool写操作结果可能未知,应先按幂等键确认。
为什么AI限流还要看Token和并发
AI请求耗时长且输入输出大小差异巨大。只限QPS时,少量超长请求仍会占满并发和成本。生产要同时限制请求速率、在途并发、单次/周期Token和金额预算,先预留后按实际Usage结算,并对未结算Reservation做过期与账单对账。
模型降级要注意什么
备用模型必须满足场景能力和已评估门槛,例如Tool Schema、结构化输出、多模态和安全策略不能缺失。高风险医疗/支付场景宁可拒答或转人工,也不应降级到未验证模型。路由和回滚记录模型、Prompt、RAG、Tool和Safety完整版本矩阵。
AI质量下降怎样排查
先确定开始时间、场景和失败样例,对齐模型路由、Prompt、Advisor、Embedding、RAG索引、检索参数、Tool和Safety变更;用相同版本矩阵重放,拆分检索错、上下文错、模型生成错和后处理错。能定位就回滚对应版本指针,不能复现再查Provider漂移、数据变化和随机参数,并把事故样例加入回归集。
四十六、学习实验与验收
- 为知识问答定义技术、质量、安全和成本 SLO。
- 给 12 秒请求拆分阶段预算,故意让 Rerank超时验证总Deadline。
- 同时压测请求QPS、并发和超长Token,比较四类限额。
- 模拟 401、429、500、连接超时和读取超时,验证重试分类。
- 让100个请求同时429,验证退避抖动不会同步重试。
- 配置两个模型路由,验证备用模型结构化输出/Tool能力门槛。
- 建立包含tenant、ACL、Prompt和index版本的缓存Key,证明不同用户不串答。
- 中断 SSE 客户端,验证上游取消和成本记录。
- 执行 Shadow/Canary,按风险分组比较生产基线。
- 演练质量下降、成本突增、RAG越权和Tool误执行事故。
验收时必须能回答:
- 为什么HTTP可用性不能代表AI业务可用性?
- TTFT和总耗时分别受什么影响?
- 为什么QPS、并发、Token和金额都要限制?
- 哪些错误能重试,为什么读取超时结果未知?
- Bulkhead、Circuit、Queue和Fallback分别解决什么?
- 模型路由为什么需要能力和质量门槛?
- AI缓存Key为什么必须包含ACL和版本?
- 一次答案需要记录哪些版本才能复现?
- Shadow和Canary怎样避免Tool副作用?
- AI安全事故为什么不能只删除聊天记录?
