chat - 工具调用与定制(Tool Call)
v3.2.0 后,原 Function 概念改为 Tool 概念。新的调整,与 MCP 里的工具可以更好的对应起来。
Tool call(或 Function call)能够让大语言模型在生成时,“按需”调用外部的工具,进而连接外部的数据和系统。通过定义一组函数作为模型可访问的工具(也叫函数工具),并根据对话历史在适当的时候使用它们。然后在应用端执行这些函数,并将结果反馈给模型。
可以实现最新的数据状态(比如,联网查询时实天气)或者指令交互(比如,做运维操作)。是 AI 交互系统的基础技术。
1、FunctionTool (函数工具申明)接口与注解
工具,目前主要是指函数工具 FunctionTool(未来可能有不同类型的工具)。接口需要申明工具的类型和名字,描述,输入架构(由输入参数的名字、描述、类型,组合构成),及以处理方法。
//工具接口
public interface Tool {
//工具类型
String type();
}
//函数工具接口
public interface FunctionTool extends Tool {
//工具类型
default String type() {
return "function";
}
//名字
String name();
//描述
String description();
//是否直接返回给调用者(v3.2.1 后支持)
boolean returnDirect();
//输入架构
ONode inputSchema();
//处理
String handle(Map<String, Object> args) throws Throwable;
}
开发时,也可以使用注解简化工具申明(不需要 Bean 容器驱动):
注解 | 描述 |
---|---|
@ToolMapping(name?, description, returnDirect?, resultConverter?) | 工具映射 |
@ToolParam(name?, description, required?) | 工具的参数申明 |
2、returnDirect (直接返回)的作用
MCP 不支持这个特性透传。当 server 和 client 都是 solon-ai-mcp 时,可支持此特性透传。
- returnDirect=false(默认)
user -> llm -> tool -> llm -> user
- returnDirect=true
user -> llm -> tool -> user
3、三种函数工具的定制方式
- (1) 注解申明的定制(比较简洁,一个类里可有多个函数工具)。示例:
我们定义个工具类,并设定一个 “天气查询” 函数和 “联网搜索” 的函数
//可以加组件注解(支持注入和拦截)
public class Tools {
//天气查询
@ToolMapping(description = "获取指定城市的天气情况")
public String get_weather(@ToolParam(name = "location", description = "根据用户提到的地点推测城市") String location) {
if (location == null) {
throw new IllegalStateException("arguments location is null (Assistant recognition failure)");
}
return "晴,24度"; //可使用 “数据库” 或 “网络” 接口根据 location 查询合适数据;
}
//...//可以加其它注解函数
}
应用示例(提醒:如果一次请求,若多个函数有交差描述,自动识别可能会混乱):
public void case3() throws IOException {
ChatResponse resp = chatModel
.prompt("今天杭州的天气情况?")
.options(o -> o.toolsAdd(new Tools()) //会自动匹配天气函数
.toolsAdd(new Tools2())) //可以添加多套工具
.call();
}
- (2) 构建申明方式(比较简洁)。示例:
FunctionToolDesc weatherTool = new FunctionToolDesc("get_weather")
.description("获取指定城市的天气情况")
.stringParam("location", "根据用户提到的地点推测城市")
.doHandle(map -> {
return "24度";
});
应用示例:
public void case3() throws IOException {
ChatResponse resp = chatModel
.prompt("今天杭州的天气情况?")
.options(o -> o.toolsAdd(weatherTool)) //会自动匹配天气函数
.call();
}
- (3) 接口实现的定制方式(比较原始)。示例:
//可以加组件注解(支持注入和拦截)
public class WeatherTool implements FunctionTool {
private List<FunctionToolParam> params = new ArrayList<>();
public WeatherTool() {
//友好的描述,有助于大模型推测参数值
params.add(new FunctionToolParamDesc("location", String.class, true, "根据用户提到的地点推测城市"));
}
@Override
public String name() {
return "get_weather";
}
@Override
public String description() {
//友好的描述,有助于大模型组织回复消息
return "获取指定城市的天气情况";
}
@Override
public ONode inputSchema() {
return ToolSchemaUtil.buildToolParametersNode(this, params, new ONode());
}
@Override
public String handle(Map<String, Object> args) {
String location = (String) args.get("location");
if(location == null) {
//大模型有可能会识别失败
throw new IllegalStateException("arguments location is null (Assistant recognition failure)");
}
return "24度";// 可使用 “数据库” 或 “网络” 接口根据 location 查询合适数据;
}
}
应用示例:
public void case3() throws IOException {
ChatResponse resp = chatModel
.prompt("今天杭州的天气情况?")
.options(o -> o.toolsAdd(new WeatherTool())) //会自动匹配天气函数
.call();
}
4、工具的添加和作用域
- 默认工具(是即每次请求时都会附加)。可在语言模型构建时添加。
public void case3() throws IOException {
ChatModel.of("http://127.0.0.1:11434/api/chat")
.provider("ollama")
.model("llama3.2")
.defaultToolsAdd(new WeatherTool()) //添加默认工具(即所有请求可用)
.defaultToolsAdd(new WeatherTool2()) //可以添加多套工具(只是示例下)
.build();
ChatResponse resp = chatModel
.prompt("今天杭州的天气情况?")
.call();
//打印消息
log.info("{}", resp.getMessage());
}
- 请求工具(当次请求时附加)。和全局工具相比,只是作用域不同。
public void case3() throws IOException {
ChatModel.of("http://127.0.0.1:11434/api/chat")
.provider("ollama")
.model("llama3.2")
.build();
ChatResponse resp = chatModel
.prompt("今天杭州的天气情况?")
.options(o -> o.toolsAdd(new WeatherTool())) //添加请求函数
.call();
//打印消息
log.info("{}", resp.getMessage());
}