Skip to content
Embabel 文档版本 0.3.0-SNAPSHOT 源快照 c7830366 更新于 2026-09-29

20 · 调用 Agent(Invoking Embabel Agents)

虽然很多示例都展示通过 Embabel shell、以 UserInput 方式调用 Agent, 但它们也可以被强类型地以编程方式调用。

这通常就是Web 应用里的用法。它也是最确定的方式 —— 由代码而非"LLM 对用户输入的评估"来决定调用哪个 Agent、以及怎么调用。

以编程方式创建 AgentProcess

你可以直接用 AgentPlatform 创建并执行 Agent 进程:

java
// 用 bindings 创建 agent 进程
AgentProcess agentProcess = agentPlatform.createAgentProcess(
        myAgent,
        new ProcessOptions(),
        Map.of("input", userRequest)
);

// 启动进程并等待完成
Object result = agentPlatform.start(agentProcess).get();

// 或者同步运行
AgentProcess completedProcess = agentProcess.run();
MyResultType result = completedProcess.last(MyResultType.class);

你也可以用可变参数对象创建进程并填充其输入 map:

java
// 从对象创建进程(就像在 web controller 里那样)
AgentProcess agentProcess = agentPlatform.createAgentProcessFrom(
        travelAgent,
        new ProcessOptions(),
        travelRequest,
        userPreferences
);

使用 AgentInvocation

AgentInvocation 提供了更高层、类型安全的调用 API。它会根据期望的结果类型自动找到合适的 Agent。

基本用法

java
// 用显式结果类型做简单调用
var invocation = AgentInvocation.create(agentPlatform, TravelPlan.class);

TravelPlan plan = invocation.invoke(travelRequest);

用命名输入调用

java
// 用命名的输入 map 调用
Map<String, Object> inputs = Map.of(
        "request", travelRequest,
        "preferences", userPreferences
);

TravelPlan plan = invocation.invoke(inputs);

自定义 ProcessOptions

配置详细程度(verbosity)、预算以及其它执行选项:

java
var processOptions = new ProcessOptions()
        .withVerbosity(new Verbosity()
                .withShowPrompts(true)
                .withShowLlmResponses(true)
                .withDebug(true));

var invocation = AgentInvocation.builder(agentPlatform)
        .options(processOptions)
        .build(TravelPlan.class);

TravelPlan plan = invocation.invoke(travelRequest);

设置在 ProcessOptions 上的 verbosity 会被子 Agent 调用继承。 子 Agent 与触发它的进程使用相同的 Verbosity —— 在顶层设置一次,就会贯穿整棵调用树。 GoalTool 与 AgentTool 属于入口点,无论父上下文如何,默认 showPrompts = true。

在调用时传入 Tool Call Context

用 ProcessOptions.withToolCallContext() 挂载带外元数据,它会贯穿整个 Agent 运行、传给每一个被调用的工具 —— 包括远程 MCP 工具(在那里它变成线上的 MCP _meta)。 这里适合放那些来自入站请求的横切基础设施关注点:auth token、tenant ID、correlation ID。

java
// 在 REST controller 或事件处理器里
var processOptions = new ProcessOptions()
        .withToolCallContext(Map.of(
                "authToken", request.getHeader("Authorization"),
                "tenantId", request.getHeader("X-Tenant-Id"),
                "correlationId", UUID.randomUUID().toString()
        ));

var invocation = AgentInvocation.builder(agentPlatform)
        .options(processOptions)
        .build(CustomerReport.class);

CustomerReport report = invocation.invoke(customerQuery);

这里设置的上下文,可以被任何声明了 ToolCallContext 参数的 @LlmTool 方法读取。 它也可以在 @Action 方法内部用 PromptRunner.withToolCallContext() 按交互补充;冲突时交互级的值优先。 完整的上下文流水线见 /reference/tools#tool-call-context。

异步调用

对长耗时操作,使用异步调用:

java
CompletableFuture<TravelPlan> future = invocation.invokeAsync(travelRequest);

// 完成时处理结果
future.thenAccept(plan -> {
    logger.info("Travel plan generated: {}", plan);
});

// 或者等待完成
TravelPlan plan = future.get();

