跳转到内容

AI 能力

BasicApp 的 AI 能力落在独立模块 XiHan.BasicApp.AI 里,与代码生成模块同构(独立工程、独立种子段、独立权限资源)。它坐落在框架薄层 XiHan.Framework.AI 之上:框架提供 provider 解析、会话/嵌入门面、RAG 切片检索、提示词模板、Agent 与 MCP 桥接的抽象与默认实现;BasicApp 在其上做三件业务落地——把 AI Provider 配置库化管理(存库 + 加密 + 前端可维护 + 热切换),把 RAG 知识库接上真实向量库(Qdrant)并暴露为文档管理 / 检索问答 / MCP 工具,以及把提示词模板库化(覆盖框架默认 Options 实现,前端可维护)。

模块只依赖 XiHanBasicAppSaasModule(复用 RBAC 表、SaasRepository、DataProtection 密文前缀),框架 AI 经 Saas → Core 传递进来。

三大能力

能力做什么关键实体 / 服务权限资源
Provider 库化管理AI 服务商配置存库、加密密钥、CRUD、设默认、测连接、改配置即时热切换SysAiProviderAiProviderAppServiceSaasAiProviderConfigStoreai
RAG 知识库文档摄取(切片 → 嵌入 → 写 Qdrant)、重建索引、语义检索、注入 prompt 生成带引用答案SysKnowledgeDocumentKnowledgeDocumentAppServiceKnowledgeQueryAppServiceknowledge_base
提示词库提示词模板存库、CRUD、按编码/版本取用,覆盖框架默认提示词库SysAiPromptAiPromptAppServiceSaasAiPromptStoreai_prompt

此外还有一条Agent / MCP 支线:知识检索被封装为技能(KnowledgeRetrieveSkill),框架技能注册表自动收纳,进而投影为对话工具与 MCP tools,供外部 AI 客户端(Claude Code / Cursor 等)调用。

模块前端页面挂在「开发工具(develop)」目录下:AI 提供商/develop/aiProvider)、知识库/develop/knowledge)与AI 提示词/develop/aiPrompt),三者均仅对持有 ai:read / knowledge_base:read / ai_prompt:read 者(默认=超管通配 *)可见。


Provider 库化管理

为什么存库而不写 appsettings

框架默认的 provider 配置源 OptionsAiProviderConfigStoreXiHan:AI 配置节兜底。BasicApp 把它替换为数据库实现 SaasAiProviderConfigStore,好处是:配置可在前端维护、天然按租户隔离、密钥加密落库、且改完能不重启热切换。

SysAiProvider(表 Sys_Ai_Provider)关键字段:

字段说明
ConfigCodeConfig_Code配置编码,租户内唯一UX_TeId_CoCd,含 IsDeleted 末列,软删后可复用),上层解析器/配置源以它为 provider 键;创建后不可变
ConfigNameConfig_Name配置名称(展示)
ProviderProvider提供商/协议标识,自由字符串OpenAI/DeepSeek/Azure/vLLM/Custom 等),仅作分组与展示,不是枚举
ModelModel会话模型名
EmbeddingModelEmbedding_Model嵌入模型名(RAG 用;与会话同端点同密钥、仅模型 id 不同,如 text-embedding-3-small;空则该 provider 不支持嵌入)
BaseUrlBase_Url端点地址(空则用提供商默认端点,如 OpenAI 官方)
ApiKeyApi_KeyAPI 密钥,可逆加密落库,双 [JsonIgnore] 防外泄
MaxOutputTokens / Temperature / TimeoutSeconds / ExtraJson同名列采样与扩展参数,ExtraJson 免改表结构扩展
IsDefaultIs_Default是否默认 provider(租户内单默认,软约束)
IsEnabled / StatusIs_Enabled / Status运行开关与状态;禁用或非 Enabled 均不参与配置源解析

一个适配器打天下

