refactor(modules): 拆分多模块工程并收口common基础模块
This commit is contained in:
50
common-agent-modelprovider/pom.xml
Normal file
50
common-agent-modelprovider/pom.xml
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.bruce</groupId>
|
||||
<artifactId>common-agent-parent</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>common-agent-modelprovider</artifactId>
|
||||
<name>common-agent-modelprovider</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.bruce</groupId>
|
||||
<artifactId>common-agent-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.bruce.modelprovider.client;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class OpenAiChatCompletionResult {
|
||||
private String upstreamRequestId;
|
||||
private String content;
|
||||
private Integer promptTokens;
|
||||
private Integer completionTokens;
|
||||
private Integer totalTokens;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.bruce.modelprovider.client;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class OpenAiChatMessage {
|
||||
private String role;
|
||||
private String content;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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<List<Double>> embeddings(ModelProvider provider, ModelConfig model, List<String> texts, Integer expectedDimension);
|
||||
/**
|
||||
* 方法 chatCompletions,用于定义接口能力契约。
|
||||
*/
|
||||
OpenAiChatCompletionResult chatCompletions(ModelProvider provider, ModelConfig model, List<OpenAiChatMessage> messages);
|
||||
/**
|
||||
* 方法 health,用于定义接口能力契约。
|
||||
*/
|
||||
boolean health(ModelProvider provider);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
package com.bruce.modelprovider.client;
|
||||
|
||||
import com.bruce.modelprovider.config.AiSecretProperties;
|
||||
import com.bruce.modelprovider.entity.ModelConfig;
|
||||
import com.bruce.modelprovider.entity.ModelProvider;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* OpenAI-compatible 客户端实现。
|
||||
* <p>
|
||||
* 说明:
|
||||
* 1. 统一使用服务商 baseUrl 访问上游接口;
|
||||
* 2. Embedding 调用使用 `/embeddings`;
|
||||
* 3. 健康检查优先使用轻量接口 `/models`;
|
||||
* 4. API Key 从 `secretRef` 对应环境变量读取,不在代码中硬编码。
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
/**
|
||||
* OpenAiCompatibleModelClientImpl,负责模型平台对应层的职责。
|
||||
*/
|
||||
public class OpenAiCompatibleModelClientImpl implements OpenAiCompatibleModelClient {
|
||||
|
||||
/**
|
||||
* 统一读取独立 AI 配置文件中的密钥映射。
|
||||
*/
|
||||
private final AiSecretProperties aiSecretProperties;
|
||||
|
||||
/**
|
||||
* 调用上游 Embedding 接口并解析向量数组。
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
/**
|
||||
* 方法 embeddings,用于执行业务逻辑处理。
|
||||
*/
|
||||
public List<List<Double>> embeddings(ModelProvider provider, ModelConfig model, List<String> texts, Integer expectedDimension) {
|
||||
RestClient client = RestClient.builder().baseUrl(provider.getBaseUrl()).build();
|
||||
Map<String, Object> 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<String, Object> response = request.retrieve().body(Map.class);
|
||||
if (response == null || !(response.get("data") instanceof List<?> dataList)) {
|
||||
throw new IllegalStateException("上游Embedding响应缺少data字段");
|
||||
}
|
||||
List<List<Double>> 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<Double> vector = new ArrayList<>();
|
||||
for (Object value : vectorValues) {
|
||||
vector.add(Double.parseDouble(String.valueOf(value)));
|
||||
}
|
||||
vectors.add(vector);
|
||||
}
|
||||
return vectors;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public OpenAiChatCompletionResult chatCompletions(ModelProvider provider, ModelConfig model, List<OpenAiChatMessage> messages) {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
throw new IllegalArgumentException("聊天消息不能为空");
|
||||
}
|
||||
RestClient client = RestClient.builder().baseUrl(provider.getBaseUrl()).build();
|
||||
|
||||
List<Map<String, String>> payloadMessages = new ArrayList<>();
|
||||
for (OpenAiChatMessage message : messages) {
|
||||
if (message == null || !StringUtils.hasText(message.getContent())) {
|
||||
continue;
|
||||
}
|
||||
Map<String, String> item = new HashMap<>();
|
||||
item.put("role", StringUtils.hasText(message.getRole()) ? message.getRole().trim() : "user");
|
||||
item.put("content", message.getContent());
|
||||
payloadMessages.add(item);
|
||||
}
|
||||
if (payloadMessages.isEmpty()) {
|
||||
throw new IllegalArgumentException("聊天消息内容不能为空");
|
||||
}
|
||||
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("model", model.getUpstreamModel());
|
||||
body.put("messages", payloadMessages);
|
||||
|
||||
RestClient.RequestBodySpec request = client.post().uri("/chat/completions")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(body);
|
||||
String apiKey = resolveApiKey(provider);
|
||||
if (apiKey != null) {
|
||||
request = request.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey);
|
||||
}
|
||||
|
||||
Map<String, Object> response = request.retrieve().body(Map.class);
|
||||
if (response == null || !(response.get("choices") instanceof List<?> choices) || choices.isEmpty()) {
|
||||
throw new IllegalStateException("上游Chat响应缺少choices字段");
|
||||
}
|
||||
Object first = choices.getFirst();
|
||||
if (!(first instanceof Map<?, ?> firstChoice)
|
||||
|| !(firstChoice.get("message") instanceof Map<?, ?> message)
|
||||
|| !(message.get("content") instanceof String content)
|
||||
|| !StringUtils.hasText(content)) {
|
||||
throw new IllegalStateException("上游Chat响应缺少message.content");
|
||||
}
|
||||
|
||||
OpenAiChatCompletionResult result = new OpenAiChatCompletionResult();
|
||||
result.setUpstreamRequestId(String.valueOf(response.get("id")));
|
||||
result.setContent(content);
|
||||
if (response.get("usage") instanceof Map<?, ?> usage) {
|
||||
result.setPromptTokens(toInteger(usage.get("prompt_tokens")));
|
||||
result.setCompletionTokens(toInteger(usage.get("completion_tokens")));
|
||||
result.setTotalTokens(toInteger(usage.get("total_tokens")));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用 `/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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取服务商密钥:
|
||||
* 1) 优先读取 Spring AI 独立配置文件(ai-config.ini);
|
||||
* 2) 再读取环境变量,兼容原有部署方式;
|
||||
* 3) 最后回退数据库密文/占位字段(兼容历史数据)。
|
||||
*/
|
||||
private String resolveApiKey(ModelProvider provider) {
|
||||
if (provider.getSecretRef() != null && !provider.getSecretRef().isBlank()) {
|
||||
String secretRef = provider.getSecretRef().trim();
|
||||
String fromSpringConfig = aiSecretProperties.getApiKeyBySecretRef(secretRef);
|
||||
if (StringUtils.hasText(fromSpringConfig)) {
|
||||
return fromSpringConfig;
|
||||
}
|
||||
String fromEnv = System.getenv(secretRef);
|
||||
if (StringUtils.hasText(fromEnv)) {
|
||||
return fromEnv.trim();
|
||||
}
|
||||
}
|
||||
if (StringUtils.hasText(provider.getApiKeyCipher())) {
|
||||
return provider.getApiKeyCipher().trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Integer toInteger(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return Integer.valueOf(String.valueOf(value));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.bruce.modelprovider.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
/**
|
||||
* 加载独立 AI 配置文件。
|
||||
* <p>
|
||||
* 说明:
|
||||
* 1. 该文件使用 INI 扩展名,但内容采用 key=value 形式,Spring 可直接按 Properties 解析;
|
||||
* 2. ignoreResourceNotFound=true,允许某些环境不提供该文件,避免启动失败;
|
||||
* 3. 具体键值由 {@link AiSecretProperties} 统一绑定与读取。
|
||||
*/
|
||||
@Configuration
|
||||
@PropertySource(value = "classpath:ai-config.ini", ignoreResourceNotFound = true)
|
||||
public class AiConfigFilePropertySourceConfig {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.bruce.modelprovider.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AI 密钥配置绑定。
|
||||
* <p>
|
||||
* 支持从 ai-config.ini 读取如下配置:
|
||||
* ai.secret-refs[SILICONFLOW_API_KEY]=your-key
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "ai")
|
||||
public class AiSecretProperties {
|
||||
|
||||
/**
|
||||
* key 为 secretRef(例如 SILICONFLOW_API_KEY),value 为实际密钥。
|
||||
*/
|
||||
private Map<String, String> secretRefs = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 根据 secretRef 获取配置文件中的密钥,并做空白清理。
|
||||
*/
|
||||
public String getApiKeyBySecretRef(String secretRef) {
|
||||
if (!StringUtils.hasText(secretRef)) {
|
||||
return null;
|
||||
}
|
||||
String value = secretRefs.get(secretRef.trim());
|
||||
if (!StringUtils.hasText(value)) {
|
||||
return null;
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<List<ModelCallLogResponse>> query(@RequestBody(required = false) ModelCallLogQueryRequest request) {
|
||||
return RequestResult.success(modelCallLogService.query(request));
|
||||
}
|
||||
|
||||
@GetMapping("/detail")
|
||||
/**
|
||||
* 方法 detail,用于执行业务逻辑处理。
|
||||
*/
|
||||
public RequestResult<ModelCallLogResponse> detail(@RequestParam("id") Long id) {
|
||||
return RequestResult.success(ModelCallLogResponse.fromEntity(modelCallLogService.getById(id)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<List<ModelConfigResponse>> query() {
|
||||
return RequestResult.success(modelConfigService.listResponses());
|
||||
}
|
||||
|
||||
@GetMapping("/detail")
|
||||
/**
|
||||
* 方法 detail,用于执行业务逻辑处理。
|
||||
*/
|
||||
public RequestResult<ModelConfigResponse> detail(@RequestParam("id") Long id) {
|
||||
return RequestResult.success(modelConfigService.getResponseById(id));
|
||||
}
|
||||
|
||||
@PostMapping("/save")
|
||||
/**
|
||||
* 方法 save,用于执行业务逻辑处理。
|
||||
*/
|
||||
public RequestResult<Boolean> save(@RequestBody ModelConfigSaveRequest request) {
|
||||
return RequestResult.success(modelConfigService.saveOrUpdate(request));
|
||||
}
|
||||
|
||||
@PostMapping("/delete")
|
||||
/**
|
||||
* 方法 delete,用于执行业务逻辑处理。
|
||||
*/
|
||||
public RequestResult<Boolean> delete(@RequestParam("id") Long id) {
|
||||
return RequestResult.success(modelConfigService.removeById(id));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<List<ModelProviderResponse>> query() {
|
||||
return RequestResult.success(modelProviderService.listResponses());
|
||||
}
|
||||
|
||||
@GetMapping("/detail")
|
||||
/**
|
||||
* 方法 detail,用于执行业务逻辑处理。
|
||||
*/
|
||||
public RequestResult<ModelProviderResponse> detail(@RequestParam("id") Long id) {
|
||||
return RequestResult.success(modelProviderService.getResponseById(id));
|
||||
}
|
||||
|
||||
@PostMapping("/save")
|
||||
/**
|
||||
* 方法 save,用于执行业务逻辑处理。
|
||||
*/
|
||||
public RequestResult<Boolean> save(@RequestBody ModelProviderSaveRequest request) {
|
||||
return RequestResult.success(modelProviderService.saveOrUpdate(request));
|
||||
}
|
||||
|
||||
@PostMapping("/delete")
|
||||
/**
|
||||
* 方法 delete,用于执行业务逻辑处理。
|
||||
*/
|
||||
public RequestResult<Boolean> delete(@RequestParam("id") Long id) {
|
||||
return RequestResult.success(modelProviderService.removeById(id));
|
||||
}
|
||||
|
||||
@PostMapping("/checkHealth")
|
||||
/**
|
||||
* 方法 checkHealth,用于执行业务逻辑处理。
|
||||
*/
|
||||
public RequestResult<Boolean> checkHealth(@RequestParam("id") Long id) {
|
||||
return RequestResult.success(modelProviderService.checkHealth(id));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<List<ModelRouteRuleResponse>> query() {
|
||||
return RequestResult.success(modelRouteRuleService.listResponses());
|
||||
}
|
||||
|
||||
@GetMapping("/detail")
|
||||
/**
|
||||
* 方法 detail,用于执行业务逻辑处理。
|
||||
*/
|
||||
public RequestResult<ModelRouteRuleResponse> detail(@RequestParam("id") Long id) {
|
||||
return RequestResult.success(modelRouteRuleService.getResponseById(id));
|
||||
}
|
||||
|
||||
@PostMapping("/save")
|
||||
/**
|
||||
* 方法 save,用于执行业务逻辑处理。
|
||||
*/
|
||||
public RequestResult<Boolean> save(@RequestBody ModelRouteRuleSaveRequest request) {
|
||||
return RequestResult.success(modelRouteRuleService.saveOrUpdate(request));
|
||||
}
|
||||
|
||||
@PostMapping("/delete")
|
||||
/**
|
||||
* 方法 delete,用于执行业务逻辑处理。
|
||||
*/
|
||||
public RequestResult<Boolean> delete(@RequestParam("id") Long id) {
|
||||
return RequestResult.success(modelRouteRuleService.removeById(id));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<RagStoreModelConfigResponse> modelConfig(@RequestParam("storeId") Long storeId) {
|
||||
return RequestResult.success(ragStoreModelConfigService.getByStoreId(storeId));
|
||||
}
|
||||
|
||||
@PostMapping("/modelConfig/save")
|
||||
/**
|
||||
* 方法 save,用于执行业务逻辑处理。
|
||||
*/
|
||||
public RequestResult<Boolean> save(@RequestBody RagStoreModelConfigSaveRequest request) {
|
||||
return RequestResult.success(ragStoreModelConfigService.saveOrUpdate(request));
|
||||
}
|
||||
|
||||
@PostMapping("/rebuildIndex")
|
||||
/**
|
||||
* 方法 rebuildIndex,用于执行业务逻辑处理。
|
||||
*/
|
||||
public RequestResult<Boolean> rebuildIndex(@RequestParam("storeId") Long storeId) {
|
||||
return RequestResult.success(storeId != null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 模型调用日志实体。
|
||||
* <p>
|
||||
* 用于记录每次模型调用的请求上下文、耗时、状态与错误摘要,支持排障与成本分析。
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.common.typehandler.PgJsonbStringTypeHandler;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 模型配置实体。
|
||||
* <p>
|
||||
* 每条记录描述某个服务商下的一个具体模型,包含模型类型、能力、价格和默认参数。
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 模型服务商配置实体。
|
||||
* <p>
|
||||
* 说明:
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.common.typehandler.PgJsonbStringTypeHandler;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 模型路由规则实体。
|
||||
* <p>
|
||||
* 用于定义任务在不同范围(全局/知识库/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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 知识库模型绑定实体。
|
||||
* <p>
|
||||
* 用于固定知识库当前生效的 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.bruce.modelprovider.gateway;
|
||||
|
||||
public interface ChatModelGateway {
|
||||
ChatResult chat(ChatRequest request);
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.bruce.modelprovider.gateway;
|
||||
|
||||
import com.bruce.modelprovider.client.OpenAiChatCompletionResult;
|
||||
import com.bruce.modelprovider.client.OpenAiChatMessage;
|
||||
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 org.springframework.util.StringUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ChatModelGatewayImpl implements ChatModelGateway {
|
||||
|
||||
private final IModelRouteService modelRouteService;
|
||||
private final IModelProviderService modelProviderService;
|
||||
private final IModelCallLogService modelCallLogService;
|
||||
private final OpenAiCompatibleModelClient openAiCompatibleModelClient;
|
||||
|
||||
@Override
|
||||
public ChatResult chat(ChatRequest request) {
|
||||
if (request == null || request.getMessages() == null || request.getMessages().isEmpty()) {
|
||||
throw new IllegalArgumentException("聊天请求不能为空");
|
||||
}
|
||||
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("CHAT");
|
||||
callLog.setRequestHash(buildRequestHash(request.getMessages()));
|
||||
try {
|
||||
ModelRouteContext routeContext = new ModelRouteContext();
|
||||
routeContext.setTaskType(request.getTaskType());
|
||||
routeContext.setMatchScope(request.getMatchScope());
|
||||
routeContext.setScopeId(request.getScopeId());
|
||||
routeContext.setRequiredModelType("CHAT");
|
||||
routeContext.setBizType(request.getBizType());
|
||||
routeContext.setBizId(request.getBizId());
|
||||
ModelRouteDecision decision = modelRouteService.route(routeContext);
|
||||
|
||||
ModelCallExecution execution = executeWithFallback(
|
||||
decision.getPrimaryModel(),
|
||||
decision.getFallbackModels(),
|
||||
request.getMessages()
|
||||
);
|
||||
|
||||
callLog.setProviderId(execution.provider().getId());
|
||||
callLog.setModelId(execution.model().getId());
|
||||
callLog.setStatus(ModelCallStatusEnum.SUCCESS.name());
|
||||
callLog.setPromptTokens(execution.result().getPromptTokens());
|
||||
callLog.setCompletionTokens(execution.result().getCompletionTokens());
|
||||
callLog.setTotalTokens(execution.result().getTotalTokens());
|
||||
callLog.setDurationMs((int) (System.currentTimeMillis() - start));
|
||||
modelCallLogService.save(callLog);
|
||||
|
||||
ChatResult result = new ChatResult();
|
||||
result.setModelId(execution.model().getId());
|
||||
result.setModelName(execution.model().getModelName());
|
||||
result.setContent(execution.result().getContent());
|
||||
result.setUpstreamRequestId(execution.result().getUpstreamRequestId());
|
||||
result.setPromptTokens(execution.result().getPromptTokens());
|
||||
result.setCompletionTokens(execution.result().getCompletionTokens());
|
||||
result.setTotalTokens(execution.result().getTotalTokens());
|
||||
result.setCallLog(callLog);
|
||||
return result;
|
||||
} catch (Exception ex) {
|
||||
callLog.setStatus(ModelCallStatusEnum.FAILED.name());
|
||||
callLog.setDurationMs((int) (System.currentTimeMillis() - start));
|
||||
callLog.setErrorCode("CHAT_COMPLETION_FAILED");
|
||||
String message = ex.getMessage();
|
||||
callLog.setErrorMessage(message == null ? "unknown" : message.substring(0, Math.min(message.length(), 1000)));
|
||||
modelCallLogService.save(callLog);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private ModelCallExecution executeWithFallback(ModelConfig primaryModel,
|
||||
List<ModelConfig> fallbackModels,
|
||||
List<OpenAiChatMessage> messages) {
|
||||
ModelProvider primaryProvider = requireAvailableProvider(primaryModel.getProviderId());
|
||||
try {
|
||||
OpenAiChatCompletionResult result = openAiCompatibleModelClient.chatCompletions(primaryProvider, primaryModel, messages);
|
||||
return new ModelCallExecution(primaryProvider, primaryModel, result);
|
||||
} catch (Exception primaryEx) {
|
||||
for (ModelConfig fallbackModel : fallbackModels) {
|
||||
try {
|
||||
ModelProvider fallbackProvider = requireAvailableProvider(fallbackModel.getProviderId());
|
||||
OpenAiChatCompletionResult result = openAiCompatibleModelClient.chatCompletions(
|
||||
fallbackProvider,
|
||||
fallbackModel,
|
||||
messages
|
||||
);
|
||||
return new ModelCallExecution(fallbackProvider, fallbackModel, result);
|
||||
} catch (Exception ignored) {
|
||||
// continue fallback chain
|
||||
}
|
||||
}
|
||||
throw primaryEx;
|
||||
}
|
||||
}
|
||||
|
||||
private ModelProvider requireAvailableProvider(Long providerId) {
|
||||
ModelProvider provider = modelProviderService.getById(providerId);
|
||||
if (provider == null || !Boolean.TRUE.equals(provider.getEnabled())) {
|
||||
throw new IllegalStateException("模型服务商不可用");
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
private String buildRequestHash(List<OpenAiChatMessage> messages) {
|
||||
String plainText = messages.stream()
|
||||
.map(message -> (StringUtils.hasText(message.getRole()) ? message.getRole() : "user") + ":" + message.getContent())
|
||||
.reduce((left, right) -> left + "|" + right)
|
||||
.orElse("");
|
||||
return DigestUtils.md5DigestAsHex(plainText.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private record ModelCallExecution(ModelProvider provider, ModelConfig model, OpenAiChatCompletionResult result) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.bruce.modelprovider.gateway;
|
||||
|
||||
import com.bruce.modelprovider.client.OpenAiChatMessage;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ChatRequest {
|
||||
private List<OpenAiChatMessage> messages;
|
||||
private String taskType;
|
||||
private String matchScope;
|
||||
private Long scopeId;
|
||||
private String bizType;
|
||||
private String bizId;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.bruce.modelprovider.gateway;
|
||||
|
||||
import com.bruce.modelprovider.entity.ModelCallLog;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ChatResult {
|
||||
private Long modelId;
|
||||
private String modelName;
|
||||
private String content;
|
||||
private String upstreamRequestId;
|
||||
private Integer promptTokens;
|
||||
private Integer completionTokens;
|
||||
private Integer totalTokens;
|
||||
private ModelCallLog callLog;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.bruce.modelprovider.gateway;
|
||||
|
||||
/**
|
||||
* 该类属于模型平台模块,用于承载对应分层职责。
|
||||
*/
|
||||
public interface EmbeddingModelGateway {
|
||||
/**
|
||||
* 方法 embed,用于定义接口能力契约。
|
||||
*/
|
||||
EmbeddingResult embed(EmbeddingRequest request);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 网关实现。
|
||||
* <p>
|
||||
* 主要职责:
|
||||
* 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<List<Double>> 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<List<Double>> executeWithFallback(ModelProvider primaryProvider,
|
||||
ModelConfig primaryModel,
|
||||
List<ModelConfig> fallbackModels,
|
||||
List<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.bruce.modelprovider.gateway;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
/**
|
||||
* 该类属于模型平台模块,用于承载对应分层职责。
|
||||
*/
|
||||
public class EmbeddingRequest {
|
||||
private List<String> texts;
|
||||
private String taskType;
|
||||
private String matchScope;
|
||||
private Long scopeId;
|
||||
private String bizType;
|
||||
private String bizId;
|
||||
private Integer expectedDimension;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<List<Double>> vectors;
|
||||
private ModelCallLog callLog;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<ModelCallLog> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<ModelConfig> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<ModelProvider> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<ModelRouteRule> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<RagStoreModelConfig> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<ModelConfig> fallbackModels = new ArrayList<>();
|
||||
private String routeStrategy;
|
||||
private String reason;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<ModelCallLog> {
|
||||
/**
|
||||
* 方法 query,用于定义接口能力契约。
|
||||
*/
|
||||
List<ModelCallLogResponse> query(ModelCallLogQueryRequest request);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<ModelConfig> {
|
||||
/**
|
||||
* 方法 listResponses,用于定义接口能力契约。
|
||||
*/
|
||||
List<ModelConfigResponse> listResponses();
|
||||
/**
|
||||
* 方法 getResponseById,用于定义接口能力契约。
|
||||
*/
|
||||
ModelConfigResponse getResponseById(Long id);
|
||||
/**
|
||||
* 方法 saveOrUpdate,用于定义接口能力契约。
|
||||
*/
|
||||
boolean saveOrUpdate(ModelConfigSaveRequest request);
|
||||
/**
|
||||
* 方法 getEnabledModel,用于定义接口能力契约。
|
||||
*/
|
||||
ModelConfig getEnabledModel(Long modelId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<ModelProvider> {
|
||||
/**
|
||||
* 方法 listResponses,用于定义接口能力契约。
|
||||
*/
|
||||
List<ModelProviderResponse> listResponses();
|
||||
/**
|
||||
* 方法 getResponseById,用于定义接口能力契约。
|
||||
*/
|
||||
ModelProviderResponse getResponseById(Long id);
|
||||
/**
|
||||
* 方法 saveOrUpdate,用于定义接口能力契约。
|
||||
*/
|
||||
boolean saveOrUpdate(ModelProviderSaveRequest request);
|
||||
/**
|
||||
* 方法 checkHealth,用于定义接口能力契约。
|
||||
*/
|
||||
boolean checkHealth(Long id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<ModelRouteRule> {
|
||||
/**
|
||||
* 方法 listResponses,用于定义接口能力契约。
|
||||
*/
|
||||
List<ModelRouteRuleResponse> listResponses();
|
||||
/**
|
||||
* 方法 getResponseById,用于定义接口能力契约。
|
||||
*/
|
||||
ModelRouteRuleResponse getResponseById(Long id);
|
||||
/**
|
||||
* 方法 saveOrUpdate,用于定义接口能力契约。
|
||||
*/
|
||||
boolean saveOrUpdate(ModelRouteRuleSaveRequest request);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<RagStoreModelConfig> {
|
||||
/**
|
||||
* 方法 getByStoreId,用于定义接口能力契约。
|
||||
*/
|
||||
RagStoreModelConfigResponse getByStoreId(Long storeId);
|
||||
/**
|
||||
* 方法 saveOrUpdate,用于定义接口能力契约。
|
||||
*/
|
||||
boolean saveOrUpdate(RagStoreModelConfigSaveRequest request);
|
||||
/**
|
||||
* 方法 getActiveEntity,用于定义接口能力契约。
|
||||
*/
|
||||
RagStoreModelConfig getActiveEntity(Long storeId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<ModelCallLogMapper, ModelCallLog>
|
||||
implements IModelCallLogService {
|
||||
|
||||
@Override
|
||||
/**
|
||||
* 方法 query,用于执行业务逻辑处理。
|
||||
*/
|
||||
public List<ModelCallLogResponse> 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<ModelConfigMapper, ModelConfig> implements IModelConfigService {
|
||||
|
||||
@Override
|
||||
/**
|
||||
* 方法 listResponses,用于执行业务逻辑处理。
|
||||
*/
|
||||
public List<ModelConfigResponse> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 模型服务商服务实现。
|
||||
* <p>
|
||||
* 主要职责:
|
||||
* 1. 服务商配置的增删改查;
|
||||
* 2. 服务商编码唯一性校验;
|
||||
* 3. 默认健康状态初始化;
|
||||
* 4. 对接上游健康检查并回写检查结果。
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
/**
|
||||
* ModelProviderServiceImpl,负责模型平台对应层的职责。
|
||||
*/
|
||||
public class ModelProviderServiceImpl extends ServiceImpl<ModelProviderMapper, ModelProvider>
|
||||
implements IModelProviderService {
|
||||
|
||||
private final OpenAiCompatibleModelClient openAiCompatibleModelClient;
|
||||
|
||||
/**
|
||||
* 查询全部服务商并转换为响应对象。
|
||||
*/
|
||||
@Override
|
||||
/**
|
||||
* 方法 listResponses,用于执行业务逻辑处理。
|
||||
*/
|
||||
public List<ModelProviderResponse> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<ModelRouteRuleMapper, ModelRouteRule>
|
||||
implements IModelRouteRuleService {
|
||||
|
||||
@Override
|
||||
/**
|
||||
* 方法 listResponses,用于执行业务逻辑处理。
|
||||
*/
|
||||
public List<ModelRouteRuleResponse> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 模型路由服务实现。
|
||||
* <p>
|
||||
* 主要职责:
|
||||
* 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<ModelConfig> 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数组。
|
||||
* <p>
|
||||
* 支持最简 JSON 数组文本(如 [1,2,3]),解析失败时返回空列表,避免阻断主流程。
|
||||
*/
|
||||
private List<Long> 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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<RagStoreModelConfigMapper, RagStoreModelConfig>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user