AIManage 已具备可运行的持久化和 Provider 调用骨架,但 AI 请求同时跨越身份、Secret、外部网络、不可确定输出和计费边界。商业 GA 的证据强度必须高于普通 CRUD 模块。
1. 当前自动化证据
| 测试面 | 已覆盖 |
|---|---|
| Persistence | 工作区、Provider、模型、对话、消息、技能的核心 Store 行为 |
| Tenant | Provider 查询与默认项的租户范围 |
| Client factory | Provider/Model/Key 变化产生不同缓存项,Builder 被调用 |
| RAG / Agent | Adapter 参数转发和返回映射 |
| Architecture | Infrastructure ORM 中立、失败关闭注册、非对称子表、应用层 Id/Clock 规则 |
| Integration | AIManage Store 在 SqlSugar / EF Core 的 parity 场景 |
| Host smoke | 未配置 Provider 时解析为 Unavailable adapter |
| 所有权 | 跨租户/非所有者 ConversationId 返回 ConversationNotOwner |
| 幂等 | IAIMessageRequestCoordinator 的 claim/replay/conflict/failed 状态 |
| 流式 | NDJSON kind 帧、预取 403 映射、终态错误帧、取消行为 |
没有覆盖:真实 Provider、首字节、取消与 Provider 取消的竞态、密钥损坏/轮换、文件技能权限、内容安全、容量、SLO 和恢复。
2. 最小回归组合
dotnet test tests/BitzOrcas.Application.Tests/BitzOrcas.Application.Tests.csproj \ --filter "FullyQualifiedName~AIManage|FullyQualifiedName~SemanticKernelChatClientFactory|FullyQualifiedName~RagServiceAdapter|FullyQualifiedName~AgentServiceAdapter"
dotnet test tests/BitzOrcas.Architecture.Tests/BitzOrcas.Architecture.Tests.csproj \ --filter "FullyQualifiedName~AIManage"
# 需要 Docker 与两种真实 ORM adapter。dotnet test tests/BitzOrcas.Integration.Tests/BitzOrcas.Integration.Tests.csproj \ --filter "FullyQualifiedName~AIManageStores_Should_Behave_The_Same_On_SqlSugar_And_EfCore"候选版本还必须在隔离的 Provider stub 上跑 HTTP contract test,固定认证、Problem Details、NDJSON schema、超时、取消、usage、错误脱敏和幂等行为。
3. 威胁与授权矩阵
| 场景 | 当前行为 | GA 预期 |
|---|---|---|
| 租户 A 猜到租户 B ConversationId | ConversationNotOwner(Forbidden) | 已强制;持续测试 |
| 同租户用户读取他人对话 | Send/Stream/History 绑定租户+用户 | 已强制;持续测试 |
| 重复 RequestId in-flight | AI.Message.RequestInProgress(Conflict) | 已强制;持续测试 |
| 任意认证用户 reload 文件技能 | 当前允许 | skill.manage/ops 权限 |
| Provider Endpoint 指向内网 | 无显式 allowlist | SSRF 策略与出站代理 |
| Data Protection key 丢失 | 密文原样当 Key | 失败关闭并告警 |
| Prompt 含 Secret/PII | 原样外发 | 分类、阻断/脱敏、审计 |
| 模型返回工具调用 | 当前不执行 | 将来逐次授权与确认 |
| 任意 ModelId | 当前透传 | 受管模型 allowlist |
4. 幂等与一致性
// 两个并发请求携带完全相同的稳定 RequestId。var command = new SendMessage.Command(conversationId, "turn-42", content, modelId);
var results = await Task.WhenAll( sender.Send(command, cancellationToken).AsTask(), sender.Send(command, cancellationToken).AsTask());
results.ShouldAllBe(result => result.IsSuccess);// 同时固定外部计费边界和最终持久化结果。await provider.Received(1).CompleteAsync(Arg.Any<ChatRequest>(), Arg.Any<CancellationToken>());await store.ShouldContainOneUserAndOneAssistantAsync("turn-42", cancellationToken);RequestId 必填,IAIMessageRequestCoordinator 的 claim 状态机已强制执行。还要覆盖 Provider 成功/保存 Assistant 失败、用户消息保存后进程崩溃、流式部分保存、重复完成回调(重放不调 Provider)、消息计数并发、双默认 Provider、缓存构建竞争和账本结算重放。
5. Provider 故障注入
至少模拟:DNS 失败、TLS/证书失败、401/403、404 模型不存在、429+Retry-After、连接超时、首字节超时、分块中断、无效 UTF-8/超大帧、usage 缺失、空响应、内容过滤结束、慢流、连接池耗尽和 Provider 恢复。
错误映射不得返回 Endpoint、ApiKey、供应商原始 body 或堆栈。是否重试由错误类别、幂等 attempt 和预算共同决定;429 不能无限重试,401 不应重试,流式已产生内容后不能偷偷切 Provider 拼接答案。
6. 流式验收
真实测试必须测时间而非只枚举结果:Provider stub 在 t=0/200/400ms 发三块,客户端应在约定阈值内分别收到,而不是 400ms 后一次收到。断开测试要断言数据库状态、Provider cancellation、无后台泄漏和客户端可恢复合同。
代理层同时验证 Kestrel、IIS/Nginx/Ingress 的 buffering、compression、idle timeout 和最大连接时长。X-Accel-Buffering: no 只对部分代理有效,不能代替部署验证。
7. 容量与成本基线
矩阵至少包括 1/10/50/500 条历史、1KB/32KB 输入、1KB/100KB/1MB 输出、1/10/100 并发流、1/8/32/64 个 Provider+Model 缓存键,以及长慢流。记录 P50/P95/P99、first byte、full duration、分配、GC、活动连接、Provider QPS、Token、费用和数据库 round trips。
预算门禁应覆盖租户日/月上限、用户/工作区速率、单轮最大输入输出、并发上限、异常消费突增和账单对账差异。
8. 指标与日志
建议最少指标:
ai_request_total/failed/denied,按 workspace/provider/model/error code;ai_first_byte_duration、ai_completion_duration、ai_stream_gap;ai_input_tokens/output_tokens/cost与 budget denied;ai_provider_status、429、timeout、circuit state;ai_client_cache_hit/build/evict/size;ai_partial_response、cancel、orphan turn、reconcile lag;ai_skill_reload、parse failure、unauthorized;ai_rag_search_duration/results/denied与 index lag。
日志可包含 TenantId、WorkspaceId、ConversationId、TurnId、ProviderId、ModelId、ErrorCode、CorrelationId;不得包含 ApiKey、Prompt、SystemPrompt、模型正文、检索片段或未经分类的文件名。需要内容级取证时走受控审计存储,不走普通日志。
9. 密钥与恢复演练
备份必须涵盖四个聚合/子表数据、消息、用量账本(落地后)和 Data Protection key ring。恢复演练验证存量 Provider 可解密、旧/新 key 均可用、错误 key ring 会红灯而不是把密文外发、对话计数与消息一致、失败 attempt 可调和、索引/外部向量库可重建。
10. 建议 SLO
把平台处理与 Provider 质量分开。例如:99.9% 受理决策在 100ms 内完成;在 Provider SLA 范围内,99% 流式请求在 2s 内收到首帧;99.9% 已完成 turn 的消息与用量账本在 1 分钟内一致。SLO 必须按模型、区域和响应大小分层,不能用一个平均值掩盖慢模型。
11. GA 阻断项
已交付:对话所有权(绑定租户 + 用户)、幂等 turn(RequestId + IAIMessageRequestCoordinator)、带类型化 kind 帧的增量流式、稳定不泄漏的流式错误合同。剩余阻断项:
- 对话可引用不存在/停用工作区;
- 任意 ModelId 与固定参数绕过模型目录;
- 凭据解密失败仍回退原字符串,key ring 未形成部署契约;
- Provider 默认项、模型归属和缓存并发未收敛;
- 文件技能端点只有认证,技能/RAG/Agent 被过度宣传;
- 数据分类、脱敏、内容安全、Prompt Injection 与保留策略缺失;
- Token、成本、预算、配额和对账缺失;
- Provider 健康、超时、熔断、可审计 fallback 和告警缺失;
- HTTP、安全、Provider contract、流式时序、容量和恢复证据缺失。
12. 分阶段落地
P0:工作区校验、模型 allowlist、密钥失败关闭。P1:用量账本、预算、内容安全、Provider resilience、文件技能权限、观测与恢复。P2:在逐次授权和审计完成后接入 RAG 与工具;多 Agent、复杂编排和 Provider marketplace 属于后续产品能力。
# 高风险现状应持续可见。rg -n "GetConversationAsync\(|CancellationToken.None|return cipherKey|DisableRequestTimeout" \ src/Platform/AIManage src/Hosts/BitzOrcas.Api -g '*.cs'
# 省略、待办和临时实现不应进入候选版本。rg -n "TODO|FIXME|// \.\.\.|省略" src/Platform/AIManage tests -g '*.cs'