Embabel 上手:Java Agent 是怎么干活的

一、Agent 与 Embabel 概览

1.什么是 Agent

Agent(智能体)不是"回答问题的大模型",而是一个能够围绕目标持续执行任务的软件系统。

一个完整的 Agent 通常包含:

•目标:最终要完成什么

•状态:当前已经知道什么

•推理与规划:下一步应该做什么

•工具:能够调用哪些外部能力

•记忆:保存任务过程和历史信息

•执行循环:观察结果并调整后续行动

•完成条件:如何判断任务已经结束

普通大模型调用通常是:

复制代码
用户输入 → LLM → 文本回答

Agent 的执行过程则是:

理解目标 → 感知信息 → 制定计划 → 执行行动 → 观察反馈 → 完成

例如,用户提出"帮我修改订单收货地址",Agent 可能需要:

1.从自然语言中提取订单号和新地址。

2.查询订单状态。

3.检索地址修改政策。

4.判断当前订单是否允许修改。

5.修改地址或创建人工工单。

6.返回操作结果和依据。

这里不仅涉及文本生成,还涉及状态管理、工具调用、业务决策和任务闭环。

2.Agent 与普通大模型应用的区别

普通大模型应用主要解决"生成什么内容",Agent 更关注"怎样完成一个目标"。

维度 普通大模型应用 Agent
输入 Prompt 目标、状态和上下文
输出 一段文本 任务结果或业务状态
执行过程 通常一次模型调用 多步骤循环执行
工具调用 可选 通常是核心能力
状态管理 主要依赖上下文窗口 显式保存中间状态
路径 通常固定 可以根据结果动态调整
完成判断 模型生成结束 Goal 达成或触发终止条件
错误处理 重试模型调用 重规划、降级或人工介入

例如,一个普通大模型可以告诉用户"应该怎样修改地址",但生产级 Agent 需要真正完成:

复制代码
理解请求 → 验证身份 → 查询订单 → 判断政策
→ 修改地址或创建工单 → 返回操作凭证

因此,Agent 的关键不是让模型"更聪明",而是系统具备了完成任务所需的执行能力和控制机制。

3.Workflow、ReAct、GOAP、Supervisor、Utility AI的区别

这五种模式解决的是不同问题

复制代码
Workflow:代码按剧本执行
ReAct:LLM 边想边调用工具
GOAP:规划器奔着 Goal 找路径
Supervisor:上层 LLM 调度 Actions
Utility AI:每次选择当前最值得做的 Action
模式 谁决定下一步 是否需要明确 Goal 决策方式 适用场景
Workflow 开发者 按预先定义的流程执行 固定审批、支付、数据同步
ReAct LLM 通常不强制 推理、调用工具、观察结果、继续推理 搜索、排障、短链路探索
GOAP 规划器 根据前置条件和效果搜索到 Goal 的路径 目标明确、存在多条执行路径
Utility AI 效用计算器 计算每个可执行 Action 的当前价值,选择最高者 事件响应、持续探索、动态运营
Supervisor 上层 LLM 可以有 根据语义动态选择 Action 或 Subagent 开放任务、动态分工、多 Agent 协作
Workflow:预先定义流程

Workflow 由开发人员提前确定执行顺序和分支。优点是确定性强、容易测试和审计。缺点是路径需要提前穷举,环境变化时通常需要修改流程代码。

css 复制代码
A → B → 判断条件 → C 或 D → E

适合:

•审批流程

•支付流程

•数据同步

•固定审核链路

•强合规业务

示例代码:

typescript 复制代码
@Service
public class TicketWorkflow {

    public ResolvedTicket process(Ticket ticket) {
        // 第一步:分类
        TicketCategory category = classify(ticket);

        // 第二步:固定分支
        if (category.urgency() >= 9) {
            return handleCritical(ticket);
        }

        if ("BUG".equals(category.name())) {
            return handleBug(ticket);
        }

        return handleGeneral(ticket);
    }

    private TicketCategory classify(Ticket ticket) {
        String description = ticket.description().toLowerCase();

        if (description.contains("down")) {
            return new TicketCategory("CRITICAL", 10);
        }

        if (description.contains("bug")) {
            return new TicketCategory("BUG", 6);
        }

        return new TicketCategory("GENERAL", 2);
    }

    private ResolvedTicket handleCritical(Ticket ticket) {
        return new ResolvedTicket(
            ticket.id(),
            "Escalated to on-call engineer",
            "CRITICAL_RESPONSE_TEAM"
        );
    }

    private ResolvedTicket handleBug(Ticket ticket) {
        return new ResolvedTicket(
            ticket.id(),
            "Bug created in issue tracker",
            "ENGINEERING_TEAM"
        );
    }

    private ResolvedTicket handleGeneral(Ticket ticket) {
        return new ResolvedTicket(
            ticket.id(),
            "FAQ response sent",
            "SUPPORT_TEAM"
        );
    }
}
ReAct:模型边推理边调用工具

ReAct 是 Reasoning + Acting,LLM 根据当前观察结果决定下一步调用哪个 Tool,即模型在推理和行动之间循环:

复制代码
思考下一步
   ↓
调用 Tool
   ↓
观察 Tool 返回
   ↓
继续思考
   ↓
调用下一个 Tool

ReAct 的灵活性较高,但执行路径受模型影响,生产环境需要限制工具范围、调用次数、成本和副作用。

适合:

•开放式搜索

•故障排查

•数据探索

•短链路工具组合

•难以提前确定步骤的任务

示例代码:

typescript 复制代码
public class TicketTools {

    @LlmTool(description = "查询客户等级和未关闭工单数量")
    public CustomerContext queryCustomer(String customerId) {
        return new CustomerContext(
            customerId,
            "VIP",
            2
        );
    }

    @LlmTool(description = "查询系统是否存在服务故障")
    public String querySystemStatus() {
        return "payment-service is healthy";
    }

    @LlmTool(description = "将紧急工单升级给值班工程师")
    public String escalate(String ticketId) {
        return "Ticket " + ticketId + " escalated";
    }

    @LlmTool(description = "在问题跟踪系统中创建 Bug")
    public String createBug(String ticketId, String description) {
        return "BUG-" + ticketId;
    }
}


@Agent(description = "使用工具处理客服工单")
public class ReActTicketAgent {

    private final TicketTools ticketTools;

    public ReActTicketAgent(TicketTools ticketTools) {
        this.ticketTools = ticketTools;
    }

    @AchievesGoal(description = "完成工单处理")
    @Action
    public ResolvedTicket resolve(
            Ticket ticket,
            OperationContext context) {

        return context.ai()
            .withDefaultLlm()
            .withToolObject(ticketTools)
            .createObject("""
                处理下面的客服工单。

                你可以根据需要调用工具:
                - 查询客户信息
                - 查询系统状态
                - 升级紧急工单
                - 创建 Bug

                根据工具返回结果决定下一步。
                最终返回 ResolvedTicket。

                工单:%s
                """.formatted(ticket),
                ResolvedTicket.class
            );
    }
}

可能出现的运行过程:

复制代码
LLM:先查询客户信息
Tool:返回 VIP 客户

LLM:再查询系统状态
Tool:服务正常

LLM:判断为产品 Bug
Tool:创建 Bug

LLM:生成 ResolvedTicket

这里真正做决策的是 LLM。

GOAP:围绕 Goal 搜索路径

GOAP 是 Goal-Oriented Action Planning,即目标导向行动规划,GOAP 的核心思想就是把规划变成寻路游戏:

把你所有可用的 Action 当作地图上的节点,每个 Action 都有"进门条件"(前置条件)和"出门效果"(后置效果),然后用 A* 搜索找到从当前位置到目标的最短路径。A* 搜索算法(A-star search algorithm)是一种在图形或网格中寻找起点到终点最低成本(最短路径)的启发式搜索算法

开发者声明:

•当前有哪些事实

•系统有哪些 Action

•每个 Action 需要什么输入

•每个 Action 会产生什么结果

•最终 Goal 是什么

规划器根据这些信息动态搜索路径:

复制代码
当前状态 + Action 能力图 → 可达路径 → Goal

GOAP 与 ReAct 的核心区别是:GOAP 的路径由规划器根据显式能力和状态计算,不需要让 LLM 决定每一个业务步骤。

适合:

•目标明确

•存在多条可达路径

•环境状态会发生变化

•需要重规划

•需要解释和测试执行路径

示例代码:

typescript 复制代码
@Agent(
    description = "通过目标规划处理客服工单",
    planner = PlannerType.GOAP
)
public class GoapTicketAgent {

    @Action(cost = 0.1)
    public TicketCategory classify(Ticket ticket) {
        String text = ticket.description().toLowerCase();

        if (text.contains("down")) {
            return new TicketCategory("CRITICAL", 10);
        }

        if (text.contains("bug")) {
            return new TicketCategory("BUG", 6);
        }

        return new TicketCategory("GENERAL", 2);
    }

    @Action(cost = 0.2)
    public CustomerContext loadCustomer(Ticket ticket) {
        return new CustomerContext(
            ticket.customerId(),
            "VIP",
            2
        );
    }

    @Action(cost = 0.4)
    public TechnicalEvidence investigate(
            Ticket ticket,
            TicketCategory category) {

        return new TechnicalEvidence(
            "Configuration error",
            "Restore previous configuration"
        );
    }

    @AchievesGoal(description = "得到完整的工单处理结果")
    @Action
    public ResolvedTicket resolve(
            Ticket ticket,
            TicketCategory category,
            CustomerContext customer,
            TechnicalEvidence evidence) {

        String team = category.urgency() >= 9
            ? "CRITICAL_RESPONSE_TEAM"
            : "SUPPORT_TEAM";

        return new ResolvedTicket(
            ticket.id(),
            evidence.suggestion(),
            team
        );
    }
}

