# 模型服务商配置与路由设计文档 ## 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 { List query(ModelProviderQueryRequest request); ModelProviderResponse getResponseById(Long id); boolean saveOrUpdate(ModelProviderSaveRequest request); boolean checkHealth(Long id); } ``` ```java public interface IModelConfigService extends IService { List 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://: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`。 - 请求对象使用业务语义明确的 `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= ``` 约束: - 主密钥不入库。 - 前端不返回密钥明文。 - 修改密钥时只允许写入,不允许读取原文。 ## 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