标签 :
JavaAgentj-langchainRPCDubboFeign@AgentTool@Param@ParamDesc
前置阅读 :AgentExecutor:用一行代码启动 ReAct Agent
适合人群:希望把已有 Dubbo / Feign / gRPC 等 RPC 服务快速接入 AI Agent 的 Java 开发者
一、背景
企业里有大量存量 RPC 服务,想让 AI Agent 调用这些服务,最自然的期望是:
- 不改动已有 RPC 接口定义
- 只做最少的"配置",而不是"重写一套工具层"
- 不管底层是 Dubbo 还是 Feign,接入方式应该一致
j-langchain 的 @AgentTool + @Param / @ParamDesc 体系正好满足这个需求。同一套工具包装类既可以交给 AgentExecutor(ReAct),也可以交给 McpAgentExecutor(Function Calling),无需任何修改。
二、参数描述的三种方式
工具的参数 Schema 是给 LLM 看的,框架按以下优先级生成:
| 优先级 | 方式 | 适用场景 |
|---|---|---|
| 1 | @AgentTool.params 内联 @ParamDesc |
VO 来自第三方包,无法修改字段 |
| 2 | VO 字段上的 @Param |
自己定义的 VO |
| 3 | 方法参数上的 @Param |
简单基础类型参数 |
三种方式互为补充,向下兼容,按实际情况选择即可。
三、场景一:Dubbo --- 第三方 VO + @AgentTool.params 内联描述
VO 来自合作方三方包,字段上无法加 @Param。改用 @AgentTool.params 内联描述,效果完全相同。
工具包装类
java
@Component
public class EcommerceDubboTools {
@DubboReference
private OrderFacade orderFacade;
@DubboReference
private RefundFacade refundFacade;
@DubboReference
private LogisticsFacade logisticsFacade;
@AgentTool(
value = "查询用户订单信息",
params = {
@ParamDesc(name = "orderId", desc = "订单ID,格式:ORD-2024-XXXXXX,如不知道可留空"),
@ParamDesc(name = "userId", desc = "用户ID,格式:USR-XXXXXX,如不知道可留空"),
@ParamDesc(name = "queryType", desc = "查询类型:LATEST(最近一笔)/ ALL(全部订单),默认 LATEST")
}
)
public String queryOrder(OrderQueryRequest request) {
return orderFacade.queryOrder(request).toString();
}
@AgentTool(
value = "提交退款申请",
params = {
@ParamDesc(name = "orderId", desc = "订单ID,格式:ORD-2024-XXXXXX"),
@ParamDesc(name = "reason", desc = "退款原因,如:质量问题 / 物流超时 / 不想要了 / 收到错误商品"),
@ParamDesc(name = "amount", desc = "退款金额(元),不填则申请全额退款")
}
)
public String applyRefund(RefundRequest request) {
return refundFacade.applyRefund(request).toString();
}
@AgentTool(
value = "查询物流配送状态",
params = {
@ParamDesc(name = "orderId", desc = "订单ID,格式:ORD-2024-XXXXXX"),
@ParamDesc(name = "trackingNo", desc = "快递单号(可选,不填则通过订单ID自动关联查询)")
}
)
public String trackLogistics(LogisticsQueryRequest request) {
return logisticsFacade.track(request).toString();
}
}
框架读取 @AgentTool.params,自动生成如下 Schema:
查询用户订单信息
Input JSON keys (OrderQueryRequest):
- orderId (String): 订单ID,格式:ORD-2024-XXXXXX,如不知道可留空
- userId (String): 用户ID,格式:USR-XXXXXX,如不知道可留空
- queryType (String): 查询类型:LATEST(最近一笔)/ ALL(全部订单),默认 LATEST
Action Input format: JSON, e.g. {"orderId": ..., "userId": ..., "queryType": ...}
示例用例(使用 AgentExecutor --- ReAct)
对应 dubboAgentDemo():
java
@Test
public void dubboAgentDemo() {
AgentExecutor agent = AgentExecutor.builder(chainActor)
.llm(ChatAliyun.builder().model("qwen-plus").temperature(0f).build())
.tools(ecommerceDubboTools)
.maxIterations(8)
.build();
agent.invoke("我的订单 ORD-2024-001 付款已经好几天了,显示运输中但迟迟没有送达," +
"帮我查一下:1) 这个订单的基本信息;2) 目前物流到哪了;" +
"3) 如果快递确实异常,帮我提交一个退款申请,原因是物流超时。");
}
同一套
ecommerceDubboTools也可以直接传给McpAgentExecutor,工具定义无需修改。
执行过程
[思考] 先查询订单基本信息。
Action: query_order {"orderId": "ORD-2024-001"}
[服务返回] 订单 ORD-2024-001:「索尼 WH-1000XM5 耳机」¥2199,运输中(已超时2天)
[思考] 订单确实超时,查询物流详情。
Action: track_logistics {"orderId": "ORD-2024-001"}
[服务返回] SF1234567890,2024-03-12 09:00 在成都青羊区网点,此后无更新
[思考] 物流异常,提交退款。
Action: apply_refund {"orderId": "ORD-2024-001", "reason": "物流超时"}
[服务返回] 退款申请已提交,¥2199(全额),工单号 TKT-20240316-8821
Final Answer: 已为您处理完毕...
四、场景二:Feign --- 自有 VO + @Param 标注字段
VO 是自己团队定义的,可以直接在字段上加 @Param,工具方法本身保持干净。
VO 定义
java
@Data
public class ProductDetailRequest {
@Param("商品ID,格式:PROD-XXXXXX")
private String productId;
@Param("商品类目,如:手机/笔记本/耳机,可留空")
private String category;
}
@Data
public class InventoryQueryRequest {
@Param("商品SKU,格式:SKU-XXXXXX")
private String sku;
@Param("仓库区域:EAST/WEST/SOUTH/NORTH,可留空(查全部仓库)")
private String region;
}
@Data
public class PriceQueryRequest {
@Param("商品SKU,格式:SKU-XXXXXX")
private String sku;
@Param("用户等级:VIP/NORMAL,影响折扣,默认 NORMAL")
private String userLevel;
}
工具包装类
java
@Component
public class RetailFeignTools {
@Autowired
private ProductFeignClient productFeignClient;
@Autowired
private InventoryFeignClient inventoryFeignClient;
@Autowired
private PricingFeignClient pricingFeignClient;
@AgentTool("查询商品详情")
public String getProductDetail(ProductDetailRequest request) {
return productFeignClient.getProductDetail(request).toString();
}
@AgentTool("查询商品库存")
public String queryInventory(InventoryQueryRequest request) {
return inventoryFeignClient.queryInventory(request).toString();
}
@AgentTool("查询商品价格")
public String queryPrice(PriceQueryRequest request) {
return pricingFeignClient.queryPrice(request).toString();
}
}
框架扫描 VO 字段上的 @Param,自动生成如下 Schema:
查询商品库存
Input JSON keys (InventoryQueryRequest):
- sku (String): 商品SKU,格式:SKU-XXXXXX
- region (String): 仓库区域:EAST/WEST/SOUTH/NORTH,可留空(查全部仓库)
Action Input format: JSON, e.g. {"sku": ..., "region": ...}
示例用例(使用 McpAgentExecutor --- Function Calling)
对应 feignAgentDemo():
java
@Test
public void feignAgentDemo() {
McpAgentExecutor agent = McpAgentExecutor.builder(chainActor)
.llm(ChatAliyun.builder().model("qwen3.6-plus").temperature(0f).build())
.tools(retailFeignTools)
.maxIterations(8)
.build();
agent.invoke("我想买索尼 WH-1000XM5 耳机(商品ID:PROD-SONY-001,SKU:SKU-SONY-WH1000XM5)," +
"帮我查一下:1) 这款产品的详情;2) 华东仓库是否有库存;3) 我是 VIP 用户,购买价格是多少?");
}
同一套
retailFeignTools也可以直接传给AgentExecutor,工具定义无需修改。
执行过程
[思考] 先查询商品详情。
Action: get_product_detail {"productId": "PROD-SONY-001"}
[服务返回] 索尼 WH-1000XM5,主动降噪,30h 续航,黑色/银色
[思考] 查华东仓库库存。
Action: query_inventory {"sku": "SKU-SONY-WH1000XM5", "region": "EAST"}
[服务返回] EAST 仓库:黑色 47 件,银色 12 件,均可当日发货
[思考] 查 VIP 价格。
Action: query_price {"sku": "SKU-SONY-WH1000XM5", "userLevel": "VIP"}
[服务返回] ¥1979(9折 VIP 优惠,立省 ¥220)
Final Answer: 索尼 WH-1000XM5 详情、库存、VIP 价格已查完...
五、两种场景对比
| 维度 | Dubbo(@AgentTool.params) | Feign(@Param on field) |
|---|---|---|
| VO 是否可修改 | 否(二方包提供) | 是(自有定义) |
| 参数描述位置 | 工具方法注解 | VO 字段注解 |
| LLM 看到的 Schema | 完全相同 | 完全相同 |
| AgentExecutor 可用 | ✓ | ✓ |
| McpAgentExecutor 可用 | ✓ | ✓ |
两种方式生成的 Schema 对 LLM 完全透明,差别仅在于描述写在哪里。
六、总结
- 自有 VO :
@Param标注在字段上,描述集中在数据模型层 - 第三方 VO :
@AgentTool.params内联@ParamDesc,描述集中在工具方法上 - AgentExecutor 与 McpAgentExecutor 通用:同一套工具定义,无需为不同执行器维护两份代码
- 框架透明:Schema 生成、JSON 反序列化、方法调用全部自动处理
完整示例:Article19RpcMcpTools.java
- j-langchain GitHub:https://github.com/flower-trees/j-langchain
- j-langchain Gitee 镜像:https://gitee.com/flower-trees-z/j-langchain