SpringBoot整合LangChain4J
LangChain4j官网直达:https://docs.langchain4j.dev/intro/
前置模型准备
本文中出了快速开始部分使用的是openai,openai由于网络原因无法访问,所以其余的讲解中使用的大模型是阿里百炼中提供的大模型接口。
官网:https://www.aliyun.com/product/bailian
下面是使用步骤:
- 首先进入官网
- 点击免费体验,登录我们的支付宝账号,然后开通这个功能,截至目前(2025.3.20)为止,阿里云百炼是提供了模型的免费token次数的。关于免费详情的话可以到这里查看:新人免费额度详情页面
- 然后我们就可以看到模型广场中有一堆大模型可供选择,当然也有目前最火的deepseek
- 我们在“模型筛选”处选择我们所需要的大模型,然后查看详情以及调用示例。
- 想要用我们的langchain4j去调用百炼的大模型,我们还需要去获取一个api-key,点击如图:
- 点击“创建我的API-KEY”即可
- 创建完成之后就可以查看并复制api-key了,在后续的使用中我们会用到这个api-key。
快速开始
- 导入依赖
<!-- 版本可以自行去中央仓库或者github查看最新版 -->
<!-- langchain4j的核心依赖 -->
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId><version>${langchain4j.version}</version>
</dependency><!-- deepseek也可以用这个,因为规范都一样 -->
<!-- openai和langchain4j的整合包 -->
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-open-ai</artifactId><version>${langchain4j.version}</version>
</dependency>
<!-- 目前写这篇文章的时候使用的langchain4j.version是1.0.0-beta2 -->
- 编写代码
@Test
void quickStartAI(){// xxxAIChatModel就可以实现去调用某个AI去回答我们的问题OpenAiChatModel openai = OpenAiChatModel.builder().apiKey("your key") //这里配置自己的.baseUrl("your url") //这个地址也是配置自己的.modelName("gpt-40-mini").build();String answer = openai.chat("你好");System.out.println(answer);
}
// 因为openai需要科学上网,所以自己去想一想办法。
// 直接使用国内的也可以,deepseek的配置和openai的一样,都是一套规范
- run
SpringBoot整合
依赖
- 导入我们所需要的依赖
<!-- langchain4j核心依赖 -->
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId><version>${langchain4j.version}</version>
</dependency>
<!-- 导入依赖(这里是社区版本的阿里ai整合的依赖) -->
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
</dependency>
- 导入依赖管理(管理我们上面导入的AI相关依赖)
<!-- 社区版的依赖管理依赖 -->
<dependencyManagement><dependencies><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-community-bom</artifactId><version>${langchain4j.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>
自动配置
我们先到项目管理栏中的外部库
下找到langchain4j.community.dashscope.chat-model.api-key
中的自动配置类-AutoConfig
:
@AutoConfiguration
@EnableConfigurationProperties({Properties.class})
public class AutoConfig {public AutoConfig() {}private static List<QwenChatRequestParameters.TranslationOptionTerm> getTmList(ChatModelProperties.TranslationOptions translationOptions) {List<ChatModelProperties.TranslationOptionTerm> tmList = translationOptions.getTmList();return Utils.isNullOrEmpty(tmList) ? Collections.emptyList() : (List)tmList.stream().map((term) -> {return TranslationOptionTerm.builder().source(term.getSource()).target(term.getTarget()).build();}).collect(Collectors.toList());}private static List<QwenChatRequestParameters.TranslationOptionTerm> getTerms(ChatModelProperties.TranslationOptions translationOptions) {List<ChatModelProperties.TranslationOptionTerm> terms = translationOptions.getTerms();return Utils.isNullOrEmpty(terms) ? Collections.emptyList() : (List)terms.stream().map((term) -> {return TranslationOptionTerm.builder().source(term.getSource()).target(term.getTarget()).build();}).collect(Collectors.toList());}private static QwenChatRequestParameters.TranslationOptions getTranslationOptions(ChatModelProperties.Parameters parameters) {ChatModelProperties.TranslationOptions translationOptions = parameters.getTranslationOptions();return translationOptions == null ? null : TranslationOptions.builder().sourceLang(translationOptions.getSourceLang()).targetLang(translationOptions.getTargetLang()).terms(getTerms(translationOptions)).tmLists(getTmList(translationOptions)).domains(translationOptions.getDomains()).build();}private static QwenChatRequestParameters.SearchOptions getSearchOption(ChatModelProperties.Parameters parameters) {ChatModelProperties.SearchOptions searchOptions = parameters.getSearchOptions();return searchOptions == null ? null : SearchOptions.builder().enableSource(searchOptions.getEnableSource()).enableCitation(searchOptions.getEnableCitation()).citationFormat(searchOptions.getCitationFormat()).forcedSearch(searchOptions.getForcedSearch()).searchStrategy(searchOptions.getSearchStrategy()).build();}private static ResponseFormat getResponseFormat(ChatModelProperties.Parameters parameters) {ResponseFormatType responseFormatType = parameters.getResponseFormat();return responseFormatType == null ? null : ResponseFormat.builder().type(responseFormatType).build();}private QwenChatRequestParameters getParameters(ChatModelProperties properties) {ChatModelProperties.Parameters parameters = properties.getParameters();return parameters == null ? null : ((QwenChatRequestParameters.Builder)((QwenChatRequestParameters.Builder)((QwenChatRequestParameters.Builder)((QwenChatRequestParameters.Builder)((QwenChatRequestParameters.Builder)((QwenChatRequestParameters.Builder)((QwenChatRequestParameters.Builder)((QwenChatRequestParameters.Builder)((QwenChatRequestParameters.Builder)((QwenChatRequestParameters.Builder)QwenChatRequestParameters.builder().modelName(parameters.getModelName())).temperature(parameters.getTemperature())).topP(parameters.getTopP())).topK(parameters.getTopK())).frequencyPenalty(parameters.getFrequencyPenalty())).presencePenalty(parameters.getPresencePenalty())).maxOutputTokens(parameters.getMaxOutputTokens())).stopSequences(parameters.getStopSequences())).toolChoice(parameters.getToolChoice())).responseFormat(getResponseFormat(parameters))).seed(parameters.getSeed()).enableSearch(parameters.getEnableSearch()).searchOptions(getSearchOption(parameters)).translationOptions(getTranslationOptions(parameters)).vlHighResolutionImages(parameters.getVlHighResolutionImages()).build();}@Bean@ConditionalOnProperty({"langchain4j.community.dashscope.chat-model.api-key"})QwenChatModel qwenChatModel(Properties properties) {ChatModelProperties chatModelProperties = properties.getChatModel();return QwenChatModel.builder().baseUrl(chatModelProperties.getBaseUrl()).apiKey(chatModelProperties.getApiKey()).modelName(chatModelProperties.getModelName()).temperature(chatModelProperties.getTemperature()).topP(chatModelProperties.getTopP()).topK(chatModelProperties.getTopK()).enableSearch(chatModelProperties.getEnableSearch()).seed(chatModelProperties.getSeed()).repetitionPenalty(chatModelProperties.getRepetitionPenalty()).temperature(chatModelProperties.getTemperature()).stops(chatModelProperties.getStops()).maxTokens(chatModelProperties.getMaxTokens()).defaultRequestParameters(this.getParameters(chatModelProperties)).build();}@Bean@ConditionalOnProperty({"langchain4j.community.dashscope.streaming-chat-model.api-key"})QwenStreamingChatModel qwenStreamingChatModel(Properties properties) {ChatModelProperties chatModelProperties = properties.getStreamingChatModel();return QwenStreamingChatModel.builder().baseUrl(chatModelProperties.getBaseUrl()).apiKey(chatModelProperties.getApiKey()).modelName(chatModelProperties.getModelName()).temperature(chatModelProperties.getTemperature()).topP(chatModelProperties.getTopP()).topK(chatModelProperties.getTopK()).enableSearch(chatModelProperties.getEnableSearch()).seed(chatModelProperties.getSeed()).repetitionPenalty(chatModelProperties.getRepetitionPenalty()).temperature(chatModelProperties.getTemperature()).stops(chatModelProperties.getStops()).maxTokens(chatModelProperties.getMaxTokens()).defaultRequestParameters(this.getParameters(chatModelProperties)).build();}@Bean@ConditionalOnProperty({"langchain4j.community.dashscope.language-model.api-key"})QwenLanguageModel qwenLanguageModel(Properties properties) {LanguageModelProperties languageModelProperties = properties.getLanguageModel();return QwenLanguageModel.builder().baseUrl(languageModelProperties.getBaseUrl()).apiKey(languageModelProperties.getApiKey()).modelName(languageModelProperties.getModelName()).temperature(languageModelProperties.getTemperature()).topP(languageModelProperties.getTopP()).topK(languageModelProperties.getTopK()).enableSearch(languageModelProperties.getEnableSearch()).seed(languageModelProperties.getSeed()).repetitionPenalty(languageModelProperties.getRepetitionPenalty()).temperature(languageModelProperties.getTemperature()).stops(languageModelProperties.getStops()).maxTokens(languageModelProperties.getMaxTokens()).build();}@Bean@ConditionalOnProperty({"langchain4j.community.dashscope.streaming-language-model.api-key"})QwenStreamingLanguageModel qwenStreamingLanguageModel(Properties properties) {LanguageModelProperties languageModelProperties = properties.getStreamingLanguageModel();return QwenStreamingLanguageModel.builder().baseUrl(languageModelProperties.getBaseUrl()).apiKey(languageModelProperties.getApiKey()).modelName(languageModelProperties.getModelName()).temperature(languageModelProperties.getTemperature()).topP(languageModelProperties.getTopP()).topK(languageModelProperties.getTopK()).enableSearch(languageModelProperties.getEnableSearch()).seed(languageModelProperties.getSeed()).repetitionPenalty(languageModelProperties.getRepetitionPenalty()).temperature(languageModelProperties.getTemperature()).stops(languageModelProperties.getStops()).maxTokens(languageModelProperties.getMaxTokens()).build();}@Bean@ConditionalOnProperty({"langchain4j.community.dashscope.embedding-model.api-key"})QwenEmbeddingModel qwenEmbeddingModel(Properties properties) {EmbeddingModelProperties embeddingModelProperties = properties.getEmbeddingModel();return QwenEmbeddingModel.builder().baseUrl(embeddingModelProperties.getBaseUrl()).apiKey(embeddingModelProperties.getApiKey()).modelName(embeddingModelProperties.getModelName()).build();}@Bean@ConditionalOnProperty({"langchain4j.community.dashscope.tokenizer.api-key"})QwenTokenizer qwenTokenizer(Properties properties) {TokenizerProperties tokenizerProperties = properties.getTokenizer();return QwenTokenizer.builder().apiKey(tokenizerProperties.getApiKey()).modelName(tokenizerProperties.getModelName()).build();}
}
Bean的创建
可以看到几个@ConditionalOnProperty
注解,这个注解是根据条件来创建bean的,如果配置文件中配置了该属性那么才会创建该类型的bean。
可以看到这其实是一堆api-key,也就是我们配置了这些api-key,springboot就会给我们自动创建对应的bean并交给IOC管理。
我们要使用什么功能的bean就配置什么api-key就行了。
那么接下来我们就明白了配置文件中该配置什么东西了,以下是这些配置都是干嘛的:
<font style="color:rgb(28, 31, 35);">langchain4j.community.dashscope.chat-model.api-key</font>
用于访问对话模型服务实现问答等;<font style="color:rgb(28, 31, 35);">langchain4j.community.dashscope.streaming-chat-model.api-key</font>
用于流式对话模型实时交互;<font style="color:rgb(28, 31, 35);">langchain4j.community.dashscope.language-model.api-key</font>
用于通用语言模型做文本生成等任务;<font style="color:rgb(28, 31, 35);">langchain4j.community.dashscope.streaming-language-model.api-key</font>
用于流式通用语言模型实时处理<font style="color:rgb(28, 31, 35);">langchain4j.community.dashscope.embedding-model.api-key</font>
用于嵌入模型将文本转向量做相似度计算<font style="color:rgb(28, 31, 35);">langchain4j.community.dashscope.tokenizer.api-key</font>
用于分词器服务分割文本成词元。
我们可以看到在创建某个<font style="color:rgb(28, 31, 35);">ChatModel</font>
的时候是使用的<font style="color:rgb(28, 31, 35);">getxxx</font>
方法获取到对应的配置文件中的参数,而这个配置的根源就是<font style="color:rgb(28, 31, 35);">Properties</font>
这个类。
下面介绍这个类。
配置的读取
我们还可以看到每个创建Bean的方法中都设计到了一个<font style="color:rgb(28, 31, 35);">Properties</font>
的参数,而这个参数也是被自动装配进来的。我们点进去这个文件看看:
@ConfigurationProperties(prefix = "langchain4j.community.dashscope"
)
public class Properties {static final String PREFIX = "langchain4j.community.dashscope";@NestedConfigurationPropertyChatModelProperties chatModel;@NestedConfigurationPropertyChatModelProperties streamingChatModel;@NestedConfigurationPropertyLanguageModelProperties languageModel;@NestedConfigurationPropertyLanguageModelProperties streamingLanguageModel;@NestedConfigurationPropertyEmbeddingModelProperties embeddingModel;@NestedConfigurationPropertyTokenizerProperties tokenizer;public Properties() {}ChatModelProperties getChatModel() {return this.chatModel;}void setChatModel(ChatModelProperties chatModel) {this.chatModel = chatModel;}ChatModelProperties getStreamingChatModel() {return this.streamingChatModel;}void setStreamingChatModel(ChatModelProperties streamingChatModel) {this.streamingChatModel = streamingChatModel;}LanguageModelProperties getLanguageModel() {return this.languageModel;}void setLanguageModel(LanguageModelProperties languageModel) {this.languageModel = languageModel;}LanguageModelProperties getStreamingLanguageModel() {return this.streamingLanguageModel;}void setStreamingLanguageModel(LanguageModelProperties streamingLanguageModel) {this.streamingLanguageModel = streamingLanguageModel;}EmbeddingModelProperties getEmbeddingModel() {return this.embeddingModel;}void setEmbeddingModel(EmbeddingModelProperties embeddingModel) {this.embeddingModel = embeddingModel;}TokenizerProperties getTokenizer() {return this.tokenizer;}void setTokenizer(TokenizerProperties tokenizer) {this.tokenizer = tokenizer;}
}
其中用到了一个很关键的注解@NestedConfigurationProperty
,这个注解的作用是:用于处理配置属性嵌套的注解,它能实现将配置绑定到这个内部嵌套的类上去。
简单讲就是这个类是所有类型的<font style="color:rgba(0, 0, 0, 0.85);">ChatModel</font>
的配置总类,而内部又嵌套了各个具体类型的<font style="color:rgba(0, 0, 0, 0.85);">ChatModel</font>
的配置类,使用这个注解就能将配置读到嵌套的子类里面。
如果我们想要去使用某个ChatModel
又不知道该怎么配置,我们就可以这么查看,点进去某个ChatModel
看看里面到底要配置些什么信息。
就比如第一个chatModel
我们需要配置:
private String baseUrl;
private String apiKey;
private String modelName;
private Double topP;
private Integer topK;
private Boolean enableSearch;
private Integer seed;
private Float repetitionPenalty;
private Float temperature;
private List<String> stops;
private Integer maxTokens;
private Parameters parameters;
使用
因为我们使用的是Springboot的整合,而springboot最大的优势就是自动装配,我们只需要在配置文件中配置我们需要的api-key即可,这里我们使用QWenChatModel
,根据上文中自动配置的讲解,我们需要这样配置:
langchain4j:community:dashscope:chat-model:api-key: ${ALAI_API_KEY:} #这个是读取环境变量的方式model-name: qwen-max #这个是模型名称
根据配置的选项可以得知我们这个配置会让springboot创建一个QwenChatModel
,我们可以在controller层中注入这个bean测试功能:
@RestController
@RequestMapping("/myai")
public class QWController {// 这里用的是ChatLanguageModel多态的写法@Autowiredprivate ChatLanguageModel qwenChatModel;@GetMapping("/test")public String test(){String answer = qwenChatModel.chat("你好");return answer;}
}
流式调用
如果模型返回的数据很多的话,一次性返回给前端是不现实的。这个时候通常会使用流形式的数据返回,而不是一次性返回全部。流式响应是根据token来返回的。
对于这种情况就需要使用带有流式返回功能的模型streaming-chat-model
。
首先你选择的大模型需要先支持流式返回,其次你需要在配置文件配置流式大模型。
配置如下:
langchain4j:community:dashscope:streaming-chat-model:api-key: ${ALAI_API_KEY:} #这个是读取环境变量的方式model-name: qwen-max #这个是模型名称
想要实现对响应的流式打印,我们还需要引入spring-webflux
的依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
接下来我们编写controller:
@PostMapping(value = "/stream",produces = "text/event-stream;charset=UTF-8")// 设置produces,防止乱码// 这里的返回值是Flux<String>,因为是流式返回,所以是一个字符串的流public Flux<String> stream(@RequestBody String request){Flux<String> flux = Flux.create(fluxSink -> {qwenStreamingChatModel.chat(request, new StreamingChatResponseHandler() {@Overridepublic void onPartialResponse(String s) {// 这里的s是每次流式返回的结果fluxSink.next(s); //每次返回一个字符串}@Overridepublic void onCompleteResponse(ChatResponse chatResponse) {// 这里是响应结束的文本fluxSink.complete(); //响应结束调用complete}@Overridepublic void onError(Throwable throwable) {// 这里是错误的处理fluxSink.error(throwable);}});});return flux;}
模型记忆
记忆过程
目前为止,我们接触到的大模型还都是没有记忆能力的,比如我们开始跟大模型说了我们的名字,接下来立马问大模型:”我是谁“,大模型就打不上来。
出现这种现象就是因为目前接触到的大模型没有记忆能力,我们需要去让大模型能够有一个短期记忆。
想要实现这功能,其实最简单的方式就是第二次请求的时候带上第一次请求发送的信息和响应就行了。
langchain中提供了一个ChatMemory
来操作我们的聊天记录:
public interface ChatMemory {Object id();void add(ChatMessage var1);List<ChatMessage> messages();void clear();
}
ChatMemory
有多种实现方式,这里我们先使用基于滑动窗口实现的MessageWindowChatMemory
,它只会往后滑动记录最近的几条记录。我们点进去看看记录都是存在哪里的:
public void add(ChatMessage message) {
List<ChatMessage> messages = this.messages();
if (message instanceof SystemMessage) {Optional<SystemMessage> systemMessage = findSystemMessage(messages);if (systemMessage.isPresent()) {if (((SystemMessage)systemMessage.get()).equals(message)) {return;}messages.remove(systemMessage.get());}
}messages.add(message);
ensureCapacity(messages, this.maxMessages);
this.store.updateMessages(this.id, messages);
}
这里可以看到是使用了this.store.updateMessages(this.id, messages);
来跟新了存储的消息,找到就这个方法的实现类InMemoryChatMemoryStore
,我们看看究竟存储在了哪里,发现它是声明了一个private final Map<Object, List<ChatMessage>> messagesByMemoryId = new ConcurrentHashMap();
来存储的,而这个变量是存储在JVM的内存中的。
实现记忆
了解了记忆载体,我们接下来就可以实现让模型有记忆了。但是这个记忆能力需要通过动态代理来实现。
首先我们需要先根据规范来编写一个接口,让实现记忆以后的代理方法能够自动装配到接口中。返回类型为String
的是用于返回普通文本响应的方法,返回类型为TokenStream
的是用于返回流式处理的方法。而参数部分使用@UserMessage
注解来标注用户消息参数即可。
然后我们使用AiServices
来构建一个有记忆能力的AI,首先编写配置类,去将我们有记忆功能的AI注入到ioc中:
@Configuration
public class MemoryAIConfig {// 接口,作用是代理后的AI能够自动装配到这public interface Assistant{String chat(@UserMessage String message);TokenStream stream(@UserMessage String message);}@Beanpublic Assistant assistant(ChatLanguageModel chatLanguageModel, StreamingChatLanguageModel streamingChatLanguageModel){MessageWindowChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10); //基于滑动窗口的记忆,只记录最新的10条// 在使用的时候会首先携带历史消息,然后再将新的消息添加到记忆中,这样就可以保证记忆的连续性return AiServices.builder(Assistant.class).chatLanguageModel(chatLanguageModel).streamingChatLanguageModel(streamingChatLanguageModel).chatMemory(chatMemory).build();}
}
编写带记忆功能的AI接口:
@Autowired
private MemoryAIConfig.MemoryAI memoryAI;@PostMapping("/memory")
public String memory(@RequestBody String request){String answer = memoryAI.chat(request);return answer;
}
测试一下:
第一次请求
记忆隔离
目前为止我们仅实现了对记忆的实现,但是如果同时多个人去聊天的话,记忆是混乱的,它会用跟a的历史记录去和b扯皮。这是因为我们没有对多用户场景下做隔离。
想要实现隔离其实很简单,不论是基于cookie-session的用户还是基于jwt令牌的用户我们都可以使用用户的id去作为隔离标志。让AI对于每个id有独立的记忆。当然也可以实现一个用户有多个会话,多个会话之间也隔离。
在接口的参数部分我们使用@MemoryId
注解标记的参数就代表这个会话的ID。然后再使用chatMemoryProvider
去构造我们的记忆载体,让它带上隔离标识。
继续完善我们的MemoryAI
@Configuration
public class MemoryAIConfig {public interface MemoryAI{String chat(@MemoryId String id,@UserMessage String message);TokenStream stream(@MemoryId String id,@UserMessage String message);}@Beanpublic MemoryAI assistant(ChatLanguageModel chatLanguageModel, StreamingChatLanguageModel streamingChatLanguageModel){// 在使用的时候会首先携带历史消息,然后再将新的消息添加到记忆中,这样就可以保证记忆的连续性return AiServices.builder(MemoryAI.class).chatLanguageModel(chatLanguageModel).streamingChatLanguageModel(streamingChatLanguageModel).chatMemoryProvider(memoryId -> MessageWindowChatMemory.builder().maxMessages(10).id(memoryId).build()).build();}
}
接口:
@Autowired
private MemoryAIConfig.MemoryAI memoryAI;@PostMapping("/memory")
public String memory(@RequestParam("id") String id,@RequestParam("message") String request){String answer = memoryAI.chat(id,request);return answer;
}
测试一下:
手搓记忆载体
之前的记忆内容我们是使用的默认实现,而默认实现是存储在内存中的,下面开始讲解如何自己手搓一个记忆载体。
我们上面提到的ChatMemory
是一个高级的抽象,用于直接管理聊天记忆的操作。
ChatMemoryStore
是一个底层的存储接口,用于实现自定义的存储方式。
所以我们想要去自定义记忆载体就需要自己去实现ChatMemoryStore
,下面是说明,自行实现即可:
public class MyMemoryStoreImpl implements ChatMemoryStore {@Overridepublic List<ChatMessage> getMessages(Object o) {// 根据o获取对话记录,一般是idreturn null;}@Overridepublic void updateMessages(Object o, List<ChatMessage> list) {// 根据o去更新对话记录}@Overridepublic void deleteMessages(Object o) {// 根据o去删除对话记录}
}
然后,使用我们自己的实现MyMemoryStoreImpl
:
@Configuration
public class MemoryAIConfig {public interface MemoryAI{String chat(@MemoryId String id,@UserMessage String message);TokenStream stream(@MemoryId String id,@UserMessage String message);}@Beanpublic MemoryAI assistant(ChatLanguageModel chatLanguageModel, StreamingChatLanguageModel streamingChatLanguageModel){// 在使用的时候会首先携带历史消息,然后再将新的消息添加到记忆中,这样就可以保证记忆的连续性return AiServices.builder(MemoryAI.class).chatLanguageModel(chatLanguageModel).streamingChatLanguageModel(streamingChatLanguageModel).chatMemoryProvider(memoryId -> MessageWindowChatMemory.builder().maxMessages(10).id(memoryId).chatMemoryStore(new MyMemoryStoreImpl()) // 不适用默认的,将自定义的MyMemoryStore换成自己的实现.build()).build();}
}
工具Tools(Function-call)
快速开始
现在想让AI去完成我们特定的工作,想让AI能够学会使用很多的工具去帮我们完成工作,也就是想要AI和贾维斯一样,我们说一个工作它就能帮我们实现,比如买个什么东西,那就需要调用买东西的接口。关个灯,就需要调关灯的接口。
AI能够通过我们给接口以及接口参数的定义去完成调用过程。
下面看看该如何实现:
首先我们需要先定义被AI调用的接口:
@Service
public class ToolsService {// 调光的工具@Tool("调整房间的亮度为{message}") // 什么样的请求会用到这个工具public String dimming(@P("message") String message) {return "光亮调整到" + message + "了";}
}
之后把工具和我们创建的ai绑定起来:
@Beanpublic MemoryAI assistant(ChatLanguageModel chatLanguageModel,StreamingChatLanguageModel streamingChatLanguageModel,ToolsService toolsService){// 在使用的时候会首先携带历史消息,然后再将新的消息添加到记忆中,这样就可以保证记忆的连续性return AiServices.builder(MemoryAI.class).chatLanguageModel(chatLanguageModel).streamingChatLanguageModel(streamingChatLanguageModel).chatMemoryProvider(memoryId -> MessageWindowChatMemory.builder().maxMessages(10).id(memoryId).build()).tools(toolsService) //绑定工具.build();}
编写controller:
@Autowired
private MemoryAIConfig.MemoryAI memoryAI;@PostMapping("/memory")
public String memory(@RequestParam("id") String id,@RequestParam("message") String request){String answer = memoryAI.chat(id,request);return answer;
}
测试:
可以看到我们的表述不是和工具完全一模一样,只是语义相近就可以,这就是AI的能力。
但是我们发现我们的本意其实是让用户指定一个{亮度值},然后ai根据亮度值去做调光,但是实际是我们不传递亮度值它也能去调光,是不是不够准确,如果我们设计的是去签收某个订单,连订单号都没指定,他就给我们签收了,这怎么行。
再次优化
上面的问题是作为一个调光的机器人就显得非常不专业,我连亮度都没告诉你,你就根据我说的更高或者更低去调整,是不是不太好。
所以我们需要使用一些提示词给AI限定角色,以及告诉AI必须得拿到用户传递的哪些信息。
给我们的AI助手定义角色:
@Configuration
public class MemoryAIConfig {public interface MemoryAI{String chat(@MemoryId String id,@UserMessage String message);@SystemMessage("""你是一个专业的调光助手,你可以根据用户的指令来调整房间的亮度。你必须要拿到用户提供的具体的亮度值(单位:尼特)之后,你才能去根据这个亮度值来调整房间的亮度。""") // 提示词模板定义AI的角色TokenStream stream(@MemoryId String id,@UserMessage String message);}@Beanpublic MemoryAI assistant(ChatLanguageModel chatLanguageModel,StreamingChatLanguageModel streamingChatLanguageModel,ToolsService toolsService){// 在使用的时候会首先携带历史消息,然后再将新的消息添加到记忆中,这样就可以保证记忆的连续性return AiServices.builder(MemoryAI.class).chatLanguageModel(chatLanguageModel).streamingChatLanguageModel(streamingChatLanguageModel).chatMemoryProvider(memoryId -> MessageWindowChatMemory.builder().maxMessages(10).id(memoryId).build()).tools(toolsService).build();}
}
然后接口不变即可,只不过调用时候的接口也得使用stream这个定义了角色的方法。
下面定义相关的接口:
@PostMapping("/memory/stream")
public Flux<String> memoryStream(@RequestParam("id") String id,@RequestParam("message") String request){TokenStream stream = memoryAI.stream(id, request);// 将 TokenStream 转换为 Flux<String>return Flux.create(fluxSink -> {stream.onPartialResponse(token -> {fluxSink.next(token);}).onCompleteResponse(response -> {fluxSink.complete();}).onError(error -> {fluxSink.error(error);}).start();});
}
测试:
补充:提示词参数
假如我们提示词模板中有些内容是需要通过参数方式指定的,而不是给一个固定的值,那么我们可以使用下面的方式去做:
首先提供一个占位符,然后在方法参数中使用@V
注解标记这个参数。
比如我们想要传给AI一个当前位置,我们就可以修改提示词为:
@SystemMessage("""你是一个专业的调光助手,你可以根据用户的指令来调整房间的亮度。你必须要拿到用户提供的具体的亮度值(单位:尼特)之后,你才能去根据这个亮度值来调整房间的亮度。当前的房间位置是{{currentPosition}},你可以根据这个位置来调整房间的亮度。""")
TokenStream stream(@MemoryId String id, @UserMessage String message,@V("currentPosition") String currentPosition);
修改接口:
@PostMapping("/memory/stream")
public Flux<String> memoryStream(@RequestParam("id") String id,@RequestParam("message") String request,@RequestParam("currentPosition") String currentPosition){TokenStream stream = memoryAI.stream(id, request,currentPosition);// 将 TokenStream 转换为 Flux<String>return Flux.create(fluxSink -> {stream.onPartialResponse(token -> {fluxSink.next(token);}).onCompleteResponse(response -> {fluxSink.complete();}).onError(error -> {fluxSink.error(error);}).start();});
}
测试:
RAG
我们使用的大模型训练数据有限,而且存在信息滞后,无法得知实时的信息。
还有一部分数据是个人私有的,想要基于个人私有的数据二次开发大模型。
就比如我们的淘宝等网站,用户想要退货,还需要考虑是否已经送达、是否已经签收、是否满足7天无理由退款、以及退货的运费由谁承担等等因素。而这些数据是存储在淘宝自己的数据库中的,你不给到AI,它怎么根据这些信息去做处理和应用。
当前我们编写的大模型还存在以上的问题,而我们的SystemMessage
和function-call
只能提供很少量的信息,实际情况是大部分数据都在公网和私有数据库中。我们可以通过RAG将我们自己的专业领域数据库给挂到大模型上,以达到能让大模型根据我们的专业数据去做回答和处理,而不是一本正经的胡说八道。
明白了需要怎么去做,接下来就又需要思考一个新的问题了,我们给AI的数据是什么格式都可以么?
包不行的,就好比你给一个老外一本中文操作手册,语言不通,看不懂啊。虽然让他再学习一下中文就能看懂了,但是我们为什么不一开始就给他一本英文的呢?
在AI领域我们一般使用的数据是向量数据库,主要是因为向量数据库在处理和存储向量数据方面具有独特的优势,能够高效地支持 RAG 的检索和生成过程。
这里什么使用向量数据库,这里建议去了解一下深度学习,了解深度学习以后就不迷茫了。
文本向量化
我们存储数据使用的是向量数据库,但是我们的信息一般都是文本形式的,我们还需要先将我们的文本转换成向量,然后才能存进数据库。
文本向量化需要使用到”向量大模型“,一般的大模型是不具备将文本转化成向量这个功能的。
如果是本地的话可以去Ollama中下载“向量大模型”
如果是用第三方提供的接口的话,可以使用阿里的百炼,其中也有很多的“向量大模型”。
快速上手
@Test
void vectorTranslation(){// 创建向量大模型实例QwenEmbeddingModel embeddingModel = QwenEmbeddingModel.builder().apiKey(System.getenv("OPENAI_API_KEY")).build();//这里没有指定模型名称,直接使用默认的Response<Embedding> embeddingResponse = embeddingModel.embed("想要向量化的文本内容");System.out.println(embeddingResponse.toString());System.out.println(embeddingResponse.content().vector().length);
}
这个例子中我们实例化了一个向量模型,并用模型将我们的文本给转化成了向量,输出内容如下:
Response { content = Embedding { vector = [-0.017882334, 0.025318032, 0.019335436, 0.017044509, 0.05108116, -0.02106345, -0.011723008, 0.0031402083, -0.019060524, 0.026522405, 0.0020601992, -0.0066731474, 0.010407361, 0.01937471, -0.010701909, 0.008463345, 0.0150154, 0.0017738332, -0.025527488, -0.015643768, 0.0038291232, -0.018379793, 0.011075003, 6.263235E-4, 7.4332446E-4, -3.745259E-4, -0.026614044, -0.008489527, -0.0016936507, 0.029376248, 0.032020636, 0.022254733, 0.019924533, 0.018157244, 0.014531032, 0.008698983, 0.01335284, 0.0050825886, 0.008784074, -0.03246573, -0.007795702, -0.010603726, 0.016769597, 0.029611887, -0.021351453, -0.028433695, 0.01861543, 0.027281685, -0.0017836514, -0.020500537, 0.01303211, -0.16442321, -0.02170491, -0.01629832, 0.036419217, -0.010544817, 0.006722239, 0.004954951, 0.02605113, 0.018183427, 0.04021561, -0.0041105803, -0.002983116, -0.0032253, -0.033015553, 0.01925689, -0.009975357, 0.013640842, -0.045268748, -0.01937471, -0.010813182, 0.003406938, -0.011101185, 0.017895425, -0.009163714, 0.015264129, -0.0024692935, 0.014871399, -0.010937547, -0.020225625, -0.018209608, 0.008103342, 0.03427229, -0.0047389492, 0.016442321, -0.024035113, 0.038592327, -0.008509163, 0.037702136, 0.019086707, 0.013418295, 0.018065607, -0.009844447, 0.0068007847, 0.05890959, 0.0030632988, 0.008568073, 0.019021252, 0.026496224, -0.010813182, -0.019230708, -0.0056585935, -0.014661943, 0.0014252848, 0.009733173, -0.025894037, -0.014661943, 0.019701986, -0.0034298473, 0.03262282, 0.027203139, -0.034324653, -0.026627135, -0.01039427, 0.025475124, -0.024087477, 0.036026485, 0.018039426, 0.0051316796, 0.0028882062, 0.014871399, -0.007933158, 0.002700023, -0.020867085, 0.0064996914, 0.0170576, -0.03144463, 0.031392265, -0.039770517, -0.008345525, -0.0032072999, -0.021534728, 0.01989835, -0.0029471158, -0.0038291232, 0.039770517, 0.0011634644, -0.011016093, -0.023629291, 0.01680887, -0.030580623, -0.036628675, -0.020657629, -0.006820421, -0.0071869697, 0.0051513165, -0.026286768, -0.029847525, 0.016350685, -0.020644538, 0.049326964, -0.030240256, 0.009739718, 0.05890959, -0.03243955, -0.012285923, -0.04233636, 8.9918944E-4, -0.0034102106, 0.005504774, -0.04516402, 0.043593097, -0.0043429458, 0.027988601, -0.0032400275, -0.049614966, -0.0014105574, 0.021377636, -0.0054687737, -0.01912598, -0.014138302, 0.028328966, 0.0022549282, -0.0015201947, 0.0010759182, 0.035974123, -0.030502077, 0.02014708, -0.0032956642, -0.0060676876, 0.02657477, -0.03759741, -0.009058986, -0.023393653, -0.03694286, -0.027805327, 0.0258024, 0.033094097, 0.031758815, -0.028459877, 0.009098259, 0.007160788, 0.012305559, 0.013045201, 0.0058876863, -0.020474356, 0.013208839, -0.009929539, -0.011723008, 0.018680885, 0.0012845563, -0.014688124, -0.010728091, -0.020369627, -0.0027065685, -0.02374711, 0.0031811178, 0.047729857, -0.01962344, -0.0056127748, 1.7243328E-4, 0.028564606, -0.0015267401, 0.0077564293, 0.020984905, 0.036366854, 0.002757296, 0.011899737, 0.030868625, 0.01489758, -0.018013243, 0.0067484207, 0.015251039, 0.038304325, 0.011716463, 0.03505775, 0.006077506, 0.021587092, -0.007933158, -0.036157396, -0.0150154, -0.03898506, -0.038016323, 0.027988601, -6.300053E-4, -0.015643768, -0.0074618813, 0.010649545, -0.016141228, -0.029376248, -0.0019669258, 0.038042504, 0.01847143, -0.010282996, 0.020670721, 0.020526718, -0.023406744, 0.032413367, -0.021600183, -0.018877251, 0.04875096, -0.0023547472, 0.017149236, 0.0031696633, -0.025108576, 0.022739101, 0.022909286, 0.048332047, 0.012030647, -0.01937471, 0.01950562, -0.010354997, 4.9664057E-4, 0.0037047586, 0.010544817, 0.021403817, -0.0027883872, 0.03199445, -0.04492838, 0.041341443, -0.021875095, -0.028093329, -0.02053981, 0.0013369204, 0.014321576, -0.015002308, 0.011212459, -7.825975E-4, 0.019453255, -0.036393035, -0.012338286, -0.016141228, -4.2504905E-4, 0.010381179, 0.022189278, 0.009818265, -0.015028491, -0.052468807, 0.013667025, -0.015931772, 0.0096939, 0.033355918, -0.022451099, 0.007069151, 0.008554981, -0.007409517, -0.0020929268, 0.012456105, -0.03885415, 0.020565992, -0.0044051283, -0.007841521, 0.032361, 0.05644848, 0.031392265, -0.0021191088, -0.022987831, -0.011867009, 0.05608193, 0.028590787, 0.010269905, -0.01989835, 0.027098412, 0.017136145, -0.011258277, -0.016468503, -0.00936008, 0.007069151, -0.009988449, 0.0040254886, -0.025108576, -0.005861504, -0.0056160474, -0.02838133, 0.031889725, 0.018589249, 0.008509163, -0.0038094867, 0.02644386, 0.032308638, 0.0032056635, 0.022163097, 0.008928075, 0.022529645, 0.004100762, 0.008037887, 0.028643152, 0.0064538727, 0.009144077, -0.02504312, -0.01641614, 0.0033513012, -0.009968812, 0.018654704, 0.016075773, 0.04788695, -0.055505924, 0.01116664, 0.027491141, 0.007520791, 0.008253888, 0.03285846, 0.04194363, -0.041655626, -0.0026116585, 0.0017705604, -0.010354997, -0.03757123, -0.026744954, -0.024663482, -0.04848914, -0.018327428, -0.0070495144, 0.00886262, -0.0050302246, 0.022123823, 0.048829503, 0.013235021, -0.012318649, 0.015879408, -0.011389188, 0.019335436, -0.020579083, 0.019597257, 0.0086858915, -0.036811948, 0.030711534, -0.02027799, 0.047310945, 0.0018441974, 0.014426304, -0.02825042, 0.018419065, 0.0013991027, -0.01007354, -0.0039436696, 0.013889573, 0.014177575, 0.022621283, -0.028774062, -0.03199445, 0.020304171, 0.03853996, 0.032308638, 0.03733559, -0.006048051, 0.002328565, -5.097316E-4, 0.055401195, 0.034874476, -0.034665022, -0.007357153, -0.008712074, 0.0043462184, -0.022660555, -0.0067418753, 0.003963306, -0.0070756963, 0.0068466035, -7.5968826E-4, -0.015355767, 0.033434466, 0.005517865, -0.023864929, -0.016010318, 0.01976744, 0.0036916677, -0.0014121938, -3.5693485E-4, 0.048070226, -0.046237484, 0.0044804015, 0.016468503, 0.014347758, -0.015957953, -0.027046047, -0.009523717, -0.0027851146, 0.010747727, -0.015853224, -0.007200061, -0.037047587, 0.006679693, 0.024872938, -0.015971044, -0.030580623, 0.011173186, -0.023184197, 0.012731017, 0.002505294, -0.011853918, 0.032753732, 0.017267056, 0.055505924, -0.0133790225, 0.03916833, -0.02914061, 0.013064838, -0.011107731, 0.024218386, -0.2526043, -0.02053981, -0.0110226385, -0.020565992, 0.00603496, 0.034089018, -0.022215461, -0.030135527, 0.0023432926, 0.024846755, -0.0051480434, -0.012704835, -0.032073, 0.032779913, 0.052337896, 0.04479747, -7.040514E-4, -0.010819728, -0.0161805, 0.04016325, -0.0059465957, 0.0141513925, -0.028014783, -0.028643152, 0.033120282, 0.0019292891, -0.030214073, -0.02078854, 0.01872016, -0.002484021, -0.027386414, -0.011107731, -0.031078082, -0.009438626, -0.034874476, -0.012848836, -0.022254733, 0.0050302246, -0.015303402, -0.025828583, 0.01782997, 7.539609E-4, 0.028931154, -0.0021354726, 0.0074880635, -0.015735406, 0.035005387, -0.015447403, -0.011965192, 0.024493298, 0.0065127825, -0.011919374, 0.01861543, -0.0026967502, 0.024624208, 4.2300357E-4, -0.025095485, 1.0830774E-4, -0.034324653, 0.012083012, -0.02001617, 0.034769747, 0.028669333, -0.004634221, -0.03233482, 0.021587092, 0.049274597, 0.03890651, 0.01628523, 0.03270137, -0.004310218, -0.011912828, -0.019453255, -0.03390574, -0.0030142074, 0.02979516, 0.02835515, -0.005638957, 0.023668565, 0.0037898503, -0.010289541, 0.0021600183, 0.03257046, 0.020945631, 0.0015030127, 0.032046817, 0.025318032, -0.0051349527, 0.011467733, 0.009857538, 0.01759433, 0.05275681, -0.074356996, 0.014400122, 0.047546584, 0.013667025, -0.055348832, 0.036157396, 0.025972584, -0.013418295, -0.018170336, -0.025003849, 0.017384874, 0.005655321, -0.015290312, 0.04018943, 2.3113831E-4, 0.021416908, 0.015800862, -0.019283073, -0.008784074, 0.011513552, 0.019073617, 0.03270137, -0.016979054, 0.008142615, 0.0018818341, 0.001742742, 0.0074356995, -0.021141997, 0.020238716, 0.03081626, 0.0058713225, 0.024598027, 0.0059924144, -0.01020445, 0.020317262, 0.013431386, 0.004634221, -0.031418446, 0.0017852879, -0.011369551, 0.0024692935, -0.012757199, 0.001435103, -4.4059465E-4, -0.019453255, -0.00609387, 0.052730627, 0.0038651237, -0.027019866, 0.021220542, 0.016494686, 0.0026623863, 0.0026083856, -0.030763896, 0.03696904, 0.023930384, -3.5734393E-4, -0.014675033, -0.012318649, 0.035214845, 0.0067811483, -0.016756505, -0.020081624, -0.027883872, 0.0065815104, 0.053830273, -0.0010939183, -0.0030043891, -0.038592327, 0.03744032, 0.027779145, -0.05959032, 0.04720622, 0.032361, -4.4550377E-4, 0.0290097, -0.0130124735, -0.0014907399, 0.013392113, 0.022254733, 0.018589249, 0.01539504, -0.019976897, -0.015093946, 0.018968888, 2.691841E-4, 0.010440089, -0.007147697, 0.023144923, 0.03220391, -0.015565223, 0.018104881, 0.010734636, 0.008234251, -0.0042840363, -0.031235173, -0.028040964, 0.021600183, -0.00763861, -0.054301552, 0.046289846, 0.012161558, 0.052730627, -0.027046047, 0.012881564, -0.0049484055, -0.0069382405, -0.031915907, 0.0062345983, -0.012312104, 0.007363699, -0.0096219, -0.036916677, 0.016259046, 0.04194363, 0.01174919, 0.032779913, 5.862322E-4, -0.046499304, 7.1509695E-4, -0.015447403, 0.012875018, 0.01874634, 0.028747879, -0.014845217, -0.034455564, 0.0161805, -0.018104881, 0.04262436, 0.004686585, -0.020997996, 0.0143739395, 0.036105033, -0.011729554, -0.004938587, -0.002804751, -0.032387182, 0.024336206, 0.005170953, -0.01097682, -0.008934621, -0.015669951, 0.015460495, -0.0014514668, -0.01539504, 0.038697056, -0.017515784, 0.04249345, -0.010498998, -0.044849835, 7.375972E-4, 0.026718771, 0.029978435, -0.014203757, 0.0070822416, -0.04686585, -0.03885415, -0.01872016, 0.007978977, 0.012469197, -0.01335284, -0.0073833354, -0.016272137, 0.06032342, -0.0051218616, 0.047389492, -0.017214691, -0.0021518364, 9.965539E-4, 0.028538423, 0.02080163, -0.0016699232, 0.02091945, -0.023341289, -0.04532111, 0.005694594, -0.006267326, -0.015159401, 0.009294624, -0.014164483, 0.011363005, -0.028171875, 0.0020438356, -0.008934621, -0.028800244, 0.03387956, 0.012351377, 0.013667025, -2.8513878E-4, -0.01809179, 0.02657477, -0.017044509, -6.856422E-4, 0.028171875, -0.015486676, 0.017882334, 0.024964575, -0.00507277, -0.031496994, -0.03686431, 0.002639477, 0.019479437, 4.929587E-4, -0.03416756, -0.0026525678, -0.021848911, -0.0040254886, -0.01899507, 0.011742645, 0.034612656, -0.018275063, 0.048698593, -0.049300782, -0.019453255, 0.0076713376, 0.0038683964, 0.040634524, -0.025462033, 0.015669951, 0.012030647, -0.0070887874, 0.037963957, -0.020186353, 0.058961954, 0.025985675, 0.013392113, -0.012050284, -0.0024676572, 0.030737715, -0.0042905817, -0.020526718, -0.009955721, 0.07990759, 0.051028796, 0.010093177, -0.016259046, 0.05917141, -0.031496994, 0.0032629366, -0.012776836, 0.0040189433, 0.002400566, -0.023734018, -0.23668563, -0.006103688, 0.011513552, 0.023432925, -0.031889725, 0.04901278, 0.013064838, 0.02979516, 0.0019341982, 0.012416832, 0.007933158, 0.05990451, -0.031758815, 0.012429924, 0.029742798, -0.009510626, 0.0029062065, -0.021744184, -0.0081360685, 0.025056211, 0.0017754696, 0.072943166, 0.021076541, -0.035974123, -0.023864929, -2.1620638E-4, -0.031732634, -0.007010241, -0.0029242067, 0.0046538576, 0.020565992, -0.003446211, 0.027910054, -0.01631141, 0.024362389, 0.004604766, -0.004045125, -0.002097836, 0.025501307, 0.011768827, 0.047153853, -0.013163021, -0.040608343, -0.00343312, -0.007546973, -0.004647312, -0.0031418449, 0.010053903, -0.054144457, -0.0045556747, 6.913695E-5, 0.03092099, 0.020775449, 0.05650084, 0.029297702, 0.01578777, -0.015211765, -0.018261973, 0.024336206, -0.054249186, 0.0068727853, 0.0015332857, -0.016403047, 0.012848836, -0.013549206, 0.10247651, -0.0405298, -0.0039862157, -0.016992144, -0.026483133, 0.010407361, 0.011343368, 0.0019718348, -0.0010644635, -0.014544123, 0.012731017, -0.0384876, -0.039770517, -0.057600487, 0.0015611041, 0.017267056, 0.04003234, -0.028957335, 0.03170645, -0.01148737, 0.007409517, -0.0028587515, -0.0019881986, -0.023563836, 0.0069906046, 0.020304171, -0.021875095, 0.011657553, 0.0074226083, 0.03725704, -0.004421492, -0.025946401, -0.01013245, 0.010361542, -0.01937471, 0.016625596, -0.011107731, 0.052547354, -0.0036556674, -0.019715076, -0.024035113, 0.024715846, 0.012888109, -0.014360849, 0.033041734, -0.021128906, 0.016887415, 0.047782224, 0.003393847, 0.01039427, 0.017332511, -0.014269211, 2.757296E-4, 0.015748497, -0.0065749646, 0.016730323, -0.01309102, 0.012312104, 0.001391739, 0.015905589, 0.016651778, 0.0060022324, 0.019060524, -0.00290457, -0.029035883, 0.040608343, -0.024035113, -0.0055244104, 0.029559523, 0.01707069, 0.039875247, -0.0017721968, 0.03414138, 0.03223009, -0.0055636833, -0.03531957, -0.027386414, -0.0012608288, -0.04440474, 0.0135622965, -0.04377637, -0.021678729, 2.292974E-4, 0.009058986, 5.007315E-4, -0.034560293, 0.003133663, 0.022189278, 0.02631295, -0.007114969, 0.0012428287, -0.008764437, 0.023210378, -0.0438811, 0.00439531, 0.004529493, -6.7950576E-4, -0.025422761, -0.025187122, 0.033696286, -0.00898044, 0.02029108, 0.013333204, -0.06770676, -0.031261355, -8.9755305E-4, 0.009445171, -0.020238716, -0.008286616, -0.02658786, 0.01116664, 0.017869242, -0.009805174, -0.019793622, 0.02606422, -0.061684884, -0.016154319, -0.0021911093, 0.01250847, 0.0058255037, 0.012384105, -0.01680887, -0.020133989, 0.007226243, 0.0011871919, -0.0077302475, 0.032753732, 0.0065880558, -0.02618204, -0.004794586, 0.020120898, -0.0135622965, -0.040267978, -0.0018147427, -0.017816879, -0.008247343, 0.0032367546, -0.022503464, -0.006080779, 0.016520867, 0.009792083, -0.024100568, -0.029533342, -0.0075797006, -0.05828122, 0.024781302, -0.018458338, -0.01373248, 0.017123055, -0.03581703, 0.011369551, 0.0011258277, 0.0062345983, -0.028643152, 0.009104804, 0.039770517, 0.017659785, -9.1228046E-4, -0.010217541, 6.7950576E-4, -0.022097642, 0.014177575, 0.024807483, 0.013850299, -0.009117896, 0.045137838, -0.009517171, -0.019780532, -0.008875712, -0.023419835, 0.062313255, -0.01629832, -0.009346988, -0.044090554, 0.011945556, -0.015002308, -0.0013581933, 0.0090524405, 0.031601723, 0.004051671, 0.022935467, -0.021443091, 0.029638069, -0.019950714, -0.016363775, 0.02194055, -0.071215145, -0.0018981979, -0.005792776, -0.0066895112, -0.046525486, -0.012096102, -0.010852455, 0.05186662, 0.03992761, -0.03916833, -3.5059388E-4, -0.028512241, 0.05042661, 0.0042022173, 0.015447403, 0.017502693, 0.026993683, 0.019034343, 0.0059498684, -0.001556195, -0.015041582, -0.02825042, -0.061056517, 4.1584444E-4, -0.019872168, 0.007442245, -0.013005928, 0.027700597, 0.041629445, -0.0096219, 0.03901124, -0.0354243, 0.014360849, 8.1368873E-4, 0.006905513, 0.03403665, -0.016507776, 0.024218386, 0.009706992, -0.050976433, 0.0027851146, 0.002812933, -0.02489912, 0.016232865, 0.0028063874, 0.011500461, -0.030763896, 0.011703372, 0.0053869546, -0.01925689, 0.0088888025, -0.021796549, 0.0014743761, 0.008738256, -0.03243955, 0.038094867, -0.033277374, 0.009929539, 0.048227318, -0.01539504, -0.006077506, -0.06713075, 0.0029438431, 0.03416756, -0.031758815, 0.022647465, -0.01225974, 0.017541967, -0.014190665, 0.0038945784, -0.011801555, 0.025985675, 0.0011871919, -0.01772524, 0.029035883, 0.0160365, -0.01347066, -0.026666408, -0.007920067, -0.018890342, -0.01809179, 0.009307715, -0.008319343, 0.03555521, -0.020984905, 0.0018245609, 0.05362082, -0.0026640226, -0.008286616, 0.047258582, -0.029611887, -0.030397348, -0.007455336, 0.031863544, 0.004647312, 0.024794392, -0.029062064, 0.008581163, 0.03851378, 0.025894037, -0.029062064, 0.026090402, -0.019112889, -0.0020340171, 0.017620513, 0.020317262, 0.013431386, -0.027255503, 9.605536E-4, -0.036550127, -0.0044018556, 0.033225007, 0.0022925648, -0.035502847, -0.047834586, 6.7991484E-4, -0.011055366, 0.017175417, 0.04505929, -0.035241026, -0.009556444, 0.023197288, 0.008122978, -0.012979746, -0.020133989, -0.031287536, -0.0076255193, 0.03670722, 0.027700597, 0.008535345, -0.0041334894, 0.0025822036, -0.006931695, 0.020984905, 0.018759431, 0.0016412867, 0.004608039, 0.0011691917, -0.018772522, 0.012619743, -0.007690974, 0.015578314, -0.013693207, 0.001391739, 0.0056160474, -0.036654856, -0.0036785766, 0.027046047, 0.010512089, -0.0054458645, 0.021351453, -0.01809179, 0.012835745, -1.1996693E-4, 0.0026509315, -0.026352223, 0.013261203, 0.029559523, -0.019610347, 0.04516402, 0.0067418753, 0.0064866003, -0.04003234, -0.0017787423, -0.025396578, 0.0160365, 0.026326042, -0.020343445, 0.00820807, -0.06351763, 0.0051447707, 0.019322345, 0.009739718, 0.031156627, -0.016743414, 0.03864469, 0.0065520555, -0.030947171, 0.019715076, 0.012390651, 0.014216848, -0.014347758, -0.026640225, -0.013117202, 0.008430617, -0.03351301, 0.06582165, 0.007265516, 0.002873479, 0.008476435, -0.0015038309, -0.01926998, -0.032779913, 0.003272755, -0.026771136, -0.012246649, 0.0069251494, 0.022883102, -0.02914061, -0.021862004, 0.016612504, 0.012534652, 0.0013467387, -0.0043593096, 0.033931922, 0.008450253, -0.015591404, -0.016272137, -0.033408284, 0.024545662, -0.0016134682, 0.005753503, -0.008371707, 1.1526234E-4, 0.057024483, -0.0011061912, 0.0075077, 0.009988449, -0.047180034, -0.016115045, -0.004143308, 0.009464808, 4.8068588E-4, 0.023537654, 0.040084705, 0.03249191, -2.0659265E-4, 0.008273524, 0.019139072, -0.022686737, 0.017476512, 0.012266286, 0.019348528, 0.019950714, -0.0066829654, -0.002128927, 0.014544123, -0.02825042, 0.006905513, 0.0068596946, 0.016625596, -0.0033971197, -4.356855E-4, -0.025985675, -0.025422761, 0.032963187, -0.01940089, -0.02746496, -0.012115739, -0.034089018, 0.0026623863, -0.014465577, -0.015342675, -0.023210378, 0.035136297, -0.02269983, -0.036916677, 0.015827043, 0.031261355, -0.032151546, -0.010793546, 0.020683812, 8.71371E-4, 0.028067147, -0.02451948, 0.044483285, 0.026273677, -0.025880946, 0.0119979195, -0.015565223, -0.0903804, -0.04542584, -7.809612E-4, 0.012626288, 0.0062411437, -0.017869242, 0.031392265, 0.0043887645, 0.01835361, 0.03744032, 0.021377636, 0.033591557, -0.012096102, 0.0064538727, -0.012318649, -0.011513552, 0.010747727, -0.003966579, -0.005062952, -0.017136145, 0.0037963958, 0.0135622965, -0.04312182, 0.0045622205, -7.716542E-5, -0.048462957, -0.0027212957, -0.004686585, 0.011801555, 0.002423475, 9.9870165E-5, 0.014360849, 0.0058189584, 0.010040812, 0.016141228, -0.022150006, -0.005010588, -0.0035313026, -0.0010006449, -0.011317187, 0.0012829199, 0.007880794, -0.0011446461, -0.004539311, -0.026155857, -0.04558293, -0.025239486, 0.017384874, 0.023197288, -0.05503465, -0.04403819, -0.0023645654, 0.014033574, -0.007010241, 0.023969658, 0.023040196, 0.022006005, 0.02192746, -0.028721698, -5.6915254E-5, -0.057024483, 0.040503617, 0.006666602, -0.04390728, 0.027281685, -0.0044280374, 0.015735406, 0.04888187, 0.028145693, 0.030502077, -0.008253888, -0.0016347411, 0.011199367, -0.009765901, 0.03992761, -0.0014195575, -0.0044280374, 0.026862772, -0.03618358, -0.06660711, 0.0076844287, 0.014766671, 0.03686431, -0.030449713, 0.010433543, 0.014387031, -0.019701986, 0.017515784, 0.009045895, 0.0125673795, -0.004434583, -0.008777529, 0.0013663752, 0.0013827389, -0.0782843, 0.0032482094, 0.017437238, 0.004768404, 0.010570998, 0.026522405, 0.008509163, 0.032884642, -0.024231479, -0.004837132, -0.0124233775, -0.03377483, -0.036105033, -0.04890805, -0.028957335, 0.007213152, -0.019649621, -0.0114284605, 0.0074226083, 0.010734636, 0.003272755, -0.02772678, 0.015251039, 0.022438008, 0.020186353, 0.0032874823, -0.023210378, -0.026339132, 0.017044509, -0.0046309484, 0.01090482, 0.01759433, -0.018157244, -0.021115815, -0.013706298, -0.011912828, -0.021011086, -0.015997225, 0.040372707, -0.039665792, 0.022909286, -0.008973894, 0.009569536, 0.036262125, 0.041917447, 0.00673533, 0.034665022, -0.0050465884, -0.0024807483, 0.035607573, 0.028407514, 0.004702949, -0.016677959, -0.0039338516, 0.044195283, -0.017162327, 0.012371014, -0.018510703, -0.0038814873, -0.0041923993, -0.014858307, 0.0046276757, 0.026758045, -0.0022042005, -0.020971814, -0.012986292, 0.021456182, 0.023576926, 0.008810257, 0.0016347411, 0.015525949, 0.017371783, 0.014845217, -0.0049942243, -0.018968888, -0.008640073, -0.0036916677, -0.004702949, 0.041393805, -0.031418446, 0.011703372, 0.022123823, -0.012816109, 0.007278607, 0.021966731, -0.01848452, -0.024715846, 0.012227013, 0.026535498, -0.008240797, -0.004326582, 0.01631141, 0.006277144, -0.022136915, 0.063622355, 1.0086733E-5, 0.005557138, 0.001903107, -0.017332511, -0.023027103, 0.0022811103, 0.012312104, -0.047625132, -0.0064178724, 1.127055E-4, -0.016887415, 0.003724395, -0.0073309713, 0.020657629, 0.010492452, -0.006555328, -0.006267326, 0.01301902, -4.221854E-4, 0.016520867, -0.032937005, 0.0135622965, 0.01604959, -0.035843212, 0.0022123824, 0.016389957, -0.03670722, -0.010184813, -0.006427691, -0.024859848, -0.008437162, 0.044378556, 0.018327428, -0.0038127596, -0.028328966, -0.0060742334, -0.012776836, -0.03670722, 0.00744879, 0.02848606, 0.02180964] }, tokenUsage = TokenUsage { inputTokenCount = 5, outputTokenCount = null, totalTokenCount = 5 }, finishReason = null, metadata = {} }
1536
解释是,大模型将我们输入的句子转化成了具有1536个维度的多维向量。
向量数据的存储
那么这些转化好的向量数据应该存储在哪呢?langchain4j官方提供了20+数据库的支持:
向量数据库 | 元数据存储 | 按元素据筛选 | 删除嵌入 |
---|---|---|---|
In-memory | ✅ | ✅ | ✅ |
Astra DB | ✅ | ||
Azure AI Search | ✅ | ✅ | ✅ |
Azure CosmosDB Mongo vCore | ✅ | ||
Azure CosmosDB NoSQL | ✅ | ||
Cassandra | ✅ | ||
Chroma | ✅ | ✅ | ✅ |
ClickHouse | ✅ | ✅ | ✅ |
Coherence | ✅ | ✅ | ✅ |
Couchbase | ✅ | ✅ | |
DuckDB | ✅ | ✅ | ✅ |
Elasticsearch | ✅ | ✅ | ✅ |
Infinispan | ✅ | ||
Milvus | ✅ | ✅ | ✅ |
MongoDB Atlas | ✅ | ✅ | ✅ |
Neo4j | ✅ | ||
OpenSearch | ✅ | ||
Oracle | ✅ | ✅ | ✅ |
PGVector | ✅ | ✅ | ✅ |
Pinecone | ✅ | ✅ | ✅ |
Qdrant | ✅ | ✅ | ✅ |
Redis | ✅ | ||
Tablestore | ✅ | ✅ | ✅ |
Vearch | ✅ | ||
Vespa | |||
Weaviate | ✅ | ✅ |
通常情况下我们要存储的数据量是巨大的,所以一般是不会使用内存数据库来做的,但是下面的例子中我们采用了in-memory在内存中的方式,因为本文主要以教会使用为主,实际使用根据情况选择硬盘数据库,放在内存中极其容易OOM(out of memory)
In-Memory案例
这里我们将向量数据存储在jvm内存的方式去做演示案例,代码如下:
@Test
void test(){// 一、转换向量// 1.1创建基于内存的向量存储实例InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();// 1.2创建向量大模型实例QwenEmbeddingModel embeddingModel = QwenEmbeddingModel.builder().apiKey(System.getenv("OPENAI_API_KEY")).build();// 1.3 将供AI参考的文本内容转换为向量TextSegment thzc = TextSegment.from("""退货退款政策1. **商品退回**:如果顾客收到的商品与描述不符或存在质量问题,可以在收货后7天内申请退货。2. **退款处理时间**:商家将在收到退回商品后的7天内处理退款申请。3. **退款金额计算**:退款金额将基于商品的价值和退款的原因来确定。如果退款金额超过商品实际价值,则按照商品的实际价值进行退款。4. **退款方式**:支持多种退款方式,包括但不限于微信退款、银行转账等。若未指定退款方式,默认为原支付路径退还。5. **运费处理**:- 如果商品有运费险,则全额退还;- 若无运费险,则按商品价值的5%计算退货运费,并从退款总额中扣除这部分费用。""");Embedding content = embeddingModel.embed(thzc).content();// 1.5 将供AI参考的向量存储到向量存储中embeddingStore.add(content,thzc);// 1.6 将用户的问题转换为向量String question = "商品购买价格是100RMB,这个商品有运费险,根据退货退款政策那么退多少钱呢?";Embedding userQuestion = embeddingModel.embed(question).content();// 1.7 根据用户问题从向量存储中查询EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder().queryEmbedding(userQuestion).maxResults(1) // 最大的返回条数.build();// 1.8 获取查询结果String thzcContent = embeddingStore.search(embeddingSearchRequest).matches().get(0).embedded().text();// // 也可以打印出来看看结果,查询结果是以评分算计算匹配机制的// EmbeddingSearchResult<TextSegment> search = embeddingStore.search(embeddingSearchRequest);// search.matches().forEach(embeddingMatch -> {// System.out.println(embeddingMatch.score()); // 相似度的得分// System.out.println(embeddingMatch.embedded().text()); // 检索到的文本// });// 二、根据检索到的文本内容进行回答// 2.1 创建大模型实例QwenChatModel model = QwenChatModel.builder().apiKey(System.getenv("OPENAI_API_KEY")).modelName("qwen-max").build();// 2.2 生成回答String res = model.chat(thzcContent+"\n根据上面的政策计算:"+question);System.out.println(res);
}
可以看到:
我如果将问题换成没有运费险,ai的回答:
ok,可以看到回答没有丝毫问题。
总结就是,我们设定好告诉ai某些场景的处理规则,把这些规则存储到向量数据库中,让ai去替我们处理就好了。
潜在问题
如果我们想做一个超大型的知识库,直接将大段文本丢给解析器是否有点不显示。首先肯定有分段表述,然后每一段或者每一部分的词义也是不同的。
还有一个问题是大语言模型对token的数量都有限制,不可能让你无限制的传大段文本上去。
这个时候我们就需要使用分词器将大段文本切分成一段一段的,方便做向量存储。
文本分词
关于分词器,LangChain4j提供了多种分词器以供选择:
分词器类型 | 匹配能力 | 适用场景 |
---|---|---|
DocumentByCharacterSplitter | 无符号分割 | 根据文本字数进行分割 |
DocumentByRegexSplitter | 正则表达式分割 | 根据自定义正则表达式分割 |
DocumentByParagraphSplitter | 删除大段空白内容 | 处理连续换行符(如段落分割) |
DocumentByLineSplitter | 删除单个换行符周围的空白 | 其实就是删除了首行缩进 |
DocumentByWordSplitter | 删除连续的空白字符 | 将词语中间的多个空格替换成一个 |
DocumentBySentenceSplitter | 按句子分割 | Apahce OpenNLP库中的一个类,用于检测文本中的句子边界。它能够识别标点符号,从而将一个较长的文本分割成多个句子 |
至于分词器该如何使用,这里不赘述。但是在正常开发中,可能会使用一些自定义的分词器,还可能会进行人工的干预。
内容检索器绑定
在上面的代码中我们从是通过根据需求检索向量数据拿到匹配结果后,再将我们的要求跟它拼接成一个字符串直接问AI的,但是其实不需要我们这样去做,因为langchain4j通过动态代理实现了将检索到的内容自动带入的功能,我们只需要去配置内容检索器并给到大模型。
改造上面的代码,修改后的代码如下:
@Testvoid test2(){interface MyAI{String chat(@UserMessage String message);TokenStream stream(@UserMessage String message);}// 1.先创建一个大向量模型实例QwenEmbeddingModel embeddingModel = QwenEmbeddingModel.builder().apiKey(System.getenv("OPENAI_API_KEY")).build();// 2.创建一个基于内存的向量存储实例InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();// 3.将供AI参考的文本内容转换为向量TextSegment thzc = TextSegment.from("""退货退款政策1. **商品退回**:如果顾客收到的商品与描述不符或存在质量问题,可以在收货后7天内申请退货。2. **退款处理时间**:商家将在收到退回商品后的7天内处理退款申请。3. **退款金额计算**:退款金额将基于商品的价值和退款的原因来确定。如果退款金额超过商品实际价值,则按照商品的实际价值进行退款。4. **退款方式**:支持多种退款方式,包括但不限于微信退款、银行转账等。若未指定退款方式,默认为原支付路径退还。5. **运费处理**:- 如果商品有运费险,则全额退还,不扣除运费;- 若无运费险,则按商品价值的5%计算退货运费,并从退款总额中扣除这部分费用。""");Embedding content = embeddingModel.embed(thzc).content();// 4.将供AI参考的向量存储到向量存储中embeddingStore.add(content,thzc);// 5.创建一个检索器实例EmbeddingStoreContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder().embeddingModel(embeddingModel).embeddingStore(embeddingStore).maxResults(1).minScore(0.5).build();// 6.创建一个大模型QwenChatModel model = QwenChatModel.builder().apiKey(System.getenv("OPENAI_API_KEY")).modelName("qwen-max").build();// 7.创建Assistant动态代理MyAI assistant = AiServices.builder(MyAI.class).chatLanguageModel(model).contentRetriever(contentRetriever).build();// 8.调用方法String chat = assistant.chat("商品购买价格是100RMB,这个商品有运费险,根据退货退款政策那么退多少钱呢?");System.out.println(chat);}
与springboot做整合的话,只需要将对应需要的参数配置成bean,让它自动注入到我们的方法中即可。
多个大模型协同工作
在一个应用中,可能需要多个模型共同协作完成一个任务。
为什么需要这样:
您的LLM可能不需要始终了解您拥有的每个tools。例如,当用户只是向LLM(大模型)打招呼或说再见时,让LLM访问数十或数百个toos的成本很高,有时甚至很危险(LLM调用中包含的每个toos都会消耗大量tokn),并且可能会导致意想不到的结果(LLM可能会产生幻觉或被操纵以使用非预期的输入来调用tools)
关于RAG同样,有时需要为LLM提供一些上下文,但并非总是如此,因为它会产生额外的成本(更多上下文就需要更多令牌)并增加响应时间(更多的令牌会导致更高的延迟)关于模型参数:在某些情况下,您可能想不通的对话使用不同的LLM,以利用不同LM的最佳特性。
我们可以一个接一个的调用AI的服务,也就是链式调用。这样其实也是解耦的思想,让AI尽量各司其职,流水线的思想。
这个就不写实现了,简单讲就是根据上一个大模型的返回结果去决定要调用哪个大模型去实现特定的功能,这样一步一步下去形成的大模型链就能够实现非常复杂的功能。