Agent 选择机制

AgentInvocation 通过检查 Agent 的目标来自动找到它们:

  • 搜索平台中所有已注册的 Agent;
  • 找出目标会产出所请求结果类型的 Agent;
  • 使用找到的第一个匹配 Agent;
  • 如果没有合适的 Agent,抛错。

真实 Web 应用示例

下面是 Tripper 旅行规划应用中 AgentInvocation 的用法 —— 配合 htmx 做异步 UI 更新:

java
@Controller
public class TripPlanningController {

    private final AgentPlatform agentPlatform;
    private final ConcurrentHashMap<String, CompletableFuture<TripPlan>> activeJobs =
            new ConcurrentHashMap<>();
    private static final Logger logger =
            LoggerFactory.getLogger(TripPlanningController.class);
    private static final ConcurrentHashMap<String, TripPlan> tripResultCache =
            new ConcurrentHashMap<>();

    public TripPlanningController(AgentPlatform agentPlatform) {
        this.agentPlatform = agentPlatform;
    }

    @PostMapping("/plan-trip")
    public String planTrip(
            @ModelAttribute TripRequest tripRequest,
            Model model) {
        // 生成用于追踪的唯一 job ID
        String jobId = UUID.randomUUID().toString();

        // 用自定义选项创建 agent 调用
        var processOptions = new ProcessOptions()
                .withVerbosity(new Verbosity().withShowPrompts(true));
        var invocation = AgentInvocation.builder(agentPlatform)
                .options(processOptions)
                .build(TripPlan.class);

        // 启动异步 agent 执行
        CompletableFuture<TripPlan> future = invocation.invokeAsync(tripRequest);
        activeJobs.put(jobId, future);

        // 设置完成处理器
        future.whenComplete((result, throwable) -> {
            if (throwable != null) {
                logger.error("Trip planning failed for job {}", jobId, throwable);
            } else {
                logger.info("Trip planning completed for job {}", jobId);
            }
        });

        model.addAttribute("jobId", jobId);
        model.addAttribute("tripRequest", tripRequest);

        // 返回会轮询结果的 htmx 模板
        return "trip-planning-progress";
    }

    @GetMapping("/trip-status/{jobId}")
    @ResponseBody
    public ResponseEntity<Map<String, Object>> getTripStatus(@PathVariable String jobId) {
        CompletableFuture<TripPlan> future = activeJobs.get(jobId);
        if (future == null) {
            return ResponseEntity.notFound().build();
        }

        if (future.isDone()) {
            try {
                TripPlan tripPlan = future.get();
                activeJobs.remove(jobId);

                return ResponseEntity.ok(Map.of(
                        "status", "completed",
                        "result", tripPlan,
                        "redirect", "/trip-result/" + jobId
                ));
            } catch (Exception e) {
                activeJobs.remove(jobId);
                return ResponseEntity.ok(Map.of(
                        "status", "failed",
                        "error", e.getMessage()
                ));
            }
        } else if (future.isCancelled()) {
            activeJobs.remove(jobId);
            return ResponseEntity.ok(Map.of("status", "cancelled"));
        } else {
            return ResponseEntity.ok(Map.of(
                    "status", "in_progress",
                    "message", "Planning your amazing trip..."
            ));
        }
    }

    @GetMapping("/trip-result/{jobId}")
    public String showTripResult(
            @PathVariable String jobId,
            Model model) {
        // 从缓存或数据库中取出已完成的结果
        TripPlan tripPlan = tripResultCache.get(jobId);
        if (tripPlan == null) {
            return "redirect:/error";
        }

        model.addAttribute("tripPlan", tripPlan);
        return "trip-result";
    }

    @DeleteMapping("/cancel-trip/{jobId}")
    @ResponseBody
    public ResponseEntity<Map<String, String>> cancelTrip(@PathVariable String jobId) {
        CompletableFuture<TripPlan> future = activeJobs.get(jobId);

        if (future != null && !future.isDone()) {
            future.cancel(true);
            activeJobs.remove(jobId);
            return ResponseEntity.ok(Map.of("status", "cancelled"));
        } else {
            return ResponseEntity.badRequest()
                    .body(Map.of("error", "Job not found or already completed"));
        }
    }
}

