feat: 先进智能体功能上线

- 基于 agent-runtime 打造,默认 ReAct agent
- 支持 agent 能力对接,已对接工作流、插件、知识库等 tool 能力
- 全新 agent 编排界面,支持可视化便捷配置 agent
- 全新 agent 聊天界面,支持快捷操作、额外知识库选择等
This commit is contained in:
2026-05-28 11:29:18 +08:00
parent 11e595b088
commit 1c205c3720
39 changed files with 3546 additions and 217 deletions

View File

@@ -0,0 +1,187 @@
package tech.easyflow.agent.runtime;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Assert;
import org.junit.Test;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.entity.AgentKnowledgeBinding;
import tech.easyflow.ai.entity.DocumentCollection;
import tech.easyflow.ai.enums.PublishStatus;
import tech.easyflow.ai.service.DocumentCollectionService;
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.permission.resource.VisibilityResource;
import tech.easyflow.system.service.ResourceAccessService;
import java.lang.reflect.Proxy;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Agent 聊天临时能力编排服务测试。
*/
public class AgentChatCapabilityServiceTest {
@Test
public void applyShouldAppendPublishedKnowledgeAndSkipBoundDuplicate() {
DocumentCollectionService documentService = documentService(
knowledge(1, PublishStatus.PUBLISHED),
knowledge(2, PublishStatus.PUBLISHED)
);
AgentChatCapabilityService service = new AgentChatCapabilityService(
documentService,
new AllowResourceAccessService(),
new ObjectMapper()
);
Agent agent = agentWithBoundKnowledge(1);
AgentChatCapabilityService.AgentChatCapabilityResolution resolution = service.apply(
agent,
List.of(capability(1, 2, 2)),
account()
);
List<AgentKnowledgeBinding> bindings = resolution.agent().getKnowledgeBindings();
Assert.assertEquals(List.of(BigInteger.ONE, BigInteger.valueOf(2)), resolution.extraKnowledgeIds());
Assert.assertEquals(2, bindings.size());
Assert.assertEquals(BigInteger.ONE, bindings.get(0).getKnowledgeId());
Assert.assertEquals(BigInteger.valueOf(2), bindings.get(1).getKnowledgeId());
Assert.assertEquals("HYBRID", bindings.get(1).getRetrievalMode());
Assert.assertTrue(bindings.get(1).getEnabled());
}
@Test
public void applyShouldAppendWhenBoundKnowledgeIsDisabled() {
DocumentCollectionService documentService = documentService(knowledge(2, PublishStatus.PUBLISHED));
AgentChatCapabilityService service = new AgentChatCapabilityService(
documentService,
new AllowResourceAccessService(),
new ObjectMapper()
);
Agent agent = agentWithBoundKnowledge(2);
agent.getKnowledgeBindings().get(0).setEnabled(false);
AgentChatCapabilityService.AgentChatCapabilityResolution resolution = service.apply(
agent,
List.of(capability(2)),
account()
);
List<AgentKnowledgeBinding> bindings = resolution.agent().getKnowledgeBindings();
Assert.assertEquals(2, bindings.size());
Assert.assertFalse(bindings.get(0).getEnabled());
Assert.assertTrue(bindings.get(1).getEnabled());
Assert.assertEquals(BigInteger.valueOf(2), bindings.get(1).getKnowledgeId());
}
@Test
public void resolveKnowledgeIdsShouldRejectTooManyItems() {
AgentChatCapabilityService service = new AgentChatCapabilityService(
documentService(),
new AllowResourceAccessService(),
new ObjectMapper()
);
Assert.assertThrows(BusinessException.class,
() -> service.resolveKnowledgeIds(List.of(capability(1, 2, 3, 4))));
}
@Test
public void applyShouldRejectUnpublishedKnowledge() {
DocumentCollectionService documentService = documentService(knowledge(2, PublishStatus.OFFLINE));
AgentChatCapabilityService service = new AgentChatCapabilityService(
documentService,
new AllowResourceAccessService(),
new ObjectMapper()
);
Assert.assertThrows(BusinessException.class,
() -> service.apply(agentWithBoundKnowledge(1), List.of(capability(2)), account()));
}
@Test
public void applyShouldRejectUnauthorizedKnowledge() {
DocumentCollectionService documentService = documentService(knowledge(2, PublishStatus.PUBLISHED));
AgentChatCapabilityService service = new AgentChatCapabilityService(
documentService,
new DenyResourceAccessService(),
new ObjectMapper()
);
Assert.assertThrows(BusinessException.class,
() -> service.apply(agentWithBoundKnowledge(1), List.of(capability(2)), account()));
}
private static Agent agentWithBoundKnowledge(int knowledgeId) {
Agent agent = new Agent();
agent.setId(BigInteger.TEN);
agent.setTenantId(BigInteger.ONE);
AgentKnowledgeBinding binding = new AgentKnowledgeBinding();
binding.setAgentId(agent.getId());
binding.setKnowledgeId(BigInteger.valueOf(knowledgeId));
binding.setRetrievalMode("HYBRID");
binding.setEnabled(true);
agent.setKnowledgeBindings(List.of(binding));
return agent;
}
private static AgentChatCapability capability(int... ids) {
AgentChatCapability capability = new AgentChatCapability();
capability.setType("KNOWLEDGE");
capability.setResourceIds(java.util.Arrays.stream(ids)
.mapToObj(BigInteger::valueOf)
.toList());
return capability;
}
private static DocumentCollection knowledge(int id, PublishStatus status) {
DocumentCollection collection = new DocumentCollection();
collection.setId(BigInteger.valueOf(id));
collection.setTitle("知识库" + id);
collection.setPublishStatus(status.name());
return collection;
}
private static LoginAccount account() {
LoginAccount account = new LoginAccount();
account.setId(BigInteger.valueOf(100));
account.setTenantId(BigInteger.ONE);
return account;
}
private static DocumentCollectionService documentService(DocumentCollection... collections) {
Map<BigInteger, DocumentCollection> collectionMap = new HashMap<>();
for (DocumentCollection collection : collections) {
collectionMap.put(collection.getId(), collection);
}
return (DocumentCollectionService) Proxy.newProxyInstance(
AgentChatCapabilityServiceTest.class.getClassLoader(),
new Class<?>[]{DocumentCollectionService.class},
(proxy, method, args) -> switch (method.getName()) {
case "getById" -> collectionMap.get(new BigInteger(String.valueOf(args[0])));
case "toPublishedView" -> args[0];
case "listByIds" -> ((java.util.Collection<?>) args[0]).stream()
.map(id -> collectionMap.get(new BigInteger(String.valueOf(id))))
.filter(java.util.Objects::nonNull)
.toList();
default -> throw new UnsupportedOperationException(method.getName());
}
);
}
private static class AllowResourceAccessService implements ResourceAccessService {
@Override public boolean canAccess(CategoryResourceType resourceType, VisibilityResource resource, ResourceAction action) { return true; }
@Override public boolean canAccess(LoginAccount loginAccount, CategoryResourceType resourceType, VisibilityResource resource, ResourceAction action) { return true; }
@Override public void assertAccess(CategoryResourceType resourceType, VisibilityResource resource, ResourceAction action, String message) {}
}
private static class DenyResourceAccessService extends AllowResourceAccessService {
@Override public void assertAccess(CategoryResourceType resourceType, VisibilityResource resource, ResourceAction action, String message) {
throw new BusinessException(message);
}
}
}