基于AiService实现智能文章小助手

news2024/11/16 0:52:16

顾名思义,这个应用就是希望能利用大模型的能力来帮助我写文章,那这样一个应用该如何利用LangChain4j来实现呢?接下来我们来利用AiService进行实现。

AiService代理

首先,我们定义一个接口Writer,表示作家:

interface Writer {
	String write();
}

然后利用定义AiService来创建一个Writer代理对象:

package com.timi;

import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;

public class _02_AiService {

    interface Writer {
        String write(String title);
    }

    public static void main(String[] args) {

        ChatLanguageModel model = OpenAiChatModel.builder()
                .baseUrl("http://langchain4j.dev/demo/openai/v1")
                .apiKey("demo")
                .build();

        Writer writer = AiServices.create(Writer.class, model);
    }
}

创建代理对象时,我们传入一个提前定义了的ChatLanguageModel,拿到Writer对象后,就可以调用write()方法来写文章了:

String content = writer.write("我最爱的人");
System.out.println(content);

执行代码结果为:

是我的家人,他们是我生命中最重要的人,无论发生什么事情,他们都会一直支持和爱护着我。他们给予我无限的爱和关怀,让我感到无比幸福和幸运。我愿意为他们奉献一切,尽我所能地去照顾和呵护他们。他们是我生命中最珍贵的存在,我永远都会珍惜和爱护他们。

以上例子可以看出AiServices的第一个作用:能够生成特定接口的代理对象,从而使得在调用代理对象方法时,能间接的调用大模型。这种代理模式的实现在Java各种框架中是非常常见的,这样就使得Writer接口或Writer对象具有了大模型的智能能力。

只不过上面的答案并不是一篇作文,大模型似乎是在回答“它最爱的人是谁?”这个问题,而不是在写文章,也就是大模型并不知道它需要扮演为一名作家,因此,我们需要告诉大模型让它先扮演一名作家,然后再回答我的问题,这就需要用到上一节提到的SystemMessage,只不过我们这里用的是@SystemMessage注解。

@SystemMessage

我们只需要在write()方法上定义@SystemMessage,并描述系统提示词,如下:

interface Writer {
	@SystemMessage("请扮演一名作家,根据输入的文章题目写一篇200字以内的作文")
	String write(String title);
}

那么当我们调用write()方法时,LangChain4j就会自动组合SystemMessage和用户输入的标题,然后发送给大模型,这样大模型就知道自己是一名作家了。

比如运行以上代码的结果就变为了:

在我生命中,最爱的人是我母亲。她是我生命中最重要的人,也是我永远的依靠和支持。母亲那温暖的微笑,总是能给我无限的力量和勇气。

我记得小时候,母亲总是在我生病时守在我身边,用温柔的手轻轻拍着我的背,给我端来热腾腾的粥汤。她总是用她的爱和关心包裹着我,让我感受到无比的安全和幸福。

母亲是一个坚强而又温柔的女人,她用她的辛勤劳动支撑起这个家庭,用她的慈爱呵护着我们。我常常想,如果没有母亲,我将会是一个怎样的人呢?母亲是我生命中的灯塔,指引着我前行的方向。

无论我遇到什么困难和挑战,母亲总是在我身边默默支持着我。她的爱如同一股暖流,贯穿着我的整个生命。我爱你,母亲,永远都爱你。

写得比我好多了,并且现在Writer接口就是一名作家了,当然我们可以把创建ChatLanguageModel、代理对象的操作都封装到Writer接口中,比如:

package com.timi;

import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.SystemMessage;

public class _02_AiService {

    interface Writer {

        @SystemMessage("请扮演一名作家,根据输入的文章题目写一篇200字以内的作文")
        String write(String title);

        static Writer create(){
            ChatLanguageModel model = OpenAiChatModel.builder()
                    .baseUrl("http://langchain4j.dev/demo/openai/v1")
                    .apiKey("demo")
                    .build();

            return AiServices.create(Writer.class, model);
        }
        
    }

    public static void main(String[] args) {
        Writer writer = Writer.create();
        String content = writer.write("我最爱的人");
        System.out.println(content);
    }
}

