From 5d7ca5b31f422e9354cc57eb97322704b45ee18d Mon Sep 17 00:00:00 2001 From: bruce Date: Wed, 27 May 2026 22:14:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(model-provider):=20=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E6=9C=8D=E5=8A=A1=E5=95=86=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E4=B8=8ERAG=E5=90=91=E9=87=8F=E6=8E=A5=E5=85=A5=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/MODEL_PROVIDER_SCHEMA.sql | 222 ++++++++++++++++++ .../client/OpenAiCompatibleModelClient.java | 23 ++ .../OpenAiCompatibleModelClientImpl.java | 112 +++++++++ .../controller/ModelCallLogController.java | 40 ++++ .../controller/ModelConfigController.java | 56 +++++ .../controller/ModelProviderController.java | 64 +++++ .../controller/ModelRouteRuleController.java | 56 +++++ .../RagStoreModelConfigController.java | 46 ++++ .../dto/request/ModelCallLogQueryRequest.java | 18 ++ .../dto/request/ModelConfigSaveRequest.java | 25 ++ .../dto/request/ModelProviderSaveRequest.java | 26 ++ .../request/ModelRouteRuleSaveRequest.java | 25 ++ .../RagStoreModelConfigSaveRequest.java | 22 ++ .../dto/response/ModelCallLogResponse.java | 39 +++ .../dto/response/ModelConfigResponse.java | 39 +++ .../dto/response/ModelProviderResponse.java | 42 ++++ .../dto/response/ModelRouteRuleResponse.java | 39 +++ .../response/RagStoreModelConfigResponse.java | 38 +++ .../modelprovider/entity/ModelCallLog.java | 91 +++++++ .../modelprovider/entity/ModelConfig.java | 88 +++++++ .../modelprovider/entity/ModelProvider.java | 81 +++++++ .../modelprovider/entity/ModelRouteRule.java | 66 ++++++ .../entity/RagStoreModelConfig.java | 60 +++++ .../enums/ModelCallStatusEnum.java | 35 +++ .../enums/ModelHealthStatusEnum.java | 34 +++ .../enums/ModelProtocolTypeEnum.java | 49 ++++ .../enums/ModelProviderTypeEnum.java | 53 +++++ .../enums/ModelRouteStrategyEnum.java | 35 +++ .../enums/ModelTaskTypeEnum.java | 38 +++ .../modelprovider/enums/ModelTypeEnum.java | 52 ++++ .../gateway/EmbeddingModelGateway.java | 14 ++ .../gateway/EmbeddingModelGatewayImpl.java | 140 +++++++++++ .../gateway/EmbeddingRequest.java | 22 ++ .../gateway/EmbeddingResult.java | 21 ++ .../mapper/ModelCallLogMapper.java | 15 ++ .../mapper/ModelConfigMapper.java | 15 ++ .../mapper/ModelProviderMapper.java | 15 ++ .../mapper/ModelRouteRuleMapper.java | 15 ++ .../mapper/RagStoreModelConfigMapper.java | 15 ++ .../route/ModelRouteContext.java | 21 ++ .../route/ModelRouteDecision.java | 21 ++ .../service/IModelCallLogService.java | 21 ++ .../service/IModelConfigService.java | 33 +++ .../service/IModelProviderService.java | 33 +++ .../service/IModelRouteRuleService.java | 29 +++ .../service/IModelRouteService.java | 17 ++ .../service/IRagStoreModelConfigService.java | 27 +++ .../service/impl/ModelCallLogServiceImpl.java | 42 ++++ .../service/impl/ModelConfigServiceImpl.java | 93 ++++++++ .../impl/ModelProviderServiceImpl.java | 132 +++++++++++ .../impl/ModelRouteRuleServiceImpl.java | 75 ++++++ .../service/impl/ModelRouteServiceImpl.java | 153 ++++++++++++ .../impl/RagStoreModelConfigServiceImpl.java | 88 +++++++ .../impl/RagDocumentChunkServiceImpl.java | 88 +++++++ .../enumconfig/EnumDefinitionTests.java | 40 ++++ .../enumconfig/SysEnumDataInitTests.java | 24 +- .../ModelProviderComponentStructureTests.java | 100 ++++++++ 57 files changed, 2922 insertions(+), 1 deletion(-) create mode 100644 docs/MODEL_PROVIDER_SCHEMA.sql create mode 100644 src/main/java/com/bruce/modelprovider/client/OpenAiCompatibleModelClient.java create mode 100644 src/main/java/com/bruce/modelprovider/client/OpenAiCompatibleModelClientImpl.java create mode 100644 src/main/java/com/bruce/modelprovider/controller/ModelCallLogController.java create mode 100644 src/main/java/com/bruce/modelprovider/controller/ModelConfigController.java create mode 100644 src/main/java/com/bruce/modelprovider/controller/ModelProviderController.java create mode 100644 src/main/java/com/bruce/modelprovider/controller/ModelRouteRuleController.java create mode 100644 src/main/java/com/bruce/modelprovider/controller/RagStoreModelConfigController.java create mode 100644 src/main/java/com/bruce/modelprovider/dto/request/ModelCallLogQueryRequest.java create mode 100644 src/main/java/com/bruce/modelprovider/dto/request/ModelConfigSaveRequest.java create mode 100644 src/main/java/com/bruce/modelprovider/dto/request/ModelProviderSaveRequest.java create mode 100644 src/main/java/com/bruce/modelprovider/dto/request/ModelRouteRuleSaveRequest.java create mode 100644 src/main/java/com/bruce/modelprovider/dto/request/RagStoreModelConfigSaveRequest.java create mode 100644 src/main/java/com/bruce/modelprovider/dto/response/ModelCallLogResponse.java create mode 100644 src/main/java/com/bruce/modelprovider/dto/response/ModelConfigResponse.java create mode 100644 src/main/java/com/bruce/modelprovider/dto/response/ModelProviderResponse.java create mode 100644 src/main/java/com/bruce/modelprovider/dto/response/ModelRouteRuleResponse.java create mode 100644 src/main/java/com/bruce/modelprovider/dto/response/RagStoreModelConfigResponse.java create mode 100644 src/main/java/com/bruce/modelprovider/entity/ModelCallLog.java create mode 100644 src/main/java/com/bruce/modelprovider/entity/ModelConfig.java create mode 100644 src/main/java/com/bruce/modelprovider/entity/ModelProvider.java create mode 100644 src/main/java/com/bruce/modelprovider/entity/ModelRouteRule.java create mode 100644 src/main/java/com/bruce/modelprovider/entity/RagStoreModelConfig.java create mode 100644 src/main/java/com/bruce/modelprovider/enums/ModelCallStatusEnum.java create mode 100644 src/main/java/com/bruce/modelprovider/enums/ModelHealthStatusEnum.java create mode 100644 src/main/java/com/bruce/modelprovider/enums/ModelProtocolTypeEnum.java create mode 100644 src/main/java/com/bruce/modelprovider/enums/ModelProviderTypeEnum.java create mode 100644 src/main/java/com/bruce/modelprovider/enums/ModelRouteStrategyEnum.java create mode 100644 src/main/java/com/bruce/modelprovider/enums/ModelTaskTypeEnum.java create mode 100644 src/main/java/com/bruce/modelprovider/enums/ModelTypeEnum.java create mode 100644 src/main/java/com/bruce/modelprovider/gateway/EmbeddingModelGateway.java create mode 100644 src/main/java/com/bruce/modelprovider/gateway/EmbeddingModelGatewayImpl.java create mode 100644 src/main/java/com/bruce/modelprovider/gateway/EmbeddingRequest.java create mode 100644 src/main/java/com/bruce/modelprovider/gateway/EmbeddingResult.java create mode 100644 src/main/java/com/bruce/modelprovider/mapper/ModelCallLogMapper.java create mode 100644 src/main/java/com/bruce/modelprovider/mapper/ModelConfigMapper.java create mode 100644 src/main/java/com/bruce/modelprovider/mapper/ModelProviderMapper.java create mode 100644 src/main/java/com/bruce/modelprovider/mapper/ModelRouteRuleMapper.java create mode 100644 src/main/java/com/bruce/modelprovider/mapper/RagStoreModelConfigMapper.java create mode 100644 src/main/java/com/bruce/modelprovider/route/ModelRouteContext.java create mode 100644 src/main/java/com/bruce/modelprovider/route/ModelRouteDecision.java create mode 100644 src/main/java/com/bruce/modelprovider/service/IModelCallLogService.java create mode 100644 src/main/java/com/bruce/modelprovider/service/IModelConfigService.java create mode 100644 src/main/java/com/bruce/modelprovider/service/IModelProviderService.java create mode 100644 src/main/java/com/bruce/modelprovider/service/IModelRouteRuleService.java create mode 100644 src/main/java/com/bruce/modelprovider/service/IModelRouteService.java create mode 100644 src/main/java/com/bruce/modelprovider/service/IRagStoreModelConfigService.java create mode 100644 src/main/java/com/bruce/modelprovider/service/impl/ModelCallLogServiceImpl.java create mode 100644 src/main/java/com/bruce/modelprovider/service/impl/ModelConfigServiceImpl.java create mode 100644 src/main/java/com/bruce/modelprovider/service/impl/ModelProviderServiceImpl.java create mode 100644 src/main/java/com/bruce/modelprovider/service/impl/ModelRouteRuleServiceImpl.java create mode 100644 src/main/java/com/bruce/modelprovider/service/impl/ModelRouteServiceImpl.java create mode 100644 src/main/java/com/bruce/modelprovider/service/impl/RagStoreModelConfigServiceImpl.java create mode 100644 src/test/java/com/bruce/modelprovider/ModelProviderComponentStructureTests.java diff --git a/docs/MODEL_PROVIDER_SCHEMA.sql b/docs/MODEL_PROVIDER_SCHEMA.sql new file mode 100644 index 0000000..e4b586c --- /dev/null +++ b/docs/MODEL_PROVIDER_SCHEMA.sql @@ -0,0 +1,222 @@ +-- 模型平台与RAG模型绑定核心表(首期手工维护,后续可迁移到 Flyway/Liquibase) + +CREATE TABLE IF NOT EXISTS 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) +); + +CREATE TABLE IF NOT EXISTS 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) +); + +CREATE TABLE IF NOT EXISTS 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) +); + +CREATE TABLE IF NOT EXISTS 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_embedding_model_id FOREIGN KEY (embedding_model_id) REFERENCES model_config (id) +); + +CREATE TABLE IF NOT EXISTS 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) +); + +COMMENT ON TABLE model_provider IS '模型服务商配置表'; +COMMENT ON COLUMN model_provider.id IS 'ID'; +COMMENT ON COLUMN model_provider.provider_code IS '服务商编码'; +COMMENT ON COLUMN model_provider.provider_name IS '服务商名称'; +COMMENT ON COLUMN model_provider.provider_type IS '服务商类型'; +COMMENT ON COLUMN model_provider.protocol_type IS '协议类型'; +COMMENT ON COLUMN model_provider.base_url IS '服务基础地址'; +COMMENT ON COLUMN model_provider.auth_type IS '鉴权类型'; +COMMENT ON COLUMN model_provider.secret_ref IS '密钥环境变量引用'; +COMMENT ON COLUMN model_provider.api_key_cipher IS '密钥密文'; +COMMENT ON COLUMN model_provider.timeout_ms IS '超时时间(毫秒)'; +COMMENT ON COLUMN model_provider.priority IS '优先级'; +COMMENT ON COLUMN model_provider.enabled IS '是否启用'; +COMMENT ON COLUMN model_provider.health_status IS '健康状态'; +COMMENT ON COLUMN model_provider.last_health_check_time IS '最近健康检查时间'; +COMMENT ON COLUMN model_provider.version IS '版本'; +COMMENT ON COLUMN model_provider.create_time IS '创建时间'; +COMMENT ON COLUMN model_provider.update_time IS '更新时间'; +COMMENT ON COLUMN model_provider.remark IS '备注'; +COMMENT ON COLUMN model_provider.create_by IS '创建者'; +COMMENT ON COLUMN model_provider.update_by IS '更新者'; + +COMMENT ON TABLE model_config IS '模型配置表'; +COMMENT ON COLUMN model_config.id IS 'ID'; +COMMENT ON COLUMN model_config.provider_id IS '服务商ID'; +COMMENT ON COLUMN model_config.model_code IS '模型编码'; +COMMENT ON COLUMN model_config.model_name IS '模型名称'; +COMMENT ON COLUMN model_config.upstream_model IS '上游模型名称'; +COMMENT ON COLUMN model_config.model_type IS '模型类型'; +COMMENT ON COLUMN model_config.context_window IS '上下文窗口大小'; +COMMENT ON COLUMN model_config.max_output_tokens IS '最大输出Token数'; +COMMENT ON COLUMN model_config.embedding_dimension IS '向量维度'; +COMMENT ON COLUMN model_config.input_price_per_1k IS '输入千Token单价'; +COMMENT ON COLUMN model_config.output_price_per_1k IS '输出千Token单价'; +COMMENT ON COLUMN model_config.local_model IS '是否本地模型'; +COMMENT ON COLUMN model_config.default_model IS '是否默认模型'; +COMMENT ON COLUMN model_config.capabilities_json IS '能力配置JSON'; +COMMENT ON COLUMN model_config.options_json IS '扩展选项JSON'; +COMMENT ON COLUMN model_config.enabled IS '是否启用'; +COMMENT ON COLUMN model_config.version IS '版本'; +COMMENT ON COLUMN model_config.create_time IS '创建时间'; +COMMENT ON COLUMN model_config.update_time IS '更新时间'; +COMMENT ON COLUMN model_config.remark IS '备注'; +COMMENT ON COLUMN model_config.create_by IS '创建者'; +COMMENT ON COLUMN model_config.update_by IS '更新者'; + +COMMENT ON TABLE model_route_rule IS '模型路由规则表'; +COMMENT ON COLUMN model_route_rule.id IS 'ID'; +COMMENT ON COLUMN model_route_rule.route_code IS '路由规则编码'; +COMMENT ON COLUMN model_route_rule.route_name IS '路由规则名称'; +COMMENT ON COLUMN model_route_rule.task_type IS '任务类型'; +COMMENT ON COLUMN model_route_rule.match_scope IS '匹配范围'; +COMMENT ON COLUMN model_route_rule.scope_id IS '匹配范围业务ID'; +COMMENT ON COLUMN model_route_rule.primary_model_id IS '主模型ID'; +COMMENT ON COLUMN model_route_rule.fallback_model_ids_json IS '降级模型ID列表JSON'; +COMMENT ON COLUMN model_route_rule.route_strategy IS '路由策略'; +COMMENT ON COLUMN model_route_rule.max_latency_ms IS '最大延迟限制(毫秒)'; +COMMENT ON COLUMN model_route_rule.enabled IS '是否启用'; +COMMENT ON COLUMN model_route_rule.version IS '版本'; +COMMENT ON COLUMN model_route_rule.create_time IS '创建时间'; +COMMENT ON COLUMN model_route_rule.update_time IS '更新时间'; +COMMENT ON COLUMN model_route_rule.remark IS '备注'; +COMMENT ON COLUMN model_route_rule.create_by IS '创建者'; +COMMENT ON COLUMN model_route_rule.update_by IS '更新者'; + +COMMENT ON TABLE rag_store_model_config IS '知识库模型配置表'; +COMMENT ON COLUMN rag_store_model_config.id IS 'ID'; +COMMENT ON COLUMN rag_store_model_config.store_id IS '知识库ID'; +COMMENT ON COLUMN rag_store_model_config.embedding_model_id IS 'Embedding模型ID'; +COMMENT ON COLUMN rag_store_model_config.embedding_dimension IS '向量维度'; +COMMENT ON COLUMN rag_store_model_config.chunk_strategy IS '切片策略'; +COMMENT ON COLUMN rag_store_model_config.chunk_size IS '切片大小'; +COMMENT ON COLUMN rag_store_model_config.chunk_overlap IS '切片重叠大小'; +COMMENT ON COLUMN rag_store_model_config.delimiter IS '切片分隔符'; +COMMENT ON COLUMN rag_store_model_config.active IS '是否生效'; +COMMENT ON COLUMN rag_store_model_config.index_version IS '索引版本号'; +COMMENT ON COLUMN rag_store_model_config.version IS '版本'; +COMMENT ON COLUMN rag_store_model_config.create_time IS '创建时间'; +COMMENT ON COLUMN rag_store_model_config.update_time IS '更新时间'; +COMMENT ON COLUMN rag_store_model_config.remark IS '备注'; +COMMENT ON COLUMN rag_store_model_config.create_by IS '创建者'; +COMMENT ON COLUMN rag_store_model_config.update_by IS '更新者'; + +COMMENT ON TABLE model_call_log IS '模型调用日志表'; +COMMENT ON COLUMN model_call_log.id IS 'ID'; +COMMENT ON COLUMN model_call_log.request_id IS '请求唯一ID'; +COMMENT ON COLUMN model_call_log.provider_id IS '服务商ID'; +COMMENT ON COLUMN model_call_log.model_id IS '模型ID'; +COMMENT ON COLUMN model_call_log.task_type IS '任务类型'; +COMMENT ON COLUMN model_call_log.biz_type IS '业务类型'; +COMMENT ON COLUMN model_call_log.biz_id IS '业务ID'; +COMMENT ON COLUMN model_call_log.call_type IS '调用类型'; +COMMENT ON COLUMN model_call_log.status IS '调用状态'; +COMMENT ON COLUMN model_call_log.prompt_tokens IS '输入Token数'; +COMMENT ON COLUMN model_call_log.completion_tokens IS '输出Token数'; +COMMENT ON COLUMN model_call_log.total_tokens IS '总Token数'; +COMMENT ON COLUMN model_call_log.estimated_cost IS '预估成本'; +COMMENT ON COLUMN model_call_log.duration_ms IS '耗时(毫秒)'; +COMMENT ON COLUMN model_call_log.request_hash IS '请求哈希'; +COMMENT ON COLUMN model_call_log.error_code IS '错误码'; +COMMENT ON COLUMN model_call_log.error_message IS '错误信息摘要'; +COMMENT ON COLUMN model_call_log.create_time IS '创建时间'; +COMMENT ON COLUMN model_call_log.remark IS '备注'; diff --git a/src/main/java/com/bruce/modelprovider/client/OpenAiCompatibleModelClient.java b/src/main/java/com/bruce/modelprovider/client/OpenAiCompatibleModelClient.java new file mode 100644 index 0000000..c2d7ff4 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/client/OpenAiCompatibleModelClient.java @@ -0,0 +1,23 @@ +package com.bruce.modelprovider.client; + +import com.bruce.modelprovider.entity.ModelConfig; +import com.bruce.modelprovider.entity.ModelProvider; + +import java.util.List; + +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public interface OpenAiCompatibleModelClient { + /** + * 方法 embeddings,用于定义接口能力契约。 + */ + List> embeddings(ModelProvider provider, ModelConfig model, List texts, Integer expectedDimension); + /** + * 方法 health,用于定义接口能力契约。 + */ + boolean health(ModelProvider provider); +} + + + diff --git a/src/main/java/com/bruce/modelprovider/client/OpenAiCompatibleModelClientImpl.java b/src/main/java/com/bruce/modelprovider/client/OpenAiCompatibleModelClientImpl.java new file mode 100644 index 0000000..b1e5afb --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/client/OpenAiCompatibleModelClientImpl.java @@ -0,0 +1,112 @@ +package com.bruce.modelprovider.client; + +import com.bruce.modelprovider.entity.ModelConfig; +import com.bruce.modelprovider.entity.ModelProvider; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestClient; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * OpenAI-compatible 客户端实现。 + *