关键模式:

  • 异步执行:用 invokeAsync() 避免阻塞 Web 请求;
  • 任务追踪:维护活跃 future 的 map 以便轮询状态;
  • htmx 集成:返回 htmx 可消费的状态更新,用于刷新 UI;
  • 错误处理:恰当的异常处理与用户反馈;
  • 资源清理:把已完成的任务从内存中移除;
  • Process Options:为生产使用配置 verbosity 与调试。

替代方案:直接创建 AgentProcess

对更简单的用例,你可以不经过 AgentInvocation,直接创建并启动 AgentProcess。 Tripper 应用就用了这种方式,它很适合 webhook 或表单提交场景,因为你想要:

  • 启动一个长时间运行的 Agent 进程;
  • 立刻返回一个进程 ID;
  • 用平台内置的 controller 轮询状态。
java
@Controller
@RequestMapping("/journey")
public class JourneyController {

    private final AgentPlatform agentPlatform;

    public JourneyController(AgentPlatform agentPlatform) {
        this.agentPlatform = agentPlatform;
    }

    @PostMapping("/plan")
    public String planJourney(@ModelAttribute JourneyPlanForm form, Model model) {
        // 把表单转换成领域对象
        TravelBrief travelBrief = new TravelBrief(
                form.getFrom(),
                form.getTo(),
                form.getDepartureDate(),
                form.getReturnDate(),
                form.getBrief()
        );

        // 找到合适的 agent
        Agent agent = agentPlatform.agents().stream()
                .filter(a -> a.getName().toLowerCase().contains("travel"))
                .findFirst()
                .orElseThrow(() -> new IllegalStateException("No travel agent found"));

        // 用输入 bindings 创建 agent 进程
        AgentProcess agentProcess = agentPlatform.createAgentProcessFrom(
                agent,
                new ProcessOptions(
                        new Verbosity().withShowPrompts(true),
                        Budget.DEFAULT // 或自定义预算
                ),
                travelBrief // 可变参数输入会被绑定到黑板
        );

        // 异步启动进程
        agentPlatform.start(agentProcess);

        // 把进程 ID 放进 model 以便轮询状态
        model.addAttribute("processId", agentProcess.getId());
        model.addAttribute("travelBrief", travelBrief);

        // 返回一个会轮询 /api/v1/process/{processId} 的视图
        return "processing";
    }
}

平台提供了内置的 REST 端点用于状态查询:

  • GET /api/v1/process/{processId} —— 返回进程状态、结果与相关 URL;
  • DELETE /api/v1/process/{processId} —— 终止一个运行中的进程;
  • GET /events/process/{processId} —— 进程事件的 SSE 流。

每个端点都可以通过配置单独关闭。把对应属性设为 false,该端点就会返回 HTTP 404:

properties
embabel.agent.platform.rest.process-status-enabled=false
embabel.agent.platform.rest.process-kill-enabled=false
embabel.agent.platform.rest.process-events-enabled=false

一个简单的状态轮询 controller 可以检查完成情况并重定向到结果页:

java
@Controller
public class ProcessStatusController {

    private final AgentPlatform agentPlatform;

    public ProcessStatusController(AgentPlatform agentPlatform) {
        this.agentPlatform = agentPlatform;
    }

    @GetMapping("/status/{processId}")
    public String checkStatus(
            @PathVariable String processId,
            @RequestParam String successView,
            @RequestParam String resultModelKey,
            Model model) {

        AgentProcess process = agentPlatform.getAgentProcess(processId);
        if (process == null) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Process not found");
        }

        switch (process.getStatus()) {
            case COMPLETED:
                model.addAttribute(resultModelKey, process.lastResult());
                return successView;

            case FAILED:
                model.addAttribute("error", "Process failed: " + process.getFailureInfo());
                return "error";

            case TERMINATED:
                model.addAttribute("error", "Process was terminated");
                return "error";

            default:
                // 仍在运行 —— 返回轮询视图
                model.addAttribute("processId", processId);
                return "processing";
        }
    }
}

两种方式各自适合什么:

