langchain4j DefaultAiServices源码解析

版本

0.28.0

源码

使用langchain4j,可以通过AiServices来封装聊天模型API,实现会话记忆,工具调用,搜索增强,内容审查等功能,并提供简单灵活的用户接口
DefaultAiServices是其默认实现类型,通过动态代理的方式实现用户定义的服务接口

class DefaultAiServices<T> extends AiServices<T> {private static final int MAX_SEQUENTIAL_TOOL_EXECUTIONS = 10;DefaultAiServices(AiServiceContext context) {super(context);}// 校验使用提示词模板发送消息的方法参数static void validateParameters(Method method) {// 如果只有一个参数或者没有参数跳过检查(参数直接作为内容发送的方法/其他非发送内容的方法)Parameter[] parameters = method.getParameters();if (parameters == null || parameters.length < 2) {return;}for (Parameter parameter : parameters) {// 获取应用于提示词模板的参数(带有V注解)V v = parameter.getAnnotation(V.class);// 获取用户消息模板参数dev.langchain4j.service.UserMessage userMessage = parameter.getAnnotation(dev.langchain4j.service.UserMessage.class);// 获取记忆ID参数MemoryId memoryId = parameter.getAnnotation(MemoryId.class);// 获取用户名参数UserName userName = parameter.getAnnotation(UserName.class);// 如果没有任何模板参数则报错if (v == null && userMessage == null && memoryId == null && userName == null) {throw illegalConfiguration("Parameter '%s' of method '%s' should be annotated with @V or @UserMessage or @UserName or @MemoryId",parameter.getName(), method.getName());}}}public T build() {// 基本校验// 1. 校验chatModel/streamingChatModel是否有值// 2. 校验toolSpecifications有值时上下文是否启用记忆(使用工具调用至少需要在记忆中保存3个消息)performBasicValidation();// 校验方法使用了Moderate时是否同时指定了审查模型(moderationModel)for (Method method : context.aiServiceClass.getMethods()) {if (method.isAnnotationPresent(Moderate.class) && context.moderationModel == null) {throw illegalConfiguration("The @Moderate annotation is present, but the moderationModel is not set up. " +"Please ensure a valid moderationModel is configured before using the @Moderate annotation.");}}// 构造动态代理Object proxyInstance = Proxy.newProxyInstance(context.aiServiceClass.getClassLoader(),new Class<?>[]{context.aiServiceClass},new InvocationHandler() {private final ExecutorService executor = Executors.newCachedThreadPool();@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Exception {// 直接执行Object类定义的方法if (method.getDeclaringClass() == Object.class) {// methods like equals(), hashCode() and toString() should not be handled by this proxyreturn method.invoke(this, args);}// 校验提示词模板参数validateParameters(method);// 获取系统消息Optional<SystemMessage> systemMessage = prepareSystemMessage(method, args);// 获取用户消息UserMessage userMessage = prepareUserMessage(method, args);// 获取记忆ID参数值,如果没有记忆ID参数则使用默认值“default”Object memoryId = memoryId(method, args).orElse(DEFAULT);// 使用检索增强生成(RAG),将检索结果内容与用户原始消息文本整合作为用户消息if (context.retrievalAugmentor != null) {List<ChatMessage> chatMemory = context.hasChatMemory()? context.chatMemory(memoryId).messages(): null;Metadata metadata = Metadata.from(userMessage, memoryId, chatMemory);userMessage = context.retrievalAugmentor.augment(userMessage, metadata);}// 用于提供客制化的输出解析,根据函数返回类型生成需要返回的消息格式的相关提示词,追加到用户消息里面// 如果返回类型为String,AiMessage,TokenStream,Response则不追加格式提示词// 如果返回类型为void则报错// 如果返回类型为enum枚举类型,则追加提示词“\nYou must answer strictly in the following format: one of value1,value2,value3...,valueN”// 如果返回类型是 boolean/byte/short/int/long/BigInteger/float/double/BigDecimal/Date/LocalDate/LocalTime/LocalDateTime 或其对应包装类型,则追加对应值类型提示词,例如“\nYou must answer strictly in the following format: one of [true, false]” ,“...format: integer number in range [-128, 127]”// 如果返回类型是List/Set,则追加提示词“You must put every item on a separate line.”// 否则追加提示词,以json形式返回 “You must answer strictly in the following JSON format: {...}”String outputFormatInstructions = outputFormatInstructions(method.getReturnType());userMessage = UserMessage.from(userMessage.text() + outputFormatInstructions);// 如果包含聊天记忆,则在聊天记忆中追加系统消息和用户消息if (context.hasChatMemory()) {ChatMemory chatMemory = context.chatMemory(memoryId);systemMessage.ifPresent(chatMemory::add);chatMemory.add(userMessage);}// 从记忆中获取消息清单或构建新的消息清单List<ChatMessage> messages;if (context.hasChatMemory()) {messages = context.chatMemory(memoryId).messages();} else {messages = new ArrayList<>();systemMessage.ifPresent(messages::add);messages.add(userMessage);}// 执行审查Future<Moderation> moderationFuture = triggerModerationIfNeeded(method, messages);// 以流式处理消息if (method.getReturnType() == TokenStream.class) {return new AiServiceTokenStream(messages, context, memoryId); // 尚未实现响应内容审查,也不支持工具调用}// 调用chatModel生成响应Response<AiMessage> response = context.toolSpecifications == null? context.chatModel.generate(messages): context.chatModel.generate(messages, context.toolSpecifications);// 获取token用量TokenUsage tokenUsageAccumulator = response.tokenUsage();// 校验审查结果verifyModerationIfNeeded(moderationFuture);// 执行工具调用// 工具调用的最大执行次数(10)int executionsLeft = MAX_SEQUENTIAL_TOOL_EXECUTIONS;while (true) {if (executionsLeft-- == 0) {throw runtime("Something is wrong, exceeded %s sequential tool executions",MAX_SEQUENTIAL_TOOL_EXECUTIONS);}// 获取AI响应消息,添加到记忆中AiMessage aiMessage = response.content();if (context.hasChatMemory()) {context.chatMemory(memoryId).add(aiMessage);}// 如果不存在工具调用请求则中断if (!aiMessage.hasToolExecutionRequests()) {break;}// 根据工具调用请求,依次调用工具,并将工具执行结果消息添加到记忆中ChatMemory chatMemory = context.chatMemory(memoryId);							for (ToolExecutionRequest toolExecutionRequest : aiMessage.toolExecutionRequests()) {ToolExecutor toolExecutor = context.toolExecutors.get(toolExecutionRequest.name());String toolExecutionResult = toolExecutor.execute(toolExecutionRequest, memoryId);ToolExecutionResultMessage toolExecutionResultMessage = ToolExecutionResultMessage.from(toolExecutionRequest,toolExecutionResult);chatMemory.add(toolExecutionResultMessage);}// 根据添加了工具执行结果的记忆再次调用模型生成response = context.chatModel.generate(chatMemory.messages(), context.toolSpecifications);// 累计token用量tokenUsageAccumulator = tokenUsageAccumulator.add(response.tokenUsage());}// 返回最终的响应response = Response.from(response.content(), tokenUsageAccumulator, response.finishReason());// 将响应解析为方法对应的返回类型对象return parse(response, method.getReturnType());}private Future<Moderation> triggerModerationIfNeeded(Method method, List<ChatMessage> messages) {if (method.isAnnotationPresent(Moderate.class)) {return executor.submit(() -> {List<ChatMessage> messagesToModerate = removeToolMessages(messages);return context.moderationModel.moderate(messagesToModerate).content();});}return null;}});return (T) proxyInstance;}// 准备系统消息private Optional<SystemMessage> prepareSystemMessage(Method method, Object[] args) {// 获取提示词模板变量Parameter[] parameters = method.getParameters();Map<String, Object> variables = getPromptTemplateVariables(args, parameters);dev.langchain4j.service.SystemMessage annotation = method.getAnnotation(dev.langchain4j.service.SystemMessage.class);if (annotation != null) {// 获取 SystemMessage 注解的系统消息提示词模板String systemMessageTemplate = getPromptText(method,"System",annotation.fromResource(), // 提示词资源文件,如果没有则取value值annotation.value(), // 提示词文本annotation.delimiter() // 换行符);// 根据模板和变量获取提示词Prompt prompt = PromptTemplate.from(systemMessageTemplate).apply(variables);return Optional.of(prompt.toSystemMessage());}return Optional.empty();}// 准备用户消息private static UserMessage prepareUserMessage(Method method, Object[] args) {Parameter[] parameters = method.getParameters();Map<String, Object> variables = getPromptTemplateVariables(args, parameters);// 获取用户名参数String userName = getUserName(parameters, args);dev.langchain4j.service.UserMessage annotation = method.getAnnotation(dev.langchain4j.service.UserMessage.class);if (annotation != null) {String userMessageTemplate = getPromptText(method,"User",annotation.fromResource(),annotation.value(),annotation.delimiter());// 如果模板中使用了{{it}}占位符,则只允许使用一个模板参数if (userMessageTemplate.contains("{{it}}")) {if (parameters.length != 1) {throw illegalConfiguration("Error: The {{it}} placeholder is present but the method does not have exactly one parameter. " +"Please ensure that methods using the {{it}} placeholder have exactly one parameter.");}variables = singletonMap("it", toString(args[0]));}Prompt prompt = PromptTemplate.from(userMessageTemplate).apply(variables);if (userName != null) {// 使用用户名构造用户消息return userMessage(userName, prompt.text());} else {return prompt.toUserMessage();}}// 方法如果没有UserMessage注解,查找使用UserMessage注解的参数,作为消息内容for (int i = 0; i < parameters.length; i++) {if (parameters[i].isAnnotationPresent(dev.langchain4j.service.UserMessage.class)) {String text = toString(args[i]);if (userName != null) {return userMessage(userName, text);} else {return userMessage(text);}}}// 如果完全没有参数则报错if (args == null || args.length == 0) {throw illegalConfiguration("Method should have at least one argument");}// 如果只有一个没有注解的参数,则作为消息内容if (args.length == 1) {String text = toString(args[0]);if (userName != null) {return userMessage(userName, text);} else {return userMessage(text);}}throw illegalConfiguration("For methods with multiple parameters, each parameter must be annotated with @V, @UserMessage, @UserName or @MemoryId");}// 根据方法提示词注解获取提示词文本// resource 提示词资源文件,如果没有则取value值// value 提示词文本// delimiter 分隔符(换行符)private static String getPromptText(Method method, String type, String resource, String[] value, String delimiter) {String messageTemplate;if (!resource.trim().isEmpty()) {messageTemplate = getResourceText(method.getDeclaringClass(), resource);if (messageTemplate == null) {throw illegalConfiguration("@%sMessage's resource '%s' not found", type, resource);}} else {messageTemplate = String.join(delimiter, value);}if (messageTemplate.trim().isEmpty()) {throw illegalConfiguration("@%sMessage's template cannot be empty", type);}return messageTemplate;}private static String getResourceText(Class<?> clazz, String name) {return getText(clazz.getResourceAsStream(name));}private static String getText(InputStream inputStream) {if (inputStream == null) {return null;}try (Scanner scanner = new Scanner(inputStream);Scanner s = scanner.useDelimiter("\\A")) {return s.hasNext() ? s.next() : "";}}private Optional<Object> memoryId(Method method, Object[] args) {Parameter[] parameters = method.getParameters();for (int i = 0; i < parameters.length; i++) {if (parameters[i].isAnnotationPresent(MemoryId.class)) {Object memoryId = args[i];if (memoryId == null) {throw illegalArgument("The value of parameter %s annotated with @MemoryId in method %s must not be null",parameters[i].getName(), method.getName());}return Optional.of(memoryId);}}return Optional.empty();}// 获取用户名参数private static String getUserName(Parameter[] parameters, Object[] args) {for (int i = 0; i < parameters.length; i++) {if (parameters[i].isAnnotationPresent(UserName.class)) {return args[i].toString();}}return null;}// 获取提示词模板变量// 遍历V注解的变量,返回变量名和变量值映射private static Map<String, Object> getPromptTemplateVariables(Object[] args, Parameter[] parameters) {Map<String, Object> variables = new HashMap<>();for (int i = 0; i < parameters.length; i++) {V varAnnotation = parameters[i].getAnnotation(V.class);if (varAnnotation != null) {String variableName = varAnnotation.value();Object variableValue = args[i];variables.put(variableName, variableValue);}}return variables;}private static String toString(Object arg) {if (arg.getClass().isArray()) {return arrayToString(arg);} else if (arg.getClass().isAnnotationPresent(StructuredPrompt.class)) {return StructuredPromptProcessor.toPrompt(arg).text();} else {return arg.toString();}}private static String arrayToString(Object arg) {StringBuilder sb = new StringBuilder("[");int length = Array.getLength(arg);for (int i = 0; i < length; i++) {sb.append(toString(Array.get(arg, i)));if (i < length - 1) {sb.append(", ");}}sb.append("]");return sb.toString();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/765882.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

5G智能网关助力工业铸造设备监测升级

随着物联网技术的迅猛发展和工业4.0浪潮的推进&#xff0c;传统工业正面临着严峻的转型升级压力。在这一背景下&#xff0c;铸造行业——这一典型的传统重工业领域&#xff0c;也必须积极探索借助5G、物联网、边缘计算等技术提升生产经营效率的新路径。 本文就基于佰马合作伙伴…

【技巧】ChatGPT Prompt 提示语大全

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 主要来自&#xff1a;https://github.com/f/awesome-chatgpt-prompts ChatGPT SEO提示 Contributed by: StoryChief AI Reference: 7 Powerful ChatGPT Prompts to Create SEO Content Faster 供稿人&#xff1a;…

MyBatis Plus笔记

1、删除 物理删除&#xff1a;从硬盘上直接删除掉 。好处&#xff1a;数据条数少了&#xff0c;不好的地方在于可能会影响到基于这条数据产生的记录 逻辑删除&#xff1a;假删除 两个区别&#xff1a; 删除时 之前&#xff1a;DELETE from sys_user …

链表oj测试题(上)

链表的申明&#xff1a; struct ListNode {int val;struct ListNode* next; }; 1.题1 删除指定元素 例如&#xff1a;链表1 2 6 3 4 5 6&#xff0c;然后选择删除元素6&#xff0c;返回的链表为1 2 3 4 5 。 代码演示&#xff1a; typedef struct ListNode ListNode;List…

Spark与flink计算引擎工作原理

Spark是大批量分布式计算引擎框架&#xff0c;scale语言开发的&#xff0c;核心技术是弹性分布式数据集&#xff08;RDD&#xff09;可以快速在内存中对数据集进行多次迭代&#xff0c;支持复杂的数据挖掘算法及图形计算算法&#xff0c;spark与Hadoop区别主要是spark多个作业之…

什么是行业垂直类媒体?有哪些?怎么邀约

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体胡老师。 行业垂直类媒体是聚焦于特定行业或领域的媒体平台。 行业垂直类媒体不同于主流媒体&#xff0c;它们专注于提供与某个特定领域相关的深入内容和服务&#xff0c;例如商业新闻、旅游、数字…

能拍英语的搜题软件?九个免费好用的大学生搜题工具 #经验分享#知识分享#其他

积极参加社团活动和实践项目&#xff0c;可以帮助大学生拓宽人脉圈和锻炼实际操作能力。 1.粉鹿搜题 这是一个公众号 搜题拥有非常强大的题库&#xff0c;包含IT认证、建筑工程:、会计资格、教师资格、研究生、公务员等类型的题目。 下方附上一些测试的试题及答案 1、BPR基…

面试算法-79-搜索旋转排序数组

题目 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nums[1…

Qt 利用共享内存实现一次只能启动一个程序(单实例运行)

Qt 利用共享内存实现一次只能启动一个程序 文章目录 Qt 利用共享内存实现一次只能启动一个程序摘要利用共享内存实现一次只能启动一个程序示例代码 关键字&#xff1a; Qt、 unique、 单一、 QSharedMemory、 共享内存 摘要 今天接着在公司搞我的屎山代码&#xff0c;按照…

[MAUI]集成高德地图组件至.NET MAUI Blazor项目

文章目录 前期准备&#xff1a;注册高德开发者并创建 key登录控制台创建 key获取 key 和密钥 创建项目创建JS API Loader配置权限创建定义创建模型创建地图组件创建交互逻辑 项目地址 地图组件在手机App中常用地理相关业务&#xff0c;如查看线下门店&#xff0c;设置导航&…

LeetCode 热题 100 | 堆(二)

目录 1 什么是优先队列 1.1 优先队列与堆的关系 1.2 如何定义优先队列 1.3 如何使用优先队列 1.4 如何设置排序规则 2 347. 前 K 个高频元素 2.1 第 2 步的具体实现 2.2 举例说明 2.3 完整代码 3 215. 数组中的第 K 个最大元素 - v2 菜鸟做题&#xff0c;语…

Shell学习

一、 变量 shell是弱类型语言&#xff0c;不用定义数据类型&#xff0c;默认都是字符串。 变量与值之间不得有空格 只能包含数字、字母、下划线 不能以数字开头 区分大小写 根据变量的作用域可以将shell变量分为&#xff1a;全局变量、局部变量、环境变量。全局变量通常和…

app自动化测试怎么学?

app测试的主要内容有那些 1、功能测试 : 查看功能是否正常&#xff0c;主要针对每一个功能点进行一一测试&#xff0c;主要核心就是把验证的每个测试点都满足需求的对应功能&#xff0c;验证标准就是让预期结果和实际结果保持一致。 2、安装卸载测试&#xff1a;首先要测试的…

【Linux】从零认识进程 — 中下篇

送给大家一句话&#xff1a; 人一切的痛苦&#xff0c;本质上都是对自己无能的愤怒。而自律&#xff0c;恰恰是解决人生痛苦的根本途径。—— 王小波 从零认识进程 1 进程优先级1.1 什么是优先级1.2 为什么要有优先级1.3 Linux优先级的特点 && 查看方式1.4 其他概念 2…

深度解析深度学习中的长短期记忆网络(LSTM)(含代码实现)

在深度学习中&#xff0c;长短期记忆网络&#xff08;LSTM&#xff09;是一种强大的循环神经网络结构&#xff0c;能够更好地处理长序列数据并减轻梯度消失的问题。本文将介绍LSTM的工作原理&#xff0c;并使用PyTorch实现一个简单的LSTM模型来展示其在自然语言处理中的应用。 …

MongoDB完全开发手册(一篇学会MongoDB所有知识点)

目录 一、MongoDB 基础 1.1 、MongoDB 是什么&#xff1f; 1.2、 MongoDB 的存储结构是什么&#xff1f; 1.3 、文档 1.4 、集合 1.5 、数据库 1.6、 MongoDB 有什么特点&#xff1f; 1.7、 MongoDB 适合什么应用场景&#xff1f; 二、MongoDB 存储引擎 2.1 、MongoDB…

Autosar Crypto Interface学习笔记

文章目录 前言Functional specificationError classificationError detection API specificationType DefinitionsFunction definitionsGeneral APICryIf_InitCryIf_GetVersionInfo Job Processing InterfaceCryIf_ProcessJobDispatch Key IDs匹配KeyId Job Cancellation Inter…

【嵌入式——QT】Charts常见的图表的绘制

【嵌入式——QT】Charts常见的图表的绘制 柱状图QBarSetQBarSeriesQBarCategoryAxis图示 饼图堆叠柱状图百分比柱状图散点图和光滑曲线图代码示例 柱状图 QBarSet 用于创建柱状图的数据集。 主要函数 setLabel()&#xff1a;设置数据集标签 &#xff1b;setLabelBrush()&am…

租用阿里云2核2G服务器配置报价,61元和99元

阿里云2核2G服务器配置优惠价格61元和99元&#xff0c;61元是轻量应用服务器2核2G3M带宽、50G高效云盘&#xff0c;99元服务器是ECS云服务器经济型e实例2核2G、3M固定带宽、40G ESSD entry 系统盘。活动 aliyunfuwuqi.com/go/aliyun 阿里云服务器网aliyunfuwuqi.com根据上面的官…

​ YOLOv9改进策略:SPPELAN优化 | 新一代高效可形变卷积DCNv4如何做二次创新?高效结合SPPELAN| CVPR2024

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文改进内容&#xff1a; DCNv4来自CVPR2024 的论文&#xff0c;它不仅收敛速度明显快于DCNv3&#xff0c;而且正向速度提高了3倍以上。这一改进使DCNv4能够充分利用其稀疏特性&#xff0c;成为最快的通用核心视觉算子之一。 |新一代…