该内容为自助投放广告,真伪自辨
立即入驻

用 Koog 在 Java 中创建智能体工具:从方法注解到任务调用

广告也精彩

让大语言模型调用一个 Java 方法,中间隔着的不是复杂配置,而是“模型根本不知道这个方法存在”。很多人第一次给智能体接业务代码时,会写一大段系统提示词,把“你能查余额、能转账、能问用户问题”全塞进去,结果模型照样答非所问。Koog 提供的思路正好相反:不要用提示词描述能力,而是直接在 Java 方法上标注,让方法自己站出来说明“我能做什么”。

Java 代码方法通过注解连接到 AI 智能体工具调用的概念示意图

一个方法变成工具,只差两个注解

在 Koog 里,工具的入口非常朴素:把方法放进一个实现了 ToolSet 接口的类,然后给它打上 @Tool。从这个注解出现的那一刻起,这个方法就不再是普通方法了,它会通过反射被收集起来,作为可调用的工具交给大语言模型。

真正决定模型能不能用对工具的,是紧挨着的另一个注解 @LLMDescription。它承载的是“给模型看的说明书”,而不是给同事看的注释。JetBrains 官方博客给出的例子很典型:一个银行工具类里,转账方法的描述是“Sends money to a recipient”,查询余额的方法描述是“Account balance in $”。模型读到的就是这两句话,它据此判断在什么情况下该调用哪个方法。

public class BankingTools implements ToolSet {

    @Tool
    @LLMDescription("Account balance in $")
    public Integer getAccountBalance(String userId) {
        // 真实实现替换为你的账户服务调用
        return 1000000;
    }
}

@Tool 本身还有一个可选参数 customName,用来指定工具对外的名字;不填的时候,方法名就是工具名。这个细节值得留意:如果你的方法名是 qryBal 这类缩写,模型对它的理解成本会明显高于 getAccountBalance,此时要么改方法名,要么用 customName 补一个语义清楚的名字。

描述写得好不好,直接决定调用准不准

写过提示词的人会有类似体会:越抽象的要求,模型执行得越飘。工具描述也是同一个道理。@LLMDescription 里写“处理用户数据”,模型只能靠猜;写“根据用户 ID 查询该用户的账户余额,返回以整数表示的金额”,模型才知道该在什么时候伸手。

一条可用的描述通常包含三件事:这个工具做什么、需要什么输入、会返回什么。三者缺一,模型就容易在参数上出错,或者在明明该调用的时候选择自己编答案。尤其当工具集里有多个名字相近的方法时,描述之间的区分度就是唯一的路标。

还有一点容易被忽略:描述应该描述“能力”,而不是描述“实现”。模型不需要知道你在里面用了哪个数据库、走了哪条缓存,它只需要知道自己能干什么、能拿到什么。把实现细节塞进描述,反而会稀释关键信息。

工具要窄:一个方法只做一件事

在 Koog 的功能型智能体里,工具是让模型与外部世界交互的通道,比如读写数据、调用接口、执行确定性操作。这类通道越窄,模型判断起来越稳。

有资料提到一个很实用的观察:validateOrder 的提示里只问“欺诈与完整性”,不问库存,也不问价格,这种聚焦让提示远比一个涵盖所有环节的大提示可靠。工具设计同理。一个叫 processOrder、内部同时校验、扣款、发货的方法,模型很难判断该在流程的哪一步调用它;拆成 checkOrderFraud、deductBalance、shipOrder 三个窄工具,每个的描述都能写得干净利落,调用时机也自然清晰。

拆分的另一面是数量。工具堆到十几个、描述又互相重叠时,模型的选错概率会上升。与其把所有能力一次性暴露,不如先只给两三个职责明确、彼此不重叠的工具,把链路跑通再逐个加。这也是“先让最小智能体跑起来,再加一个窄工具”这个顺序的价值所在:第一次成功足够小,下一次出错也容易定位。

多个职责单一的窄工具方块围绕智能体核心排列的示意插画

注册与调用:把工具交到智能体手里

注解只完成了“定义”,工具要被真正使用,还得经过注册这一步。Koog 的做法是把打了 @Tool 的方法通过反射收集,再注册进 ToolRegistry,然后把注册表交给智能体,模型在运行时才有权决定调用哪个工具、什么时候调用。

需要提醒的是,Java 侧具体的注册与构建语法,不同资料给出的写法并不完全一致,有的示例偏概念演示。动手时最稳妥的方式是直接对照官方文档 Annotation-based tools 以及你本地依赖的实际代码库核对一遍方法签名,而不是照抄某篇博客的片段。官方博客中关于 Java 版 Koog 的介绍(JetBrains AI Blog)也提到,Java API 提供了流畅的构建器风格,整体接入路径与 Kotlin 版本保持一致的思路:配置 LLM 提供商、写系统提示定义角色、注册工具,然后运行。

调用本身由模型发起。你在智能体里定义好角色,比如“你是帮助客户查询账户信息的银行助手”,再把工具注册进去,模型会根据用户的话判断是否需要调用工具、调用哪一个。这里有个心理预期要摆正:模型不是编译器,它不会必然调用你期望的那个工具。所以第一次跑通之后,值得专门准备几条边界输入去试探,比如“我账户里还有多少钱”和“帮我转 100 给张三”,看它是否分别命中查询与转账。

上手时容易踩的几个坑

一类常见问题出在返回类型上。工具方法返回什么,模型就会拿到什么。返回一个完整对象或冗长的 JSON,模型需要再花力气从中提取关键信息;返回一句清楚的结果或一个简单值,后续推理会顺很多。示例里返回 Integer 的余额就是这种思路。

另一类是忽视方法所在类的要求。带有 @Tool 的方法必须放在实现了 ToolSet 的类里,否则注解不起作用——这不是可选的风格问题,而是收集机制的前提。刚上手时忘了 implements,工具悄无声息地不生效,排查起来反而费时。

最后一类是描述语言。@LLMDescription 的内容会直接进入模型的上下文,用中文写、用英文写都可以,关键是全站保持一致,并且用模型容易理解的自然语言,而不是内部术语缩写。

搭一个能跑的工具调用原型,其实不需要把 Koog 的全部能力都用上。先从现有代码里挑一个输入输出都很干净的方法,加上 @Tool 和一句讲得清楚的描述,注册进去,用一句真实的话去测它。跑通了,你手里就有一条可以继续往上叠加的链路;跑不通,问题大概率就出在描述或工具粒度上,而不是框架本身。

© 版权声明

相关文章

暂无评论

none
暂无评论...