feat(ai): 支持图片上传与COS存储
- 新增图片上传功能,支持PNG、JPEG等常见格式 - 集成腾讯云COS对象存储服务,实现文件云端存储 -优化文档上传逻辑,图片文件不再进行向量化处理 - 升级DashScope模型配置,启用多模态支持 - 移除废弃的SaveDocumentsTools工具类 - 添加hutool和腾讯云COS SDK依赖 - 调整文件上传大小限制,支持更大文件上传 -修复部分空指针异常问题,增强代码健壮性
This commit is contained in:
13
pom.xml
13
pom.xml
@@ -113,7 +113,18 @@
|
|||||||
<groupId>org.springframework.ai</groupId>
|
<groupId>org.springframework.ai</groupId>
|
||||||
<artifactId>spring-ai-tika-document-reader</artifactId>
|
<artifactId>spring-ai-tika-document-reader</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!--hutool工具类-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-all</artifactId>
|
||||||
|
<version>5.8.40</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- 腾讯云 OSS -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.qcloud</groupId>
|
||||||
|
<artifactId>cos_api</artifactId>
|
||||||
|
<version>5.6.227</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.hanserwei.chat.config;
|
|||||||
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
|
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
|
||||||
import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
|
import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
|
||||||
import com.hanserwei.chat.tools.AiDBTools;
|
import com.hanserwei.chat.tools.AiDBTools;
|
||||||
import com.hanserwei.chat.tools.SaveDocumentsTools;
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.ai.chat.client.ChatClient;
|
import org.springframework.ai.chat.client.ChatClient;
|
||||||
import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor;
|
import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor;
|
||||||
@@ -14,7 +13,6 @@ import org.springframework.ai.chat.memory.MessageWindowChatMemory;
|
|||||||
import org.springframework.ai.chat.memory.repository.cassandra.CassandraChatMemoryRepository;
|
import org.springframework.ai.chat.memory.repository.cassandra.CassandraChatMemoryRepository;
|
||||||
import org.springframework.ai.vectorstore.SimpleVectorStore;
|
import org.springframework.ai.vectorstore.SimpleVectorStore;
|
||||||
import org.springframework.ai.vectorstore.VectorStore;
|
import org.springframework.ai.vectorstore.VectorStore;
|
||||||
import org.springframework.aot.hint.annotation.RegisterReflection;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@@ -52,14 +50,13 @@ public class ChatClientConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public ChatClient dashScopeChatClient(ChatMemory chatMemory,
|
public ChatClient dashScopeChatClient(ChatMemory chatMemory
|
||||||
SaveDocumentsTools saveDocumentsTools
|
|
||||||
) {
|
) {
|
||||||
PromptChatMemoryAdvisor chatMemoryAdvisor = PromptChatMemoryAdvisor.builder(chatMemory).build();
|
PromptChatMemoryAdvisor chatMemoryAdvisor = PromptChatMemoryAdvisor.builder(chatMemory).build();
|
||||||
SimpleLoggerAdvisor simpleLoggerAdvisor = new SimpleLoggerAdvisor();
|
SimpleLoggerAdvisor simpleLoggerAdvisor = new SimpleLoggerAdvisor();
|
||||||
QuestionAnswerAdvisor questionAnswerAdvisor = new QuestionAnswerAdvisor(vectorStore);
|
QuestionAnswerAdvisor questionAnswerAdvisor = new QuestionAnswerAdvisor(vectorStore);
|
||||||
return ChatClient.builder(dashScopeChatModel)
|
return ChatClient.builder(dashScopeChatModel)
|
||||||
.defaultTools(aiDBTools, saveDocumentsTools)
|
.defaultTools(aiDBTools)
|
||||||
.defaultSystem(aiAssistantResource)
|
.defaultSystem(aiAssistantResource)
|
||||||
.defaultAdvisors(chatMemoryAdvisor,simpleLoggerAdvisor,questionAnswerAdvisor)
|
.defaultAdvisors(chatMemoryAdvisor,simpleLoggerAdvisor,questionAnswerAdvisor)
|
||||||
.build();
|
.build();
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.hanserwei.chat.config.cos;
|
||||||
|
|
||||||
|
import com.qcloud.cos.COSClient;
|
||||||
|
import com.qcloud.cos.ClientConfig;
|
||||||
|
import com.qcloud.cos.auth.BasicCOSCredentials;
|
||||||
|
import com.qcloud.cos.auth.COSCredentials;
|
||||||
|
import com.qcloud.cos.endpoint.EndpointBuilder;
|
||||||
|
import com.qcloud.cos.region.Region;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class CosConfig {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CosProperties cosProperties;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public COSClient cosClient() {
|
||||||
|
// 1. 初始化用户身份信息(SecretId, SecretKey)
|
||||||
|
COSCredentials cred = new BasicCOSCredentials(
|
||||||
|
cosProperties.getSecretId(),
|
||||||
|
cosProperties.getSecretKey()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. 设置 bucket 的地域
|
||||||
|
Region region = new Region(cosProperties.getRegion());
|
||||||
|
ClientConfig clientConfig = new ClientConfig(region);
|
||||||
|
if (cosProperties.getEndpoint() != null && !cosProperties.getEndpoint().isEmpty()) {
|
||||||
|
clientConfig.setEndpointBuilder(new EndpointBuilder() {
|
||||||
|
@Override
|
||||||
|
public String buildGeneralApiEndpoint(String bucketName) {
|
||||||
|
// 所有 API 请求都会使用自定义域名
|
||||||
|
return cosProperties.getEndpoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String buildGetServiceApiEndpoint() {
|
||||||
|
return cosProperties.getEndpoint();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 构建 COSClient
|
||||||
|
return new COSClient(cred, clientConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.hanserwei.chat.config.cos;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Data
|
||||||
|
@ConfigurationProperties(prefix = "storage.cos")
|
||||||
|
public class CosProperties {
|
||||||
|
private String endpoint;
|
||||||
|
private String secretId;
|
||||||
|
private String secretKey;
|
||||||
|
private String appId;
|
||||||
|
private String region;
|
||||||
|
}
|
||||||
@@ -7,10 +7,21 @@ import jakarta.annotation.Resource;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.ai.chat.client.ChatClient;
|
import org.springframework.ai.chat.client.ChatClient;
|
||||||
import org.springframework.ai.chat.memory.ChatMemory;
|
import org.springframework.ai.chat.memory.ChatMemory;
|
||||||
|
import org.springframework.ai.chat.messages.UserMessage;
|
||||||
|
import org.springframework.ai.chat.prompt.Prompt;
|
||||||
|
import org.springframework.ai.content.Media;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.util.MimeTypeUtils;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/ai")
|
@RequestMapping("/ai")
|
||||||
@@ -23,9 +34,20 @@ public class AiChatController {
|
|||||||
public Flux<AIResponse> chatWithAi(@RequestBody ChatMessageDTO chatMessageDTO) {
|
public Flux<AIResponse> chatWithAi(@RequestBody ChatMessageDTO chatMessageDTO) {
|
||||||
log.info("会话ID:{}", chatMessageDTO.getConversionId());
|
log.info("会话ID:{}", chatMessageDTO.getConversionId());
|
||||||
ConversationContext.setConversationId(chatMessageDTO.getConversionId());
|
ConversationContext.setConversationId(chatMessageDTO.getConversionId());
|
||||||
|
Media media;
|
||||||
return dashScopeChatClient.prompt()
|
UserMessage userMessage;
|
||||||
.user(chatMessageDTO.getMessage())
|
if (Objects.nonNull(chatMessageDTO.getImage())) {
|
||||||
|
media = new Media(MimeTypeUtils.IMAGE_PNG, URI.create(chatMessageDTO.getImage()));
|
||||||
|
userMessage = UserMessage.builder()
|
||||||
|
.media(media)
|
||||||
|
.text(chatMessageDTO.getMessage())
|
||||||
|
.build();
|
||||||
|
} else {
|
||||||
|
userMessage = UserMessage.builder()
|
||||||
|
.text(chatMessageDTO.getMessage())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
return dashScopeChatClient.prompt(new Prompt(List.of(userMessage)))
|
||||||
.advisors(p -> p.param(ChatMemory.CONVERSATION_ID, chatMessageDTO.getConversionId()))
|
.advisors(p -> p.param(ChatMemory.CONVERSATION_ID, chatMessageDTO.getConversionId()))
|
||||||
.stream()
|
.stream()
|
||||||
.chatResponse()
|
.chatResponse()
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ public class DocumentController {
|
|||||||
|
|
||||||
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
public ResponseEntity<DocumentUploadResponse> upload(@RequestParam("file") MultipartFile file) {
|
public ResponseEntity<DocumentUploadResponse> upload(@RequestParam("file") MultipartFile file) {
|
||||||
int documentCount = documentIngestionService.ingest(file);
|
String result = documentIngestionService.ingest(file);
|
||||||
log.info("文件 {} 上传成功。", file.getOriginalFilename());
|
log.info("文件 {} 上传成功。", file.getOriginalFilename());
|
||||||
return ResponseEntity.ok(new DocumentUploadResponse(file.getOriginalFilename(), documentCount, "ok"));
|
return ResponseEntity.ok(new DocumentUploadResponse(file.getOriginalFilename(), result));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import lombok.Builder;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@@ -13,5 +15,7 @@ public class ChatMessageDTO {
|
|||||||
|
|
||||||
private String message;
|
private String message;
|
||||||
|
|
||||||
|
private String image;
|
||||||
|
|
||||||
private Long conversionId;
|
private Long conversionId;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.hanserwei.chat.model.vo;
|
package com.hanserwei.chat.model.vo;
|
||||||
|
|
||||||
public record DocumentUploadResponse(String filename, int documentCount, String message) {
|
public record DocumentUploadResponse(String filename, String message) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import org.springframework.stereotype.Component;
|
|||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MyHtmlReader implements DocumentParser {
|
public class MyHtmlReader implements DocumentParser {
|
||||||
@@ -19,7 +20,7 @@ public class MyHtmlReader implements DocumentParser {
|
|||||||
.charset("UTF-8") // 使用 UTF-8 编码
|
.charset("UTF-8") // 使用 UTF-8 编码
|
||||||
.includeLinkUrls(true) // 在元数据中包含链接 URL(绝对链接)
|
.includeLinkUrls(true) // 在元数据中包含链接 URL(绝对链接)
|
||||||
.metadataTags(List.of("author", "date")) // 提取 author 和 date 元标签
|
.metadataTags(List.of("author", "date")) // 提取 author 和 date 元标签
|
||||||
.additionalMetadata("source", file.getOriginalFilename()) // 添加自定义元数据
|
.additionalMetadata("source", Objects.requireNonNull(file.getOriginalFilename())) // 添加自定义元数据
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// 新建 JsoupDocumentReader 阅读器
|
// 新建 JsoupDocumentReader 阅读器
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import org.springframework.stereotype.Component;
|
|||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MyMarkdownReader implements DocumentParser {
|
public class MyMarkdownReader implements DocumentParser {
|
||||||
@@ -18,7 +19,7 @@ public class MyMarkdownReader implements DocumentParser {
|
|||||||
.withHorizontalRuleCreateDocument(true) // 遇到水平线 ---,则创建新文档
|
.withHorizontalRuleCreateDocument(true) // 遇到水平线 ---,则创建新文档
|
||||||
.withIncludeCodeBlock(false) // 排除代码块(代码块生成单独文档)
|
.withIncludeCodeBlock(false) // 排除代码块(代码块生成单独文档)
|
||||||
.withIncludeBlockquote(false) // 排除块引用(块引用生成单独文档)
|
.withIncludeBlockquote(false) // 排除块引用(块引用生成单独文档)
|
||||||
.withAdditionalMetadata("filename", file.getOriginalFilename()) // 添加自定义元数据,如文件名称
|
.withAdditionalMetadata("filename", Objects.requireNonNull(file.getOriginalFilename())) // 添加自定义元数据,如文件名称
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// 新建 MarkdownDocumentReader 阅读器
|
// 新建 MarkdownDocumentReader 阅读器
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
public interface DocumentIngestionService {
|
public interface DocumentIngestionService {
|
||||||
|
|
||||||
int ingest(MultipartFile file);
|
String ingest(MultipartFile file);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,111 @@
|
|||||||
package com.hanserwei.chat.service.impl;
|
package com.hanserwei.chat.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import com.hanserwei.chat.config.cos.CosProperties;
|
||||||
import com.hanserwei.chat.reader.DocumentParser;
|
import com.hanserwei.chat.reader.DocumentParser;
|
||||||
import com.hanserwei.chat.service.DocumentIngestionService;
|
import com.hanserwei.chat.service.DocumentIngestionService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import com.qcloud.cos.COSClient;
|
||||||
|
import com.qcloud.cos.model.ObjectMetadata;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.ai.document.Document;
|
import org.springframework.ai.document.Document;
|
||||||
import org.springframework.ai.vectorstore.VectorStore;
|
import org.springframework.ai.vectorstore.VectorStore;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class DocumentIngestionServiceImpl implements DocumentIngestionService {
|
public class DocumentIngestionServiceImpl implements DocumentIngestionService {
|
||||||
|
|
||||||
private final List<DocumentParser> documentParsers;
|
@Resource
|
||||||
private final VectorStore vectorStore;
|
private List<DocumentParser> documentParsers;
|
||||||
|
@Resource
|
||||||
|
private VectorStore vectorStore;
|
||||||
|
@Resource
|
||||||
|
private COSClient cosClient;
|
||||||
|
@Resource
|
||||||
|
private CosProperties cosProperties;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int ingest(MultipartFile file) {
|
public String ingest(MultipartFile file) {
|
||||||
if (file == null || file.isEmpty()) {
|
if (file == null || file.isEmpty()) {
|
||||||
throw new IllegalArgumentException("上传文件不能为空");
|
throw new IllegalArgumentException("上传文件不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果是图片,则不进行向量化,选择上传到图片存储桶
|
||||||
|
String originalFilename = file.getOriginalFilename();
|
||||||
|
String extName = Objects.requireNonNull(FileUtil.extName(originalFilename)).toLowerCase();
|
||||||
|
|
||||||
|
// 判断是否为图片类型
|
||||||
|
boolean isImage = extName.matches("jpg|jpeg|png|gif|bmp|webp");
|
||||||
|
if (isImage) {
|
||||||
|
log.info("上传文件 {} 为图片,不进行向量化。", originalFilename);
|
||||||
|
return uploadFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
DocumentParser parser = documentParsers.stream()
|
DocumentParser parser = documentParsers.stream()
|
||||||
.filter(candidate -> candidate.supports(file.getOriginalFilename(), file.getContentType()))
|
.filter(candidate -> candidate.supports(originalFilename, file.getContentType()))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow(() -> new IllegalArgumentException("不支持的文件类型:" + file.getOriginalFilename()));
|
.orElseThrow(() -> new IllegalArgumentException("不支持的文件类型:" + originalFilename));
|
||||||
|
|
||||||
List<Document> documents = parser.parse(file);
|
List<Document> documents = parser.parse(file);
|
||||||
if (documents.isEmpty()) {
|
if (documents.isEmpty()) {
|
||||||
log.warn("文件 {} 解析后未生成任何文档,跳过入库。", file.getOriginalFilename());
|
log.warn("文件 {} 解析后未生成任何文档,跳过入库。", originalFilename);
|
||||||
return 0;
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
vectorStore.add(documents);
|
vectorStore.add(documents);
|
||||||
log.info("文件 {} 入库成功,共写入 {} 条向量。", file.getOriginalFilename(), documents.size());
|
log.info("文件 {} 入库成功,共写入 {} 条向量。", originalFilename, documents.size());
|
||||||
return documents.size();
|
return documents.size() + "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public String uploadFile(MultipartFile file) {
|
||||||
|
log.info("## 上传文件至腾讯云Cos ...");
|
||||||
|
|
||||||
|
// 判断文件是否为空
|
||||||
|
if (file == null || file.getSize() == 0) {
|
||||||
|
log.error("==> 上传文件异常:文件大小为空 ...");
|
||||||
|
throw new RuntimeException("文件大小不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件的原始名称
|
||||||
|
String originalFileName = file.getOriginalFilename();
|
||||||
|
|
||||||
|
// 生成存储对象的名称(将 UUID 字符串中的 - 替换成空字符串)
|
||||||
|
String key = UUID.randomUUID().toString().replace("-", "");
|
||||||
|
// 获取文件的后缀,如 .jpg
|
||||||
|
String suffix = null;
|
||||||
|
if (originalFileName != null) {
|
||||||
|
suffix = originalFileName.substring(originalFileName.lastIndexOf("."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拼接上文件后缀,即为要存储的文件名
|
||||||
|
String objectName = String.format("%s%s", key, suffix);
|
||||||
|
|
||||||
|
log.info("==> 开始上传文件至腾讯云Cos, ObjectName: {}", objectName);
|
||||||
|
|
||||||
|
// 设置元数据
|
||||||
|
ObjectMetadata metadata = new ObjectMetadata();
|
||||||
|
metadata.setContentLength(file.getSize());
|
||||||
|
metadata.setContentType(file.getContentType());
|
||||||
|
|
||||||
|
// 执行上传
|
||||||
|
try (InputStream inputStream = file.getInputStream()) {
|
||||||
|
cosClient.putObject("snails-ai-1308845726", objectName, inputStream, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回文件的访问链接
|
||||||
|
String url = String.format("https://%s/%s", cosProperties.getEndpoint(), objectName);
|
||||||
|
log.info("==> 上传文件至腾讯云 Cos 成功,访问路径: {}", url);
|
||||||
|
return url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
package com.hanserwei.chat.tools;
|
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.ai.document.Document;
|
|
||||||
import org.springframework.ai.tool.annotation.Tool;
|
|
||||||
import org.springframework.ai.tool.annotation.ToolParam;
|
|
||||||
import org.springframework.ai.vectorstore.SimpleVectorStore;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Component
|
|
||||||
public class SaveDocumentsTools {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private SimpleVectorStore simpleVectorStore;
|
|
||||||
|
|
||||||
@Tool(name = "SaveDocuments",
|
|
||||||
description = "保存文档为本地的向量化知识库。")
|
|
||||||
public boolean saveDocuments(
|
|
||||||
@ToolParam(description = "文档内容") String documents
|
|
||||||
) {
|
|
||||||
simpleVectorStore.add(List.of(new Document(documents)));
|
|
||||||
//把内存中的向量数据,持久化到磁盘
|
|
||||||
File file = new File("/home/hanserwei/IdeaProjects/snails-ai-backend/snails-chat/src/main/resources/documents/vector.json");
|
|
||||||
simpleVectorStore.save(file);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,10 @@ server:
|
|||||||
charset: utf-8
|
charset: utf-8
|
||||||
force: true
|
force: true
|
||||||
spring:
|
spring:
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
max-file-size: 20MB
|
||||||
|
max-request-size: 100MB
|
||||||
application:
|
application:
|
||||||
name: snails-ai
|
name: snails-ai
|
||||||
banner:
|
banner:
|
||||||
@@ -60,8 +64,9 @@ spring:
|
|||||||
api-key: ENC(cMgcKZkFllyE88DIbGwLKot9Vg02co+gsmY8L8o4/o3UjhcmqO4lJzFU35Sx0n+qFG8pDL0wBjoWrT8X6BuRw9vNlQhY1LgRWHaF9S1zzyM=)
|
api-key: ENC(cMgcKZkFllyE88DIbGwLKot9Vg02co+gsmY8L8o4/o3UjhcmqO4lJzFU35Sx0n+qFG8pDL0wBjoWrT8X6BuRw9vNlQhY1LgRWHaF9S1zzyM=)
|
||||||
chat:
|
chat:
|
||||||
options:
|
options:
|
||||||
model: qwen-plus
|
model: qwen3-omni-flash
|
||||||
temperature: 0.5
|
temperature: 0.5
|
||||||
|
multi-model: true
|
||||||
embedding:
|
embedding:
|
||||||
options:
|
options:
|
||||||
model: text-embedding-v4
|
model: text-embedding-v4
|
||||||
@@ -83,4 +88,11 @@ jasypt:
|
|||||||
encryptor:
|
encryptor:
|
||||||
password: ${jasypt.encryptor.password}
|
password: ${jasypt.encryptor.password}
|
||||||
algorithm: PBEWithHMACSHA512AndAES_256
|
algorithm: PBEWithHMACSHA512AndAES_256
|
||||||
iv-generator-classname: org.jasypt.iv.RandomIvGenerator
|
iv-generator-classname: org.jasypt.iv.RandomIvGenerator
|
||||||
|
storage:
|
||||||
|
cos:
|
||||||
|
endpoint: snails-ai-1308845726.cos.ap-chengdu.myqcloud.com
|
||||||
|
appId: 1308845726
|
||||||
|
region: ap-chengdu
|
||||||
|
secretId: ENC(nbQSc/HpYon4HMw0sVHOB/mIkC3Z0r2bZ2ndI/V0GahiBN1Hc3Ki4+CDzch6dn+2AksNzvdazHyoiaozaUIpC9t/QGiAJ7Mdbdwpl6/F6S4=)
|
||||||
|
secretKey: ENC(f6Rkk1rf6DwEYCyW0xV584+fShN7c/fGvWbRdWnp46/MHk/EmOIJ5HHxhT+VtvdM6XXNSIprDmggPCdaOwdmOpdWzpoMnidnTsmSUsRw5NA=)
|
||||||
Reference in New Issue
Block a user