Files
common_agent/docs/MODEL_PROVIDER_DESIGN.md

899 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 模型服务商配置与路由设计文档
## 1. 文档信息
| 项目 | 内容 |
|------|------|
| 所属项目 | Common Agent |
| 文档类型 | 设计文档 |
| 编写日期 | 2026-05-25 |
| 对应需求 | `docs/MODEL_PROVIDER_REQUIREMENTS.md` |
| 目标阶段 | RAG 向量导入与模型网关基础能力 |
## 2. 设计目标
本设计用于在 Common Agent 中新增模型服务商配置与模型路由能力,使系统可以统一接入 Ollama、硅基流动、百炼、OpenAI 等模型服务,并为 RAG 和后续 Agent 运行时提供统一模型调用入口。
核心设计目标:
1. 业务模块只依赖平台内部模型网关,不直接依赖具体服务商。
2. 首期优先支持 OpenAI-compatible 协议,减少多服务商适配成本。
3. 支持本地模型和云端模型混用,实现成本控制。
4. 支持 RAG Embedding 模型配置化,并保证同一知识库向量模型一致。
5. 支持调用日志,为排障、统计和后续成本分析打基础。
## 3. 总体架构
```mermaid
flowchart TD
Admin["管理控制台"] --> ProviderApi["模型配置 API"]
Rag["RAG 模块"] --> ModelGateway["ModelGateway"]
Agent["Agent 模块"] --> ModelGateway
ModelGateway --> Router["ModelRouteService"]
Router --> Config["模型配置与路由表"]
ModelGateway --> ClientFactory["ModelClientFactory"]
ClientFactory --> Ollama["Ollama OpenAI-compatible"]
ClientFactory --> SiliconFlow["SiliconFlow OpenAI-compatible"]
ClientFactory --> DashScope["DashScope OpenAI-compatible"]
ClientFactory --> Other["其他 OpenAI-compatible 服务"]
ModelGateway --> Log["ModelCallLogService"]
Rag --> RagChunk["rag_chunk"]
Rag --> RagEmbedding["rag_chunk_embedding"]
```
设计上将模型平台能力拆成四层:
| 层级 | 职责 |
|------|------|
| 配置层 | 管理服务商、模型、路由规则和知识库模型绑定 |
| 路由层 | 根据任务类型、范围和策略选择具体模型 |
| 调用层 | 通过统一客户端调用 Chat、Embedding、Rerank 等能力 |
| 观测层 | 记录调用日志、耗时、token、费用估算和错误信息 |
## 4. 包结构设计
建议新增后端包:
```text
src/main/java/com/bruce/modelprovider
├── controller
├── dto
│ ├── request
│ └── response
├── entity
├── enums
├── mapper
├── service
│ └── impl
├── gateway
├── client
└── config
```
各包职责:
| 包 | 职责 |
|----|------|
| `controller` | 对外暴露服务商、模型、路由规则、日志查询接口 |
| `dto` | 请求和响应对象,不直接暴露实体 |
| `entity` | 数据库实体,继承 `BaseEntity` |
| `enums` | 服务商类型、模型类型、任务类型、路由策略、调用状态 |
| `mapper` | MyBatis-Plus `BaseMapper` |
| `service` | 配置管理、路由选择、调用日志 |
| `gateway` | 面向业务模块的模型调用入口 |
| `client` | 具体协议客户端,例如 OpenAI-compatible 客户端 |
| `config` | 模型平台配置,例如默认超时、批量大小、密钥加密开关 |
## 5. 核心数据模型
### 5.1 `model_provider` 模型服务商表
用于保存服务商基础配置。
```sql
CREATE TABLE model_provider (
id BIGSERIAL PRIMARY KEY,
provider_code VARCHAR(64) NOT NULL,
provider_name VARCHAR(100) NOT NULL,
provider_type VARCHAR(50) NOT NULL,
protocol_type VARCHAR(50) NOT NULL DEFAULT 'OPENAI_COMPATIBLE',
base_url VARCHAR(500) NOT NULL,
auth_type VARCHAR(50) NOT NULL DEFAULT 'BEARER_TOKEN',
secret_ref VARCHAR(200),
api_key_cipher TEXT,
timeout_ms INTEGER NOT NULL DEFAULT 60000,
priority INTEGER NOT NULL DEFAULT 100,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
health_status VARCHAR(50) NOT NULL DEFAULT 'UNKNOWN',
last_health_check_time TIMESTAMP,
version INTEGER NOT NULL DEFAULT 1,
create_time TIMESTAMP,
update_time TIMESTAMP,
remark VARCHAR(500) DEFAULT '',
create_by VARCHAR(64),
update_by VARCHAR(64),
CONSTRAINT uk_model_provider_code UNIQUE (provider_code)
);
```
字段说明:
| 字段 | 说明 |
|------|------|
| `provider_code` | 服务商编码,例如 `ollama-main``siliconflow` |
| `provider_type` | 服务商类型,例如 `OLLAMA``SILICONFLOW``DASHSCOPE``OPENAI``CUSTOM` |
| `protocol_type` | 协议类型,首期使用 `OPENAI_COMPATIBLE` |
| `base_url` | API 基础地址,例如 `https://api.siliconflow.cn/v1` |
| `secret_ref` | 密钥引用,例如环境变量名或配置中心键 |
| `api_key_cipher` | 可选的加密密钥内容,前端不返回 |
| `health_status` | 健康检查状态 |
### 5.2 `model_config` 模型配置表
用于保存服务商下的具体模型。
```sql
CREATE TABLE model_config (
id BIGSERIAL PRIMARY KEY,
provider_id BIGINT NOT NULL,
model_code VARCHAR(100) NOT NULL,
model_name VARCHAR(200) NOT NULL,
upstream_model VARCHAR(200) NOT NULL,
model_type VARCHAR(50) NOT NULL,
context_window INTEGER,
max_output_tokens INTEGER,
embedding_dimension INTEGER,
input_price_per_1k NUMERIC(12, 8),
output_price_per_1k NUMERIC(12, 8),
local_model BOOLEAN NOT NULL DEFAULT FALSE,
default_model BOOLEAN NOT NULL DEFAULT FALSE,
capabilities_json JSONB NOT NULL DEFAULT '{}'::jsonb,
options_json JSONB NOT NULL DEFAULT '{}'::jsonb,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
version INTEGER NOT NULL DEFAULT 1,
create_time TIMESTAMP,
update_time TIMESTAMP,
remark VARCHAR(500) DEFAULT '',
create_by VARCHAR(64),
update_by VARCHAR(64),
CONSTRAINT uk_model_config_provider_code UNIQUE (provider_id, model_code),
CONSTRAINT fk_model_config_provider_id FOREIGN KEY (provider_id) REFERENCES model_provider (id)
);
```
字段说明:
| 字段 | 说明 |
|------|------|
| `model_code` | 平台内部模型编码 |
| `model_name` | 展示名称 |
| `upstream_model` | 上游真实模型名,例如 `Qwen/Qwen3-Embedding-0.6B``qwen2.5:7b` |
| `model_type` | `CHAT``EMBEDDING``RERANK``MULTIMODAL` |
| `embedding_dimension` | Embedding 输出维度RAG 首期使用 1024 |
| `capabilities_json` | 能力标签例如是否支持工具调用、视觉、JSON 输出 |
| `options_json` | 模型调用默认参数,例如 `temperature``topP``dimensions` |
### 5.3 `model_route_rule` 模型路由规则表
用于根据任务类型和范围选择模型。
```sql
CREATE TABLE model_route_rule (
id BIGSERIAL PRIMARY KEY,
route_code VARCHAR(100) NOT NULL,
route_name VARCHAR(100) NOT NULL,
task_type VARCHAR(50) NOT NULL,
match_scope VARCHAR(50) NOT NULL DEFAULT 'GLOBAL',
scope_id BIGINT,
primary_model_id BIGINT NOT NULL,
fallback_model_ids_json JSONB NOT NULL DEFAULT '[]'::jsonb,
route_strategy VARCHAR(50) NOT NULL DEFAULT 'MANUAL',
max_latency_ms INTEGER,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
version INTEGER NOT NULL DEFAULT 1,
create_time TIMESTAMP,
update_time TIMESTAMP,
remark VARCHAR(500) DEFAULT '',
create_by VARCHAR(64),
update_by VARCHAR(64),
CONSTRAINT uk_model_route_rule_code UNIQUE (route_code),
CONSTRAINT fk_model_route_primary_model_id FOREIGN KEY (primary_model_id) REFERENCES model_config (id)
);
```
字段说明:
| 字段 | 说明 |
|------|------|
| `task_type` | 任务类型,例如 `RAG_EMBEDDING``CHAT_SIMPLE``AGENT_PLANNING` |
| `match_scope` | 匹配范围,例如 `GLOBAL``RAG_STORE``AGENT` |
| `scope_id` | 范围 ID例如知识库 ID 或 Agent ID |
| `primary_model_id` | 主模型 |
| `fallback_model_ids_json` | 备用模型 ID 列表 |
| `route_strategy` | `LOCAL_FIRST``COST_FIRST``QUALITY_FIRST``MANUAL` |
### 5.4 `rag_store_model_config` 知识库模型绑定表
用于固定知识库的 Embedding 模型和维度,避免同一知识库混用向量空间。
```sql
CREATE TABLE rag_store_model_config (
id BIGSERIAL PRIMARY KEY,
store_id BIGINT NOT NULL,
embedding_model_id BIGINT NOT NULL,
embedding_dimension INTEGER NOT NULL DEFAULT 1024,
chunk_strategy INTEGER,
chunk_size INTEGER,
chunk_overlap INTEGER,
delimiter VARCHAR(50),
active BOOLEAN NOT NULL DEFAULT TRUE,
index_version INTEGER NOT NULL DEFAULT 1,
version INTEGER NOT NULL DEFAULT 1,
create_time TIMESTAMP,
update_time TIMESTAMP,
remark VARCHAR(500) DEFAULT '',
create_by VARCHAR(64),
update_by VARCHAR(64),
CONSTRAINT uk_rag_store_model_config_store_active UNIQUE (store_id, active),
CONSTRAINT fk_rag_store_model_config_store_id FOREIGN KEY (store_id) REFERENCES rag_store (id),
CONSTRAINT fk_rag_store_model_config_embedding_model_id FOREIGN KEY (embedding_model_id) REFERENCES model_config (id)
);
```
设计约束:
- 首期每个知识库只有一个生效配置。
- 更换 Embedding 模型或维度时,`index_version` 增加,并触发重建索引。
- 检索时只使用当前生效配置对应的向量。
### 5.5 `model_call_log` 模型调用日志表
用于记录模型调用行为。
```sql
CREATE TABLE model_call_log (
id BIGSERIAL PRIMARY KEY,
request_id VARCHAR(64) NOT NULL,
provider_id BIGINT,
model_id BIGINT,
task_type VARCHAR(50) NOT NULL,
biz_type VARCHAR(50),
biz_id VARCHAR(100),
call_type VARCHAR(50) NOT NULL,
status VARCHAR(50) NOT NULL,
prompt_tokens INTEGER,
completion_tokens INTEGER,
total_tokens INTEGER,
estimated_cost NUMERIC(14, 8),
duration_ms INTEGER,
request_hash VARCHAR(64),
error_code VARCHAR(100),
error_message VARCHAR(1000),
create_time TIMESTAMP,
remark VARCHAR(500) DEFAULT '',
CONSTRAINT uk_model_call_log_request_id UNIQUE (request_id)
);
```
字段说明:
| 字段 | 说明 |
|------|------|
| `request_id` | 单次模型调用请求 ID |
| `task_type` | 任务类型 |
| `biz_type` | 业务类型,例如 `RAG_DOCUMENT_INDEX` |
| `biz_id` | 业务 ID例如文档 ID |
| `call_type` | `CHAT``EMBEDDING``RERANK` |
| `status` | `SUCCESS``FAILED``TIMEOUT``FALLBACK_SUCCESS` |
| `request_hash` | 请求内容哈希,用于排障和幂等分析,不保存完整敏感内容 |
## 6. 枚举设计
新增枚举应实现现有 `PersistableSysEnumDefinition`,并同步到 `sys_enum`
| 枚举 | 值 |
|------|----|
| `ModelProviderTypeEnum` | `OLLAMA``SILICONFLOW``DASHSCOPE``OPENAI``CUSTOM` |
| `ModelProtocolTypeEnum` | `OPENAI_COMPATIBLE` |
| `ModelTypeEnum` | `CHAT``EMBEDDING``RERANK``MULTIMODAL` |
| `ModelTaskTypeEnum` | `RAG_EMBEDDING``RAG_QUERY_EMBEDDING``RAG_ANSWER``CHAT_SIMPLE``CHAT_COMPLEX``AGENT_PLANNING``RERANK` |
| `ModelRouteStrategyEnum` | `LOCAL_FIRST``COST_FIRST``QUALITY_FIRST``MANUAL` |
| `ModelCallStatusEnum` | `SUCCESS``FAILED``TIMEOUT``FALLBACK_SUCCESS` |
| `ModelHealthStatusEnum` | `UNKNOWN``HEALTHY``UNHEALTHY` |
## 7. 服务接口设计
### 7.1 配置管理服务
```java
public interface IModelProviderService extends IService<ModelProvider> {
List<ModelProviderResponse> query(ModelProviderQueryRequest request);
ModelProviderResponse getResponseById(Long id);
boolean saveOrUpdate(ModelProviderSaveRequest request);
boolean checkHealth(Long id);
}
```
```java
public interface IModelConfigService extends IService<ModelConfig> {
List<ModelConfigResponse> query(ModelConfigQueryRequest request);
ModelConfigResponse getResponseById(Long id);
boolean saveOrUpdate(ModelConfigSaveRequest request);
ModelConfig getEnabledModel(Long modelId);
}
```
### 7.2 路由服务
```java
public interface IModelRouteService {
ModelRouteDecision route(ModelRouteContext context);
}
```
`ModelRouteContext` 应包含:
- `taskType`
- `matchScope`
- `scopeId`
- `requiredModelType`
- `preferredLocal`
- `requiredEmbeddingDimension`
- `bizType`
- `bizId`
`ModelRouteDecision` 应包含:
- 主模型。
- 备用模型列表。
- 路由策略。
- 决策原因。
### 7.3 模型网关
```java
public interface EmbeddingModelGateway {
EmbeddingResult embed(EmbeddingRequest request);
}
```
```java
public interface ChatModelGateway {
ChatResult chat(ChatRequest request);
}
```
`EmbeddingRequest` 应包含:
- 文本列表。
- 任务类型。
- 匹配范围。
- 范围 ID。
- 业务类型。
- 业务 ID。
- 期望维度。
`EmbeddingResult` 应包含:
- 模型 ID。
- 模型名称。
- 维度。
- 向量列表。
- 调用日志 ID。
## 8. 客户端设计
首期优先实现 `OpenAiCompatibleModelClient`,统一调用以下接口:
- `POST /v1/embeddings`
- `POST /v1/chat/completions`
OpenAI-compatible 客户端输入来自数据库配置:
| 配置来源 | 字段 |
|----------|------|
| `model_provider.base_url` | 服务基础地址 |
| `model_provider.secret_ref` / `api_key_cipher` | 鉴权信息 |
| `model_config.upstream_model` | 上游模型名 |
| `model_config.options_json` | 调用参数 |
| `model_config.embedding_dimension` | Embedding 维度 |
### 8.1 Spring AI 使用方式
项目可以在两个阶段使用 Spring AI
第一阶段:使用项目自定义 `ModelGateway` 和 OpenAI-compatible HTTP 客户端,优先解决多服务商动态配置问题。
第二阶段:在稳定后引入 Spring AI 的 `EmbeddingModel``ChatModel` 抽象或适配器,将动态客户端包装为平台内部统一接口。
这样设计的原因是 Spring Boot 自动配置更适合单默认服务商,而本项目需要从数据库动态选择多个 provider。业务层保持 `ModelGateway` 抽象,后续替换底层实现不会影响 RAG 和 Agent。
### 8.2 Ollama 适配
Ollama 使用 OpenAI-compatible 地址:
```text
http://<ollama-host>:11434/v1
```
配置示例:
| 字段 | 示例 |
|------|------|
| `provider_code` | `ollama-main` |
| `provider_type` | `OLLAMA` |
| `protocol_type` | `OPENAI_COMPATIBLE` |
| `base_url` | `http://10.0.0.10:11434/v1` |
| `auth_type` | `NONE``BEARER_TOKEN` |
| `model_config.upstream_model` | `qwen2.5:7b` |
部署建议:
- 开发环境可以通过内网访问。
- 生产环境不要直接开放 11434 到公网。
- 推荐使用 VPN、Tailscale、Cloudflare Tunnel、Nginx 鉴权反向代理或安全网关。
- 如果必须公网访问,需要 HTTPS、鉴权、IP 白名单和访问日志。
### 8.3 硅基流动适配
配置示例:
| 字段 | 示例 |
|------|------|
| `provider_code` | `siliconflow` |
| `provider_type` | `SILICONFLOW` |
| `protocol_type` | `OPENAI_COMPATIBLE` |
| `base_url` | `https://api.siliconflow.cn/v1` |
| `secret_ref` | `SILICONFLOW_API_KEY` |
| `model_config.upstream_model` | `Qwen/Qwen3-Embedding-0.6B` |
| `embedding_dimension` | `1024` |
RAG 首期推荐使用 1024 维 Embedding匹配当前 `rag_chunk_embedding.embedding VECTOR(1024)`
### 8.4 百炼适配
百炼 OpenAI-compatible 配置示例:
| 字段 | 示例 |
|------|------|
| `provider_code` | `dashscope` |
| `provider_type` | `DASHSCOPE` |
| `protocol_type` | `OPENAI_COMPATIBLE` |
| `base_url` | `https://dashscope.aliyuncs.com/compatible-mode/v1` |
| `secret_ref` | `DASHSCOPE_API_KEY` |
| `model_config.upstream_model` | `text-embedding-v4` |
| `embedding_dimension` | `1024` |
## 9. 路由策略设计
### 9.1 路由优先级
模型路由按以下顺序匹配:
1. 业务范围精确规则,例如某个知识库或某个 Agent。
2. 任务类型规则,例如 `RAG_EMBEDDING`
3. 全局默认规则。
4. 模型类型默认模型。
如果没有匹配到模型,应返回清晰错误,不隐式选择不确定模型。
### 9.2 策略说明
| 策略 | 行为 |
|------|------|
| `MANUAL` | 使用规则中指定的主模型 |
| `LOCAL_FIRST` | 优先选择本地模型,失败后使用备用云端模型 |
| `COST_FIRST` | 在可用模型中优先选择成本低的模型 |
| `QUALITY_FIRST` | 优先选择质量更高或优先级更高的模型 |
首期可以只实现 `MANUAL``LOCAL_FIRST`,其余策略先完成数据结构和枚举。
### 9.3 失败兜底
主模型调用失败时:
1. 记录主模型失败日志。
2. 判断是否存在备用模型。
3. 按备用模型顺序重试。
4. 若备用模型成功,返回结果并记录 `FALLBACK_SUCCESS`
5. 若全部失败,返回最后一次错误,并将业务状态更新为失败。
RAG 向量导入首期应谨慎使用 fallback。Embedding 模型 fallback 只有在维度和语义模型族一致时才允许自动切换,否则应失败并提示重建或重新配置。
## 10. RAG 向量导入设计
### 10.1 当前 RAG 状态
当前项目已有:
- `rag_document`
- `rag_document_parse_result`
- `rag_chunk`
- `rag_chunk_embedding`
- `RagDocumentParseServiceImpl`
- `RagDocumentChunkServiceImpl`
- `FixedLengthChunker`
- `DelimiterChunker`
下一步需要把切片服务和 Embedding 网关串起来。
### 10.2 目标流程
```mermaid
sequenceDiagram
participant User as 用户/后台
participant Doc as RagDocumentService
participant Parse as RagDocumentParseService
participant Chunk as RagDocumentChunkService
participant Gateway as EmbeddingModelGateway
participant Provider as 上游模型服务
participant DB as PostgreSQL
User->>Doc: 上传文档
Doc->>Parse: 自动解析
Parse->>DB: 保存解析快照
Parse->>DB: 更新 parseStatus=PARSED
Doc->>Chunk: 提交切片索引任务
Chunk->>DB: 更新 indexStatus=INDEXING
Chunk->>DB: 读取解析快照
Chunk->>DB: 删除旧切片和旧向量
Chunk->>DB: 写入 rag_chunk
Chunk->>Gateway: 批量生成 Embedding
Gateway->>Provider: POST /v1/embeddings
Provider-->>Gateway: 返回向量
Gateway->>DB: 记录 model_call_log
Chunk->>DB: 写入 rag_chunk_embedding
Chunk->>DB: 更新 indexStatus=INDEXED
```
### 10.3 状态流转
`rag_document.index_status` 流转:
```text
PENDING -> INDEXING -> INDEXED
PENDING -> INDEXING -> FAILED
INDEXED -> INDEXING -> INDEXED
INDEXED -> INDEXING -> FAILED
```
失败时应写入 `rag_document.error_message`
### 10.4 切片与向量写入规则
1. 生成新切片前,先删除当前文档旧向量,再删除旧切片。
2. 新切片保存成功后,再批量调用 Embedding。
3. Embedding 成功后写入 `rag_chunk_embedding`
4. 每条 embedding 记录保存 `embedding_model``embedding_dimension``content_hash`
5. `embedding` 字段以 pgvector 可接受的字符串格式保存,例如 `[0.1,0.2,0.3]`
6. 向量数量必须等于切片数量,否则本次索引失败。
### 10.5 知识库模型绑定
RAG 索引选择模型的优先级:
1. `rag_store_model_config` 中该知识库绑定的 Embedding 模型。
2. `model_route_rule``match_scope=RAG_STORE` 的规则。
3. `model_route_rule``task_type=RAG_EMBEDDING` 的全局规则。
4. 全局默认 Embedding 模型。
如果以上都不存在,索引任务失败并提示需要配置 Embedding 模型。
## 11. API 设计
### 11.1 服务商接口
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/model/providers/query` | 查询服务商 |
| GET | `/api/model/providers/detail` | 服务商详情 |
| POST | `/api/model/providers/save` | 新增或修改服务商 |
| POST | `/api/model/providers/delete` | 删除服务商 |
| POST | `/api/model/providers/checkHealth` | 健康检查 |
服务商详情接口不返回 `apiKeyCipher` 明文。
### 11.2 模型接口
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/model/configs/query` | 查询模型 |
| GET | `/api/model/configs/detail` | 模型详情 |
| POST | `/api/model/configs/save` | 新增或修改模型 |
| POST | `/api/model/configs/delete` | 删除模型 |
### 11.3 路由规则接口
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/model/routes/query` | 查询路由规则 |
| GET | `/api/model/routes/detail` | 路由规则详情 |
| POST | `/api/model/routes/save` | 新增或修改路由规则 |
| POST | `/api/model/routes/delete` | 删除路由规则 |
### 11.4 调用日志接口
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/model/call-logs/query` | 查询调用日志 |
| GET | `/api/model/call-logs/detail` | 调用日志详情 |
### 11.5 RAG 模型配置接口
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/rag/store/modelConfig` | 查询知识库模型配置 |
| POST | `/api/rag/store/modelConfig/save` | 保存知识库模型配置 |
| POST | `/api/rag/store/rebuildIndex` | 重建知识库索引 |
## 12. DTO 设计要点
所有 API 保持现有项目规范:
- Controller 返回 `RequestResult<T>`
- 请求对象使用业务语义明确的 `Request` 类,例如 `ModelProviderSaveRequest`
- 响应对象使用业务语义明确的 `Response` 类,例如 `ModelProviderResponse`
- 响应 DTO 提供 `fromEntity()` 静态转换。
- Long ID 输出给前端时使用 `ToStringSerializer`
`ModelProviderResponse` 不包含完整密钥,只包含:
- `secretRef`
- `hasApiKey`
- `authType`
## 13. 密钥处理设计
### 13.1 推荐方式
首期推荐使用 `secret_ref`
```text
model_provider.secret_ref = SILICONFLOW_API_KEY
```
运行时从环境变量读取:
```text
SILICONFLOW_API_KEY=sk-***
```
优点:
- 数据库不保存密钥。
- 本地、测试、生产可以使用不同环境变量。
- 实现简单,风险较低。
### 13.2 可选加密方式
如需在数据库中保存密钥,应保存 `api_key_cipher`,并使用环境变量中的主密钥加解密:
```text
COMMON_AGENT_SECRET_KEY=<secret-from-environment>
```
约束:
- 主密钥不入库。
- 前端不返回密钥明文。
- 修改密钥时只允许写入,不允许读取原文。
## 14. 调用日志与费用估算
调用日志记录在 `model_call_log`
费用估算规则:
```text
estimatedCost = promptTokens / 1000 * inputPricePer1k
+ completionTokens / 1000 * outputPricePer1k
```
Embedding 模型通常只使用输入 token 费用。若上游不返回 token 信息,首期可以记录空值,后续通过本地 tokenizer 或文本长度估算。
日志脱敏规则:
- 不记录完整 API Key。
- 不默认记录完整 prompt。
- 可记录请求哈希。
- 错误信息截断到 1000 字以内。
## 15. 健康检查设计
健康检查目标是判断服务商是否可用。
首期检查方式:
- OpenAI-compatible 服务调用 `/v1/models`
- Ollama 同样可通过 OpenAI-compatible `/v1/models` 检查。
- 如果服务商不支持 `/v1/models`,可配置跳过健康检查或使用轻量模型调用。
健康状态:
| 状态 | 说明 |
|------|------|
| `UNKNOWN` | 尚未检查 |
| `HEALTHY` | 最近一次检查成功 |
| `UNHEALTHY` | 最近一次检查失败 |
## 16. 异常处理设计
常见异常:
| 场景 | 处理方式 |
|------|----------|
| 服务商停用 | 路由阶段直接失败 |
| 模型停用 | 路由阶段跳过或失败 |
| 缺少 API Key | 调用前失败,提示密钥未配置 |
| 上游超时 | 记录 `TIMEOUT`,尝试备用模型 |
| 上游返回错误 | 记录 `FAILED`,保留错误码和摘要 |
| Embedding 维度不匹配 | RAG 索引失败,不写入向量 |
| 向量数量不匹配 | RAG 索引失败,清理本次中间数据 |
## 17. 前端页面设计
前端延续当前后台风格,保持信息密度和可扫描性。
建议新增页面:
| 页面 | 功能 |
|------|------|
| 模型服务商 | 服务商列表、启停用、健康检查、编辑 |
| 模型配置 | 模型列表、类型筛选、价格和能力配置 |
| 路由规则 | 按任务类型配置主模型和备用模型 |
| 调用日志 | 查询调用状态、耗时、服务商、模型、错误 |
| 知识库模型配置 | 在知识库详情中配置 Embedding 模型和重建索引 |
首期后端优先,前端可以在 RAG 最小闭环完成后接入。
## 18. 测试设计
### 18.1 单元测试
需要覆盖:
- 实体字段结构。
- Mapper 和 Service 继承结构。
- DTO 转换。
- 服务商编码唯一校验。
- 模型编码唯一校验。
- 路由优先级。
- 密钥引用解析。
- Embedding 维度校验。
### 18.2 RAG 集成测试
需要覆盖:
- 文档解析快照转切片。
- 切片批量调用 Embedding 网关。
- 向量数量与切片数量一致。
- 向量写入 `rag_chunk_embedding`
- 索引状态成功流转。
- Embedding 失败时索引状态为 `FAILED`
### 18.3 客户端测试
OpenAI-compatible 客户端使用 Mock Web Server 或类似方式测试:
- `/v1/embeddings` 成功响应。
- `/v1/chat/completions` 成功响应。
- 401 鉴权失败。
- 429 限流。
- 5xx 上游错误。
- 超时。
## 19. 实施计划
### 19.1 第一阶段:配置数据结构
1. 新增 SQL 脚本。
2. 新增实体、Mapper、Service、Controller。
3. 新增枚举并同步 `sys_enum` 测试。
4. 新增结构稳定性测试。
### 19.2 第二阶段Embedding 网关
1. 新增 `EmbeddingModelGateway`
2. 新增 OpenAI-compatible Embedding 客户端。
3. 新增密钥解析器。
4. 新增调用日志服务。
5. 完成 Mock 单元测试。
### 19.3 第三阶段RAG 向量导入
1. 扩展 `RagDocumentChunkServiceImpl`
2. 切片后调用 Embedding 网关。
3. 写入 `rag_chunk_embedding`
4. 更新 `indexStatus`
5. 完成失败回滚和日志记录。
### 19.4 第四阶段:路由与前端
1. 新增路由规则服务。
2. RAG 按知识库配置或路由规则选择模型。
3. 接入前端模型配置页面。
4. 接入调用日志查询页面。
## 20. 配置示例
### 20.1 Ollama 服务商
```json
{
"providerCode": "ollama-main",
"providerName": "远程 Ollama",
"providerType": "OLLAMA",
"protocolType": "OPENAI_COMPATIBLE",
"baseUrl": "http://10.0.0.10:11434/v1",
"authType": "NONE",
"timeoutMs": 120000,
"enabled": true
}
```
### 20.2 硅基流动 Embedding 模型
```json
{
"providerId": "1001",
"modelCode": "siliconflow-qwen3-embedding-0_6b",
"modelName": "硅基流动 Qwen3 Embedding 0.6B",
"upstreamModel": "Qwen/Qwen3-Embedding-0.6B",
"modelType": "EMBEDDING",
"embeddingDimension": 1024,
"localModel": false,
"enabled": true,
"optionsJson": {
"dimensions": 1024,
"encoding_format": "float"
}
}
```
### 20.3 RAG Embedding 路由规则
```json
{
"routeCode": "global-rag-embedding",
"routeName": "全局 RAG 向量模型",
"taskType": "RAG_EMBEDDING",
"matchScope": "GLOBAL",
"primaryModelId": "2001",
"fallbackModelIds": [],
"routeStrategy": "MANUAL",
"enabled": true
}
```
## 21. 风险与对策
| 风险 | 对策 |
|------|------|
| 动态多服务商与 Spring AI 自动配置存在差异 | 先用项目内部 `ModelGateway` 隔离,后续逐步适配 Spring AI |
| Ollama 远程服务暴露风险 | 文档和配置中明确生产环境必须通过安全通道访问 |
| Embedding 模型混用导致检索漂移 | 知识库绑定模型和维度,变更时重建索引 |
| 云端 API 费用不可控 | 路由策略、调用日志和费用估算逐步完善 |
| 上游接口返回格式差异 | 首期只承诺 OpenAI-compatible特殊服务商后续单独适配 |
| 大批量向量化超时 | 支持批量大小配置、异步任务和失败重试 |
## 22. 后续扩展
后续可以扩展:
- Rerank 模型网关。
- Agent 级模型配置。
- 用户级额度和限流。
- 模型质量评测。
- 模型调用缓存。
- Prompt 模板与模型绑定。
- 多索引版本并存。
- 调用日志聚合报表。
## 23. 参考资料
- Ollama OpenAI-compatible API: https://docs.ollama.com/api/openai-compatibility
- Ollama Embeddings: https://docs.ollama.com/capabilities/embeddings
- Spring AI OpenAI Embeddings: https://docs.spring.io/spring-ai/reference/api/embeddings/openai-embeddings.html
- SiliconFlow Embedding 模型列表: https://www.siliconflow.com/models/embedding