方式最适合
AgentInvocation.invokeAsync()当你需要一个 CompletableFuture 来做程序化处理、链式组合,或与响应式框架集成时
直接创建 AgentProcessWebhook、表单提交,或用 REST/SSE 轮询状态的 UI 流程

Webhook 集成示例

对 webhook 触发的工作流(例如 JIRA、GitHub),直接创建进程的方式很好用:

java
@RestController
@RequestMapping("/webhook")
public class WebhookController {

    private final AgentPlatform agentPlatform;

    public WebhookController(AgentPlatform agentPlatform) {
        this.agentPlatform = agentPlatform;
    }

    @PostMapping("/jira/issue-created")
    public ResponseEntity<Map<String, String>> onJiraIssueCreated(
            @RequestBody JiraWebhookPayload payload) {

        // 找到处理 JIRA issue 的 agent
        Agent agent = agentPlatform.agents().stream()
                .filter(a -> a.getName().contains("JiraIssue"))
                .findFirst()
                .orElseThrow(() -> new IllegalStateException("No JIRA agent configured"));

        // 从 webhook payload 构造领域对象
        JiraIssue issue = new JiraIssue(
                payload.getIssue().getKey(),
                payload.getIssue().getFields().getSummary(),
                payload.getIssue().getFields().getDescription()
        );

        // 创建并启动 agent 进程
        AgentProcess process = agentPlatform.createAgentProcessFrom(
                agent,
                ProcessOptions.DEFAULT,
                issue
        );
        agentPlatform.start(process);

        // 返回进程 ID 用于状态追踪
        return ResponseEntity.accepted().body(Map.of(
                "processId", process.getId(),
                "statusUrl", "/api/v1/process/" + process.getId(),
                "sseUrl", "/events/process/" + process.getId()
        ));
    }
}

webhook 的调用方随后就可以轮询 /api/v1/process/{processId},或订阅 /events/process/{processId} 的 SSE 事件来跟踪进度。

Agent 也可以被暴露为 MCP server,供 Claude Desktop 之类的工具消费。

用 Autonomy 动态选择 Agent 与目标

Autonomy 类基于用户意图,提供 LLM 驱动的 Agent 与目标动态选择。 它不是由代码指定运行哪个 Agent,而是用 LLM 把可用 Agent 或目标与用户输入做排序,选出最佳匹配。

Embabel Shell 就是这样处理自然语言命令的。

执行模式

Autonomy 支持两种执行模式:

  • Closed 模式(chooseAndRunAgent):LLM 根据用户意图选出最合适的 Agent。 被选中的 Agent 隔离运行,只使用它自己的动作与目标。
  • Open 模式(chooseAndAccomplishGoal):LLM 从所有 Agent 的所有可用目标中选出最合适的目标。 随后 Embabel 组装一个动态 Agent,它可以使用任意 Agent 的任意动作来达成该目标。

Closed 模式示例

当你想要严格的 Agent 边界时,用 closed 模式:

java
@Service
public class IntentHandler {

    private final Autonomy autonomy;

    public IntentHandler(Autonomy autonomy) {
        this.autonomy = autonomy;
    }

    public AgentProcessExecution handleUserIntent(String userIntent) {
        // LLM 对所有 agent 排序,选出最匹配的
        return autonomy.chooseAndRunAgent(
                userIntent,
                ProcessOptions.DEFAULT
        );
    }
}

Open 模式示例

当你希望达成目标的方式最灵活时,用 open 模式:

java
@Service
public class GoalHandler {

    private final Autonomy autonomy;
    private final AgentPlatform agentPlatform;

    public GoalHandler(Autonomy autonomy, AgentPlatform agentPlatform) {
        this.autonomy = autonomy;
        this.agentPlatform = agentPlatform;
    }

    public AgentProcessExecution handleUserIntent(String userIntent) {
        // LLM 对所有 goal 排序,选出最匹配的
        // 然后从可用动作中组装一个 agent 来达成它
        return autonomy.chooseAndAccomplishGoal(
                ProcessOptions.DEFAULT,
                GoalChoiceApprover.APPROVE_ALL,
                agentPlatform, // 包含 goals 与 actions 的 AgentScope
                Map.of("userInput", new UserInput(userIntent)),
                new GoalSelectionOptions()
        );
    }
}