规划器看到最终 Goal Action 需要:

复制代码
Ticket
TicketCategory
CustomerContext
TechnicalEvidence

然后根据类型依赖反向寻找生产这些对象的 Action:

markdown 复制代码
                       ┌─ classify ───────→ TicketCategory ─┐
Ticket ────────────────┼─ loadCustomer ───→ CustomerContext ├─→ resolve
                       └─ investigate ─────→ TechnicalEvidence ┘

它可能形成:

复制代码
classify
→ investigate
→ loadCustomer
→ resolve

也可能先执行 loadCustomer。重要的不是代码书写顺序,而是对象依赖和 Goal 是否可达。

Supervisor:由上层模型动态分派

Supervisor 通常是一种多 Agent 协作模式,核心思想是多 Agent 系统中的"管理者":自己不一定完成所有具体工作,而是通过任务拆解、动态分派、结果评估和循环协调,让多个专业 Agent 共同完成目标,使用一个上层 LLM 判断:

•需要完成哪些子任务

•应该调用哪个 Agent 或 Action

•是否继续、停止或重新分配任务

复制代码
用户任务
   ↓
Supervisor LLM
   ├── Research Agent
   ├── Coding Agent
   └── Review Agent

典型过程:

Supervisor 只是按照固定步骤调用三个普通 Java 方法,那么它更像 Workflow;如果它能动态选择、分派和协调具备自主决策能力的 Subagent,才是典型的 Supervisor 多 Agent 模式

适合高度开放、需要动态分工的任务,例如复杂调研、软件开发和跨领域分析。

它的灵活性最高,但模型调用成本、行为不确定性和治理难度也更高。

示例代码:

kotlin 复制代码
@Agent(
    description = "由 Supervisor 调度工单调查和处理",
    planner = PlannerType.SUPERVISOR
)
public class SupervisorTicketAgent {

    @Action(description = "查询客户等级、历史记录和未解决工单")
    public CustomerContext researchCustomer(
            Ticket ticket,
            Ai ai) {

        return ai.withDefaultLlm()
            .createObject(
                "查询并整理客户背景:" + ticket.customerId(),
                CustomerContext.class
            );
    }

    @Action(description = "分析工单描述并调查可能的技术原因")
    public TechnicalEvidence investigateProblem(
            Ticket ticket,
            Ai ai) {

        return ai.withDefaultLlm()
            .createObject(
                "调查下面工单的技术原因:" + ticket.description(),
                TechnicalEvidence.class
            );
    }

    @Action(description = "根据工单内容判断分类和紧急程度")
    public TicketCategory classify(
            Ticket ticket,
            Ai ai) {

        return ai.withDefaultLlm()
            .createObject(
                "判断工单分类和紧急程度:" + ticket.description(),
                TicketCategory.class
            );
    }

    @AchievesGoal(description = "综合已有信息生成最终处理结果")
    @Action(description = "汇总客户、分类和技术调查结果")
    public ResolvedTicket compileResolution(
            Ticket ticket,
            CustomerContext customer,
            TicketCategory category,
            TechnicalEvidence evidence,
            Ai ai) {

        return ai.withDefaultLlm()
            .createObject("""
                综合以下信息生成工单处理结果:

                工单:%s
                客户:%s
                分类:%s
                技术证据:%s
                """.formatted(ticket, customer, category, evidence),
                ResolvedTicket.class
            );
    }
}

Supervisor LLM 看到的能力类似:

scss 复制代码
researchCustomer(Ticket) → CustomerContext
investigateProblem(Ticket) → TechnicalEvidence
classify(Ticket) → TicketCategory
compileResolution(...) → ResolvedTicket

它可能选择:

复制代码
classify
→ investigateProblem
→ researchCustomer
→ compileResolution

也可能根据输入先调查客户。

Utility AI:没有固定终点、持续选择当前最有价值的动作

Utility AI 是一种基于效用评分的动态决策机制,而不是一套固定流程。它根据当前状态对候选动作进行评分,使 Agent 在运行时选择综合效用最高的下一步行动,核心思想就是不先搜索完整路径,而是在每一步选择当前 value - cost 最大的可执行 Action。

典型过程:

它通过一个简单的四步循环来做出决策,而不是按照固定的流程图死板执行:

•感知环境: 收集智能体自身和周围环境的数据(例如:NPC当前的血量、与敌人的距离、子弹数量)。

•计算效用: 将这些数据输入到评分曲线(曲线函数)中,计算出每个候选行为的"效用值"(分值在 0 到 1 之间)。

•行为评估: 比如"攻击"得分 0.8,"逃跑"得分 0.3,"加血"得分 0.9。

•执行最高分: 智能体最终决定去执行最高分的行为(在这个例子中是"加血")。

适合:

•智能客服和工单处理

◦根据工单状态选择处理策略:(立即升级、分配工程师、知识库自动回复、请求补充信息)

•游戏角色决策

◦候选动作:攻击、逃跑、治疗、寻找掩体。

◦状态变量:血量、敌人距离、弹药、危险程度

•告警和故障处置

◦系统出现多个告警时,动态决定优先处理哪个(数据库不可用、磁盘使用率、接口响应变慢、非核心服务异常)

示例代码:

typescript 复制代码
@Agent(
    description = "根据行动效用处理客服工单",
    planner = PlannerType.UTILITY
)
public class UtilityTicketAgent {

    @Action(
        description = "读取客户历史",
        value = 0.8,
        cost = 0.1
    )
    public CustomerContext loadCustomerHistory(Ticket ticket) {
        return new CustomerContext(
            ticket.customerId(),
            "VIP",
            2
        );
    }

    @Action(
        description = "收集技术诊断信息",
        value = 0.9,
        cost = 0.3
    )
    public TechnicalEvidence collectDiagnostics(Ticket ticket) {
        return new TechnicalEvidence(
            "Database connection pool exhausted",
            "Increase pool size and restart service"
        );
    }

    @Action(
        description = "判断工单分类",
        value = 0.7,
        cost = 0.05
    )
    public TicketCategory classify(Ticket ticket) {
        boolean critical = ticket.description()
            .toLowerCase()
            .contains("down");

        return critical
            ? new TicketCategory("CRITICAL", 10)
            : new TicketCategory("GENERAL", 2);
    }

    @AchievesGoal(description = "完成工单处理")
    @Action(
        description = "根据完整证据解决工单",
        value = 1.0,
        cost = 0.1
    )
    public ResolvedTicket resolve(
            Ticket ticket,
            CustomerContext customer,
            TicketCategory category,
            TechnicalEvidence evidence) {

        return new ResolvedTicket(
            ticket.id(),
            evidence.suggestion(),
            category.urgency() >= 9
                ? "CRITICAL_RESPONSE_TEAM"
                : "SUPPORT_TEAM"
        );
    }
}

初始只有 Ticket 时,三个 Action 都可执行:

Action value cost 净价值
loadCustomerHistory 0.80 0.10 0.70
collectDiagnostics 0.90 0.30 0.60
classify 0.70 0.05 0.65

所以第一步会选择:

复制代码
loadCustomerHistory:0.70

执行后重新计算,再选择:

复制代码
classify:0.65

然后:

复制代码
collectDiagnostics:0.60

当所需对象齐备后,resolve() 才变成可执行 Action:

ini 复制代码
resolve:1.00 - 0.10 = 0.90

于是得到最终 ResolvedTicket。

官方定义的基础净效用计算为:

ini 复制代码
net value = value - cost

规划器每一步选择当前可执行 Action 中净价值最高的一个。

4.Embabel 的定位

Embabel 是 Spring 创始人 Rod Johnson 打造的 JVM 原生 Agent 框架。我们可以用一句话来定义它:Embabel 就是声明式编程 + 自动规划的在 JVM 上运行的 Agent 框架。它关注的不是单次模型调用,而是如何将大模型能力安全地嵌入真实业务系统。

你在领域模型即 Java Bean 里写 @Action(做什么,描述能力)、@Condition(什么时候可以做,描述执行条件),@Goal(目标是什么),它用 OODA 循环自动规划执行 Agent。

Embabel 的核心概念包括:

•@Agent:定义一个 Agent

•@Action:声明可规划的业务能力

•@AchievesGoal:声明能够达到最终目标的 Action

•Domain Model:描述业务事实

•Blackboard:保存一次执行过程中的状态

•Planner:根据状态搜索可达路径

•AgentProcess:管理一次完整执行

•Tool:提供给 LLM 调用的能力

•Subagent:执行具有独立 Goal 的子任务

概念 含义 示例
Domain Model Agent运行过程中使用和产生的领域对象 用户需求、研究报告、审核结果
Action Agent能够执行的一个步骤 搜索资料、生成报告、审核内容
Condition Action执行或Goal完成所依赖的条件 已取得研究资料、报告已审核
Goal Agent最终需要达成的状态 得到一份审核通过的报告
Plan 为达成Goal动态组合出的Action序列 搜索→撰写→审核→修改

一个简化的 Embabel Agent:

kotlin 复制代码
@Agent(description = "处理客户订单请求")
class OrderSupportAgent {

    @Action
    SupportRequest extract(UserInput input) {
        return requestExtractor.extract(input.content());
    }

    @Action
    OrderEvidence queryOrder(SupportRequest request) {
        return orderService.query(request.orderId());
    }

    @AchievesGoal(description = "返回订单处理结果")
    @Action
    SupportOutcome respond(
            SupportRequest request,
            OrderEvidence evidence) {
        return outcomeService.create(request, evidence);
    }
}

框架可以根据方法参数和返回类型推导:

erlang 复制代码
UserInput
   ↓ extract
SupportRequest
   ↓ queryOrder
OrderEvidence
   ↓ respond
