feat: 全新智能体功能
- 基于先进智能体框架,增加智能体编排功能 - 增加智能体聊天,并对接持久化
This commit is contained in:
@@ -20,6 +20,10 @@
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-module-ai</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-module-agent</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-module-chatlog</artifactId>
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package tech.easyflow.admin.controller.agent;
|
||||
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import tech.easyflow.agent.entity.Agent;
|
||||
import tech.easyflow.agent.entity.AgentCategory;
|
||||
import tech.easyflow.agent.mapper.AgentMapper;
|
||||
import tech.easyflow.agent.service.AgentCategoryService;
|
||||
import tech.easyflow.common.annotation.UsePermission;
|
||||
import tech.easyflow.common.domain.Result;
|
||||
import tech.easyflow.common.web.controller.BaseCurdController;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot;
|
||||
import tech.easyflow.system.enums.CategoryResourceType;
|
||||
import tech.easyflow.system.service.CategoryPermissionService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Agent 分类管理控制器。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/agentCategory")
|
||||
@UsePermission(moduleName = "/api/v1/agent")
|
||||
public class AgentCategoryController extends BaseCurdController<AgentCategoryService, AgentCategory> {
|
||||
|
||||
@Resource
|
||||
private AgentMapper agentMapper;
|
||||
@Resource
|
||||
private CategoryPermissionService categoryPermissionService;
|
||||
|
||||
/**
|
||||
* 创建 Agent 分类管理控制器。
|
||||
*
|
||||
* @param service Agent 分类服务
|
||||
*/
|
||||
public AgentCategoryController(AgentCategoryService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户可见的 Agent 分类。
|
||||
*
|
||||
* @param entity 查询条件
|
||||
* @param asTree 是否转树
|
||||
* @param sortKey 排序字段
|
||||
* @param sortType 排序方式
|
||||
* @return 可见分类列表
|
||||
*/
|
||||
@GetMapping("visibleList")
|
||||
public Result<List<AgentCategory>> visibleList(AgentCategory entity, Boolean asTree, String sortKey, String sortType) {
|
||||
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
|
||||
RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess(CategoryResourceType.AGENT.getCode());
|
||||
if (access.isRestricted()) {
|
||||
if (access.getCategoryIds().isEmpty()) {
|
||||
return Result.ok(Collections.emptyList());
|
||||
}
|
||||
queryWrapper.in("id", access.getCategoryIds());
|
||||
}
|
||||
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
|
||||
return Result.ok(service.list(queryWrapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分类前校验是否仍被 Agent 使用。
|
||||
*
|
||||
* @param ids 分类 ID 集合
|
||||
* @return 校验结果
|
||||
*/
|
||||
@Override
|
||||
protected Result<?> onRemoveBefore(Collection<Serializable> ids) {
|
||||
for (Serializable id : ids) {
|
||||
QueryWrapper queryWrapper = QueryWrapper.create().eq(Agent::getCategoryId, id);
|
||||
List<Agent> agents = agentMapper.selectListByQuery(queryWrapper);
|
||||
if (!agents.isEmpty()) {
|
||||
throw new BusinessException("请先删除该分类下的所有 Agent");
|
||||
}
|
||||
}
|
||||
return super.onRemoveBefore(ids);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
package tech.easyflow.admin.controller.agent;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import tech.easyflow.admin.controller.ai.support.AiResourceCreatorNameSupport;
|
||||
import tech.easyflow.agent.entity.Agent;
|
||||
import tech.easyflow.agent.entity.AgentKnowledgeBinding;
|
||||
import tech.easyflow.agent.entity.AgentToolBinding;
|
||||
import tech.easyflow.agent.publish.AgentPublishAppService;
|
||||
import tech.easyflow.agent.runtime.AgentChatRequest;
|
||||
import tech.easyflow.agent.runtime.AgentDraftChatRequest;
|
||||
import tech.easyflow.agent.runtime.AgentRunService;
|
||||
import tech.easyflow.agent.service.AgentApprovalStateService;
|
||||
import tech.easyflow.agent.service.AgentKnowledgeBindingService;
|
||||
import tech.easyflow.agent.service.AgentService;
|
||||
import tech.easyflow.agent.service.AgentToolBindingService;
|
||||
import tech.easyflow.ai.enums.PublishStatus;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalActionResult;
|
||||
import tech.easyflow.common.domain.Result;
|
||||
import tech.easyflow.common.web.controller.BaseCurdController;
|
||||
import tech.easyflow.common.web.jsonbody.JsonBody;
|
||||
import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot;
|
||||
import tech.easyflow.system.enums.CategoryResourceType;
|
||||
import tech.easyflow.system.enums.ResourceAction;
|
||||
import tech.easyflow.system.service.CategoryPermissionService;
|
||||
import tech.easyflow.system.service.ResourceAccessService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static tech.easyflow.agent.entity.table.AgentTableDef.AGENT;
|
||||
|
||||
/**
|
||||
* Agent 管理端控制器。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/agent")
|
||||
public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
|
||||
@Resource
|
||||
private AgentToolBindingService agentToolBindingService;
|
||||
@Resource
|
||||
private AgentKnowledgeBindingService agentKnowledgeBindingService;
|
||||
@Resource
|
||||
private AgentRunService agentRunService;
|
||||
@Resource
|
||||
private AgentPublishAppService agentPublishAppService;
|
||||
@Resource
|
||||
private ResourceAccessService resourceAccessService;
|
||||
@Resource
|
||||
private CategoryPermissionService categoryPermissionService;
|
||||
@Resource
|
||||
private AgentApprovalStateService agentApprovalStateService;
|
||||
@Resource
|
||||
private AiResourceCreatorNameSupport aiResourceCreatorNameSupport;
|
||||
|
||||
/**
|
||||
* 创建 Agent 控制器。
|
||||
*
|
||||
* @param service Agent 服务
|
||||
*/
|
||||
public AgentController(AgentService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Agent 详情。
|
||||
*
|
||||
* @param id Agent ID
|
||||
* @return Agent 详情
|
||||
*/
|
||||
@GetMapping("/getDetail")
|
||||
public Result<Agent> getDetail(BigInteger id) {
|
||||
Agent agent = service.getDetail(id);
|
||||
agentApprovalStateService.fillAgentApprovalState(agent);
|
||||
return Result.ok(agent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存 Agent 草稿。
|
||||
*
|
||||
* @param agent Agent 草稿
|
||||
* @return Agent 详情
|
||||
*/
|
||||
@Override
|
||||
@PostMapping("save")
|
||||
public Result<?> save(@JsonBody Agent agent) {
|
||||
return Result.ok(service.saveDraft(agent));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Agent 草稿。
|
||||
*
|
||||
* @param agent Agent 草稿
|
||||
* @return Agent 详情
|
||||
*/
|
||||
@Override
|
||||
@PostMapping("update")
|
||||
public Result<?> update(@JsonBody Agent agent) {
|
||||
return Result.ok(service.updateDraft(agent));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 Agent 列表。
|
||||
*
|
||||
* @param entity 查询条件
|
||||
* @param asTree 是否转树
|
||||
* @param sortKey 排序字段
|
||||
* @param sortType 排序方式
|
||||
* @return Agent 列表
|
||||
*/
|
||||
@Override
|
||||
public Result<List<Agent>> list(Agent entity, Boolean asTree, String sortKey, String sortType) {
|
||||
HttpServletRequest request = currentRequest();
|
||||
QueryWrapper queryWrapper = request == null ? QueryWrapper.create() : buildQueryWrapper(request);
|
||||
if (!applyCategoryPermission(queryWrapper)) {
|
||||
return Result.ok(Collections.emptyList());
|
||||
}
|
||||
applyPublishedOnlyFilter(queryWrapper);
|
||||
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
|
||||
List<Agent> agents = service.list(queryWrapper);
|
||||
if (isPublishedOnlyRequest()) {
|
||||
agents = agents.stream().map(agent -> service.fromSnapshot(agent.getPublishedSnapshotJson())).toList();
|
||||
}
|
||||
agentApprovalStateService.fillAgentApprovalState(agents);
|
||||
aiResourceCreatorNameSupport.fillAgentCreatorNames(agents);
|
||||
return Result.ok(agents);
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行 Agent 纯文本聊天。
|
||||
*
|
||||
* @param request 聊天请求
|
||||
* @return SSE Emitter
|
||||
*/
|
||||
@PostMapping("chat")
|
||||
public SseEmitter chat(@JsonBody AgentChatRequest request) {
|
||||
return agentRunService.chat(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行 Agent 草稿态纯文本试用。
|
||||
*
|
||||
* @param request 草稿试用请求
|
||||
* @return SSE Emitter
|
||||
*/
|
||||
@PostMapping("/chat/draft")
|
||||
public SseEmitter chatDraft(@JsonBody AgentDraftChatRequest request) {
|
||||
return agentRunService.chatDraft(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理 Agent 草稿试运行会话。
|
||||
*
|
||||
* @param sessionId 草稿试运行会话 ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/chat/draft/clear")
|
||||
public Result<Void> clearDraftSession(@JsonBody(value = "sessionId", required = true) String sessionId) {
|
||||
agentRunService.clearDraftSession(sessionId);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批准工具执行。
|
||||
*
|
||||
* @param requestId 请求 ID
|
||||
* @param resumeToken 恢复令牌
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/run/approve")
|
||||
public Result<Void> approve(@JsonBody("requestId") String requestId,
|
||||
@JsonBody(value = "resumeToken", required = true) String resumeToken) {
|
||||
agentRunService.approve(requestId, resumeToken);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 拒绝工具执行。
|
||||
*
|
||||
* @param requestId 请求 ID
|
||||
* @param resumeToken 恢复令牌
|
||||
* @param reason 拒绝原因
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/run/reject")
|
||||
public Result<Void> reject(@JsonBody("requestId") String requestId,
|
||||
@JsonBody(value = "resumeToken", required = true) String resumeToken,
|
||||
@JsonBody("reason") String reason) {
|
||||
agentRunService.reject(requestId, resumeToken, reason);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Agent 工具绑定。
|
||||
*
|
||||
* @param agentId Agent ID
|
||||
* @param bindings 工具绑定
|
||||
* @return 保存后的启用绑定
|
||||
*/
|
||||
@PostMapping("/toolBinding/update")
|
||||
@SaCheckPermission("/api/v1/agent/save")
|
||||
public Result<List<AgentToolBinding>> updateToolBinding(@JsonBody(value = "agentId", required = true) BigInteger agentId,
|
||||
@JsonBody("bindings") List<AgentToolBinding> bindings) {
|
||||
return Result.ok(agentToolBindingService.replaceBindings(agentId, bindings));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Agent 知识库绑定。
|
||||
*
|
||||
* @param agentId Agent ID
|
||||
* @param bindings 知识库绑定
|
||||
* @return 保存后的启用绑定
|
||||
*/
|
||||
@PostMapping("/knowledgeBinding/update")
|
||||
@SaCheckPermission("/api/v1/agent/save")
|
||||
public Result<List<AgentKnowledgeBinding>> updateKnowledgeBinding(@JsonBody(value = "agentId", required = true) BigInteger agentId,
|
||||
@JsonBody("bindings") List<AgentKnowledgeBinding> bindings) {
|
||||
return Result.ok(agentKnowledgeBindingService.replaceBindings(agentId, bindings));
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交发布审批。
|
||||
*
|
||||
* @param id Agent ID
|
||||
* @return 审批实例 ID
|
||||
*/
|
||||
@PostMapping("/submitPublishApproval")
|
||||
@SaCheckPermission("/api/v1/agent/save")
|
||||
public Result<BigInteger> submitPublishApproval(@JsonBody("id") BigInteger id) {
|
||||
return buildApprovalActionResult(agentPublishAppService.submitPublishApproval(id), "已提交发布审批", "已直接发布");
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交下线审批。
|
||||
*
|
||||
* @param id Agent ID
|
||||
* @return 审批实例 ID
|
||||
*/
|
||||
@PostMapping("/submitOfflineApproval")
|
||||
@SaCheckPermission("/api/v1/agent/save")
|
||||
public Result<BigInteger> submitOfflineApproval(@JsonBody("id") BigInteger id) {
|
||||
return buildApprovalActionResult(agentPublishAppService.submitOfflineApproval(id), "已提交下线审批", "已直接下线");
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交删除审批。
|
||||
*
|
||||
* @param id Agent ID
|
||||
* @return 审批实例 ID
|
||||
*/
|
||||
@PostMapping("/submitDeleteApproval")
|
||||
@SaCheckPermission("/api/v1/agent/remove")
|
||||
public Result<BigInteger> submitDeleteApproval(@JsonBody("id") BigInteger id) {
|
||||
return buildApprovalActionResult(agentPublishAppService.submitDeleteApproval(id), "已提交删除审批", "已直接删除");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<?> onRemoveBefore(Collection<Serializable> ids) {
|
||||
for (Serializable id : ids) {
|
||||
Agent agent = service.getById(String.valueOf(id));
|
||||
if (agent != null) {
|
||||
resourceAccessService.assertAccess(CategoryResourceType.AGENT, agent, ResourceAction.MANAGE, "无权限删除该 Agent");
|
||||
}
|
||||
}
|
||||
agentToolBindingService.remove(QueryWrapper.create().in("agent_id", ids));
|
||||
agentKnowledgeBindingService.remove(QueryWrapper.create().in("agent_id", ids));
|
||||
return super.onRemoveBefore(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 Agent 分页。
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param queryWrapper 查询条件
|
||||
* @return Agent 分页
|
||||
*/
|
||||
@Override
|
||||
protected Page<Agent> queryPage(Page<Agent> page, QueryWrapper queryWrapper) {
|
||||
if (!applyCategoryPermission(queryWrapper)) {
|
||||
return new Page<>(Collections.emptyList(), page.getPageNumber(), page.getPageSize(), 0L);
|
||||
}
|
||||
applyPublishedOnlyFilter(queryWrapper);
|
||||
Page<Agent> result = super.queryPage(page, queryWrapper);
|
||||
if (isPublishedOnlyRequest()) {
|
||||
result.setRecords(result.getRecords().stream().map(agent -> service.fromSnapshot(agent.getPublishedSnapshotJson())).toList());
|
||||
}
|
||||
agentApprovalStateService.fillAgentApprovalState(result.getRecords());
|
||||
aiResourceCreatorNameSupport.fillAgentCreatorNames(result.getRecords());
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean applyCategoryPermission(QueryWrapper queryWrapper) {
|
||||
RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess(CategoryResourceType.AGENT.getCode());
|
||||
if (!access.isRestricted()) {
|
||||
return true;
|
||||
}
|
||||
if (access.getCategoryIds().isEmpty()) {
|
||||
queryWrapper.eq(Agent::getCreatedBy, access.getAccountId());
|
||||
return true;
|
||||
}
|
||||
queryWrapper.and(AGENT.CREATED_BY.eq(access.getAccountId()).or(AGENT.CATEGORY_ID.in(access.getCategoryIds())));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void applyPublishedOnlyFilter(QueryWrapper queryWrapper) {
|
||||
if (isPublishedOnlyRequest()) {
|
||||
queryWrapper.eq("publish_status", PublishStatus.PUBLISHED.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPublishedOnlyRequest() {
|
||||
HttpServletRequest request = currentRequest();
|
||||
if (request == null) {
|
||||
return false;
|
||||
}
|
||||
return "true".equalsIgnoreCase(request.getParameter("publishedOnly"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 HTTP 请求。
|
||||
*
|
||||
* @return 当前请求,不在 Web 请求上下文中时返回 null
|
||||
*/
|
||||
private HttpServletRequest currentRequest() {
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attributes == null) {
|
||||
return null;
|
||||
}
|
||||
return attributes.getRequest();
|
||||
}
|
||||
|
||||
private Result<BigInteger> buildApprovalActionResult(ApprovalActionResult actionResult,
|
||||
String approvalMessage,
|
||||
String directMessage) {
|
||||
return Result.ok(actionResult.isApprovalRequired() ? approvalMessage : directMessage, actionResult.getInstanceId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package tech.easyflow.admin.controller.agent;
|
||||
|
||||
import com.mybatisflex.core.keygen.impl.SnowFlakeIDKeyGenerator;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import tech.easyflow.admin.dto.chatworkspace.ChatWorkspaceConversationView;
|
||||
import tech.easyflow.admin.dto.chatworkspace.ChatWorkspaceSessionDetailView;
|
||||
import tech.easyflow.admin.dto.chatworkspace.ChatWorkspaceSessionPage;
|
||||
import tech.easyflow.admin.service.agent.AgentSessionService;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatHistoryPage;
|
||||
import tech.easyflow.chatlog.domain.query.ChatPageQuery;
|
||||
import tech.easyflow.common.domain.Result;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||
import tech.easyflow.common.web.jsonbody.JsonBody;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Agent 管理端会话控制器。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/agent/session")
|
||||
public class AgentSessionController {
|
||||
|
||||
private final AgentSessionService agentSessionService;
|
||||
|
||||
/**
|
||||
* 创建 Agent 管理端会话控制器。
|
||||
*
|
||||
* @param agentSessionService Agent 会话服务
|
||||
*/
|
||||
public AgentSessionController(AgentSessionService agentSessionService) {
|
||||
this.agentSessionService = agentSessionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 Agent 会话 ID。
|
||||
*
|
||||
* @return 会话 ID 字符串
|
||||
*/
|
||||
@GetMapping("/generateId")
|
||||
public Result<String> generateId() {
|
||||
long nextId = new SnowFlakeIDKeyGenerator().nextId();
|
||||
return Result.ok(String.valueOf(nextId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 Agent 会话分页。
|
||||
*
|
||||
* @param agentId Agent ID,可为空
|
||||
* @param query 分页参数
|
||||
* @return 会话分页
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public Result<ChatWorkspaceSessionPage> list(BigInteger agentId, ChatPageQuery query) {
|
||||
return Result.ok(agentSessionService.queryCurrentUserSessions(currentAccount(), agentId, query));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 Agent 会话详情。
|
||||
*
|
||||
* @param sessionId 会话 ID
|
||||
* @return 会话详情
|
||||
*/
|
||||
@GetMapping("/{sessionId}")
|
||||
public Result<ChatWorkspaceSessionDetailView> detail(@PathVariable BigInteger sessionId) {
|
||||
return Result.ok(agentSessionService.getCurrentUserSession(currentAccount(), sessionId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 Agent 会话消息。
|
||||
*
|
||||
* @param sessionId 会话 ID
|
||||
* @param query 分页参数
|
||||
* @return 消息分页
|
||||
*/
|
||||
@GetMapping("/{sessionId}/messages")
|
||||
public Result<ChatHistoryPage> messages(@PathVariable BigInteger sessionId, ChatPageQuery query) {
|
||||
return Result.ok(agentSessionService.queryCurrentUserMessages(currentAccount(), sessionId, query));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 Agent 完整会话。
|
||||
*
|
||||
* @param sessionId 会话 ID
|
||||
* @return 完整会话
|
||||
*/
|
||||
@GetMapping("/{sessionId}/conversation")
|
||||
public Result<ChatWorkspaceConversationView> conversation(@PathVariable BigInteger sessionId) {
|
||||
return Result.ok(agentSessionService.getCurrentUserConversation(currentAccount(), sessionId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 重命名 Agent 会话。
|
||||
*
|
||||
* @param sessionId 会话 ID
|
||||
* @param title 新标题
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/{sessionId}/rename")
|
||||
public Result<Void> rename(@PathVariable BigInteger sessionId,
|
||||
@JsonBody(value = "title", required = true) String title) {
|
||||
agentSessionService.renameCurrentUserSession(currentAccount(), sessionId, title);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 Agent 会话。
|
||||
*
|
||||
* @param sessionId 会话 ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/{sessionId}/delete")
|
||||
public Result<Void> delete(@PathVariable BigInteger sessionId) {
|
||||
agentSessionService.deleteCurrentUserSession(currentAccount(), sessionId);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
private LoginAccount currentAccount() {
|
||||
return SaTokenUtil.getLoginAccount();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package tech.easyflow.admin.controller.ai.support;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.easyflow.agent.entity.Agent;
|
||||
import tech.easyflow.ai.entity.Bot;
|
||||
import tech.easyflow.ai.entity.DocumentCollection;
|
||||
import tech.easyflow.ai.entity.Plugin;
|
||||
@@ -66,6 +67,15 @@ public class AiResourceCreatorNameSupport {
|
||||
fillCreatorNames(plugins, Plugin::getCreatedBy, Plugin::setCreatedByName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量填充 Agent 创建人名称。
|
||||
*
|
||||
* @param agents Agent 集合
|
||||
*/
|
||||
public void fillAgentCreatorNames(Collection<Agent> agents) {
|
||||
fillCreatorNames(agents, Agent::getCreatedBy, Agent::setCreatedByName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用的创建人名称填充逻辑。
|
||||
*
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
package tech.easyflow.admin.service.agent;
|
||||
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import tech.easyflow.admin.dto.chatworkspace.*;
|
||||
import tech.easyflow.agent.entity.Agent;
|
||||
import tech.easyflow.agent.runtime.AgentRuntimeStateCleanupService;
|
||||
import tech.easyflow.agent.service.AgentService;
|
||||
import tech.easyflow.ai.entity.DocumentCollection;
|
||||
import tech.easyflow.ai.enums.PublishStatus;
|
||||
import tech.easyflow.ai.service.DocumentCollectionService;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatHistoryPage;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatMessageRecord;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatSessionPage;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatSessionSummary;
|
||||
import tech.easyflow.chatlog.domain.query.ChatPageQuery;
|
||||
import tech.easyflow.chatlog.service.ChatSessionCommandService;
|
||||
import tech.easyflow.chatlog.service.ChatSessionQueryService;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.enums.CategoryResourceType;
|
||||
import tech.easyflow.system.enums.ResourceAction;
|
||||
import tech.easyflow.system.service.ResourceAccessService;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Agent 管理端会话服务。
|
||||
*/
|
||||
@Service
|
||||
public class AgentSessionService {
|
||||
|
||||
private static final String ASSISTANT_CODE = "AGENT";
|
||||
|
||||
private final ChatSessionQueryService chatSessionQueryService;
|
||||
private final ChatSessionCommandService chatSessionCommandService;
|
||||
private final AgentService agentService;
|
||||
private final DocumentCollectionService documentCollectionService;
|
||||
private final ResourceAccessService resourceAccessService;
|
||||
private final AgentRuntimeStateCleanupService agentRuntimeStateCleanupService;
|
||||
|
||||
/**
|
||||
* 创建 Agent 管理端会话服务。
|
||||
*
|
||||
* @param chatSessionQueryService 聊天会话查询服务
|
||||
* @param chatSessionCommandService 聊天会话命令服务
|
||||
* @param agentService Agent 服务
|
||||
* @param documentCollectionService 知识库服务
|
||||
* @param resourceAccessService 资源访问服务
|
||||
* @param agentRuntimeStateCleanupService Agent 运行态清理服务
|
||||
*/
|
||||
public AgentSessionService(ChatSessionQueryService chatSessionQueryService,
|
||||
ChatSessionCommandService chatSessionCommandService,
|
||||
AgentService agentService,
|
||||
DocumentCollectionService documentCollectionService,
|
||||
ResourceAccessService resourceAccessService,
|
||||
AgentRuntimeStateCleanupService agentRuntimeStateCleanupService) {
|
||||
this.chatSessionQueryService = chatSessionQueryService;
|
||||
this.chatSessionCommandService = chatSessionCommandService;
|
||||
this.agentService = agentService;
|
||||
this.documentCollectionService = documentCollectionService;
|
||||
this.resourceAccessService = resourceAccessService;
|
||||
this.agentRuntimeStateCleanupService = agentRuntimeStateCleanupService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户的 Agent 会话分页。
|
||||
*
|
||||
* @param account 当前登录账号
|
||||
* @param agentId Agent ID
|
||||
* @param query 分页参数
|
||||
* @return Agent 会话分页
|
||||
*/
|
||||
public ChatWorkspaceSessionPage queryCurrentUserSessions(LoginAccount account, BigInteger agentId, ChatPageQuery query) {
|
||||
ChatSessionPage page = chatSessionQueryService.pageSessions(account.getId(), agentId, ASSISTANT_CODE, query);
|
||||
Map<BigInteger, AgentAvailability> availabilityMap = resolveAgentAvailability(page.getRecords());
|
||||
ChatWorkspaceSessionPage result = new ChatWorkspaceSessionPage();
|
||||
result.setTotal(page.getTotal());
|
||||
result.setPageNumber(page.getPageNumber());
|
||||
result.setPageSize(page.getPageSize());
|
||||
List<ChatWorkspaceSessionView> records = new ArrayList<>();
|
||||
for (ChatSessionSummary summary : page.getRecords()) {
|
||||
records.add(toSessionView(summary, availabilityMap.get(summary.getAssistantId())));
|
||||
}
|
||||
result.setRecords(records);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户的 Agent 会话详情。
|
||||
*
|
||||
* @param account 当前登录账号
|
||||
* @param sessionId 会话 ID
|
||||
* @return Agent 会话详情
|
||||
*/
|
||||
public ChatWorkspaceSessionDetailView getCurrentUserSession(LoginAccount account, BigInteger sessionId) {
|
||||
ChatSessionSummary summary = requireUserAgentSession(account, sessionId);
|
||||
AgentAvailability availability = resolveAgentAvailability(List.of(summary)).get(summary.getAssistantId());
|
||||
ChatWorkspaceSessionDetailView detail = new ChatWorkspaceSessionDetailView();
|
||||
fillSessionView(detail, summary, availability);
|
||||
Agent displayAgent = availability == null ? null : availability.displayAgent();
|
||||
detail.setAssistant(toAssistantView(displayAgent, summary));
|
||||
detail.setBoundKnowledges(resolveBoundKnowledges(displayAgent));
|
||||
return detail;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户的 Agent 会话消息。
|
||||
*
|
||||
* @param account 当前登录账号
|
||||
* @param sessionId 会话 ID
|
||||
* @param query 分页参数
|
||||
* @return 消息分页
|
||||
*/
|
||||
public ChatHistoryPage queryCurrentUserMessages(LoginAccount account, BigInteger sessionId, ChatPageQuery query) {
|
||||
requireUserAgentSession(account, sessionId);
|
||||
return chatSessionQueryService.pageMainlineMessages(sessionId, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户的 Agent 完整会话。
|
||||
*
|
||||
* @param account 当前登录账号
|
||||
* @param sessionId 会话 ID
|
||||
* @return 完整会话
|
||||
*/
|
||||
public ChatWorkspaceConversationView getCurrentUserConversation(LoginAccount account, BigInteger sessionId) {
|
||||
requireUserAgentSession(account, sessionId);
|
||||
List<ChatMessageRecord> records = chatSessionQueryService.listMainlineMessages(sessionId);
|
||||
ChatWorkspaceConversationView view = new ChatWorkspaceConversationView();
|
||||
view.setRecords(records);
|
||||
view.setTotal(records.size());
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重命名当前用户的 Agent 会话。
|
||||
*
|
||||
* @param account 当前登录账号
|
||||
* @param sessionId 会话 ID
|
||||
* @param title 新标题
|
||||
*/
|
||||
public void renameCurrentUserSession(LoginAccount account, BigInteger sessionId, String title) {
|
||||
if (!StringUtils.hasText(title)) {
|
||||
throw new BusinessException("标题不能为空");
|
||||
}
|
||||
requireUserAgentSession(account, sessionId);
|
||||
chatSessionCommandService.renameSession(sessionId, account.getId(), title.trim(), account.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除当前用户的 Agent 会话。
|
||||
*
|
||||
* @param account 当前登录账号
|
||||
* @param sessionId 会话 ID
|
||||
*/
|
||||
public void deleteCurrentUserSession(LoginAccount account, BigInteger sessionId) {
|
||||
requireUserAgentSession(account, sessionId);
|
||||
agentRuntimeStateCleanupService.clearChatSession(sessionId, account.getId());
|
||||
chatSessionCommandService.deleteSession(sessionId, account.getId(), account.getId());
|
||||
}
|
||||
|
||||
private ChatSessionSummary requireUserAgentSession(LoginAccount account, BigInteger sessionId) {
|
||||
ChatSessionSummary summary = chatSessionQueryService.getSessionSummary(sessionId);
|
||||
if (summary == null || Integer.valueOf(1).equals(summary.getIsDeleted())
|
||||
|| !ASSISTANT_CODE.equals(summary.getAssistantCode())) {
|
||||
throw new BusinessException("Agent 会话不存在");
|
||||
}
|
||||
if (!Objects.equals(summary.getUserId(), account.getId())) {
|
||||
throw new BusinessException("无权访问该 Agent 会话");
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
private Map<BigInteger, AgentAvailability> resolveAgentAvailability(List<ChatSessionSummary> sessions) {
|
||||
Map<BigInteger, AgentAvailability> result = new LinkedHashMap<>();
|
||||
if (sessions == null || sessions.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
Set<BigInteger> agentIds = new LinkedHashSet<>();
|
||||
for (ChatSessionSummary session : sessions) {
|
||||
if (session != null && session.getAssistantId() != null) {
|
||||
agentIds.add(session.getAssistantId());
|
||||
}
|
||||
}
|
||||
if (agentIds.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
List<Agent> agents = agentService.list(QueryWrapper.create().in("id", agentIds));
|
||||
Map<BigInteger, Agent> agentMap = new LinkedHashMap<>();
|
||||
for (Agent agent : agents) {
|
||||
agentMap.put(agent.getId(), agent);
|
||||
}
|
||||
for (BigInteger agentId : agentIds) {
|
||||
Agent currentAgent = agentMap.get(agentId);
|
||||
if (currentAgent == null) {
|
||||
result.put(agentId, new AgentAvailability(false, ChatWorkspaceReadOnlyReason.ASSISTANT_DELETED, null));
|
||||
continue;
|
||||
}
|
||||
if (!resourceAccessService.canAccess(CategoryResourceType.AGENT, currentAgent, ResourceAction.USE)) {
|
||||
result.put(agentId, new AgentAvailability(false, ChatWorkspaceReadOnlyReason.NO_PERMISSION, null));
|
||||
continue;
|
||||
}
|
||||
boolean online = Integer.valueOf(1).equals(currentAgent.getStatus())
|
||||
&& PublishStatus.from(currentAgent.getPublishStatus()) == PublishStatus.PUBLISHED;
|
||||
result.put(agentId, new AgentAvailability(
|
||||
online,
|
||||
online ? null : ChatWorkspaceReadOnlyReason.ASSISTANT_OFFLINE,
|
||||
toDisplayAgent(currentAgent)
|
||||
));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Agent toDisplayAgent(Agent currentAgent) {
|
||||
if (currentAgent.getPublishedSnapshotJson() != null && !currentAgent.getPublishedSnapshotJson().isEmpty()) {
|
||||
return agentService.fromSnapshot(currentAgent.getPublishedSnapshotJson());
|
||||
}
|
||||
return currentAgent;
|
||||
}
|
||||
|
||||
private ChatWorkspaceSessionView toSessionView(ChatSessionSummary summary, AgentAvailability availability) {
|
||||
ChatWorkspaceSessionView view = new ChatWorkspaceSessionView();
|
||||
fillSessionView(view, summary, availability);
|
||||
return view;
|
||||
}
|
||||
|
||||
private void fillSessionView(ChatWorkspaceSessionView view, ChatSessionSummary summary, AgentAvailability availability) {
|
||||
view.setSessionId(summary.getId());
|
||||
view.setAssistantId(summary.getAssistantId());
|
||||
view.setAssistantCode(summary.getAssistantCode());
|
||||
view.setAssistantName(summary.getAssistantName());
|
||||
view.setTitle(summary.getTitle());
|
||||
view.setLastMessagePreview(summary.getLastMessagePreview());
|
||||
view.setMessageCount(summary.getMessageCount());
|
||||
view.setAccessAt(summary.getAccessAt());
|
||||
view.setLastMessageAt(summary.getLastMessageAt());
|
||||
view.setContinuable(availability != null && availability.continuable());
|
||||
view.setReadOnlyReason(availability == null ? ChatWorkspaceReadOnlyReason.ASSISTANT_DELETED : availability.reason());
|
||||
}
|
||||
|
||||
private ChatWorkspaceAssistantView toAssistantView(Agent agent, ChatSessionSummary summary) {
|
||||
ChatWorkspaceAssistantView view = new ChatWorkspaceAssistantView();
|
||||
if (agent != null) {
|
||||
view.setId(agent.getId());
|
||||
view.setAlias(agent.getId() == null ? null : agent.getId().toString());
|
||||
view.setTitle(agent.getName());
|
||||
view.setDescription(agent.getDescription());
|
||||
view.setIcon(agent.getAvatar());
|
||||
return view;
|
||||
}
|
||||
view.setId(summary == null ? null : summary.getAssistantId());
|
||||
view.setAlias(summary == null ? null : summary.getAssistantCode());
|
||||
view.setTitle(summary == null ? null : summary.getAssistantName());
|
||||
return view;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<ChatWorkspaceKnowledgeView> resolveBoundKnowledges(Agent displayAgent) {
|
||||
if (displayAgent == null || displayAgent.getKnowledgeBindings() == null || displayAgent.getKnowledgeBindings().isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<BigInteger> knowledgeIds = displayAgent.getKnowledgeBindings().stream()
|
||||
.map(binding -> binding.getKnowledgeId())
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
if (knowledgeIds.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<DocumentCollection> collections = documentCollectionService.listByIds(knowledgeIds);
|
||||
Map<BigInteger, DocumentCollection> collectionMap = new LinkedHashMap<>();
|
||||
for (DocumentCollection collection : collections) {
|
||||
collectionMap.put(collection.getId(), collection);
|
||||
}
|
||||
List<ChatWorkspaceKnowledgeView> views = new ArrayList<>();
|
||||
for (BigInteger knowledgeId : knowledgeIds) {
|
||||
DocumentCollection collection = collectionMap.get(knowledgeId);
|
||||
if (collection == null || PublishStatus.from(collection.getPublishStatus()) != PublishStatus.PUBLISHED) {
|
||||
continue;
|
||||
}
|
||||
views.add(toKnowledgeView(documentCollectionService.toPublishedView(collection)));
|
||||
}
|
||||
return views;
|
||||
}
|
||||
|
||||
private ChatWorkspaceKnowledgeView toKnowledgeView(DocumentCollection collection) {
|
||||
ChatWorkspaceKnowledgeView view = new ChatWorkspaceKnowledgeView();
|
||||
view.setId(collection.getId());
|
||||
view.setAlias(collection.getAlias());
|
||||
view.setTitle(collection.getTitle());
|
||||
view.setDescription(collection.getDescription());
|
||||
view.setIcon(collection.getIcon());
|
||||
return view;
|
||||
}
|
||||
|
||||
private record AgentAvailability(boolean continuable,
|
||||
ChatWorkspaceReadOnlyReason reason,
|
||||
Agent displayAgent) {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user