这样,我们只需要调用Writer.create()就能得到一名作家了,如果你用SpringBoot,那么就可以把创建出来的Writer代理对象注册为一个Bean,在其他Controller、Service中任意使用了,你甚至可以基于同样的思路定义更多的角色扮演,比如算命大师、取名大师、冷笑话大师等等。

源码分析

这一节中主要逻辑为:

  1. 代理对象的创建流程
  2. 代理对象的方法执行流程

代理对象的创建流程

创建代理对象是通过AiServices.create(Writer.class, model)进行的,由于AiServices是一个抽象类,源码中有一个默认的子类DefaultAiServices,核心实现源码都在DefaultAiServices中。

DefaultAiServices的build方法就是用来创建指定接口的代理对象:

public T build() {

	// 验证是否配置了ChatLanguageModel
	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.");
		}
	}

	// JDK动态代理创建代理对象
	Object proxyInstance = Proxy.newProxyInstance(
		context.aiServiceClass.getClassLoader(),
		new Class<?>[]{context.aiServiceClass},
		new InvocationHandler() {

			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
				// ...
			}

		});

	return (T) proxyInstance;
}

可以发现,其实就是用的JDK动态代理机制创建的代理对象,只不过在创建代理对象之前有两步验证:

  1. 验证是否配置了ChatLanguageModel:这一步不难理解,如果代理对象没有配置ChatLanguageModel,那就利用不上大模型的能力了
  2. 验证接口中是否有方法上加了@Moderate,但是又没有配置ModerationModel

@Moderate和ModerationModel

Moderate是温和的意思,这是一种安全机制,比如:

@SystemMessage("请扮演一名作家,根据输入的文章题目写一篇200字以内的作文")
@Moderate
String write(String title);

我们在write()方法上加了@Moderate注解,那么当调用write()方法时,会调用两次大模型:

  1. 首先是配置的ModerationModel,如果没有配置则创建代理对象都不会成功,ModerationModel会对方法的输入进行审核,看是否涉及敏感、不安全的内容。
  2. 然后才是配置的ChatLanguageModel

配置ModerationModel的方式如下:

interface Writer {

	@SystemMessage("请扮演一名作家,根据输入的文章题目写一篇200字以内的作文")
	@Moderate
	String write(String title);

	static Writer create() {
		ChatLanguageModel model = OpenAiChatModel.builder()
			.baseUrl("http://langchain4j.dev/demo/openai/v1")
			.apiKey("demo")
			.build();

		ModerationModel moderationModel = OpenAiModerationModel.builder()
			.baseUrl("http://langchain4j.dev/demo/openai/v1")
			.apiKey("demo")
			.build();

		return AiServices.builder(Writer.class)
			.chatLanguageModel(model)
			.moderationModel(moderationModel)
			.build();
	}

}

虽然ChatLanguageModel和ModerationModel都是OpenAi,但是你可以理解为OpenAiModerationModel在安全方面更近专业。

代理对象的方法执行流程

代理对象创建出来之后,就可以指定代理对象的方法了,而一旦执行代理对象的方法就是进入到上述源码中InvocationHandler的invoke()方法,而这个invoke()方法是LangChain4j中的最为重要的,里面涉及的组件、功能是非常多的,而本节我们只关心是怎么解析@SystemMessage得到系统提示词,然后组合用户输入的标题,最后发送给大模型得到响应结果的。

在invoke()方法的源码中有这么两行代码:

Optional<SystemMessage> systemMessage = prepareSystemMessage(method, args);
UserMessage userMessage = prepareUserMessage(method, args);

分别调用了prepareSystemMessage()和prepareUserMessage()两个方法,而入参都是代理对象当前正在执行的方法和参数。

在看prepareSystemMessage()方法之前,我们需要再了解一个跟@SystemMessage有关的功能,前面我们是这么定义SystemMessage的:

@SystemMessage("请扮演一名作家,根据输入的文章题目写一篇200字以内的作文")

其中200是固定的,但是作为一名作家不可能永远只能写200字以内的作文,而这个字数应该都用户来指定,也就是说200应该得是个变量,那么我们可以这么做:

@SystemMessage("请扮演一名作家,根据输入的文章题目写一篇{{num}}字以内的作文")
String write(@UserMessage String title, @V("num") int num);

其中{num}就是变量,该变量的值由用户在调用write方法时指定,注意由于write()有两个参数了,需要在title参数前面定义@UserMessage,表示title是用户消息。