SupportOutcome(Goal)

Embabel 的重点不是替代业务代码,而是把 LLM、领域服务、工具和规划能力组织在一起。

5.Embabel 与 Spring AI 的关系

作为 Java 开发者,我们用 Spring Boot 快速搭建项目,用 Spring Data 访问数据库,用 Spring Security 保护应用安全等等。20 年来,Spring 生态已经成为企业级开发的标准。而现在,AI 浪潮席卷而来。作为 Java 开发者,我们有一个天然的优势:我们不需要从零开始学习一套全新的生态系统。Spring 生态已经在扩展,Spring AI 作为基础设施层加入进来,而 Spring 之父 Rod Johnson 又带来了 Embabel------一个站在 Spring AI 肩膀上的智能体框架。

Spring 生态远不止是一个 IoC 容器------它是一个完整的企业级开发生态系统。Spring 生态的真正价值不在于它提供了多少功能,而在于两个核心点:

•一是降低认知负担,你不需要学 10 种不同的数据访问方式,Spring Data 用一套统一的抽象解决了这个问题。同样,Spring Security 用一套方式解决了各种安全场景。

•二是渐进式能力叠加,你可以先只用 Spring Boot,然后需要数据访问时加 Spring Data,需要安全时加 Spring Security,需要微服务时加 Spring Cloud。每一步都是渐进的,不会让你重写代码。

Spring AI:AI 集成的基础设施层

Spring AI 是基础设施层,不是应用框架层,Spring AI 的价值不是提供新的大模型,而是通过统一抽象、自动配置和生态集成,降低 Java 应用接入模型、RAG 与工具调用的成本,如果直接写代码调用 OpenAI 的 API,需要处理 API 认证、处理各种模型参数、解析 JSON 响应、处理流式输出、换模型提供商时(比如从 OpenAI 换到 Anthropic)需要重写一堆代码适配不同的模型,Spring AI 就是来解决这些问题的,它提供了一套统一的抽象,让我们可以用同样的代码调用不同的 AI 模型。它的核心设计理念是:将 Spring 生态系统设计原则(如可移植性和模块化设计)应用于 AI 领域,并推广使用 POJO(Plain Ordinary Java Object,简单的 Java 对象 / 普通 JavaBeans)作为 AI 领域应用程序的构建块。Spring AI 用 Java 开发者熟悉的方式在做 AI。

Spring AI 的核心能力

1.多模型提供商支持 支持 OpenAI、Anthropic、Amazon Bedrock、Google Vertex AI、Ollama 等模型平台。应用通过统一方式调用不同模型,切换模型通常只需调整依赖或配置。

2.统一的模型抽象 就像 JDBC 统一了数据库访问方式,Spring AI 为不同厂商的模型提供统一 API。开发者不必针对每个模型重复编写适配代码。

3.完整的 RAG 支持 集成多种向量数据库,并提供文档加载、内容分块、Embedding、向量存储、相似度检索和上下文增强等能力,帮助开发者快速构建企业知识库问答。

4.Tool Calling 工具调用 可以将现有 Java 方法、业务服务和外部 API 暴露给 LLM,让模型根据用户意图选择并调用工具,将语言理解与真实业务操作连接起来。

5.Spring Boot 自动配置 通过 Starter 引入依赖,配置模型地址和 API Key,即可获得所需的模型客户端与相关组件,减少大量初始化和适配代码。

6.与 Spring 生态无缝集成 可以直接复用 Spring 的依赖注入、配置管理、AOP、事务、安全、监控和测试能力,这正是熟悉的"Spring 开发体验"。

Embabel:Spring 生态中的 Agent 框架

Embabel站在 Spring AI 肩膀上的智能体编排框架,最底层是 Spring 生态,我们已经拥有的一切。中间层是 Spring AI,AI 集成的基础设施层。最上层是 Embabel,智能体编排的应用框架层。

为什么需要 Embabel?而不是直接用 Spring AI?

1.更高级的动态规划 Spring AI 提供模型调用能力,但复杂任务的执行顺序通常仍需开发者自己控制。 Embabel 引入 Goal、Action、Condition 和 Planner,根据当前状态动态生成计划,并在每个 Action 完成后重新规划。它不仅支持 GOAP,还支持 Utility、Supervisor 等规划策略。

css 复制代码
Spring AI:开发者编写 A → B → C
Embabel:定义目标和能力,Planner 动态选择 A、B、C 的组合

1.更好的扩展性和复用性 传统 Workflow 增加一个步骤,往往需要修改原有流程图或条件分支。 Embabel 可以通过增加新的:Action、Goal、Condition、Domain Model、扩展系统能力,而不一定需要修改已有 Action。

复制代码
原有能力:搜索 → 分析 → 生成报告
新增能力:数据库查询

Planner 可以自动判断是否将"数据库查询"加入计划

1.强类型和领域模型 很多 Agent 框架主要通过字符串、Map 或 JSON 在节点之间传递信息。 Embabel 使用 Java/Kotlin 类型作为 Action 的输入输出:

typescript 复制代码
@Action
public Analysis analyze(
        UserQuestion question,
        KnowledgeContext context
) {
    return new Analysis(...);
}

•它带来的价值包括:

•编译期类型检查

•IDE 自动补全

•安全重构

•明确的 Action 输入输出契约

•领域对象可以包含业务行为

•Planner 也可以根据类型关系判断 Action 是否具备执行条件

1.编程模型与运行平台分离 Embabel 将 Agent 的定义与底层运行机制分开:

sql 复制代码
编程模型:Agent、Goal、Action、Condition
运行平台:AgentPlatform、AgentProcess、Planner、Blackboard

•同一套 Agent 代码可以在本地运行,也可以接入生产环境中的:状态持久化

•生命周期管理

•重试机制

•事件监听

•可观测性

•分布式执行能力

1.支持多模型组合 复杂 Agent 不一定所有任务都应该使用同一个模型。 Embabel 可以针对不同 Action 使用不同模型进行平衡:

◦效果

◦成本

◦延迟

◦隐私

◦稳定性

2.直接复用 Spring 和 JVM 生态 Embabel 本身基于 Spring,因此 Agent 可以直接使用企业应用已有能力:

◦Spring 依赖注入

◦Spring AOP

◦事务管理

◦Spring Data

◦Spring Security

◦数据库和缓存

◦消息中间件

◦企业内部 Java 服务

3.从设计阶段支持测试 直接使用 LLM 编写复杂执行逻辑时,业务代码、Prompt、工具调用和流程控制容易混在一起。 Embabel 将任务拆成独立 Action,因此可以分别测试:

◦普通 Java 业务逻辑

◦单个 Action

◦Prompt

◦Tool 调用

◦Planner 生成的路径

◦Agent 端到端执行结果

6.代码对比:手动编排 vs 自动规划

Spring AI(手动编排),自己写流程的每一步:先做什么,再做什么

arduino 复制代码
@Service
public class SpringAIWeatherService {
    
    private final ChatClient chatClient;
    private final WeatherApiClient weatherApi;
    
    public SpringAIWeatherService(ChatClient chatClient,
                              WeatherApiClient weatherApi) {
        this.chatClient = chatClient;
        this.weatherApi = weatherApi;
    }
    
    public String getWeatherResponse(String city) {
        // 步骤1:先调用天气API
        WeatherData weather = weatherApi.fetch(city);
        
        // 步骤2:构建提示词
        String prompt = String.format(
            "用自然语言描述天气: %s", weather);
        
        // 步骤3:调用LLM
        return chatClient.prompt(prompt).call().content();
    }
}

Embabel(自动规划),用 Embabel,你只需要定义你有什么 Action(能力),以及你想达成什么 Goal(目标)。然后 Embabel 自动规划:需要调用哪些 Action,按什么顺序调用。

二、Embabel 核心架构

接入层

接入层负责把任务交给 Embabel,这一层只负责"任务从哪里来",不负责决定任务怎样完成:

•REST API

•Spring Shell

•定时任务

•MQ 消息

•业务代码直接调用

•其他 Agent 调用

Agent Platform 平台层

AgentPlatform 是整个框架的运行平台,类似 Embabel 的"容器"。

主要职责包括:

•Agent 注册与发现

•Agent、Goal 的选择

•创建 AgentProcess

•管理执行生命周期

•发布执行事件

•异常处理

•可观测性支持

三种常见运行模式:

模式 含义
Focused 调用方明确指定 Agent 或 Goal
Closed 平台从注册的 Agent 中动态选择
Open 平台组合可用 Goal 和 Action 完成任务

可以把 AgentPlatform 类比为:

Spring 容器负责管理 Bean,AgentPlatform 负责管理和运行 Agent

描述"系统具备哪些智能能力"

Agent 定义层

"系统具备哪些智能能力"

Agent

Agent 是一个领域能力集合,内部组织 Goal、Action 和 Condition。

kotlin 复制代码
@Agent(description = "负责处理客户工单")
public class TicketAgent {
}

Agent 更像一个能力边界,并不等于每一次运行实例。

Goal

Goal 描述期望得到的最终结果:

复制代码
完成工单处理
生成研究报告
制定旅行计划

它强调"要得到什么",不规定"严格按照什么顺序执行"。

Action

Action 是 Planner 可以组合和选择的基本执行单元:

typescript 复制代码
@Action
public ClassifiedTicket classify(Ticket ticket) {
    // 分类逻辑
}

Action 可以执行普通 Java 代码,也可以调用:

•LLM

•Tool

•MCP

•RAG

•数据库

•远程服务

•Subagent

Condition

Condition 表示当前是否满足某个条件:

复制代码
工单是否完成分类
是否已经找到知识
是否需要人工审批
Goal 是否已经完成

它可以作为 Action 的前置条件,也可以作为 Goal 的完成条件。

