feat: 全新智能体功能

- 基于先进智能体框架,增加智能体编排功能
- 增加智能体聊天,并对接持久化
This commit is contained in:
2026-05-25 11:42:48 +08:00
parent 6c3d98eaac
commit 72df00f25b
168 changed files with 22045 additions and 400 deletions

View File

@@ -0,0 +1,25 @@
package tech.easyflow.agent.service;
import tech.easyflow.agent.entity.Agent;
import java.util.Collection;
/**
* Agent 审批状态派生服务。
*/
public interface AgentApprovalStateService {
/**
* 填充单个 Agent 的审批展示状态。
*
* @param agent Agent 资源
*/
void fillAgentApprovalState(Agent agent);
/**
* 批量填充 Agent 的审批展示状态。
*
* @param agents Agent 资源集合
*/
void fillAgentApprovalState(Collection<Agent> agents);
}

View File

@@ -0,0 +1,10 @@
package tech.easyflow.agent.service;
import com.mybatisflex.core.service.IService;
import tech.easyflow.agent.entity.AgentCategory;
/**
* Agent 分类服务。
*/
public interface AgentCategoryService extends IService<AgentCategory> {
}

View File

@@ -0,0 +1,30 @@
package tech.easyflow.agent.service;
import com.mybatisflex.core.service.IService;
import tech.easyflow.agent.entity.AgentKnowledgeBinding;
import java.math.BigInteger;
import java.util.List;
/**
* Agent 知识库绑定服务。
*/
public interface AgentKnowledgeBindingService extends IService<AgentKnowledgeBinding> {
/**
* 替换 Agent 知识库绑定。
*
* @param agentId Agent ID
* @param bindings 新绑定列表
* @return 保存后的绑定列表
*/
List<AgentKnowledgeBinding> replaceBindings(BigInteger agentId, List<AgentKnowledgeBinding> bindings);
/**
* 查询 Agent 启用知识库绑定。
*
* @param agentId Agent ID
* @return 启用绑定列表
*/
List<AgentKnowledgeBinding> listEnabled(BigInteger agentId);
}

View File

@@ -0,0 +1,61 @@
package tech.easyflow.agent.service;
import com.mybatisflex.core.service.IService;
import tech.easyflow.agent.entity.Agent;
import java.math.BigInteger;
import java.util.Map;
/**
* Agent 配置服务。
*/
public interface AgentService extends IService<Agent> {
/**
* 获取 Agent 详情。
*
* @param id Agent ID
* @return Agent 详情
*/
Agent getDetail(BigInteger id);
/**
* 保存草稿 Agent。
*
* @param agent Agent 草稿
* @return 保存后的 Agent
*/
Agent saveDraft(Agent agent);
/**
* 更新草稿 Agent。
*
* @param agent Agent 草稿
* @return 更新后的 Agent
*/
Agent updateDraft(Agent agent);
/**
* 获取已发布运行视图。
*
* @param id Agent ID
* @return 已发布运行视图
*/
Agent getPublishedView(BigInteger id);
/**
* 构建发布快照。
*
* @param agent Agent 当前草稿
* @return 发布快照
*/
Map<String, Object> buildPublishSnapshot(Agent agent);
/**
* 从快照还原运行视图。
*
* @param snapshot 发布快照
* @return Agent 运行视图
*/
Agent fromSnapshot(Map<String, Object> snapshot);
}

View File

@@ -0,0 +1,30 @@
package tech.easyflow.agent.service;
import com.mybatisflex.core.service.IService;
import tech.easyflow.agent.entity.AgentToolBinding;
import java.math.BigInteger;
import java.util.List;
/**
* Agent 工具绑定服务。
*/
public interface AgentToolBindingService extends IService<AgentToolBinding> {
/**
* 替换 Agent 工具绑定。
*
* @param agentId Agent ID
* @param bindings 新绑定列表
* @return 保存后的绑定列表
*/
List<AgentToolBinding> replaceBindings(BigInteger agentId, List<AgentToolBinding> bindings);
/**
* 查询 Agent 启用工具绑定。
*
* @param agentId Agent ID
* @return 启用绑定列表
*/
List<AgentToolBinding> listEnabled(BigInteger agentId);
}

View File