OpenAI / DeepSeek / Azure / vLLM / Ollama / 自训模型多数走 OpenAI 兼容协议——BaseUrl 指向对应端点、填 ModelApiKey 即可,无须每家写一个适配器。Provider 字段只是分组标签,真正决定行为的是 BaseUrl + Model

密钥加密

ApiKeyDataProtectionAiProviderSecretProtector 用 ASP.NET Core Data Protection 加密,密文带统一前缀,使用独立 Purpose(与存储密钥/短信密钥/邮件密码/租户连接串互不影响)。要点:

  • 写入:创建时明文提交、服务端加密;更新时 ApiKey 留空表示保留原密钥,非空才替换重加密。
  • 读出:列表/详情 DTO 永不回读密钥——只暴露布尔标志 HasApiKey[JsonIgnore] 双注解在实体层再兜一道底。
  • 解密:只解本保护器写入的密文,去前缀直接解密,失败即抛(fail-closed,不做旧明文兼容)。
  • 多实例部署须共享 Data Protection 密钥环(持久化到共享存储),否则其它实例无法解密。

配置源解析

SaasAiProviderConfigStore(Singleton,经 IServiceScopeFactory 开作用域取 Scoped 仓储)实现框架的 IAiProviderConfigStore

  • GetAsync(null):取默认且启用行(IsDefault && IsEnabled && Status==Enabled,按 Sort 取首条)。
  • GetAsync(configCode):按 ConfigCode 取启用行;无匹配返回 null(框架按「未配置」处理)。
  • GetAllAsync:枚举全部启用行;单行解密失败跳过并告警,不阻断全表、不回退明文(fail-closed)。
  • 映射为框架 AiProviderOptions 时,Provider 键即 ConfigCode,供解析器按此缓存。配置源本身不做进程内缓存——缓存在解析器那层。

热切换(改配置即时生效)

框架解析器(IAiChatClientResolver / IAiEmbeddingGeneratorResolver)按 provider 名缓存构建好的 IChatClient / 嵌入生成器,不会自动感知 DB 变更。所以 AiProviderAppService每个写操作后都调 InvalidateResolvers()

csharp
private void InvalidateResolvers()
{
    _chatClientResolver.Invalidate();      // 清空会话客户端缓存
    _embeddingResolver.Invalidate();       // 清空嵌入生成器缓存
}

Create / Update / UpdateStatus / SetDefault / Delete 全部在返回前 Invalidate——下次解析按最新 DB 配置重建,运维改完 key/baseUrl/model 无须重启即生效。这是「库化管理 + 配置热切换」的闭环关键。

应用服务动作

AiProviderAppService[DynamicApi],无 Controller)暴露的动作:

动作权限码事务说明
CreateAsyncai:create[UnitOfWork]新增配置;ConfigCode 不能含空白、租户内不重复
UpdateAsyncai:update[UnitOfWork]更新;ConfigCode 不可变,ApiKey 留空保留原值
UpdateStatusAsyncai:update[UnitOfWork]启停状态
SetDefaultAsyncai:update[UnitOfWork]设为默认(已禁用者不可设默认);领域服务清除其它行 IsDefault(单默认互斥)
DeleteAsyncai:delete[UnitOfWork]软删
TestConnectionAsyncai:execute[HttpPost];用一次性客户端(不入解析器缓存)向 provider 发一句 ping,返回成功/耗时/finishReason——可测尚未启用的配置

查询侧 AiProviderQueryServiceGetPageAsync[HttpPost]ai:read,FLS 门控排序/过滤 + 默认兜底排序)与 GetDetailAsync。列表/详情均走 HasApiKey 布尔而非明文。

采样温度限定 0~2,越界抛异常。


RAG 知识库

一条最小可用的 RAG:摄取(文档切片 → 嵌入 → 写向量库)与检索问答(query 嵌入 → 向量检索 → 注入 prompt → 会话生成带引用答案)。切片与向量落在 Qdrant,本模块的 SysKnowledgeDocument(表 Sys_Knowledge_Document)只存文档元信息与原文RawContent,用于免重传重建索引)。

