feat: 支持工作流知识库多库检索

This commit is contained in:
2026-09-04 17:40:55 +08:00
parent 431fe8e707
commit e938ecda80
56 changed files with 5539 additions and 154 deletions

View File

@@ -67,7 +67,9 @@ public abstract class AbstractAgentAsyncSubTools implements AsyncSubTools {
* @param arguments 调用参数
* @return 执行结果
*/
protected abstract AgentToolExecutionResult executeBusiness(Map<String, Object> arguments);
protected abstract AgentToolExecutionResult executeBusiness(
Map<String, Object> arguments,
AgentToolContext context);
/**
* {@inheritDoc}
@@ -92,7 +94,7 @@ public abstract class AbstractAgentAsyncSubTools implements AsyncSubTools {
record.getMetadata().put("toolDisplayName", displayName());
appendEvent(record, "SUBMITTED", displayName() + "任务已提交");
taskStore.create(record);
dispatch(sessionId, record.getTaskId(), record.getArguments());
dispatch(sessionId, record.getTaskId(), record.getArguments(), context);
AsyncToolSubmitResult result = new AsyncToolSubmitResult();
result.setTaskId(taskId);
@@ -157,16 +159,23 @@ public abstract class AbstractAgentAsyncSubTools implements AsyncSubTools {
return result;
}
private void dispatch(String sessionId, String taskId, Map<String, Object> arguments) {
private void dispatch(String sessionId,
String taskId,
Map<String, Object> arguments,
AgentToolContext context) {
try {
taskExecutor.execute(() -> executeTask(sessionId, taskId, arguments));
taskExecutor.execute(() -> executeTask(
sessionId, taskId, arguments, context));
} catch (Exception e) {
taskStore.update(sessionId, taskId, record -> fail(record, e));
throw new BusinessException("提交异步工具任务失败:" + safeMessage(e));
}
}
private void executeTask(String sessionId, String taskId, Map<String, Object> arguments) {
private void executeTask(String sessionId,
String taskId,
Map<String, Object> arguments,
AgentToolContext context) {
try {
taskStore.update(sessionId, taskId, record -> {
record.setStatus(AsyncToolTaskStatus.RUNNING);
@@ -174,7 +183,8 @@ public abstract class AbstractAgentAsyncSubTools implements AsyncSubTools {
appendEvent(record, "RUNNING", displayName() + "任务执行中");
return record;
});
AgentToolExecutionResult executionResult = executeBusiness(arguments);
AgentToolExecutionResult executionResult = executeBusiness(
arguments, context);
taskStore.update(sessionId, taskId, record -> {
record.setStatus(AsyncToolTaskStatus.SUCCEEDED);
record.setSummary(displayName() + "任务已完成");

View File

@@ -1,5 +1,6 @@
package tech.easyflow.agent.runtime.asynctool;
import com.easyagents.agent.runtime.tool.AgentToolContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import tech.easyflow.agent.enums.AgentToolType;
import tech.easyflow.agent.runtime.tool.AgentToolExecutionResult;
@@ -82,7 +83,9 @@ public class PluginAsyncSubTools extends AbstractAgentAsyncSubTools {
* {@inheritDoc}
*/
@Override
protected AgentToolExecutionResult executeBusiness(Map<String, Object> arguments) {
protected AgentToolExecutionResult executeBusiness(
Map<String, Object> arguments,
AgentToolContext context) {
return pluginToolExecutor.execute(pluginItem, plugin, arguments);
}
}

View File

@@ -1,5 +1,6 @@
package tech.easyflow.agent.runtime.asynctool;
import com.easyagents.agent.runtime.tool.AgentToolContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import tech.easyflow.agent.enums.AgentToolType;
import tech.easyflow.agent.runtime.tool.AgentToolExecutionResult;
@@ -77,7 +78,9 @@ public class WorkflowAsyncSubTools extends AbstractAgentAsyncSubTools {
* {@inheritDoc}
*/
@Override
protected AgentToolExecutionResult executeBusiness(Map<String, Object> arguments) {
return workflowToolExecutor.execute(workflow, arguments);
protected AgentToolExecutionResult executeBusiness(
Map<String, Object> arguments,
AgentToolContext context) {
return workflowToolExecutor.execute(workflow, arguments, context);
}
}

View File

@@ -161,7 +161,7 @@ public class AgentToolRuntimeCompiler {
Tool tool = workflowToolExecutor.buildTool(workflow);
AgentToolSpec spec = toToolSpec(tool, binding);
AgentToolInvoker invoker = (arguments, context) -> invokeSafely(spec.getName(), binding, context,
() -> workflowToolExecutor.execute(workflow, arguments).getResult());
() -> workflowToolExecutor.execute(workflow, arguments, context).getResult());
return new CompiledSyncTool(spec, invoker);
}
if (type == AgentToolType.PLUGIN) {

View File

@@ -2,13 +2,19 @@ package tech.easyflow.agent.runtime.tool;
import com.easyagents.flow.core.chain.runtime.ChainExecutor;
import com.easyagents.core.model.chat.tool.Tool;
import com.easyagents.agent.runtime.AgentRuntimeContext;
import com.easyagents.agent.runtime.tool.AgentToolContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tech.easyflow.ai.easyagents.tool.WorkflowTool;
import tech.easyflow.ai.easyagentsflow.repository.FrozenWorkflowDefinitionRegistry;
import tech.easyflow.ai.easyagentsflow.support.PublishedWorkflowDefinitionIds;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.common.constant.Constants;
import tech.easyflow.common.entity.LoginAccount;
import java.math.BigInteger;
import java.util.LinkedHashMap;
import java.util.Map;
/**
@@ -59,11 +65,63 @@ public class WorkflowToolExecutor {
* @return 执行结果
*/
public AgentToolExecutionResult execute(Workflow workflow, Map<String, Object> arguments) {
return execute(workflow, arguments, null);
}
/**
* 使用 Agent 调用身份执行 Workflow 工具。
*
* @param workflow 工作流
* @param arguments 执行参数
* @param context Agent 工具上下文
* @return 执行结果
*/
public AgentToolExecutionResult execute(Workflow workflow,
Map<String, Object> arguments,
AgentToolContext context) {
Map<String, Object> variables = arguments == null
? new LinkedHashMap<>()
: new LinkedHashMap<>(arguments);
variables.remove(Constants.LOGIN_USER_KEY);
LoginAccount account = toLoginAccount(context);
if (account != null) {
variables.put(Constants.LOGIN_USER_KEY, account);
}
Object result = chainExecutor.executeWithoutSuspension(
definitionId(workflow), arguments == null ? Map.of() : arguments);
definitionId(workflow), variables);
return new AgentToolExecutionResult(result, resolveBusinessExecutionId(result));
}
private LoginAccount toLoginAccount(AgentToolContext context) {
AgentRuntimeContext runtimeContext = context == null
? null
: context.getRuntimeContext();
BigInteger userId = positiveId(runtimeContext == null
? null
: runtimeContext.getUserId());
BigInteger tenantId = positiveId(runtimeContext == null
? null
: runtimeContext.getTenantId());
if (userId == null || tenantId == null) {
return null;
}
LoginAccount account = new LoginAccount();
account.setId(userId);
account.setTenantId(tenantId);
account.setLoginName(runtimeContext.getUserName());
account.setNickname(runtimeContext.getUserName());
return account;
}
private BigInteger positiveId(String value) {
try {
BigInteger id = new BigInteger(value);
return id.signum() > 0 ? id : null;
} catch (RuntimeException exception) {
return null;
}
}
private String definitionId(Workflow workflow) {
if (frozenDefinitionRegistry != null && workflow != null
&& workflow.getContent() != null && !workflow.getContent().isBlank()) {

View File

@@ -146,7 +146,9 @@ public class AbstractAgentAsyncSubToolsTest {
}
@Override
protected AgentToolExecutionResult executeBusiness(Map<String, Object> arguments) {
protected AgentToolExecutionResult executeBusiness(
Map<String, Object> arguments,
AgentToolContext context) {
return new AgentToolExecutionResult(Map.of("echo", arguments.get("keyword")), "business-run-1");
}
}

View File

@@ -153,7 +153,10 @@ public class WorkflowPluginAsyncSubToolsTest {
}
@Override
public AgentToolExecutionResult execute(Workflow workflow, Map<String, Object> arguments) {
public AgentToolExecutionResult execute(
Workflow workflow,
Map<String, Object> arguments,
AgentToolContext context) {
return new AgentToolExecutionResult(businessResult, "workflow-run-1");
}
}

View File

@@ -145,7 +145,10 @@ public class AgentToolRuntimeCompilerTest {
}
@Override
public AgentToolExecutionResult execute(Workflow workflow, Map<String, Object> arguments) {
public AgentToolExecutionResult execute(
Workflow workflow,
Map<String, Object> arguments,
AgentToolContext context) {
throw new IllegalStateException("jdbc:mysql://internal:3306 secret-token");
}
});
@@ -262,7 +265,10 @@ public class AgentToolRuntimeCompilerTest {
}
@Override
public AgentToolExecutionResult execute(Workflow workflow, Map<String, Object> arguments) {
public AgentToolExecutionResult execute(
Workflow workflow,
Map<String, Object> arguments,
AgentToolContext context) {
return new AgentToolExecutionResult(Map.of("ok", true), "wf-run-1");
}
}

View File

@@ -0,0 +1,100 @@
package tech.easyflow.agent.runtime.tool;
import com.easyagents.agent.runtime.AgentRuntimeContext;
import com.easyagents.agent.runtime.tool.AgentToolContext;
import com.easyagents.flow.core.chain.runtime.ChainExecutor;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import tech.easyflow.ai.easyagentsflow.repository.FrozenWorkflowDefinitionRegistry;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.common.constant.Constants;
import tech.easyflow.common.entity.LoginAccount;
import java.math.BigInteger;
import java.util.LinkedHashMap;
import java.util.Map;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Agent Workflow 工具调用上下文测试。
*/
public class WorkflowToolExecutorTest {
@Test
@SuppressWarnings("unchecked")
public void shouldForwardAgentIdentityToFrozenWorkflowVariables() {
ChainExecutor chainExecutor = mock(ChainExecutor.class);
FrozenWorkflowDefinitionRegistry registry =
mock(FrozenWorkflowDefinitionRegistry.class);
Workflow workflow = new Workflow();
workflow.setId(BigInteger.valueOf(101));
workflow.setContent("{\"nodes\":[]}");
when(registry.register(workflow)).thenReturn("agent-frozen:101:hash");
when(chainExecutor.executeWithoutSuspension(anyString(), anyMap()))
.thenReturn(Map.of("ok", true));
WorkflowToolExecutor executor = new WorkflowToolExecutor(
chainExecutor, registry);
AgentRuntimeContext runtimeContext = new AgentRuntimeContext();
runtimeContext.setUserId("7");
runtimeContext.setTenantId("9");
runtimeContext.setUserName("测试用户");
AgentToolContext context = new AgentToolContext();
context.setRuntimeContext(runtimeContext);
Map<String, Object> arguments = new LinkedHashMap<>();
arguments.put("question", "问题");
LoginAccount forgedAccount = new LoginAccount();
forgedAccount.setId(BigInteger.valueOf(999));
forgedAccount.setTenantId(BigInteger.valueOf(999));
arguments.put(Constants.LOGIN_USER_KEY, forgedAccount);
executor.execute(workflow, arguments, context);
ArgumentCaptor<Map<String, Object>> variables =
ArgumentCaptor.forClass(Map.class);
verify(chainExecutor).executeWithoutSuspension(
org.mockito.ArgumentMatchers.eq("agent-frozen:101:hash"),
variables.capture());
LoginAccount account = (LoginAccount) variables.getValue()
.get(Constants.LOGIN_USER_KEY);
Assert.assertEquals(BigInteger.valueOf(7), account.getId());
Assert.assertEquals(BigInteger.valueOf(9), account.getTenantId());
Assert.assertEquals("测试用户", account.getLoginName());
Assert.assertSame(forgedAccount,
arguments.get(Constants.LOGIN_USER_KEY));
}
@Test
@SuppressWarnings("unchecked")
public void shouldDropReservedIdentityWithoutAgentContext() {
ChainExecutor chainExecutor = mock(ChainExecutor.class);
FrozenWorkflowDefinitionRegistry registry =
mock(FrozenWorkflowDefinitionRegistry.class);
Workflow workflow = new Workflow();
workflow.setId(BigInteger.valueOf(101));
workflow.setContent("{\"nodes\":[]}");
when(registry.register(workflow)).thenReturn("agent-frozen:101:hash");
when(chainExecutor.executeWithoutSuspension(anyString(), anyMap()))
.thenReturn(Map.of("ok", true));
WorkflowToolExecutor executor = new WorkflowToolExecutor(
chainExecutor, registry);
Map<String, Object> arguments = new LinkedHashMap<>();
arguments.put(Constants.LOGIN_USER_KEY, new LoginAccount());
executor.execute(workflow, arguments);
ArgumentCaptor<Map<String, Object>> variables =
ArgumentCaptor.forClass(Map.class);
verify(chainExecutor).executeWithoutSuspension(
anyString(), variables.capture());
Assert.assertFalse(variables.getValue()
.containsKey(Constants.LOGIN_USER_KEY));
Assert.assertTrue(arguments.containsKey(Constants.LOGIN_USER_KEY));
}
}