@@ -0,0 +1,116 @@
package tech.easyflow.agent.service.impl;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.service.AgentApprovalStateService;
import tech.easyflow.ai.enums.PublishStatus;
import tech.easyflow.approval.entity.ApprovalInstance;
import tech.easyflow.approval.enums.ApprovalActionType;
import tech.easyflow.approval.enums.ApprovalInstanceStatus;
import tech.easyflow.approval.enums.ApprovalResourceType;
import tech.easyflow.approval.mapper.ApprovalInstanceMapper;
import java.math.BigInteger;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Agent 审批状态派生服务实现。
*/
@Service
public class AgentApprovalStateServiceImpl implements AgentApprovalStateService {
private final ApprovalInstanceMapper approvalInstanceMapper;
/**
* 创建 Agent 审批状态派生服务。
*
* @param approvalInstanceMapper 审批实例 Mapper
*/
public AgentApprovalStateServiceImpl(ApprovalInstanceMapper approvalInstanceMapper) {
this.approvalInstanceMapper = approvalInstanceMapper;
}
/**
* {@inheritDoc}
*/
@Override
public void fillAgentApprovalState(Agent agent) {
fillAgentApprovalState(agent == null ? List.of() : List.of(agent));
}
/**
* {@inheritDoc}
*/
@Override
public void fillAgentApprovalState(Collection<Agent> agents) {
if (CollectionUtils.isEmpty(agents)) {
return;
}
List<Agent> validAgents = agents.stream().filter(Objects::nonNull).toList();
if (validAgents.isEmpty()) {
return;
}
Map<BigInteger, ApprovalInstance> instanceMap = loadInstanceMap(validAgents, Agent::getCurrentApprovalInstanceId);
for (Agent agent : validAgents) {
fillOne(agent, instanceMap.get(agent.getCurrentApprovalInstanceId()));
}
}
private void fillOne(Agent agent, ApprovalInstance instance) {
PublishStatus currentStatus = PublishStatus.from(agent.getPublishStatus());
if (!isValidCurrentInstance(instance)) {
agent.setApprovalPending(false);
agent.setCurrentApprovalActionType(null);
agent.setDisplayPublishStatus(currentStatus.getCode());
return;
}
ApprovalInstanceStatus instanceStatus = ApprovalInstanceStatus.from(instance.getStatus());
if (instanceStatus.isFinished()) {
agent.setApprovalPending(false);
agent.setCurrentApprovalActionType(null);
agent.setDisplayPublishStatus(currentStatus.getCode());
return;
}
ApprovalActionType actionType = ApprovalActionType.from(instance.getActionType());
agent.setApprovalPending(true);
agent.setCurrentApprovalActionType(actionType.getCode());
agent.setDisplayPublishStatus(resolveDisplayStatusWithActiveInstance(currentStatus, actionType).getCode());
}
private Map<BigInteger, ApprovalInstance> loadInstanceMap(Collection<Agent> agents,
Function<Agent, BigInteger> instanceIdGetter) {
Set<BigInteger> instanceIds = agents.stream()
.map(instanceIdGetter)
.filter(Objects::nonNull)
.collect(Collectors.toCollection(LinkedHashSet::new));
if (instanceIds.isEmpty()) {
return Collections.emptyMap();
}
List<ApprovalInstance> instances = approvalInstanceMapper.selectListByQuery(
QueryWrapper.create().in(ApprovalInstance::getId, instanceIds)
);
return instances.stream().collect(Collectors.toMap(ApprovalInstance::getId, Function.identity()));
}
private boolean isValidCurrentInstance(ApprovalInstance instance) {
return instance != null && ApprovalResourceType.AGENT.getCode().equals(instance.getResourceType());
}
private PublishStatus resolveDisplayStatusWithActiveInstance(PublishStatus currentStatus,
ApprovalActionType actionType) {
if (currentStatus == PublishStatus.PUBLISH_PENDING
|| currentStatus == PublishStatus.OFFLINE_PENDING
|| currentStatus == PublishStatus.DELETE_PENDING) {
return currentStatus;
}
return switch (actionType) {
case PUBLISH -> PublishStatus.PUBLISH_PENDING;
case OFFLINE -> PublishStatus.OFFLINE_PENDING;
case DELETE -> PublishStatus.DELETE_PENDING;
};
}
}

