From 46f1cb0d4f12d734610532695248947d5df16b1f Mon Sep 17 00:00:00 2001 From: 8ga Date: Mon, 17 Mar 2025 11:04:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20SpringAI/0=5F=E4=BD=BF?= =?UTF-8?q?=E7=94=A8SpringAI=E6=8E=A5=E5=85=A5AI=E6=A8=A1=E5=9E=8B.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SpringAI/0_使用SpringAI接入AI模型.md | 213 +++++++++++++++++++++++++-- 1 file changed, 202 insertions(+), 11 deletions(-) diff --git a/SpringAI/0_使用SpringAI接入AI模型.md b/SpringAI/0_使用SpringAI接入AI模型.md index cb02d52..5de83fb 100644 --- a/SpringAI/0_使用SpringAI接入AI模型.md +++ b/SpringAI/0_使用SpringAI接入AI模型.md @@ -120,7 +120,7 @@ Spring AI 支持 spring boot 3.2.x 及更高版本,对JDK的最低要求是JDK ``` -以千问举例,引入阿里灵积(DashScope)平台的starter,这官方适配Spring AI的依赖包,版本和 Spring AI 的版本是一致的。 +以千问举例,引入阿里的starter,这官方适配Spring AI的依赖包,版本和 Spring AI 的版本是一致的。 ``` @@ -130,27 +130,36 @@ Spring AI 支持 spring boot 3.2.x 及更高版本,对JDK的最低要求是JDK ``` -申请灵积平台的 API Key,https://dashscope.console.aliyun.com/apiKey,添加相关配置: +申请 API Key,https://bailian.console.aliyun.com/?apiKey=1#/api-key,添加相关配置: + +```yml +spring: + ai: + dash-scope: # 这是阿里的配置根路径 + api-key: xxx +``` + +指定聊天模型 ```yml spring: ai: dash-scope: - api-key: xxx chat: options: - model: qwen-max # 使用的模型 + model: qwen-max # 阿里云的文档中有提供模型名称 ``` ### 使用ChatClient发送消息示例 -1. 引入ChatModel,可以切换不同的AI模型 +1. 引入ChatModel ```java +// 如果需要根据名称注入,则可以指定为:dashScopeChatModel private final ChatModel chatModel; ``` -2. 同步返回答案 +2. 阻塞式传输 ```java @GetMapping("chat") @@ -163,7 +172,7 @@ public String chat(@RequestParam String prompt) {// 用户输入的prompt } ``` -3. 流式返回答案 +3. 流式传输 ```java @GetMapping(value = "chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE) @@ -189,14 +198,196 @@ ChatMermory仅仅是一个存储和获取历史对话消息的接口,而Messag ```java // 这里用InMemoryChatMemory做示例 private static final ChatMemory chatMemory = new InMemoryChatMemory(); -// 从历史记录里取6条对话消息一起发送至模型的API。 -// 历史消息也是算在这一次对话Token消耗的,要关注Token膨胀的问题 -var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, sessionId, 6); + ChatClient.create(chatModel) .prompt() .user(prompt) - .advisors(messageChatMemoryAdvisor) + // 从历史记录里取6条对话消息一起发送至模型的API。 + // 历史消息也是算在这一次对话Token消耗的,要关注Token膨胀 + .advisors(new MessageChatMemoryAdvisor(chatMemory, sessionId, 6)) .stream() .content() .map(chatResponse -> ServerSentEvent.builder(chatResponse).event("message").build()); +``` + +### ETL + +ETL的全称是Extract, Transform, Load,即抽取、转换、加载。我们可以利用ETL框架 [Apache Tika](https://tika.apache.org/2.9.0/formats.html),将文档(.pdf .xlsx .docx .pptx .md .json等)导入至向量数据库,让AI模型能够从向量数据库中检索并生成答案。 + +**DocumentReader**负责读取文档: + +- JsonReader:读取JSON +- TextReader:读取text文档 +- PagePdfDocumentReader:读取PDF +- TikaDocumentReader:读取各种文件(.pdf .xlsx .docx .pptx .md .json)都支持 + +**DocumentTransformer**用于处理文档: + +- TextSplitter:文档切割成小块 +- ContentFormatTransformer:将文档转换成键值对 +- SummaryMetadataEnricher:使用大模型总结文档 +- KeywordMetadataEnricher:使用大模型提取文档关键词 + +**DocumentWriter**负责文档写入: + +- VectorStore:写入到向量数据库 +- FileDocumentWriter:写入到文件 + + + +引入相关依赖 + +```xml + + org.springframework.ai + spring-ai-tika-document-reader + +``` + +Document对象是ETL的核心,它包含了文档的元数据和内容。 + +#### 读取 + +1. 从输入流读取 + +```java +// 适合前端上传 MultipartFile 的场景 +Resource resource = new InputStreamResource(file.getInputStream()); +List documents = new TikaDocumentReader(resource).read(); +``` + +2. 从本地文件读取 + +```java +Resource resource = new FileSystemResource("D:\\xxx.pdf"); +List documents = new TikaDocumentReader(resource).read(); +``` + +3. 从URL读取 + +```java +Resource resource = new UrlResource("http://oss.com/xxx.pdf"); +List documents = new TikaDocumentReader(resource).read(); +``` + +#### 转换 + +内容转换: + +- TokenTextSplitter 可以把内容切割成更小的块,在RAG的时候可以提升响应速度、节省Token。 +- ContentFormatTransformer 可以把元数据的内容变成字符串键值对。 + +元数据转换: + +- SummaryMetadataEnricher 使用大模型总结文档,在元数据里增加一个**summary**字段。 +- KeywordMetadataEnricher 使用大模型提取文档关键词,在元数据里面增加一个**keywords**字段。 + +```java +List documents = new TikaDocumentReader(resource).read(); +// 这里示例用 TokenTextSplitter 分块 +List splitDocuments = new TokenTextSplitter().apply(documents); +``` + +#### 存储 + +这里需要引入一个新的组件**向量数据库**(VectorStore),这是AI记忆的核心组件。前面提到的**ChatMemory**属于短期记忆的组件,一般只在聊天对话的上下文中生效。而**VectorStore**是持久化存储的,也就是大家常说的AI知识库。 + +什么是向量?我这里贴一段通义千问的回答: + +> 向量在向量数据库中指的是数学意义上的向量,即一维数组,它可以包含实数或复数。但在计算机科学和信息技术领域,特别是在机器学习、人工智能以及数据检索的上下文中,向量通常是指特征向量。这些向量用来表示数据点或对象的特征,每个元素代表一个特定的特征值。 + +例如,在文本处理中,文档可以用词频-逆文档频率(TF-IDF)向量或者词嵌入(如Word2Vec或GloVe模型生成的向量)来表示;在图像识别中,图像可以转换为一个描述其视觉特征的向量;在推荐系统中,用户偏好和物品属性也可以被编码成向量形式。 + +向量数据库就是专门设计用来存储、索引和查询这些高维度向量数据的数据库系统。它们优化了相似度搜索(比如通过计算向量之间的距离,如欧氏距离、余弦相似度等),使得能够快速找到与给定向量最接近的数据点。这种能力对于实现诸如图像搜索、语音识别、自然语言处理等任务非常有用。 + +还记得前面提到的**Embedding**(嵌入模型)吗?这个组件就是SpringAI框架中用于把文档、音视频转换成向量的。向量化以后,可以使用向量存储数据库进行存储,下面用**RedisStack**来进行示例。 + +##### 引入redis相关依赖 + +```xml + + org.springframework.ai + spring-ai-redis-store + + + redis.clients + jedis + +``` + +##### 配置连接参数 + +```yml +spring: + data: + redis: + host: 地址 + port: 端口 + password: 密码 + repositories: + enabled: false +``` + +如果项目本身也用到了redis做为缓存或者分布式锁,可能会导致配置冲突,可以排除RedisVectorStoreAutoConfiguration,手动配置来规避。 + +```java +@Configuration +@EnableAutoConfiguration(exclude = {RedisVectorStoreAutoConfiguration.class}) +@EnableConfigurationProperties({RedisVectorStoreProperties.class}) +@AllArgsConstructor +public class RedisVectorConfig { + @Bean + public VectorStore vectorStore(EmbeddingModel embeddingModel, + RedisVectorStoreProperties properties, + RedisConnectionDetails redisConnectionDetails) { + RedisVectorStore.RedisVectorStoreConfig config = + RedisVectorStore.RedisVectorStoreConfig.builder().withIndexName(properties.getIndex()).withPrefix(properties.getPrefix()).build(); + return new RedisVectorStore(config, embeddingModel, + new JedisPooled(redisConnectionDetails.getStandalone().getHost(), + redisConnectionDetails.getStandalone().getPort() + , redisConnectionDetails.getUsername(), + redisConnectionDetails.getPassword()), + properties.isInitializeSchema()); + } +} +``` + +##### 声明Embedding的模型 + +```yml +dash-scope: + embedding: + options: + model: text-embedding-v2 +``` + +##### 使用示例 + +注入模型: + +```java +private final EmbeddingModel embeddingModel; +``` + +向量化: + +``` +public void embedding() { + float[] embed = embeddingModel.embed("Hello World"); +} +``` + +向量化存储文档 + +注入VectorStore组件: + +```java +private final VectorStore vectorStore; +``` +**vectorStore.add**会自动调用embeddingModel完成向量化并存储 + +```java +List documents = new TikaDocumentReader(resource).read(); +List splitDocuments = new TokenTextSplitter().apply(documents); +vectorStore.add(splitDocuments); ``` \ No newline at end of file