这样我们就可以让Write写一篇任意字数以内的文章了:

String content = writer.write("我最爱的人", 300);

知道了这个场景,我们再来看prepareSystemMessage()方法的实现:

private Optional<SystemMessage> prepareSystemMessage(Method method, Object[] args) {

	// 得到当前正在执行的方法参数
	Parameter[] parameters = method.getParameters();

	// 解析方法参数前定义的@V注解,@V的value为Map的key,对应的参数值为Map的value
	Map<String, Object> variables = getPromptTemplateVariables(args, parameters);

	// 解析方法上的@SystemMessage注解
	dev.langchain4j.service.SystemMessage annotation = method.getAnnotation(dev.langchain4j.service.SystemMessage.class);
	if (annotation != null) {

		// 拼接多个SystemMessage注解
		String systemMessageTemplate = String.join(annotation.delimiter(), annotation.value());
		if (systemMessageTemplate.isEmpty()) {
			throw illegalConfiguration("@SystemMessage's template cannot be empty");
		}

		// 填充变量
		Prompt prompt = PromptTemplate.from(systemMessageTemplate).apply(variables);
		
		// 返回最终的SystemMessage对象
		return Optional.of(prompt.toSystemMessage());
	}

	return Optional.empty();
}

从源码看出@SystemMessage注解的value属性是一个String[]:

@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface SystemMessage {
	
    String[] value();

    String delimiter() default "\n";
}

表示如果系统提示词比较长,可以写成多个String,不过最后会使用delimiter的值将这多个String拼接为一个SystemMessage,并且在拼接完以后会根据@V的值填充SystemMessage中的变量,从而得到最终的SystemMessage。

再来看prepareUserMessage()方法,本节我们只关心:

// 如果有多个参数,获取加了@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);
		}
	}
}

// 如果只有一个参数,则直接使用该参数值作为UserMessage
if (args.length == 1) {
	String text = toString(args[0]);
	if (userName != null) {
		return userMessage(userName, text);
	} else {
		return userMessage(text);
	}
}

还是比较简单的:

  1. 如果有多个参数,获取加了@UserMessage注解参数的值作为UserMessage
  2. 如果只有一个参数,则直接使用该参数值作为UserMessage

这样就得到了最终的SystemMessage和UserMessage,那么如何将他们组装在一起呢?

还记得上一节提到的历史对话吗?请看代码:

List<ChatMessage> messages;
if (context.hasChatMemory()) {
	messages = context.chatMemory(memoryId).messages();
} else {
	
	messages = new ArrayList<>();
	
	// 添加SystemMessage
	systemMessage.ifPresent(messages::add);

	// 添加UserMessage
	messages.add(userMessage);
}

我们还没有设置ChatMemory,所以组装的逻辑其实就是按顺序将SystemMessage和UserMessage添加到一个List中,后续只要将这个List传入给ChatLanguageModel的generate()方法就可以了。

那么ChatLanguageModel的generate()方法是如何处理List的呢?会拼接为一个字符串吗?比如:“请扮演一名作家,根据输入的文章题目写一篇300字以内的作文,我最爱的人”,并不会,我们看看OpenAiChatModel的实现:

public static List<Message> toOpenAiMessages(List<ChatMessage> messages) {
	return messages.stream()
		.map(InternalOpenAiHelper::toOpenAiMessage)
		.collect(toList());
}

在正式调用OpenAi的接口之前,OpenAiChatModel会利用toOpenAiMessages来处理List,不过注意它的返回仍然是一个List,只不过变成了List,ChatMessage是LangChain4j定义的,Message是OpenAi定义的,实际上它们并没有太多的区别,我们看下转换之后的List:
image.png
content确实没有区别,多了role属性,意义其实是一样的,SYSTEM表示系统提示词,USER表示用户提示词,那为什么这样可以呢?是因为OpenAi提供的接口本来就支持通过这种方式来设置系统提示词,比如:
image.png
大家可以访问openai-api-reference详细了解,同时大家也要注意到OpenAi的接口还支持Assistant message和Tool message两种类型,这两种类型是跟工具机制有关系的,我们后续会进行分析。

本节总结