View File

@@ -0,0 +1,14 @@
package tech.easyflow.agent.service.impl;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import tech.easyflow.agent.entity.AgentCategory;
import tech.easyflow.agent.mapper.AgentCategoryMapper;
import tech.easyflow.agent.service.AgentCategoryService;
/**
* Agent 分类服务实现。
*/
@Service
public class AgentCategoryServiceImpl extends ServiceImpl<AgentCategoryMapper, AgentCategory> implements AgentCategoryService {
}

View File

@@ -0,0 +1,123 @@
package tech.easyflow.agent.service.impl;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.entity.AgentKnowledgeBinding;
import tech.easyflow.agent.mapper.AgentKnowledgeBindingMapper;
import tech.easyflow.agent.mapper.AgentMapper;
import tech.easyflow.agent.service.AgentKnowledgeBindingService;
import tech.easyflow.ai.entity.DocumentCollection;
import tech.easyflow.ai.enums.PublishStatus;
import tech.easyflow.ai.rag.KnowledgeRetrievalModes;
import tech.easyflow.ai.service.DocumentCollectionService;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
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 javax.annotation.Resource;
import java.math.BigInteger;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
* Agent 知识库绑定服务实现。
*/
@Service
public class AgentKnowledgeBindingServiceImpl extends ServiceImpl<AgentKnowledgeBindingMapper, AgentKnowledgeBinding>
implements AgentKnowledgeBindingService {
private static final String DEFAULT_RETRIEVAL_MODE = "HYBRID";
@Resource
private AgentMapper agentMapper;
@Resource
private DocumentCollectionService documentCollectionService;
@Resource
private ResourceAccessService resourceAccessService;
/**
* {@inheritDoc}
*/
@Override
@Transactional(rollbackFor = Exception.class)
public List<AgentKnowledgeBinding> replaceBindings(BigInteger agentId, List<AgentKnowledgeBinding> bindings) {
Agent agent = requireAgent(agentId);
resourceAccessService.assertAccess(CategoryResourceType.AGENT, agent, ResourceAction.MANAGE, "无权限管理该 Agent");
remove(QueryWrapper.create().where("agent_id = ?", agentId));
if (bindings == null || bindings.isEmpty()) {
return Collections.emptyList();
}
for (int i = 0; i < bindings.size(); i++) {
AgentKnowledgeBinding binding = bindings.get(i);
validateBinding(binding);
applyBindingDefaults(agent, binding, i);
}
saveBatch(bindings);
return listEnabled(agentId);
}
/**
* {@inheritDoc}
*/
@Override
public List<AgentKnowledgeBinding> listEnabled(BigInteger agentId) {
return list(QueryWrapper.create()
.where("agent_id = ?", agentId)
.and("enabled = ?", true)
.orderBy("sort_no asc, id asc"));
}
private Agent requireAgent(BigInteger agentId) {
Agent agent = agentMapper.selectOneById(agentId);
if (agent == null) {
throw new BusinessException("Agent 不存在");
}
return agent;
}
private void validateBinding(AgentKnowledgeBinding binding) {
if (binding == null || binding.getKnowledgeId() == null) {
throw new BusinessException("知识库绑定参数不完整");
}
DocumentCollection knowledge = documentCollectionService.getById(binding.getKnowledgeId());
if (knowledge == null || PublishStatus.from(knowledge.getPublishStatus()) != PublishStatus.PUBLISHED) {
throw new BusinessException("绑定知识库不存在或未发布");
}
KnowledgeRetrievalModes.parse(binding.getRetrievalMode());
resourceAccessService.assertAccess(CategoryResourceType.KNOWLEDGE, knowledge, ResourceAction.USE, "无权限绑定该知识库");
}
private void applyBindingDefaults(Agent agent, AgentKnowledgeBinding binding, int index) {
LoginAccount account = requireCurrentLoginAccount();
Date now = new Date();
binding.setId(null);
binding.setTenantId(agent.getTenantId());
binding.setAgentId(agent.getId());
if (binding.getRetrievalMode() == null || binding.getRetrievalMode().isBlank()) {
binding.setRetrievalMode(DEFAULT_RETRIEVAL_MODE);
} else {
binding.setRetrievalMode(binding.getRetrievalMode().trim().toUpperCase());
}
binding.setEnabled(binding.getEnabled() == null || binding.getEnabled());
binding.setSortNo(binding.getSortNo() == null ? index : binding.getSortNo());
binding.setCreated(now);
binding.setCreatedBy(account.getId());
binding.setModified(now);
binding.setModifiedBy(account.getId());
}
private LoginAccount requireCurrentLoginAccount() {
try {
return SaTokenUtil.getLoginAccount();
} catch (Exception e) {
throw new BusinessException("当前登录状态失效,请重新登录后再试");
}
}
}