Domain Model

领域模型是 Action 之间交换的类型化数据:

arduino 复制代码
public record Ticket(String id, String content) {}

public record ClassifiedTicket(
        Ticket ticket,
        String category
) {}

public record ResolvedTicket(
        String id,
        String resolution
) {}

类型不仅是数据结构,也参与规划过程。

规划与运行时层

Embabel 的核心

AgentProcess

AgentProcess 表示一次具体的 Agent 运行。

复制代码
Agent:能力定义,可以被重复使用
AgentProcess:某一次任务的运行实例

例如,处理 100 个工单:

复制代码
1 个 TicketAgent 定义
100 个独立的 AgentProcess
100 个独立的 Blackboard

Blackboard

Blackboard 它是类型化的共享工作区,保存当前任务的实际数据:

•用户输入

•已知事实

•领域对象

•Action 中间结果

•工具调用结果

•RAG 检索结果

•条件状态

•最终结果

复制代码
初始 Blackboard
└── Ticket

执行分类 Action 后
├── Ticket
└── ClassifiedTicket

执行知识检索 Action 后
├── Ticket
├── ClassifiedTicket
└── KnowledgeContext

执行处理 Action 后
├── Ticket
├── ClassifiedTicket
├── KnowledgeContext
└── ResolvedTicket

因此,Blackboard 可以理解为"上下文",但它比普通 Prompt Context 更精确:

普通 LLM Context Blackboard
主要是文本消息 主要是类型化对象和状态
给 LLM 阅读 给 Planner、Action、Condition 共同使用
受 Token 窗口限制 可以独立于 Prompt 长期保存
语义比较松散 可以按名称、类型进行绑定 WorldState

WorldState 是根据 Blackboard 推导出来的逻辑状态:

ini 复制代码
ticketReceived = true
ticketClassified = true
knowledgeFound = false
ticketResolved = false
Blackboard WorldState
保存对象和事实 保存条件的判断结果
数据视角 规划视角
ClassifiedTicket 对象 ticketClassified=true

Planner

Planner 根据以下信息选择可执行路径:

•当前 WorldState

•当前 Blackboard

•目标 Goal

•可用 Action

•Action 前置条件与效果

•Action 成本和价值

•类型依赖关系

Action Runner

Action Runner 负责真正执行 Planner 选出的 Action,包括:

•从 Blackboard 绑定方法参数

•调用 Action 方法

•处理返回值

•将输出写入 Blackboard

•记录执行事件

•处理异常和结果

规划策略层

Planner 是可替换的,Action、Goal 和 Blackboard 不必因为规划算法变化而重写。同一组 Action 可以由不同 Planner 调度。

Planner 决策方式
GOAP 根据前置条件、效果和成本寻找通往 Goal 的路径
Utility 选择当前净效用最高的 Action
Hybrid Utility 式选择 Action,Goal 达成后停止
Supervisor LLM 根据 Action 描述和类型动态选择调用顺序
能力与集成层

这一层是 Action 执行时可以使用的能力。

LLM / AI

负责:

•语义理解

•内容生成

•结构化对象生成

•信息抽取

•分类和判断

•Agentic Tools

Agentic Tool 是提供给 LLM 或 Agent 使用的能力,协调其他工具的工具,例如:

•SimpleAgenticTool

•PlaybookTool

•StateMachineTool

类型 控制方式 适用场景
SimpleAgenticTool 所有子工具立即可见 简单探索和短链路组合
PlaybookTool 满足条件后逐步解锁工具 调研、审核、分阶段任务
StateMachineTool 根据明确状态开放工具 订单、审批、发布等状态流程

示例代码

scss 复制代码
SimpleAgenticTool researchTool =
    new SimpleAgenticTool(
        "researchTopic",
        "搜索资料并生成调研报告"
    )
    .withTools(
        searchTool,
        ragTool,
        summarizeTool
    );

context.ai()
    .withTool(researchTool)
    .generateText("分析新能源汽车行业");

MCP

MCP 是外部工具的标准接入方式:

arduino 复制代码
Embabel Action / LLM
        ↓
MCP Client
        ↓
MCP Server
        ↓
GitHub、数据库、搜索、文件系统

MCP 属于能力接入层,不负责 Embabel 的顶层规划。

RAG

RAG 为 Agent 提供企业知识:

复制代码
知识库检索 → 相关文档 → Action/LLM → 类型化结果

RAG 是知识获取能力,不是 Planner。

Subagent

Subagent 是可以被委派子任务的独立 Agent:

复制代码
研究 Agent
编码 Agent
审核 Agent
数据分析 Agent

它通常通过以下方式接入:

•Action 调用 Subagent

•Supervisor 调度 Subagent

•将 Subagent 包装成 Tool

Subagent 属于多 Agent 协作能力,不是基础 GOAP 模型的必要组成部分

基础设施层

Embabel 构建在 Spring 和 JVM 生态之上,因此可以直接复用:

•Spring Bean 和依赖注入

•Spring AI

•模型提供商

•数据库与事务

•向量数据库

•HTTP Client

•消息中间件

•OpenTelemetry

•Metrics 和 Tracing

这一层负责"能力怎么落地",上层负责"能力如何组合"。

三、Embabel核心流程

三种模式的核心区别,是谁来决定 Agent 和 Goal,以及允许使用多大的能力范围

模式 Agent 谁选择 Goal 谁选择 Action 范围 确定性
Focused 业务代码指定 由指定 Agent 决定 指定 Agent 内部 最高
Closed LLM 根据意图选择 被选 Agent 内部 只能使用被选 Agent 中等
Open 不固定单个 Agent LLM 从全部 Goal 中选择 可以组合多个 Agent 的 Action 最低
Focused:明确指定 Agent

核心思想:

业务代码明确知道要运行哪个 Agent,不需要 LLM 选择 Agent。

适合:

•REST API

•定时任务

•Webhook

•订单、支付、审批等确定性业务

•对审计和稳定性要求较高的场景

css 复制代码
业务代码
   ↓ 明确指定
TicketAgent
   ↓ 内部规划
Action A → Action B → Goal

示例代码:

arduino 复制代码
@Agent(description = "处理客户工单")
public class TicketAgent {

    public record TicketRequest(String id, String content) {}

    public record ClassifiedTicket(
            String id,
            String content,
            String category
    ) {}

    public record TicketResult(
            String id,
            String resolution
    ) {}

    @Action(description = "对工单进行分类")
    public ClassifiedTicket classify(TicketRequest request) {
        String category = request.content().contains("宕机")
                ? "CRITICAL"
                : "GENERAL";

        return new ClassifiedTicket(
                request.id(),
                request.content(),
                category
        );
    }

    @AchievesGoal(description = "完成工单处理")
    @Action(description = "生成工单处理方案")
    public TicketResult resolve(ClassifiedTicket ticket) {
        String resolution = switch (ticket.category()) {
            case "CRITICAL" -> "立即升级到值班工程师";
            default -> "发送知识库解决方案";
        };

        return new TicketResult(ticket.id(), resolution);
    }
}

@Service
public class FocusedTicketService {

    private final AgentPlatform agentPlatform;

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

    public TicketAgent.TicketResult handle(
            TicketAgent.TicketRequest request
    ) {
        // 1. 明确找到要运行的 Agent
        Agent ticketAgent = agentPlatform.agents()
                .stream()
                .filter(agent ->
                        agent.getName().contains("TicketAgent"))
                .findFirst()
                .orElseThrow();

        // 2. 创建本次运行实例
        AgentProcess process =
                agentPlatform.createAgentProcessFrom(
                        ticketAgent,
                        ProcessOptions.DEFAULT,
                        request
                );

        // 3. 同步执行
        AgentProcess completed = process.run();

        // 4. 取得类型化结果
        return completed.last(TicketAgent.TicketResult.class);
    }
}
Closed:动态选择一个 Agent

核心思想:

LLM 根据用户意图,从所有已注册 Agent 中选择最合适的一个;选中后,只能运行该 Agent 内部的 Action 和 Goal。

适合:

•智能客服入口

•企业助手

•多业务意图路由

•用户使用自然语言,但仍需保持 Agent 边界

假设系统中存在:

复制代码
TicketAgent:处理故障和工单
OrderAgent:处理订单和退款
KnowledgeAgent:回答制度和产品问题

用户输入
"生产数据库宕机了,帮我提交紧急工单"

Closed 模式会选择 TicketAgent,之后不会调用 OrderAgent 或 KnowledgeAgent 的 Action。

示例代码:

arduino 复制代码
@Service
public class ClosedModeService {

    private final Autonomy autonomy;

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

    public AgentProcessExecution execute(String userIntent) {
        return autonomy.chooseAndRunAgent(
                userIntent,
                ProcessOptions.DEFAULT
        );
    }
}

-- 调用
AgentProcessExecution execution =
        closedModeService.execute(
                "生产数据库宕机了,帮我提交紧急工单"
        );
sql 复制代码
用户意图
   ↓
LLM 对所有 Agent 排名
   ↓
选择 TicketAgent
   ↓
只使用 TicketAgent 的 Goal、Action 和 Condition
Open:选择 Goal 并组合多个 Agent

核心思想:

LLM 不是选择一个固定 Agent,而是从所有 Goal 中选择最符合用户意图的目标,然后组合系统中可用的 Action 达成目标。

适合:

•跨领域研究

•多 Agent 协作

•开放式任务

•无法提前确定执行路径的复杂问题

•需要动态组合能力的场景

假设有三个 Agent:

复制代码
CustomerAgent
└── 查询客户信息

KnowledgeAgent
└── 检索知识库

TicketAgent
├── 分析故障
└── 生成处理方案

用户提出:
"查询客户等级,结合历史工单和知识库,
分析这次数据库故障并生成处理方案。"

Open 模式可以组合:

erlang 复制代码
CustomerAgent.queryCustomer
KnowledgeAgent.searchKnowledge
TicketAgent.analyzeIncident
TicketAgent.createResolution

它不受单一 Agent 边界限制

四、OODA 与 GOAP 原理

OODA 解决"Agent 如何持续适应变化",GOAP 解决"Agent 如何规划一条通向目标的行动路径"。

二者不是竞争关系,而是不同层次:

复制代码
OODA:外层决策循环
GOAP:循环中 Decide 阶段使用的规划算法

OODA:持续观察和调整的决策循环

OODA 由美国空军上校 John Boyd 提出,最初用于描述动态对抗环境中的快速决策过程,包含四个阶段:

Observe:观察

获取当前环境以及上一步执行结果。 在 Agent 中可能包括:

•用户的新输入

•Tool 调用结果

•MCP 返回数据

•RAG 检索结果

•Action 执行结果

•外部系统状态

•异常和失败信息

例如:

复制代码
观察到:
工单内容包含"生产数据库无法连接"
客户等级为 VIP
SLA 只剩 10 分钟
Orient:判断

把原始信息转换成对当前情况的理解。

这一阶段并不只是"读取数据",还要结合:

•当前上下文

•领域知识

•历史经验

•业务规则

•风险与约束

•已经完成的动作

例如:

markdown 复制代码
数据库无法连接 + VIP 客户 + SLA 即将超时
                ↓
判断为:高优先级生产故障

在 Embabel 中,可以类比为:

markdown 复制代码
Blackboard 中的对象
        ↓
WorldStateDeterminer
        ↓
Condition / WorldState
Decide:决策

根据当前状态决定下一步采取什么行动。

决策方式可以是:

•固定 Workflow

•规则引擎

•状态机

•Utility AI

•LLM Supervisor

•GOAP

如果使用 Embabel 默认规划器,这一步主要由 GOAP 完成。

Act:行动

执行选定的 Action,例如:

•查询知识库

•调用 MCP Tool

•查询数据库

•发送通知

•创建工单

•调用 Subagent

•生成人工审批任务

Action 执行完成后,不是直接机械执行原计划的剩余步骤,而是返回 Observe,重新观察结果。

OODA 的关键不是"四个步骤",而是循环

传统流程通常是:

复制代码
制定计划 → 从头执行到尾

OODA 强调的是:

复制代码
执行一步
   ↓
观察结果
   ↓
重新判断
   ↓
调整下一步

例如,Agent 原来准备自动回复用户:

复制代码
计划:分类 → 查询知识库 → 自动回复

查询知识库后发现没有可靠答案:

复制代码
新观察:知识库没有匹配方案
新判断:自动回复风险过高
新决策:转人工专家

新的执行路径变为:

复制代码
分类 → 查询知识库 → 转人工专家

所以,OODA 的价值在于:

•应对不确定结果

•根据反馈及时调整

•避免错误计划执行到底

•支持异常恢复

•适合动态环境

GOAP:面向目标的行动规划

GOAP 全称:Goal-Oriented Action Planning,面向目标的行动规划。

GOAP 不要求开发人员写死完整执行顺序,而是声明四类信息:

•当前状态 State

•目标状态 Goal

•可执行动作 Action

•动作的前置条件、执行效果和成本

GOAP模型

当前状态

当前状态可以表示为一组事实:

ini 复制代码
ticketReceived = true
ticketClassified = false
knowledgeFound = false
ticketResolved = false

在 Embabel 中:

•Blackboard 保存真实对象

•WorldState 保存由对象推导出的条件状态

ini 复制代码
Blackboard:
Ticket{id="T-1001"}

WorldState:
ticketReceived = true
ticketClassified = false

目标

Goal 描述期望达到的状态:

ini 复制代码
ticketResolved = true

或者通过类型表示:

复制代码
Blackboard 中存在 ResolvedTicket

Action

每个 Action 可以抽象为:

ini 复制代码
Action = 前置条件 + 执行效果 + 执行成本

例如:

Action 前置条件 执行效果 成本
分类工单 已收到工单 工单已分类 1
查询知识库 工单已分类 找到知识 2
自动处理 找到知识 工单已解决 1
人工处理 工单已分类 工单已解决 5

Plan

Planner 需要寻找一组 Action

css 复制代码
当前状态
  ──执行 P──>
目标状态
GOAP 如何搜索路径

以工单处理为例,存在两条路径。

路径一:人工处理

ini 复制代码
分类工单 → 人工处理

成本:
1 + 5 = 6

路径二:自动处理

ini 复制代码
分类工单 → 查询知识库 → 自动处理

成本:
1 + 2 + 1 = 4

GOAP 会优先选择成本更低的第二条路径:

A* 的基本思想

A* 搜索通过下面的评分选择下一条候选路径:

scss 复制代码
f(n) = g(n) + h(n)

其中:

g(n):到达当前状态已经付出的成本

h(n):从当前状态到目标的预计剩余成本

f(n):整条路径的预计总成本

Planner 不需要尝试所有组合,而是优先搜索更可能以低成本达到 Goal 的路径

代码示例
typescript 复制代码
@Agent(description = "处理客户支持工单")
public class TicketAgent {

    public record Ticket(
            String id,
            String description
    ) {}

    public record ClassifiedTicket(
            Ticket ticket,
            String category
    ) {}

    public record KnowledgeResult(
            String solution
    ) {}

    public record ResolvedTicket(
            String id,
            String resolution
    ) {}

    @Action(
        description = "分析工单内容并完成分类",
        cost = 0.1
    )
    public ClassifiedTicket classify(Ticket ticket) {
        String category =
                ticket.description().contains("数据库")
                        ? "DATABASE"
                        : "GENERAL";

        return new ClassifiedTicket(ticket, category);
    }

    @Action(
        description = "根据工单分类查询知识库",
        cost = 0.2
    )
    public KnowledgeResult searchKnowledge(
            ClassifiedTicket ticket
    ) {
        return new KnowledgeResult(
                "检查数据库连接池和网络配置"
        );
    }

    @AchievesGoal(description = "工单已经处理完成")
    @Action(
        description = "根据知识库结果解决工单",
        cost = 0.1
    )
    public ResolvedTicket resolve(
            ClassifiedTicket ticket,
            KnowledgeResult knowledge
    ) {
        return new ResolvedTicket(
                ticket.ticket().id(),
                knowledge.solution()
        );
    }
}

类型关系已经形成:

复制代码
Ticket
   ↓ classify
ClassifiedTicket
   ↓ searchKnowledge
KnowledgeResult
   ↓ resolve
ResolvedTicket

因为 resolve() 需要:

复制代码
ClassifiedTicket + KnowledgeResult

Planner 会反向分析:

复制代码
要得到 ResolvedTicket
  → 需要执行 resolve

要执行 resolve
  → Blackboard 必须有 ClassifiedTicket 和 KnowledgeResult

要得到 KnowledgeResult
  → 需要执行 searchKnowledge

要执行 searchKnowledge
  → 必须先得到 ClassifiedTicket

最终生成计划:

复制代码
classify → searchKnowledge → resolve

五、Blackboard:类型化共享状态

一个 Agent 在执行过程中,数据到底放在哪里?用户输入放哪儿?中间结果放哪儿?每一步的执行状态怎么保存?在 Embabel 里,这一切数据都由一个核心组件来负责:Blackboard(黑板)

什么是Blackboard

Agent 执行过程中的一张共享工作台,保存输入、Action 中间结果、工具产物和最终结果,并通过 Java 类型组织这些数据。

每个 Action:

•从 Blackboard 获取自己需要的输入对象;

•执行业务逻辑或调用 LLM;

•返回新的类型化对象;

•框架自动把返回对象放回 Blackboard;

•Planner 根据 Blackboard 当前拥有的类型,决定下一步可以执行哪些 Action。

Blackboard设计模式

Blackboard 是 Embabel 中的共享内存系统,维护整个 Agent 过程执行期间的状态。你可以把它想象成 Agent 的工作台,所有需要的数据、中间结果都放在这里。每个 Action 执行时从这里取需要的输入,执行完后把输出放回去。

Blackboard 的几个核心特性:

统一管理运行状态

◦Blackboard 是 Agent 执行过程中的"中央工作台",统一保存用户输入、中间结果和最终产物。Action 不需要层层传递参数,只需声明自己需要的数据类型,框架就会自动从 Blackboard 中获取。

按类型存取数据

◦Blackboard 主要通过对象类型匹配数据,而不是依赖容易出错的字符串 Key。例如,Action 需要 WeatherRequest 或 SearchResult,框架就会查找对应类型的对象。这种方式更符合面向对象设计,也具备更好的类型安全性。

默认使用最新数据

◦Blackboard 会记录数据的加入顺序。同一类型存在多个对象时,框架默认获取最近加入的对象。例如,先后产生两次天气查询结果,后续 Action 默认使用最新结果。旧数据并未被覆盖,仍保留在执行历史中。

保留完整执行轨迹

◦对象写入 Blackboard 后通常不会被直接删除;不再参与规划的数据可以被隐藏。通过保留各阶段产生的对象,可以减少状态被意外覆盖的风险,并支持问题排查、过程审计和执行回溯。

保存并驱动任务条件

◦Blackboard 不仅保存业务对象,也维护影响规划的条件状态,例如"天气数据是否已经获取""用户是否已经登录""订单是否通过审核"。Planner 会结合当前对象和条件,判断哪些 Action 可以执行,并选择下一步操作。

场景示例

假设正在开发一个电商平台的客服工单系统,用户通过简单文本提交问题:

sql 复制代码
user-456 支付服务不可用,我无法完成付款

系统需要:

1.从文本中提取用户 ID 和问题描述;

2.自动验证用户身份;

3.创建客服工单;

4.分析问题等级;

5.根据诊断结果完成处理;

6.返回最终工单处理结果;

完整类型链路:

领域对象

arduino 复制代码
package com.example.ticket.domain;

// 从用户输入中提取的工单请求
public record TicketRequest(
    String userId,
    String description
) {
}


// 验证完成的用户
public record VerifiedUser(
    String userId,
    String customerLevel,
    boolean verified
) {
}

// 已创建的工单
public record Ticket(
    String ticketId,
    String userId,
    String description,
    String status
) {
}

// 工单诊断结果
public record Diagnosis(
    String ticketId,
    String severity,
    String reason
) {
}

// 最终处理结果
public record ResolvedTicket(
    String ticketId,
    String userId,
    String resolution,
    String handledBy,
    String status
) {
}

业务服务

用户服务

typescript 复制代码
package com.example.ticket.service;

import com.example.ticket.domain.VerifiedUser;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    public VerifiedUser verify(String userId) {
        // 示例中固定认为 user-456 是 VIP 用户
        if ("user-456".equals(userId)) {
            return new VerifiedUser(
                userId,
                "VIP",
                true
            );
        }

        return new VerifiedUser(
            userId,
            "NORMAL",
            true
        );
    }
}

工单服务

java 复制代码
package com.example.ticket.service;

import com.example.ticket.domain.Ticket;
import org.springframework.stereotype.Service;

import java.util.UUID;

@Service
public class TicketService {

    public Ticket create(
        String userId,
        String description
    ) {
        String ticketId =
            "T-" + UUID.randomUUID()
                .toString()
                .substring(0, 8);

        return new Ticket(
            ticketId,
            userId,
            description,
            "CREATED"
        );
    }
}

升级服务

typescript 复制代码
package com.example.ticket.service;

import org.springframework.stereotype.Service;

@Service
public class EscalationService {

    public void escalate(
        String ticketId,
        String team,
        String reason
    ) {
        System.out.printf(
            "工单 %s 已升级到 %s,原因:%s%n",
            ticketId,
            team,
            reason
        );
    }
}

Embabel Agent

java 复制代码
package com.example.ticket.agent;

import com.embabel.agent.api.annotation.AchievesGoal;
import com.embabel.agent.api.annotation.Action;
import com.embabel.agent.api.annotation.Agent;
import com.embabel.agent.domain.io.UserInput;
import com.example.ticket.domain.Diagnosis;
import com.example.ticket.domain.ResolvedTicket;
import com.example.ticket.domain.Ticket;
import com.example.ticket.domain.TicketRequest;
import com.example.ticket.domain.VerifiedUser;
import com.example.ticket.service.EscalationService;
import com.example.ticket.service.TicketService;
import com.example.ticket.service.UserService;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Agent(description = "接收用户问题并自动完成工单处理")
public class CustomerSupportAgent {

    private static final Pattern USER_ID_PATTERN =
        Pattern.compile("(user-\d+)");

    private final UserService userService;
    private final TicketService ticketService;
    private final EscalationService escalationService;

    public CustomerSupportAgent(
        UserService userService,
        TicketService ticketService,
        EscalationService escalationService
    ) {
        this.userService = userService;
        this.ticketService = ticketService;
        this.escalationService = escalationService;
    }

    /**
     * 第一步:理解用户输入
     *
     * Blackboard:
     * UserInput → TicketRequest
     */
    @Action(description = "从用户文本中提取用户ID和工单问题")
    public TicketRequest extractTicketRequest(
        UserInput userInput
    ) {
        String content = userInput.getContent();
        Matcher matcher =
            USER_ID_PATTERN.matcher(content);

        if (!matcher.find()) {
            throw new IllegalArgumentException(
                "用户输入中缺少用户ID,例如 user-456"
            );
        }

        String userId = matcher.group(1);

        String description = content
            .replaceFirst(Pattern.quote(userId), "")
            .trim();

        if (description.isBlank()) {
            throw new IllegalArgumentException(
                "工单问题描述不能为空"
            );
        }

        return new TicketRequest(
            userId,
            description
        );
    }

    /**
     * 第二步之一:验证用户
     *
     * Blackboard:
     * TicketRequest → VerifiedUser
     */
    @Action(description = "验证提交工单的用户身份")
    public VerifiedUser verifyUser(
        TicketRequest request
    ) {
        return userService.verify(
            request.userId()
        );
    }

    /**
     * 第二步之二:创建工单
     *
     * Blackboard:
     * TicketRequest → Ticket
     */
    @Action(description = "根据用户问题创建客服工单")
    public Ticket createTicket(
        TicketRequest request
    ) {
        return ticketService.create(
            request.userId(),
            request.description()
        );
    }

    /**
     * 第三步:诊断工单
     *
     * Blackboard:
     * Ticket → Diagnosis
     */
    @Action(description = "分析工单问题并判断严重等级")
    public Diagnosis diagnoseTicket(
        Ticket ticket
    ) {
        String description =
            ticket.description().toLowerCase();

        if (description.contains("支付")
                && description.contains("不可用")) {
            return new Diagnosis(
                ticket.ticketId(),
                "P1",
                "支付核心服务不可用"
            );
        }

        if (description.contains("无法付款")
                || description.contains("付款失败")) {
            return new Diagnosis(
                ticket.ticketId(),
                "P2",
                "用户支付失败"
            );
        }

        return new Diagnosis(
            ticket.ticketId(),
            "P3",
            "一般业务问题"
        );
    }

    /**
     * 第四步:完成工单处理
     *
     * 需要 Blackboard 中同时存在:
     * Ticket
     * VerifiedUser
     * Diagnosis
     *
     * 返回 ResolvedTicket 并达成 Goal
     */
    @AchievesGoal(
        description = "用户工单已经完成处理"
    )
    @Action(
        description = "根据用户身份和诊断结果处理工单",
        pre = {
            "spel:verifiedUser.verified == true"
        }
    )
    public ResolvedTicket resolveTicket(
        Ticket ticket,
        VerifiedUser verifiedUser,
        Diagnosis diagnosis
    ) {
        String handledBy;
        String resolution;

        if ("P1".equals(diagnosis.severity())) {
            handledBy = "PAYMENT_ON_CALL_TEAM";
            resolution = "立即升级到支付系统值班团队";

            escalationService.escalate(
                ticket.ticketId(),
                handledBy,
                diagnosis.reason()
            );
        } else if ("P2".equals(diagnosis.severity())) {
            handledBy = "PAYMENT_SUPPORT_TEAM";
            resolution = "转交支付客服团队处理";
        } else {
            handledBy = "CUSTOMER_SUPPORT_TEAM";
            resolution = "发送标准问题解决方案";
        }

        // VIP 用户可以采用更高的处理优先级
        if ("VIP".equals(
                verifiedUser.customerLevel()
        )) {
            resolution = "[VIP 优先] " + resolution;
        }

        return new ResolvedTicket(
            ticket.ticketId(),
            verifiedUser.userId(),
            resolution,
            handledBy,
            "RESOLVED"
        );
    }
}

调用Agent

kotlin 复制代码
package com.example.ticket.controller;

import com.embabel.agent.api.AgentInvocation;
import com.embabel.agent.core.AgentPlatform;
import com.embabel.agent.domain.io.UserInput;
import com.example.ticket.domain.ResolvedTicket;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/support")
public class SupportController {

    private final AgentPlatform agentPlatform;

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

    @PostMapping("/tickets")
    public ResolvedTicket submit(
        @RequestBody String text
    ) {
        var invocation = AgentInvocation.create(
            agentPlatform,
            ResolvedTicket.class
        );

        return invocation.invoke(
            new UserInput(text)
        );
    }
}

六、Action 与类型驱动规划

开发者定义"能做什么",规划器根据当前已有的数据类型和目标,动态决定"按什么顺序做"。它不是让 LLM 自由编排工具,也不是要求开发者预先写死工作流;Embabel 会把 Action 的方法签名转换为规划关系,并默认使用 GOAP(Goal-Oriented Action Planning)寻找可执行路径。每个 Action 完成后,系统都会重新评估状态并规划下一步。

Action 是带类型契约的能力

Action 通过方法参数声明执行前所需的领域类型,通过返回值声明执行后产生的领域类型,使 Embabel 能依据类型契约自动规划执行路径。

arduino 复制代码
import com.embabel.agent.api.annotation.AchievesGoal;
import com.embabel.agent.api.annotation.Action;
import com.embabel.agent.api.annotation.Agent;
import com.embabel.agent.api.common.Ai;

// ---------- 领域类型 ----------

public record UserRequest(String text) {}

public record CustomerId(long value) {}

public record Customer(
        CustomerId id,
        String name,
        String email
) {}

public record OrderHistory(
        CustomerId customerId,
        int orderCount,
        double totalAmount
) {}

public record CustomerReport(String content) {}

@Agent(description = "生成客户分析报告")
public class CustomerReportAgent {

    private final CustomerRepository customerRepository;
    private final OrderRepository orderRepository;

    public CustomerReportAgent(
            CustomerRepository customerRepository,
            OrderRepository orderRepository
    ) {
        this.customerRepository = customerRepository;
        this.orderRepository = orderRepository;
    }

    /**
     * 类型契约:
     *
     * 前置输入:UserRequest
     * 执行结果:CustomerId
     */
    @Action
    public CustomerId extractCustomerId(
            UserRequest request,
            Ai ai
    ) {
        return ai.withDefaultLlm()
                .createObject(
                        """
                        从下面的用户请求中提取客户 ID:
                        %s
                        """.formatted(request.text()),
                        CustomerId.class
                );
    }

    /**
     * 类型契约:
     *
     * 前置输入:CustomerId
     * 执行结果:Customer
     */
    @Action
    public Customer loadCustomer(
            CustomerId customerId
    ) {
        return customerRepository
                .findById(customerId.value())
                .orElseThrow(() ->
                        new IllegalArgumentException(
                                "Customer not found: " + customerId.value()
                        )
                );
    }

    /**
     * 类型契约:
     *
     * 前置输入:Customer
     * 执行结果:OrderHistory
     */
    @Action
    public OrderHistory loadOrderHistory(
            Customer customer
    ) {
        return orderRepository.summarizeByCustomerId(
                customer.id().value()
        );
    }

    /**
     * 类型契约:
     *
     * 前置输入:Customer + OrderHistory
     * 执行结果:CustomerReport
     *
     * @AchievesGoal 表示执行完成后目标达成。
     */
    @AchievesGoal(
            description = "生成包含客户信息和消费情况的分析报告"
    )
    @Action
    public CustomerReport createReport(
            Customer customer,
            OrderHistory orderHistory,
            Ai ai
    ) {
        String prompt = """
                根据以下信息生成客户分析报告。

                客户:
                - 姓名:%s
                - 邮箱:%s

                订单:
                - 订单数量:%d
                - 总消费金额:%.2f

                分析客户价值,并给出后续运营建议。
                """.formatted(
                customer.name(),
                customer.email(),
                orderHistory.orderCount(),
                orderHistory.totalAmount()
        );

        return ai.withDefaultLlm()
                .createObject(prompt, CustomerReport.class);
    }
}

规划器看到的不是方法调用顺序,而是下面这些类型契约:

makefile 复制代码
extractCustomerId:
    UserRequest → CustomerId

loadCustomer:
    CustomerId → Customer

loadOrderHistory:
    Customer → OrderHistory

createReport:
    Customer + OrderHistory → CustomerReport(目标)
什么是类型驱动规划

当 Blackboard 初始只有 UserRequest,而目标是 CustomerReport 时,规划器可以推导出:

markdown 复制代码
UserRequest
    ↓ extractCustomerId
CustomerId
    ↓ loadCustomer
Customer
    ↓ loadOrderHistory
OrderHistory
    ↓ createReport,需要 Customer + OrderHistory
CustomerReport
类型不能表达的规则交给 Condition

类型只能说明"数据存在",不能说明"数据符合业务要求"。

例如,拥有 Budget 对象并不意味着预算足够:

arduino 复制代码
import com.embabel.agent.api.annotation.Action;
import com.embabel.agent.api.annotation.Condition;
import com.embabel.agent.api.annotation.ConditionalOnCondition;

public record Budget(double amount) {}

public record Hotel(
        String id,
        String name,
        double price
) {}

public record Reservation(
        String reservationId,
        String hotelId
) {}

public class HotelBookingAgent {

    /**
     * 显式业务条件:
     * 有 Budget 和 Hotel 对象,不代表预算一定足够。
     */
    @Condition("hasEnoughBudget")
    public boolean hasEnoughBudget(
            Budget budget,
            Hotel hotel
    ) {
        return budget.amount() >= hotel.price();
    }

    /**
     * 只有 hasEnoughBudget 条件成立时,
     * 该 Action 才能被规划和执行。
     */
    @Action
    @ConditionalOnCondition("hasEnoughBudget")
    public Reservation bookHotel(
            Hotel hotel,
            Budget budget
    ) {
        return new Reservation(
                "RES-10001",
                hotel.id()
        );
    }
}
建模时最重要的原则

更好的建模方式是使用具有明确业务含义的领域类型,避免所有 Action 都使用 String:

arduino 复制代码
public record UserInput(String text) {}

public record SearchRequest(String keywords) {}

public record SearchResults(List<String> items) {}

public record ResearchReport(String content) {}

七、Tools 与 Agentic Tools

在 Embabel 中:

普通 Tool:一个可以被 LLM 调用的具体操作。

Agentic Tool:外表仍是一个 Tool,但内部会启动另一个 LLM 工具调用循环,由内部 LLM 编排多个子 Tools。

关键差别不在于"是否使用 LLM 选择工具"------普通 Tool 也可能由外层 LLM 选择;区别在于:

普通 Tool 的内部是一次具体执行;Agentic Tool 的内部是一个能够继续推理、选择并调用子工具的小型 Agent 循环。

Embabel 提供三种 Agentic Tool:SimpleAgenticTool、PlaybookTool 和 StateMachineTool

普通 Tool

普通 Tool 通常完成一个边界清晰的原子操作,例如:

•查询客户;

•计算金额;

•调用天气 API;

•创建订单;

•读取文件;

•更新邮箱。

执行原理

典型过程是:

1.Embabel 把 Tool 名称、描述和参数 JSON Schema 发给模型。

2.LLM 决定是否调用 Tool,并生成结构化参数。

3.Embabel 将参数绑定到 JVM 方法。

4.方法执行确定性的 Java/Kotlin 逻辑。

5.返回值序列化后交给 LLM。

6.外层 LLM 根据结果继续回答或调用其他 Tool。

这里的"确定性"是指 Tool 本身按代码执行;数据库、网络等外部系统当然仍可能产生不同结果。

Agentic Tool

Agentic Tool 自身实现了 Tool 接口,但它的 Handler 不是简单调用一个业务方法,而是启动一个内部 PromptRunner

官方描述的内部过程是:

1.获取当前 AgentProcess。

2.按指定的 LlmOptions 创建内部 PromptRunner。

3.将子 Tools 加入内部 PromptRunner。

4.使用输入启动 LLM---Tool 循环。

5.把内部 LLM 的最终结果作为 Agentic Tool 的结果返回。

所以它会产生嵌套调用:

sql 复制代码
外层 LLM
  └── 调用 research-assistant
        └── 内层 LLM
              ├── 调用 search
              ├── 调用 fetch
              └── 调用 summarize
Agentic Tool解决什么问题

Agentic Tool 是为了把"一组需要 LLM 动态判断和多步调用的工具"封装成一个可复用的高层 Tool。

普通 Tool 解决的是:

执行一个明确操作。

Agentic Tool 解决的是:

面对一个局部目标,动态决定调用哪些工具、以什么顺序调用,以及何时结束。

例如"研究一家公司"并不是一次原子操作,可能需要:

复制代码
搜索公司
→ 读取官网
→ 查询财务数据
→ 补充搜索管理层信息
→ 交叉验证
→ 生成摘要

实际执行顺序取决于中途得到的信息,不适合写成单个普通 Tool,也不一定值得建立一套完整 GOAP Agent。此时可以将其封装成:

markdown 复制代码
research-company Agentic Tool
  └── 内部 LLM
        ├── searchWeb
        ├── fetchPage
        ├── queryFinancialData
        └── summarize

外层 LLM 只需要调用:

scss 复制代码
research-company("OpenAI")

主要解决的问题:

1.普通 Tool 粒度太小

如果外层 LLM 直接面对几十个底层工具:

sql 复制代码
search
fetch
parse
filter
calculate
validate
format
save
...

模型容易:

•选错工具;

•遗漏步骤;

•参数传递错误;

•在相似工具之间混淆;

•消耗大量 Tool Schema Token。

Agentic Tool 把它们包装成一个高层能力:

sql 复制代码
底层:search + fetch + analyze + summarize
对外:research

1.固定代码无法处理动态路径

实际研究可能需要根据中间结果动态决定:

复制代码
搜索结果不足 → 换关键词再次搜索
发现官方文档 → 优先读取官方文档
内容相互冲突 → 搜索第二来源
信息已经充分 → 停止继续搜索

1.提供分层编排

Agentic Tool 形成了两层决策:

markdown 复制代码
外层 LLM:决定调用哪个高层能力
    ↓
Agentic Tool:决定如何完成这个局部任务
    ↓
普通 Tool:执行具体操作

八、MCP:Agent 的外部能力协议

MCP(Model Context Protocol)是一套让 AI 应用统一发现、读取和调用外部能力的协议。REST API 让应用调用服务,MCP 让 Agent 能够理解并调用服务。

MCP 不是工具本身,也不负责规划,而是规定:

•外部系统如何描述自己有哪些能力;

•工具需要哪些参数;

•Agent 如何调用工具;

•工具如何返回结果和错误;

•外部资源和 Prompt 如何提供给 AI;

为什么需要 MCP?

没有 MCP 时,每接入一个外部系统都要单独适配:

复制代码
Agent → GitHub SDK
Agent → 数据库驱动
Agent → Jira API
Agent → 企业内部 HTTP API
Agent → 文件系统 API

每套接口的鉴权、参数和返回格式都不一样。

有了 MCP 后:

arduino 复制代码
Agent
  ↓
统一的 MCP Client
  ↓
不同的 MCP Server
  ├── GitHub
  ├── Jira
  ├── 数据库
  ├── 文件系统
  └── 企业工单系统

MCP Server 把不同系统包装成统一协议,Agent 不再关心底层使用 REST、数据库还是第三方 SDK。

可以把 MCP 理解成 Agent 世界里的 USB-C:

复制代码
USB-C 统一设备连接方式
MCP   统一 Agent 能力连接方式
核心架构

三个核心角色:

Host

承载 Agent 和 LLM 的应用,例如:

•Embabel 应用

•IDE

•AI 桌面客户端

•企业智能助手

Host 负责:

•管理 MCP Client;

•决定向 LLM 暴露哪些能力;

•管理权限和用户确认;

•将工具结果交回 LLM或Agent。

Client

运行在 Host 内部,负责与某个 MCP Server 通信。

通常是:

arduino 复制代码
一个 MCP Client ↔ 一个 MCP Server

Server

对外暴露具体能力,例如:

复制代码
verify_user
create_ticket
get_ticket
notify_on_call

MCP 采用 Host--Client--Server 架构,Server 只获得完成调用所需的信息,不应默认看到完整对话或其他 Server 的数据。

九、RAG 与 Agentic RAG

普通 RAG 是"系统先查一次,再让 LLM 回答";Agentic RAG 是"把检索能力交给 Agent,由 Agent 决定查什么、查几次、使用哪种检索方式以及什么时候停止"。在 Agent 中使用 RAG,不一定就是 Agentic RAG。

如果检索步骤和查询语句仍然是固定的,本质上还是普通 RAG。

普通 RAG

普通 RAG 原理

RAG 是 Retrieval-Augmented Generation,即检索增强生成。

它分为两个阶段。

知识入库

例如讲以下知识库入库:

复制代码
文档1:
支付服务返回 PAY-503 时,首先检查支付网关状态。
如果网关不可用,应升级 PAYMENT_ON_CALL_TEAM。

文档2:
VIP 用户的 P1 工单需要在 5 分钟内响应。

文档3:
支付失败但网关正常时,应检查用户账户和支付渠道。

检索并回答

检索过程通常是固定的:

复制代码
用户问题
→ 向量检索一次
→ 返回 TopK 文档
→ 拼接 Prompt
→ LLM 回答
普通 RAG 的局限

用户提问:

复制代码
VIP 用户支付失败,错误码 PAY-503,
昨天升级过但今天又出现了,应该怎么处理?

一次向量检索可能遇到以下问题:

•用户问题过长,检索重点不明确;

•"PAY-503"适合关键词检索,不一定适合向量检索;

•需要同时查询故障手册、SLA 和历史工单;

•查到一个片段后,还需要读取它的上下文;

•第一次检索结果不足,但系统不会主动再查;

•检索得到冲突信息时,没有验证环节。

这些问题引出了 Agentic RAG。

Agentic RAG

Agentic RAG 原理

Agentic RAG 把"检索"从固定的前置步骤变成 Agent 可以调用的工具。

核心循环是:

复制代码
思考检索目标
→ 选择检索工具
→ 构造查询
→ 观察结果
→ 判断信息是否充分
→ 继续检索或生成答案
对比总结
对比项 普通 RAG Agentic RAG
是否检索 每次固定检索 Agent 判断
查询内容 通常直接使用用户问题 Agent 可拆分、改写查询
检索次数 通常一次 可以多次
检索方式 通常单一向量检索 向量、全文、正则、多数据源
结果判断 直接交给 LLM Agent 可以评估是否充分
上下文扩展 固定 TopK 可以展开相邻块和父章节
执行路径 固定 动态
延迟和成本 较低 较高
可控性 较高 相对较低
复杂问题效果 一般 通常更好

十、完整业务案例

智能保险平台项目,是一个面向车险场景的 AI Agent 演示系统:用户可以用自然语言发起投保或咨询,系统由核保 Agent、理赔 Agent、客服 Agent 协同,将大模型的语言理解能力与可审计的业务规则、人工审批、数据库事务和权限控制结合起来,演示从"投保---报价---审批---支付---出单---理赔---结案"的完整业务闭环。

项目背景

业务痛点

传统车险流程通常存在以下问题:

•客户输入是自然语言或非结构化描述,业务系统要求结构化字段,人工录入成本高。

•核保和理赔包含大量规则判断,同时存在需要人工复核的灰区。

•条款、理赔指南和 FAQ 分散,客服检索慢、回答一致性不足。

•单纯使用大模型直接决策,结果不可控、不可审计,也无法可靠执行数据库和交易操作。

•单纯使用固定工作流,又难以处理语言表达差异和复杂上下文。

解决思路

项目采用"LLM 负责理解,规则负责决策,Agent 负责规划,服务负责执行,人工负责兜底"的分工:

能力 主要责任
LLM 从自然语言提取车辆、事故信息;基于知识库生成答案
Embabel Agent 根据当前状态和目标规划动作,完成状态路由
Java 规则服务 计算核保风险、保费和欺诈风险,保证结果确定、可测试
Spring Service/JPA 查询客户车辆、保存报价/保单/理赔单、处理事务
人工岗位 处理中风险报价和中风险理赔
Guardrail(护栏) + Security 输入输出校验、认证、权限隔离和越权指令检测
业务价值

•自动结构化:降低投保和报案录入成本。

•自动分流:低风险自动通过,高风险自动拒绝,中风险交给人工。

•人机协同:AI 不替代必要的审批岗位,而是聚焦标准场景和前置判断。

•知识一致:客服回答优先引用本地保险文档,减少无依据回答。

•工程可控:关键金额与状态变更由 Java 代码完成,不让 LLM 直接操作核心交易。

•可测试:规则、Agent 动作、集成流程和真实模型 E2E 分层验证。

目标用户与角色
角色 业务诉求 当前系统能力
投保客户 快速获得车险报价、支付并查看保单 自然语言投保、保单查询、知识问答
核保员 处理系统无法自动批准的中风险申请 审批 REFERRED 报价、调整保费、填写备注
理赔员 审核疑似风险但不足以自动拒绝的案件 审核 INVESTIGATING 理赔,批准或拒绝
管理员 管理知识库和访问全部业务能力 文档摄入/重建、全部接口权限
技术人员 学习和验证 Java Agent 工程模式 Agent 状态路由、RAG、护栏、测试、可观测性配置

业务案例

案例 A:低风险客户自动投保并完成小额理赔

客户 Alice,41 岁、15 年驾龄、1 次事故,为 2022 年 Toyota RAV4 投保。

1.核保员或业务渠道提交自然语言投保申请。

2.LLM 提取车型、品牌和车牌。

3.系统查询 Alice 与车辆档案。

4.规则引擎算出风险分 15,命中低风险区间 ≤ 60

5.系统自动创建 APPROVED 报价。

6.综合险保费:300000 × 2% × 0.8 × 1.0 = 4800 元

7.客户支付后,系统签发一年期 ACTIVE 保单。

8.后续客户提交小额理赔;若欺诈分 < 30,系统自动批准。

9.赔付金额不超过 年保费 × 5 的当前演示上限。

业务结果:标准低风险业务实现端到端自动化。

案例 B:中风险客户由人工核保和人工理赔

客户 Bob,27 岁、4 年驾龄、2 次事故,为 2018 年 Honda Civic 投保。

1.风险评分为 63,命中中风险区间 60 < score < 80

2.系统创建 REFERRED 报价,等待核保员处理。

3.核保员可保留系统保费,也可输入调整后的保费并填写意见。

4.审批后报价变为 APPROVED,客户才能支付并获得保单。

5.客户后续提交中等风险理赔;若欺诈分为 30--69,生成 INVESTIGATING 理赔单。

6.理赔员通过专用审核接口作出 APPROVEDDENIED 终态决定。

业务结果:AI 完成资料理解和风险预判,人类保留灰区案件的最终决策权。

案例 C:高风险客户自动拒保

客户 Charlie,21 岁、1 年驾龄、3 次事故,为 2013 年 BMW X5 投保。

1.原始风险累计超过 100,系统钳制为 100。

2.命中高风险区间 ≥ 80

3.系统保存 DECLINED 报价,保费记为 0,并记录拒绝原因。

4.被拒报价不能支付,因此不会生成保单,也不能进入后续理赔链路。

业务结果:高风险申请在交易发生前被拦截。

案例 D:知识客服多轮问答

客户询问"综合险包含什么?"或"发生事故后如何理赔?":

1.ChatService 校验输入并创建/恢复用户会话。

2.ChatbotAgent 挂载 insurance_docs_textSearch 工具。

3.LLM 主动搜索保险条款、理赔指南和 FAQ。

4.LLM 阅读检索片段,按用户语言综合回答并提示文档来源。

5.服务保存最多 20 轮对话;会话 30 分钟无操作后过期。

业务结果:以企业知识为依据提供连续、统一的客户服务。

总体架构

分层职责
层次 组件 职责
接口层 InsuranceController、ChatController、RagAdminController HTTP 入参/出参、状态码、认证用户获取
编排层 AgentService、ChatService 启动 Agent、等待结果、超时、人工审核、会话管理
Agent 层 3 个 Agent 将业务目标拆解为动作,并依据状态选择后续路径
领域服务层 Risk、Premium、Payment、Policy、Data 等 Service 可预测、可单测的业务计算和事务操作
数据层 JPA Repository + Entity 客户、车辆、报价、保单、理赔单持久化
AI/知识层 DeepSeek、Lucene、ToolishRag 语言理解、生成、知识检索
横切能力 Security、Guardrail、Cache、日志 权限、安全、性能与诊断

代码链接

github.com/pysmell/emb...

相关推荐
大模型真好玩3 小时前
DeepSeek Harness 入门很简单(三)——DeepSeek Harness接入工具、MCP、Skill
人工智能·agent·deepseek
阿里云云原生8 小时前
Agent 观测与优化开发者摸底报告
云原生·agent
阿里云大数据AI技术9 小时前
使用 PAI,一键拉起云端 AI “投资智囊团”
人工智能·agent
四六的六9 小时前
让 AI 自己去点后台页面,它把我们的库存点没了
前端·人工智能·agent·个人开发·ai编程·ai产品·ai前端
苏灿烤鱼9 小时前
给 AI Agent 装一个「会进化的大脑」:OpenViking 深度拆解
开源·github·agent
武子康9 小时前
Agent 的工具没变,SGLang 缓存为什么没命中?
人工智能·llm·agent
阿里云大数据AI技术9 小时前
基于阿里云Milvus 知识库服务,快速搭建智能客服问答系统实践
人工智能·agent
Code_Artist9 小时前
从 Tool Calling 到能力编排:重新理解 Agent Skill 的运行机制——以 tRPC-Agent-Go 为例
人工智能·openai·agent