+ * 说明: + * 1. 统一使用服务商 baseUrl 访问上游接口; + * 2. Embedding 调用使用 `/embeddings`; + * 3. 健康检查优先使用轻量接口 `/models`; + * 4. API Key 从 `secretRef` 对应环境变量读取,不在代码中硬编码。 + */ +@Component +/** + * OpenAiCompatibleModelClientImpl,负责模型平台对应层的职责。 + */ +public class OpenAiCompatibleModelClientImpl implements OpenAiCompatibleModelClient { + + /** + * 调用上游 Embedding 接口并解析向量数组。 + */ + @Override + @SuppressWarnings("unchecked") + /** + * 方法 embeddings,用于执行业务逻辑处理。 + */ + public List> embeddings(ModelProvider provider, ModelConfig model, List texts, Integer expectedDimension) { + RestClient client = RestClient.builder().baseUrl(provider.getBaseUrl()).build(); + Map body = new HashMap<>(); + body.put("model", model.getUpstreamModel()); + body.put("input", texts); + if (expectedDimension != null) { + body.put("dimensions", expectedDimension); + } + + RestClient.RequestBodySpec request = client.post().uri("/embeddings") + .contentType(MediaType.APPLICATION_JSON) + .body(body); + String apiKey = resolveApiKey(provider); + if (apiKey != null) { + request = request.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey); + } + + Map response = request.retrieve().body(Map.class); + if (response == null || !(response.get("data") instanceof List dataList)) { + throw new IllegalStateException("上游Embedding响应缺少data字段"); + } + List> vectors = new ArrayList<>(); + for (Object item : dataList) { + if (!(item instanceof Map itemMap)) { + continue; + } + Object embedding = itemMap.get("embedding"); + if (!(embedding instanceof List vectorValues)) { + continue; + } + List vector = new ArrayList<>(); + for (Object value : vectorValues) { + vector.add(Double.parseDouble(String.valueOf(value))); + } + vectors.add(vector); + } + return vectors; + } + + /** + * 调用 `/models` 做健康探测:成功返回 true,异常返回 false。 + */ + @Override + /** + * 方法 health,用于执行业务逻辑处理。 + */ + public boolean health(ModelProvider provider) { + try { + RestClient client = RestClient.builder().baseUrl(provider.getBaseUrl()).build(); + RestClient.RequestHeadersSpec request = client.get().uri("/models"); + String apiKey = resolveApiKey(provider); + if (apiKey != null) { + request = request.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey); + } + request.retrieve().toBodilessEntity(); + return true; + } catch (Exception ex) { + return false; + } + } + + /** + * 读取服务商密钥: + * 有 secretRef 时从环境变量读取;首期不使用数据库密钥明文。 + */ + private String resolveApiKey(ModelProvider provider) { + if (provider.getSecretRef() != null && !provider.getSecretRef().isBlank()) { + return System.getenv(provider.getSecretRef().trim()); + } + return null; + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/controller/ModelCallLogController.java b/src/main/java/com/bruce/modelprovider/controller/ModelCallLogController.java new file mode 100644 index 0000000..39cf496 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/controller/ModelCallLogController.java @@ -0,0 +1,40 @@ +package com.bruce.modelprovider.controller; + +import com.bruce.common.domain.model.RequestResult; +import com.bruce.modelprovider.dto.request.ModelCallLogQueryRequest; +import com.bruce.modelprovider.dto.response.ModelCallLogResponse; +import com.bruce.modelprovider.service.IModelCallLogService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/model/call-logs") +@RequiredArgsConstructor +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class ModelCallLogController { + + private final IModelCallLogService modelCallLogService; + + @PostMapping("/query") + /** + * 方法 query,用于执行业务逻辑处理。 + */ + public RequestResult> query(@RequestBody(required = false) ModelCallLogQueryRequest request) { + return RequestResult.success(modelCallLogService.query(request)); + } + + @GetMapping("/detail") + /** + * 方法 detail,用于执行业务逻辑处理。 + */ + public RequestResult detail(@RequestParam("id") Long id) { + return RequestResult.success(ModelCallLogResponse.fromEntity(modelCallLogService.getById(id))); + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/controller/ModelConfigController.java b/src/main/java/com/bruce/modelprovider/controller/ModelConfigController.java new file mode 100644 index 0000000..90953b6 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/controller/ModelConfigController.java @@ -0,0 +1,56 @@ +package com.bruce.modelprovider.controller; + +import com.bruce.common.domain.model.RequestResult; +import com.bruce.modelprovider.dto.request.ModelConfigSaveRequest; +import com.bruce.modelprovider.dto.response.ModelConfigResponse; +import com.bruce.modelprovider.service.IModelConfigService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/model/configs") +@RequiredArgsConstructor +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class ModelConfigController { + + private final IModelConfigService modelConfigService; + + @PostMapping("/query") + /** + * 方法 query,用于执行业务逻辑处理。 + */ + public RequestResult> query() { + return RequestResult.success(modelConfigService.listResponses()); + } + + @GetMapping("/detail") + /** + * 方法 detail,用于执行业务逻辑处理。 + */ + public RequestResult detail(@RequestParam("id") Long id) { + return RequestResult.success(modelConfigService.getResponseById(id)); + } + + @PostMapping("/save") + /** + * 方法 save,用于执行业务逻辑处理。 + */ + public RequestResult save(@RequestBody ModelConfigSaveRequest request) { + return RequestResult.success(modelConfigService.saveOrUpdate(request)); + } + + @PostMapping("/delete") + /** + * 方法 delete,用于执行业务逻辑处理。 + */ + public RequestResult delete(@RequestParam("id") Long id) { + return RequestResult.success(modelConfigService.removeById(id)); + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/controller/ModelProviderController.java b/src/main/java/com/bruce/modelprovider/controller/ModelProviderController.java new file mode 100644 index 0000000..e1191b0 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/controller/ModelProviderController.java @@ -0,0 +1,64 @@ +package com.bruce.modelprovider.controller; + +import com.bruce.common.domain.model.RequestResult; +import com.bruce.modelprovider.dto.request.ModelProviderSaveRequest; +import com.bruce.modelprovider.dto.response.ModelProviderResponse; +import com.bruce.modelprovider.service.IModelProviderService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/model/providers") +@RequiredArgsConstructor +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class ModelProviderController { + + private final IModelProviderService modelProviderService; + + @PostMapping("/query") + /** + * 方法 query,用于执行业务逻辑处理。 + */ + public RequestResult> query() { + return RequestResult.success(modelProviderService.listResponses()); + } + + @GetMapping("/detail") + /** + * 方法 detail,用于执行业务逻辑处理。 + */ + public RequestResult detail(@RequestParam("id") Long id) { + return RequestResult.success(modelProviderService.getResponseById(id)); + } + + @PostMapping("/save") + /** + * 方法 save,用于执行业务逻辑处理。 + */ + public RequestResult save(@RequestBody ModelProviderSaveRequest request) { + return RequestResult.success(modelProviderService.saveOrUpdate(request)); + } + + @PostMapping("/delete") + /** + * 方法 delete,用于执行业务逻辑处理。 + */ + public RequestResult delete(@RequestParam("id") Long id) { + return RequestResult.success(modelProviderService.removeById(id)); + } + + @PostMapping("/checkHealth") + /** + * 方法 checkHealth,用于执行业务逻辑处理。 + */ + public RequestResult checkHealth(@RequestParam("id") Long id) { + return RequestResult.success(modelProviderService.checkHealth(id)); + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/controller/ModelRouteRuleController.java b/src/main/java/com/bruce/modelprovider/controller/ModelRouteRuleController.java new file mode 100644 index 0000000..835c5ac --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/controller/ModelRouteRuleController.java @@ -0,0 +1,56 @@ +package com.bruce.modelprovider.controller; + +import com.bruce.common.domain.model.RequestResult; +import com.bruce.modelprovider.dto.request.ModelRouteRuleSaveRequest; +import com.bruce.modelprovider.dto.response.ModelRouteRuleResponse; +import com.bruce.modelprovider.service.IModelRouteRuleService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/model/routes") +@RequiredArgsConstructor +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class ModelRouteRuleController { + + private final IModelRouteRuleService modelRouteRuleService; + + @PostMapping("/query") + /** + * 方法 query,用于执行业务逻辑处理。 + */ + public RequestResult> query() { + return RequestResult.success(modelRouteRuleService.listResponses()); + } + + @GetMapping("/detail") + /** + * 方法 detail,用于执行业务逻辑处理。 + */ + public RequestResult detail(@RequestParam("id") Long id) { + return RequestResult.success(modelRouteRuleService.getResponseById(id)); + } + + @PostMapping("/save") + /** + * 方法 save,用于执行业务逻辑处理。 + */ + public RequestResult save(@RequestBody ModelRouteRuleSaveRequest request) { + return RequestResult.success(modelRouteRuleService.saveOrUpdate(request)); + } + + @PostMapping("/delete") + /** + * 方法 delete,用于执行业务逻辑处理。 + */ + public RequestResult delete(@RequestParam("id") Long id) { + return RequestResult.success(modelRouteRuleService.removeById(id)); + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/controller/RagStoreModelConfigController.java b/src/main/java/com/bruce/modelprovider/controller/RagStoreModelConfigController.java new file mode 100644 index 0000000..f87bc85 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/controller/RagStoreModelConfigController.java @@ -0,0 +1,46 @@ +package com.bruce.modelprovider.controller; + +import com.bruce.common.domain.model.RequestResult; +import com.bruce.modelprovider.dto.request.RagStoreModelConfigSaveRequest; +import com.bruce.modelprovider.dto.response.RagStoreModelConfigResponse; +import com.bruce.modelprovider.service.IRagStoreModelConfigService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/rag/store") +@RequiredArgsConstructor +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class RagStoreModelConfigController { + + private final IRagStoreModelConfigService ragStoreModelConfigService; + + @GetMapping("/modelConfig") + /** + * 方法 modelConfig,用于执行业务逻辑处理。 + */ + public RequestResult modelConfig(@RequestParam("storeId") Long storeId) { + return RequestResult.success(ragStoreModelConfigService.getByStoreId(storeId)); + } + + @PostMapping("/modelConfig/save") + /** + * 方法 save,用于执行业务逻辑处理。 + */ + public RequestResult save(@RequestBody RagStoreModelConfigSaveRequest request) { + return RequestResult.success(ragStoreModelConfigService.saveOrUpdate(request)); + } + + @PostMapping("/rebuildIndex") + /** + * 方法 rebuildIndex,用于执行业务逻辑处理。 + */ + public RequestResult rebuildIndex(@RequestParam("storeId") Long storeId) { + return RequestResult.success(storeId != null); + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/dto/request/ModelCallLogQueryRequest.java b/src/main/java/com/bruce/modelprovider/dto/request/ModelCallLogQueryRequest.java new file mode 100644 index 0000000..7bfc53a --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/dto/request/ModelCallLogQueryRequest.java @@ -0,0 +1,18 @@ +package com.bruce.modelprovider.dto.request; + +import lombok.Data; + +@Data +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class ModelCallLogQueryRequest { + private String taskType; + private Long providerId; + private Long modelId; + private String status; + private String bizType; +} + + + diff --git a/src/main/java/com/bruce/modelprovider/dto/request/ModelConfigSaveRequest.java b/src/main/java/com/bruce/modelprovider/dto/request/ModelConfigSaveRequest.java new file mode 100644 index 0000000..38766f5 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/dto/request/ModelConfigSaveRequest.java @@ -0,0 +1,25 @@ +package com.bruce.modelprovider.dto.request; + +import lombok.Data; + +@Data +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class ModelConfigSaveRequest { + private Long id; + private Long providerId; + private String modelCode; + private String modelName; + private String upstreamModel; + private String modelType; + private Integer embeddingDimension; + private Boolean localModel; + private Boolean defaultModel; + private String optionsJson; + private Boolean enabled; + private String remark; +} + + + diff --git a/src/main/java/com/bruce/modelprovider/dto/request/ModelProviderSaveRequest.java b/src/main/java/com/bruce/modelprovider/dto/request/ModelProviderSaveRequest.java new file mode 100644 index 0000000..31026a1 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/dto/request/ModelProviderSaveRequest.java @@ -0,0 +1,26 @@ +package com.bruce.modelprovider.dto.request; + +import lombok.Data; + +@Data +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class ModelProviderSaveRequest { + private Long id; + private String providerCode; + private String providerName; + private String providerType; + private String protocolType; + private String baseUrl; + private String authType; + private String secretRef; + private String apiKeyCipher; + private Integer timeoutMs; + private Integer priority; + private Boolean enabled; + private String remark; +} + + + diff --git a/src/main/java/com/bruce/modelprovider/dto/request/ModelRouteRuleSaveRequest.java b/src/main/java/com/bruce/modelprovider/dto/request/ModelRouteRuleSaveRequest.java new file mode 100644 index 0000000..9201b53 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/dto/request/ModelRouteRuleSaveRequest.java @@ -0,0 +1,25 @@ +package com.bruce.modelprovider.dto.request; + +import lombok.Data; + +@Data +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class ModelRouteRuleSaveRequest { + private Long id; + private String routeCode; + private String routeName; + private String taskType; + private String matchScope; + private Long scopeId; + private Long primaryModelId; + private String fallbackModelIdsJson; + private String routeStrategy; + private Integer maxLatencyMs; + private Boolean enabled; + private String remark; +} + + + diff --git a/src/main/java/com/bruce/modelprovider/dto/request/RagStoreModelConfigSaveRequest.java b/src/main/java/com/bruce/modelprovider/dto/request/RagStoreModelConfigSaveRequest.java new file mode 100644 index 0000000..0514abd --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/dto/request/RagStoreModelConfigSaveRequest.java @@ -0,0 +1,22 @@ +package com.bruce.modelprovider.dto.request; + +import lombok.Data; + +@Data +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class RagStoreModelConfigSaveRequest { + private Long id; + private Long storeId; + private Long embeddingModelId; + private Integer embeddingDimension; + private Integer chunkStrategy; + private Integer chunkSize; + private Integer chunkOverlap; + private String delimiter; + private String remark; +} + + + diff --git a/src/main/java/com/bruce/modelprovider/dto/response/ModelCallLogResponse.java b/src/main/java/com/bruce/modelprovider/dto/response/ModelCallLogResponse.java new file mode 100644 index 0000000..4e9707e --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/dto/response/ModelCallLogResponse.java @@ -0,0 +1,39 @@ +package com.bruce.modelprovider.dto.response; + +import com.bruce.modelprovider.entity.ModelCallLog; +import lombok.Data; +import org.springframework.beans.BeanUtils; + +@Data +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class ModelCallLogResponse { + private Long id; + private String requestId; + private Long providerId; + private Long modelId; + private String taskType; + private String bizType; + private String bizId; + private String callType; + private String status; + private Integer durationMs; + private String errorCode; + private String errorMessage; + + /** + * 方法 fromEntity,用于执行业务逻辑处理。 + */ + public static ModelCallLogResponse fromEntity(ModelCallLog entity) { + if (entity == null) { + return null; + } + ModelCallLogResponse response = new ModelCallLogResponse(); + BeanUtils.copyProperties(entity, response); + return response; + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/dto/response/ModelConfigResponse.java b/src/main/java/com/bruce/modelprovider/dto/response/ModelConfigResponse.java new file mode 100644 index 0000000..e39701b --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/dto/response/ModelConfigResponse.java @@ -0,0 +1,39 @@ +package com.bruce.modelprovider.dto.response; + +import com.bruce.modelprovider.entity.ModelConfig; +import lombok.Data; +import org.springframework.beans.BeanUtils; + +@Data +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class ModelConfigResponse { + private Long id; + private Long providerId; + private String modelCode; + private String modelName; + private String upstreamModel; + private String modelType; + private Integer embeddingDimension; + private Boolean localModel; + private Boolean defaultModel; + private String optionsJson; + private Boolean enabled; + private String remark; + + /** + * 方法 fromEntity,用于执行业务逻辑处理。 + */ + public static ModelConfigResponse fromEntity(ModelConfig entity) { + if (entity == null) { + return null; + } + ModelConfigResponse response = new ModelConfigResponse(); + BeanUtils.copyProperties(entity, response); + return response; + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/dto/response/ModelProviderResponse.java b/src/main/java/com/bruce/modelprovider/dto/response/ModelProviderResponse.java new file mode 100644 index 0000000..44f1c47 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/dto/response/ModelProviderResponse.java @@ -0,0 +1,42 @@ +package com.bruce.modelprovider.dto.response; + +import com.bruce.modelprovider.entity.ModelProvider; +import lombok.Data; +import org.springframework.beans.BeanUtils; + +@Data +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class ModelProviderResponse { + private Long id; + private String providerCode; + private String providerName; + private String providerType; + private String protocolType; + private String baseUrl; + private String authType; + private String secretRef; + private Boolean hasApiKey; + private Integer timeoutMs; + private Integer priority; + private Boolean enabled; + private String healthStatus; + private String remark; + + /** + * 方法 fromEntity,用于执行业务逻辑处理。 + */ + public static ModelProviderResponse fromEntity(ModelProvider entity) { + if (entity == null) { + return null; + } + ModelProviderResponse response = new ModelProviderResponse(); + BeanUtils.copyProperties(entity, response); + response.setHasApiKey(entity.getApiKeyCipher() != null && !entity.getApiKeyCipher().isBlank()); + return response; + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/dto/response/ModelRouteRuleResponse.java b/src/main/java/com/bruce/modelprovider/dto/response/ModelRouteRuleResponse.java new file mode 100644 index 0000000..015ee25 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/dto/response/ModelRouteRuleResponse.java @@ -0,0 +1,39 @@ +package com.bruce.modelprovider.dto.response; + +import com.bruce.modelprovider.entity.ModelRouteRule; +import lombok.Data; +import org.springframework.beans.BeanUtils; + +@Data +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class ModelRouteRuleResponse { + private Long id; + private String routeCode; + private String routeName; + private String taskType; + private String matchScope; + private Long scopeId; + private Long primaryModelId; + private String fallbackModelIdsJson; + private String routeStrategy; + private Integer maxLatencyMs; + private Boolean enabled; + private String remark; + + /** + * 方法 fromEntity,用于执行业务逻辑处理。 + */ + public static ModelRouteRuleResponse fromEntity(ModelRouteRule entity) { + if (entity == null) { + return null; + } + ModelRouteRuleResponse response = new ModelRouteRuleResponse(); + BeanUtils.copyProperties(entity, response); + return response; + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/dto/response/RagStoreModelConfigResponse.java b/src/main/java/com/bruce/modelprovider/dto/response/RagStoreModelConfigResponse.java new file mode 100644 index 0000000..471deca --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/dto/response/RagStoreModelConfigResponse.java @@ -0,0 +1,38 @@ +package com.bruce.modelprovider.dto.response; + +import com.bruce.modelprovider.entity.RagStoreModelConfig; +import lombok.Data; +import org.springframework.beans.BeanUtils; + +@Data +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class RagStoreModelConfigResponse { + private Long id; + private Long storeId; + private Long embeddingModelId; + private Integer embeddingDimension; + private Integer chunkStrategy; + private Integer chunkSize; + private Integer chunkOverlap; + private String delimiter; + private Boolean active; + private Integer indexVersion; + private String remark; + + /** + * 方法 fromEntity,用于执行业务逻辑处理。 + */ + public static RagStoreModelConfigResponse fromEntity(RagStoreModelConfig entity) { + if (entity == null) { + return null; + } + RagStoreModelConfigResponse response = new RagStoreModelConfigResponse(); + BeanUtils.copyProperties(entity, response); + return response; + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/entity/ModelCallLog.java b/src/main/java/com/bruce/modelprovider/entity/ModelCallLog.java new file mode 100644 index 0000000..0869fb7 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/entity/ModelCallLog.java @@ -0,0 +1,91 @@ +package com.bruce.modelprovider.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.bruce.common.domain.model.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 模型调用日志实体。 + *

+ * 用于记录每次模型调用的请求上下文、耗时、状态与错误摘要,支持排障与成本分析。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("model_call_log") +/** + * ModelCallLog,负责模型平台对应层的职责。 + */ +public class ModelCallLog extends BaseEntity { + + /** 数据库字段:request_id(请求唯一ID,一次网关调用唯一标识)。 */ + @TableField(value = "request_id") + private String requestId; + + /** 数据库字段:provider_id(命中的服务商ID)。 */ + @TableField(value = "provider_id") + private Long providerId; + + /** 数据库字段:model_id(命中的模型ID)。 */ + @TableField(value = "model_id") + private Long modelId; + + /** 数据库字段:task_type(任务类型,例如 RAG_EMBEDDING)。 */ + @TableField(value = "task_type") + private String taskType; + + /** 数据库字段:biz_type(业务类型,例如 RAG_DOCUMENT_INDEX)。 */ + @TableField(value = "biz_type") + private String bizType; + + /** 数据库字段:biz_id(业务主键,字符串形式,兼容 Long/UUID)。 */ + @TableField(value = "biz_id") + private String bizId; + + /** 数据库字段:call_type(调用类型,例如 EMBEDDING / CHAT / RERANK)。 */ + @TableField(value = "call_type") + private String callType; + + /** 调用状态,例如 SUCCESS / FAILED / TIMEOUT / FALLBACK_SUCCESS。 */ + private String status; + + /** 数据库字段:prompt_tokens(输入 token 数)。 */ + @TableField(value = "prompt_tokens") + private Integer promptTokens; + + /** 数据库字段:completion_tokens(输出 token 数)。 */ + @TableField(value = "completion_tokens") + private Integer completionTokens; + + /** 数据库字段:total_tokens(总 token 数)。 */ + @TableField(value = "total_tokens") + private Integer totalTokens; + + /** 数据库字段:estimated_cost(费用估算)。 */ + @TableField(value = "estimated_cost") + private BigDecimal estimatedCost; + + /** 数据库字段:duration_ms(调用耗时,单位毫秒)。 */ + @TableField(value = "duration_ms") + private Integer durationMs; + + /** 数据库字段:request_hash(请求内容哈希,用于排障,不落原始敏感文本)。 */ + @TableField(value = "request_hash") + private String requestHash; + + /** 数据库字段:error_code(错误码,业务定义)。 */ + @TableField(value = "error_code") + private String errorCode; + + /** 数据库字段:error_message(错误摘要,已截断)。 */ + @TableField(value = "error_message") + private String errorMessage; + + /** 备注信息。 */ + private String remark; +} + + diff --git a/src/main/java/com/bruce/modelprovider/entity/ModelConfig.java b/src/main/java/com/bruce/modelprovider/entity/ModelConfig.java new file mode 100644 index 0000000..ce28593 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/entity/ModelConfig.java @@ -0,0 +1,88 @@ +package com.bruce.modelprovider.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.bruce.common.domain.model.BaseEntity; +import com.bruce.rag.typehandler.PgJsonbStringTypeHandler; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 模型配置实体。 + *

+ * 每条记录描述某个服务商下的一个具体模型,包含模型类型、能力、价格和默认参数。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName(value = "model_config", autoResultMap = true) +/** + * ModelConfig,负责模型平台对应层的职责。 + */ +public class ModelConfig extends BaseEntity { + + /** 数据库字段:provider_id(关联的服务商ID)。 */ + @TableField(value = "provider_id") + private Long providerId; + + /** 数据库字段:model_code(模型编码,在同一服务商下唯一)。 */ + @TableField(value = "model_code") + private String modelCode; + + /** 数据库字段:model_name(模型展示名称)。 */ + @TableField(value = "model_name") + private String modelName; + + /** 数据库字段:upstream_model(上游真实模型名,例如 Qwen/Qwen3-Embedding-0.6B)。 */ + @TableField(value = "upstream_model") + private String upstreamModel; + + /** 数据库字段:model_type(模型类型,例如 CHAT / EMBEDDING / RERANK / MULTIMODAL)。 */ + @TableField(value = "model_type") + private String modelType; + + /** 数据库字段:context_window(上下文窗口大小)。 */ + @TableField(value = "context_window") + private Integer contextWindow; + + /** 数据库字段:max_output_tokens(单次输出 token 上限)。 */ + @TableField(value = "max_output_tokens") + private Integer maxOutputTokens; + + /** 数据库字段:embedding_dimension(向量维度,仅 EMBEDDING 模型使用)。 */ + @TableField(value = "embedding_dimension") + private Integer embeddingDimension; + + /** 数据库字段:input_price_per_1k(输入单价,每 1k token)。 */ + @TableField(value = "input_price_per_1k") + private BigDecimal inputPricePer1k; + + /** 数据库字段:output_price_per_1k(输出单价,每 1k token)。 */ + @TableField(value = "output_price_per_1k") + private BigDecimal outputPricePer1k; + + /** 数据库字段:local_model(是否本地模型,如自建 Ollama)。 */ + @TableField(value = "local_model") + private Boolean localModel; + + /** 数据库字段:default_model(是否该类型默认模型)。 */ + @TableField(value = "default_model") + private Boolean defaultModel; + + /** 数据库字段:capabilities_json(能力标签JSON,例如是否支持工具调用、视觉能力等)。 */ + @TableField(value = "capabilities_json", typeHandler = PgJsonbStringTypeHandler.class) + private String capabilitiesJson; + + /** 数据库字段:options_json(默认调用参数JSON,例如 temperature、dimensions)。 */ + @TableField(value = "options_json", typeHandler = PgJsonbStringTypeHandler.class) + private String optionsJson; + + /** 是否启用该模型。 */ + private Boolean enabled; + + /** 备注信息。 */ + private String remark; +} + + diff --git a/src/main/java/com/bruce/modelprovider/entity/ModelProvider.java b/src/main/java/com/bruce/modelprovider/entity/ModelProvider.java new file mode 100644 index 0000000..9f6d85e --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/entity/ModelProvider.java @@ -0,0 +1,81 @@ +package com.bruce.modelprovider.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.bruce.common.domain.model.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +/** + * 模型服务商配置实体。 + *

+ * 说明: + * 1. 数据库列名沿用英文下划线命名,便于与既有 SQL 设计保持一致; + * 2. 字段语义通过中文注释明确,减少后续维护者对英文缩写的理解成本; + * 3. 密钥类字段仅用于后端调用,不应返回明文到前端。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("model_provider") +/** + * ModelProvider,负责模型平台对应层的职责。 + */ +public class ModelProvider extends BaseEntity { + + /** 数据库字段:provider_code(服务商编码,全局唯一,用于路由规则与配置引用)。 */ + @TableField(value = "provider_code") + private String providerCode; + + /** 数据库字段:provider_name(服务商展示名称)。 */ + @TableField(value = "provider_name") + private String providerName; + + /** 数据库字段:provider_type(服务商类型,例如 OLLAMA / SILICONFLOW / DASHSCOPE / OPENAI / CUSTOM)。 */ + @TableField(value = "provider_type") + private String providerType; + + /** 数据库字段:protocol_type(协议类型,首期固定为 OPENAI_COMPATIBLE)。 */ + @TableField(value = "protocol_type") + private String protocolType; + + /** 数据库字段:base_url(上游服务基础地址,通常包含 /v1)。 */ + @TableField(value = "base_url") + private String baseUrl; + + /** 数据库字段:auth_type(鉴权类型,例如 NONE / BEARER_TOKEN)。 */ + @TableField(value = "auth_type") + private String authType; + + /** 数据库字段:secret_ref(密钥引用名,通常是环境变量键,例如 SILICONFLOW_API_KEY)。 */ + @TableField(value = "secret_ref") + private String secretRef; + + /** 数据库字段:api_key_cipher(密钥密文,可选,首期不默认启用该方案)。 */ + @TableField(value = "api_key_cipher") + private String apiKeyCipher; + + /** 数据库字段:timeout_ms(请求超时时间,单位毫秒)。 */ + @TableField(value = "timeout_ms") + private Integer timeoutMs; + + /** 调度优先级,数值越小优先级越高(用于后续扩展)。 */ + private Integer priority; + + /** 是否启用该服务商。 */ + private Boolean enabled; + + /** 数据库字段:health_status(健康状态,例如 UNKNOWN / HEALTHY / UNHEALTHY)。 */ + @TableField(value = "health_status") + private String healthStatus; + + /** 数据库字段:last_health_check_time(最近一次健康检查时间)。 */ + @TableField(value = "last_health_check_time") + private Date lastHealthCheckTime; + + /** 备注信息。 */ + private String remark; +} + + diff --git a/src/main/java/com/bruce/modelprovider/entity/ModelRouteRule.java b/src/main/java/com/bruce/modelprovider/entity/ModelRouteRule.java new file mode 100644 index 0000000..b962478 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/entity/ModelRouteRule.java @@ -0,0 +1,66 @@ +package com.bruce.modelprovider.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.bruce.common.domain.model.BaseEntity; +import com.bruce.rag.typehandler.PgJsonbStringTypeHandler; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 模型路由规则实体。 + *

+ * 用于定义任务在不同范围(全局/知识库/Agent)下如何选择主模型与备用模型。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName(value = "model_route_rule", autoResultMap = true) +/** + * ModelRouteRule,负责模型平台对应层的职责。 + */ +public class ModelRouteRule extends BaseEntity { + + /** 数据库字段:route_code(路由规则编码,全局唯一)。 */ + @TableField(value = "route_code") + private String routeCode; + + /** 数据库字段:route_name(路由规则名称)。 */ + @TableField(value = "route_name") + private String routeName; + + /** 数据库字段:task_type(任务类型,例如 RAG_EMBEDDING / CHAT_SIMPLE)。 */ + @TableField(value = "task_type") + private String taskType; + + /** 数据库字段:match_scope(匹配范围,例如 GLOBAL / RAG_STORE / AGENT)。 */ + @TableField(value = "match_scope") + private String matchScope; + + /** 数据库字段:scope_id(范围ID,例如知识库ID或Agent ID)。 */ + @TableField(value = "scope_id") + private Long scopeId; + + /** 数据库字段:primary_model_id(主模型ID)。 */ + @TableField(value = "primary_model_id") + private Long primaryModelId; + + /** 数据库字段:fallback_model_ids_json(备用模型ID列表,JSON数组)。 */ + @TableField(value = "fallback_model_ids_json", typeHandler = PgJsonbStringTypeHandler.class) + private String fallbackModelIdsJson; + + /** 数据库字段:route_strategy(路由策略,例如 MANUAL / LOCAL_FIRST / COST_FIRST / QUALITY_FIRST)。 */ + @TableField(value = "route_strategy") + private String routeStrategy; + + /** 数据库字段:max_latency_ms(最大允许耗时,单位毫秒,用于策略扩展)。 */ + @TableField(value = "max_latency_ms") + private Integer maxLatencyMs; + + /** 是否启用该规则。 */ + private Boolean enabled; + + /** 备注信息。 */ + private String remark; +} + + diff --git a/src/main/java/com/bruce/modelprovider/entity/RagStoreModelConfig.java b/src/main/java/com/bruce/modelprovider/entity/RagStoreModelConfig.java new file mode 100644 index 0000000..a539a33 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/entity/RagStoreModelConfig.java @@ -0,0 +1,60 @@ +package com.bruce.modelprovider.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.bruce.common.domain.model.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 知识库模型绑定实体。 + *

+ * 用于固定知识库当前生效的 Embedding 模型与向量维度,避免同库混用不同向量空间。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("rag_store_model_config") +/** + * RagStoreModelConfig,负责模型平台对应层的职责。 + */ +public class RagStoreModelConfig extends BaseEntity { + + /** 数据库字段:store_id(知识库ID)。 */ + @TableField(value = "store_id") + private Long storeId; + + /** 数据库字段:embedding_model_id(绑定的 Embedding 模型ID)。 */ + @TableField(value = "embedding_model_id") + private Long embeddingModelId; + + /** 数据库字段:embedding_dimension(绑定维度,首期固定 1024)。 */ + @TableField(value = "embedding_dimension") + private Integer embeddingDimension; + + /** 数据库字段:chunk_strategy(切片策略枚举值)。 */ + @TableField(value = "chunk_strategy") + private Integer chunkStrategy; + + /** 数据库字段:chunk_size(切片长度)。 */ + @TableField(value = "chunk_size") + private Integer chunkSize; + + /** 数据库字段:chunk_overlap(切片重叠长度)。 */ + @TableField(value = "chunk_overlap") + private Integer chunkOverlap; + + /** 分隔符(分隔符策略时生效)。 */ + private String delimiter; + + /** 是否生效(同一知识库仅允许一个 active=true)。 */ + private Boolean active; + + /** 数据库字段:index_version(索引版本号,模型或维度变化时递增)。 */ + @TableField(value = "index_version") + private Integer indexVersion; + + /** 备注信息。 */ + private String remark; +} + + diff --git a/src/main/java/com/bruce/modelprovider/enums/ModelCallStatusEnum.java b/src/main/java/com/bruce/modelprovider/enums/ModelCallStatusEnum.java new file mode 100644 index 0000000..db02714 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/enums/ModelCallStatusEnum.java @@ -0,0 +1,35 @@ +package com.bruce.modelprovider.enums; + +import com.bruce.common.enums.PersistableSysEnumDefinition; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public enum ModelCallStatusEnum implements PersistableSysEnumDefinition { + + SUCCESS(1, "成功"), + FAILED(2, "失败"), + TIMEOUT(3, "超时"), + FALLBACK_SUCCESS(4, "降级成功"); + + private static final String CATALOG = "model_provider"; + private static final String TYPE = "call_status"; + private static final String REMARK = "模型调用状态"; + + private final Integer value; + private final String label; + + @Override + public String getCatalog() { return CATALOG; } + @Override + public String getType() { return TYPE; } + @Override + public String getRemark() { return REMARK; } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/enums/ModelHealthStatusEnum.java b/src/main/java/com/bruce/modelprovider/enums/ModelHealthStatusEnum.java new file mode 100644 index 0000000..c4d336e --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/enums/ModelHealthStatusEnum.java @@ -0,0 +1,34 @@ +package com.bruce.modelprovider.enums; + +import com.bruce.common.enums.PersistableSysEnumDefinition; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public enum ModelHealthStatusEnum implements PersistableSysEnumDefinition { + + UNKNOWN(1, "未知"), + HEALTHY(2, "健康"), + UNHEALTHY(3, "不健康"); + + private static final String CATALOG = "model_provider"; + private static final String TYPE = "health_status"; + private static final String REMARK = "模型服务商健康状态"; + + private final Integer value; + private final String label; + + @Override + public String getCatalog() { return CATALOG; } + @Override + public String getType() { return TYPE; } + @Override + public String getRemark() { return REMARK; } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/enums/ModelProtocolTypeEnum.java b/src/main/java/com/bruce/modelprovider/enums/ModelProtocolTypeEnum.java new file mode 100644 index 0000000..43661d2 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/enums/ModelProtocolTypeEnum.java @@ -0,0 +1,49 @@ +package com.bruce.modelprovider.enums; + +import com.bruce.common.enums.PersistableSysEnumDefinition; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public enum ModelProtocolTypeEnum implements PersistableSysEnumDefinition { + + OPENAI_COMPATIBLE(1, "OpenAI兼容协议"); + + private static final String CATALOG = "model_provider"; + private static final String TYPE = "protocol_type"; + private static final String REMARK = "模型服务协议类型"; + + private final Integer value; + private final String label; + + @Override + /** + * 方法 getCatalog,用于执行业务逻辑处理。 + */ + public String getCatalog() { + return CATALOG; + } + + @Override + /** + * 方法 getType,用于执行业务逻辑处理。 + */ + public String getType() { + return TYPE; + } + + @Override + /** + * 方法 getRemark,用于执行业务逻辑处理。 + */ + public String getRemark() { + return REMARK; + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/enums/ModelProviderTypeEnum.java b/src/main/java/com/bruce/modelprovider/enums/ModelProviderTypeEnum.java new file mode 100644 index 0000000..38ef083 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/enums/ModelProviderTypeEnum.java @@ -0,0 +1,53 @@ +package com.bruce.modelprovider.enums; + +import com.bruce.common.enums.PersistableSysEnumDefinition; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public enum ModelProviderTypeEnum implements PersistableSysEnumDefinition { + + OLLAMA(1, "Ollama"), + SILICONFLOW(2, "硅基流动"), + DASHSCOPE(3, "百炼"), + OPENAI(4, "OpenAI"), + CUSTOM(5, "自定义"); + + private static final String CATALOG = "model_provider"; + private static final String TYPE = "provider_type"; + private static final String REMARK = "模型服务商类型"; + + private final Integer value; + private final String label; + + @Override + /** + * 方法 getCatalog,用于执行业务逻辑处理。 + */ + public String getCatalog() { + return CATALOG; + } + + @Override + /** + * 方法 getType,用于执行业务逻辑处理。 + */ + public String getType() { + return TYPE; + } + + @Override + /** + * 方法 getRemark,用于执行业务逻辑处理。 + */ + public String getRemark() { + return REMARK; + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/enums/ModelRouteStrategyEnum.java b/src/main/java/com/bruce/modelprovider/enums/ModelRouteStrategyEnum.java new file mode 100644 index 0000000..2ade095 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/enums/ModelRouteStrategyEnum.java @@ -0,0 +1,35 @@ +package com.bruce.modelprovider.enums; + +import com.bruce.common.enums.PersistableSysEnumDefinition; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public enum ModelRouteStrategyEnum implements PersistableSysEnumDefinition { + + LOCAL_FIRST(1, "本地优先"), + COST_FIRST(2, "成本优先"), + QUALITY_FIRST(3, "质量优先"), + MANUAL(4, "手工指定"); + + private static final String CATALOG = "model_provider"; + private static final String TYPE = "route_strategy"; + private static final String REMARK = "模型路由策略"; + + private final Integer value; + private final String label; + + @Override + public String getCatalog() { return CATALOG; } + @Override + public String getType() { return TYPE; } + @Override + public String getRemark() { return REMARK; } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/enums/ModelTaskTypeEnum.java b/src/main/java/com/bruce/modelprovider/enums/ModelTaskTypeEnum.java new file mode 100644 index 0000000..d4107e6 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/enums/ModelTaskTypeEnum.java @@ -0,0 +1,38 @@ +package com.bruce.modelprovider.enums; + +import com.bruce.common.enums.PersistableSysEnumDefinition; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public enum ModelTaskTypeEnum implements PersistableSysEnumDefinition { + + RAG_EMBEDDING(1, "RAG文档向量化"), + RAG_QUERY_EMBEDDING(2, "RAG查询向量化"), + RAG_ANSWER(3, "RAG问答生成"), + CHAT_SIMPLE(4, "简单文本处理"), + CHAT_COMPLEX(5, "复杂文本处理"), + AGENT_PLANNING(6, "Agent规划"), + RERANK(7, "重排序"); + + private static final String CATALOG = "model_provider"; + private static final String TYPE = "task_type"; + private static final String REMARK = "模型任务类型"; + + private final Integer value; + private final String label; + + @Override + public String getCatalog() { return CATALOG; } + @Override + public String getType() { return TYPE; } + @Override + public String getRemark() { return REMARK; } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/enums/ModelTypeEnum.java b/src/main/java/com/bruce/modelprovider/enums/ModelTypeEnum.java new file mode 100644 index 0000000..27801b3 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/enums/ModelTypeEnum.java @@ -0,0 +1,52 @@ +package com.bruce.modelprovider.enums; + +import com.bruce.common.enums.PersistableSysEnumDefinition; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public enum ModelTypeEnum implements PersistableSysEnumDefinition { + + CHAT(1, "聊天模型"), + EMBEDDING(2, "向量模型"), + RERANK(3, "重排模型"), + MULTIMODAL(4, "多模态模型"); + + private static final String CATALOG = "model_provider"; + private static final String TYPE = "model_type"; + private static final String REMARK = "模型类型"; + + private final Integer value; + private final String label; + + @Override + /** + * 方法 getCatalog,用于执行业务逻辑处理。 + */ + public String getCatalog() { + return CATALOG; + } + + @Override + /** + * 方法 getType,用于执行业务逻辑处理。 + */ + public String getType() { + return TYPE; + } + + @Override + /** + * 方法 getRemark,用于执行业务逻辑处理。 + */ + public String getRemark() { + return REMARK; + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/gateway/EmbeddingModelGateway.java b/src/main/java/com/bruce/modelprovider/gateway/EmbeddingModelGateway.java new file mode 100644 index 0000000..1e874a5 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/gateway/EmbeddingModelGateway.java @@ -0,0 +1,14 @@ +package com.bruce.modelprovider.gateway; + +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public interface EmbeddingModelGateway { + /** + * 方法 embed,用于定义接口能力契约。 + */ + EmbeddingResult embed(EmbeddingRequest request); +} + + + diff --git a/src/main/java/com/bruce/modelprovider/gateway/EmbeddingModelGatewayImpl.java b/src/main/java/com/bruce/modelprovider/gateway/EmbeddingModelGatewayImpl.java new file mode 100644 index 0000000..3701a4a --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/gateway/EmbeddingModelGatewayImpl.java @@ -0,0 +1,140 @@ +package com.bruce.modelprovider.gateway; + +import com.bruce.modelprovider.client.OpenAiCompatibleModelClient; +import com.bruce.modelprovider.entity.ModelCallLog; +import com.bruce.modelprovider.entity.ModelConfig; +import com.bruce.modelprovider.entity.ModelProvider; +import com.bruce.modelprovider.enums.ModelCallStatusEnum; +import com.bruce.modelprovider.route.ModelRouteContext; +import com.bruce.modelprovider.route.ModelRouteDecision; +import com.bruce.modelprovider.service.IModelCallLogService; +import com.bruce.modelprovider.service.IModelProviderService; +import com.bruce.modelprovider.service.IModelRouteService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.util.DigestUtils; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.UUID; + +/** + * Embedding 网关实现。 + *

+ * 主要职责: + * 1. 将业务请求转换为统一路由上下文并完成模型决策; + * 2. 调用上游 Embedding 接口并执行结果校验(数量、维度); + * 3. 记录调用日志(成功/失败、耗时、错误摘要、请求哈希); + * 4. 在主模型失败时按备用模型顺序执行兜底调用。 + */ +@Component +@RequiredArgsConstructor +/** + * EmbeddingModelGatewayImpl,负责模型平台对应层的职责。 + */ +public class EmbeddingModelGatewayImpl implements EmbeddingModelGateway { + + private final IModelRouteService modelRouteService; + private final IModelProviderService modelProviderService; + private final IModelCallLogService modelCallLogService; + private final OpenAiCompatibleModelClient openAiCompatibleModelClient; + + /** + * 统一 Embedding 调用入口。 + * + * @param request 向量化请求,包含文本、任务类型和业务上下文 + * @return 向量化结果(模型信息 + 向量数组 + 调用日志) + */ + @Override + /** + * 方法 embed,用于执行业务逻辑处理。 + */ + public EmbeddingResult embed(EmbeddingRequest request) { + long start = System.currentTimeMillis(); + ModelCallLog callLog = new ModelCallLog(); + callLog.setRequestId(UUID.randomUUID().toString().replace("-", "")); + callLog.setTaskType(request.getTaskType()); + callLog.setBizType(request.getBizType()); + callLog.setBizId(request.getBizId()); + callLog.setCallType("EMBEDDING"); + callLog.setRequestHash(DigestUtils.md5DigestAsHex(String.join("|", request.getTexts()).getBytes(StandardCharsets.UTF_8))); + + try { + ModelRouteContext routeContext = new ModelRouteContext(); + routeContext.setTaskType(request.getTaskType()); + routeContext.setMatchScope(request.getMatchScope()); + routeContext.setScopeId(request.getScopeId()); + routeContext.setRequiredModelType("EMBEDDING"); + routeContext.setRequiredEmbeddingDimension(request.getExpectedDimension()); + routeContext.setBizType(request.getBizType()); + routeContext.setBizId(request.getBizId()); + ModelRouteDecision decision = modelRouteService.route(routeContext); + + ModelConfig model = decision.getPrimaryModel(); + ModelProvider provider = modelProviderService.getById(model.getProviderId()); + if (provider == null || !Boolean.TRUE.equals(provider.getEnabled())) { + throw new IllegalStateException("模型服务商不可用"); + } + + List> vectors = executeWithFallback(provider, model, decision.getFallbackModels(), request.getTexts(), request.getExpectedDimension()); + if (vectors.size() != request.getTexts().size()) { + throw new IllegalStateException("向量数量与输入文本数量不一致"); + } + Integer dimension = vectors.isEmpty() ? 0 : vectors.getFirst().size(); + if (request.getExpectedDimension() != null && !request.getExpectedDimension().equals(dimension)) { + throw new IllegalStateException("向量维度不匹配,expected=" + request.getExpectedDimension() + ", actual=" + dimension); + } + + callLog.setProviderId(provider.getId()); + callLog.setModelId(model.getId()); + callLog.setStatus(ModelCallStatusEnum.SUCCESS.name()); + callLog.setDurationMs((int) (System.currentTimeMillis() - start)); + modelCallLogService.save(callLog); + + EmbeddingResult result = new EmbeddingResult(); + result.setModelId(model.getId()); + result.setModelName(model.getModelName()); + result.setDimension(dimension); + result.setVectors(vectors); + result.setCallLog(callLog); + return result; + } catch (Exception ex) { + callLog.setStatus(ModelCallStatusEnum.FAILED.name()); + callLog.setDurationMs((int) (System.currentTimeMillis() - start)); + callLog.setErrorCode("EMBEDDING_FAILED"); + String msg = ex.getMessage(); + callLog.setErrorMessage(msg == null ? "unknown" : msg.substring(0, Math.min(msg.length(), 1000))); + modelCallLogService.save(callLog); + throw ex; + } + } + + /** + * 主模型优先调用,失败后按备用模型顺序重试。 + */ + private List> executeWithFallback(ModelProvider primaryProvider, + ModelConfig primaryModel, + List fallbackModels, + List texts, + Integer expectedDimension) { + try { + return openAiCompatibleModelClient.embeddings(primaryProvider, primaryModel, texts, expectedDimension); + } catch (Exception primaryEx) { + for (ModelConfig fallback : fallbackModels) { + try { + ModelProvider provider = modelProviderService.getById(fallback.getProviderId()); + if (provider == null || !Boolean.TRUE.equals(provider.getEnabled())) { + continue; + } + return openAiCompatibleModelClient.embeddings(provider, fallback, texts, expectedDimension); + } catch (Exception ignored) { + // continue fallback chain + } + } + throw primaryEx; + } + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/gateway/EmbeddingRequest.java b/src/main/java/com/bruce/modelprovider/gateway/EmbeddingRequest.java new file mode 100644 index 0000000..6293967 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/gateway/EmbeddingRequest.java @@ -0,0 +1,22 @@ +package com.bruce.modelprovider.gateway; + +import lombok.Data; + +import java.util.List; + +@Data +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class EmbeddingRequest { + private List texts; + private String taskType; + private String matchScope; + private Long scopeId; + private String bizType; + private String bizId; + private Integer expectedDimension; +} + + + diff --git a/src/main/java/com/bruce/modelprovider/gateway/EmbeddingResult.java b/src/main/java/com/bruce/modelprovider/gateway/EmbeddingResult.java new file mode 100644 index 0000000..08686d2 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/gateway/EmbeddingResult.java @@ -0,0 +1,21 @@ +package com.bruce.modelprovider.gateway; + +import com.bruce.modelprovider.entity.ModelCallLog; +import lombok.Data; + +import java.util.List; + +@Data +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class EmbeddingResult { + private Long modelId; + private String modelName; + private Integer dimension; + private List> vectors; + private ModelCallLog callLog; +} + + + diff --git a/src/main/java/com/bruce/modelprovider/mapper/ModelCallLogMapper.java b/src/main/java/com/bruce/modelprovider/mapper/ModelCallLogMapper.java new file mode 100644 index 0000000..4e6109a --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/mapper/ModelCallLogMapper.java @@ -0,0 +1,15 @@ +package com.bruce.modelprovider.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bruce.modelprovider.entity.ModelCallLog; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public interface ModelCallLogMapper extends BaseMapper { +} + + + diff --git a/src/main/java/com/bruce/modelprovider/mapper/ModelConfigMapper.java b/src/main/java/com/bruce/modelprovider/mapper/ModelConfigMapper.java new file mode 100644 index 0000000..7c7a23d --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/mapper/ModelConfigMapper.java @@ -0,0 +1,15 @@ +package com.bruce.modelprovider.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bruce.modelprovider.entity.ModelConfig; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public interface ModelConfigMapper extends BaseMapper { +} + + + diff --git a/src/main/java/com/bruce/modelprovider/mapper/ModelProviderMapper.java b/src/main/java/com/bruce/modelprovider/mapper/ModelProviderMapper.java new file mode 100644 index 0000000..c4107ed --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/mapper/ModelProviderMapper.java @@ -0,0 +1,15 @@ +package com.bruce.modelprovider.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bruce.modelprovider.entity.ModelProvider; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public interface ModelProviderMapper extends BaseMapper { +} + + + diff --git a/src/main/java/com/bruce/modelprovider/mapper/ModelRouteRuleMapper.java b/src/main/java/com/bruce/modelprovider/mapper/ModelRouteRuleMapper.java new file mode 100644 index 0000000..a0d259b --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/mapper/ModelRouteRuleMapper.java @@ -0,0 +1,15 @@ +package com.bruce.modelprovider.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bruce.modelprovider.entity.ModelRouteRule; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public interface ModelRouteRuleMapper extends BaseMapper { +} + + + diff --git a/src/main/java/com/bruce/modelprovider/mapper/RagStoreModelConfigMapper.java b/src/main/java/com/bruce/modelprovider/mapper/RagStoreModelConfigMapper.java new file mode 100644 index 0000000..62c1467 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/mapper/RagStoreModelConfigMapper.java @@ -0,0 +1,15 @@ +package com.bruce.modelprovider.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bruce.modelprovider.entity.RagStoreModelConfig; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public interface RagStoreModelConfigMapper extends BaseMapper { +} + + + diff --git a/src/main/java/com/bruce/modelprovider/route/ModelRouteContext.java b/src/main/java/com/bruce/modelprovider/route/ModelRouteContext.java new file mode 100644 index 0000000..a840dcf --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/route/ModelRouteContext.java @@ -0,0 +1,21 @@ +package com.bruce.modelprovider.route; + +import lombok.Data; + +@Data +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class ModelRouteContext { + private String taskType; + private String matchScope; + private Long scopeId; + private String requiredModelType; + private Boolean preferredLocal; + private Integer requiredEmbeddingDimension; + private String bizType; + private String bizId; +} + + + diff --git a/src/main/java/com/bruce/modelprovider/route/ModelRouteDecision.java b/src/main/java/com/bruce/modelprovider/route/ModelRouteDecision.java new file mode 100644 index 0000000..8421f2f --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/route/ModelRouteDecision.java @@ -0,0 +1,21 @@ +package com.bruce.modelprovider.route; + +import com.bruce.modelprovider.entity.ModelConfig; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class ModelRouteDecision { + private ModelConfig primaryModel; + private List fallbackModels = new ArrayList<>(); + private String routeStrategy; + private String reason; +} + + + diff --git a/src/main/java/com/bruce/modelprovider/service/IModelCallLogService.java b/src/main/java/com/bruce/modelprovider/service/IModelCallLogService.java new file mode 100644 index 0000000..43eb113 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/service/IModelCallLogService.java @@ -0,0 +1,21 @@ +package com.bruce.modelprovider.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bruce.modelprovider.dto.request.ModelCallLogQueryRequest; +import com.bruce.modelprovider.dto.response.ModelCallLogResponse; +import com.bruce.modelprovider.entity.ModelCallLog; + +import java.util.List; + +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public interface IModelCallLogService extends IService { + /** + * 方法 query,用于定义接口能力契约。 + */ + List query(ModelCallLogQueryRequest request); +} + + + diff --git a/src/main/java/com/bruce/modelprovider/service/IModelConfigService.java b/src/main/java/com/bruce/modelprovider/service/IModelConfigService.java new file mode 100644 index 0000000..d4dd8e7 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/service/IModelConfigService.java @@ -0,0 +1,33 @@ +package com.bruce.modelprovider.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bruce.modelprovider.dto.request.ModelConfigSaveRequest; +import com.bruce.modelprovider.dto.response.ModelConfigResponse; +import com.bruce.modelprovider.entity.ModelConfig; + +import java.util.List; + +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public interface IModelConfigService extends IService { + /** + * 方法 listResponses,用于定义接口能力契约。 + */ + List listResponses(); + /** + * 方法 getResponseById,用于定义接口能力契约。 + */ + ModelConfigResponse getResponseById(Long id); + /** + * 方法 saveOrUpdate,用于定义接口能力契约。 + */ + boolean saveOrUpdate(ModelConfigSaveRequest request); + /** + * 方法 getEnabledModel,用于定义接口能力契约。 + */ + ModelConfig getEnabledModel(Long modelId); +} + + + diff --git a/src/main/java/com/bruce/modelprovider/service/IModelProviderService.java b/src/main/java/com/bruce/modelprovider/service/IModelProviderService.java new file mode 100644 index 0000000..812c012 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/service/IModelProviderService.java @@ -0,0 +1,33 @@ +package com.bruce.modelprovider.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bruce.modelprovider.dto.request.ModelProviderSaveRequest; +import com.bruce.modelprovider.dto.response.ModelProviderResponse; +import com.bruce.modelprovider.entity.ModelProvider; + +import java.util.List; + +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public interface IModelProviderService extends IService { + /** + * 方法 listResponses,用于定义接口能力契约。 + */ + List listResponses(); + /** + * 方法 getResponseById,用于定义接口能力契约。 + */ + ModelProviderResponse getResponseById(Long id); + /** + * 方法 saveOrUpdate,用于定义接口能力契约。 + */ + boolean saveOrUpdate(ModelProviderSaveRequest request); + /** + * 方法 checkHealth,用于定义接口能力契约。 + */ + boolean checkHealth(Long id); +} + + + diff --git a/src/main/java/com/bruce/modelprovider/service/IModelRouteRuleService.java b/src/main/java/com/bruce/modelprovider/service/IModelRouteRuleService.java new file mode 100644 index 0000000..278a801 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/service/IModelRouteRuleService.java @@ -0,0 +1,29 @@ +package com.bruce.modelprovider.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bruce.modelprovider.dto.request.ModelRouteRuleSaveRequest; +import com.bruce.modelprovider.dto.response.ModelRouteRuleResponse; +import com.bruce.modelprovider.entity.ModelRouteRule; + +import java.util.List; + +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public interface IModelRouteRuleService extends IService { + /** + * 方法 listResponses,用于定义接口能力契约。 + */ + List listResponses(); + /** + * 方法 getResponseById,用于定义接口能力契约。 + */ + ModelRouteRuleResponse getResponseById(Long id); + /** + * 方法 saveOrUpdate,用于定义接口能力契约。 + */ + boolean saveOrUpdate(ModelRouteRuleSaveRequest request); +} + + + diff --git a/src/main/java/com/bruce/modelprovider/service/IModelRouteService.java b/src/main/java/com/bruce/modelprovider/service/IModelRouteService.java new file mode 100644 index 0000000..5114aac --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/service/IModelRouteService.java @@ -0,0 +1,17 @@ +package com.bruce.modelprovider.service; + +import com.bruce.modelprovider.route.ModelRouteContext; +import com.bruce.modelprovider.route.ModelRouteDecision; + +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public interface IModelRouteService { + /** + * 方法 route,用于定义接口能力契约。 + */ + ModelRouteDecision route(ModelRouteContext context); +} + + + diff --git a/src/main/java/com/bruce/modelprovider/service/IRagStoreModelConfigService.java b/src/main/java/com/bruce/modelprovider/service/IRagStoreModelConfigService.java new file mode 100644 index 0000000..ebee9c1 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/service/IRagStoreModelConfigService.java @@ -0,0 +1,27 @@ +package com.bruce.modelprovider.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bruce.modelprovider.dto.request.RagStoreModelConfigSaveRequest; +import com.bruce.modelprovider.dto.response.RagStoreModelConfigResponse; +import com.bruce.modelprovider.entity.RagStoreModelConfig; + +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public interface IRagStoreModelConfigService extends IService { + /** + * 方法 getByStoreId,用于定义接口能力契约。 + */ + RagStoreModelConfigResponse getByStoreId(Long storeId); + /** + * 方法 saveOrUpdate,用于定义接口能力契约。 + */ + boolean saveOrUpdate(RagStoreModelConfigSaveRequest request); + /** + * 方法 getActiveEntity,用于定义接口能力契约。 + */ + RagStoreModelConfig getActiveEntity(Long storeId); +} + + + diff --git a/src/main/java/com/bruce/modelprovider/service/impl/ModelCallLogServiceImpl.java b/src/main/java/com/bruce/modelprovider/service/impl/ModelCallLogServiceImpl.java new file mode 100644 index 0000000..0b9fd11 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/service/impl/ModelCallLogServiceImpl.java @@ -0,0 +1,42 @@ +package com.bruce.modelprovider.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bruce.modelprovider.dto.request.ModelCallLogQueryRequest; +import com.bruce.modelprovider.dto.response.ModelCallLogResponse; +import com.bruce.modelprovider.entity.ModelCallLog; +import com.bruce.modelprovider.mapper.ModelCallLogMapper; +import com.bruce.modelprovider.service.IModelCallLogService; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.List; + +@Service +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class ModelCallLogServiceImpl extends ServiceImpl + implements IModelCallLogService { + + @Override + /** + * 方法 query,用于执行业务逻辑处理。 + */ + public List query(ModelCallLogQueryRequest request) { + ModelCallLogQueryRequest q = request == null ? new ModelCallLogQueryRequest() : request; + return lambdaQuery() + .eq(StringUtils.hasText(q.getTaskType()), ModelCallLog::getTaskType, q.getTaskType()) + .eq(q.getProviderId() != null, ModelCallLog::getProviderId, q.getProviderId()) + .eq(q.getModelId() != null, ModelCallLog::getModelId, q.getModelId()) + .eq(StringUtils.hasText(q.getStatus()), ModelCallLog::getStatus, q.getStatus()) + .eq(StringUtils.hasText(q.getBizType()), ModelCallLog::getBizType, q.getBizType()) + .orderByDesc(ModelCallLog::getId) + .list() + .stream() + .map(ModelCallLogResponse::fromEntity) + .toList(); + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/service/impl/ModelConfigServiceImpl.java b/src/main/java/com/bruce/modelprovider/service/impl/ModelConfigServiceImpl.java new file mode 100644 index 0000000..06bac35 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/service/impl/ModelConfigServiceImpl.java @@ -0,0 +1,93 @@ +package com.bruce.modelprovider.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bruce.modelprovider.dto.request.ModelConfigSaveRequest; +import com.bruce.modelprovider.dto.response.ModelConfigResponse; +import com.bruce.modelprovider.entity.ModelConfig; +import com.bruce.modelprovider.mapper.ModelConfigMapper; +import com.bruce.modelprovider.service.IModelConfigService; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.List; + +@Service +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class ModelConfigServiceImpl extends ServiceImpl implements IModelConfigService { + + @Override + /** + * 方法 listResponses,用于执行业务逻辑处理。 + */ + public List listResponses() { + /** + * 方法 list,用于定义接口能力契约。 + */ + return list().stream().map(ModelConfigResponse::fromEntity).toList(); + } + + @Override + /** + * 方法 getResponseById,用于执行业务逻辑处理。 + */ + public ModelConfigResponse getResponseById(Long id) { + return ModelConfigResponse.fromEntity(getById(id)); + } + + @Override + /** + * 方法 saveOrUpdate,用于执行业务逻辑处理。 + */ + public boolean saveOrUpdate(ModelConfigSaveRequest request) { + if (request == null) { + throw new IllegalArgumentException("模型保存请求不能为空"); + } + if (request.getProviderId() == null) { + throw new IllegalArgumentException("服务商ID不能为空"); + } + if (!StringUtils.hasText(request.getModelCode())) { + throw new IllegalArgumentException("模型编码不能为空"); + } + ModelConfig duplicate = lambdaQuery() + .eq(ModelConfig::getProviderId, request.getProviderId()) + .eq(ModelConfig::getModelCode, request.getModelCode().trim()) + .ne(request.getId() != null, ModelConfig::getId, request.getId()) + .one(); + if (duplicate != null) { + throw new IllegalArgumentException("同一服务商下模型编码已存在: " + request.getModelCode().trim()); + } + ModelConfig entity = request.getId() == null ? new ModelConfig() : getById(request.getId()); + if (entity == null) { + throw new IllegalArgumentException("模型不存在,ID: " + request.getId()); + } + entity.setProviderId(request.getProviderId()); + entity.setModelCode(request.getModelCode().trim()); + entity.setModelName(request.getModelName()); + entity.setUpstreamModel(request.getUpstreamModel()); + entity.setModelType(request.getModelType()); + entity.setEmbeddingDimension(request.getEmbeddingDimension()); + entity.setLocalModel(request.getLocalModel()); + entity.setDefaultModel(request.getDefaultModel()); + entity.setOptionsJson(request.getOptionsJson()); + entity.setEnabled(request.getEnabled() == null ? Boolean.TRUE : request.getEnabled()); + entity.setRemark(request.getRemark()); + return request.getId() == null ? save(entity) : updateById(entity); + } + + @Override + /** + * 方法 getEnabledModel,用于执行业务逻辑处理。 + */ + public ModelConfig getEnabledModel(Long modelId) { + ModelConfig model = getById(modelId); + if (model == null || !Boolean.TRUE.equals(model.getEnabled())) { + throw new IllegalArgumentException("模型不可用,ID: " + modelId); + } + return model; + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/service/impl/ModelProviderServiceImpl.java b/src/main/java/com/bruce/modelprovider/service/impl/ModelProviderServiceImpl.java new file mode 100644 index 0000000..e295eef --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/service/impl/ModelProviderServiceImpl.java @@ -0,0 +1,132 @@ +package com.bruce.modelprovider.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bruce.modelprovider.client.OpenAiCompatibleModelClient; +import com.bruce.modelprovider.dto.request.ModelProviderSaveRequest; +import com.bruce.modelprovider.dto.response.ModelProviderResponse; +import com.bruce.modelprovider.entity.ModelProvider; +import com.bruce.modelprovider.enums.ModelHealthStatusEnum; +import com.bruce.modelprovider.mapper.ModelProviderMapper; +import com.bruce.modelprovider.service.IModelProviderService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.Date; +import java.util.List; + +/** + * 模型服务商服务实现。 + *

+ * 主要职责: + * 1. 服务商配置的增删改查; + * 2. 服务商编码唯一性校验; + * 3. 默认健康状态初始化; + * 4. 对接上游健康检查并回写检查结果。 + */ +@Service +@RequiredArgsConstructor +/** + * ModelProviderServiceImpl,负责模型平台对应层的职责。 + */ +public class ModelProviderServiceImpl extends ServiceImpl + implements IModelProviderService { + + private final OpenAiCompatibleModelClient openAiCompatibleModelClient; + + /** + * 查询全部服务商并转换为响应对象。 + */ + @Override + /** + * 方法 listResponses,用于执行业务逻辑处理。 + */ + public List listResponses() { + /** + * 方法 list,用于定义接口能力契约。 + */ + return list().stream().map(ModelProviderResponse::fromEntity).toList(); + } + + /** + * 按ID查询服务商详情(响应对象已脱敏,不返回明文密钥)。 + */ + @Override + /** + * 方法 getResponseById,用于执行业务逻辑处理。 + */ + public ModelProviderResponse getResponseById(Long id) { + return ModelProviderResponse.fromEntity(getById(id)); + } + + /** + * 保存或更新服务商配置。 + * 会执行必要校验:请求非空、编码/名称非空、编码唯一。 + */ + @Override + /** + * 方法 saveOrUpdate,用于执行业务逻辑处理。 + */ + public boolean saveOrUpdate(ModelProviderSaveRequest request) { + if (request == null) { + throw new IllegalArgumentException("服务商保存请求不能为空"); + } + if (!StringUtils.hasText(request.getProviderCode())) { + throw new IllegalArgumentException("服务商编码不能为空"); + } + if (!StringUtils.hasText(request.getProviderName())) { + throw new IllegalArgumentException("服务商名称不能为空"); + } + ModelProvider duplicate = lambdaQuery() + .eq(ModelProvider::getProviderCode, request.getProviderCode().trim()) + .ne(request.getId() != null, ModelProvider::getId, request.getId()) + .one(); + if (duplicate != null) { + throw new IllegalArgumentException("服务商编码已存在: " + request.getProviderCode().trim()); + } + ModelProvider entity = request.getId() == null ? new ModelProvider() : getById(request.getId()); + if (entity == null) { + throw new IllegalArgumentException("服务商不存在,ID: " + request.getId()); + } + entity.setProviderCode(request.getProviderCode().trim()); + entity.setProviderName(request.getProviderName().trim()); + entity.setProviderType(request.getProviderType()); + entity.setProtocolType(request.getProtocolType()); + entity.setBaseUrl(request.getBaseUrl()); + entity.setAuthType(request.getAuthType()); + entity.setSecretRef(request.getSecretRef()); + entity.setApiKeyCipher(request.getApiKeyCipher()); + entity.setTimeoutMs(request.getTimeoutMs()); + entity.setPriority(request.getPriority()); + entity.setEnabled(request.getEnabled() == null ? Boolean.TRUE : request.getEnabled()); + entity.setRemark(request.getRemark()); + if (!StringUtils.hasText(entity.getHealthStatus())) { + entity.setHealthStatus(ModelHealthStatusEnum.UNKNOWN.name()); + } + return request.getId() == null ? save(entity) : updateById(entity); + } + + /** + * 主动健康检查并回写健康状态与检查时间。 + */ + @Override + /** + * 方法 checkHealth,用于执行业务逻辑处理。 + */ + public boolean checkHealth(Long id) { + ModelProvider provider = getById(id); + if (provider == null) { + throw new IllegalArgumentException("服务商不存在,ID: " + id); + } + boolean healthy = openAiCompatibleModelClient.health(provider); + provider.setHealthStatus(healthy ? ModelHealthStatusEnum.HEALTHY.name() : ModelHealthStatusEnum.UNHEALTHY.name()); + provider.setLastHealthCheckTime(new Date()); + /** + * 方法 updateById,用于定义接口能力契约。 + */ + return updateById(provider); + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/service/impl/ModelRouteRuleServiceImpl.java b/src/main/java/com/bruce/modelprovider/service/impl/ModelRouteRuleServiceImpl.java new file mode 100644 index 0000000..8c63975 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/service/impl/ModelRouteRuleServiceImpl.java @@ -0,0 +1,75 @@ +package com.bruce.modelprovider.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bruce.modelprovider.dto.request.ModelRouteRuleSaveRequest; +import com.bruce.modelprovider.dto.response.ModelRouteRuleResponse; +import com.bruce.modelprovider.entity.ModelRouteRule; +import com.bruce.modelprovider.mapper.ModelRouteRuleMapper; +import com.bruce.modelprovider.service.IModelRouteRuleService; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.List; + +@Service +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class ModelRouteRuleServiceImpl extends ServiceImpl + implements IModelRouteRuleService { + + @Override + /** + * 方法 listResponses,用于执行业务逻辑处理。 + */ + public List listResponses() { + /** + * 方法 list,用于定义接口能力契约。 + */ + return list().stream().map(ModelRouteRuleResponse::fromEntity).toList(); + } + + @Override + /** + * 方法 getResponseById,用于执行业务逻辑处理。 + */ + public ModelRouteRuleResponse getResponseById(Long id) { + return ModelRouteRuleResponse.fromEntity(getById(id)); + } + + @Override + /** + * 方法 saveOrUpdate,用于执行业务逻辑处理。 + */ + public boolean saveOrUpdate(ModelRouteRuleSaveRequest request) { + if (request == null || !StringUtils.hasText(request.getRouteCode())) { + throw new IllegalArgumentException("路由编码不能为空"); + } + ModelRouteRule duplicate = lambdaQuery() + .eq(ModelRouteRule::getRouteCode, request.getRouteCode().trim()) + .ne(request.getId() != null, ModelRouteRule::getId, request.getId()) + .one(); + if (duplicate != null) { + throw new IllegalArgumentException("路由编码已存在: " + request.getRouteCode().trim()); + } + ModelRouteRule entity = request.getId() == null ? new ModelRouteRule() : getById(request.getId()); + if (entity == null) { + throw new IllegalArgumentException("路由不存在,ID: " + request.getId()); + } + entity.setRouteCode(request.getRouteCode().trim()); + entity.setRouteName(request.getRouteName()); + entity.setTaskType(request.getTaskType()); + entity.setMatchScope(request.getMatchScope()); + entity.setScopeId(request.getScopeId()); + entity.setPrimaryModelId(request.getPrimaryModelId()); + entity.setFallbackModelIdsJson(request.getFallbackModelIdsJson()); + entity.setRouteStrategy(request.getRouteStrategy()); + entity.setMaxLatencyMs(request.getMaxLatencyMs()); + entity.setEnabled(request.getEnabled() == null ? Boolean.TRUE : request.getEnabled()); + entity.setRemark(request.getRemark()); + return request.getId() == null ? save(entity) : updateById(entity); + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/service/impl/ModelRouteServiceImpl.java b/src/main/java/com/bruce/modelprovider/service/impl/ModelRouteServiceImpl.java new file mode 100644 index 0000000..947f02d --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/service/impl/ModelRouteServiceImpl.java @@ -0,0 +1,153 @@ +package com.bruce.modelprovider.service.impl; + +import com.bruce.modelprovider.entity.ModelConfig; +import com.bruce.modelprovider.entity.ModelRouteRule; +import com.bruce.modelprovider.route.ModelRouteContext; +import com.bruce.modelprovider.route.ModelRouteDecision; +import com.bruce.modelprovider.service.IModelConfigService; +import com.bruce.modelprovider.service.IModelRouteRuleService; +import com.bruce.modelprovider.service.IModelRouteService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 模型路由服务实现。 + *

+ * 主要职责: + * 1. 按优先级选择路由规则:范围规则 -> 任务全局规则 -> 模型类型默认模型; + * 2. 根据规则解析主模型与备用模型,并过滤不可用模型; + * 3. 对 Embedding 场景执行维度一致性校验,避免不同向量空间混用; + * 4. 在 LOCAL_FIRST 策略下优先选择本地模型。 + */ +@Service +@RequiredArgsConstructor +/** + * ModelRouteServiceImpl,负责模型平台对应层的职责。 + */ +public class ModelRouteServiceImpl implements IModelRouteService { + + private final IModelRouteRuleService modelRouteRuleService; + private final IModelConfigService modelConfigService; + + /** + * 执行一次路由决策。 + * + * @param context 路由上下文,不能为空 + * @return 路由决策结果,包含主模型、备用模型和命中原因 + */ + @Override + /** + * 方法 route,用于执行业务逻辑处理。 + */ + public ModelRouteDecision route(ModelRouteContext context) { + if (context == null) { + throw new IllegalArgumentException("路由上下文不能为空"); + } + ModelRouteRule rule = selectRule(context); + if (rule == null) { + ModelConfig defaultModel = modelConfigService.lambdaQuery() + .eq(ModelConfig::getModelType, context.getRequiredModelType()) + .eq(ModelConfig::getDefaultModel, true) + .eq(ModelConfig::getEnabled, true) + .last("limit 1") + .one(); + if (defaultModel == null) { + throw new IllegalStateException("未找到可用模型路由,请先配置规则或默认模型"); + } + ModelRouteDecision decision = new ModelRouteDecision(); + decision.setPrimaryModel(defaultModel); + decision.setRouteStrategy("MANUAL"); + decision.setReason("命中模型类型默认模型"); + return decision; + } + + ModelConfig primary = modelConfigService.getEnabledModel(rule.getPrimaryModelId()); + if ("EMBEDDING".equals(context.getRequiredModelType()) && context.getRequiredEmbeddingDimension() != null + && !context.getRequiredEmbeddingDimension().equals(primary.getEmbeddingDimension())) { + throw new IllegalStateException("主模型Embedding维度不匹配"); + } + + List fallbackModels = new ArrayList<>(); + for (Long fallbackId : parseFallbackIds(rule.getFallbackModelIdsJson())) { + ModelConfig fallback = modelConfigService.getById(fallbackId); + if (fallback == null || !Boolean.TRUE.equals(fallback.getEnabled())) { + continue; + } + if ("EMBEDDING".equals(context.getRequiredModelType()) && context.getRequiredEmbeddingDimension() != null + && !context.getRequiredEmbeddingDimension().equals(fallback.getEmbeddingDimension())) { + continue; + } + fallbackModels.add(fallback); + } + + if ("LOCAL_FIRST".equals(rule.getRouteStrategy()) && !Boolean.TRUE.equals(primary.getLocalModel())) { + ModelConfig localCandidate = fallbackModels.stream().filter(ModelConfig::getLocalModel).findFirst().orElse(null); + if (localCandidate != null) { + fallbackModels.remove(localCandidate); + fallbackModels.add(0, primary); + primary = localCandidate; + } + } + + ModelRouteDecision decision = new ModelRouteDecision(); + decision.setPrimaryModel(primary); + decision.setFallbackModels(fallbackModels); + decision.setRouteStrategy(rule.getRouteStrategy()); + decision.setReason("命中规则: " + rule.getRouteCode()); + return decision; + } + + /** + * 优先匹配范围级规则;未命中时回退到同任务类型的全局规则。 + */ + private ModelRouteRule selectRule(ModelRouteContext context) { + ModelRouteRule scopeRule = modelRouteRuleService.lambdaQuery() + .eq(ModelRouteRule::getEnabled, true) + .eq(ModelRouteRule::getTaskType, context.getTaskType()) + .eq(context.getMatchScope() != null, ModelRouteRule::getMatchScope, context.getMatchScope()) + .eq(context.getScopeId() != null, ModelRouteRule::getScopeId, context.getScopeId()) + .last("limit 1") + .one(); + if (scopeRule != null) { + return scopeRule; + } + return modelRouteRuleService.lambdaQuery() + .eq(ModelRouteRule::getEnabled, true) + .eq(ModelRouteRule::getTaskType, context.getTaskType()) + .eq(ModelRouteRule::getMatchScope, "GLOBAL") + .last("limit 1") + .one(); + } + + /** + * 解析备用模型ID数组。 + *

+ * 支持最简 JSON 数组文本(如 [1,2,3]),解析失败时返回空列表,避免阻断主流程。 + */ + private List parseFallbackIds(String json) { + if (json == null || json.isBlank()) { + return List.of(); + } + String normalized = json.trim(); + if (!normalized.startsWith("[") || !normalized.endsWith("]")) { + return List.of(); + } + String body = normalized.substring(1, normalized.length() - 1).trim(); + if (body.isEmpty()) { + return List.of(); + } + return List.of(body.split(",")).stream() + .map(String::trim) + .map(v -> v.replace("\"", "")) + .filter(v -> !v.isEmpty()) + .map(Long::valueOf) + .collect(Collectors.toList()); + } +} + + + diff --git a/src/main/java/com/bruce/modelprovider/service/impl/RagStoreModelConfigServiceImpl.java b/src/main/java/com/bruce/modelprovider/service/impl/RagStoreModelConfigServiceImpl.java new file mode 100644 index 0000000..6694db6 --- /dev/null +++ b/src/main/java/com/bruce/modelprovider/service/impl/RagStoreModelConfigServiceImpl.java @@ -0,0 +1,88 @@ +package com.bruce.modelprovider.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bruce.modelprovider.dto.request.RagStoreModelConfigSaveRequest; +import com.bruce.modelprovider.dto.response.RagStoreModelConfigResponse; +import com.bruce.modelprovider.entity.ModelConfig; +import com.bruce.modelprovider.entity.RagStoreModelConfig; +import com.bruce.modelprovider.mapper.RagStoreModelConfigMapper; +import com.bruce.modelprovider.service.IModelConfigService; +import com.bruce.modelprovider.service.IRagStoreModelConfigService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +/** + * 该类属于模型平台模块,用于承载对应分层职责。 + */ +public class RagStoreModelConfigServiceImpl extends ServiceImpl + implements IRagStoreModelConfigService { + + private final IModelConfigService modelConfigService; + + @Override + /** + * 方法 getByStoreId,用于执行业务逻辑处理。 + */ + public RagStoreModelConfigResponse getByStoreId(Long storeId) { + return RagStoreModelConfigResponse.fromEntity(getActiveEntity(storeId)); + } + + @Override + /** + * 方法 saveOrUpdate,用于执行业务逻辑处理。 + */ + public boolean saveOrUpdate(RagStoreModelConfigSaveRequest request) { + if (request == null || request.getStoreId() == null) { + throw new IllegalArgumentException("知识库模型配置请求不能为空"); + } + ModelConfig embeddingModel = modelConfigService.getEnabledModel(request.getEmbeddingModelId()); + if (!"EMBEDDING".equals(embeddingModel.getModelType())) { + throw new IllegalArgumentException("仅允许配置EMBEDDING类型模型"); + } + if (request.getEmbeddingDimension() == null || request.getEmbeddingDimension() != 1024) { + throw new IllegalArgumentException("首期仅支持1024维Embedding"); + } + RagStoreModelConfig current = getActiveEntity(request.getStoreId()); + RagStoreModelConfig entity = current == null ? new RagStoreModelConfig() : current; + entity.setStoreId(request.getStoreId()); + entity.setEmbeddingModelId(request.getEmbeddingModelId()); + entity.setEmbeddingDimension(request.getEmbeddingDimension()); + entity.setChunkStrategy(request.getChunkStrategy()); + entity.setChunkSize(request.getChunkSize()); + entity.setChunkOverlap(request.getChunkOverlap()); + entity.setDelimiter(request.getDelimiter()); + entity.setActive(Boolean.TRUE); + entity.setRemark(request.getRemark()); + if (current == null) { + entity.setIndexVersion(1); + /** + * 方法 save,用于定义接口能力契约。 + */ + return save(entity); + } + boolean changed = !current.getEmbeddingModelId().equals(request.getEmbeddingModelId()) + || !current.getEmbeddingDimension().equals(request.getEmbeddingDimension()); + if (changed) { + entity.setIndexVersion(current.getIndexVersion() == null ? 2 : current.getIndexVersion() + 1); + } + /** + * 方法 updateById,用于定义接口能力契约。 + */ + return updateById(entity); + } + + @Override + /** + * 方法 getActiveEntity,用于执行业务逻辑处理。 + */ + public RagStoreModelConfig getActiveEntity(Long storeId) { + return lambdaQuery().eq(RagStoreModelConfig::getStoreId, storeId) + .eq(RagStoreModelConfig::getActive, true) + .one(); + } +} + + + diff --git a/src/main/java/com/bruce/rag/service/impl/RagDocumentChunkServiceImpl.java b/src/main/java/com/bruce/rag/service/impl/RagDocumentChunkServiceImpl.java index 9320be5..043b2c6 100644 --- a/src/main/java/com/bruce/rag/service/impl/RagDocumentChunkServiceImpl.java +++ b/src/main/java/com/bruce/rag/service/impl/RagDocumentChunkServiceImpl.java @@ -2,14 +2,22 @@ package com.bruce.rag.service.impl; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.bruce.common.document.parse.DocumentParseResult; +import com.bruce.modelprovider.entity.RagStoreModelConfig; +import com.bruce.modelprovider.gateway.EmbeddingModelGateway; +import com.bruce.modelprovider.gateway.EmbeddingRequest; +import com.bruce.modelprovider.gateway.EmbeddingResult; +import com.bruce.modelprovider.service.IRagStoreModelConfigService; import com.bruce.rag.dto.request.RagDocumentChunkRequest; import com.bruce.rag.entity.RagChunk; +import com.bruce.rag.entity.RagChunkEmbedding; import com.bruce.rag.entity.RagDocument; import com.bruce.rag.entity.RagDocumentParseResult; import com.bruce.rag.enums.RagChunkStrategyEnum; +import com.bruce.rag.enums.RagIndexStatusEnum; import com.bruce.rag.parse.Chunker; import com.bruce.rag.parse.ChunkerFactory; import com.bruce.rag.parse.RagChunkCommand; +import com.bruce.rag.service.IRagChunkEmbeddingService; import com.bruce.rag.service.IRagChunkService; import com.bruce.rag.service.IRagDocumentChunkService; import com.bruce.rag.service.IRagDocumentParseResultService; @@ -19,12 +27,18 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.DigestUtils; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; @Slf4j @Service @RequiredArgsConstructor +/** + * RagDocumentChunkServiceImpl,负责模型平台对应层的职责。 + */ public class RagDocumentChunkServiceImpl implements IRagDocumentChunkService { private final IRagDocumentService ragDocumentService; @@ -35,8 +49,17 @@ public class RagDocumentChunkServiceImpl implements IRagDocumentChunkService { private final IRagChunkService ragChunkService; + private final IRagChunkEmbeddingService ragChunkEmbeddingService; + + private final IRagStoreModelConfigService ragStoreModelConfigService; + + private final EmbeddingModelGateway embeddingModelGateway; + @Override @Async + /** + * 方法 submitChunkTask,用于执行业务逻辑处理。 + */ public void submitChunkTask(RagDocumentChunkRequest request) { validateRequest(request); RagChunkStrategyEnum strategy = RagChunkStrategyEnum.fromValue(request.getChunkStrategy()); @@ -48,10 +71,15 @@ public class RagDocumentChunkServiceImpl implements IRagDocumentChunkService { log.warn("RagDocumentChunkServiceImpl.chunkAsync document not found, documentId={}", documentId); continue; } + updateIndexStatus(document, RagIndexStatusEnum.INDEXING.name(), null); RagDocumentParseResult snapshot = ragDocumentParseResultService.getByDocumentId(documentId); if (snapshot == null) { throw new IllegalStateException("文档尚未生成解析快照,documentId=" + documentId); } + RagStoreModelConfig storeModelConfig = ragStoreModelConfigService.getActiveEntity(document.getStoreId()); + if (storeModelConfig == null || storeModelConfig.getEmbeddingModelId() == null) { + throw new IllegalStateException("请先配置知识库 Embedding 模型"); + } DocumentParseResult parseResult = ragDocumentParseResultService.toParseResult(snapshot); RagChunkCommand command = new RagChunkCommand(); command.setDocument(document); @@ -62,20 +90,78 @@ public class RagDocumentChunkServiceImpl implements IRagDocumentChunkService { command.setDelimiter(request.getDelimiter()); List chunks = chunker.chunk(command); + ragChunkEmbeddingService.remove(Wrappers.lambdaQuery() + .eq(RagChunkEmbedding::getDocumentId, documentId)); ragChunkService.remove(Wrappers.lambdaQuery() .eq(RagChunk::getDocumentId, documentId)); if (!chunks.isEmpty()) { ragChunkService.saveBatch(chunks); } + writeEmbeddings(document, chunks, storeModelConfig); + updateIndexStatus(document, RagIndexStatusEnum.INDEXED.name(), null); log.info("RagDocumentChunkServiceImpl.chunkAsync success, documentId={}, chunkCount={}", documentId, chunks.size()); } catch (RuntimeException e) { + RagDocument failedDoc = ragDocumentService.getById(documentId); + if (failedDoc != null) { + updateIndexStatus(failedDoc, RagIndexStatusEnum.FAILED.name(), e.getMessage()); + } log.warn("RagDocumentChunkServiceImpl.chunkAsync failed, documentId={}, message={}", documentId, e.getMessage()); } } } + @Transactional + /** + * 方法 writeEmbeddings,用于执行业务逻辑处理。 + */ + public void writeEmbeddings(RagDocument document, List chunks, RagStoreModelConfig storeModelConfig) { + if (chunks == null || chunks.isEmpty()) { + return; + } + EmbeddingRequest embeddingRequest = new EmbeddingRequest(); + embeddingRequest.setTexts(chunks.stream().map(RagChunk::getChunkContent).toList()); + embeddingRequest.setTaskType("RAG_EMBEDDING"); + embeddingRequest.setMatchScope("RAG_STORE"); + embeddingRequest.setScopeId(document.getStoreId()); + embeddingRequest.setBizType("RAG_DOCUMENT_INDEX"); + embeddingRequest.setBizId(String.valueOf(document.getId())); + embeddingRequest.setExpectedDimension(storeModelConfig.getEmbeddingDimension()); + EmbeddingResult result = embeddingModelGateway.embed(embeddingRequest); + if (result.getVectors().size() != chunks.size()) { + throw new IllegalStateException("向量数量与切片数量不一致"); + } + List embeddingRows = new ArrayList<>(); + for (int i = 0; i < chunks.size(); i++) { + RagChunk chunk = chunks.get(i); + List vector = result.getVectors().get(i); + RagChunkEmbedding row = new RagChunkEmbedding(); + row.setStoreId(document.getStoreId()); + row.setDocumentId(document.getId()); + row.setChunkId(chunk.getId()); + row.setEmbeddingModel(result.getModelName()); + row.setEmbeddingDimension(result.getDimension()); + row.setEmbedding(vector.toString()); + row.setContentHash(DigestUtils.md5DigestAsHex(chunk.getChunkContent().getBytes(StandardCharsets.UTF_8))); + row.setEnabled(true); + embeddingRows.add(row); + } + ragChunkEmbeddingService.saveBatch(embeddingRows); + } + + /** + * 方法 updateIndexStatus,用于执行业务逻辑处理。 + */ + private void updateIndexStatus(RagDocument document, String status, String errorMessage) { + document.setIndexStatus(status); + document.setErrorMessage(errorMessage == null ? null : errorMessage.substring(0, Math.min(errorMessage.length(), 1000))); + ragDocumentService.updateById(document); + } + + /** + * 方法 validateRequest,用于执行业务逻辑处理。 + */ private void validateRequest(RagDocumentChunkRequest request) { if (request == null) { throw new IllegalArgumentException("切片请求不能为空"); @@ -86,3 +172,5 @@ public class RagDocumentChunkServiceImpl implements IRagDocumentChunkService { RagChunkStrategyEnum.fromValue(request.getChunkStrategy()); } } + + diff --git a/src/test/java/com/bruce/common/enumconfig/EnumDefinitionTests.java b/src/test/java/com/bruce/common/enumconfig/EnumDefinitionTests.java index e824d30..85bd8f7 100644 --- a/src/test/java/com/bruce/common/enumconfig/EnumDefinitionTests.java +++ b/src/test/java/com/bruce/common/enumconfig/EnumDefinitionTests.java @@ -3,6 +3,13 @@ package com.bruce.common.enumconfig; import com.bruce.common.enums.CommonStatusEnum; import com.bruce.common.enums.EnableStatusEnum; import com.bruce.common.enums.PersistableSysEnumDefinition; +import com.bruce.modelprovider.enums.ModelCallStatusEnum; +import com.bruce.modelprovider.enums.ModelHealthStatusEnum; +import com.bruce.modelprovider.enums.ModelProtocolTypeEnum; +import com.bruce.modelprovider.enums.ModelProviderTypeEnum; +import com.bruce.modelprovider.enums.ModelRouteStrategyEnum; +import com.bruce.modelprovider.enums.ModelTaskTypeEnum; +import com.bruce.modelprovider.enums.ModelTypeEnum; import com.bruce.rag.enums.RagChunkStrategyEnum; import com.bruce.rag.enums.RagIndexStatusEnum; import com.bruce.rag.enums.RagParseStatusEnum; @@ -14,6 +21,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows; class EnumDefinitionTests { @Test + /** + * 方法 enumValuesShouldBeStable,用于执行业务逻辑处理。 + */ void enumValuesShouldBeStable() { assertEquals(1, EnableStatusEnum.ENABLED.getValue()); assertEquals(0, EnableStatusEnum.DISABLED.getValue()); @@ -30,9 +40,19 @@ class EnumDefinitionTests { assertEquals(1, RagChunkStrategyEnum.FIXED_LENGTH.getValue()); assertEquals(5, RagChunkStrategyEnum.DELIMITER.getValue()); assertEquals(6, RagChunkStrategyEnum.SEMANTIC.getValue()); + assertEquals(1, ModelProviderTypeEnum.OLLAMA.getValue()); + assertEquals(1, ModelProtocolTypeEnum.OPENAI_COMPATIBLE.getValue()); + assertEquals(2, ModelTypeEnum.EMBEDDING.getValue()); + assertEquals(1, ModelTaskTypeEnum.RAG_EMBEDDING.getValue()); + assertEquals(4, ModelRouteStrategyEnum.MANUAL.getValue()); + assertEquals(1, ModelCallStatusEnum.SUCCESS.getValue()); + assertEquals(2, ModelHealthStatusEnum.HEALTHY.getValue()); } @Test + /** + * 方法 enumNamesShouldBeStable,用于执行业务逻辑处理。 + */ void enumNamesShouldBeStable() { assertEquals("启用", EnableStatusEnum.ENABLED.getLabel()); assertEquals("禁用", EnableStatusEnum.DISABLED.getLabel()); @@ -47,9 +67,19 @@ class EnumDefinitionTests { assertEquals("固定长度切片", RagChunkStrategyEnum.FIXED_LENGTH.getLabel()); assertEquals("按分隔符切片", RagChunkStrategyEnum.DELIMITER.getLabel()); assertEquals("语义切片", RagChunkStrategyEnum.SEMANTIC.getLabel()); + assertEquals("Ollama", ModelProviderTypeEnum.OLLAMA.getLabel()); + assertEquals("OpenAI兼容协议", ModelProtocolTypeEnum.OPENAI_COMPATIBLE.getLabel()); + assertEquals("向量模型", ModelTypeEnum.EMBEDDING.getLabel()); + assertEquals("RAG文档向量化", ModelTaskTypeEnum.RAG_EMBEDDING.getLabel()); + assertEquals("手工指定", ModelRouteStrategyEnum.MANUAL.getLabel()); + assertEquals("成功", ModelCallStatusEnum.SUCCESS.getLabel()); + assertEquals("健康", ModelHealthStatusEnum.HEALTHY.getLabel()); } @Test + /** + * 方法 enumsShouldExposeStableSysEnumMetadata,用于执行业务逻辑处理。 + */ void enumsShouldExposeStableSysEnumMetadata() { PersistableSysEnumDefinition chunkStrategy = RagChunkStrategyEnum.DELIMITER; PersistableSysEnumDefinition parseStatus = RagParseStatusEnum.PARSED; @@ -69,12 +99,22 @@ class EnumDefinitionTests { assertEquals("common", enableStatus.getCatalog()); assertEquals("enable_status", enableStatus.getType()); assertEquals("启用", enableStatus.getName()); + + PersistableSysEnumDefinition providerType = ModelProviderTypeEnum.OLLAMA; + assertEquals("model_provider", providerType.getCatalog()); + assertEquals("provider_type", providerType.getType()); + assertEquals("Ollama", providerType.getName()); } @Test + /** + * 方法 ragChunkStrategyShouldResolveByIntegerValue,用于执行业务逻辑处理。 + */ void ragChunkStrategyShouldResolveByIntegerValue() { assertEquals(RagChunkStrategyEnum.FIXED_LENGTH, RagChunkStrategyEnum.fromValue(1)); assertEquals(RagChunkStrategyEnum.DELIMITER, RagChunkStrategyEnum.fromValue(5)); assertThrows(IllegalArgumentException.class, () -> RagChunkStrategyEnum.fromValue(999)); } } + + diff --git a/src/test/java/com/bruce/common/enumconfig/SysEnumDataInitTests.java b/src/test/java/com/bruce/common/enumconfig/SysEnumDataInitTests.java index be8a3ee..a4166d4 100644 --- a/src/test/java/com/bruce/common/enumconfig/SysEnumDataInitTests.java +++ b/src/test/java/com/bruce/common/enumconfig/SysEnumDataInitTests.java @@ -5,6 +5,13 @@ import com.bruce.common.domain.entity.SysEnum; import com.bruce.common.enums.PersistableSysEnumDefinition; import com.bruce.common.enums.CommonStatusEnum; import com.bruce.common.enums.EnableStatusEnum; +import com.bruce.modelprovider.enums.ModelCallStatusEnum; +import com.bruce.modelprovider.enums.ModelHealthStatusEnum; +import com.bruce.modelprovider.enums.ModelProtocolTypeEnum; +import com.bruce.modelprovider.enums.ModelProviderTypeEnum; +import com.bruce.modelprovider.enums.ModelRouteStrategyEnum; +import com.bruce.modelprovider.enums.ModelTaskTypeEnum; +import com.bruce.modelprovider.enums.ModelTypeEnum; import com.bruce.common.service.ISysEnumService; import com.bruce.rag.enums.RagChunkStrategyEnum; import com.bruce.rag.enums.RagIndexStatusEnum; @@ -24,13 +31,23 @@ class SysEnumDataInitTests { private ISysEnumService sysEnumService; @Test + /** + * 方法 initDefaultEnums,用于执行业务逻辑处理。 + */ public void initDefaultEnums() { List groups = List.of( buildGroup(EnableStatusEnum.values()), buildGroup(CommonStatusEnum.values()), buildGroup(RagParseStatusEnum.values()), buildGroup(RagIndexStatusEnum.values()), - buildGroup(RagChunkStrategyEnum.values()) + buildGroup(RagChunkStrategyEnum.values()), + buildGroup(ModelProviderTypeEnum.values()), + buildGroup(ModelProtocolTypeEnum.values()), + buildGroup(ModelTypeEnum.values()), + buildGroup(ModelTaskTypeEnum.values()), + buildGroup(ModelRouteStrategyEnum.values()), + buildGroup(ModelCallStatusEnum.values()), + buildGroup(ModelHealthStatusEnum.values()) ); SysEnumDefinitionSyncSupport.validateUniqueGroupKeys(groups); @@ -40,7 +57,12 @@ class SysEnumDataInitTests { } } + /** + * 方法 buildGroup,用于执行业务逻辑处理。 + */ private SysEnumDefinitionSyncSupport.EnumGroup buildGroup(PersistableSysEnumDefinition[] definitions) { return SysEnumDefinitionSyncSupport.groupOf(List.of(definitions)); } } + + diff --git a/src/test/java/com/bruce/modelprovider/ModelProviderComponentStructureTests.java b/src/test/java/com/bruce/modelprovider/ModelProviderComponentStructureTests.java new file mode 100644 index 0000000..3f2da2e --- /dev/null +++ b/src/test/java/com/bruce/modelprovider/ModelProviderComponentStructureTests.java @@ -0,0 +1,100 @@ +package com.bruce.modelprovider; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.service.IService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bruce.common.domain.model.RequestResult; +import com.bruce.modelprovider.controller.ModelCallLogController; +import com.bruce.modelprovider.controller.ModelConfigController; +import com.bruce.modelprovider.controller.ModelProviderController; +import com.bruce.modelprovider.controller.ModelRouteRuleController; +import com.bruce.modelprovider.dto.response.ModelProviderResponse; +import com.bruce.modelprovider.entity.ModelCallLog; +import com.bruce.modelprovider.entity.ModelConfig; +import com.bruce.modelprovider.entity.ModelProvider; +import com.bruce.modelprovider.entity.ModelRouteRule; +import com.bruce.modelprovider.entity.RagStoreModelConfig; +import com.bruce.modelprovider.mapper.ModelCallLogMapper; +import com.bruce.modelprovider.mapper.ModelConfigMapper; +import com.bruce.modelprovider.mapper.ModelProviderMapper; +import com.bruce.modelprovider.mapper.ModelRouteRuleMapper; +import com.bruce.modelprovider.mapper.RagStoreModelConfigMapper; +import com.bruce.modelprovider.service.IModelCallLogService; +import com.bruce.modelprovider.service.IModelConfigService; +import com.bruce.modelprovider.service.IModelProviderService; +import com.bruce.modelprovider.service.IModelRouteRuleService; +import com.bruce.modelprovider.service.IRagStoreModelConfigService; +import com.bruce.modelprovider.service.impl.ModelCallLogServiceImpl; +import com.bruce.modelprovider.service.impl.ModelConfigServiceImpl; +import com.bruce.modelprovider.service.impl.ModelProviderServiceImpl; +import com.bruce.modelprovider.service.impl.ModelRouteRuleServiceImpl; +import com.bruce.modelprovider.service.impl.RagStoreModelConfigServiceImpl; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.*; + +class ModelProviderComponentStructureTests { + + @Test + /** + * 方法 modelProviderComponentsShouldReuseMybatisPlusBaseTypes,用于执行业务逻辑处理。 + */ + void modelProviderComponentsShouldReuseMybatisPlusBaseTypes() { + assertTrue(BaseMapper.class.isAssignableFrom(ModelProviderMapper.class)); + assertTrue(BaseMapper.class.isAssignableFrom(ModelConfigMapper.class)); + assertTrue(BaseMapper.class.isAssignableFrom(ModelRouteRuleMapper.class)); + assertTrue(BaseMapper.class.isAssignableFrom(ModelCallLogMapper.class)); + assertTrue(BaseMapper.class.isAssignableFrom(RagStoreModelConfigMapper.class)); + + assertTrue(IService.class.isAssignableFrom(IModelProviderService.class)); + assertTrue(IService.class.isAssignableFrom(IModelConfigService.class)); + assertTrue(IService.class.isAssignableFrom(IModelRouteRuleService.class)); + assertTrue(IService.class.isAssignableFrom(IModelCallLogService.class)); + assertTrue(IService.class.isAssignableFrom(IRagStoreModelConfigService.class)); + + assertTrue(ServiceImpl.class.isAssignableFrom(ModelProviderServiceImpl.class)); + assertTrue(ServiceImpl.class.isAssignableFrom(ModelConfigServiceImpl.class)); + assertTrue(ServiceImpl.class.isAssignableFrom(ModelRouteRuleServiceImpl.class)); + assertTrue(ServiceImpl.class.isAssignableFrom(ModelCallLogServiceImpl.class)); + assertTrue(ServiceImpl.class.isAssignableFrom(RagStoreModelConfigServiceImpl.class)); + } + + @Test + void controllersShouldExposeRequestResult() throws NoSuchMethodException { + Method providerQuery = ModelProviderController.class.getMethod("query"); + Method configQuery = ModelConfigController.class.getMethod("query"); + Method routeQuery = ModelRouteRuleController.class.getMethod("query"); + Method logQuery = ModelCallLogController.class.getMethod("query", com.bruce.modelprovider.dto.request.ModelCallLogQueryRequest.class); + + assertEquals(RequestResult.class, providerQuery.getReturnType()); + assertEquals(RequestResult.class, configQuery.getReturnType()); + assertEquals(RequestResult.class, routeQuery.getReturnType()); + assertEquals(RequestResult.class, logQuery.getReturnType()); + } + + @Test + /** + * 方法 modelProviderResponseShouldHideApiKeyCipher,用于执行业务逻辑处理。 + */ + void modelProviderResponseShouldHideApiKeyCipher() { + ModelProvider provider = new ModelProvider(); + provider.setProviderCode("sf"); + provider.setApiKeyCipher("secret"); + + ModelProviderResponse response = ModelProviderResponse.fromEntity(provider); + assertTrue(response.getHasApiKey()); + } + + @Test + void entitiesShouldMapCoreFields() throws NoSuchFieldException { + assertEquals(String.class, ModelProvider.class.getDeclaredField("providerCode").getType()); + assertEquals(String.class, ModelConfig.class.getDeclaredField("modelCode").getType()); + assertEquals(Long.class, ModelRouteRule.class.getDeclaredField("primaryModelId").getType()); + assertEquals(String.class, ModelCallLog.class.getDeclaredField("requestId").getType()); + assertEquals(Long.class, RagStoreModelConfig.class.getDeclaredField("embeddingModelId").getType()); + } +} + +