Skip to content
bitzorcas
中EN

Reference

AIManage 工作区、Provider 与模型

深入解释 AI 工作区聚合、Provider 凭据加密、单默认项、模型配置、租户归属、缓存失效与生产配置边界。

Last updated

工作区是 AIManage 的租户内配置边界:它聚合名称、状态、默认模型和系统提示词;Provider 是带密钥的非对称子表;AIModel 是独立统一聚合。三者目前都能持久化,但只有工作区和默认 Provider 真正进入聊天执行路径。

1. 数据模型与职责

TenantId + WorkspaceIdTenantId + ProviderId

AIWorkspace
Id · TenantId · Name · IsActive
DefaultModelId · SystemPrompt

AIProviderCredentialRecord
Id · TenantId · WorkspaceId
Endpoint · EncryptedApiKey · IsDefault

AIModel
Id · TenantId · ProviderId
ModelIdentifier · MaxTokens · Temperature

Provider 与 Message 使用 *Record 命名,因为它们是聚合跨表的真实非对称子项,不是旧式 1:1 Entity 镜像。基础设施只依赖 IEntitySet<T>,由统一 ORM 元数据生成器向 SqlSugar 与 EF Core 投影。

2. 创建工作区

创建租户 AI 工作区
var result = await mediator.Send(new ManageWorkspace.CreateCommand(
Name: "客服知识助手",
Description: "只用于已审核知识库问答",
DefaultModelId: "gpt-4o-mini",
SystemPrompt: "回答必须引用资料;没有依据时明确说明。"), cancellationToken);
// Handler 从 ICurrentUser 获取 TenantId,调用 AIWorkspace.Create,
// 最终 Id 由持久化适配器分配,而不是应用层自行生成。
return result.Value;

当前只拒绝空白名称。名称长度、租户内唯一、SystemPrompt 大小、内容分类和 DefaultModelId 是否存在都没有在 Handler 中验证。AIWorkspace.Create 的领域约束仍应以源码为准,但 HTTP 契约没有完整的验证错误矩阵。

3. 添加 Provider 与单默认项

添加默认 Provider
POST /api/ai/workspaces/ws-001/providers
Content-Type: application/json
{
"providerType": "OpenAICompatible",
"name": "production-east",
"endpoint": "https://ai-gateway.example.com/v1",
"apiKey": "<secret-from-vault>",
"isDefault": true
}

Store 先用 workspaceId + current TenantId 验证工作区,再生成 Provider Id。若新项为默认项,它读取同工作区其他默认项并逐个取消,然后写入新项。同一工作单元内 pending Provider 也会被同步处理。

这保证了普通串行用例的“只有一个默认项”,但数据库元数据只有 (TenantId, WorkspaceId) 普通索引,没有过滤唯一约束。两个并发事务仍可能都观察不到对方并各自写入默认项。GA 需要数据库可表达的唯一策略、串行化更新或租户工作区锁,并把冲突映射为稳定错误。

4. 凭据加密的真实语义

ApiKeyCipher 使用 ASP.NET Core Data Protection,purpose 为 AIManage.ApiKey。保存时 Protect,读取 Provider 时 Unprotect,列表 DTO 不返回密钥。

生产环境 Data Protection 配置方向
// 示例展示部署要求,不表示仓库已经完成该 Host 配置。
services.AddDataProtection()
// 多实例必须共享并持久化 key ring;具体存储由部署平台决定。
.SetApplicationName("BitzOrcas.Modern");

当前 Decrypt 捕获所有异常并原样返回输入,用于兼容历史明文。这意味着 key ring 丢失、密文损坏或 purpose 不匹配时,密文可能被当作真实 API Key 发送给 Provider。生产应采用带版本/前缀的密文格式:明确识别历史明文,只迁移一次;已标记密文解密失败必须失败关闭并告警。

还要固定以下部署契约:

  • key ring 跨重启和多实例共享;
  • key 加密密钥来自 KMS/Key Vault,而非发布目录;
  • 轮换前后均能解密存量凭据;
  • 备份恢复同时恢复 key ring;
  • 日志、异常、配置快照和诊断转储不得包含 ApiKey。

5. 模型配置与执行脱节

SaveModelAsync 会从 Provider 继承 TenantId,保存 ModelIdentifier、DisplayName、ModelType、MaxTokens 与 Temperature。值得注意的是外部 AIModelInfo 不携带 TenantId,Store 查 Provider 时只按 ProviderId;如果 Provider Id 不是全局唯一或该端口被错误暴露,租户边界不够自解释。

更重要的是 Send/Stream 根本不读取 AIModel。调用方可直接给任意 ModelId,执行选项固定为 4096/0.7。因此配置模型不会自动改变推理参数。

GA 目标:解析受管模型配置
// 这是目标用例,不是当前源码逐字复制。
var resolved = await modelPolicy.ResolveAsync(
tenantId,
workspaceId,
requestedModelId,
cancellationToken);
// 解析结果同时固定 Provider、模型标识和允许的执行上限。
var options = new ChatOptions
{
ModelId = resolved.ModelIdentifier,
MaxOutputTokens = Math.Min(requestedMaxTokens, resolved.MaxTokens),
Temperature = resolved.Temperature
};

6. 系统提示词优先级

创建对话时可传 SystemPrompt。未传时才复制工作区当前 SystemPrompt;发送时又使用 conversation.SystemPrompt ?? workspace.SystemPrompt。因此:显式对话 Prompt 是快照;空值对话会随工作区 Prompt 后续变化。需要在产品契约中明确“快照”还是“动态继承”,并保存 Prompt 版本/哈希,才能重放和审计历史结果。

7. 缓存与密钥变更

聊天客户端缓存键包含 ProviderId、ModelId,以及 Endpoint/ModelId/ApiKey 的 SHA-256 前 16 个十六进制字符;CacheEntry 还保存原始 ApiKey 以核对碰撞。密钥或端点变化会生成新键,但旧客户端只能等待 LRU 驱逐或工厂 Dispose。

并发创建相同键时,两个线程都可能在锁外构建客户端;后进入锁的线程返回已有实例,却没有 Dispose 自己新建但未使用的实例。这是资源泄漏窗口。缓存也没有显式凭据轮换通知、按 Provider 失效、构建失败退避或命中率指标。

8. 配置审查清单

主题当前事实GA 要求
Workspace Name仅非空长度、唯一、字符策略
Provider EndpointDTO 接收字符串HTTPS/allowlist/SSRF 策略
ApiKeyData Protectionkey ring、版本迁移、失败关闭
默认 Provider应用层取消旧默认并发唯一与冲突映射
ModelId任意请求字符串受管目录与权限/配额
参数固定 4096 / 0.7读取 AIModel 并设上下限
SystemPrompt工作区/对话字符串大小、版本、分类、审计

9. 测试重点

现有持久化测试覆盖租户 Provider 列表、默认项切换、模型保存和工作区归档;双 ORM parity 覆盖核心 Store 行为。仍需补:并发默认项、跨租户 ProviderId、密文损坏、key ring 丢失与轮换、Endpoint SSRF、缓存并发泄漏、模型覆盖拒绝、Prompt 版本和真实 Provider contract test。

10. 故障处置原则

Provider 密文无法解密、Endpoint 不受信任或模型不在目录时必须失败关闭,不能自动切换到另一个租户 Provider。缓存构建失败应返回可诊断的稳定错误并限制重试,且日志只记录 Provider/Model 标识与关联 ID,不记录 ApiKey、完整 Prompt 或用户内容。

AIManage 总览 · 对话与安全 · 测试与 GA

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%