feat: 归档 XL10 异步工具业务编译层

- 将 AgentDefinitionCompiler 升级为 AgentRuntimeCompiler

- 接入 Workflow 和 Plugin 的同步/异步工具编译与 Redis 任务态

- 增加异步执行配置开关、聊天时间线聚合和后端测试
This commit is contained in:
2026-06-04 15:23:56 +08:00
parent 1ea863cb2c
commit c316eff5be
26 changed files with 2859 additions and 62 deletions

View File

@@ -8,6 +8,7 @@ import org.junit.Test;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.entity.AgentToolBinding;
import tech.easyflow.agent.enums.AgentToolType;
import tech.easyflow.agent.runtime.tool.AgentToolRuntimeCompiler;
import tech.easyflow.ai.entity.Mcp;
import tech.easyflow.ai.entity.Model;
import tech.easyflow.ai.entity.ModelProvider;
@@ -36,10 +37,13 @@ public class AgentDefinitionCompilerMcpTest {
BigInteger mcpId = BigInteger.valueOf(20L);
Model model = model(modelId);
Mcp mcp = mcp(mcpId);
AgentDefinitionCompiler compiler = new AgentDefinitionCompiler();
AgentRuntimeCompiler compiler = new AgentRuntimeCompiler();
AgentToolRuntimeCompiler toolCompiler = new AgentToolRuntimeCompiler();
setField(compiler, "objectMapper", new com.fasterxml.jackson.databind.ObjectMapper());
setField(compiler, "modelService", modelService(model));
setField(compiler, "mcpService", mcpService(mcp));
setField(toolCompiler, "objectMapper", new com.fasterxml.jackson.databind.ObjectMapper());
setField(toolCompiler, "mcpService", mcpService(mcp));
setField(compiler, "agentToolRuntimeCompiler", toolCompiler);
Agent agent = agent(modelId, mcpId);

View File

@@ -429,11 +429,11 @@ public class AgentRunServiceDraftAndHitlTest {
@Test
public void startRuntimeShouldUseDraftSessionStoreWithoutBindingMysqlSession() throws Exception {
AgentRunService service = new AgentRunService();
RecordingAgentDefinitionCompiler compiler = new RecordingAgentDefinitionCompiler();
RecordingAgentRuntimeCompiler compiler = new RecordingAgentRuntimeCompiler();
RecordingAgentRuntime runtime = new RecordingAgentRuntime();
RecordingAgentRuntimeFactory runtimeFactory = new RecordingAgentRuntimeFactory(runtime);
AgentSessionStore draftStore = new InMemoryAgentSessionStore();
setField(service, "agentDefinitionCompiler", compiler);
setField(service, "agentRuntimeCompiler", compiler);
setField(service, "agentRuntimeFactory", runtimeFactory);
setField(service, "agentRunRegistry", new AgentRunRegistry());
@@ -1001,7 +1001,7 @@ public class AgentRunServiceDraftAndHitlTest {
}
}
private static class RecordingAgentDefinitionCompiler extends AgentDefinitionCompiler {
private static class RecordingAgentRuntimeCompiler extends AgentRuntimeCompiler {
@Override
public AgentRuntimeBundle compile(Agent agent) {

View File

@@ -0,0 +1,195 @@
package tech.easyflow.agent.runtime.asynctool;
import com.easyagents.agent.runtime.tool.AgentToolContext;
import com.easyagents.agent.runtime.tool.asynctool.AsyncToolCancelRequest;
import com.easyagents.agent.runtime.tool.asynctool.AsyncToolObserveRequest;
import com.easyagents.agent.runtime.tool.asynctool.AsyncToolResultRequest;
import com.easyagents.agent.runtime.tool.asynctool.AsyncToolSubmitResult;
import com.easyagents.agent.runtime.tool.asynctool.AsyncToolTaskStatus;
import com.easyagents.agent.runtime.tool.asynctool.AsyncToolTaskView;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import tech.easyflow.agent.runtime.tool.AgentToolExecutionResult;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.UnaryOperator;
/**
* EasyFlow 异步业务工具基类测试。
*/
public class AbstractAgentAsyncSubToolsTest {
/**
* 验证 submit、observe、result 与 list 的基础任务生命周期。
*
* @throws Exception 等待后台执行超时时抛出
*/
@Test
public void asyncSubToolsShouldSubmitObserveResultAndListCurrentSessionTasks() throws Exception {
ThreadPoolTaskExecutor executor = executor();
try {
InMemoryTaskStore store = new InMemoryTaskStore();
TestAsyncSubTools subTools = new TestAsyncSubTools(store, executor);
AgentToolContext context = context("session-a");
AsyncToolSubmitResult submitted = subTools.submit(Map.of("keyword", "hello"), context);
Assert.assertEquals(AsyncToolTaskStatus.PENDING, submitted.getStatus());
Assert.assertTrue(submitted.getTaskId().startsWith("async_"));
AsyncToolTaskView completed = waitTerminal(subTools, submitted.getTaskId(), context);
Assert.assertEquals(AsyncToolTaskStatus.SUCCEEDED, completed.getStatus());
Assert.assertEquals(Map.of("echo", "hello"), completed.getResult());
Assert.assertTrue(completed.getNextCursor() >= 2L);
AsyncToolResultRequest resultRequest = new AsyncToolResultRequest();
resultRequest.setTaskId(submitted.getTaskId());
resultRequest.setCursor(1L);
AsyncToolTaskView result = subTools.result(resultRequest, context);
Assert.assertEquals(AsyncToolTaskStatus.SUCCEEDED, result.getStatus());
Assert.assertEquals(Map.of("echo", "hello"), result.getResult());
Assert.assertFalse(result.getEvents().isEmpty());
Assert.assertEquals(1, subTools.list(null, context).getTasks().size());
Assert.assertTrue(subTools.list(null, context("session-b")).getTasks().isEmpty());
AsyncToolTaskView crossedSessionView = observe(subTools, submitted.getTaskId(), context("session-b"));
Assert.assertEquals(AsyncToolTaskStatus.FAILED, crossedSessionView.getStatus());
Assert.assertEquals("TASK_NOT_FOUND", crossedSessionView.getErrorType());
} finally {
executor.shutdown();
}
}
/**
* 验证首版取消语义返回明确失败结果。
*/
@Test
public void cancelShouldReturnUnsupportedFailure() {
TestAsyncSubTools subTools = new TestAsyncSubTools(new InMemoryTaskStore(), executor());
AsyncToolCancelRequest request = new AsyncToolCancelRequest();
request.setTaskId("task-1");
var result = subTools.cancel(request, context("session-a"));
Assert.assertEquals(AsyncToolTaskStatus.FAILED, result.getStatus());
Assert.assertEquals("不支持取消", result.getMessage());
}
private AsyncToolTaskView waitTerminal(TestAsyncSubTools subTools, String taskId, AgentToolContext context) throws Exception {
long deadline = System.currentTimeMillis() + 3000L;
AsyncToolTaskView view = observe(subTools, taskId, context);
while (!Boolean.TRUE.equals(view.getTerminal()) && System.currentTimeMillis() < deadline) {
Thread.sleep(20L);
view = observe(subTools, taskId, context);
}
Assert.assertTrue("异步任务应在测试超时前完成", Boolean.TRUE.equals(view.getTerminal()));
return view;
}
private AsyncToolTaskView observe(TestAsyncSubTools subTools, String taskId, AgentToolContext context) {
AsyncToolObserveRequest request = new AsyncToolObserveRequest();
request.setTaskId(taskId);
request.setCursor(0L);
return subTools.observe(request, context);
}
private AgentToolContext context(String sessionId) {
AgentToolContext context = new AgentToolContext();
context.setRequestId("request-1");
context.setTraceId("trace-1");
context.setSessionId(sessionId);
context.setAgentId("agent-1");
context.setToolCallId("tool-call-1");
return context;
}
private ThreadPoolTaskExecutor executor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
executor.setMaxPoolSize(1);
executor.setQueueCapacity(4);
executor.setThreadNamePrefix("async-sub-tools-test-");
executor.initialize();
return executor;
}
private static final class TestAsyncSubTools extends AbstractAgentAsyncSubTools {
private TestAsyncSubTools(AgentAsyncToolTaskStore taskStore, ThreadPoolTaskExecutor taskExecutor) {
super(taskStore, taskExecutor);
}
@Override
protected String toolType() {
return "PLUGIN";
}
@Override
protected String toolName() {
return "test_tool";
}
@Override
protected String displayName() {
return "测试工具";
}
@Override
protected String businessId() {
return "business-1";
}
@Override
protected AgentToolExecutionResult executeBusiness(Map<String, Object> arguments) {
return new AgentToolExecutionResult(Map.of("echo", arguments.get("keyword")), "business-run-1");
}
}
private static final class InMemoryTaskStore implements AgentAsyncToolTaskStore {
private final Map<String, AgentAsyncToolTaskRecord> records = new ConcurrentHashMap<>();
@Override
public void create(AgentAsyncToolTaskRecord record) {
record.setSessionScopedKey(key(record.getSessionId(), record.getTaskId()));
records.put(record.getSessionScopedKey(), record);
}
@Override
public Optional<AgentAsyncToolTaskRecord> get(String sessionId, String taskId) {
return Optional.ofNullable(records.get(key(sessionId, taskId)));
}
@Override
public Optional<AgentAsyncToolTaskRecord> update(String sessionId,
String taskId,
UnaryOperator<AgentAsyncToolTaskRecord> updater) {
String key = key(sessionId, taskId);
AgentAsyncToolTaskRecord updated = records.computeIfPresent(key,
(ignored, existing) -> updater == null ? existing : updater.apply(existing));
return Optional.ofNullable(updated);
}
@Override
public List<AgentAsyncToolTaskRecord> list(String sessionId, AsyncToolTaskStatus status) {
List<AgentAsyncToolTaskRecord> result = new ArrayList<>();
for (AgentAsyncToolTaskRecord record : records.values()) {
if (sessionId.equals(record.getSessionId()) && (status == null || status == record.getStatus())) {
result.add(record);
}
}
result.sort(Comparator.comparing(AgentAsyncToolTaskRecord::getCreatedAt).reversed());
return result;
}
private String key(String sessionId, String taskId) {
return sessionId + ":" + taskId;
}
}
}

View File

@@ -0,0 +1,213 @@
package tech.easyflow.agent.runtime.asynctool;
import com.easyagents.agent.runtime.tool.AgentToolContext;
import com.easyagents.agent.runtime.tool.asynctool.AsyncToolObserveRequest;
import com.easyagents.agent.runtime.tool.asynctool.AsyncToolResultRequest;
import com.easyagents.agent.runtime.tool.asynctool.AsyncToolSubmitResult;
import com.easyagents.agent.runtime.tool.asynctool.AsyncToolTaskStatus;
import com.easyagents.agent.runtime.tool.asynctool.AsyncToolTaskView;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import tech.easyflow.agent.runtime.tool.AgentToolExecutionResult;
import tech.easyflow.agent.runtime.tool.PluginToolExecutor;
import tech.easyflow.agent.runtime.tool.WorkflowToolExecutor;
import tech.easyflow.ai.entity.PluginItem;
import tech.easyflow.ai.entity.Workflow;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.UnaryOperator;
/**
* Workflow 与 Plugin 异步子工具测试。
*/
public class WorkflowPluginAsyncSubToolsTest {
/**
* 验证 Workflow 异步子工具会把业务执行结果保留到任务视图。
*
* @throws Exception 等待后台执行超时时抛出
*/
@Test
public void workflowAsyncSubToolsShouldKeepBusinessResultInTaskView() throws Exception {
ThreadPoolTaskExecutor executor = executor();
try {
Map<String, Object> businessResult = Map.of("workflowOutput", "ok");
WorkflowAsyncSubTools subTools = new WorkflowAsyncSubTools(workflow(),
"workflow_demo",
"测试工作流",
new StubWorkflowToolExecutor(businessResult),
new InMemoryTaskStore(),
executor);
AsyncToolTaskView view = submitAndResult(subTools);
Assert.assertEquals(AsyncToolTaskStatus.SUCCEEDED, view.getStatus());
Assert.assertEquals(businessResult, view.getResult());
Assert.assertEquals("workflow-run-1", view.getPayload().get("businessExecutionId"));
} finally {
executor.shutdown();
}
}
/**
* 验证 Plugin 异步子工具会把业务执行结果保留到任务视图。
*
* @throws Exception 等待后台执行超时时抛出
*/
@Test
public void pluginAsyncSubToolsShouldKeepBusinessResultInTaskView() throws Exception {
ThreadPoolTaskExecutor executor = executor();
try {
Map<String, Object> businessResult = Map.of("pluginOutput", List.of("a", "b"));
PluginAsyncSubTools subTools = new PluginAsyncSubTools(pluginItem(),
"plugin_demo",
"测试插件",
new StubPluginToolExecutor(businessResult),
new InMemoryTaskStore(),
executor);
AsyncToolTaskView view = submitAndResult(subTools);
Assert.assertEquals(AsyncToolTaskStatus.SUCCEEDED, view.getStatus());
Assert.assertEquals(businessResult, view.getResult());
} finally {
executor.shutdown();
}
}
private AsyncToolTaskView submitAndResult(AbstractAgentAsyncSubTools subTools) throws Exception {
AgentToolContext context = context();
AsyncToolSubmitResult submitted = subTools.submit(Map.of("keyword", "hello"), context);
waitTerminal(subTools, submitted.getTaskId(), context);
AsyncToolResultRequest request = new AsyncToolResultRequest();
request.setTaskId(submitted.getTaskId());
return subTools.result(request, context);
}
private void waitTerminal(AbstractAgentAsyncSubTools subTools, String taskId, AgentToolContext context) throws Exception {
long deadline = System.currentTimeMillis() + 3000L;
AsyncToolTaskView view = observe(subTools, taskId, context);
while (!Boolean.TRUE.equals(view.getTerminal()) && System.currentTimeMillis() < deadline) {
Thread.sleep(20L);
view = observe(subTools, taskId, context);
}
Assert.assertTrue("异步任务应在测试超时前完成", Boolean.TRUE.equals(view.getTerminal()));
}
private AsyncToolTaskView observe(AbstractAgentAsyncSubTools subTools, String taskId, AgentToolContext context) {
AsyncToolObserveRequest request = new AsyncToolObserveRequest();
request.setTaskId(taskId);
return subTools.observe(request, context);
}
private AgentToolContext context() {
AgentToolContext context = new AgentToolContext();
context.setRequestId("request-1");
context.setTraceId("trace-1");
context.setSessionId("session-1");
context.setAgentId("agent-1");
context.setToolCallId("tool-call-1");
return context;
}
private Workflow workflow() {
Workflow workflow = new Workflow();
workflow.setId(BigInteger.valueOf(101L));
workflow.setTitle("测试工作流");
return workflow;
}
private PluginItem pluginItem() {
PluginItem pluginItem = new PluginItem();
pluginItem.setId(BigInteger.valueOf(102L));
pluginItem.setName("测试插件");
return pluginItem;
}
private ThreadPoolTaskExecutor executor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
executor.setMaxPoolSize(1);
executor.setQueueCapacity(4);
executor.setThreadNamePrefix("workflow-plugin-async-test-");
executor.initialize();
return executor;
}
private static final class StubWorkflowToolExecutor extends WorkflowToolExecutor {
private final Map<String, Object> businessResult;
private StubWorkflowToolExecutor(Map<String, Object> businessResult) {
super(null);
this.businessResult = businessResult;
}
@Override
public AgentToolExecutionResult execute(Workflow workflow, Map<String, Object> arguments) {
return new AgentToolExecutionResult(businessResult, "workflow-run-1");
}
}
private static final class StubPluginToolExecutor extends PluginToolExecutor {
private final Map<String, Object> businessResult;
private StubPluginToolExecutor(Map<String, Object> businessResult) {
this.businessResult = businessResult;
}
@Override
public AgentToolExecutionResult execute(PluginItem pluginItem, Map<String, Object> arguments) {
return new AgentToolExecutionResult(businessResult, null);
}
}
private static final class InMemoryTaskStore implements AgentAsyncToolTaskStore {
private final Map<String, AgentAsyncToolTaskRecord> records = new ConcurrentHashMap<>();
@Override
public void create(AgentAsyncToolTaskRecord record) {
record.setSessionScopedKey(key(record.getSessionId(), record.getTaskId()));
records.put(record.getSessionScopedKey(), record);
}
@Override
public Optional<AgentAsyncToolTaskRecord> get(String sessionId, String taskId) {
return Optional.ofNullable(records.get(key(sessionId, taskId)));
}
@Override
public Optional<AgentAsyncToolTaskRecord> update(String sessionId,
String taskId,
UnaryOperator<AgentAsyncToolTaskRecord> updater) {
AgentAsyncToolTaskRecord updated = records.computeIfPresent(key(sessionId, taskId),
(ignored, existing) -> updater == null ? existing : updater.apply(existing));
return Optional.ofNullable(updated);
}
@Override
public List<AgentAsyncToolTaskRecord> list(String sessionId, AsyncToolTaskStatus status) {
List<AgentAsyncToolTaskRecord> result = new ArrayList<>();
for (AgentAsyncToolTaskRecord record : records.values()) {
if (sessionId.equals(record.getSessionId()) && (status == null || status == record.getStatus())) {
result.add(record);
}
}
result.sort(Comparator.comparing(AgentAsyncToolTaskRecord::getCreatedAt).reversed());
return result;
}
private String key(String sessionId, String taskId) {
return sessionId + ":" + taskId;
}
}
}

View File

@@ -0,0 +1,239 @@
package tech.easyflow.agent.runtime.tool;
import com.easyagents.agent.runtime.tool.AgentToolSpec;
import com.easyagents.core.model.chat.tool.Parameter;
import com.easyagents.core.model.chat.tool.Tool;
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.AgentToolBinding;
import tech.easyflow.agent.enums.AgentToolType;
import tech.easyflow.ai.entity.PluginItem;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.common.web.exceptions.BusinessException;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Agent 工具运行时编译测试。
*/
public class AgentToolRuntimeCompilerTest {
/**
* 验证 Workflow 默认按同步工具编译。
*
* @throws Exception 反射注入依赖失败时抛出
*/
@Test
public void compileShouldUseSyncModeByDefault() throws Exception {
AgentToolRuntimeCompiler compiler = compiler();
AgentToolRuntimeCompilation compilation = compiler.compile(agent(workflowBinding(null, false, "flow-sync")));
Assert.assertEquals(List.of("flow-sync"), toolNames(compilation));
Assert.assertEquals(1, compilation.getToolInvokers().size());
Assert.assertFalse(compilation.getToolSpecs().get(0).isApprovalRequired());
}
/**
* 验证非法执行模式会回退为同步工具。
*
* @throws Exception 反射注入依赖失败时抛出
*/
@Test
public void compileShouldFallbackToSyncWhenExecutionModeInvalid() throws Exception {
AgentToolRuntimeCompiler compiler = compiler();
AgentToolRuntimeCompilation compilation = compiler.compile(agent(workflowBinding("BAD", false, "flow-sync")));
Assert.assertEquals(List.of("flow-sync"), toolNames(compilation));
Assert.assertEquals(1, compilation.getToolInvokers().size());
}
/**
* 验证 Workflow 异步模式会展开为五个固定子工具。
*
* @throws Exception 反射注入依赖失败时抛出
*/
@Test
public void compileShouldExpandWorkflowAsyncSubToolsAndNormalizeName() throws Exception {
AgentToolRuntimeCompiler compiler = compiler();
AgentToolRuntimeCompilation compilation = compiler.compile(agent(workflowBinding("ASYNC", true, "flow-alpha")));
Assert.assertEquals(List.of(
"flow_alpha_submit",
"flow_alpha_observe",
"flow_alpha_result",
"flow_alpha_cancel",
"flow_alpha_list"
), toolNames(compilation));
Assert.assertEquals(5, compilation.getToolInvokers().size());
Assert.assertEquals(List.of("keyword"), compilation.getToolSpecs().get(0).getParametersSchema().get("required"));
Assert.assertTrue(compilation.getToolSpecs().get(0).isApprovalRequired());
Assert.assertEquals("确认执行?", compilation.getToolSpecs().get(0).getApprovalRequest().getApprovalPrompt());
Assert.assertFalse(compilation.getToolSpecs().get(1).isApprovalRequired());
Assert.assertEquals("flow_alpha", compilation.getToolSpecs().get(0).getMetadata().get("asyncToolName"));
Assert.assertEquals("submit", compilation.getToolSpecs().get(0).getMetadata().get("asyncToolPhase"));
}
/**
* 验证 Plugin 异步模式同样展开为五个固定子工具。
*
* @throws Exception 反射注入依赖失败时抛出
*/
@Test
public void compileShouldExpandPluginAsyncSubTools() throws Exception {
AgentToolRuntimeCompiler compiler = compiler();
AgentToolRuntimeCompilation compilation = compiler.compile(agent(pluginBinding("ASYNC", "plugin-tool")));
Assert.assertEquals(List.of(
"plugin_tool_submit",
"plugin_tool_observe",
"plugin_tool_result",
"plugin_tool_cancel",
"plugin_tool_list"
), toolNames(compilation));
Assert.assertEquals(5, compilation.getToolInvokers().size());
for (AgentToolSpec spec : compilation.getToolSpecs()) {
Assert.assertEquals(Boolean.TRUE, spec.getMetadata().get("asyncTool"));
Assert.assertEquals("插件工具", spec.getMetadata().get("toolDisplayName"));
}
}
/**
* 验证异步工具名归一化后发生冲突时会在编译阶段失败。
*
* @throws Exception 反射注入依赖失败时抛出
*/
@Test
public void compileShouldRejectNormalizedAsyncToolNameCollision() throws Exception {
AgentToolRuntimeCompiler compiler = compiler();
AgentToolBinding first = workflowBinding("ASYNC", false, "flow-alpha");
AgentToolBinding second = workflowBinding("ASYNC", false, "flow_alpha");
second.setId(BigInteger.valueOf(13L));
second.setTargetId(BigInteger.valueOf(103L));
try {
compiler.compile(agent(List.of(first, second)));
Assert.fail("异步工具名冲突时应编译失败");
} catch (BusinessException e) {
Assert.assertTrue(e.getMessage().contains("flow_alpha_submit"));
}
}
private AgentToolRuntimeCompiler compiler() throws Exception {
AgentToolRuntimeCompiler compiler = new AgentToolRuntimeCompiler();
setField(compiler, "objectMapper", new ObjectMapper());
setField(compiler, "workflowToolExecutor", new StubWorkflowToolExecutor());
setField(compiler, "pluginToolExecutor", new StubPluginToolExecutor());
return compiler;
}
private Agent agent(AgentToolBinding binding) {
return agent(List.of(binding));
}
private Agent agent(List<AgentToolBinding> bindings) {
Agent agent = new Agent();
agent.setId(BigInteger.ONE);
agent.setToolBindings(bindings);
return agent;
}
private AgentToolBinding workflowBinding(String executionMode, boolean hitlEnabled, String toolName) {
AgentToolBinding binding = new AgentToolBinding();
binding.setId(BigInteger.valueOf(11L));
binding.setToolType(AgentToolType.WORKFLOW.name());
binding.setTargetId(BigInteger.valueOf(101L));
binding.setToolName(toolName);
binding.setEnabled(true);
binding.setHitlEnabled(hitlEnabled);
binding.setHitlConfigJson(Map.of("prompt", "确认执行?"));
binding.setOptionsJson(executionMode == null ? Map.of() : Map.of("executionMode", executionMode));
binding.setResourceSnapshot(Map.of(
"id", BigInteger.valueOf(101L),
"title", "客户检索工作流",
"description", "按关键词检索客户",
"englishName", "flow-alpha"
));
return binding;
}
private AgentToolBinding pluginBinding(String executionMode, String toolName) {
AgentToolBinding binding = new AgentToolBinding();
binding.setId(BigInteger.valueOf(12L));
binding.setToolType(AgentToolType.PLUGIN.name());
binding.setTargetId(BigInteger.valueOf(102L));
binding.setToolName(toolName);
binding.setEnabled(true);
binding.setOptionsJson(Map.of("executionMode", executionMode));
binding.setResourceSnapshot(Map.of(
"id", BigInteger.valueOf(102L),
"name", "插件工具",
"description", "调用插件",
"englishName", "plugin-tool"
));
return binding;
}
private List<String> toolNames(AgentToolRuntimeCompilation compilation) {
return compilation.getToolSpecs().stream().map(AgentToolSpec::getName).collect(Collectors.toList());
}
private void setField(Object target, String fieldName, Object value) throws Exception {
Field field = target.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(target, value);
}
private Tool testTool(String name, String description) {
Parameter parameter = new Parameter();
parameter.setName("keyword");
parameter.setDescription("关键词");
parameter.setType("string");
parameter.setRequired(true);
return Tool.builder()
.name(name)
.description(description)
.addParameter(parameter)
.function(arguments -> Map.of("ok", true))
.build();
}
private final class StubWorkflowToolExecutor extends WorkflowToolExecutor {
private StubWorkflowToolExecutor() {
super(null);
}
@Override
public Tool buildTool(Workflow workflow) {
return testTool(workflow.getEnglishName(), workflow.getDescription());
}
@Override
public AgentToolExecutionResult execute(Workflow workflow, Map<String, Object> arguments) {
return new AgentToolExecutionResult(Map.of("ok", true), "wf-run-1");
}
}
private final class StubPluginToolExecutor extends PluginToolExecutor {
@Override
public Tool buildTool(PluginItem pluginItem) {
return testTool(pluginItem.getEnglishName(), pluginItem.getDescription());
}
@Override
public AgentToolExecutionResult execute(PluginItem pluginItem, Map<String, Object> arguments) {
return new AgentToolExecutionResult(Map.of("ok", true), null);
}
}
}