使用任意 Bindings

chooseAndAccomplishGoal 接受任意 bindings,并不限于 UserInput。 BindingsFormatter 负责从 bindings 中抽取意图文本用于目标排序:

java
public AgentProcessExecution processTask(Task task, Person person) {
    // bindings 可以是任意对象
    Map<String, Object> bindings = Map.of(
            "task", task,
            "person", person
    );

    return autonomy.chooseAndAccomplishGoal(
            ProcessOptions.DEFAULT,
            GoalChoiceApprover.APPROVE_ALL,
            agentPlatform,
            bindings,
            new GoalSelectionOptions(),
            BindingsFormatter.DEFAULT // 从 bindings 中抽取意图
    );
}

默认的 BindingsFormatter 按以下优先级抽取文本:

  1. 若对象实现了 PromptContributor,用 PromptContributor.contribution();
  2. 若对象实现了 HasInfoString,用 HasInfoString.infoString();
  3. 否则用 toString()。

你也可以提供自定义 formatter:

java
BindingsFormatter customFormatter = bindings -> {
    Task task = (Task) bindings.get("task");
    Person person = (Person) bindings.get("person");
    return String.format("Process task '%s' for %s", task.getDescription(), person.getName());
};

return autonomy.chooseAndAccomplishGoal(
        ProcessOptions.DEFAULT,
        GoalChoiceApprover.APPROVE_ALL,
        agentPlatform,
        bindings,
        new GoalSelectionOptions(),
        customFormatter
);

目标选择审批(Goal Choice Approval)

你可以要求在执行被选中的目标之前先经过审批:

java
// 只批准高置信度匹配
GoalChoiceApprover approver = GoalChoiceApprover.approveWithScoreOver(0.8);

// 或实现自定义审批逻辑
GoalChoiceApprover customApprover = request -> {
    if (request.getGoal().getName().contains("dangerous")) {
        return new GoalChoiceNotApproved("Dangerous goals require manual approval");
    }
    return GoalChoiceApproved.INSTANCE;
};

置信度阈值(Confidence Thresholds)

Autonomy 使用可配置的置信度阈值来过滤匹配结果。 如果没有 Agent 或目标超过阈值,会抛出 NoAgentFound 或 NoGoalFound 异常。

在 application.properties 中配置阈值:

properties
# Agent 选择的最低置信度(0.0 ~ 1.0)
embabel.agent.platform.autonomy.agent-confidence-cut-off=0.6

# 目标选择的最低置信度(0.0 ~ 1.0)
embabel.agent.platform.autonomy.goal-confidence-cut-off=0.6

或者按请求覆盖,使用 GoalSelectionOptions:

java
GoalSelectionOptions options = new GoalSelectionOptions(
        0.5,   // goalConfidenceCutOff —— 覆盖平台默认值
        null,  // agentConfidenceCutOff —— 沿用平台默认值
        false  // multiGoal —— 是否选择多个目标
);

Shell 用法

Embabel Shell 用 Autonomy 来实现 execute(x)与 choose-goal 命令:

bash
# Closed 模式(默认)—— 选出最佳 agent
x "Find a horoscope for Alice who is a Scorpio"

# Open 模式 —— 选出最佳 goal,使用任意动作
x "Find a horoscope for Alice who is a Scorpio" -o

# 只显示目标排序,不执行
choose-goal "Find a horoscope for Alice"

处理选择失败

java
try {
    return autonomy.chooseAndRunAgent(userIntent, ProcessOptions.DEFAULT);
} catch (NoAgentFound e) {
    // 没有 agent 以足够置信度匹配
    logger.info("No matching agent. Rankings: {}", e.getAgentRankings());
    return fallbackResponse();
} catch (NoGoalFound e) {
    // 没有 goal 以足够置信度匹配(open 模式)
    logger.info("No matching goal. Rankings: {}", e.getGoalRankings());
    return fallbackResponse();
} catch (GoalNotApproved e) {
    // 目标被审批器拒绝
    logger.info("Goal not approved: {}", e.getReason());
    return requiresApprovalResponse();
}

BytesRouter 是社区翻译站点,所有译文仅供参考,一切以官方英文原文为准。