# LLM Provider 管理与模型工厂
**本文引用的文件**
- [pom.xml](file://pom.xml)
- [ai-server/pom.xml](file://ai-server/pom.xml)
- [AI_AGENT.md](file://AI_AGENT.md)
- [AgentEntity.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentEntity.java)
- [AgentModel.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentModel.java)
- [AgentModelProvider.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentModelProvider.java)
- [ModelTypeEnum.java](file://ai-server/src/main/java/com/zsjz/ai/common/enums/ModelTypeEnum.java)
- [AgentModelService.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelService.java)
- [AgentModelServiceImpl.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentModelServiceImpl.java)
- [AgentModelFactory.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelFactory.java)
- [ModelConnectivityTester.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/ModelConnectivityTester.java)
- [EmbeddingModelFactory.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/EmbeddingModelFactory.java)
- [AgentModelController.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentModelController.java)
- [application-dev.yaml](file://ai-server/src/main/resources/application-dev.yaml)
## 目录
1. [引言](#引言)
2. [项目结构与定位](#项目结构与定位)
3. [依赖与扩展边界](#依赖与扩展边界)
4. [核心组件总览](#核心组件总览)
5. [架构与数据流](#架构与数据流)
6. [详细组件分析](#详细组件分析)
7. [模型配置项与枚举](#模型配置项与枚举)
8. [可用性与健康检查策略](#可用性与健康检查策略)
9. [操作示例](#操作示例)
10. [依赖关系分析](#依赖关系分析)
11. [性能与可观测性](#性能与可观测性)
12. [问题排查指南](#问题排查指南)
13. [结论](#结论)
## 引言
本文围绕 `ai-server` 模块中的 **LLM Provider 管理** 与 **模型工厂** 展开,目标是帮助开发者理解:
- Maven 依赖中 `agentscope-harness`、DashScope/Ollama/OpenAI 扩展、mem0、redis 等扩展的作用;
- 数据库实体 `AgentModel`、服务 `AgentModelService`、工厂 `AgentModelFactory` 如何协作;
- 如何通过 AgentScope SPI 将 DB 中的模型配置注册为 DashScope、Ollama、OpenAI 三种 Provider;
- 模型配置项、用途类型枚举、默认模型隔离规则;
- 连通性测试与健康检查机制;
- 新增一个 LLM Provider、切换默认模型、通过控制器查询模型列表和状态的完整流程。
本项目的 Agent 子系统基于 **AgentScope 2.0.4**,后端是 Spring Boot 3 + MyBatis-Plus,模型配置不写 YAML,而是持久化到平台库的 `agent_model` / `agent_model_provider` 表,由前端「模型管理」界面维护。
**章节来源**
- [AI_AGENT.md:1-60](file://AI_AGENT.md#L1-L60)
- [AI_AGENT.md:160-180](file://AI_AGENT.md#L160-L180)
## 项目结构与定位
仓库根 `pom.xml` 是一个聚合 POM,打包类型为 `pom`,包含 `ai-server` 与 `ai-gateway` 两个子模块。LLM Provider 管理与模型工厂全部位于 `ai-server` 模块的 `com.zsjz.ai.module.agent` 包下。
```mermaid
graph TB
Root["根 POM
zsjz-ai"] --> Server["ai-server
Spring Boot 3 + AgentScope"]
Root --> Gateway["ai-gateway
Spring Cloud Gateway"]
Server --> Controller["AgentModelController
/models/*"]
Server --> Service["AgentModelService
模型配置服务"]
Server --> Factory["AgentModelFactory
AgentScope Model 工厂"]
Server --> Tester["ModelConnectivityTester
连通性测试"]
Server --> Entities["AgentModel / AgentModelProvider"]
Server --> EnumType["ModelTypeEnum
llm / embedding"]
```
**图表来源**
- [pom.xml:1-23](file://pom.xml#L1-L23)
- [AgentModelController.java:1-91](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentModelController.java#L1-L91)
- [AgentModelService.java:1-70](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelService.java#L1-L70)
- [AgentModelFactory.java:1-131](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelFactory.java#L1-L131)
- [AgentModel.java:1-63](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentModel.java#L1-L63)
- [AgentModelProvider.java:1-43](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentModelProvider.java#L1-L43)
- [ModelTypeEnum.java:1-54](file://ai-server/src/main/java/com/zsjz/ai/common/enums/ModelTypeEnum.java#L1-L54)
**章节来源**
- [pom.xml:1-23](file://pom.xml#L1-L23)
- [AI_AGENT.md:20-60](file://AI_AGENT.md#L20-L60)
## 依赖与扩展边界
`ai-server/pom.xml` 集中声明了 AgentScope 及其扩展依赖。它们不是业务逻辑代码,而是让 Spring Boot 应用能接入不同大模型厂商或本地推理服务的运行时扩展。
| 依赖 | 作用 | 在本项目中的意义 |
|---|---|---|
| `agentscope-harness` | AgentScope 运行期入口,提供 HarnessAgent、Toolkit、中间件等基础设施 | 本项目不引入外部 MCP SDK(显式排除 `io.modelcontextprotocol.sdk:mcp`),只使用其 Agent 编排能力 |
| `agentscope-extensions-model-dashscope` | DashScope 原生协议的 LLM 与 Embedding 扩展 | 用于阿里通义 DashScope 对话与向量模型 |
| `agentscope-extensions-model-ollama` | Ollama 本地推理服务的扩展 | 用于本地 llama.cpp / Ollama 兼容端点 |
| `agentscope-extensions-model-openai` | OpenAI 兼容协议的扩展 | 作为兜底协议前缀,覆盖 OpenAI、DeepSeek、GLM、Kimi、Ollama 兼容服务等 |
| `agentscope-extensions-mem0` | Agent 记忆扩展 | 与 Redis 配合实现会话级或用户级记忆存储 |
| `agentscope-extensions-rag-simple` | 简易 RAG 扩展 | 配合 pgvector 做模板元数据语义检索 |
| `agentscope-extensions-redis` | Redis 扩展 | 用于 mem0、缓存与会话相关能力 |
注意:**Provider 名称不等于模型用途类型**。
- `agent_model.type` 表示用途:`llm`(对话)或 `embedding`(向量)。
- `agent_model_provider.type` 表示厂商协议:如 `OLLAMA`、`OPENAI`、`DASHSCOPE`、`DEEPSEEK`。
**章节来源**
- [ai-server/pom.xml:1-200](file://ai-server/pom.xml#L1-L200)
- [AI_AGENT.md:140-180](file://AI_AGENT.md#L140-L180)
- [ModelTypeEnum.java:1-54](file://ai-server/src/main/java/com/zsjz/ai/common/enums/ModelTypeEnum.java#L1-L54)
## 核心组件总览
| 组件 | 职责 | 关键行为 |
|---|---|---|
| `AgentModel` | 持久化单个模型配置 | 保存 provider、modelId、apiKey、baseUrl、type、config、headersJson、extraJson、status |
| `AgentModelProvider` | 持久化厂商元信息 | 保存 name、baseUrl、config、status、modelsEndpoint、headersJson、extraJson、type |
| `AgentModelService` | 模型配置的 CRUD、默认模型、连通性测试 | 按 type 隔离默认模型,删除采用软删除 |
| `AgentModelFactory` | DB 配置 → AgentScope `Model` 实例 | 根据 provider 归一化为 `dashscope:` 或 `openai:` 前缀,构造 `ModelCreationContext` |
| `ModelConnectivityTester` | 最小化真实调用验证连通性 | 对话模型 ping 首个流式分片,向量模型做一次嵌入 |
| `EmbeddingModelFactory` | RAG 用向量模型构建 | 按 provider 选择 Ollama / DashScope / OpenAI 嵌入实现 |
| `AgentModelController` | REST API 暴露模型管理能力 | `/models/providers`、`/models`、`/{id}/default`、`/{id}/test` |
| `AgentEntity` | 用户自定义 Agent 定义 | 携带 model、tools、skills、sandboxMode、sandboxScope 等业务字段,但不直接承载 LLM Provider 配置 |
```mermaid
classDiagram
class AgentModel {
+Long id
+String provider
+String modelId
+String name
+String type
+String apiKey
+String baseUrl
+Boolean defaultModel
+String config
+String headersJson
+String extraJson
+StatusEnum status
}
class AgentModelProvider {
+Long id
+String name
+String baseUrl
+String config
+String status
+String modelsEndpoint
+String headersJson
+String extraJson
+String type
}
class AgentModelService {
+listActiveModels()
+createModel(dto)
+updateModel(id, dto)
+deleteModel(id)
+setDefaultModel(id)
+testModel(id)
+getDefaultModelId()
+getDefaultModelId(type)
}
class AgentModelFactory {
+create(config)
+create(config, enableThinkingOverride)
-resolvePrefix(provider)
-readEnableThinking(config)
}
class ModelConnectivityTester {
+test(config)
-testChat(config)
-testEmbedding(config)
-describeFailure(e)
}
class EmbeddingModelFactory {
+create(config)
-buildOllama(...)
-buildDashScope(...)
-buildOpenAI(...)
}
class AgentModelController {
+listProviders()
+listModels()
+createModel(dto)
+updateModel(id, dto)
+deleteModel(id)
+setDefaultModel(id)
+testModel(id)
}
AgentModelService --> AgentModel : "读写"
AgentModelService --> ModelConnectivityTester : "委托测试"
ModelConnectivityTester --> AgentModelFactory : "创建 LLM Model"
ModelConnectivityTester --> EmbeddingModelFactory : "创建向量模型"
AgentModelController --> AgentModelService : "调用"
AgentModelController --> AgentModelProviderService : "读取厂商"
```
**图表来源**
- [AgentModel.java:1-63](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentModel.java#L1-L63)
- [AgentModelProvider.java:1-43](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentModelProvider.java#L1-L43)
- [AgentModelService.java:1-70](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelService.java#L1-L70)
- [AgentModelServiceImpl.java:1-198](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentModelServiceImpl.java#L1-L198)
- [AgentModelFactory.java:1-131](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelFactory.java#L1-L131)
- [ModelConnectivityTester.java:1-139](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/ModelConnectivityTester.java#L1-L139)
- [EmbeddingModelFactory.java:1-144](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/EmbeddingModelFactory.java#L1-L144)
- [AgentModelController.java:1-91](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentModelController.java#L1-L91)
**章节来源**
- [AgentModel.java:1-63](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentModel.java#L1-L63)
- [AgentModelProvider.java:1-43](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentModelProvider.java#L1-L43)
- [AgentModelService.java:1-70](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelService.java#L1-L70)
- [AgentModelController.java:1-91](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentModelController.java#L1-L91)
## 架构与数据流
模型管理的整体流程可以拆成两条主线:
1. **配置主线**:前端 → `AgentModelController` → `AgentModelService` → `AgentModelMapper` → PostgreSQL `agent_model` 表。
2. **推理主线**:上层业务 → `AgentModelFactory` → AgentScope `ModelRegistry.resolve(...)` → 具体 Provider(DashScope / OpenAI 兼容 / Ollama)。
```mermaid
sequenceDiagram
participant FE as "前端"
participant C as "AgentModelController"
participant S as "AgentModelService"
participant M as "AgentModelMapper"
participant DB as "PostgreSQL"
participant F as "AgentModelFactory"
participant MS as "ModelRegistry"
participant P as "DashScope / OpenAI / Ollama"
FE->>C: GET /models
C->>S: listActiveModels()
S->>M: 查询未删除模型
M->>DB: SELECT agent_model
DB-->>M: 模型列表
M-->>S: 实体列表
S-->>C: VO 列表
C-->>FE: Result>
FE->>C: GET /models/{id}/test
C->>S: testModel(id)
S->>F: create(config)
F->>MS: resolve("dashscope:"|"openai:"+modelId, ctx)
MS->>P: 初始化 Provider
P-->>MS: Model 实例
MS-->>F: Model
F-->>S: Model
S->>S: 发起最小化调用
S-->>C: ModelTestResultVO
C-->>FE: Result
```
**图表来源**
- [AgentModelController.java:32-91](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentModelController.java#L32-L91)
- [AgentModelServiceImpl.java:40-198](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentModelServiceImpl.java#L40-L198)
- [AgentModelFactory.java:56-113](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelFactory.java#L56-L113)
- [ModelConnectivityTester.java:66-139](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/ModelConnectivityTester.java#L66-L139)
**章节来源**
- [AgentModelController.java:32-91](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentModelController.java#L32-L91)
- [AgentModelServiceImpl.java:40-198](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentModelServiceImpl.java#L40-L198)
- [AgentModelFactory.java:56-113](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelFactory.java#L56-L113)
## 详细组件分析
### AgentEntity:Agent 定义实体
`AgentEntity` 是用户自定义 Agent 的持久化实体,对应数据库 `agent` 表。它负责保存 Agent 的业务配置,例如:
- `ownerId`、`userId`:用户归属;
- `isBuiltin`:是否内置专家;
- `workspacePath`:Agent 工作区路径;
- `name`、`description`、`sysPrompt`:名称、描述、系统提示词;
- `model`:绑定的模型标识;
- `maxIters`:最大迭代次数;
- `toolsAllowJson`、`toolsDenyJson`:工具白名单/黑名单;
- `toolPacksJson`:方案绑定的工具包;
- `knowledgeBaseId`:知识库 ID;
- `skillsAllowJson`、`skillsDenyJson`:技能白名单/黑名单;
- `runAs`、`forkOf`:执行身份与 Fork 来源;
- `skillRepositoriesJson`:技能仓库配置;
- `sandboxMode`、`sandboxScope`:沙箱模式与隔离范围。
需要特别注意:**`AgentEntity` 并不等同于 LLM Provider 配置**。它只是引用某个模型,真正的 Provider、API Key、Base URL、模型 ID 等保存在 `AgentModel` 表中。
**章节来源**
- [AgentEntity.java:1-137](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentEntity.java#L1-L137)
### AgentModel:模型配置实体
`AgentModel` 是 LLM Provider 管理的核心数据对象,对应 `agent_model` 表。关键字段如下:
| 字段 | 含义 | 约束与行为 |
|---|---|---|
| `provider` | 用户填写的 Provider 名称 | 会被 `AgentModelFactory` 归一化为 DashScope 或 OpenAI 兼容前缀 |
| `modelId` | 模型 ID | 必填;缺失时工厂抛 400 |
| `name` | 模型展示名 | 日志与调试用 |
| `type` | 用途类型 | `llm` 或 `embedding`;历史空值按 `llm` 处理 |
| `apiKey` | API 密钥 | 必填;缺失时工厂抛 400 |
| `baseUrl` | 基础地址 | 可选;DashScope 通常为空,OpenAI 兼容或 Ollama 常填本地或代理地址 |
| `defaultModel` | 是否默认模型 | 同类型互斥,设置时清除其他同类型默认 |
| `config` | JSON 扩展配置 | 支持 `enableThinking` 控制思考模式 |
| `headersJson` | 请求头扩展 | JSON 字符串 |
| `extraJson` | 扩展字段 | JSON 字符串 |
| `status` | 状态 | 使用 `StatusEnum`,删除为软删除 |
**章节来源**
- [AgentModel.java:1-63](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentModel.java#L1-L63)
### AgentModelProvider:厂商实体
`AgentModelProvider` 对应 `agent_model_provider` 表,主要用于前端展示可用的厂商选项。它不是 AgentScope 的 Provider 注册中心,而是平台侧的厂商元数据表。
| 字段 | 含义 |
|---|---|
| `name` | 厂商名称 |
| `baseUrl` | 厂商默认 Base URL |
| `config` | 厂商级 JSON 配置 |
| `status` | 启用状态,查询时使用 `"active"` |
| `modelsEndpoint` | 厂商模型列表接口地址 |
| `headersJson` | 厂商级请求头 |
| `extraJson` | 厂商级扩展字段 |
| `type` | 厂商协议类型,如 `OLLAMA`、`OPENAI`、`DASHSCOPE`、`DEEPSEEK` |
**章节来源**
- [AgentModelProvider.java:1-43](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentModelProvider.java#L1-L43)
- [AgentModelProviderServiceImpl.java:23-42](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentModelProviderServiceImpl.java#L23-L42)
### AgentModelService:模型配置服务
`AgentModelService` 是模型管理的业务门面,提供以下能力:
1. **列出启用模型**:过滤已删除记录,按默认模型和创建人排序。
2. **创建模型**:生成 ID、填充默认值、写入 DB;若设为默认,则清除同类型其它默认。
3. **更新模型**:按 ID 更新;若表单标记为默认,同样清理同类型其它默认。
4. **删除模型**:软删除,将状态改为 `DELETED`。
5. **设置默认模型**:按 `type` 隔离,`llm` 与 `embedding` 各自独立默认。
6. **测试连通性**:委托 `ModelConnectivityTester` 对已保存配置发起真实调用。
7. **获取默认模型 ID**:优先返回 `llm` 类型默认;也可按传入 type 查询。
默认模型的隔离规则非常关键:
- 历史数据中 `type` 可能为空;
- 空值在查询和清除默认时都视为 `llm`;
- 因此设置一个 `embedding` 默认不会清掉 `llm` 默认,反之亦然。
**章节来源**
- [AgentModelService.java:1-70](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelService.java#L1-L70)
- [AgentModelServiceImpl.java:40-198](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentModelServiceImpl.java#L40-L198)
### AgentModelFactory:AgentScope 模型工厂
`AgentModelFactory` 是连接平台模型配置与 AgentScope 运行时的重要桥梁。它的核心职责是:
1. 校验 `AgentModel` 配置是否完整;
2. 将 `provider` 归一化为 AgentScope SPI 前缀;
3. 构造 `ModelCreationContext`,携带 apiKey、baseUrl、stream、结构化输出选项、思考模式;
4. 调用 `ModelRegistry.resolve(modelRef, ctx)` 创建实际 Model 实例。
Provider 归一化规则:
| 输入 provider | 结果前缀 | 说明 |
|---|---|---|
| 包含 `dashscope` 或 `阿里`(忽略大小写) | `dashscope:` | 使用 DashScope 原生协议 |
| 其余任何值 | `openai:` | 使用 OpenAI 兼容协议,通过 baseUrl 指向各厂商 |
这意味着:
- DashScope 走原生 Provider;
- OpenAI、DeepSeek、GLM、Kimi、Ollama 兼容端点都可以通过 `openai:` 前缀 + baseUrl 接入;
- 不需要为每个兼容厂商新增 Java 类,只要其 API 符合 OpenAI 兼容协议即可。
思考模式开关:
- 如果调用方显式传 `enableThinkingOverride`,优先使用该值;
- 否则从 `config` JSON 中读取 `enableThinking`;
- 都没有则交给各 Provider 的默认行为;
- 该设计避免推理模型 reasoning 吃光 maxTokens,导致正文为空。
**章节来源**
- [AgentModelFactory.java:1-131](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelFactory.java#L1-L131)
### ModelConnectivityTester:连通性测试器
`ModelConnectivityTester` 不负责业务推理,只负责“这个模型配置能不能用”。
测试策略:
| 模型类型 | 测试方式 | 超时 | 失败处理 |
|---|---|---|---|
| `llm` | 发送文本 `"ping"`,取第一个流式分片后取消 | 20 秒 | 返回 `success=false`,不抛异常 |
| `embedding` | 对固定短文本做一次向量化 | 20 秒 | 返回空向量时报错 |
重要特性:
- 失败是预期结果,HTTP 返回 200,错误信息放在返回值中;
- 超时异常被识别并翻译为“连接超时(N s),请检查网络与 Base URL”;
- 非超时异常会提取根因消息,并限制最大长度;
- 向量模型返回空向量被视为失败;
- 测试耗时会记录到 `latencyMs`。
**章节来源**
- [ModelConnectivityTester.java:1-139](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/ModelConnectivityTester.java#L1-L139)
### EmbeddingModelFactory:RAG 向量模型工厂
`EmbeddingModelFactory` 面向 RAG 场景,根据 provider 选择具体嵌入实现:
| provider | 嵌入实现 | 特点 |
|---|---|---|
| `ollama` | `OllamaTextEmbedding` | 本地端点,无需 apiKey |
| `dashscope` | `DashScopeTextEmbedding` | 原生 DashScope 向量模型 |
| 其他 | `OpenAITextEmbedding` | OpenAI 兼容协议,靠 baseUrl 指向 |
它与 `AgentModelFactory` 的区别在于:
- `AgentModelFactory` 用于 LLM 对话;
- `EmbeddingModelFactory` 用于向量嵌入;
- 两者共用 `AgentModel` 配置,但构造的对象不同。
**章节来源**
- [EmbeddingModelFactory.java:1-144](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/EmbeddingModelFactory.java#L1-L144)
### AgentModelController:REST API
控制器暴露 `/models` 下的模型管理能力:
| 方法 | 路径 | 功能 | 返回 |
|---|---|---|---|
| `GET` | `/models/providers` | 查询启用的厂商列表 | `Result>` |
| `GET` | `/models` | 查询启用的模型列表 | `Result>` |
| `POST` | `/models` | 创建模型 | `Result` |
| `PUT` | `/models/{id}` | 更新模型 | `Result` |
| `GET` | `/models/{id}` | 删除模型(软删除) | `Result` |
| `GET` | `/models/{id}/default` | 设置默认模型 | `Result` |
| `GET` | `/models/{id}/test` | 测试模型连通性 | `Result` |
**章节来源**
- [AgentModelController.java:1-91](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentModelController.java#L1-L91)
## 模型配置项与枚举
### 模型配置项
| 配置项 | 所属实体 | 是否必填 | 说明 |
|---|---|---:|---|
| `provider` | `AgentModel` | 建议填写 | 用户自由填写;工厂按规则归一化为 DashScope 或 OpenAI 兼容 |
| `modelId` | `AgentModel` | 是 | 模型 ID;缺失时 `AgentModelFactory` 抛 400 |
| `name` | `AgentModel` | 否 | 展示名称 |
| `type` | `AgentModel` | 建议填写 | `llm` 或 `embedding`;空值按 `llm` 处理 |
| `apiKey` | `AgentModel` | 是 | API Key;缺失时 `AgentModelFactory` 抛 400 |
| `baseUrl` | `AgentModel` | 否 | Base URL;DashScope 通常留空,OpenAI 兼容或 Ollama 常用 |
| `defaultModel` | `AgentModel` | 否 | 默认模型开关,同类型唯一 |
| `config` | `AgentModel` | 否 | JSON;支持 `enableThinking` |
| `headersJson` | `AgentModel` | 否 | JSON;额外请求头 |
| `extraJson` | `AgentModel` | 否 | JSON;扩展字段 |
| `status` | `AgentModel` | 自动 | 使用 `StatusEnum`,删除为软删除 |
### 用途类型枚举
`ModelTypeEnum` 描述模型用途,不是厂商协议:
| 枚举值 | code | 含义 |
|---|---|---|
| `LLM` | `llm` | 对话模型,可用于 AI 对话与分析推理 |
| `EMBEDDING` | `embedding` | 向量模型,仅供 RAG 表结构语义检索,不能用于对话 |
`fromCode` 的容错规则:
- null、空串、未知值一律按 `LLM` 处理;
- 只有严格等于 `embedding`(忽略大小写)才按向量模型处理;
- 这是为了兼容历史数据和前端把空 type 视为对话模型的约定。
**章节来源**
- [ModelTypeEnum.java:1-54](file://ai-server/src/main/java/com/zsjz/ai/common/enums/ModelTypeEnum.java#L1-L54)
- [AgentModel.java:1-63](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentModel.java#L1-L63)
- [AgentModelServiceImpl.java:114-198](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentModelServiceImpl.java#L114-L198)
## 可用性与健康检查策略
系统没有全局 Provider 启动时自动探测,也没有硬编码的 Provider 白名单。可用性保障主要通过三层完成:
1. **配置完整性校验**:`AgentModelFactory.create` 要求 `modelId` 和 `apiKey` 非空。
2. **默认模型隔离**:`AgentModelService` 保证同一用途类型只有一个默认模型。
3. **连通性测试**:`ModelConnectivityTester` 对已保存配置发起真实最小调用。
```mermaid
flowchart TD
Start(["开始测试"]) --> CheckType{"模型类型"}
CheckType -->|llm| CreateLLM["AgentModelFactory.create(config)"]
CheckType -->|embedding| CreateEmbed["EmbeddingModelFactory.create(config)"]
CreateLLM --> Ping["发送 'ping',取首个流式分片"]
CreateEmbed --> Embed["对固定文本做向量化"]
Ping --> Timeout{"是否超时?"}
Embed --> VectorValid{"是否返回非空向量?"}
Timeout -->|是| FailTimeout["返回超时原因"]
Timeout -->|否| Success["返回 success=true"]
VectorValid -->|否| FailEmpty["返回嵌入为空"]
VectorValid -->|是| Success
FailTimeout --> End(["结束"])
FailEmpty --> End
Success --> End
```
**图表来源**
- [ModelConnectivityTester.java:66-139](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/ModelConnectivityTester.java#L66-L139)
- [AgentModelFactory.java:56-113](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelFactory.java#L56-L113)
- [EmbeddingModelFactory.java:108-144](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/EmbeddingModelFactory.java#L108-L144)
**章节来源**
- [ModelConnectivityTester.java:1-139](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/ModelConnectivityTester.java#L1-L139)
- [AgentModelFactory.java:56-113](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelFactory.java#L56-L113)
## 操作示例
### 示例一:新增一个 LLM Provider
步骤:
1. 确保后端已引入对应 AgentScope 扩展(DashScope、Ollama、OpenAI 已引入)。
2. 打开前端「模型管理」页面或通过 `/models` 接口创建模型。
3. 填写 `provider`、`modelId`、`name`、`type=llm`、`apiKey`。
4. 如果是 DashScope,`baseUrl` 通常留空;如果是 Ollama 或 OpenAI 兼容代理,填写对应 Base URL。
5. 点击“测试”,确认 `ModelTestResultVO.success=true`。
6. 如需设为默认,调用 `/models/{id}/default`。
注意事项:
- `provider` 只要包含 `dashscope` 或 `阿里`,就会走 DashScope 原生协议;
- 其它值都会走 `openai:` 兼容协议;
- 新增模型默认就是启用状态;
- 如果同时勾选“设为默认”,系统会清除同类型其它默认。
**章节来源**
- [AgentModelController.java:32-91](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentModelController.java#L32-L91)
- [AgentModelServiceImpl.java:60-113](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentModelServiceImpl.java#L60-L113)
- [AgentModelFactory.java:56-113](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelFactory.java#L56-L113)
### 示例二:切换默认模型
调用:
```
GET /models/{id}/default
```
行为:
- 先根据当前模型记录的 `type` 确定用途类型;
- 清除该类型下其它模型的 `defaultModel=true`;
- 将目标模型设为默认;
- 另一个用途类型的默认不受影响。
典型场景:
- 将 `embedding` 默认模型切换到新向量模型;
- 将 `llm` 默认模型切换到新对话模型;
- 两者互不干扰。
**章节来源**
- [AgentModelServiceImpl.java:114-150](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentModelServiceImpl.java#L114-L150)
- [ModelTypeEnum.java:20-54](file://ai-server/src/main/java/com/zsjz/ai/common/enums/ModelTypeEnum.java#L20-L54)
### 示例三:通过 AgentModelController 查询模型列表和状态
查询模型列表:
```
GET /models
```
返回所有未被软删除的模型,并按默认模型和创建人排序。
查询厂商列表:
```
GET /models/providers
```
返回 `agent_model_provider` 表中 `status="active"` 的厂商。
测试模型连通性:
```
GET /models/{id}/test
```
返回结构体包含:
| 字段 | 含义 |
|---|---|
| `success` | 是否连通 |
| `latencyMs` | 测试耗时毫秒 |
| `message` | 失败原因摘要 |
**章节来源**
- [AgentModelController.java:32-91](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentModelController.java#L32-L91)
- [AgentModelServiceImpl.java:40-58](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentModelServiceImpl.java#L40-L58)
- [ModelConnectivityTester.java:66-139](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/ModelConnectivityTester.java#L66-L139)
## 依赖关系分析
```mermaid
graph LR
POM["ai-server/pom.xml"] --> Harness["agentscope-harness"]
POM --> DashScope["agentscope-extensions-model-dashscope"]
POM --> OllamaExt["agentscope-extensions-model-ollama"]
POM --> OpenAIExt["agentscope-extensions-model-openai"]
POM --> Mem0["agentscope-extensions-mem0"]
POM --> RAGSimple["agentscope-extensions-rag-simple"]
POM --> RedisExt["agentscope-extensions-redis"]
Controller["AgentModelController"] --> Service["AgentModelService"]
Controller --> ProviderService["AgentModelProviderService"]
Service --> Mapper["AgentModelMapper"]
Service --> Tester["ModelConnectivityTester"]
Tester --> Factory["AgentModelFactory"]
Tester --> EmbedFactory["EmbeddingModelFactory"]
Factory --> Registry["AgentScope ModelRegistry"]
```
**图表来源**
- [ai-server/pom.xml:1-200](file://ai-server/pom.xml#L1-L200)
- [AgentModelController.java:1-91](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentModelController.java#L1-L91)
- [AgentModelService.java:1-70](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelService.java#L1-L70)
- [ModelConnectivityTester.java:1-139](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/ModelConnectivityTester.java#L1-L139)
- [AgentModelFactory.java:1-131](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelFactory.java#L1-L131)
- [EmbeddingModelFactory.java:1-144](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/EmbeddingModelFactory.java#L1-L144)
**章节来源**
- [ai-server/pom.xml:1-200](file://ai-server/pom.xml#L1-L200)
- [AgentModelController.java:1-91](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentModelController.java#L1-L91)
- [AgentModelFactory.java:1-131](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelFactory.java#L1-L131)
## 性能与可观测性
### 性能特征
- 每次调用 `AgentModelFactory.create` 都会新建轻量 Model 配置对象,不做跨请求缓存;
- 连通性测试对话模型只取首个流式分片,不等完整回复,减少 token 消耗和网络等待;
- 连通性测试向量模型对固定短文本做一次嵌入;
- 测试超时统一为 20 秒,既允许 Ollama 冷启动加载模型,又避免长时间阻塞;
- 测试失败的消息长度被限制为 200 字符,便于前端展示。
### 可观测性
- 模型创建成功、更新成功、删除成功、设置默认成功均有日志;
- 模型实例构造成功记录 modelRef、name、enableThinking;
- 连通性测试成功记录 name 和耗时;
- 连通性测试失败记录 name、耗时、原因;
- 思考模式 JSON 解析失败会记录警告日志,不影响主流程。
**章节来源**
- [AgentModelServiceImpl.java:60-198](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentModelServiceImpl.java#L60-L198)
- [AgentModelFactory.java:56-131](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelFactory.java#L56-L131)
- [ModelConnectivityTester.java:66-139](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/ModelConnectivityTester.java#L66-L139)
## 问题排查指南
### 问题一:DashScope Key 无效
现象:
- 模型创建成功;
- 连通性测试返回 `success=false`;
- 消息中包含鉴权失败、Key 无效、免费额度不足等底层 Provider 错误。
排查步骤:
1. 确认 `agent_model.api_key` 是否正确复制,无多余空格;
2. 确认 `agent_model.provider` 包含 `dashscope` 或 `阿里`,否则会误走 `openai:` 前缀;
3. 确认 DashScope 账号权限和免费额度;
4. 使用 `/models/{id}/test` 查看失败原因;
5. 检查后端日志中“模型连通性测试失败”的 message。
根因映射:
- `AgentModelFactory.create` 会把 apiKey 放入 `ModelCreationContext`;
- DashScope Provider 会在首次调用时校验 Key;
- 连通性测试捕获异常并转为结构化失败结果。
**章节来源**
- [AgentModelFactory.java:56-113](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelFactory.java#L56-L113)
- [ModelConnectivityTester.java:66-139](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/ModelConnectivityTester.java#L66-L139)
### 问题二:Ollama 服务不可达
现象:
- baseUrl 指向本地 Ollama;
- 连通性测试超时或连接失败;
- 本地 Ollama 进程未启动,或端口不一致。
排查步骤:
1. 确认 Ollama 进程正在运行;
2. 确认 `baseUrl` 指向正确的 Ollama HTTP 地址;
3. 确认防火墙或容器网络允许访问该端口;
4. 首次加载本地模型可能较慢,20 秒超时可能不够长;
5. 使用 `/models/{id}/test` 观察耗时和错误信息。
根因映射:
- Ollama 通过 `openai:` 兼容协议接入;
- 连通性测试对 Ollama 也使用 20 秒超时;
- 超时异常会被识别为“连接超时(20s),请检查网络与 Base URL”。
**章节来源**
- [ModelConnectivityTester.java:30-45](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/ModelConnectivityTester.java#L30-L45)
- [ModelConnectivityTester.java:90-139](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/ModelConnectivityTester.java#L90-L139)
- [AgentModelFactory.java:98-113](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelFactory.java#L98-L113)
### 问题三:OpenAI 代理超时
现象:
- baseUrl 指向第三方代理;
- 连通性测试返回超时;
- 网络延迟高或代理限流。
排查步骤:
1. 确认代理地址可达;
2. 确认代理未限流或拒绝请求;
3. 确认代理支持 OpenAI 兼容协议;
4. 如果代理响应慢,考虑调整连通性测试超时逻辑;
5. 检查底层异常链中是否有 `TimeoutException`。
根因映射:
- 非 DashScope provider 统一走 `openai:`;
- 连通性测试使用 20 秒超时;
- `describeFailure` 会遍历异常链查找 `TimeoutException`。
**章节来源**
- [AgentModelFactory.java:98-113](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelFactory.java#L98-L113)
- [ModelConnectivityTester.java:108-139](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/ModelConnectivityTester.java#L108-L139)
### 问题四:模型枚举不生效
现象:
- 设置了一个 `embedding` 默认模型,却影响了 `llm` 默认;
- 或者反过来;
- 旧数据 `type` 为空,行为不符合预期。
排查步骤:
1. 确认 `agent_model.type` 是 `llm` 还是 `embedding`;
2. 不要混淆 `type` 与 `agent_model_provider.type`;
3. 检查历史数据是否为空;
4. 使用 `ModelTypeEnum.fromCode` 判断:只有严格 `embedding` 才按向量模型处理;
5. 默认模型清除逻辑会根据 type 决定是否包含 `isNull(type)` 分支。
根因映射:
- `ModelTypeEnum.fromCode` 将空值和未知值归一为 `LLM`;
- `clearDefaultOfType` 对 `LLM` 包含 `isNull(type)`,对 `EMBEDDING` 严格按 type 匹配;
- `buildDefaultQueryWrapper` 也有相同区分。
**章节来源**
- [ModelTypeEnum.java:20-54](file://ai-server/src/main/java/com/zsjz/ai/common/enums/ModelTypeEnum.java#L20-L54)
- [AgentModelServiceImpl.java:114-198](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentModelServiceImpl.java#L114-L198)
### 问题五:新增 Provider 后模型无法创建
现象:
- 创建模型时抛 400;
- 提示缺少模型 ID 或 API Key;
- 或构造 Model 时抛 500。
排查步骤:
1. 确认 `modelId` 非空;
2. 确认 `apiKey` 非空;
3. 确认 `provider` 能被正确归一化;
4. 确认 AgentScope 扩展已引入;
5. 查看 `AgentModelFactory.create` 日志和异常信息。
根因映射:
- 配置不完整直接抛 `ServerException(400, ...)`;
- ModelRegistry 解析失败抛 `ServerException(500, ...)`;
- pom 中必须引入对应 extension,否则 SPI 找不到前缀。
**章节来源**
- [AgentModelFactory.java:56-113](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/AgentModelFactory.java#L56-L113)
- [ai-server/pom.xml:1-200](file://ai-server/pom.xml#L1-L200)
### 问题六:Redis 不可用导致登录或会话异常
虽然 Redis 不属于 LLM Provider 本身,但文档中明确说明:
- Redis 是 Sa-Token 的硬依赖;
- Redis 不可用则所有 `StpUtil.*` 抛异常;
- 本项目没有降级实现;
- mem0 也依赖 Redis。
排查步骤:
1. 检查 `spring.data.redis.host` 和 `port`;
2. 确认 Redis 服务可连接;
3. 检查网络、密码、ACL;
4. 不要误以为 LLM 不通是 Redis 问题——LLM 不通主要看 `/models/{id}/test`。
**章节来源**
- [application-dev.yaml:1-87](file://ai-server/src/main/resources/application-dev.yaml#L1-L87)
- [AI_AGENT.md:120-160](file://AI_AGENT.md#L120-L160)
## 结论
本项目将 LLM Provider 管理分为两层:
- **平台层**:`AgentModel`、`AgentModelProvider`、`AgentModelService`、`AgentModelController` 负责持久化、CRUD、默认模型隔离和连通性测试;
- **运行时层**:`AgentModelFactory` 借助 AgentScope SPI 将 DB 配置转换为 DashScope、OpenAI 兼容、Ollama 三类 Provider 实例。
这种设计的好处是:
- Provider 扩展集中在 pom 中,新增 Provider 主要依赖 AgentScope 扩展,而不必大量改动业务代码;
- 模型配置可动态管理,不需要重启服务;
- 默认模型按用途隔离,避免对话模型和向量模型互相污染;
- 连通性测试以结构化结果返回,适合前端展示诊断信息。
扩展建议:
- 新增 LLM Provider 时,优先确认其是否遵循 OpenAI 兼容协议;若是,可通过 `openai:` + baseUrl 接入;
- 若需 DashScope 原生能力,则确保 provider 名称命中 `dashscope` 或 `阿里`;
- 对本地 Ollama 场景,适当评估连通性测试超时是否足够;
- 对生产环境,建议补充更细粒度的 Provider 健康检查定时任务,而不仅依赖用户手动触发 `/models/{id}/test`。