From 041ed0b4468cecfeeb6acc2d1b8563b5245ec122 Mon Sep 17 00:00:00 2001 From: bruce Date: Mon, 1 Jun 2026 03:47:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(modelprovider):=20=E8=A1=A5=E9=BD=90?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E5=B7=A5=E4=BD=9C=E5=8F=B0=E8=81=9A=E5=90=88?= =?UTF-8?q?=E4=B8=8E=E8=BD=AC=E6=8D=A2=E5=88=86=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ModelCallLogController.java | 17 +- .../controller/ModelConfigController.java | 23 +-- .../controller/ModelProviderController.java | 27 ++-- .../controller/ModelRouteRuleController.java | 23 +-- .../controller/ModelWorkspaceController.java | 35 ++++ .../RagStoreModelConfigController.java | 19 +-- .../factory/ModelCallLogFactory.java | 28 ++++ .../factory/ModelConfigFactory.java | 57 +++++++ .../factory/ModelProviderFactory.java | 65 ++++++++ .../factory/ModelRouteRuleFactory.java | 57 +++++++ .../service/IModelWorkspaceService.java | 14 ++ .../service/impl/ModelCallLogServiceImpl.java | 23 +-- .../service/impl/ModelConfigServiceImpl.java | 65 ++++---- .../impl/ModelProviderServiceImpl.java | 70 ++++---- .../impl/ModelRouteRuleServiceImpl.java | 61 +++---- .../impl/ModelWorkspaceServiceImpl.java | 77 +++++++++ .../impl/RagStoreModelConfigServiceImpl.java | 34 ++-- .../vo/ModelWorkspaceFailureVO.java | 24 +++ .../modelprovider/vo/ModelWorkspaceVO.java | 54 +++++++ .../factory/ModelProviderFactoryTests.java | 150 ++++++++++++++++++ .../workspace/ModelWorkspaceServiceTests.java | 123 ++++++++++++++ .../src/api/__tests__/modelWorkspace.spec.ts | 20 +++ frontend/src/api/modelWorkspace.ts | 28 ++++ 23 files changed, 898 insertions(+), 196 deletions(-) create mode 100644 common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelWorkspaceController.java create mode 100644 common-agent-modelprovider/src/main/java/com/bruce/modelprovider/factory/ModelCallLogFactory.java create mode 100644 common-agent-modelprovider/src/main/java/com/bruce/modelprovider/factory/ModelConfigFactory.java create mode 100644 common-agent-modelprovider/src/main/java/com/bruce/modelprovider/factory/ModelProviderFactory.java create mode 100644 common-agent-modelprovider/src/main/java/com/bruce/modelprovider/factory/ModelRouteRuleFactory.java create mode 100644 common-agent-modelprovider/src/main/java/com/bruce/modelprovider/service/IModelWorkspaceService.java create mode 100644 common-agent-modelprovider/src/main/java/com/bruce/modelprovider/service/impl/ModelWorkspaceServiceImpl.java create mode 100644 common-agent-modelprovider/src/main/java/com/bruce/modelprovider/vo/ModelWorkspaceFailureVO.java create mode 100644 common-agent-modelprovider/src/main/java/com/bruce/modelprovider/vo/ModelWorkspaceVO.java create mode 100644 common-agent-modelprovider/src/test/java/com/bruce/modelprovider/factory/ModelProviderFactoryTests.java create mode 100644 common-agent-modelprovider/src/test/java/com/bruce/modelprovider/workspace/ModelWorkspaceServiceTests.java create mode 100644 frontend/src/api/__tests__/modelWorkspace.spec.ts create mode 100644 frontend/src/api/modelWorkspace.ts diff --git a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelCallLogController.java b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelCallLogController.java index 39cf496..6871113 100644 --- a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelCallLogController.java +++ b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelCallLogController.java @@ -3,36 +3,33 @@ 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.factory.ModelCallLogFactory; import com.bruce.modelprovider.service.IModelCallLogService; +import lombok.extern.slf4j.Slf4j; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.List; +@Slf4j @RestController @RequestMapping("/api/model/call-logs") @RequiredArgsConstructor -/** - * 该类属于模型平台模块,用于承载对应分层职责。 - */ public class ModelCallLogController { private final IModelCallLogService modelCallLogService; + private final ModelCallLogFactory modelCallLogFactory; @PostMapping("/query") - /** - * 方法 query,用于执行业务逻辑处理。 - */ public RequestResult> query(@RequestBody(required = false) ModelCallLogQueryRequest request) { + log.info("ModelCallLogController.query start, request={}", request); return RequestResult.success(modelCallLogService.query(request)); } @GetMapping("/detail") - /** - * 方法 detail,用于执行业务逻辑处理。 - */ public RequestResult detail(@RequestParam("id") Long id) { - return RequestResult.success(ModelCallLogResponse.fromEntity(modelCallLogService.getById(id))); + log.info("ModelCallLogController.detail start, id={}", id); + return RequestResult.success(modelCallLogFactory.toResponse(modelCallLogService.getById(id))); } } diff --git a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelConfigController.java b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelConfigController.java index 90953b6..4705d52 100644 --- a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelConfigController.java +++ b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelConfigController.java @@ -4,50 +4,43 @@ 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.extern.slf4j.Slf4j; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.List; +@Slf4j @RestController @RequestMapping("/api/model/configs") @RequiredArgsConstructor -/** - * 该类属于模型平台模块,用于承载对应分层职责。 - */ public class ModelConfigController { private final IModelConfigService modelConfigService; @PostMapping("/query") - /** - * 方法 query,用于执行业务逻辑处理。 - */ public RequestResult> query() { + log.info("ModelConfigController.query start"); return RequestResult.success(modelConfigService.listResponses()); } @GetMapping("/detail") - /** - * 方法 detail,用于执行业务逻辑处理。 - */ public RequestResult detail(@RequestParam("id") Long id) { + log.info("ModelConfigController.detail start, id={}", id); return RequestResult.success(modelConfigService.getResponseById(id)); } @PostMapping("/save") - /** - * 方法 save,用于执行业务逻辑处理。 - */ public RequestResult save(@RequestBody ModelConfigSaveRequest request) { + log.info("ModelConfigController.save start, id={}, modelCode={}", + request == null ? null : request.getId(), + request == null ? null : request.getModelCode()); return RequestResult.success(modelConfigService.saveOrUpdate(request)); } @PostMapping("/delete") - /** - * 方法 delete,用于执行业务逻辑处理。 - */ public RequestResult delete(@RequestParam("id") Long id) { + log.info("ModelConfigController.delete start, id={}", id); return RequestResult.success(modelConfigService.removeById(id)); } } diff --git a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelProviderController.java b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelProviderController.java index e1191b0..5042c2e 100644 --- a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelProviderController.java +++ b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelProviderController.java @@ -4,58 +4,49 @@ 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.extern.slf4j.Slf4j; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.List; +@Slf4j @RestController @RequestMapping("/api/model/providers") @RequiredArgsConstructor -/** - * 该类属于模型平台模块,用于承载对应分层职责。 - */ public class ModelProviderController { private final IModelProviderService modelProviderService; @PostMapping("/query") - /** - * 方法 query,用于执行业务逻辑处理。 - */ public RequestResult> query() { + log.info("ModelProviderController.query start"); return RequestResult.success(modelProviderService.listResponses()); } @GetMapping("/detail") - /** - * 方法 detail,用于执行业务逻辑处理。 - */ public RequestResult detail(@RequestParam("id") Long id) { + log.info("ModelProviderController.detail start, id={}", id); return RequestResult.success(modelProviderService.getResponseById(id)); } @PostMapping("/save") - /** - * 方法 save,用于执行业务逻辑处理。 - */ public RequestResult save(@RequestBody ModelProviderSaveRequest request) { + log.info("ModelProviderController.save start, id={}, providerCode={}", + request == null ? null : request.getId(), + request == null ? null : request.getProviderCode()); return RequestResult.success(modelProviderService.saveOrUpdate(request)); } @PostMapping("/delete") - /** - * 方法 delete,用于执行业务逻辑处理。 - */ public RequestResult delete(@RequestParam("id") Long id) { + log.info("ModelProviderController.delete start, id={}", id); return RequestResult.success(modelProviderService.removeById(id)); } @PostMapping("/checkHealth") - /** - * 方法 checkHealth,用于执行业务逻辑处理。 - */ public RequestResult checkHealth(@RequestParam("id") Long id) { + log.info("ModelProviderController.checkHealth start, id={}", id); return RequestResult.success(modelProviderService.checkHealth(id)); } } diff --git a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelRouteRuleController.java b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelRouteRuleController.java index 835c5ac..6558995 100644 --- a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelRouteRuleController.java +++ b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelRouteRuleController.java @@ -4,50 +4,43 @@ 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.extern.slf4j.Slf4j; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.List; +@Slf4j @RestController @RequestMapping("/api/model/routes") @RequiredArgsConstructor -/** - * 该类属于模型平台模块,用于承载对应分层职责。 - */ public class ModelRouteRuleController { private final IModelRouteRuleService modelRouteRuleService; @PostMapping("/query") - /** - * 方法 query,用于执行业务逻辑处理。 - */ public RequestResult> query() { + log.info("ModelRouteRuleController.query start"); return RequestResult.success(modelRouteRuleService.listResponses()); } @GetMapping("/detail") - /** - * 方法 detail,用于执行业务逻辑处理。 - */ public RequestResult detail(@RequestParam("id") Long id) { + log.info("ModelRouteRuleController.detail start, id={}", id); return RequestResult.success(modelRouteRuleService.getResponseById(id)); } @PostMapping("/save") - /** - * 方法 save,用于执行业务逻辑处理。 - */ public RequestResult save(@RequestBody ModelRouteRuleSaveRequest request) { + log.info("ModelRouteRuleController.save start, id={}, routeCode={}", + request == null ? null : request.getId(), + request == null ? null : request.getRouteCode()); return RequestResult.success(modelRouteRuleService.saveOrUpdate(request)); } @PostMapping("/delete") - /** - * 方法 delete,用于执行业务逻辑处理。 - */ public RequestResult delete(@RequestParam("id") Long id) { + log.info("ModelRouteRuleController.delete start, id={}", id); return RequestResult.success(modelRouteRuleService.removeById(id)); } } diff --git a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelWorkspaceController.java b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelWorkspaceController.java new file mode 100644 index 0000000..b818dd6 --- /dev/null +++ b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/ModelWorkspaceController.java @@ -0,0 +1,35 @@ +package com.bruce.modelprovider.controller; + +import com.bruce.common.domain.model.RequestResult; +import com.bruce.modelprovider.service.IModelWorkspaceService; +import com.bruce.modelprovider.vo.ModelWorkspaceVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 模型工作台聚合接口。 + */ +@Tag(name = "模型工作台") +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/model/workspace") +public class ModelWorkspaceController { + + private final IModelWorkspaceService modelWorkspaceService; + + @Operation(summary = "查询模型与路由工作台聚合视图") + @GetMapping + public RequestResult getWorkspace() { + log.info("ModelWorkspaceController.getWorkspace start"); + ModelWorkspaceVO workspace = modelWorkspaceService.getWorkspace(); + log.info("ModelWorkspaceController.getWorkspace success, providerCount={}, modelCount={}, routeRuleCount={}", + workspace.getProviderCount(), workspace.getModelCount(), workspace.getRouteRuleCount()); + return RequestResult.success(workspace); + } +} diff --git a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/RagStoreModelConfigController.java b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/RagStoreModelConfigController.java index f87bc85..e0024b1 100644 --- a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/RagStoreModelConfigController.java +++ b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/controller/RagStoreModelConfigController.java @@ -4,40 +4,35 @@ 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.extern.slf4j.Slf4j; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; +@Slf4j @RestController @RequestMapping("/api/rag/store") @RequiredArgsConstructor -/** - * 该类属于模型平台模块,用于承载对应分层职责。 - */ public class RagStoreModelConfigController { private final IRagStoreModelConfigService ragStoreModelConfigService; @GetMapping("/modelConfig") - /** - * 方法 modelConfig,用于执行业务逻辑处理。 - */ public RequestResult modelConfig(@RequestParam("storeId") Long storeId) { + log.info("RagStoreModelConfigController.modelConfig start, storeId={}", storeId); return RequestResult.success(ragStoreModelConfigService.getByStoreId(storeId)); } @PostMapping("/modelConfig/save") - /** - * 方法 save,用于执行业务逻辑处理。 - */ public RequestResult save(@RequestBody RagStoreModelConfigSaveRequest request) { + log.info("RagStoreModelConfigController.save start, storeId={}, embeddingModelId={}", + request == null ? null : request.getStoreId(), + request == null ? null : request.getEmbeddingModelId()); return RequestResult.success(ragStoreModelConfigService.saveOrUpdate(request)); } @PostMapping("/rebuildIndex") - /** - * 方法 rebuildIndex,用于执行业务逻辑处理。 - */ public RequestResult rebuildIndex(@RequestParam("storeId") Long storeId) { + log.info("RagStoreModelConfigController.rebuildIndex start, storeId={}", storeId); return RequestResult.success(storeId != null); } } diff --git a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/factory/ModelCallLogFactory.java b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/factory/ModelCallLogFactory.java new file mode 100644 index 0000000..e43fa53 --- /dev/null +++ b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/factory/ModelCallLogFactory.java @@ -0,0 +1,28 @@ +package com.bruce.modelprovider.factory; + +import com.bruce.modelprovider.dto.response.ModelCallLogResponse; +import com.bruce.modelprovider.entity.ModelCallLog; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 模型调用日志工厂。 + */ +@Component +public class ModelCallLogFactory { + + public ModelCallLogResponse toResponse(ModelCallLog entity) { + if (entity == null) { + return null; + } + ModelCallLogResponse response = new ModelCallLogResponse(); + BeanUtils.copyProperties(entity, response); + return response; + } + + public List toResponses(List entities) { + return entities == null ? List.of() : entities.stream().map(this::toResponse).toList(); + } +} diff --git a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/factory/ModelConfigFactory.java b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/factory/ModelConfigFactory.java new file mode 100644 index 0000000..9109de5 --- /dev/null +++ b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/factory/ModelConfigFactory.java @@ -0,0 +1,57 @@ +package com.bruce.modelprovider.factory; + +import com.bruce.modelprovider.dto.request.ModelConfigSaveRequest; +import com.bruce.modelprovider.dto.response.ModelConfigResponse; +import com.bruce.modelprovider.entity.ModelConfig; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.List; + +/** + * 模型配置工厂,统一转换模型配置请求、实体和返回对象。 + */ +@Component +public class ModelConfigFactory { + + public ModelConfig toEntity(ModelConfigSaveRequest request) { + if (request == null) { + return null; + } + ModelConfig entity = new ModelConfig(); + entity.setId(request.getId()); + entity.setProviderId(request.getProviderId()); + entity.setModelCode(trimToNull(request.getModelCode())); + entity.setModelName(trimToNull(request.getModelName())); + entity.setUpstreamModel(trimToNull(request.getUpstreamModel())); + entity.setModelType(trimToNull(request.getModelType())); + entity.setEmbeddingDimension(request.getEmbeddingDimension()); + entity.setLocalModel(request.getLocalModel()); + entity.setDefaultModel(request.getDefaultModel()); + entity.setOptionsJson(trimToNull(request.getOptionsJson())); + entity.setEnabled(request.getEnabled()); + entity.setRemark(trimToNull(request.getRemark())); + return entity; + } + + public ModelConfigResponse toResponse(ModelConfig entity) { + if (entity == null) { + return null; + } + ModelConfigResponse response = new ModelConfigResponse(); + BeanUtils.copyProperties(entity, response); + return response; + } + + public List toResponses(List entities) { + return entities == null ? List.of() : entities.stream().map(this::toResponse).toList(); + } + + private String trimToNull(String value) { + if (!StringUtils.hasText(value)) { + return null; + } + return value.trim(); + } +} diff --git a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/factory/ModelProviderFactory.java b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/factory/ModelProviderFactory.java new file mode 100644 index 0000000..468a37e --- /dev/null +++ b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/factory/ModelProviderFactory.java @@ -0,0 +1,65 @@ +package com.bruce.modelprovider.factory; + +import com.bruce.modelprovider.dto.request.ModelProviderSaveRequest; +import com.bruce.modelprovider.dto.response.ModelProviderResponse; +import com.bruce.modelprovider.entity.ModelProvider; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.List; + +/** + * 模型服务商工厂,统一负责服务商 DTO、Entity、VO 的转换。 + */ +@Component +public class ModelProviderFactory { + + /** + * 将保存请求转换为实体,统一处理字符串裁剪逻辑。 + */ + public ModelProvider toEntity(ModelProviderSaveRequest request) { + if (request == null) { + return null; + } + ModelProvider entity = new ModelProvider(); + entity.setId(request.getId()); + entity.setProviderCode(trimToNull(request.getProviderCode())); + entity.setProviderName(trimToNull(request.getProviderName())); + entity.setProviderType(trimToNull(request.getProviderType())); + entity.setProtocolType(trimToNull(request.getProtocolType())); + entity.setBaseUrl(trimToNull(request.getBaseUrl())); + entity.setAuthType(trimToNull(request.getAuthType())); + entity.setSecretRef(trimToNull(request.getSecretRef())); + entity.setApiKeyCipher(trimToNull(request.getApiKeyCipher())); + entity.setTimeoutMs(request.getTimeoutMs()); + entity.setPriority(request.getPriority()); + entity.setEnabled(request.getEnabled()); + entity.setRemark(trimToNull(request.getRemark())); + return entity; + } + + /** + * 将实体转换为前端返回对象,并隐藏密文字段,只返回是否已配置密钥。 + */ + public ModelProviderResponse toResponse(ModelProvider entity) { + if (entity == null) { + return null; + } + ModelProviderResponse response = new ModelProviderResponse(); + BeanUtils.copyProperties(entity, response); + response.setHasApiKey(StringUtils.hasText(entity.getApiKeyCipher())); + return response; + } + + public List toResponses(List entities) { + return entities == null ? List.of() : entities.stream().map(this::toResponse).toList(); + } + + private String trimToNull(String value) { + if (!StringUtils.hasText(value)) { + return null; + } + return value.trim(); + } +} diff --git a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/factory/ModelRouteRuleFactory.java b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/factory/ModelRouteRuleFactory.java new file mode 100644 index 0000000..4e711e6 --- /dev/null +++ b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/factory/ModelRouteRuleFactory.java @@ -0,0 +1,57 @@ +package com.bruce.modelprovider.factory; + +import com.bruce.modelprovider.dto.request.ModelRouteRuleSaveRequest; +import com.bruce.modelprovider.dto.response.ModelRouteRuleResponse; +import com.bruce.modelprovider.entity.ModelRouteRule; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.List; + +/** + * 模型路由规则工厂。 + */ +@Component +public class ModelRouteRuleFactory { + + public ModelRouteRule toEntity(ModelRouteRuleSaveRequest request) { + if (request == null) { + return null; + } + ModelRouteRule entity = new ModelRouteRule(); + entity.setId(request.getId()); + entity.setRouteCode(trimToNull(request.getRouteCode())); + entity.setRouteName(trimToNull(request.getRouteName())); + entity.setTaskType(trimToNull(request.getTaskType())); + entity.setMatchScope(trimToNull(request.getMatchScope())); + entity.setScopeId(request.getScopeId()); + entity.setPrimaryModelId(request.getPrimaryModelId()); + entity.setFallbackModelIdsJson(trimToNull(request.getFallbackModelIdsJson())); + entity.setRouteStrategy(trimToNull(request.getRouteStrategy())); + entity.setMaxLatencyMs(request.getMaxLatencyMs()); + entity.setEnabled(request.getEnabled()); + entity.setRemark(trimToNull(request.getRemark())); + return entity; + } + + public ModelRouteRuleResponse toResponse(ModelRouteRule entity) { + if (entity == null) { + return null; + } + ModelRouteRuleResponse response = new ModelRouteRuleResponse(); + BeanUtils.copyProperties(entity, response); + return response; + } + + public List toResponses(List entities) { + return entities == null ? List.of() : entities.stream().map(this::toResponse).toList(); + } + + private String trimToNull(String value) { + if (!StringUtils.hasText(value)) { + return null; + } + return value.trim(); + } +} diff --git a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/service/IModelWorkspaceService.java b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/service/IModelWorkspaceService.java new file mode 100644 index 0000000..c2032cf --- /dev/null +++ b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/service/IModelWorkspaceService.java @@ -0,0 +1,14 @@ +package com.bruce.modelprovider.service; + +import com.bruce.modelprovider.vo.ModelWorkspaceVO; + +/** + * 模型工作台聚合服务。 + */ +public interface IModelWorkspaceService { + + /** + * 聚合模型服务商、模型、路由和最近失败调用摘要。 + */ + ModelWorkspaceVO getWorkspace(); +} diff --git a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/service/impl/ModelCallLogServiceImpl.java b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/service/impl/ModelCallLogServiceImpl.java index 0b9fd11..ca7c157 100644 --- a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/service/impl/ModelCallLogServiceImpl.java +++ b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/service/impl/ModelCallLogServiceImpl.java @@ -4,37 +4,38 @@ 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.factory.ModelCallLogFactory; import com.bruce.modelprovider.mapper.ModelCallLogMapper; import com.bruce.modelprovider.service.IModelCallLogService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.util.List; +@Slf4j @Service -/** - * 该类属于模型平台模块,用于承载对应分层职责。 - */ +@RequiredArgsConstructor public class ModelCallLogServiceImpl extends ServiceImpl implements IModelCallLogService { + private final ModelCallLogFactory modelCallLogFactory; + @Override - /** - * 方法 query,用于执行业务逻辑处理。 - */ public List query(ModelCallLogQueryRequest request) { + log.info("查询模型调用日志开始,request={}", request); ModelCallLogQueryRequest q = request == null ? new ModelCallLogQueryRequest() : request; - return lambdaQuery() + List responses = modelCallLogFactory.toResponses(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(); + .list()); + log.info("查询模型调用日志结束,count={}", responses.size()); + return responses; } } diff --git a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/service/impl/ModelConfigServiceImpl.java b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/service/impl/ModelConfigServiceImpl.java index 06bac35..590c583 100644 --- a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/service/impl/ModelConfigServiceImpl.java +++ b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/service/impl/ModelConfigServiceImpl.java @@ -4,43 +4,45 @@ 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.factory.ModelConfigFactory; import com.bruce.modelprovider.mapper.ModelConfigMapper; import com.bruce.modelprovider.service.IModelConfigService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.util.List; +@Slf4j @Service -/** - * 该类属于模型平台模块,用于承载对应分层职责。 - */ +@RequiredArgsConstructor public class ModelConfigServiceImpl extends ServiceImpl implements IModelConfigService { + private final ModelConfigFactory modelConfigFactory; + @Override - /** - * 方法 listResponses,用于执行业务逻辑处理。 - */ public List listResponses() { - /** - * 方法 list,用于定义接口能力契约。 - */ - return list().stream().map(ModelConfigResponse::fromEntity).toList(); + log.info("查询模型配置列表开始"); + List responses = modelConfigFactory.toResponses(list()); + log.info("查询模型配置列表结束,count={}", responses.size()); + return responses; } @Override - /** - * 方法 getResponseById,用于执行业务逻辑处理。 - */ public ModelConfigResponse getResponseById(Long id) { - return ModelConfigResponse.fromEntity(getById(id)); + log.info("查询模型配置详情开始,id={}", id); + ModelConfigResponse response = modelConfigFactory.toResponse(getById(id)); + log.info("查询模型配置详情结束,id={}, found={}", id, response != null); + return response; } @Override - /** - * 方法 saveOrUpdate,用于执行业务逻辑处理。 - */ public boolean saveOrUpdate(ModelConfigSaveRequest request) { + log.info("保存模型配置开始,id={}, providerId={}, modelCode={}", + request == null ? null : request.getId(), + request == null ? null : request.getProviderId(), + request == null ? null : request.getModelCode()); if (request == null) { throw new IllegalArgumentException("模型保存请求不能为空"); } @@ -62,24 +64,25 @@ public class ModelConfigServiceImpl extends ServiceImpl implements IModelProviderService { private final OpenAiCompatibleModelClient openAiCompatibleModelClient; + private final ModelProviderFactory modelProviderFactory; /** * 查询全部服务商并转换为响应对象。 */ @Override - /** - * 方法 listResponses,用于执行业务逻辑处理。 - */ public List listResponses() { - /** - * 方法 list,用于定义接口能力契约。 - */ - return list().stream().map(ModelProviderResponse::fromEntity).toList(); + log.info("查询模型服务商列表开始"); + List responses = modelProviderFactory.toResponses(list()); + log.info("查询模型服务商列表结束,count={}", responses.size()); + return responses; } /** * 按ID查询服务商详情(响应对象已脱敏,不返回明文密钥)。 */ @Override - /** - * 方法 getResponseById,用于执行业务逻辑处理。 - */ public ModelProviderResponse getResponseById(Long id) { - return ModelProviderResponse.fromEntity(getById(id)); + log.info("查询模型服务商详情开始,id={}", id); + ModelProviderResponse response = modelProviderFactory.toResponse(getById(id)); + log.info("查询模型服务商详情结束,id={}, found={}", id, response != null); + return response; } /** @@ -64,10 +62,10 @@ public class ModelProviderServiceImpl extends ServiceImpl implements IModelRouteRuleService { + private final ModelRouteRuleFactory modelRouteRuleFactory; + @Override - /** - * 方法 listResponses,用于执行业务逻辑处理。 - */ public List listResponses() { - /** - * 方法 list,用于定义接口能力契约。 - */ - return list().stream().map(ModelRouteRuleResponse::fromEntity).toList(); + log.info("查询模型路由规则列表开始"); + List responses = modelRouteRuleFactory.toResponses(list()); + log.info("查询模型路由规则列表结束,count={}", responses.size()); + return responses; } @Override - /** - * 方法 getResponseById,用于执行业务逻辑处理。 - */ public ModelRouteRuleResponse getResponseById(Long id) { - return ModelRouteRuleResponse.fromEntity(getById(id)); + log.info("查询模型路由规则详情开始,id={}", id); + ModelRouteRuleResponse response = modelRouteRuleFactory.toResponse(getById(id)); + log.info("查询模型路由规则详情结束,id={}, found={}", id, response != null); + return response; } @Override - /** - * 方法 saveOrUpdate,用于执行业务逻辑处理。 - */ public boolean saveOrUpdate(ModelRouteRuleSaveRequest request) { + log.info("保存模型路由规则开始,id={}, routeCode={}, taskType={}", + request == null ? null : request.getId(), + request == null ? null : request.getRouteCode(), + request == null ? null : request.getTaskType()); if (request == null || !StringUtils.hasText(request.getRouteCode())) { throw new IllegalArgumentException("路由编码不能为空"); } @@ -56,18 +58,21 @@ public class ModelRouteRuleServiceImpl extends ServiceImpl providers = modelProviderService.listResponses(); + List models = modelConfigService.listResponses(); + List routes = modelRouteRuleService.listResponses(); + List logs = modelCallLogService.query(new ModelCallLogQueryRequest()); + + List failedLogs = logs.stream() + .filter(log -> FAILED.equals(log.getStatus())) + .limit(5) + .toList(); + + ModelWorkspaceVO workspace = new ModelWorkspaceVO(); + workspace.setProviderCount(providers.size()); + workspace.setHealthyProviderCount((int) providers.stream().filter(provider -> HEALTHY.equals(provider.getHealthStatus())).count()); + workspace.setUnhealthyProviderCount((int) providers.stream().filter(provider -> UNHEALTHY.equals(provider.getHealthStatus())).count()); + workspace.setModelCount(models.size()); + workspace.setEnabledModelCount((int) models.stream().filter(model -> Boolean.TRUE.equals(model.getEnabled())).count()); + workspace.setRouteRuleCount(routes.size()); + workspace.setEnabledRouteRuleCount((int) routes.stream().filter(route -> Boolean.TRUE.equals(route.getEnabled())).count()); + workspace.setRecentFailedCallCount(failedLogs.size()); + workspace.setProviders(providers); + workspace.setModels(models); + workspace.setRoutes(routes); + workspace.setFailedCallSummaries(failedLogs.stream().map(this::toFailureVO).toList()); + log.info("模型工作台查询结束,providerCount={}, modelCount={}, routeRuleCount={}, failedCallCount={}", + workspace.getProviderCount(), workspace.getModelCount(), workspace.getRouteRuleCount(), workspace.getRecentFailedCallCount()); + return workspace; + } + + private ModelWorkspaceFailureVO toFailureVO(ModelCallLogResponse log) { + ModelWorkspaceFailureVO vo = new ModelWorkspaceFailureVO(); + vo.setRequestId(log.getRequestId()); + vo.setStatus(log.getStatus()); + vo.setErrorCode(log.getErrorCode()); + vo.setErrorMessage(log.getErrorMessage()); + return vo; + } +} diff --git a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/service/impl/RagStoreModelConfigServiceImpl.java b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/service/impl/RagStoreModelConfigServiceImpl.java index 6694db6..289ba6f 100644 --- a/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/service/impl/RagStoreModelConfigServiceImpl.java +++ b/common-agent-modelprovider/src/main/java/com/bruce/modelprovider/service/impl/RagStoreModelConfigServiceImpl.java @@ -9,31 +9,28 @@ import com.bruce.modelprovider.mapper.RagStoreModelConfigMapper; import com.bruce.modelprovider.service.IModelConfigService; import com.bruce.modelprovider.service.IRagStoreModelConfigService; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +@Slf4j @Service @RequiredArgsConstructor -/** - * 该类属于模型平台模块,用于承载对应分层职责。 - */ public class RagStoreModelConfigServiceImpl extends ServiceImpl implements IRagStoreModelConfigService { private final IModelConfigService modelConfigService; @Override - /** - * 方法 getByStoreId,用于执行业务逻辑处理。 - */ public RagStoreModelConfigResponse getByStoreId(Long storeId) { + log.info("查询知识库模型配置开始,storeId={}", storeId); return RagStoreModelConfigResponse.fromEntity(getActiveEntity(storeId)); } @Override - /** - * 方法 saveOrUpdate,用于执行业务逻辑处理。 - */ public boolean saveOrUpdate(RagStoreModelConfigSaveRequest request) { + log.info("保存知识库模型配置开始,storeId={}, embeddingModelId={}", + request == null ? null : request.getStoreId(), + request == null ? null : request.getEmbeddingModelId()); if (request == null || request.getStoreId() == null) { throw new IllegalArgumentException("知识库模型配置请求不能为空"); } @@ -57,26 +54,23 @@ public class RagStoreModelConfigServiceImpl extends ServiceImpl providers = new ArrayList<>(); + + @Schema(description = "模型列表") + private List models = new ArrayList<>(); + + @Schema(description = "路由规则列表") + private List routes = new ArrayList<>(); + + @Schema(description = "最近失败调用摘要") + private List failedCallSummaries = new ArrayList<>(); +} diff --git a/common-agent-modelprovider/src/test/java/com/bruce/modelprovider/factory/ModelProviderFactoryTests.java b/common-agent-modelprovider/src/test/java/com/bruce/modelprovider/factory/ModelProviderFactoryTests.java new file mode 100644 index 0000000..28a345a --- /dev/null +++ b/common-agent-modelprovider/src/test/java/com/bruce/modelprovider/factory/ModelProviderFactoryTests.java @@ -0,0 +1,150 @@ +package com.bruce.modelprovider.factory; + +import com.bruce.modelprovider.dto.request.ModelConfigSaveRequest; +import com.bruce.modelprovider.dto.request.ModelProviderSaveRequest; +import com.bruce.modelprovider.dto.request.ModelRouteRuleSaveRequest; +import com.bruce.modelprovider.dto.response.ModelCallLogResponse; +import com.bruce.modelprovider.dto.response.ModelConfigResponse; +import com.bruce.modelprovider.dto.response.ModelProviderResponse; +import com.bruce.modelprovider.dto.response.ModelRouteRuleResponse; +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 org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ModelProviderFactoryTests { + + private final ModelProviderFactory modelProviderFactory = new ModelProviderFactory(); + private final ModelConfigFactory modelConfigFactory = new ModelConfigFactory(); + private final ModelRouteRuleFactory modelRouteRuleFactory = new ModelRouteRuleFactory(); + private final ModelCallLogFactory modelCallLogFactory = new ModelCallLogFactory(); + + @Test + void modelProviderFactoryShouldTrimRequestAndHideCipherInResponse() { + ModelProviderSaveRequest request = new ModelProviderSaveRequest(); + request.setId(1L); + request.setProviderCode(" OPENAI_MAIN "); + request.setProviderName(" OpenAI 主服务 "); + request.setProviderType(" OPENAI "); + request.setProtocolType(" OPENAI_COMPATIBLE "); + request.setBaseUrl(" https://api.openai.com/v1 "); + request.setAuthType(" BEARER_TOKEN "); + request.setSecretRef(" OPENAI_KEY "); + request.setTimeoutMs(60000); + request.setPriority(100); + request.setEnabled(true); + request.setRemark(" 主路由服务商 "); + + ModelProvider entity = modelProviderFactory.toEntity(request); + + assertNotNull(entity); + assertEquals("OPENAI_MAIN", entity.getProviderCode()); + assertEquals("OpenAI 主服务", entity.getProviderName()); + assertEquals("OPENAI", entity.getProviderType()); + assertEquals("OPENAI_COMPATIBLE", entity.getProtocolType()); + assertEquals("https://api.openai.com/v1", entity.getBaseUrl()); + assertEquals("BEARER_TOKEN", entity.getAuthType()); + assertEquals("OPENAI_KEY", entity.getSecretRef()); + assertEquals("主路由服务商", entity.getRemark()); + + ModelProviderResponse response = modelProviderFactory.toResponse(entity); + assertFalse(Boolean.TRUE.equals(response.getHasApiKey())); + + entity.setApiKeyCipher("cipher"); + response = modelProviderFactory.toResponse(entity); + assertTrue(Boolean.TRUE.equals(response.getHasApiKey())); + } + + @Test + void modelConfigFactoryShouldMapFullConfigFields() { + ModelConfigSaveRequest request = new ModelConfigSaveRequest(); + request.setId(2L); + request.setProviderId(1L); + request.setModelCode(" EMB_1024 "); + request.setModelName(" text-embedding-3-large "); + request.setUpstreamModel(" text-embedding-3-large "); + request.setModelType(" EMBEDDING "); + request.setEmbeddingDimension(1024); + request.setLocalModel(false); + request.setDefaultModel(true); + request.setOptionsJson(" {\"dimensions\":1024} "); + request.setEnabled(true); + request.setRemark(" 默认向量模型 "); + + ModelConfig entity = modelConfigFactory.toEntity(request); + assertEquals("EMB_1024", entity.getModelCode()); + assertEquals("text-embedding-3-large", entity.getModelName()); + assertEquals("text-embedding-3-large", entity.getUpstreamModel()); + assertEquals("EMBEDDING", entity.getModelType()); + assertEquals("{\"dimensions\":1024}", entity.getOptionsJson()); + assertEquals("默认向量模型", entity.getRemark()); + + ModelConfigResponse response = modelConfigFactory.toResponse(entity); + assertEquals(1024, response.getEmbeddingDimension()); + assertTrue(Boolean.TRUE.equals(response.getDefaultModel())); + } + + @Test + void modelRouteRuleFactoryShouldMapRouteFields() { + ModelRouteRuleSaveRequest request = new ModelRouteRuleSaveRequest(); + request.setId(3L); + request.setRouteCode(" RAG_GLOBAL "); + request.setRouteName(" 全局检索路由 "); + request.setTaskType(" RAG_RETRIEVAL "); + request.setMatchScope(" GLOBAL "); + request.setScopeId(1001L); + request.setPrimaryModelId(2L); + request.setFallbackModelIdsJson(" [3,4] "); + request.setRouteStrategy(" MANUAL "); + request.setMaxLatencyMs(3000); + request.setEnabled(true); + request.setRemark(" 全局默认规则 "); + + ModelRouteRule entity = modelRouteRuleFactory.toEntity(request); + assertEquals("RAG_GLOBAL", entity.getRouteCode()); + assertEquals("全局检索路由", entity.getRouteName()); + assertEquals("RAG_RETRIEVAL", entity.getTaskType()); + assertEquals("[3,4]", entity.getFallbackModelIdsJson()); + assertEquals("全局默认规则", entity.getRemark()); + + ModelRouteRuleResponse response = modelRouteRuleFactory.toResponse(entity); + assertEquals(2L, response.getPrimaryModelId()); + assertEquals("MANUAL", response.getRouteStrategy()); + } + + @Test + void modelCallLogFactoryShouldExposeReadonlyView() { + ModelCallLog entity = new ModelCallLog(); + entity.setId(10L); + entity.setRequestId("req-001"); + entity.setProviderId(1L); + entity.setModelId(2L); + entity.setTaskType("RAG_EMBEDDING"); + entity.setBizType("RAG_DOCUMENT_INDEX"); + entity.setBizId("doc-1"); + entity.setCallType("EMBEDDING"); + entity.setStatus("FAILED"); + entity.setDurationMs(2200); + entity.setErrorCode("TIMEOUT"); + entity.setErrorMessage("上游超时"); + entity.setEstimatedCost(new BigDecimal("0.00250000")); + + ModelCallLogResponse response = modelCallLogFactory.toResponse(entity); + + assertNotNull(response); + assertEquals("req-001", response.getRequestId()); + assertEquals("RAG_EMBEDDING", response.getTaskType()); + assertEquals("RAG_DOCUMENT_INDEX", response.getBizType()); + assertEquals("FAILED", response.getStatus()); + assertEquals("TIMEOUT", response.getErrorCode()); + assertEquals("上游超时", response.getErrorMessage()); + } +} diff --git a/common-agent-modelprovider/src/test/java/com/bruce/modelprovider/workspace/ModelWorkspaceServiceTests.java b/common-agent-modelprovider/src/test/java/com/bruce/modelprovider/workspace/ModelWorkspaceServiceTests.java new file mode 100644 index 0000000..ad604bb --- /dev/null +++ b/common-agent-modelprovider/src/test/java/com/bruce/modelprovider/workspace/ModelWorkspaceServiceTests.java @@ -0,0 +1,123 @@ +package com.bruce.modelprovider.workspace; + +import com.bruce.modelprovider.dto.response.ModelCallLogResponse; +import com.bruce.modelprovider.dto.response.ModelConfigResponse; +import com.bruce.modelprovider.dto.response.ModelProviderResponse; +import com.bruce.modelprovider.dto.response.ModelRouteRuleResponse; +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.impl.ModelWorkspaceServiceImpl; +import com.bruce.modelprovider.vo.ModelWorkspaceVO; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ModelWorkspaceServiceTests { + + @Mock + private IModelProviderService modelProviderService; + + @Mock + private IModelConfigService modelConfigService; + + @Mock + private IModelRouteRuleService modelRouteRuleService; + + @Mock + private IModelCallLogService modelCallLogService; + + @InjectMocks + private ModelWorkspaceServiceImpl modelWorkspaceService; + + @Test + void getWorkspaceShouldAggregateHealthRoutesAndFailureSummary() { + when(modelProviderService.listResponses()).thenReturn(List.of( + provider(1L, "OPENAI_MAIN", "HEALTHY", true), + provider(2L, "LOCAL_OLLAMA", "UNHEALTHY", true), + provider(3L, "TEST_PROVIDER", "UNKNOWN", false) + )); + when(modelConfigService.listResponses()).thenReturn(List.of( + model(11L, 1L, "CHAT_MAIN", "CHAT", true), + model(12L, 1L, "EMB_1024", "EMBEDDING", true), + model(13L, 2L, "CHAT_FALLBACK", "CHAT", false) + )); + when(modelRouteRuleService.listResponses()).thenReturn(List.of( + route(21L, "RAG_GLOBAL", "RAG_RETRIEVAL", 11L, "[13]", true), + route(22L, "CHAT_GLOBAL", "AGENT_CHAT", 11L, "[]", false) + )); + when(modelCallLogService.query(org.mockito.ArgumentMatchers.any())).thenReturn(List.of( + log("req-001", "FAILED", "TIMEOUT", "上游超时"), + log("req-002", "FAILED", "AUTH_ERROR", "鉴权失败"), + log("req-003", "SUCCESS", null, null) + )); + + ModelWorkspaceVO workspace = modelWorkspaceService.getWorkspace(); + + assertNotNull(workspace); + assertEquals(3, workspace.getProviderCount()); + assertEquals(1, workspace.getHealthyProviderCount()); + assertEquals(1, workspace.getUnhealthyProviderCount()); + assertEquals(3, workspace.getModelCount()); + assertEquals(2, workspace.getEnabledModelCount()); + assertEquals(2, workspace.getRouteRuleCount()); + assertEquals(1, workspace.getEnabledRouteRuleCount()); + assertEquals(2, workspace.getRecentFailedCallCount()); + assertEquals(2, workspace.getFailedCallSummaries().size()); + assertEquals(3, workspace.getProviders().size()); + assertEquals(3, workspace.getModels().size()); + assertEquals(2, workspace.getRoutes().size()); + } + + private ModelProviderResponse provider(Long id, String code, String healthStatus, boolean enabled) { + ModelProviderResponse response = new ModelProviderResponse(); + response.setId(id); + response.setProviderCode(code); + response.setProviderName(code); + response.setHealthStatus(healthStatus); + response.setEnabled(enabled); + return response; + } + + private ModelConfigResponse model(Long id, Long providerId, String code, String modelType, boolean enabled) { + ModelConfigResponse response = new ModelConfigResponse(); + response.setId(id); + response.setProviderId(providerId); + response.setModelCode(code); + response.setModelName(code); + response.setModelType(modelType); + response.setEnabled(enabled); + return response; + } + + private ModelRouteRuleResponse route(Long id, String code, String taskType, Long primaryModelId, String fallbackJson, boolean enabled) { + ModelRouteRuleResponse response = new ModelRouteRuleResponse(); + response.setId(id); + response.setRouteCode(code); + response.setRouteName(code); + response.setTaskType(taskType); + response.setPrimaryModelId(primaryModelId); + response.setFallbackModelIdsJson(fallbackJson); + response.setEnabled(enabled); + return response; + } + + private ModelCallLogResponse log(String requestId, String status, String errorCode, String errorMessage) { + ModelCallLogResponse response = new ModelCallLogResponse(); + response.setRequestId(requestId); + response.setStatus(status); + response.setErrorCode(errorCode); + response.setErrorMessage(errorMessage); + return response; + } +} diff --git a/frontend/src/api/__tests__/modelWorkspace.spec.ts b/frontend/src/api/__tests__/modelWorkspace.spec.ts new file mode 100644 index 0000000..e51126a --- /dev/null +++ b/frontend/src/api/__tests__/modelWorkspace.spec.ts @@ -0,0 +1,20 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { getModelWorkspace } from '../modelWorkspace'; +import { get } from '../request'; + +vi.mock('../request', () => ({ + get: vi.fn(), +})); + +describe('model workspace api', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('loads model workspace aggregate endpoint', () => { + getModelWorkspace(); + + expect(get).toHaveBeenCalledWith('/model/workspace'); + }); +}); diff --git a/frontend/src/api/modelWorkspace.ts b/frontend/src/api/modelWorkspace.ts new file mode 100644 index 0000000..c07f137 --- /dev/null +++ b/frontend/src/api/modelWorkspace.ts @@ -0,0 +1,28 @@ +import { get } from './request'; +import type { ModelConfig, ModelProvider, ModelRouteRule } from './modelProvider'; + +export interface ModelWorkspaceFailure { + requestId: string; + status?: string | null; + errorCode?: string | null; + errorMessage?: string | null; +} + +export interface ModelWorkspace { + providerCount: number; + healthyProviderCount: number; + unhealthyProviderCount: number; + modelCount: number; + enabledModelCount: number; + routeRuleCount: number; + enabledRouteRuleCount: number; + recentFailedCallCount: number; + providers: ModelProvider[]; + models: ModelConfig[]; + routes: ModelRouteRule[]; + failedCallSummaries: ModelWorkspaceFailure[]; +} + +export function getModelWorkspace() { + return get('/model/workspace'); +}