引言:AI的"知识截止日期"尴尬
如果你想问大模型"明天是星期几?",猜猜TA会怎么答复你~
@GetMapping("/tools/simple/test")
public String simpleTest() {return chatClient.prompt("明天是星期几?").call().content();
}
返回结果:
今天是2023年11月12日,星期日。 因此,**明天是11月13日,星期一**。 如果需要其他日期的查询,可以告诉我哦! 😊
这个看似简单的例子揭示了大模型的本质:它们不是百科全书,而是"概率预测机" (未来可能进一步升级)
- 大模型通过海量历史文本进行训练,学习的是数据中的统计规律和知识关联;例如,GPT-4的训练数据截止到2023年10月,这意味着它对之后的事件一无所知,训练完成后,模型参数固定,知识被编码在神经网络的权重中,这些权重无法自动更新,除非重新训练或微调;模型的知识库像一本印刷好的书,无法动态添加新章节;
- 当用户提问时,模型通过计算输入文本与训练数据中的模式匹配,生成概率最高的回答。例如,问“今天的天气如何?”,模型会基于训练时学到的“天气”相关文本生成答案,而非实时查询;
- 大模型的核心架构(如Transformer)依赖自注意力机制分析文本关系,但缺乏与实时数据源的直接交互能力。即使集成工具调用(Tool Calling),实际的数据获取仍需依赖外部程序,如果允许模型直接访问网络,可能引发数据泄露、恶意请求等问题。例如,模型可能意外输出敏感API密钥,或被诱导访问危险网址,另外实时查询需要调用外部服务,增加响应时间,若每次生成回答都需等待API返回,用户体验会大幅下降
虽然大模型本身不能返回实时信息,但可通过工具调用(Tool Calling)将实时任务“外包”给外部程序
一、Tool入门
1.1 第一个示例
- 定义Tool , 创建一个
DateTimeTools
类,在方法getCurrentDateTime()
标记一个注解@Tool
完成对Tool的定义,其中的description
是对工具能力的描述。
class DateTimeTools {@Tool(description = "获取用户时区的当前日期和时间")String getCurrentDateTime() {return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();}}
- Tool调用,在
tools()
方法中传递本次可调用的工具清单后再发起调用
@GetMapping("/tools/simple/call")public String callTool() {return chatClient.prompt("明天是星期几?").tools(new DateTimeTools()).call().content();}
- 输出
今天是2025年4月15日,星期二。因此,明天是2025年4月16日,星期三。
1.2 Tool的作用
- Tool核心可以拆分为两大类
- 信息检索类工具
这类工具可从外部数据源实时获取信息,包括:数据库记录查询、Web服务API调用、文件系统访问、搜索引擎交互;
核心价值在于扩展模型的知识边界,使其能够回答需要实时数据的问题(如天气查询、新闻检索等),这正是检索增强生成(RAG)技术的典型应用场景;
- 系统操作类工具
这类工具可执行实际业务操作,例如:发送电子邮件、数据库记录增删改、表单自动提交、工作流引擎触发
核心价值在于实现业务流程自动化,替代传统需要人工干预或硬编码的操作。典型应用场景包括:聊天机器人订票系统、网页表单自动填写、基于测试驱动开发(TDD)的代码生成等
两类工具的本质区别在于前者为只读操作(Read-only),后者为写操作(Write-operations),在系统设计时需采用不同的安全控制策略。
- 系统操作类示例,第一个示例展示了时间检索工具,接下来展示下系统操作类工具
为用户设置一个10分钟以后的闹钟
//执行动作@Tool(description = "为用户设置闹钟,时间参数需为ISO-8601格式")void setAlarm(String time) {log.info("setAlarm time={}", time);LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);System.out.println("Alarm set for " + alarmTime);}
执行逻辑
@GetMapping("/tool/alarm")
public String setAlarm() {return chatClient.prompt("帮我设置一个10分钟以后的闹钟").tools(new DateTimeTools()).call().content();
}
结果输出
系统输出日志:
-------模拟系统调用操作,为用户设置闹钟-------
设置闹钟成功! 2025-04-15T22:04:04
大模型调用结果:
闹钟已设置,将在10分钟后(22:04)响起。
1.3 执行流程
下图展示了Tool
的执行流程
- 工具注册阶段,当需要为模型提供工具支持时,需在聊天请求中声明工具定义。每个工具定义包含三个核心要素:工具名称(唯一标识符)、功能描述(自然语言说明)、输入参数结构(JSON Schema格式)
- 模型决策阶段,模型分析请求后,若决定调用工具,将返回结构化响应,包含:目标工具名称、符合预定义Schema的格式化参数
- 工具执行阶段,客户端应用负责根据工具名称定位具体实现,使用验证后的参数执行目标工具
- 工具响应阶段,工具执行结果返回给应用程序
- 重新组装阶段,应用将标准化处理后的执行结果返回给模型再次处理
- 结果响应阶段,模型结合用户初始输入以及工具执行结果再次加工返回给用户
二、工具定义规范
2.1 Methods as Tools
除了利用@Tool
注解申明将一个方法定义为工具,还可以利用编程式定义工具;
一个普通的获取时间的方法,没有任何注解
// 不带注解String getCurrentDateTimeWithoutAnnotation() {return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();}
通过反射获取到该方法,构建ToolDefinition
后生成工具的回调类ToolCallback
,在大模型调用时传递ToolCallback
即可;
@GetMapping("/tools/programmatic")public String toolByProgrammatic() {//编程式定义Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTimeWithoutAnnotation");ToolDefinition toolDefinition = ToolDefinition.builder(method).description("获取用户时区的当前日期和时间").build();ToolCallback toolCallback = MethodToolCallback.builder().toolDefinition(toolDefinition).toolMethod(method).toolObject(new DateTimeTools()).build();return chatClient.prompt("明天是星期几?").tools(toolCallback).call().content();}
2.2 Functions as Tools
除了前面将方法定义为工具,还可以将Function
, Supplier
, Consumer
, or BiFunction
等函数式接口类型定义为工具
模拟一个查询天气的服务,实现Function
接口,依据用户输入的位置信息返回气温,由于是模拟逻辑,固定都返回30摄氏度
public class WeatherService implements Function<WeatherRequest, WeatherResponse> {public WeatherResponse apply(WeatherRequest request) {//固定返回30度return new WeatherResponse(30.0, Unit.C);}}public enum Unit {C, F}public record WeatherRequest(String location, Unit unit) {}public record WeatherResponse(double temp, Unit unit) {}
- 申明式定义
申明一个Spring Bean,Bean的名称作为工具的名称,@Description
作为工具的描述
@Configuration(proxyBeanMethods = false) //禁用基于 CGLIB 的代理方法拦截,将配置类转变为纯粹的"工厂方法"模式class WeatherTools {WeatherService weatherService = new WeatherService();@Bean@Description("依据位置获取天气信息")Function<WeatherRequest, WeatherResponse> currentWeather() {return weatherService;}}
将Bean的名称传递到tools中,发起调用
@GetMapping("/tool/function/annotation")public String toolFunctionAnnotation() {return chatClient.prompt("杭州天气怎么样?").tools("currentWeather").call().content();}
输出
杭州当前的天气是30°C,比较炎热。
- 编程式定义
@GetMapping("/tool/function/programmatic")
public String toolFunctionProgrammatic() {ToolCallback toolCallback = FunctionToolCallback.builder("currentWeather", new WeatherService()).description("依据位置获取天气信息").inputType(WeatherRequest.class).build();return chatClient.prompt("杭州天气怎么样?").tools(toolCallback).call().content();
}
三、更多技巧
3.1 参数传递
定义一个订单查询的工具,该工具需要接收一个交易订单号和所在租户ID两个参数,那在调用时如何传递过来呢?
class OrderQuery {@Tool(description = "查询用户交易订单信息")String getTradeOrderInfo(@ToolParam(description = "交易订单号") Long id, ToolContext toolContext) {//模拟按订单号和租户号查询订单信息return "订单号:" + id + " 租户Id:" + toolContext.getContext().get("tenantId");}}
针对ToolParam
注解标识的订单号id,大模型可以自动解析出来,租户信息可以通过发起请求时的上下文直接传递
@GetMapping("/tool/context/order")public String queryOrder() {//上下文return chatClient.prompt("查询订单编号为123的交易订单信息").tools(new OrderQuery()).toolContext(Map.of("tenantId", "zh")).call().content();}
输出
订单编号为123的交易订单信息如下:
订单内容: 123
租户ID: zh
3.2 直接返回
在1.3章节中分析过Tool
的执行流程,可以发现一次Tool
的调用会和大模型交互两次,第一次发起请求,第二次在工具执行完成后带着工具执行的结果再次调用大模型;多次和大模型交互在耗时上会大幅增加,通常可以再工具提前加工好业务数据直接返回即可
直接返回的功能很简单,只要在@Tool
上标记returnDirect = true
class OrderQuery {@Tool(description = "查询用户交易订单信息",returnDirect = true)String getTradeOrderInfo(@ToolParam(description = "交易订单号") Long id, ToolContext toolContext) {//模拟按订单号和租户号查询订单信息return "订单号:" + id + " 租户Id:" + toolContext.getContext().get("tenantId");}}
3.3 自定义工具执行
前面的示例中工具的执行都是执行触发和完成的,可以通过ToolCallingManager
工具调用管理器来灵活控制工具执行的生命周期;
@GetMapping("/tool/control")public String controlTool() {ToolCallingManager toolCallingManager = ToolCallingManager.builder().build();ToolCallback[] customerTools = ToolCallbacks.from(new DateTimeTools());ChatOptions chatOptions = ToolCallingChatOptions.builder().toolCallbacks(customerTools).internalToolExecutionEnabled(false).build();Prompt prompt = new Prompt("明天星期几?", chatOptions);ChatResponse chatResponse = chatClient.prompt(prompt).call().chatResponse();while (chatResponse.hasToolCalls()) {log.info("控制工具执行 start");ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(prompt, chatResponse);prompt = new Prompt(toolExecutionResult.conversationHistory(), chatOptions);chatResponse = chatClient.prompt(prompt).call().chatResponse();log.info("控制工具执行 end");}return chatResponse.getResult().getOutput().getText();}
internalToolExecutionEnabled(false)
手动标记内部自动执行工具为失效- 第一次chatClient.prompt的执行会返回大模型决策所需要调用的工具集
- 第二次通过代码手动再次发起调用,这里就是灵活定制的位置
输出结果
明天是2025年4月16日,星期三。
四、关键代码
ToolCallingAutoConfiguration
定义工具调用的自动装配信息,定义工具调用的关键两个bean ToolCallbackResolver
和ToolCallingManager
ToolCallbackResolver
ToolCallbackResolver依据工具名称返回ToolCallback
实例,利用DelegatingToolCallbackResolver
来委派调用StaticToolCallbackResolver
和SpringBeanToolCallbackResolver
StaticToolCallbackResolver
: 静态工具回调解析器SpringBeanToolCallbackResolver
: Spring容器管理的bean的工具回调解析器
@Bean@ConditionalOnMissingBeanToolCallbackResolver toolCallbackResolver(GenericApplicationContext applicationContext,List<FunctionCallback> functionCallbacks, List<ToolCallbackProvider> tcbProviders) {List<FunctionCallback> allFunctionAndToolCallbacks = new ArrayList<>(functionCallbacks);tcbProviders.stream().map(pr -> List.of(pr.getToolCallbacks())).forEach(allFunctionAndToolCallbacks::addAll);var staticToolCallbackResolver = new StaticToolCallbackResolver(allFunctionAndToolCallbacks);var springBeanToolCallbackResolver = SpringBeanToolCallbackResolver.builder().applicationContext(applicationContext).build();return new DelegatingToolCallbackResolver(List.of(staticToolCallbackResolver, springBeanToolCallbackResolver));}
SpringBeanToolCallbackResolver
解析出工具类型
、工具入参类型
、工具描述
后获取到Spring bean实例,调用buildToolCallback
构造ToolCallback
,然后存储在本地缓存中。
public ToolCallback resolve(String toolName) {Assert.hasText(toolName, "toolName cannot be null or empty");logger.debug("ToolCallback resolution attempt from Spring application context");ToolCallback resolvedToolCallback = toolCallbacksCache.get(toolName);if (resolvedToolCallback != null) {return resolvedToolCallback;}ResolvableType toolType = TypeResolverHelper.resolveBeanType(applicationContext, toolName);ResolvableType toolInputType = (ResolvableType.forType(Supplier.class).isAssignableFrom(toolType))? ResolvableType.forType(Void.class) : TypeResolverHelper.getFunctionArgumentType(toolType, 0);String toolDescription = resolveToolDescription(toolName, toolInputType.toClass());Object bean = applicationContext.getBean(toolName);resolvedToolCallback = buildToolCallback(toolName, toolType, toolInputType, toolDescription, bean);toolCallbacksCache.put(toolName, resolvedToolCallback);return resolvedToolCallback;}
在buildToolCallback
方法中调用generateSchema
生成入参的scheme信息,这一点是可以在自定义框架场景借鉴的, 有了入参类型,入参scheme等信息后真正触发调用时即可快速构建入参实例。
private String generateSchema(ResolvableType toolInputType) {if (schemaType == SchemaType.OPEN_API_SCHEMA) {return JsonSchemaGenerator.generateForType(toolInputType.getType(),JsonSchemaGenerator.SchemaOption.UPPER_CASE_TYPE_VALUES);}return JsonSchemaGenerator.generateForType(toolInputType.getType());}
ToolCallingManager
ToolCallingManager
是一个基于大模型交互的工具管理器,依赖toolCallbackResolver
,有两个核心方法:
resolveToolDefinitions
:基于ToolCallingChatOptions
解析工具定义executeToolCalls
: 执行工具调用
ToolCallingManager toolCallingManager(ToolCallbackResolver toolCallbackResolver,ToolExecutionExceptionProcessor toolExecutionExceptionProcessor,ObjectProvider<ObservationRegistry> observationRegistry) {return ToolCallingManager.builder().observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)).toolCallbackResolver(toolCallbackResolver).toolExecutionExceptionProcessor(toolExecutionExceptionProcessor).build();}
public interface ToolCallingManager {/*** Resolve the tool definitions from the model's tool calling options.*/List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions);/*** Execute the tool calls requested by the model.*/ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse);/*** Create a default {@link ToolCallingManager} builder.*/static DefaultToolCallingManager.Builder builder() {return DefaultToolCallingManager.builder();}}
resolveToolDefinitions
从ToolCallingChatOptions
中获取工具名集合toolNames,按工具名称遍历调用toolCallbackResolver
解析FunctionCallback
,最终转换为ToolDefinition
的集合
public List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions) {Assert.notNull(chatOptions, "chatOptions cannot be null");List<FunctionCallback> toolCallbacks = new ArrayList<>(chatOptions.getToolCallbacks());for (String toolName : chatOptions.getToolNames()) {// Skip the tool if it is already present in the request toolCallbacks.// That might happen if a tool is defined in the options// both as a ToolCallback and as a tool name.if (chatOptions.getToolCallbacks().stream().anyMatch(tool -> tool.getName().equals(toolName))) {continue;}FunctionCallback toolCallback = toolCallbackResolver.resolve(toolName);if (toolCallback == null) {throw new IllegalStateException("No ToolCallback found for tool name: " + toolName);}toolCallbacks.add(toolCallback);}return toolCallbacks.stream().map(functionCallback -> {if (functionCallback instanceof ToolCallback toolCallback) {return toolCallback.getToolDefinition();}else {return ToolDefinition.builder().name(functionCallback.getName()).description(functionCallback.getDescription()).inputSchema(functionCallback.getInputTypeSchema()).build();}}).toList();}
executeToolCalls
参数chatResponse
中传递了大模型返回的预期要调用的工具信息,然后构建执行上下文,executeToolCall(prompt, assistantMessage,toolContext)
触发工具的回调,在该方法中会依据ToolCallback中的ToolMetadata来识别调用完工具后是否立即返回returnDirect
public ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse) {Assert.notNull(prompt, "prompt cannot be null");Assert.notNull(chatResponse, "chatResponse cannot be null");Optional<Generation> toolCallGeneration = chatResponse.getResults().stream().filter(g -> !CollectionUtils.isEmpty(g.getOutput().getToolCalls())).findFirst();if (toolCallGeneration.isEmpty()) {throw new IllegalStateException("No tool call requested by the chat model");}AssistantMessage assistantMessage = toolCallGeneration.get().getOutput();ToolContext toolContext = buildToolContext(prompt, assistantMessage);InternalToolExecutionResult internalToolExecutionResult = executeToolCall(prompt, assistantMessage,toolContext);List<Message> conversationHistory = buildConversationHistoryAfterToolExecution(prompt.getInstructions(),assistantMessage, internalToolExecutionResult.toolResponseMessage());return ToolExecutionResult.builder().conversationHistory(conversationHistory).returnDirect(internalToolExecutionResult.returnDirect()).build();}
private InternalToolExecutionResult executeToolCall(Prompt prompt, AssistantMessage assistantMessage,ToolContext toolContext) {List<FunctionCallback> toolCallbacks = List.of();if (prompt.getOptions() instanceof ToolCallingChatOptions toolCallingChatOptions) {toolCallbacks = toolCallingChatOptions.getToolCallbacks();}else if (prompt.getOptions() instanceof FunctionCallingOptions functionOptions) {toolCallbacks = functionOptions.getFunctionCallbacks();}List<ToolResponseMessage.ToolResponse> toolResponses = new ArrayList<>();Boolean returnDirect = null;for (AssistantMessage.ToolCall toolCall : assistantMessage.getToolCalls()) {logger.debug("Executing tool call: {}", toolCall.name());String toolName = toolCall.name();String toolInputArguments = toolCall.arguments();FunctionCallback toolCallback = toolCallbacks.stream().filter(tool -> toolName.equals(tool.getName())).findFirst().orElseGet(() -> toolCallbackResolver.resolve(toolName));if (toolCallback == null) {throw new IllegalStateException("No ToolCallback found for tool name: " + toolName);}if (returnDirect == null && toolCallback instanceof ToolCallback callback) {returnDirect = callback.getToolMetadata().returnDirect();}else if (toolCallback instanceof ToolCallback callback) {returnDirect = returnDirect && callback.getToolMetadata().returnDirect();}else if (returnDirect == null) {// This is a temporary solution to ensure backward compatibility with// FunctionCallback.// TODO: remove this block when FunctionCallback is removed.returnDirect = false;}String toolResult;try {toolResult = toolCallback.call(toolInputArguments, toolContext);}catch (ToolExecutionException ex) {toolResult = toolExecutionExceptionProcessor.process(ex);}toolResponses.add(new ToolResponseMessage.ToolResponse(toolCall.id(), toolName, toolResult));}return new InternalToolExecutionResult(new ToolResponseMessage(toolResponses, Map.of()), returnDirect);}
由于工具的定义有多种形式,因此toolCallback.call
真正执行会触发不同的实现子类
以FunctionToolCallback
的实现为例,先通过JsonParser.fromJson
构建工具调用参数,执行apply
方法,然后以DefaultToolCallResultConverter
转换为目标结果对象
public String call(String toolInput, @Nullable ToolContext toolContext) {Assert.hasText(toolInput, "toolInput cannot be null or empty");logger.debug("Starting execution of tool: {}", toolDefinition.name());I request = JsonParser.fromJson(toolInput, toolInputType);O response = toolFunction.apply(request, toolContext);logger.debug("Successful execution of tool: {}", toolDefinition.name());return toolCallResultConverter.convert(response, null);}
执行链路
用Functions as Tools
的示例来串整体链路
@Configuration(proxyBeanMethods = false) //禁用基于 CGLIB 的代理方法拦截,将配置类转变为纯粹的"工厂方法"模式class WeatherTools {WeatherService weatherService = new WeatherService();@Bean@Description("依据位置获取天气信息")Function<WeatherRequest, WeatherResponse> currentWeather() {return weatherService;}}@GetMapping("/tool/function/annotation")public String toolFunctionAnnotation() {return chatClient.prompt("杭州天气怎么样?").tools("currentWeather").call().content();}
chatClient.prompt("杭州天气怎么样?").tools("currentWeather")
中的tools
方法在DefaultChatClient
类的属性functionCallbacks
添加工具的bean名称列表
public ChatClientRequestSpec tools(FunctionCallback... toolCallbacks) {Assert.notNull(toolCallbacks, "toolCallbacks cannot be null");Assert.noNullElements(toolCallbacks, "toolCallbacks cannot contain null elements");this.functionCallbacks.addAll(List.of(toolCallbacks));return this;}
后续的call().content()逻辑和Spring AI源码浅析之一山可容二虎
文章中的流程一致,会触发CallAroundAdvisor
的匿名实现类,其中**advisedRequest.toPrompt()**
会构建Prompt
实例,其中会将functionCallingOptions
中设置setFunctions
从而传递工具信息
this.advisors.add(new CallAroundAdvisor() {//省略部分代码@Overridepublic AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {return new AdvisedResponse(chatModel.call(advisedRequest.toPrompt()), Collections.unmodifiableMap(advisedRequest.adviseContext()));}});this.advisors.add(new StreamAroundAdvisor() { //省略部分代码@Overridepublic Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {return chatModel.stream(advisedRequest.toPrompt()).map(chatResponse -> new AdvisedResponse(chatResponse, Collections.unmodifiableMap(advisedRequest.adviseContext()))).publishOn(Schedulers.boundedElastic()); // TODO add option to disable.}});
public Prompt toPrompt() {var messages = new ArrayList<>(this.messages());String processedSystemText = this.systemText();if (StringUtils.hasText(processedSystemText)) {if (!CollectionUtils.isEmpty(this.systemParams())) {processedSystemText = new PromptTemplate(processedSystemText, this.systemParams()).render();}messages.add(new SystemMessage(processedSystemText));}String formatParam = (String) this.adviseContext().get("formatParam");var processedUserText = StringUtils.hasText(formatParam)? this.userText() + System.lineSeparator() + "{spring_ai_soc_format}" : this.userText();if (StringUtils.hasText(processedUserText)) {Map<String, Object> userParams = new HashMap<>(this.userParams());if (StringUtils.hasText(formatParam)) {userParams.put("spring_ai_soc_format", formatParam);}if (!CollectionUtils.isEmpty(userParams)) {processedUserText = new PromptTemplate(processedUserText, userParams).render();}messages.add(new UserMessage(processedUserText, this.media()));}if (this.chatOptions() instanceof FunctionCallingOptions functionCallingOptions) {if (!this.functionNames().isEmpty()) {functionCallingOptions.setFunctions(new HashSet<>(this.functionNames()));}if (!this.functionCallbacks().isEmpty()) {functionCallingOptions.setFunctionCallbacks(this.functionCallbacks());}if (!CollectionUtils.isEmpty(this.toolContext())) {functionCallingOptions.setToolContext(this.toolContext());}}return new Prompt(messages, this.chatOptions());}
有了Prompt
后执行org.springframework.ai.openai.OpenAiChatModel#internalCall
,
public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) {// 省略一些代码...ChatCompletionRequest request = createRequest(prompt, false);ResponseEntity<ChatCompletion> completionEntity = this.retryTemplate.execute(ctx -> this.openAiApi.chatCompletionEntity(request, getAdditionalHttpHeaders(prompt)));//省略一些代码...if (ToolCallingChatOptions.isInternalToolExecutionEnabled(prompt.getOptions()) && response != null&& response.hasToolCalls()) {var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response);if (toolExecutionResult.returnDirect()) {// Return tool execution result directly to the client.return ChatResponse.builder().from(response).generations(ToolExecutionResult.buildGenerations(toolExecutionResult)).build();}else {// Send the tool execution result back to the model.return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()),response);}}return response;}
在createRequest(prompt, false)
中通过toolCallingManager.resolveToolDefinitions
解析到工具信息,并添加到ChatCompletionRequest
中,因此第一次发起大模型调用会带着工具信息。
ChatCompletionRequest createRequest(Prompt prompt, boolean stream) {//省略一些代码... // Add the tool definitions to the request's tools parameter.List<ToolDefinition> toolDefinitions = this.toolCallingManager.resolveToolDefinitions(requestOptions);if (!CollectionUtils.isEmpty(toolDefinitions)) {request = ModelOptionsUtils.merge(OpenAiChatOptions.builder().tools(this.getFunctionTools(toolDefinitions)).build(), request,ChatCompletionRequest.class);}// Remove `streamOptions` from the request if it is not a streaming requestif (request.streamOptions() != null && !stream) {logger.warn("Removing streamOptions from the request as it is not a streaming request!");request = request.streamOptions(null);}return request;}
在执行完大模型的调用后,基于返回结果判断是否要执行工具调用,有则通过toolCallingManager
来执行工具调用executeToolCalls
方法,如果工具的元数据标识为直接返回,则立即构建返回结果,否则基于工具结果重新生成提示词发起调用。
结语:从"知道分子"到"实干家"
通过工具调用,我们终于让AI从"纸上谈兵"的书生变成了"能文能武"的全才,可以与时俱进。就像给一位博学的教授配上了实验室和助手,让它不仅能讲解理论,还能动手实验~