前面的内容我们使用的是底层组件,如:ChatLanguageModel、ChatMessage、ChatMemory等等,它们是低级别的API,虽然使用起来灵活,但是需要我们编写大样的样板代码。
然而由LLM驱动的应用不仅仅需要一个组件,而是需要多个组件一起工作(如:提示模板、聊天记忆、RAG、嵌入模型的存储...),它们之间还是相互交互的,这样的话编排他们是非常麻烦的。
通常我们更应该关注的是业务逻辑,对于底层的细节不要太过于关注。在LangChain4j中的两个概念可以帮我们实现:Chains和AiService
Chains
链这个概念起源于Python和LangChain,它的想法是为每个常见的用例创建一个Chain。链把多个低级组件组合在一起,并编排它们之间的交互。它们有一个缺点就是太过于死板了。
对于LangChain4j只实现了两个链(ConversationalChain和CoversationalRetrievalChain)未来也基本上不会考虑再新增了
AiService
AiService是专为Java量身定制的。其想法是为了隐藏LLM和其他组件交互的复杂性,把它们隐藏在一个简单的API后面。
我们需要做的就是声明式地定义一个接口,LangChain4j提供了实现这个接口的对象(代理)。可以把AiService视作为应用程序中服务层的一个组件。
AiService处理最常见的操作
- 格式化LLM的输入
- 解析从LLM得到输出
AiService支持的高级特性:
- Chat memory
- Tools
- RAG
最简单的AiService
先定义一个带chat方法的接口,这个chat方法接收一个String作为输入,并且返回一个String。
public interface Assistant {
String chat(String userMessage);
}
注意:这里的方法名称是自定义的,不一定非叫chat!!
我们定义好这个接口后,我们要能创建出它的一个实例,我们是与SpringBoot集成的,所以我们可以选择在配置类创建对应的Bean供后续的使用。
我们创建一个config包,并在其下创建配置类:AiServiceConfig
@Configuration
public class AiServiceConfig {
@Resource
private ChatLanguageModel chatLanguageModel;
@Bean
public Assistant assistant() {
return AiServices.create(Assistant.class, chatLanguageModel);
}
}
这里创建Bean的核心就是AiServices.create(Assistant.class, ChatLanguageModel);
AiServices是一个抽象类,其中有多个create方法,上面的这个create方法表示为这个AiService当中传入ChatLanguageModel
在AiServices这个抽象类当中这个create方法实现如下:
public static <T> T create(Class<T> aiService, ChatLanguageModel chatLanguageModel) {
return builder(aiService)
.chatLanguageModel(chatLanguageModel)
.build();
}
// builder方法,返回的是DefaultSAiServices,这个类是AiServices的子类
public static <T> AiServices<T> builder(Class<T> aiService) {
AiServiceContext context = new AiServiceContext(aiService);
for (AiServicesFactory factory : loadFactories(AiServicesFactory.class)) {
return factory.create(context);
}
return new DefaultAiServices<>(context);
}
// chatLanguageModel方法
public AiServices<T> chatLanguageModel(ChatLanguageModel chatLanguageModel) {
context.chatModel = chatLanguageModel;
return this;
}
工作机制
对于create方法我们可以看到,它是把接口类与底层组件一起提供给AiServices,AiServices会创建实现这个接口的代理对象。当前来看使用的是反射机制完成的。这个代理类处理输入与输出的所有转换。在我们定义的接口中chat方法是接收的一个String作为输入,然而ChatLanguageModel它接收的是珍上ChatMessage作为输入,因而在这里AiService会自动把字符串转为UserMessage并调用ChatLanguageModel,chat方法返回的是String,那么ChatLanguageModel返回后也会同样进行转换。
@SystemMessage注解
我们可以在接口方法上加上@SystemMessage,这样子的话可以在聊天中指定它的角色信息
public interface TianWenService {
@SystemMessage("你是一个无所不知的智能体,你的名字叫玄鉴,“玄鉴”源自《庄子》中“鉴明则尘垢不止”,寓意纯净的洞察力,你要有深度分析用户提问给出有深度的回答")
String chat(String message);
}
通过这个方式,@SystemMessage注解中提供的内容会转为SystemMessage,并最终与UserMessage一起发送给到LLM。
除了直接指定文本,也可以从资源中加载提示模板,如下所示:
@SystemMessage(fromResource="systemMessage.txt")
String chat(String message);
注意:fromResource相当于调用Class.getResourceAsStream(String),一般会把这个提示模板和txt文档放在resources下
系统消息提供器
系统消息也可以通过系统消息提供器来动态定义,而不是像上面这样直接写死来指定
在AiService接口中的方法上去掉@SystemMessage注解