springboot集成langchain4j实现票务助手实战_langchain票务助手
前言
看此篇的前置知识为langchain4j整合springboot,以及springboot集成langchain4j记忆对话。
Function-Calls介绍
- langchain4j 中的 Function Calls(函数调用)是一种让大语言模型(LLM)与外部工具(如 API、代码执行器等)交互的机制。通过这种机制,LLM 可以根据上下文动态调用开发者预定义的函数,从而扩展其能力边界,解决纯文本生成无法处理的复杂任务(如数学计算、实时数据查询、业务逻辑处理等)。
- 对于基础大模型来说,他只具备通用信息,他的参数都是拿公网进行训练,并且有一定的时间延迟,无法得知一些具体业务数据和实时数据,这些数据往往被各软件系统存储在自己数据库中:
- 比如我现在开发一个智能票务助手我现在跟AI说需要退票,AI怎么做到呢?就需要让AI调用我们自己系统的退票业务方法,进行数据库操作。
- 那这些都可以通过function-call进行完成,更多的用于实现类似智能客服场景,因为客服需要帮用户解决业务问题(就需要调用业务方法)。
Function-Calls流程
比如: 现在当用户问的是“kizzo页面访问量有多少”,大模型需要从程序内部获取
1.问大模型 “页面访问量有多少”
2.大模型在识别到你的问题是:“kizzo页面访问量有多少”
3.大模型提取“访问量”
4.调用 pageViewCount方法
5.通过返回的结果再结合上下文再次请求大模型
6.响应“Kizzo页面的访问量目前有1000次。”
#mermaid-svg-nRQFgp0Trd2h6GWd {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-nRQFgp0Trd2h6GWd .error-icon{fill:#552222;}#mermaid-svg-nRQFgp0Trd2h6GWd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nRQFgp0Trd2h6GWd .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-nRQFgp0Trd2h6GWd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nRQFgp0Trd2h6GWd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nRQFgp0Trd2h6GWd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nRQFgp0Trd2h6GWd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nRQFgp0Trd2h6GWd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nRQFgp0Trd2h6GWd .marker.cross{stroke:#333333;}#mermaid-svg-nRQFgp0Trd2h6GWd svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nRQFgp0Trd2h6GWd .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-nRQFgp0Trd2h6GWd text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-nRQFgp0Trd2h6GWd .actor-line{stroke:grey;}#mermaid-svg-nRQFgp0Trd2h6GWd .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-nRQFgp0Trd2h6GWd .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-nRQFgp0Trd2h6GWd #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-nRQFgp0Trd2h6GWd .sequenceNumber{fill:white;}#mermaid-svg-nRQFgp0Trd2h6GWd #sequencenumber{fill:#333;}#mermaid-svg-nRQFgp0Trd2h6GWd #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-nRQFgp0Trd2h6GWd .messageText{fill:#333;stroke:#333;}#mermaid-svg-nRQFgp0Trd2h6GWd .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-nRQFgp0Trd2h6GWd .labelText,#mermaid-svg-nRQFgp0Trd2h6GWd .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-nRQFgp0Trd2h6GWd .loopText,#mermaid-svg-nRQFgp0Trd2h6GWd .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-nRQFgp0Trd2h6GWd .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-nRQFgp0Trd2h6GWd .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-nRQFgp0Trd2h6GWd .noteText,#mermaid-svg-nRQFgp0Trd2h6GWd .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-nRQFgp0Trd2h6GWd .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-nRQFgp0Trd2h6GWd .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-nRQFgp0Trd2h6GWd .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-nRQFgp0Trd2h6GWd .actorPopupMenu{position:absolute;}#mermaid-svg-nRQFgp0Trd2h6GWd .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-nRQFgp0Trd2h6GWd .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-nRQFgp0Trd2h6GWd .actor-man circle,#mermaid-svg-nRQFgp0Trd2h6GWd line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-nRQFgp0Trd2h6GWd :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;} 用户 应用langchain4j 大模型 页面访问量有多少? 调用记忆接口,输入message\"页面访问量有多少?\" 提取关键字\"访问量”并调用pageViewCount方法\" 返回1000 组织语言,返回\"Kizzo页面的访问量目前有1000次。\" Kizzo页面的访问量目前有1000次。 用户 应用langchain4j 大模型
Function-Calls代码实现
- 加入回调
@Service@Slf4jpublic class ToolsService { @Tool(\"kizzo页面访问量有多少\") public Integer pageViewCount(@P(\"访问量\") String pv){ //todo 此处可以查询数据库或rpc方法 log.info(\"pv:{}\", pv); // 结果 return 1000; }}
ToolsService配置为了一个bean
@Tool 用于告诉AI什么对话调用这个方法
@P(“访问量\")用于告诉AI,调用方法的时候需要提取对话中的什么信息,这里提取的是访问量
- 在AiConfig中的助手对象增加Function-Calls Tools
@Bean public Assistant assistant(ChatLanguageModel chatLanguageModel, StreamingChatLanguageModel streamingChatLanguageModel, ToolsService toolsService){ // 最多存储多少聊天记录 ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10); // 为Assistant动态代理对象chat ---> 对话内容存储ChatMemoryi ---> 聊天记录ChatMemory取出来 ---->放入到当前对话中 Assistant assistant = AiServices.builder(Assistant.class) .tools(toolsService) .chatLanguageModel(chatLanguageModel) .streamingChatLanguageModel(streamingChatLanguageModel) .chatMemory(chatMemory) .build(); return assistant; }
再次调用后结果如下:
预设角色(系统消息SystemMessage)
基础大模型是没有目的性的,你聊什么给什么,但是如果我们开发的事一个智能票务助手,我需要他以一个票务助手的角色跟我对话, 并且在我跟他说”退票”的时候, 让大模型一定要告诉我“车次”和“姓名\"这样我才能去调用业务方法(假设有一个业务方法,需要根据车子和姓名才能查询具体车票),进行退票。
在langchain4j中实现也非常简单
- @SystemMessage 系统消息,一般做一些预设角色的提示词,设置大模型的基本职责
- 可以通过{{current date}} 传入参数,因为预设词中的文本可能需要实时变化
- @V(“current date”),通过@V传入{{}中的参数
- 一旦参数不止一个,就需要通过@UserMessage设置用户信息
代码实现:
- 在AiConfig中的助手对象重载一个stream流输出方法,用@SystemMessage预设提示词角色
@SystemMessage(\"\"\" 您是“xx”航空公司的客户聊天支持代理。请以友好、乐于助人且愉快的方式来回复。 您正在通过在线聊天系统与客户互动。 在提供有关预订或取消预订的信息之前,您必须始终从用户处获取以下信息:预订号、客户姓名。 请讲中文。 今天的日期是 {{current_date}}. \"\"\") TokenStream stream(@UserMessage String message, @V(\"current_date\") String currentDate);
- 在ToolsService中新增一个tool
@Tool(\"退票\") public String cancelBooking(@P(\"地区\") String bookingNumber,@P(\"姓名\") String name){ //todo 业务方法,退票数据库操作 log.info(\"bookingNumber:{},name:{}\", bookingNumber,name); // 结果 return \"退票成功\"; }
- Controller中新增一个接口调用
// 预设角色记忆流对话 @RequestMapping(value = \"/system_message_chat_stream\",produces = \"text/stream;charset=UTF8\") public Flux<String> systemMessageStreamChat(@RequestParam(defaultValue=\"我是谁\") String message) { TokenStream stream = assistant.stream(message, LocalDate.now().toString()); return Flux.create(sink -> { stream.onPartialResponse(s -> sink.next(s)) .onCompleteResponse(c -> sink.complete()) .onError(sink::error) .start(); }); }
结果如下: