Solon v3.9.0

react - 示例2 - 电商售后智能决策

</> markdown
2026年1月24日 下午9:35:36

在真实的生产环境中,AI 不应仅仅是“应答机”,更应该是能理解业务规则并闭环执行任务的“决策官”。本示例展示了如何使用 ReActAgent 处理一个典型的电商售后场景:自动化丢件处理。

1、业务场景描述

当客户投诉“没收到货”时,传统的客服系统往往需要人工介入查询多个系统。而具备 ReAct 能力的 Agent 可以自主完成以下链式决策流:

  • 查询订单:获取订单关联的物流单号及订单金额。
  • 查询物流:通过物流单号实时获取包裹轨迹状态。
  • 智能决策:根据物流状态(如“丢件”)和业务规则(如“金额 > 100 元退款,否则发券”)自动调用补偿工具。

2、示例代码:

通过 defaultToolAdd 注入不同领域的工具集,Agent 能够像拼图一样将零散的 API 组合成完整的业务逻辑。

import demo.ai.llm.LlmUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.noear.solon.ai.agent.AgentSession;
import org.noear.solon.ai.agent.react.ReActAgent;
import org.noear.solon.ai.agent.session.InMemoryAgentSession;
import org.noear.solon.ai.annotation.ToolMapping;
import org.noear.solon.ai.chat.ChatModel;
import org.noear.solon.ai.chat.prompt.Prompt;
import org.noear.solon.ai.chat.tool.MethodToolProvider;
import org.noear.solon.annotation.Param;

/**
 * 复杂的业务场景测试:电商售后智能决策
 * <p>场景:客户投诉没收到货。ReAct 模式下的 Agent 需要自主完成以下链式决策:
 * 查询订单 -> 识别物流单号 -> 查询物流状态 -> 发现丢件 -> 根据订单金额选择赔付策略(退款)。</p>
 */
public class ReActAgentComplexTest {

    /**
     * 测试客户服务自动化决策逻辑
     * <p>验证目标:</p>
     * 1. 自动调用 get_order 获取物流单号 (track_123)。<br>
     * 2. 自动调用 get_logistic_status 识别出状态为 "lost" (丢件)。<br>
     * 3. 识别订单金额 > 100,最终调用 apply_compensation 触发 "refund" (全额退款) 流程。
     */
    @Test
    public void testCustomerServiceLogic() throws Throwable {
        ChatModel chatModel = LlmUtil.getChatModel();

        // 1. 构建配置了领域工具集的 ReActAgent
        ReActAgent agent = ReActAgent.of(chatModel)
                .defaultToolAdd(new OrderTools())
                .defaultToolAdd(new LogisticTools())
                .defaultToolAdd(new MarketingTools())
                .modelOptions(o -> o.temperature(0.0)) // 设置 0 温度以确保逻辑推导的确定性
                .maxSteps(10) // 给予充足的思考步数以支持复杂的链式调用
                .build();

        // 2. 初始化 AgentSession(替换原有的 FlowContext)
        // AgentSession 会自动持有会话状态并支持后续的持久化或恢复
        AgentSession session = InMemoryAgentSession.of("demo_customer_job_001");

        String userPrompt = "我的订单号是 ORD_20251229,到现在还没收到货,帮我查查怎么回事并给出处理方案。";

        // 3. 执行智能体调用
        // 使用 call(Prompt, AgentSession) 契约,这是 3.8.x 推荐的调用方式
        String result = agent.call(Prompt.of(userPrompt), session).getContent();

        // 4. 科学验证决策产出的准确性
        Assertions.assertNotNull(result, "智能体回复不应为空");

        // 校验逻辑:是否识别到丢件
        boolean hasLostInfo = result.contains("丢失") || result.contains("lost") || result.contains("丢件");
        // 校验逻辑:是否给出了正确的全额退款赔付方案
        boolean hasRefundInfo = result.contains("退款") || result.contains("refund");

        Assertions.assertTrue(hasLostInfo, "Agent 应通过工具查询识别出物流丢失状态。当前结果:" + result);
        Assertions.assertTrue(hasRefundInfo, "针对高额丢失订单,Agent 应自动触发退款申请。当前结果:" + result);

        System.out.println("--- 智能售后决策结果 ---");
        System.out.println(result);
    }

    // --- 领域工具定义 (Domain Tools) ---

    /**
     * 订单领域工具
     */
    public static class OrderTools {
        @ToolMapping(description = "根据订单号查询订单详情,获取商品名、金额、物流单号")
        public String get_order(@Param(description = "订单号") String orderId) {
            if ("ORD_20251229".equals(orderId)) {
                return "{\"orderId\":\"ORD_20251229\", \"amount\": 158.0, \"trackNo\": \"track_123\", \"sku\": \"智能耳机\"}";
            }
            return "{\"error\": \"订单不存在\"}";
        }
    }

    /**
     * 物流领域工具
     */
    public static class LogisticTools {
        @ToolMapping(description = "根据物流单号查询当前运输状态")
        public String get_logistic_status(@Param(description = "物流单号") String trackNo) {
            if ("track_123".equals(trackNo)) {
                return "{\"status\": \"lost\", \"info\": \"包裹在上海分拨中心丢失\"}";
            }
            return "{\"error\": \"查无此单\"}";
        }
    }

    /**
     * 营销/补偿领域工具
     */
    public static class MarketingTools {
        @ToolMapping(description = "根据赔付策略发放补偿。规则:小额订单(<=100)发优惠券(coupon);大额订单(>100)申请全额退款(refund)")
        public String apply_compensation(@Param(description = "赔付策略:coupon 或 refund") String strategy,
                                         @Param(description = "订单金额") double amount) {
            if ("refund".equals(strategy) && amount > 100) {
                return "【系统指令】已成功提交退款申请,金额 158.0 元预计 24 小时内原路退回。";
            } else if ("coupon".equals(strategy)) {
                return "【系统指令】已发放 20 元补偿优惠券至用户账户。";
            }
            return "【人工逻辑】赔付策略与金额不匹配,已转交人工客服审核。";
        }
    }
}