向量库与嵌入约定(以框架为准)

RAG 的底层实现由框架 AddXiHanRAG() 提供(切片/摄取/检索/增强),BasicApp 负责登记具体向量连接器与嵌入模型选择。以下约定源自框架 VectorStoreKnowledgeRecord,是部署前必须对齐的硬约束:

约定说明
向量库Qdrant(当前 preview)BasicApp 经 AddQdrantVectorStore(...) 登记(gRPC),框架本身不引用 Qdrant
集合名xihan_knowledge固定常量
主键Guid(Qdrant 只支持 Guid/ulong 键)MD5(documentId:index) 确定性派生——同文档同序号恒等,重复摄取即覆盖(幂等)
嵌入模型取自 SysAiProvider.EmbeddingModel与会话同端点同密钥、仅模型 id 不同;空则该 provider 不支持嵌入
向量维度1536(编译期常量,硬编码)对齐 text-embedding-3-small。换用不同维度的嵌入模型必须改框架常量并重建集合,否则维度不匹配
距离 / 索引CosineSimilarity / Hnsw

换嵌入模型 = 改常量 + 重建集合

维度 1536 是框架编译期常量。若换到维度不同的嵌入模型(如某些 1024/3072 维模型),仅在前端改 EmbeddingModel 会导致 upsert/检索维度不匹配。这属于需改框架源码并重建 Qdrant 集合的动作,不是运行期配置项。拿不准的维度以仓库为准

文档摄取与索引

SysKnowledgeDocument 关键字段:TitleSourceTypePasteText / UploadFile,上传文件由前端读为文本后提交)、Source(文件名/标签,引用溯源用)、RawContent(原文)、ChunkCount(已入库切片数)、EmbeddingProviderCode(嵌入 provider 编码,空=默认 provider)、StatusPending / Indexed / Failed)、ErrorMessage

KnowledgeDocumentAppService 动作(均 [HttpPost] 除删除):

动作权限码说明
IngestAsyncknowledge_base:create摄取:落库 Pending → 切片嵌入写 Qdrant → 回写 Indexed/Failed
ReindexAsyncknowledge_base:update重建:先按当前 ChunkCount 清旧向量,再用 RawContent 重新索引
DeleteAsyncknowledge_base:delete先删向量再删文档

摄取不套 UnitOfWork

摄取/重建含外部 I/O(嵌入 API + 向量库网络调用),领域服务刻意不套 [UnitOfWork]:先落库 Pending、索引后独立提交 Indexed/Failed,避免网络 I/O 期间长事务。进程中断则停留 Pending,可重建恢复。

补偿清理(防孤儿向量)

摄取的一致性难点:向量已写进 Qdrant(N 条),但回写 ChunkCount 的 DB 更新失败——此后 DB 记 0、向量库有 N,后续按 ChunkCount(=0) 无法清理,孤儿向量持续污染检索。领域服务的处理:回写 DB 失败时,补偿清理刚写入的向量(RemoveDocumentAsync(documentId, writtenChunkCount))以保证 DB 与向量库一致;补偿本身再失败则落错误日志(可能残留孤儿,需人工介入)。删除/重建也据 ChunkCount 枚举全部确定性主键来清向量。

检索与问答

KnowledgeQueryAppService.QueryAsync[HttpPost]knowledge_base:execute):

  1. topK 收敛到 1~20(默认取 RAG 配置的 DefaultTopK)。
  2. 当前租户过滤检索(无租户上下文=平台全局 TenantId=0):IKnowledgeRetriever.RetrieveAsync(query, topK, filter, provider)
  3. 返回命中片段(Citations:文档 id / 序号 / 标题 / 来源 / 相似度 / 文本)。
  4. Answer=true 且有命中:IRagPromptAugmenter.Augment 把片段注入 prompt,走会话门面 IXiHanAiService.ChatAsync 生成带引用的答案Answer 字段)。