本节我们学习了什么是AiService以及基本应用,我们制作了一个用户可以指定字数和标题的作家应用,同时我们还研究了AiService的基本工作原理和源码,其中我们再次提到了ChatMemory,那么下节内容我们就来介绍到底什么是ChatMemory。

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

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

相关文章

002 使用kibana操作ElasticSearch7.x

文章目录 4.使用kibana操作es4.1.文档操作1.put方式发送数据2.post方式发送数据3.查看索引文档 GET4.更新文档 POST5.删除文档&索引 DELETE6.批量添加数据_bulk 4.2.Query DLS(查询领域对象语言)1.url 检索数据语法2.查询所有数据3.查询全部数据并排序4.查询全部数据排序并…

面试-java异常体系

1.java异常体系 error类是指与jvm相关的问题。如系统崩溃&#xff0c;虚拟机错误&#xff0c;内存空间不足。 非runtime异常不处理&#xff0c;程序就没有办法执行。 一旦遇到异常抛出&#xff0c;后面的异常就不会进行。 (1)常见的error以及exception 2.java异常要点分析…

安全感爆棚,锁定六氟化硫SF6气体泄漏报警监测系统

一、概述 六氟化硫SF6气体已有百年历史&#xff0c;它是法国两位化学家Moissan和Lebeau于1900年合成的人造惰性气体&#xff0c; 1947年提供商用。当前SF6气体主要用于电力工业中。化学性质稳定。微溶于水、醇及醚&#xff0c;可溶于氢氧化钾。不与氢氧化钠、液氨、盐酸及水起…

【Knowledge Graph Context-Enhanced Diversified Recommendation(MSDM2024)】

Knowledge Graph Context-Enhanced Diversified Recommendation 摘要 推荐系统&#xff08;RecSys&#xff09;领域已被广泛研究&#xff0c;以通过利用用户的历史交互来提高准确性。 尽管如此&#xff0c;这种对准确性的持续追求常常导致多样性的减少&#xff0c;最终导致众所…

阿里Qwen-2成全球开源大模型排行榜第一,中国处于领导地位。

6月27日凌晨&#xff0c;全球著名开源平台huggingface&#xff08;笑脸&#xff09;的联合创始人兼首席执行官Clem在社交平台宣布&#xff0c;阿里最新开源的Qwen2-72B指令微调版本&#xff0c;成为开源模型排行榜第一名。 他表示&#xff0c;为了提供全新的开源大模型排行榜…

KubeCon 香港:移动云与云猿生联合议题《在没有专用 Operator 的情况下管理数据库集群》

KubeCon CloudNativeCon 开源峰会 AI_dev 中国大会将于 2024 年 8 月 21 日至 23 日在香港举行。来自全球的云原生技术专家与爱好者在这里相会&#xff0c;探讨云原生领域的技术创新与最佳实践。此外&#xff0c;本次 KubeCon CloudNativeCon 和开源峰会将与 AI_dev&#x…

使用ESP32开发一款chat机器人

目的&#xff1a;使用语音对话的方式实现和ai机器人对话&#xff0c;核心硬件如下 主板&#xff1a; ESP32S3 语音&#xff08;拾音器-麦克风&#xff09;&#xff1a;INMP441全向麦克风模块 购买记录&#xff1a; https://oshwhub.com/shukkkk/esp32s3_tft_mp3

隧道管廊人员定位系统的应用与发展

随着城市建设的不断发展&#xff0c;地下管廊和隧道工程的规模和数量也在快速增长。隧道工程处于复杂、封闭的环境中&#xff0c;人员的安全管理成为一项重要任务。隧道管廊人员定位系统作为一种先进的技术手段&#xff0c;可以实时追踪人员位置&#xff0c;提供实时监控和安全…

clip系列改进Lseg、 group ViT、ViLD、Glip

Lseg 在clip后面加一个分割head&#xff0c;然后用分割数据集有监督训练。textencoder使用clip&#xff0c;frozen住。 group ViT 与Lseg不同&#xff0c;借鉴了clip做了真正的无监督学习。 具体的通过group block来做的。使用学习的N个group token&#xff08;可以理解为聚类…

数字社交的领航者:解析Facebook的引领作用