View File

@@ -0,0 +1,391 @@
package tech.easyflow.agent.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.entity.AgentKnowledgeBinding;
import tech.easyflow.agent.entity.AgentToolBinding;
import tech.easyflow.agent.mapper.AgentMapper;
import tech.easyflow.agent.service.AgentKnowledgeBindingService;
import tech.easyflow.agent.service.AgentService;
import tech.easyflow.agent.service.AgentToolBindingService;
import tech.easyflow.ai.entity.*;
import tech.easyflow.ai.enums.PublishStatus;
import tech.easyflow.ai.service.*;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.enums.CategoryResourceType;
import tech.easyflow.system.enums.ResourceAction;
import tech.easyflow.system.enums.VisibilityScope;
import tech.easyflow.system.service.ResourceAccessService;
import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Agent 配置服务实现。
*/
@Service
public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent> implements AgentService {
private static final TypeReference<List<AgentToolBinding>> TOOL_BINDING_LIST_TYPE = new TypeReference<>() {};
private static final TypeReference<List<AgentKnowledgeBinding>> KNOWLEDGE_BINDING_LIST_TYPE = new TypeReference<>() {};
@Resource
private AgentToolBindingService agentToolBindingService;
@Resource
private AgentKnowledgeBindingService agentKnowledgeBindingService;
@Resource
private ModelService modelService;
@Resource
private WorkflowService workflowService;
@Resource
private PluginItemService pluginItemService;
@Resource
private McpService mcpService;
@Resource
private DocumentCollectionService documentCollectionService;
@Resource
private ResourceAccessService resourceAccessService;
@Resource
private ObjectMapper objectMapper;
/**
* {@inheritDoc}
*/
@Override
public Agent getDetail(BigInteger id) {
Agent agent = requireAgent(id);
resourceAccessService.assertAccess(CategoryResourceType.AGENT, agent, ResourceAction.READ, "无权限查看该 Agent");
agent.setToolBindings(agentToolBindingService.listEnabled(id));
agent.setKnowledgeBindings(agentKnowledgeBindingService.listEnabled(id));
return agent;
}
/**
* {@inheritDoc}
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Agent saveDraft(Agent agent) {
validateDraft(agent);
applyDraftDefaults(agent);
save(agent);
return getDetail(agent.getId());
}
/**
* {@inheritDoc}
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Agent updateDraft(Agent agent) {
if (agent == null || agent.getId() == null) {
throw new BusinessException("Agent ID 不能为空");
}
Agent existing = requireAgent(agent.getId());
resourceAccessService.assertAccess(CategoryResourceType.AGENT, existing, ResourceAction.MANAGE, "无权限管理该 Agent");
validateDraft(agent);
applyDraftUpdate(existing, agent);
updateById(existing);
return getDetail(existing.getId());
}
/**
* {@inheritDoc}
*/
@Override
public Agent getPublishedView(BigInteger id) {
Agent agent = requireAgent(id);
PublishStatus status = PublishStatus.from(agent.getPublishStatus());
if (!status.isExternallyVisible() || agent.getPublishedSnapshotJson() == null || agent.getPublishedSnapshotJson().isEmpty()) {
throw new BusinessException("Agent 未发布");
}
return fromSnapshot(agent.getPublishedSnapshotJson());
}
/**
* {@inheritDoc}
*/
@Override
public Map<String, Object> buildPublishSnapshot(Agent agent) {
Agent detail = getDetail(agent.getId());
Map<String, Object> snapshot = new LinkedHashMap<>();
snapshot.put("id", detail.getId());
snapshot.put("tenantId", detail.getTenantId());
snapshot.put("deptId", detail.getDeptId());
snapshot.put("createdBy", detail.getCreatedBy());
snapshot.put("name", detail.getName());
snapshot.put("description", detail.getDescription());
snapshot.put("avatar", detail.getAvatar());
snapshot.put("categoryId", detail.getCategoryId());
snapshot.put("modelId", detail.getModelId());
snapshot.put("modelConfigJson", detail.getModelConfigJson());
snapshot.put("generationConfigJson", detail.getGenerationConfigJson());
snapshot.put("promptConfigJson", detail.getPromptConfigJson());
snapshot.put("memoryConfigJson", detail.getMemoryConfigJson());
snapshot.put("executionConfigJson", detail.getExecutionConfigJson());
snapshot.put("visibilityScope", detail.getVisibilityScope());
snapshot.put("toolBindings", snapshotToolBindings(detail.getToolBindings()));
snapshot.put("knowledgeBindings", snapshotKnowledgeBindings(detail.getKnowledgeBindings()));
snapshot.put("basicSummary", basicSummary(detail));
snapshot.put("modelSummary", modelSummary(detail.getModelId()));
snapshot.put("parameterSummary", parameterSummary(detail));
snapshot.put("promptSummary", promptSummary(detail));
snapshot.put("toolSummaries", toolSummaries(detail.getToolBindings()));
snapshot.put("knowledgeSummaries", knowledgeSummaries(detail.getKnowledgeBindings()));
snapshot.put("snapshotAt", new Date());
return snapshot;
}
/**
* {@inheritDoc}
*/
@Override
public Agent fromSnapshot(Map<String, Object> snapshot) {
if (snapshot == null || snapshot.isEmpty()) {
throw new BusinessException("Agent 发布快照为空");
}
Agent agent = objectMapper.convertValue(snapshot, Agent.class);
agent.setId(toBigInteger(snapshot.get("id")));
agent.setTenantId(toBigInteger(snapshot.get("tenantId")));
agent.setDeptId(toBigInteger(snapshot.get("deptId")));
agent.setCreatedBy(toBigInteger(snapshot.get("createdBy")));
agent.setModelId(toBigInteger(snapshot.get("modelId")));
agent.setCategoryId(toBigInteger(snapshot.get("categoryId")));
agent.setPublishStatus(PublishStatus.PUBLISHED.getCode());
agent.setPublishedSnapshotJson(snapshot);
agent.setToolBindings(objectMapper.convertValue(snapshot.get("toolBindings"), TOOL_BINDING_LIST_TYPE));
agent.setKnowledgeBindings(objectMapper.convertValue(snapshot.get("knowledgeBindings"), KNOWLEDGE_BINDING_LIST_TYPE));
return agent;
}
private Agent requireAgent(BigInteger id) {
Agent agent = getById(id);
if (agent == null) {
throw new BusinessException("Agent 不存在");
}
return agent;
}
private void validateDraft(Agent agent) {
if (agent == null) {
throw new BusinessException("Agent 不能为空");
}
if (agent.getName() == null || agent.getName().isBlank()) {
throw new BusinessException("Agent 名称不能为空");
}
if (agent.getModelId() == null) {
throw new BusinessException("Agent 模型不能为空");
}
Model model = modelService.getModelInstance(agent.getModelId());
if (model == null) {
throw new BusinessException("Agent 模型不存在");
}
agent.setVisibilityScope(VisibilityScope.fromOrDefault(agent.getVisibilityScope(), VisibilityScope.PRIVATE).name());
}
private void applyDraftDefaults(Agent agent) {
LoginAccount account = requireCurrentLoginAccount();
Date now = new Date();
agent.setTenantId(account.getTenantId());
agent.setDeptId(account.getDeptId());
agent.setCreated(now);
agent.setCreatedBy(account.getId());
agent.setModified(now);
agent.setModifiedBy(account.getId());
agent.setStatus(agent.getStatus() == null ? 1 : agent.getStatus());
agent.setPublishStatus(PublishStatus.DRAFT.getCode());
}
private Map<String, Object> basicSummary(Agent agent) {
Map<String, Object> summary = new LinkedHashMap<>();
summary.put("id", agent.getId());
summary.put("name", agent.getName());
summary.put("description", agent.getDescription());
summary.put("avatar", agent.getAvatar());
summary.put("categoryId", agent.getCategoryId());
summary.put("status", agent.getStatus());
summary.put("publishStatus", PublishStatus.PUBLISHED.getCode());
summary.put("visibilityScope", agent.getVisibilityScope());
return summary;
}
private void applyDraftUpdate(Agent existing, Agent incoming) {
LoginAccount account = requireCurrentLoginAccount();
existing.setName(incoming.getName());
existing.setDescription(incoming.getDescription());
existing.setAvatar(incoming.getAvatar());
existing.setCategoryId(incoming.getCategoryId());
existing.setModelId(incoming.getModelId());
existing.setModelConfigJson(incoming.getModelConfigJson());
existing.setGenerationConfigJson(incoming.getGenerationConfigJson());
existing.setPromptConfigJson(incoming.getPromptConfigJson());
existing.setMemoryConfigJson(incoming.getMemoryConfigJson());
existing.setExecutionConfigJson(incoming.getExecutionConfigJson());
existing.setStatus(incoming.getStatus() == null ? 1 : incoming.getStatus());
existing.setVisibilityScope(incoming.getVisibilityScope());
existing.setModified(new Date());
existing.setModifiedBy(account.getId());
}
private Map<String, Object> modelSummary(BigInteger modelId) {
Model model = modelService.getModelInstance(modelId);
Map<String, Object> summary = new LinkedHashMap<>();
summary.put("id", model.getId());
summary.put("title", model.getTitle());
summary.put("modelName", model.getModelName());
summary.put("providerType", model.getModelProvider() == null ? null : model.getModelProvider().getProviderType());
return summary;
}
private Map<String, Object> parameterSummary(Agent agent) {
Map<String, Object> summary = new LinkedHashMap<>();
summary.put("generationConfigJson", agent.getGenerationConfigJson());
summary.put("modelConfigJson", agent.getModelConfigJson());
summary.put("memoryConfigJson", agent.getMemoryConfigJson());
summary.put("executionConfigJson", agent.getExecutionConfigJson());
return summary;
}
private Map<String, Object> promptSummary(Agent agent) {
Map<String, Object> summary = new LinkedHashMap<>();
summary.put("promptConfigJson", agent.getPromptConfigJson());
summary.put("systemPrompt", agent.getPromptConfigJson() == null ? null : agent.getPromptConfigJson().get("systemPrompt"));
summary.put("prompt", agent.getPromptConfigJson() == null ? null : agent.getPromptConfigJson().get("prompt"));
return summary;
}
private List<Map<String, Object>> toolSummaries(List<AgentToolBinding> bindings) {
if (bindings == null) {
return List.of();
}
return bindings.stream().map(this::toolSummary).toList();
}
private Map<String, Object> toolSummary(AgentToolBinding binding) {
Map<String, Object> summary = new LinkedHashMap<>();
summary.put("bindingId", binding.getId());
summary.put("toolType", binding.getToolType());
summary.put("targetId", binding.getTargetId());
summary.put("toolName", binding.getToolName());
summary.put("enabled", Boolean.TRUE.equals(binding.getEnabled()));
summary.put("hitlEnabled", Boolean.TRUE.equals(binding.getHitlEnabled()));
summary.put("hitlConfigJson", binding.getHitlConfigJson());
summary.put("sortNo", binding.getSortNo());
if ("WORKFLOW".equalsIgnoreCase(binding.getToolType())) {
Workflow workflow = workflowService.getById(binding.getTargetId());
summary.put("title", workflow == null ? null : workflow.getTitle());
} else if ("PLUGIN".equalsIgnoreCase(binding.getToolType())) {
PluginItem pluginItem = pluginItemService.getById(binding.getTargetId());
summary.put("title", pluginItem == null ? null : pluginItem.getName());
} else {
Mcp mcp = mcpService.getById(binding.getTargetId());
summary.put("title", mcp == null ? null : mcp.getTitle());
}
return summary;
}
private List<AgentToolBinding> snapshotToolBindings(List<AgentToolBinding> bindings) {
if (bindings == null) {
return List.of();
}
return bindings.stream().map(binding -> {
AgentToolBinding snapshot = objectMapper.convertValue(binding, AgentToolBinding.class);
snapshot.setResourceSummary(toolSummary(binding));
snapshot.setResourceSnapshot(toolResourceSnapshot(binding));
return snapshot;
}).toList();
}
private Map<String, Object> toolResourceSnapshot(AgentToolBinding binding) {
if ("WORKFLOW".equalsIgnoreCase(binding.getToolType())) {
Workflow workflow = workflowService.getPublishedById(binding.getTargetId());
if (workflow == null || !PublishStatus.from(workflow.getPublishStatus()).isExternallyVisible()) {
throw new BusinessException("绑定工作流不存在或未发布");
}
return objectMapper.convertValue(workflow, new TypeReference<Map<String, Object>>() {});
}
if ("PLUGIN".equalsIgnoreCase(binding.getToolType())) {
PluginItem pluginItem = pluginItemService.getById(binding.getTargetId());
if (pluginItem == null) {
throw new BusinessException("绑定插件不存在");
}
return objectMapper.convertValue(pluginItem, new TypeReference<Map<String, Object>>() {});
}
Mcp mcp = mcpService.getById(binding.getTargetId());
if (mcp == null) {
throw new BusinessException("绑定 MCP 不存在");
}
return objectMapper.convertValue(mcp, new TypeReference<Map<String, Object>>() {});
}
private List<AgentKnowledgeBinding> snapshotKnowledgeBindings(List<AgentKnowledgeBinding> bindings) {
if (bindings == null) {
return List.of();
}
return bindings.stream().map(binding -> {
AgentKnowledgeBinding snapshot = objectMapper.convertValue(binding, AgentKnowledgeBinding.class);
snapshot.setResourceSummary(knowledgeSummary(binding));
snapshot.setResourceSnapshot(knowledgeResourceSnapshot(binding));
return snapshot;
}).toList();
}
private Map<String, Object> knowledgeResourceSnapshot(AgentKnowledgeBinding binding) {
DocumentCollection knowledge = documentCollectionService.getPublishedById(binding.getKnowledgeId());
if (knowledge == null || !PublishStatus.from(knowledge.getPublishStatus()).isExternallyVisible()) {
throw new BusinessException("绑定知识库不存在或未发布");
}
return objectMapper.convertValue(knowledge, new TypeReference<Map<String, Object>>() {});
}
private List<Map<String, Object>> knowledgeSummaries(List<AgentKnowledgeBinding> bindings) {
if (bindings == null) {
return List.of();
}
return bindings.stream().map(this::knowledgeSummary).toList();
}
private Map<String, Object> knowledgeSummary(AgentKnowledgeBinding binding) {
DocumentCollection knowledge = documentCollectionService.getById(binding.getKnowledgeId());
Map<String, Object> summary = new LinkedHashMap<>();
summary.put("bindingId", binding.getId());
summary.put("knowledgeId", binding.getKnowledgeId());
summary.put("retrievalMode", binding.getRetrievalMode());
summary.put("enabled", Boolean.TRUE.equals(binding.getEnabled()));
summary.put("optionsJson", binding.getOptionsJson());
summary.put("sortNo", binding.getSortNo());
summary.put("title", knowledge == null ? null : knowledge.getTitle());
return summary;
}
private LoginAccount requireCurrentLoginAccount() {
try {
return SaTokenUtil.getLoginAccount();
} catch (Exception e) {
throw new BusinessException("当前登录状态失效,请重新登录后再试");
}
}
private BigInteger toBigInteger(Object value) {
if (value == null) {
return null;
}
if (value instanceof BigInteger bigInteger) {
return bigInteger;
}
if (value instanceof Number number) {
return BigInteger.valueOf(number.longValue());
}
return new BigInteger(String.valueOf(value));
}
}

