LLMs 除了生成文本外,还可以触发操作。这个触发操作我们称之为工具(或函数调用),本篇文章将介绍LangChain4j框架是如何实现函数调用能力的。
什么是函数调用
函数调用功能可以增强模型推理效果或进行其他外部操作,包括信息检索、数据库操作、知识图谱搜索与推理、操作系统、触发外部操作等工具调用场景。
函数调用可以让大模型根据提示词输出一个请求调用函数的消息,其中包含所有需要调用的函数的信息、以及调用函数时所携带的参数信息。这是将大模型(LLM)能力与外部工具/API连接起来的方式。
例如,我们知道LLMs自己不太擅长数学。如果您的用例涉及数学计算,您可能希望提供LLM一个"数学工具"。通过在请求中声明一个或多个工具LLM,它可以决定在它认为合适的情况下调用其中一个工具。给定一个数学问题以及一组数学工具,LLM可能会决定要正确回答问题,它应该首先调用提供的数学工具之一。
哪些大模型支持函数调用,请参照# LangChain4j系列:LangChain4j LLM API 详解并接入OpenAI实现聊天 支持的LLMs中的Tools一栏。
函数调用解决问题
- 触发外部操作
- 解决大模型数据滞后性问题,可以将大模型链接到外部获取相关数据。比如搜索、数据库查询等
- 解决大模型无真逻辑问题,比如复杂的数据计算问题。
如何定义一个函数
为了增加使用正确参数LLM
调用正确工具的机会,我们应该提供一个清晰明确的:
- 工具的名称
- 描述该工具的作用以及何时应使用该工具
- 每个工具参数的说明
一个好的经验法则:如果一个人能够理解工具的用途以及如何使用它,那么LLM
也可以。如果定义不清晰则会导致函数调用失败。
LLMs经过专门微调,以检测何时调用工具以及如何调用它们。有些模型甚至可以同时调用多个工具,例如 OpenAI。
LangChain4j Tools API
LangChain4j 为使用工具提供了两个抽象级别:
- Low-level, 使用
ChatLanguageModel
API。 「灵活度高」 - High-level, 使用 AiService 和
@Tool
带注释的 Java 方法。 「使用方便」
一般情况下推荐使用High-level的方式进行开发,使用方便,可以让开发者集中在业务开发。但是如果想追求开发的灵活性,那Low-level是你的选择。
定义Tools方式
-
第一种方式:ToolSpecification 使用
ToolSpecification
定义工具的名称、描述、参数的名称以及描述。javaToolSpecification.builder() .name("add") .description("Calculates the sum of two numbers") .addParameter("a", type("integer"), description("The first number")) .addParameter("b", type("integer"), description("The second number")) .build();
注意:定义一个ToolSpecification,必须有一个 ToolExecutor 执行工具/函数调用。
-
第二种方式:ToolSpecifications, 需要使用@Tools配合使用
ToolSpecifications.toolSpecificationsFrom(Class)
ToolSpecifications.toolSpecificationsFrom(Object)
ToolSpecifications.toolSpecificationFrom(Method)
-
第三种方式:
@Tool
、@P
、@ToolMemoryId
@Tool
:定义工具/函数- name:名称,要具体
- value:描述,要清晰
@P
:对工具/方法的参数进行说明value
:参数说明。必填项。required
:参数是否为必填项,默认为true
。可选字段。
@ToolMemoryId
:AI Service 方法上具有@MemoryId
注释的参数。则可以使用@ToolMemoryId
注释@Tool
方法的参数。
作用:提供给AI Service 方法的值将自动传递给该
@Tool
方法。如果聊天记忆按照用户维护隔离时,并且希望在@Tool
方法中区分,那么需要使用@ToolMemoryId
。
Tools的执行
有了Tools的定义,那么就可以直接将其传给大模型,进行调用了。
使用 ChatLanguageModel
(low-level)
java
public interface ChatLanguageModel {
// toolSpecifications 参数为工具定义,将使用上述两种方式之一创建工具定义
default Response<AiMessage> generate(List<ChatMessage> messages, List<ToolSpecification> toolSpecifications) {
throw new IllegalArgumentException("Tools are currently not supported by this model");
}
// 单个工具定义
default Response<AiMessage> generate(List<ChatMessage> messages, ToolSpecification toolSpecification) {
throw new IllegalArgumentException("Tools are currently not supported by this model");
}
}
使用 AI Services
(high-level)
java
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,
tools = {"calculator"}
)
public interface Assistant {
String chat(String userMessage);
}
这个也有两种方式的使用;
- wiringMode=AiServiceWiringMode.EXPLICIT,必须指定bean的名字。
- wiringMode=AiServiceWiringMode.AUTOMATIC,会自动注入所有的@Tool标准的方法。但是类实例也必须被Spring Bean工厂管理。
代码实践
定义工具
java
package org.ivy.chatmemory.tool;
import dev.langchain4j.agent.tool.Tool;
import org.springframework.stereotype.Component;
@Component
public class Calculator {
@Tool("Calculates the sum of two numbers")
public int add(int a, int b) {
System.out.println("Called add with a=" + a + ", b=" + b);
return a + b;
}
}
AI Services
java
package org.ivy.chatmemory.service;
import dev.langchain4j.service.spring.AiService;
import dev.langchain4j.service.spring.AiServiceWiringMode;
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,
tools = {"calculator"}
)
public interface Assistant {
String chat(String userMessage);
}
Controller
java
package org.ivy.chatmemory.controller;
import org.ivy.chatmemory.service.Assistant;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ToolsController {
private final Assistant assistant;
public ToolsController(Assistant assistant) {
this.assistant = assistant;
}
@GetMapping("/cal")
public String calculate(String prompt) {
return assistant.chat(prompt);
}
}
测试结果
请求:[http://localhost:8805/cal?prompt=Calculates the sum of two numbers 2 and 10]
源码剖析
以实现的例子,看清函数调用的真实面目:
- 输入提示词:Calculates the sum of two numbers 2 and 10
- 发送给大模型的请求:
- 大模型返回:
如果大模型决定使用函数调用,则返回的AiMessage中包含toolExecutionRequests对象(一个或者多个 ),用于函数的执行,主要包含如下字段;
- id:调用工具的id,有些LLMs不提供。
- name:要调用工具的名称, 例如:add 方法。
- arguments:调用工具的参数,例如{"a":1,"b":10}。
- 函数的真正执行
- message中包含的信息
- message中包含的信息
- 最终返回结果
从整个过程来看,函数调用要进行两次与大模型交互;
- 第一次:通过提示词 + 函数定义 返回要调用的函数和函数所有需要的参数。
- 第二次:将第一次返回的函数调用结果 + 用户提示词 发送给大模型返回最终的结果。
所有源码内容都在 dev.langchain4j.service.DefaultAiServices
类中实现,大家可以单步调试,看实现原理。 通过查看源码,可以清晰的理解了什么是函数调用了!!
示例源码与总结
示例源码
本文介绍了函数调用作用,详细介绍了LangChain4j实现函数调用的API和工具类,并实现了一个简单的两数相加的工具,最后对函数调用执行过程,源码进行分析。