在当今数字化社会中&#xff0c;社交网络已经成为了人们日常生活不可或缺的一部分。而在众多社交平台中&#xff0c;Facebook凭借其巨大的用户基础和创新的技术应用&#xff0c;被公认为数字社交领域的领航者之一。本文将深入解析Facebook在数字社交中的引领作用&#xff0c;探…

Eclipse代码编辑器自主配色

1. 打开 Eclipse 的设置 - Java - Editor - Syntax Coloring 2. 自定义各种类型的颜色&#xff0c;例如&#xff1a; 1. Interface 勾选&#xff0c;设置为紫色 2. Class 勾选&#xff0c;设置为淡蓝色 3. Abstract classes 勾选&#xff0c;有自己默认的颜色 …

Web渗透:文件包含漏洞(part.1)

"文件包含漏洞"&#xff08;File Inclusion Vulnerability&#xff09;是一种常见的Web应用程序漏洞&#xff0c;攻击者可以通过这个漏洞在目标系统上包含或执行任意文件。主要有两种类型的文件包含漏洞&#xff1a; 本地文件包含&#xff08;Local File Inclusion, …

Django(根据Models中模型类反向生成数据库表)—— python篇

一、数据库的配置 1、 django默认支持 sqlite&#xff0c;mysql, oracle,postgresql数据库。 sqlite&#xff1a;django默认使用sqlite的数据库&#xff0c;默认自带sqlite的数据库驱动 , 引擎名称&#xff1a;django.db.backends.sqlite3 mysql&#xff1a;引擎名称&#xff…

克服指标管理痛点,实现数据价值最大化

在当下的企业管理中&#xff0c;由于数据量的激增&#xff0c;管理方式逐渐从基于经验转向基于数据。在此过程中&#xff0c;我们能够通过数据探查业务情况、分析数据&#xff0c;从而获取更优的决策支持数据。这通常通过数据报表或分析平台来实现&#xff0c;对于临时性场景&a…

基于YOLOv10的车辆统计跟踪与车速计算应用

文章目录 1、前言2、安装运行环境3、下载v10s模型4、代码实现5、代码详读5.1、导入必要的库5.2、识别车辆5.3、读取视频文件5.4、创建视频写入器5.5、车速计算5.6、统计车辆5.7、应用跟踪5.8、视频处理 6、目标检测系列文章 1、前言 在智能交通系统&#xff08;ITS&#xff09…

Does a vector database maintain pre-vector chunked data for RAG systems?

题意&#xff1a;一个向量数据库是否为RAG系统维护预向量化分块数据&#xff1f; 问题背景&#xff1a; I believe that when using an LLM with a Retrieval-Augmented Generation (RAG) approach, the results retrieved from a vector search must ultimately be presented…

提升用户转化率秘诀!Xinstall的H5拉起应用技术让您领先一步!

在移动互联网时代&#xff0c;App的推广和运营面临着诸多挑战。其中&#xff0c;H5页面如何高效、便捷地拉起应用&#xff0c;成为了一个亟待解决的问题。今天&#xff0c;我们就来谈谈如何利用Xinstall品牌&#xff0c;轻松解决这一痛点&#xff0c;提升用户体验&#xff0c;助…

ONLYOFFICE桌面编辑器8.1:办公体验新升级

引入 当今时代办公三件套已经成为我们日常生活中不可或缺的一部分了&#xff0c;但是说到办公软件不知道大家会首先想到那些产品 office 亦或是 WPS。最近也发布了 8.1 新版本&#xff0c;更新了一些新功能今天我们就来评测评测。 文章目录 引入一、ONLYOFFICE 是什么&#xff…

java基于ssm+jsp 仓库智能仓储系统

1管理员功能模块 管理员登录&#xff0c;通过填写用户名、密码等信息&#xff0c;输入完成后选择登录即可进入智能仓储系统 &#xff0c;如图1所示。 图1管理员登录界面图 智能仓储系统 &#xff0c;在智能仓储系统可以查看个人中心、公告信息管理、员工管理、供应商管理、商…

大模型技术的应用场景

大模型技术&#xff08;Large Language Model&#xff0c;LLM&#xff09;是指具有大量参数和训练数据的神经网络模型&#xff0c;它能够学习语言的统计规律&#xff0c;并生成与人类书写的文本相似的文本。大模型技术在近年来取得了重大进展&#xff0c;并开始在各种领域得到应…