Function-call(Tools)
- 目前的大模型AI,都是只知道一些通用的、网络上能查到的信息,并且他们的知识库都有一定的延迟。那么现在要让他查询自己私有系统中的数据时,它就需要调用我们的系统API了。
- 具体流程:
- AI收到一个问题
- AI识别到,这个问题需要去我们的程序应用中获取
- AI提取关键词
- 调用 changshaNameCount 方法
- 通过返回结果再结合上下文,再次请求大模型
- 响应结果
实现一个案例
-
创建一个maven项目
-
pom.xml
xml<dependencyManagement> <dependencies> <!-- 引入 Spring Boot BOM --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>3.4.3</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- SpringBoot 核心包 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- SpringBoot Web容器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 阿里云百炼 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId> <version>1.0.0-beta3</version> </dependency> <!-- webflux --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <!-- langchain4j核心 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>1.0.0-beta3</version> </dependency> </dependencies> -
application.yml(需要所选的大模型支持Function-call)
ymllangchain4j: community: dashscope: chatModel: api-key: "sk-xxxxxxxxxxx" model-name: qwen-plus streaming-chat-model: api-key: "sk-xxxxxxxxxxx" model-name: qwen-plus -
创建一个ToolsService,即用来提供给AI的方法
javaimport dev.langchain4j.agent.tool.P; import dev.langchain4j.agent.tool.Tool; import org.springframework.stereotype.Service; @Service public class PigToolsService{ @Tool("猪头肉市有多少人口") public Integer pigPersonNumCount(@P("人数") String personNum){ System.out.println("模拟查询业务数据库,或者模拟调用api接口......" + personNum); return 10; } @Tool("猪头肉市有的人们最爱吃的食物") public String pigPersonLikeEat(@P("城市") String city, @P("行为") String behavior){ System.out.println("模拟查询业务数据库,或者模拟调用api接口......" + city + "、" + behavior); return "爱吃青菜,总之一般不吃吃猪头肉"; } }- PigToolsService 配置到了IOC容器中
- @Tool 用于告诉AI什么对话调用这个方法
- @P("XX") 用于告诉AI ,调用方法的时候需要提取对话中的什么信息, 这里提取的是 人数
-
Config 配置类,自定义的Assistant
javaimport com.qi.tools.PigToolsService; import dev.langchain4j.memory.ChatMemory; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.chat.StreamingChatLanguageModel; import dev.langchain4j.service.AiServices; import dev.langchain4j.service.TokenStream; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AiConfig { public interface Assistant { String chat(String message); // 流式响应 TokenStream stream(String message); } @Bean public Assistant assistant(ChatLanguageModel qwenChatModel, StreamingChatLanguageModel qwenStreamingChatModel, PigToolsService toolsService //从IOC容器中取了 自定义的PigToolsService ) { ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10); Assistant assistant = AiServices.builder(Assistant.class) .chatLanguageModel(qwenChatModel) .streamingChatLanguageModel(qwenStreamingChatModel) .tools(toolsService) //使用了自定义的PigToolsService,ai有需要的话会调用 .chatMemory(chatMemory) .build(); return assistant; } } -
Controller类
javaimport com.qi.config.AiConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RequestMapping("/Tools") @RestController public class ToolsController { @Autowired AiConfig.Assistant assistant; @RequestMapping(value = "/test1") public String test1(@RequestParam("message") String message) { return assistant.chat(message); } } -
调用接口测试,可以看到AI会基于我们程序提供的方法结果上,进行自己的创作


-
此时控制台的打印

预设角色
- 大模型本身是没有目的的,对话想聊什么就聊什么。
- 那么想要把他集成进我们业务中,就需要给他预设一个角色。
- 很简单,就是告诉他自己是个什么角色。
案例:票务助手
-
原本我们有一个票务系统,现在要接入大模型,让他充当一个简单客服,帮助用户退票。
-
需求:用户说预定或者退票时,大模型会应道用户说出车次和姓名,然后帮用户预定/退票。(仅单单演示案例,不考虑业务的安全方面)
-
前提是我们本身必须为大模型准备两个业务方法,用来预定和退票。
-
在langchain4j中实现逻辑:
- @SystemMessage 系统消息, 一般做一些预设角色的提示词,设置大模型的基本职责
- 可以通过{{current_date}} 传入参数, 因为预设词中的文本可能需要实时变化
- @V("current_date"), 通过@V传入{{}}中的参数
- 一旦参数不止一个, 就需要通过@UserMessage设置用户信息
-
定义一个Assistant
javaimport dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.chat.StreamingChatLanguageModel; import dev.langchain4j.service.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RoleConfig { public interface RoleAssistant { @SystemMessage(""" 您是"猪头肉"航空公司的客户聊天支持代理。请以友好、乐于助人且愉快的方式来回复。 您正在通过在线聊天系统与客户互动。 在提供有关预订或取消预订的信息之前,您必须始终从用户处获取以下信息:车次、客户姓名。 请讲中文。 今天的日期是 {{current_date}}. """) TokenStream stream(@UserMessage String message, @V("current_date") String currentDate); } @Bean public RoleConfig.RoleAssistant roleAssistant(ChatLanguageModel qwenChatModel, StreamingChatLanguageModel qwenStreamingChatModel) { RoleConfig.RoleAssistant assistant = AiServices.builder(RoleConfig.RoleAssistant.class) .chatLanguageModel(qwenChatModel) .streamingChatLanguageModel(qwenStreamingChatModel) .build(); return assistant; } }- """ 是java15引入的文本块,用来写多行字符串,不用手动加\n,直接换行写内容,更清晰。
-
controller接口
javaimport com.qi.config.RoleConfig; import dev.langchain4j.service.TokenStream; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; import java.time.LocalDate; @RequestMapping("/role") @RestController public class RoleController { @Autowired RoleConfig.RoleAssistant roleAssistant; @RequestMapping(value = "/test1",produces ="text/stream;charset=UTF-8") public Flux<String> memoryStreamChat(@RequestParam("message") String message, HttpServletResponse response) { TokenStream stream = roleAssistant.stream(message, LocalDate.now().toString()); return Flux.create(sink -> { stream.onPartialResponse(s -> sink.next(s)) .onCompleteResponse(c -> sink.complete()) .onError(sink::error) .start(); }); } } -
调用接口,此时可以看到他已经代入了预设的角色,此时目的性已经很强了


-
加入Tools,定义预定和退票方法
javaimport dev.langchain4j.agent.tool.P; import dev.langchain4j.agent.tool.Tool; import org.springframework.stereotype.Service; @Service public class TrainToolsService { @Tool("退票") public String refundTicket(@P("车次") String train, @P("姓名") String personName){ System.out.println("模拟调用数据库退票" + train + "、" + personName); return "退票成功"; } @Tool("预定") public String reserveTicket(@P("车次") String train, @P("姓名") String personName){ System.out.println("模拟调用数据库预定票" + train + "、" + personName); return "预定成功"; } } -
修改RoleConfig,加入定义的预定和退票方法(tools)
java@Bean public RoleAssistant roleAssistant(ChatLanguageModel qwenChatModel, StreamingChatLanguageModel qwenStreamingChatModel, TrainToolsService trainToolsService) { RoleAssistant assistant = AiServices.builder(RoleAssistant.class) .chatLanguageModel(qwenChatModel) .streamingChatLanguageModel(qwenStreamingChatModel) .tools(trainToolsService) .build(); return assistant; } -
浏览器调用



-
另外:假设大模型不支持系统消息(一般都支持),可以用@UserMessage代替@SystemMessage
- 但是要注意,不能同时存在两个@UserMessage
javainterface Friend { @UserMessage("你是一个航空智能助手,你需要帮助用户进行服务: {{it}}") String chat(String userMessage); }