View File

@@ -0,0 +1,139 @@
package tech.easyflow.agent.service.impl;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.entity.AgentToolBinding;
import tech.easyflow.agent.enums.AgentToolType;
import tech.easyflow.agent.mapper.AgentMapper;
import tech.easyflow.agent.mapper.AgentToolBindingMapper;
import tech.easyflow.agent.service.AgentToolBindingService;
import tech.easyflow.ai.entity.Mcp;
import tech.easyflow.ai.entity.PluginItem;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.ai.enums.PublishStatus;
import tech.easyflow.ai.service.McpService;
import tech.easyflow.ai.service.PluginItemService;
import tech.easyflow.ai.service.WorkflowService;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
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 javax.annotation.Resource;
import java.math.BigInteger;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
* Agent 工具绑定服务实现。
*/
@Service
public class AgentToolBindingServiceImpl extends ServiceImpl<AgentToolBindingMapper, AgentToolBinding>
implements AgentToolBindingService {
@Resource
private AgentMapper agentMapper;
@Resource
private WorkflowService workflowService;
@Resource
private PluginItemService pluginItemService;
@Resource
private McpService mcpService;
@Resource
private ResourceAccessService resourceAccessService;
/**
* {@inheritDoc}
*/
@Override
@Transactional(rollbackFor = Exception.class)
public List<AgentToolBinding> replaceBindings(BigInteger agentId, List<AgentToolBinding> bindings) {
Agent agent = requireAgent(agentId);
resourceAccessService.assertAccess(CategoryResourceType.AGENT, agent, ResourceAction.MANAGE, "无权限管理该 Agent");
remove(QueryWrapper.create().where("agent_id = ?", agentId));
if (bindings == null || bindings.isEmpty()) {
return Collections.emptyList();
}
for (int i = 0; i < bindings.size(); i++) {
AgentToolBinding binding = bindings.get(i);
validateBinding(binding);
applyBindingDefaults(agent, binding, i);
}
saveBatch(bindings);
return listEnabled(agentId);
}
/**
* {@inheritDoc}
*/
@Override
public List<AgentToolBinding> listEnabled(BigInteger agentId) {
return list(QueryWrapper.create()
.where("agent_id = ?", agentId)
.and("enabled = ?", true)
.orderBy("sort_no asc, id asc"));
}
private Agent requireAgent(BigInteger agentId) {
Agent agent = agentMapper.selectOneById(agentId);
if (agent == null) {
throw new BusinessException("Agent 不存在");
}
return agent;
}
private void validateBinding(AgentToolBinding binding) {
if (binding == null || binding.getTargetId() == null || binding.getToolType() == null) {
throw new BusinessException("工具绑定参数不完整");
}
AgentToolType type = AgentToolType.from(binding.getToolType());
if (type == AgentToolType.WORKFLOW) {
Workflow workflow = workflowService.getById(binding.getTargetId());
if (workflow == null || PublishStatus.from(workflow.getPublishStatus()) != PublishStatus.PUBLISHED) {
throw new BusinessException("绑定工作流不存在或未发布");
}
resourceAccessService.assertAccess(CategoryResourceType.WORKFLOW, workflow, ResourceAction.USE, "无权限绑定该工作流");
return;
}
if (type == AgentToolType.PLUGIN) {
PluginItem pluginItem = pluginItemService.getById(binding.getTargetId());
if (pluginItem == null || pluginItem.getStatus() == null || pluginItem.getStatus() != 1) {
throw new BusinessException("绑定插件不存在或未启用");
}
return;
}
Mcp mcp = mcpService.getById(binding.getTargetId());
if (mcp == null || !Boolean.TRUE.equals(mcp.getStatus())) {
throw new BusinessException("绑定 MCP 不存在或未启用");
}
}
private void applyBindingDefaults(Agent agent, AgentToolBinding binding, int index) {
LoginAccount account = requireCurrentLoginAccount();
Date now = new Date();
binding.setId(null);
binding.setTenantId(agent.getTenantId());
binding.setAgentId(agent.getId());
binding.setEnabled(binding.getEnabled() == null || binding.getEnabled());
binding.setHitlEnabled(Boolean.TRUE.equals(binding.getHitlEnabled()));
binding.setSortNo(binding.getSortNo() == null ? index : binding.getSortNo());
binding.setCreated(now);
binding.setCreatedBy(account.getId());
binding.setModified(now);
binding.setModifiedBy(account.getId());
}
private LoginAccount requireCurrentLoginAccount() {
try {
return SaTokenUtil.getLoginAccount();
} catch (Exception e) {
throw new BusinessException("当前登录状态失效,请重新登录后再试");
}
}
}