diff --git a/common-agent-agent/src/main/java/com/bruce/agent/controller/AgentSessionController.java b/common-agent-agent/src/main/java/com/bruce/agent/controller/AgentSessionController.java new file mode 100644 index 0000000..e704218 --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/controller/AgentSessionController.java @@ -0,0 +1,59 @@ +package com.bruce.agent.controller; + +import com.bruce.agent.dto.request.AgentSessionCreateDTO; +import com.bruce.agent.dto.request.AgentSessionMessageCreateDTO; +import com.bruce.agent.service.IAgentMessageService; +import com.bruce.agent.service.IAgentSessionService; +import com.bruce.agent.service.IAgentWorkspaceService; +import com.bruce.agent.vo.AgentMessageVO; +import com.bruce.agent.vo.AgentSessionDetailVO; +import com.bruce.agent.vo.AgentWorkspaceVO; +import com.bruce.common.domain.model.RequestResult; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/api/agent-sessions") +@RequiredArgsConstructor +public class AgentSessionController { + + private final IAgentSessionService agentSessionService; + private final IAgentMessageService agentMessageService; + private final IAgentWorkspaceService agentWorkspaceService; + + @PostMapping("/create") + public RequestResult create(@RequestBody AgentSessionCreateDTO request) { + return RequestResult.success(agentSessionService.createSession(request)); + } + + @GetMapping("/detail") + public RequestResult detail(@RequestParam("id") Long id) { + return RequestResult.success(agentSessionService.getDetailById(id)); + } + + @GetMapping("/{sessionId}/messages") + public RequestResult> messages(@PathVariable("sessionId") Long sessionId) { + return RequestResult.success(agentMessageService.listBySessionId(sessionId)); + } + + @PostMapping("/{sessionId}/messages") + public RequestResult appendMessage(@PathVariable("sessionId") Long sessionId, + @RequestBody AgentSessionMessageCreateDTO request) { + request.setSessionId(sessionId); + return RequestResult.success(agentMessageService.appendMessage(request)); + } + + @GetMapping("/workspace") + public RequestResult workspace(@RequestParam("agentId") Long agentId, + @RequestParam(value = "sessionId", required = false) Long sessionId) { + return RequestResult.success(agentWorkspaceService.getWorkspace(agentId, sessionId)); + } +} diff --git a/common-agent-agent/src/main/java/com/bruce/agent/dto/request/AgentSessionCreateDTO.java b/common-agent-agent/src/main/java/com/bruce/agent/dto/request/AgentSessionCreateDTO.java new file mode 100644 index 0000000..66c4778 --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/dto/request/AgentSessionCreateDTO.java @@ -0,0 +1,13 @@ +package com.bruce.agent.dto.request; + +import lombok.Data; + +@Data +public class AgentSessionCreateDTO { + private Long agentId; + private String sessionCode; + private Long workflowRunId; + private String title; + private String metadataJson; + private String remark; +} diff --git a/common-agent-agent/src/main/java/com/bruce/agent/dto/request/AgentSessionMessageCreateDTO.java b/common-agent-agent/src/main/java/com/bruce/agent/dto/request/AgentSessionMessageCreateDTO.java new file mode 100644 index 0000000..8d2cb70 --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/dto/request/AgentSessionMessageCreateDTO.java @@ -0,0 +1,14 @@ +package com.bruce.agent.dto.request; + +import lombok.Data; + +@Data +public class AgentSessionMessageCreateDTO { + private Long sessionId; + private String role; + private String content; + private String citationJson; + private Integer tokenCount; + private String requestId; + private String remark; +} diff --git a/common-agent-agent/src/main/java/com/bruce/agent/dto/response/AgentChatResponse.java b/common-agent-agent/src/main/java/com/bruce/agent/dto/response/AgentChatResponse.java index 56f717d..97aa5e8 100644 --- a/common-agent-agent/src/main/java/com/bruce/agent/dto/response/AgentChatResponse.java +++ b/common-agent-agent/src/main/java/com/bruce/agent/dto/response/AgentChatResponse.java @@ -11,6 +11,8 @@ public class AgentChatResponse { private String agentName; private Long storeId; private String storeName; + private Long sessionId; + private String sessionCode; private String answer; private String modelRequestId; private List references; diff --git a/common-agent-agent/src/main/java/com/bruce/agent/entity/AgentCapabilityBinding.java b/common-agent-agent/src/main/java/com/bruce/agent/entity/AgentCapabilityBinding.java new file mode 100644 index 0000000..dba8d72 --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/entity/AgentCapabilityBinding.java @@ -0,0 +1,33 @@ +package com.bruce.agent.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; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("agent_capability_binding") +public class AgentCapabilityBinding extends BaseEntity { + + @TableField("owner_type") + private String ownerType; + + @TableField("owner_id") + private Long ownerId; + + @TableField("capability_type") + private String capabilityType; + + @TableField("capability_id") + private Long capabilityId; + + private Boolean enabled; + + @TableField(value = "config_json", typeHandler = PgJsonbStringTypeHandler.class) + private String configJson; + + private String remark; +} diff --git a/common-agent-agent/src/main/java/com/bruce/agent/entity/AgentDefinition.java b/common-agent-agent/src/main/java/com/bruce/agent/entity/AgentDefinition.java index 14cf889..e8f4fdb 100644 --- a/common-agent-agent/src/main/java/com/bruce/agent/entity/AgentDefinition.java +++ b/common-agent-agent/src/main/java/com/bruce/agent/entity/AgentDefinition.java @@ -25,5 +25,6 @@ public class AgentDefinition extends BaseEntity { private String status; + @TableField("remark") private String remark; } diff --git a/common-agent-agent/src/main/java/com/bruce/agent/entity/AgentMessage.java b/common-agent-agent/src/main/java/com/bruce/agent/entity/AgentMessage.java new file mode 100644 index 0000000..ea5cdf2 --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/entity/AgentMessage.java @@ -0,0 +1,29 @@ +package com.bruce.agent.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; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("agent_message") +public class AgentMessage extends BaseEntity { + + @TableField("session_id") + private Long sessionId; + + private String role; + + private String content; + + @TableField(value = "citation_json", typeHandler = PgJsonbStringTypeHandler.class) + private String citationJson; + + @TableField("token_count") + private Integer tokenCount; + + private String remark; +} diff --git a/common-agent-agent/src/main/java/com/bruce/agent/entity/AgentSession.java b/common-agent-agent/src/main/java/com/bruce/agent/entity/AgentSession.java new file mode 100644 index 0000000..e8d4166 --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/entity/AgentSession.java @@ -0,0 +1,32 @@ +package com.bruce.agent.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; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("agent_session") +public class AgentSession extends BaseEntity { + + @TableField("session_code") + private String sessionCode; + + @TableField("agent_id") + private Long agentId; + + @TableField("workflow_run_id") + private Long workflowRunId; + + private String title; + + private String status; + + @TableField(value = "metadata_json", typeHandler = PgJsonbStringTypeHandler.class) + private String metadataJson; + + private String remark; +} diff --git a/common-agent-agent/src/main/java/com/bruce/agent/factory/AgentDefinitionFactory.java b/common-agent-agent/src/main/java/com/bruce/agent/factory/AgentDefinitionFactory.java new file mode 100644 index 0000000..c08f2ec --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/factory/AgentDefinitionFactory.java @@ -0,0 +1,52 @@ +package com.bruce.agent.factory; + +import com.bruce.agent.dto.request.AgentDefinitionSaveRequest; +import com.bruce.agent.dto.response.AgentDefinitionResponse; +import com.bruce.agent.entity.AgentDefinition; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.List; + +/** + * Agent 定义工厂,统一处理请求、实体和返回对象转换。 + */ +@Component +public class AgentDefinitionFactory { + + public AgentDefinition toEntity(AgentDefinitionSaveRequest request) { + if (request == null) { + return null; + } + AgentDefinition entity = new AgentDefinition(); + entity.setId(request.getId()); + entity.setAgentCode(trimToNull(request.getAgentCode())); + entity.setAgentName(trimToNull(request.getAgentName())); + entity.setSystemPrompt(trimToNull(request.getSystemPrompt())); + entity.setStoreId(request.getStoreId()); + entity.setStatus(trimToNull(request.getStatus())); + entity.setRemark(trimToNull(request.getRemark())); + return entity; + } + + public AgentDefinitionResponse toResponse(AgentDefinition entity) { + if (entity == null) { + return null; + } + AgentDefinitionResponse response = new AgentDefinitionResponse(); + 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-agent/src/main/java/com/bruce/agent/factory/AgentMessageFactory.java b/common-agent-agent/src/main/java/com/bruce/agent/factory/AgentMessageFactory.java new file mode 100644 index 0000000..653fe69 --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/factory/AgentMessageFactory.java @@ -0,0 +1,51 @@ +package com.bruce.agent.factory; + +import com.bruce.agent.dto.request.AgentSessionMessageCreateDTO; +import com.bruce.agent.entity.AgentMessage; +import com.bruce.agent.vo.AgentMessageVO; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.List; + +/** + * Agent 消息工厂,统一处理消息入参与返回转换。 + */ +@Component +public class AgentMessageFactory { + + public AgentMessage toEntity(AgentSessionMessageCreateDTO request) { + if (request == null) { + return null; + } + AgentMessage entity = new AgentMessage(); + entity.setSessionId(request.getSessionId()); + entity.setRole(trimToNull(request.getRole())); + entity.setContent(trimToNull(request.getContent())); + entity.setCitationJson(trimToNull(request.getCitationJson())); + entity.setTokenCount(request.getTokenCount()); + entity.setRemark(trimToNull(request.getRemark())); + return entity; + } + + public AgentMessageVO toVO(AgentMessage entity) { + if (entity == null) { + return null; + } + AgentMessageVO vo = new AgentMessageVO(); + BeanUtils.copyProperties(entity, vo); + return vo; + } + + public List toVOList(List entities) { + return entities == null ? List.of() : entities.stream().map(this::toVO).toList(); + } + + private String trimToNull(String value) { + if (!StringUtils.hasText(value)) { + return null; + } + return value.trim(); + } +} diff --git a/common-agent-agent/src/main/java/com/bruce/agent/factory/AgentSessionFactory.java b/common-agent-agent/src/main/java/com/bruce/agent/factory/AgentSessionFactory.java new file mode 100644 index 0000000..28d1887 --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/factory/AgentSessionFactory.java @@ -0,0 +1,51 @@ +package com.bruce.agent.factory; + +import com.bruce.agent.dto.request.AgentSessionCreateDTO; +import com.bruce.agent.entity.AgentSession; +import com.bruce.agent.vo.AgentSessionDetailVO; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.List; + +/** + * Agent 会话工厂,统一处理会话转换逻辑。 + */ +@Component +public class AgentSessionFactory { + + public AgentSession toEntity(AgentSessionCreateDTO request) { + if (request == null) { + return null; + } + AgentSession entity = new AgentSession(); + entity.setAgentId(request.getAgentId()); + entity.setSessionCode(trimToNull(request.getSessionCode())); + entity.setWorkflowRunId(request.getWorkflowRunId()); + entity.setTitle(trimToNull(request.getTitle())); + entity.setMetadataJson(trimToNull(request.getMetadataJson())); + entity.setRemark(trimToNull(request.getRemark())); + return entity; + } + + public AgentSessionDetailVO toDetailVO(AgentSession entity) { + if (entity == null) { + return null; + } + AgentSessionDetailVO detailVO = new AgentSessionDetailVO(); + BeanUtils.copyProperties(entity, detailVO); + return detailVO; + } + + public List toDetailVOList(List entities) { + return entities == null ? List.of() : entities.stream().map(this::toDetailVO).toList(); + } + + private String trimToNull(String value) { + if (!StringUtils.hasText(value)) { + return null; + } + return value.trim(); + } +} diff --git a/common-agent-agent/src/main/java/com/bruce/agent/mapper/AgentCapabilityBindingMapper.java b/common-agent-agent/src/main/java/com/bruce/agent/mapper/AgentCapabilityBindingMapper.java new file mode 100644 index 0000000..b58922f --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/mapper/AgentCapabilityBindingMapper.java @@ -0,0 +1,9 @@ +package com.bruce.agent.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bruce.agent.entity.AgentCapabilityBinding; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface AgentCapabilityBindingMapper extends BaseMapper { +} diff --git a/common-agent-agent/src/main/java/com/bruce/agent/mapper/AgentMessageMapper.java b/common-agent-agent/src/main/java/com/bruce/agent/mapper/AgentMessageMapper.java new file mode 100644 index 0000000..45f1f15 --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/mapper/AgentMessageMapper.java @@ -0,0 +1,9 @@ +package com.bruce.agent.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bruce.agent.entity.AgentMessage; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface AgentMessageMapper extends BaseMapper { +} diff --git a/common-agent-agent/src/main/java/com/bruce/agent/mapper/AgentSessionMapper.java b/common-agent-agent/src/main/java/com/bruce/agent/mapper/AgentSessionMapper.java new file mode 100644 index 0000000..58f1059 --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/mapper/AgentSessionMapper.java @@ -0,0 +1,9 @@ +package com.bruce.agent.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bruce.agent.entity.AgentSession; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface AgentSessionMapper extends BaseMapper { +} diff --git a/common-agent-agent/src/main/java/com/bruce/agent/service/IAgentMessageService.java b/common-agent-agent/src/main/java/com/bruce/agent/service/IAgentMessageService.java new file mode 100644 index 0000000..5bd7d5e --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/service/IAgentMessageService.java @@ -0,0 +1,17 @@ +package com.bruce.agent.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bruce.agent.dto.request.AgentSessionMessageCreateDTO; +import com.bruce.agent.entity.AgentMessage; +import com.bruce.agent.vo.AgentMessageVO; + +import java.util.List; + +public interface IAgentMessageService extends IService { + + boolean appendMessage(AgentSessionMessageCreateDTO request); + + AgentMessage appendMessageEntity(AgentSessionMessageCreateDTO request); + + List listBySessionId(Long sessionId); +} diff --git a/common-agent-agent/src/main/java/com/bruce/agent/service/IAgentSessionService.java b/common-agent-agent/src/main/java/com/bruce/agent/service/IAgentSessionService.java new file mode 100644 index 0000000..290604e --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/service/IAgentSessionService.java @@ -0,0 +1,21 @@ +package com.bruce.agent.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bruce.agent.dto.request.AgentSessionCreateDTO; +import com.bruce.agent.entity.AgentSession; +import com.bruce.agent.vo.AgentSessionDetailVO; + +import java.util.List; + +public interface IAgentSessionService extends IService { + + boolean createSession(AgentSessionCreateDTO request); + + AgentSession createSessionEntity(AgentSessionCreateDTO request); + + List listByAgentId(Long agentId); + + AgentSessionDetailVO getDetailById(Long sessionId); + + boolean closeSession(Long sessionId); +} diff --git a/common-agent-agent/src/main/java/com/bruce/agent/service/IAgentWorkspaceService.java b/common-agent-agent/src/main/java/com/bruce/agent/service/IAgentWorkspaceService.java new file mode 100644 index 0000000..8e14b2c --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/service/IAgentWorkspaceService.java @@ -0,0 +1,8 @@ +package com.bruce.agent.service; + +import com.bruce.agent.vo.AgentWorkspaceVO; + +public interface IAgentWorkspaceService { + + AgentWorkspaceVO getWorkspace(Long agentId, Long sessionId); +} diff --git a/common-agent-agent/src/main/java/com/bruce/agent/service/impl/AgentDefinitionServiceImpl.java b/common-agent-agent/src/main/java/com/bruce/agent/service/impl/AgentDefinitionServiceImpl.java index 7e454d5..0995638 100644 --- a/common-agent-agent/src/main/java/com/bruce/agent/service/impl/AgentDefinitionServiceImpl.java +++ b/common-agent-agent/src/main/java/com/bruce/agent/service/impl/AgentDefinitionServiceImpl.java @@ -4,13 +4,21 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.bruce.agent.dto.request.AgentChatRequest; import com.bruce.agent.dto.request.AgentDefinitionQueryRequest; import com.bruce.agent.dto.request.AgentDefinitionSaveRequest; +import com.bruce.agent.dto.request.AgentSessionCreateDTO; +import com.bruce.agent.dto.request.AgentSessionMessageCreateDTO; import com.bruce.agent.dto.response.AgentChatResponse; import com.bruce.agent.dto.response.AgentDefinitionResponse; import com.bruce.agent.entity.AgentDefinition; +import com.bruce.agent.entity.AgentMessage; +import com.bruce.agent.entity.AgentSession; +import com.bruce.agent.factory.AgentDefinitionFactory; import com.bruce.agent.mapper.AgentDefinitionMapper; import com.bruce.agent.service.IAgentDefinitionService; +import com.bruce.agent.service.IAgentMessageService; +import com.bruce.agent.service.IAgentSessionService; import com.bruce.common.enums.EnableStatusEnum; import com.bruce.modelprovider.client.OpenAiChatMessage; +import com.bruce.modelprovider.entity.ModelCallLog; import com.bruce.modelprovider.entity.RagStoreModelConfig; import com.bruce.modelprovider.gateway.ChatModelGateway; import com.bruce.modelprovider.gateway.ChatRequest; @@ -30,6 +38,7 @@ import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.List; +import java.util.UUID; @Slf4j @Service @@ -44,35 +53,32 @@ public class AgentDefinitionServiceImpl extends ServiceImpl listResponses() { - return lambdaQuery() + return agentDefinitionFactory.toResponses(lambdaQuery() .orderByAsc(AgentDefinition::getAgentCode) - .list() - .stream() - .map(AgentDefinitionResponse::fromEntity) - .toList(); + .list()); } @Override public List query(AgentDefinitionQueryRequest request) { AgentDefinitionQueryRequest queryRequest = request == null ? new AgentDefinitionQueryRequest() : request; - return lambdaQuery() + return agentDefinitionFactory.toResponses(lambdaQuery() .eq(StringUtils.hasText(queryRequest.getAgentCode()), AgentDefinition::getAgentCode, trimToNull(queryRequest.getAgentCode())) .like(StringUtils.hasText(queryRequest.getAgentName()), AgentDefinition::getAgentName, trimToNull(queryRequest.getAgentName())) .eq(StringUtils.hasText(queryRequest.getStatus()), AgentDefinition::getStatus, trimToNull(queryRequest.getStatus())) .eq(queryRequest.getStoreId() != null, AgentDefinition::getStoreId, queryRequest.getStoreId()) .orderByAsc(AgentDefinition::getAgentCode) - .list() - .stream() - .map(AgentDefinitionResponse::fromEntity) - .toList(); + .list()); } @Override public AgentDefinitionResponse getResponseById(Long id) { - return AgentDefinitionResponse.fromEntity(getById(id)); + return agentDefinitionFactory.toResponse(getById(id)); } @Override @@ -81,30 +87,38 @@ public class AgentDefinitionServiceImpl extends ServiceImpl messages) { + if (agentMessageService == null) { + return; + } + for (AgentChatRequest.AgentMessage rawMessage : messages) { + if (rawMessage == null || !StringUtils.hasText(rawMessage.getContent())) { + continue; + } + AgentSessionMessageCreateDTO createDTO = new AgentSessionMessageCreateDTO(); + createDTO.setSessionId(sessionId); + createDTO.setRole(normalizeRole(rawMessage.getRole())); + createDTO.setContent(rawMessage.getContent().trim()); + createDTO.setCitationJson("[]"); + createDTO.setRemark("兼容聊天接口消息"); + AgentMessage entity = agentMessageService.appendMessageEntity(createDTO); + agentMessageService.save(entity); + } + } + + private AgentMessage appendAssistantMessage(Long sessionId, + List recalls, + ChatResult chatResult) { + if (agentMessageService == null) { + AgentMessage message = new AgentMessage(); + message.setSessionId(sessionId); + message.setRole("assistant"); + message.setContent(chatResult.getContent()); + message.setCitationJson(toCitationJson(recalls)); + message.setTokenCount(chatResult.getCallLog() == null ? null : chatResult.getCallLog().getTotalTokens()); + message.setRemark("兼容聊天接口模型回复"); + return message; + } + AgentSessionMessageCreateDTO createDTO = new AgentSessionMessageCreateDTO(); + createDTO.setSessionId(sessionId); + createDTO.setRole("assistant"); + createDTO.setContent(chatResult.getContent()); + createDTO.setCitationJson(toCitationJson(recalls)); + ModelCallLog callLog = chatResult.getCallLog(); + createDTO.setRequestId(callLog == null ? null : callLog.getRequestId()); + createDTO.setTokenCount(callLog == null ? null : callLog.getTotalTokens()); + createDTO.setRemark("兼容聊天接口模型回复"); + AgentMessage entity = agentMessageService.appendMessageEntity(createDTO); + agentMessageService.save(entity); + return entity; + } + + private String resolveRequestId(ChatResult chatResult) { + ModelCallLog callLog = chatResult == null ? null : chatResult.getCallLog(); + return callLog == null ? null : callLog.getRequestId(); + } + + private String toCitationJson(List recalls) { + if (recalls == null || recalls.isEmpty()) { + return "[]"; + } + StringBuilder builder = new StringBuilder("["); + for (int i = 0; i < recalls.size(); i++) { + RagChunkRecallResponse recall = recalls.get(i); + if (i > 0) { + builder.append(','); + } + builder.append("{\"chunkId\":") + .append(recall.getChunkId()) + .append(",\"documentId\":") + .append(recall.getDocumentId()) + .append(",\"score\":") + .append(recall.getScore() == null ? 0D : recall.getScore()) + .append('}'); + } + builder.append(']'); + return builder.toString(); + } + private String normalizeRole(String role) { if (!StringUtils.hasText(role)) { return "user"; diff --git a/common-agent-agent/src/main/java/com/bruce/agent/service/impl/AgentMessageServiceImpl.java b/common-agent-agent/src/main/java/com/bruce/agent/service/impl/AgentMessageServiceImpl.java new file mode 100644 index 0000000..9da54de --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/service/impl/AgentMessageServiceImpl.java @@ -0,0 +1,93 @@ +package com.bruce.agent.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bruce.agent.dto.request.AgentSessionMessageCreateDTO; +import com.bruce.agent.entity.AgentMessage; +import com.bruce.agent.entity.AgentSession; +import com.bruce.agent.factory.AgentMessageFactory; +import com.bruce.agent.mapper.AgentMessageMapper; +import com.bruce.agent.service.IAgentMessageService; +import com.bruce.agent.service.IAgentSessionService; +import com.bruce.agent.vo.AgentMessageVO; +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 AgentMessageServiceImpl extends ServiceImpl implements IAgentMessageService { + + private final IAgentSessionService agentSessionService; + private final AgentMessageFactory agentMessageFactory; + + @Override + public boolean appendMessage(AgentSessionMessageCreateDTO request) { + AgentMessage entity = appendMessageEntity(request); + boolean saved = save(entity); + log.info("Agent消息写入完成,sessionId={}, role={}, messageId={}, requestId={}, result={}", + entity.getSessionId(), entity.getRole(), entity.getId(), request.getRequestId(), saved); + return saved; + } + + @Override + public AgentMessage appendMessageEntity(AgentSessionMessageCreateDTO request) { + validateRequest(request); + AgentSession session = loadSession(request.getSessionId()); + if ("CLOSED".equals(session.getStatus())) { + throw new IllegalArgumentException("会话已关闭,不能继续写入消息"); + } + AgentMessage entity = loadFactory().toEntity(request); + if (!StringUtils.hasText(entity.getCitationJson())) { + entity.setCitationJson("[]"); + } else if (!entity.getCitationJson().trim().startsWith("[")) { + throw new IllegalArgumentException("citationJson必须是数组结构"); + } + log.info("Agent消息写入开始,sessionId={}, role={}, requestId={}", + entity.getSessionId(), entity.getRole(), request.getRequestId()); + return entity; + } + + @Override + public List listBySessionId(Long sessionId) { + if (sessionId == null) { + throw new IllegalArgumentException("会话ID不能为空"); + } + List messages = lambdaQuery() + .eq(AgentMessage::getSessionId, sessionId) + .orderByAsc(AgentMessage::getCreateTime) + .orderByAsc(AgentMessage::getId) + .list(); + return loadFactory().toVOList(messages); + } + + public AgentSession loadSession(Long sessionId) { + AgentSession session = agentSessionService.getById(sessionId); + if (session == null) { + throw new IllegalArgumentException("会话不存在,ID: " + sessionId); + } + return session; + } + + private void validateRequest(AgentSessionMessageCreateDTO request) { + if (request == null) { + throw new IllegalArgumentException("消息请求不能为空"); + } + if (request.getSessionId() == null) { + throw new IllegalArgumentException("会话ID不能为空"); + } + if (!StringUtils.hasText(request.getRole())) { + throw new IllegalArgumentException("消息角色不能为空"); + } + if (!StringUtils.hasText(request.getContent())) { + throw new IllegalArgumentException("消息内容不能为空"); + } + } + + private AgentMessageFactory loadFactory() { + return agentMessageFactory == null ? new AgentMessageFactory() : agentMessageFactory; + } +} diff --git a/common-agent-agent/src/main/java/com/bruce/agent/service/impl/AgentSessionServiceImpl.java b/common-agent-agent/src/main/java/com/bruce/agent/service/impl/AgentSessionServiceImpl.java new file mode 100644 index 0000000..0c1b826 --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/service/impl/AgentSessionServiceImpl.java @@ -0,0 +1,120 @@ +package com.bruce.agent.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bruce.agent.dto.request.AgentSessionCreateDTO; +import com.bruce.agent.entity.AgentDefinition; +import com.bruce.agent.entity.AgentSession; +import com.bruce.agent.factory.AgentSessionFactory; +import com.bruce.agent.mapper.AgentSessionMapper; +import com.bruce.agent.service.IAgentDefinitionService; +import com.bruce.agent.service.IAgentSessionService; +import com.bruce.agent.vo.AgentSessionDetailVO; +import com.bruce.common.enums.EnableStatusEnum; +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 AgentSessionServiceImpl extends ServiceImpl implements IAgentSessionService { + + private static final String SESSION_STATUS_ACTIVE = "ACTIVE"; + private static final String SESSION_STATUS_CLOSED = "CLOSED"; + + private final IAgentDefinitionService agentDefinitionService; + private final AgentSessionFactory agentSessionFactory; + + @Override + public boolean createSession(AgentSessionCreateDTO request) { + AgentSession entity = createSessionEntity(request); + boolean saved = save(entity); + log.info("Agent会话创建完成,agentId={}, sessionCode={}, sessionId={}, result={}", + entity.getAgentId(), entity.getSessionCode(), entity.getId(), saved); + return saved; + } + + @Override + public AgentSession createSessionEntity(AgentSessionCreateDTO request) { + validateCreateRequest(request); + AgentDefinition agent = agentDefinitionService.getById(request.getAgentId()); + if (agent == null) { + throw new IllegalArgumentException("Agent不存在,ID: " + request.getAgentId()); + } + if (!EnableStatusEnum.ENABLED.name().equals(agent.getStatus())) { + throw new IllegalArgumentException("Agent已停用,无法创建会话"); + } + if (baseMapper != null) { + AgentSession duplicate = lambdaQuery() + .eq(AgentSession::getSessionCode, request.getSessionCode().trim()) + .one(); + if (duplicate != null) { + throw new IllegalArgumentException("会话编码已存在: " + request.getSessionCode().trim()); + } + } + AgentSession entity = loadFactory().toEntity(request); + entity.setStatus(SESSION_STATUS_ACTIVE); + if (!StringUtils.hasText(entity.getMetadataJson())) { + entity.setMetadataJson("{}"); + } + log.info("Agent会话创建开始,agentId={}, sessionCode={}, workflowRunId={}", + entity.getAgentId(), entity.getSessionCode(), entity.getWorkflowRunId()); + return entity; + } + + @Override + public List listByAgentId(Long agentId) { + if (agentId == null) { + throw new IllegalArgumentException("Agent ID不能为空"); + } + List sessions = lambdaQuery() + .eq(AgentSession::getAgentId, agentId) + .orderByDesc(AgentSession::getUpdateTime) + .orderByDesc(AgentSession::getId) + .list(); + return loadFactory().toDetailVOList(sessions); + } + + @Override + public AgentSessionDetailVO getDetailById(Long sessionId) { + if (sessionId == null) { + throw new IllegalArgumentException("会话ID不能为空"); + } + return loadFactory().toDetailVO(getById(sessionId)); + } + + @Override + public boolean closeSession(Long sessionId) { + if (sessionId == null) { + throw new IllegalArgumentException("会话ID不能为空"); + } + AgentSession session = getById(sessionId); + if (session == null) { + throw new IllegalArgumentException("会话不存在,ID: " + sessionId); + } + session.setStatus(SESSION_STATUS_CLOSED); + boolean updated = updateById(session); + log.info("Agent会话关闭完成,sessionId={}, sessionCode={}, result={}", + session.getId(), session.getSessionCode(), updated); + return updated; + } + + private void validateCreateRequest(AgentSessionCreateDTO request) { + if (request == null) { + throw new IllegalArgumentException("创建会话请求不能为空"); + } + if (request.getAgentId() == null) { + throw new IllegalArgumentException("Agent ID不能为空"); + } + if (!StringUtils.hasText(request.getSessionCode())) { + throw new IllegalArgumentException("会话编码不能为空"); + } + } + + private AgentSessionFactory loadFactory() { + return agentSessionFactory == null ? new AgentSessionFactory() : agentSessionFactory; + } +} diff --git a/common-agent-agent/src/main/java/com/bruce/agent/service/impl/AgentWorkspaceServiceImpl.java b/common-agent-agent/src/main/java/com/bruce/agent/service/impl/AgentWorkspaceServiceImpl.java new file mode 100644 index 0000000..413a579 --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/service/impl/AgentWorkspaceServiceImpl.java @@ -0,0 +1,91 @@ +package com.bruce.agent.service.impl; + +import com.bruce.agent.entity.AgentDefinition; +import com.bruce.agent.service.IAgentDefinitionService; +import com.bruce.agent.service.IAgentMessageService; +import com.bruce.agent.service.IAgentSessionService; +import com.bruce.agent.service.IAgentWorkspaceService; +import com.bruce.agent.vo.AgentMessageVO; +import com.bruce.agent.vo.AgentSessionDetailVO; +import com.bruce.agent.vo.AgentWorkspaceVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.List; +import java.util.Objects; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AgentWorkspaceServiceImpl implements IAgentWorkspaceService { + + private final IAgentDefinitionService agentDefinitionService; + private final IAgentSessionService agentSessionService; + private final IAgentMessageService agentMessageService; + + @Override + public AgentWorkspaceVO getWorkspace(Long agentId, Long sessionId) { + log.info("Agent工作台查询开始,agentId={}, sessionId={}", agentId, sessionId); + if (agentId == null) { + throw new IllegalArgumentException("Agent ID不能为空"); + } + AgentDefinition agent = agentDefinitionService.getById(agentId); + if (agent == null) { + throw new IllegalArgumentException("Agent不存在,ID: " + agentId); + } + + List sessions = agentSessionService.listByAgentId(agentId); + AgentSessionDetailVO currentSession = resolveSession(sessionId, sessions); + List messages = currentSession == null ? List.of() : agentMessageService.listBySessionId(currentSession.getId()); + + AgentWorkspaceVO workspace = new AgentWorkspaceVO(); + workspace.setAgentId(agent.getId()); + workspace.setAgentCode(agent.getAgentCode()); + workspace.setAgentName(agent.getAgentName()); + workspace.setStoreId(agent.getStoreId()); + workspace.setStatus(agent.getStatus()); + workspace.setSessions(sessions); + workspace.setMessages(messages); + + if (currentSession != null) { + workspace.setSessionId(currentSession.getId()); + workspace.setSessionCode(currentSession.getSessionCode()); + workspace.setSessionTitle(currentSession.getTitle()); + workspace.setSessionStatus(currentSession.getStatus()); + workspace.setWorkflowRunId(currentSession.getWorkflowRunId()); + } + + int totalTokens = messages.stream() + .map(AgentMessageVO::getTokenCount) + .filter(Objects::nonNull) + .mapToInt(Integer::intValue) + .sum(); + int citationCount = (int) messages.stream() + .filter(message -> StringUtils.hasText(message.getCitationJson()) && message.getCitationJson().contains("chunkId")) + .count(); + String latestRequestId = messages.stream() + .map(AgentMessageVO::getRequestId) + .filter(StringUtils::hasText) + .reduce((first, second) -> second) + .orElse(null); + + workspace.setTotalTokens(totalTokens); + workspace.setCitationCount(citationCount); + workspace.setLatestRequestId(latestRequestId); + log.info("Agent工作台查询结束,agentId={}, sessionId={}, messageCount={}, totalTokens={}", + agentId, workspace.getSessionId(), messages.size(), totalTokens); + return workspace; + } + + private AgentSessionDetailVO resolveSession(Long sessionId, List sessions) { + if (sessions == null || sessions.isEmpty()) { + return null; + } + if (sessionId == null) { + return sessions.get(0); + } + return sessions.stream().filter(item -> sessionId.equals(item.getId())).findFirst().orElse(null); + } +} diff --git a/common-agent-agent/src/main/java/com/bruce/agent/vo/AgentMessageVO.java b/common-agent-agent/src/main/java/com/bruce/agent/vo/AgentMessageVO.java new file mode 100644 index 0000000..3843422 --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/vo/AgentMessageVO.java @@ -0,0 +1,18 @@ +package com.bruce.agent.vo; + +import lombok.Data; + +import java.util.Date; + +@Data +public class AgentMessageVO { + private Long id; + private Long sessionId; + private String role; + private String content; + private String citationJson; + private Integer tokenCount; + private String requestId; + private String remark; + private Date createTime; +} diff --git a/common-agent-agent/src/main/java/com/bruce/agent/vo/AgentSessionDetailVO.java b/common-agent-agent/src/main/java/com/bruce/agent/vo/AgentSessionDetailVO.java new file mode 100644 index 0000000..cd354fe --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/vo/AgentSessionDetailVO.java @@ -0,0 +1,19 @@ +package com.bruce.agent.vo; + +import lombok.Data; + +import java.util.Date; + +@Data +public class AgentSessionDetailVO { + private Long id; + private Long agentId; + private String sessionCode; + private Long workflowRunId; + private String title; + private String status; + private String metadataJson; + private String remark; + private Date createTime; + private Date updateTime; +} diff --git a/common-agent-agent/src/main/java/com/bruce/agent/vo/AgentWorkspaceVO.java b/common-agent-agent/src/main/java/com/bruce/agent/vo/AgentWorkspaceVO.java new file mode 100644 index 0000000..ae016c1 --- /dev/null +++ b/common-agent-agent/src/main/java/com/bruce/agent/vo/AgentWorkspaceVO.java @@ -0,0 +1,24 @@ +package com.bruce.agent.vo; + +import lombok.Data; + +import java.util.List; + +@Data +public class AgentWorkspaceVO { + private Long agentId; + private String agentCode; + private String agentName; + private Long storeId; + private String status; + private Long sessionId; + private String sessionCode; + private String sessionTitle; + private String sessionStatus; + private Long workflowRunId; + private Integer totalTokens; + private Integer citationCount; + private String latestRequestId; + private List sessions; + private List messages; +} diff --git a/common-agent-agent/src/test/java/com/bruce/agent/factory/AgentFactoryTests.java b/common-agent-agent/src/test/java/com/bruce/agent/factory/AgentFactoryTests.java new file mode 100644 index 0000000..62b9493 --- /dev/null +++ b/common-agent-agent/src/test/java/com/bruce/agent/factory/AgentFactoryTests.java @@ -0,0 +1,86 @@ +package com.bruce.agent.factory; + +import com.bruce.agent.dto.request.AgentDefinitionSaveRequest; +import com.bruce.agent.dto.request.AgentSessionCreateDTO; +import com.bruce.agent.dto.request.AgentSessionMessageCreateDTO; +import com.bruce.agent.dto.response.AgentDefinitionResponse; +import com.bruce.agent.entity.AgentDefinition; +import com.bruce.agent.entity.AgentMessage; +import com.bruce.agent.entity.AgentSession; +import com.bruce.agent.vo.AgentSessionDetailVO; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AgentFactoryTests { + + private final AgentDefinitionFactory agentDefinitionFactory = new AgentDefinitionFactory(); + private final AgentSessionFactory agentSessionFactory = new AgentSessionFactory(); + private final AgentMessageFactory agentMessageFactory = new AgentMessageFactory(); + + @Test + void agentDefinitionFactoryShouldTrimRequestAndBuildResponse() { + AgentDefinitionSaveRequest request = new AgentDefinitionSaveRequest(); + request.setId(1L); + request.setAgentCode(" AGENT_RAG_HELPER "); + request.setAgentName(" 知识问答助手 "); + request.setSystemPrompt(" 你是企业知识助手 "); + request.setStoreId(1001L); + request.setStatus(" ENABLED "); + request.setRemark(" 默认Agent "); + + AgentDefinition entity = agentDefinitionFactory.toEntity(request); + assertEquals("AGENT_RAG_HELPER", entity.getAgentCode()); + assertEquals("知识问答助手", entity.getAgentName()); + assertEquals("你是企业知识助手", entity.getSystemPrompt()); + assertEquals("ENABLED", entity.getStatus()); + assertEquals("默认Agent", entity.getRemark()); + + AgentDefinitionResponse response = agentDefinitionFactory.toResponse(entity); + assertEquals(1001L, response.getStoreId()); + assertEquals("AGENT_RAG_HELPER", response.getAgentCode()); + } + + @Test + void agentSessionFactoryShouldBuildSessionEntityAndDetailView() { + AgentSessionCreateDTO request = new AgentSessionCreateDTO(); + request.setAgentId(1L); + request.setSessionCode(" session_001 "); + request.setWorkflowRunId(2001L); + request.setTitle(" 产品问答会话 "); + request.setMetadataJson(" {\"source\":\"debug\"} "); + request.setRemark(" 首轮调试 "); + + AgentSession entity = agentSessionFactory.toEntity(request); + assertNotNull(entity); + assertEquals("session_001", entity.getSessionCode()); + assertEquals("产品问答会话", entity.getTitle()); + assertEquals("{\"source\":\"debug\"}", entity.getMetadataJson()); + assertEquals("首轮调试", entity.getRemark()); + + AgentSessionDetailVO detailVO = agentSessionFactory.toDetailVO(entity); + assertEquals(1L, detailVO.getAgentId()); + assertEquals("session_001", detailVO.getSessionCode()); + } + + @Test + void agentMessageFactoryShouldBuildMessageEntityAndPreserveCitationJson() { + AgentSessionMessageCreateDTO request = new AgentSessionMessageCreateDTO(); + request.setSessionId(10L); + request.setRole(" assistant "); + request.setContent(" 这里是回答内容 "); + request.setCitationJson(" [{\"chunkId\":1}] "); + request.setTokenCount(256); + request.setRemark(" 回答成功 "); + + AgentMessage entity = agentMessageFactory.toEntity(request); + assertEquals(10L, entity.getSessionId()); + assertEquals("assistant", entity.getRole()); + assertEquals("这里是回答内容", entity.getContent()); + assertEquals("[{\"chunkId\":1}]", entity.getCitationJson()); + assertEquals(256, entity.getTokenCount()); + assertTrue(entity.getRemark().contains("回答成功")); + } +} diff --git a/common-agent-agent/src/test/java/com/bruce/agent/session/AgentSessionServiceTests.java b/common-agent-agent/src/test/java/com/bruce/agent/session/AgentSessionServiceTests.java new file mode 100644 index 0000000..600c759 --- /dev/null +++ b/common-agent-agent/src/test/java/com/bruce/agent/session/AgentSessionServiceTests.java @@ -0,0 +1,119 @@ +package com.bruce.agent.session; + +import com.bruce.agent.dto.request.AgentSessionCreateDTO; +import com.bruce.agent.dto.request.AgentSessionMessageCreateDTO; +import com.bruce.agent.entity.AgentDefinition; +import com.bruce.agent.entity.AgentMessage; +import com.bruce.agent.entity.AgentSession; +import com.bruce.agent.service.IAgentDefinitionService; +import com.bruce.agent.service.impl.AgentMessageServiceImpl; +import com.bruce.agent.service.impl.AgentSessionServiceImpl; +import com.bruce.common.enums.EnableStatusEnum; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class AgentSessionServiceTests { + + @Mock + private IAgentDefinitionService agentDefinitionService; + + @Spy + @InjectMocks + private AgentSessionServiceImpl agentSessionService; + + @Spy + @InjectMocks + private AgentMessageServiceImpl agentMessageService; + + @Test + void createSessionShouldRejectDisabledAgent() { + AgentDefinition agent = new AgentDefinition(); + agent.setId(1L); + agent.setStatus("DISABLED"); + when(agentDefinitionService.getById(1L)).thenReturn(agent); + + AgentSessionCreateDTO request = new AgentSessionCreateDTO(); + request.setAgentId(1L); + request.setSessionCode("session_001"); + + assertThrows(IllegalArgumentException.class, () -> agentSessionService.createSession(request)); + } + + @Test + void createSessionShouldPersistActiveSession() { + AgentDefinition agent = new AgentDefinition(); + agent.setId(1L); + agent.setStatus(EnableStatusEnum.ENABLED.name()); + when(agentDefinitionService.getById(1L)).thenReturn(agent); + doAnswer(invocation -> true).when(agentSessionService).save(any(AgentSession.class)); + + AgentSessionCreateDTO request = new AgentSessionCreateDTO(); + request.setAgentId(1L); + request.setSessionCode("session_001"); + request.setTitle("产品问答"); + request.setMetadataJson("{\"source\":\"debug\"}"); + + boolean result = agentSessionService.createSession(request); + assertTrue(result); + + ArgumentCaptor captor = ArgumentCaptor.forClass(AgentSession.class); + verify(agentSessionService).save(captor.capture()); + assertEquals("session_001", captor.getValue().getSessionCode()); + assertEquals("ACTIVE", captor.getValue().getStatus()); + } + + @Test + void appendMessageShouldRejectClosedSession() { + AgentSession session = new AgentSession(); + session.setId(10L); + session.setStatus("CLOSED"); + doReturn(session).when(agentMessageService).loadSession(10L); + + AgentSessionMessageCreateDTO request = new AgentSessionMessageCreateDTO(); + request.setSessionId(10L); + request.setRole("user"); + request.setContent("你好"); + request.setCitationJson("[]"); + + assertThrows(IllegalArgumentException.class, () -> agentMessageService.appendMessage(request)); + } + + @Test + void appendMessageShouldPersistCitationJson() { + AgentSession session = new AgentSession(); + session.setId(10L); + session.setStatus("ACTIVE"); + doReturn(session).when(agentMessageService).loadSession(10L); + doAnswer(invocation -> true).when(agentMessageService).save(any(AgentMessage.class)); + + AgentSessionMessageCreateDTO request = new AgentSessionMessageCreateDTO(); + request.setSessionId(10L); + request.setRole("assistant"); + request.setContent("这里是回答"); + request.setCitationJson("[{\"chunkId\":1}]"); + request.setTokenCount(128); + + boolean result = agentMessageService.appendMessage(request); + assertTrue(result); + + ArgumentCaptor captor = ArgumentCaptor.forClass(AgentMessage.class); + verify(agentMessageService).save(captor.capture()); + assertEquals("[{\"chunkId\":1}]", captor.getValue().getCitationJson()); + assertEquals(128, captor.getValue().getTokenCount()); + } +} diff --git a/common-agent-agent/src/test/java/com/bruce/agent/workspace/AgentWorkspaceServiceTests.java b/common-agent-agent/src/test/java/com/bruce/agent/workspace/AgentWorkspaceServiceTests.java new file mode 100644 index 0000000..98294ae --- /dev/null +++ b/common-agent-agent/src/test/java/com/bruce/agent/workspace/AgentWorkspaceServiceTests.java @@ -0,0 +1,81 @@ +package com.bruce.agent.workspace; + +import com.bruce.agent.entity.AgentDefinition; +import com.bruce.agent.service.IAgentDefinitionService; +import com.bruce.agent.service.IAgentMessageService; +import com.bruce.agent.service.IAgentSessionService; +import com.bruce.agent.service.impl.AgentWorkspaceServiceImpl; +import com.bruce.agent.vo.AgentMessageVO; +import com.bruce.agent.vo.AgentSessionDetailVO; +import com.bruce.agent.vo.AgentWorkspaceVO; +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 AgentWorkspaceServiceTests { + + @Mock + private IAgentDefinitionService agentDefinitionService; + + @Mock + private IAgentSessionService agentSessionService; + + @Mock + private IAgentMessageService agentMessageService; + + @InjectMocks + private AgentWorkspaceServiceImpl agentWorkspaceService; + + @Test + void getWorkspaceShouldAggregateAgentSessionAndMessages() { + AgentDefinition agent = new AgentDefinition(); + agent.setId(1L); + agent.setAgentCode("AGENT_RAG_HELPER"); + agent.setAgentName("知识问答助手"); + agent.setStoreId(1001L); + agent.setStatus("ENABLED"); + + AgentSessionDetailVO session = new AgentSessionDetailVO(); + session.setId(10L); + session.setAgentId(1L); + session.setSessionCode("session_001"); + session.setStatus("ACTIVE"); + session.setTitle("产品问答"); + session.setWorkflowRunId(2001L); + + AgentMessageVO userMessage = new AgentMessageVO(); + userMessage.setRole("user"); + userMessage.setContent("产品支持哪些模型?"); + AgentMessageVO assistantMessage = new AgentMessageVO(); + assistantMessage.setRole("assistant"); + assistantMessage.setContent("当前支持 OpenAI Compatible 协议模型。"); + assistantMessage.setCitationJson("[{\"chunkId\":1}]"); + assistantMessage.setTokenCount(256); + assistantMessage.setRequestId("req-001"); + + when(agentDefinitionService.getById(1L)).thenReturn(agent); + when(agentSessionService.listByAgentId(1L)).thenReturn(List.of(session)); + when(agentMessageService.listBySessionId(10L)).thenReturn(List.of(userMessage, assistantMessage)); + + AgentWorkspaceVO workspace = agentWorkspaceService.getWorkspace(1L, 10L); + + assertNotNull(workspace); + assertEquals("AGENT_RAG_HELPER", workspace.getAgentCode()); + assertEquals("知识问答助手", workspace.getAgentName()); + assertEquals(10L, workspace.getSessionId()); + assertEquals("session_001", workspace.getSessionCode()); + assertEquals(2, workspace.getMessages().size()); + assertEquals(1, workspace.getCitationCount()); + assertEquals(256, workspace.getTotalTokens()); + assertEquals("req-001", workspace.getLatestRequestId()); + } +} diff --git a/frontend/src/api/__tests__/agent.spec.ts b/frontend/src/api/__tests__/agent.spec.ts index f7e0b0d..a9a6f5e 100644 --- a/frontend/src/api/__tests__/agent.spec.ts +++ b/frontend/src/api/__tests__/agent.spec.ts @@ -1,9 +1,14 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { + appendAgentMessage, chatWithAgent, + createAgentSession, deleteAgent, getAgentById, + getAgentSessionById, + getAgentWorkspace, + listAgentMessages, listAgents, queryAgents, saveAgent, @@ -27,6 +32,11 @@ describe('agent api', () => { saveAgent({ agentCode: 'agent_1', agentName: 'Agent 1', storeId: '2001', status: 'ENABLED' }); deleteAgent('1001'); chatWithAgent('1001', { messages: [{ role: 'user', content: '你好' }] }); + createAgentSession({ agentId: '1001', sessionCode: 'session_001' }); + getAgentSessionById('session-id'); + listAgentMessages('session-id'); + appendAgentMessage('session-id', { role: 'assistant', content: '收到' }); + getAgentWorkspace('1001', 'session-id'); expect(post).toHaveBeenCalledWith('/agents/list'); expect(post).toHaveBeenCalledWith('/agents/query', { agentCode: 'demo' }); @@ -39,5 +49,10 @@ describe('agent api', () => { }); expect(post).toHaveBeenCalledWith('/agents/delete', undefined, { params: { id: '1001' } }); expect(post).toHaveBeenCalledWith('/agents/1001/chat', { messages: [{ role: 'user', content: '你好' }] }); + expect(post).toHaveBeenCalledWith('/agent-sessions/create', { agentId: '1001', sessionCode: 'session_001' }); + expect(get).toHaveBeenCalledWith('/agent-sessions/detail', { params: { id: 'session-id' } }); + expect(get).toHaveBeenCalledWith('/agent-sessions/session-id/messages'); + expect(post).toHaveBeenCalledWith('/agent-sessions/session-id/messages', { role: 'assistant', content: '收到' }); + expect(get).toHaveBeenCalledWith('/agent-sessions/workspace', { params: { agentId: '1001', sessionId: 'session-id' } }); }); }); diff --git a/frontend/src/api/agent.ts b/frontend/src/api/agent.ts index 23ec587..78f60de 100644 --- a/frontend/src/api/agent.ts +++ b/frontend/src/api/agent.ts @@ -40,11 +40,53 @@ export interface AgentChatResponse { agentName: string; storeId: string; storeName?: string; + sessionId?: string; + sessionCode?: string; answer: string; modelRequestId: string; references: AgentReferenceChunk[]; } +export interface AgentSession { + id?: string; + agentId: string; + sessionCode: string; + workflowRunId?: string; + title?: string; + status?: string; + metadataJson?: string; + remark?: string; +} + +export interface AgentMessageRecord { + id?: string; + sessionId: string; + role: 'system' | 'user' | 'assistant'; + content: string; + citationJson?: string; + tokenCount?: number; + requestId?: string; + remark?: string; +} + +export interface AgentWorkspace { + agentId: string; + agentCode: string; + agentName: string; + storeId: string; + status: string; + sessionId?: string; + sessionCode?: string; + sessionTitle?: string; + sessionStatus?: string; + workflowRunId?: string; + totalTokens?: number; + citationCount?: number; + latestRequestId?: string; + sessions: AgentSession[]; + messages: AgentMessageRecord[]; +} + export function listAgents() { return post('/agents/list'); } @@ -68,3 +110,28 @@ export function deleteAgent(id: string) { export function chatWithAgent(agentId: string, data: AgentChatRequest) { return post(`/agents/${agentId}/chat`, data); } + +export function createAgentSession(data: AgentSession) { + return post('/agent-sessions/create', data); +} + +export function getAgentSessionById(id: string) { + return get('/agent-sessions/detail', { params: { id } }); +} + +export function listAgentMessages(sessionId: string) { + return get(`/agent-sessions/${sessionId}/messages`); +} + +export function appendAgentMessage(sessionId: string, data: Omit) { + return post>(`/agent-sessions/${sessionId}/messages`, data); +} + +export function getAgentWorkspace(agentId: string, sessionId?: string) { + return get('/agent-sessions/workspace', { + params: { + agentId, + sessionId, + }, + }); +}