请求里的 Provider 编码同时用于嵌入与会话Answer=false 时只返回命中片段不调用大模型。

RAG 部署配置

向量库连接属部署级基础设施配置,走 appsettingsXiHan:AI:Rag 节(XiHanRagOptions):

字段默认说明
QdrantHostlocalhostQdrant 主机(gRPC)
QdrantPort6334gRPC 端口
QdrantHttpsfalseQdrant Cloud 用 true + QdrantApiKey
QdrantApiKeynull云端鉴权
DefaultTopK5检索默认返回条数
json
{
  "XiHan": {
    "AI": {
      "Rag": {
        "QdrantHost": "localhost",
        "QdrantPort": 6334,
        "QdrantHttps": false,
        "DefaultTopK": 5
      }
    }
  }
}

提示词库

独立于 Provider 与 RAG 的第三条能力:把框架 IAiPromptStore 抽象的默认 Options 实现替换为数据库实现,让「发给大模型的提示词模板」从代码/配置搬进库里维护。提示词非机密,无需加密。

SysAiPrompt(表 Sys_Ai_Prompt)关键字段:

字段说明
PromptCodePrompt_Code提示词编码,租户内唯一UX_TeId_PrCd,含 IsDeleted 末列,软删后可复用),上层按此取用;创建后不可变
PromptNamePrompt_Name提示词名称(展示)
CategoryCategory分组/分类(可选)
VersionVersion版本(可选;空代表当前/最新,按名 + 版本显式选取,无默认标记)
ContentContent提示词正文(可含占位/Scriban 变量)
IsEnabled / StatusIs_Enabled / Status运行开关与状态;禁用或非 Enabled 均不参与库解析

SaasAiPromptStore(Singleton,经 IServiceScopeFactory 开作用域取 Scoped 仓储)实现框架 IAiPromptStore

  • GetAsync(name, version):按 PromptCode(+可选 Version)取启用行,映射为框架 AiPromptTemplateName=PromptCodeContentVersionDescription=PromptName);无匹配返回 null
  • ListAsync:枚举全部启用行。

Replace 而非 TryAdd

框架 AddXiHanAITryAddSingleton 默认 Options 提示词库,AddPromptStore 必须用 Replace 覆盖——TryAdd 会被静默忽略,导致 DB 里的提示词永不生效。这与 provider 配置源的覆盖方式(AddAIConfigStore)同构。

AiPromptAppService[DynamicApi],无 Controller)暴露的动作:CreateAsyncai_prompt:create)、UpdateAsyncai_prompt:updatePromptCode 不可变)、UpdateStatusAsyncai_prompt:update)、DeleteAsyncai_prompt:delete,软删),均 [UnitOfWork]。查询侧 AiPromptQueryServiceGetPageAsync[HttpPost]ai_prompt:read,FLS 门控排序/过滤 + 默认排序 Sort 升序→CreatedTime 降序)与 GetDetailAsync

当前无消费方

v1 仅提供提示词的库化 CRUD 与框架存储层替换(IAiPromptStore),BasicApp 内尚无对话/技能/RAG 环节显式调用 IAiPromptStore.GetAsync 取用——RAG 问答走的是独立的 IRagPromptAugmenter.Augment 拼装 prompt,不经此库。提示词库是面向未来对话/技能场景预置的模板仓库。


Agent 与 MCP

BasicApp 通过框架把知识检索能力对外暴露为标准工具,两条投影链路:

技能 → 对话工具 / MCP 工具KnowledgeRetrieveSkill 实现框架 IAiSkill(名 knowledge_retrieve),在模块 AddAISkills() 里注册为单例。框架 DefaultAiSkillRegistry 构造时自动收纳 DI 里全部 IAiSkill,一处注册即同时可用于:

  • 对话工具skill.AsFunction() 产出 AIFunction,可挂进 Agent 的工具集(框架 IXiHanAgentFactoryIChatClient.AsAIAgent(...) 产出 MAF 原生 AIAgent,支持工具调用与多轮会话)。
  • MCP tools:框架把技能 AsFunction() 经官方 McpServerTool.Create 桥接进 MCP 工具集。

KnowledgeRetrieveSkill 只读、无副作用,故 MCP 暴露安全(无需批准)。经 MCP 调用时无用户/租户上下文(应用管理的 key 为平台级凭据),检索不加租户过滤(知识库文档视为平台级);topK 内部收敛 1~20

工具会被自动执行

框架工厂套了 UseFunctionInvocation、MAF Agent 内部也套 FunctionInvokingChatClient——工具/技能会被自动调用、无人工批准。当前 v1 技能均只读(知识检索)安全;将来接入有副作用的技能须自行加批准/审计。

MCP Server(应用管理 key,fail-closed)

MCP Server 的启用与暴露由框架包 XiHan.Framework.Web.Mcp 负责;BasicApp 通过 XiHanBasicAppWebCoreModule 依赖 XiHanWebMcpModule,WebHost 无需重复声明。配置节 XiHan:AI:McpXiHanMcpOptions):

字段默认说明
Enabledfalse是否启用(默认关)
ApiKeynull应用管理的访问密钥,外部 MCP 客户端须携带
HeaderNameX-Api-Key携带密钥的请求头(或 Authorization: Bearer
Path/mcp端点路径
Statelesstrue无状态 HTTP(检索类工具足够)

鉴权是应用管理的 key、不是用户令牌:/mcp 端点 AllowAnonymous() 绕过全局授权 FallbackPolicy,改由 McpApiKeyEndpointFilter 端点过滤器守门——请求头不匹配即 401。

fail-closedIsExposable = Enabled && !string.IsNullOrWhiteSpace(ApiKey)未启用或未配 ApiKey 则根本不注册、不映射 /mcp 端点——宁可不暴露,也不裸奔。


部署前置

启用 AI / 知识库能力前,须准备:

  1. Qdrant 向量库:可连接的 Qdrant 实例,XiHan:AI:Rag 节配好 QdrantHost/QdrantPort(云端另加 QdrantHttps + QdrantApiKey)。未注册向量库而调用 RAG 会因缺依赖失败。
  2. 嵌入模型对齐 1536:默认 text-embedding-3-small(1536 维)。换维度须改框架常量并重建 Qdrant 集合
  3. 准备数据库结构:全新数据库首次启动会自动建立 AI 相关表并执行种子;存量数据库应通过对应版本的 UpdateScripts 前向迁移。
  4. provider 配置入库:库化管理下 provider 不写 appsettings——建库后在前端「AI 提供商」页新增配置、填好 Model/EmbeddingModel/ApiKey、设默认并测连接。
  5. (可选)MCP:需要对外暴露 MCP tools 时,XiHan:AI:Mcp 显式 Enabled=true 并配 ApiKey
  6. 多实例:共享 Data Protection 密钥环,否则各实例无法互相解密 provider 密钥。

种子说明:AI provider、知识库与提示词库各占独立种子 Order 段(200-204 / 205-208 / 209-212,晚于 Saas 的 10-37 与代码生成的 100-105),操作字典复用 AI provider 段的 SysOperationSeeder(200),链内顺序均为「(操作字典 →)资源 → 权限 → 菜单 → 角色授权」,权限仅授超管(其余角色/租户按需在权限管理里下放)。


相关文档

  • 框架 · XiHan.Framework.AI:底层 provider 解析/热切换、RAG 切片检索、提示词模板、Agent/MCP 桥接的抽象与默认实现
  • 权限模型module:resource:action 权限码、数据范围、字段级脱敏
  • 多租户:字段级隔离与 TenantId=0 全局约定
  • 部署:环境要求、升级入口与生产配置

Released under The MIT License