7.29 增加删除知识库功能
This commit is contained in:
parent
81b71d0d07
commit
37b696e61b
2
.gitignore
vendored
2
.gitignore
vendored
@ -47,3 +47,5 @@ build/
|
||||
/docs/tag/v1.0/ollama/models/
|
||||
/docs/tag/v1.0/ollama/id_ed25519
|
||||
/docs/tag/v1.0/ollama/id_ed25519.pub
|
||||
/docs/tag/v1.0/log/
|
||||
/docs/tag/v1.0/log/
|
||||
|
@ -1,6 +1,6 @@
|
||||
package edu.whut.api;
|
||||
|
||||
import org.springframework.ai.chat.ChatResponse;
|
||||
import org.springframework.ai.chat.model.ChatResponse; // ← 注意新包
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public interface IAiService {
|
||||
@ -10,5 +10,4 @@ public interface IAiService {
|
||||
Flux<ChatResponse> generateStream(String model, String message);
|
||||
|
||||
Flux<ChatResponse> generateStreamRag(String model, String ragTag, String message);
|
||||
|
||||
}
|
||||
|
@ -1,19 +1,25 @@
|
||||
package edu.whut.config;
|
||||
|
||||
import org.springframework.ai.ollama.OllamaChatClient;
|
||||
import org.springframework.ai.ollama.OllamaEmbeddingClient;
|
||||
import org.springframework.ai.embedding.EmbeddingModel;
|
||||
import org.springframework.ai.ollama.OllamaChatModel;
|
||||
import org.springframework.ai.ollama.OllamaEmbeddingModel;
|
||||
import org.springframework.ai.ollama.api.OllamaApi;
|
||||
import org.springframework.ai.ollama.api.OllamaOptions;
|
||||
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
|
||||
import org.springframework.ai.vectorstore.PgVectorStore;
|
||||
import org.springframework.ai.vectorstore.SimpleVectorStore;
|
||||
import org.springframework.ai.vectorstore.pgvector.PgVectorStore;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
/**
|
||||
* 配置 Ollama 和 OpenAI 客户端,以及向量存储和文本拆分器等 Bean
|
||||
* 配置 Ollama 客户端、Embedding 模型、向量存储和文本拆分器等 Bean
|
||||
* 注意(1.0.0-M6):
|
||||
* - 不再使用 OllamaChatClient,改为 OllamaChatModel
|
||||
* - Embedding 使用 EmbeddingModel 接口 + OllamaEmbeddingModel
|
||||
* - PgVectorStore 与 SimpleVectorStore 使用 builder 方式创建
|
||||
*/
|
||||
@Configuration
|
||||
public class OllamaConfig {
|
||||
@ -22,22 +28,23 @@ public class OllamaConfig {
|
||||
* 创建 Ollama API 客户端,负责与 Ollama 服务的基础通信
|
||||
*/
|
||||
@Bean
|
||||
public OllamaApi ollamaApi(
|
||||
@Value("${spring.ai.ollama.base-url}") String baseUrl) {
|
||||
public OllamaApi ollamaApi(@Value("${spring.ai.ollama.base-url}") String baseUrl) {
|
||||
return new OllamaApi(baseUrl);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 基于 OllamaApi 实例创建对话客户端,用于与 Ollama 模型进行对话交互
|
||||
* 对话模型;控制器里通过 OllamaOptions 指定具体 chat 模型(如 deepseek-r1:1.5b)
|
||||
*/
|
||||
@Bean
|
||||
public OllamaChatClient ollamaChatClient(OllamaApi ollamaApi) {
|
||||
return new OllamaChatClient(ollamaApi);
|
||||
public OllamaChatModel ollamaChatModel(OllamaApi ollamaApi) {
|
||||
// 如需设置默认对话模型,可在这里 .defaultOptions(OllamaOptions.builder().model("xxx").build())
|
||||
return OllamaChatModel.builder()
|
||||
.ollamaApi(ollamaApi)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本拆分器,根据 Token 划分长文本,便于分段处理或嵌入计算
|
||||
* 文本拆分器:基于 Token 切分
|
||||
*/
|
||||
@Bean
|
||||
public TokenTextSplitter tokenTextSplitter() {
|
||||
@ -45,36 +52,33 @@ public class OllamaConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个简单的向量存储 (in-memory),可根据配置选择 Ollama 或 OpenAI 的嵌入模型
|
||||
* EmbeddingModel(Ollama):使用配置项 spring.ai.rag.embed 指定 embedding 模型,如 nomic-embed-text
|
||||
*/
|
||||
@Bean
|
||||
public SimpleVectorStore vectorStore(
|
||||
@Value("${spring.ai.rag.embed}") String model,
|
||||
@Primary
|
||||
public EmbeddingModel embeddingModel(
|
||||
@Value("${spring.ai.rag.embed}") String embedModel,
|
||||
OllamaApi ollamaApi) {
|
||||
|
||||
// 固定使用 Ollama 的嵌入客户端(不再走 OpenAI 分支)
|
||||
OllamaEmbeddingClient embeddingClient = new OllamaEmbeddingClient(ollamaApi);
|
||||
embeddingClient.withDefaultOptions(
|
||||
OllamaOptions.create().withModel(model)
|
||||
);
|
||||
return new SimpleVectorStore(embeddingClient);
|
||||
return OllamaEmbeddingModel.builder()
|
||||
.ollamaApi(ollamaApi)
|
||||
.defaultOptions(OllamaOptions.builder().model(embedModel).build())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建基于 PostgreSQL pgvector 扩展的持久化向量存储,可根据配置选择 Ollama 或 OpenAI 模型
|
||||
* 简单内存向量存储(in-memory)
|
||||
*/
|
||||
@Bean
|
||||
public PgVectorStore pgVectorStore(
|
||||
@Value("${spring.ai.rag.embed}") String model,
|
||||
OllamaApi ollamaApi,
|
||||
JdbcTemplate jdbcTemplate) {
|
||||
|
||||
// 固定使用 Ollama 的嵌入客户端(不再走 OpenAI 分支)
|
||||
OllamaEmbeddingClient embeddingClient = new OllamaEmbeddingClient(ollamaApi);
|
||||
embeddingClient.withDefaultOptions(
|
||||
OllamaOptions.create().withModel(model)
|
||||
);
|
||||
return new PgVectorStore(jdbcTemplate, embeddingClient);
|
||||
public SimpleVectorStore vectorStore(EmbeddingModel embeddingModel) {
|
||||
return SimpleVectorStore.builder(embeddingModel).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于 PostgreSQL pgvector 的持久化向量存储
|
||||
* 如需自定义表名可追加 .vectorTableName("your_table")
|
||||
*/
|
||||
@Bean
|
||||
public PgVectorStore pgVectorStore(JdbcTemplate jdbcTemplate, EmbeddingModel embeddingModel) {
|
||||
return PgVectorStore.builder(jdbcTemplate, embeddingModel).build();
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
// src/test/java/edu/whut/test/JGitTest.java
|
||||
package edu.whut.test;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
@ -7,22 +8,20 @@ import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.ollama.OllamaChatClient;
|
||||
import org.springframework.ai.reader.tika.TikaDocumentReader;
|
||||
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
|
||||
import org.springframework.ai.vectorstore.PgVectorStore;
|
||||
import org.springframework.ai.vectorstore.SimpleVectorStore;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.core.io.PathResource;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.reader.tika.TikaDocumentReader;
|
||||
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
|
||||
import org.springframework.ai.vectorstore.SimpleVectorStore;
|
||||
import org.springframework.ai.vectorstore.pgvector.PgVectorStore;
|
||||
import org.springframework.ai.ollama.OllamaChatModel;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -33,116 +32,69 @@ import java.util.List;
|
||||
@SpringBootTest
|
||||
public class JGitTest {
|
||||
|
||||
/**
|
||||
* Ollama 聊天客户端,用于后续可能的模型调用(本例未使用)。
|
||||
*/
|
||||
/** Ollama 对话模型(本例未使用,仅演示注入) */
|
||||
@Resource
|
||||
private OllamaChatClient ollamaChatClient;
|
||||
private OllamaChatModel ollamaChatModel;
|
||||
|
||||
/**
|
||||
* 文本拆分器:将长文档拆分成多个小段。
|
||||
*/
|
||||
/** 文本拆分器:将长文档拆分成多个小段 */
|
||||
@Resource
|
||||
private TokenTextSplitter tokenTextSplitter;
|
||||
|
||||
/**
|
||||
* 简单的内存向量存储,用于快速测试(本例未使用)。
|
||||
*/
|
||||
/** 简单内存向量存储(本例未使用,仅演示注入) */
|
||||
@Resource
|
||||
private SimpleVectorStore simpleVectorStore;
|
||||
|
||||
/**
|
||||
* PostgreSQL 向量存储,用于持久化文档向量。
|
||||
*/
|
||||
/** PostgreSQL pgvector 存储,用于持久化文档向量 */
|
||||
@Resource
|
||||
private PgVectorStore pgVectorStore;
|
||||
|
||||
/**
|
||||
* 测试方法:克隆远程 Git 仓库到本地目录。
|
||||
* @throws Exception 在克隆过程出错时抛出
|
||||
*/
|
||||
/** 克隆远程 Git 仓库到本地 */
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
// 远程仓库地址
|
||||
String repoURL = "http://124.71.159.195:3000/zy123/group-buying.git";
|
||||
// 认证用户名和密码,如果仓库是私有的需要提供
|
||||
String username = "zy123xxxx";
|
||||
String password = "xxxxxxx";
|
||||
|
||||
// 指定本地克隆目录
|
||||
public void testClone() throws Exception {
|
||||
String repoURL = "http://example.com/your-repo.git";
|
||||
String username = "your-username";
|
||||
String password = "your-token-or-password";
|
||||
String localPath = "./cloned-repo";
|
||||
log.info("克隆路径:" + new File(localPath).getAbsolutePath());
|
||||
|
||||
// 如果目录已存在,则删除,确保每次都是全新克隆
|
||||
log.info("克隆路径:{}", new File(localPath).getAbsolutePath());
|
||||
FileUtils.deleteDirectory(new File(localPath));
|
||||
|
||||
// 使用 JGit 克隆仓库
|
||||
Git git = Git.cloneRepository()
|
||||
.setURI(repoURL)
|
||||
.setDirectory(new File(localPath))
|
||||
.setCredentialsProvider(
|
||||
new UsernamePasswordCredentialsProvider(username, password)
|
||||
)
|
||||
.setCredentialsProvider(new UsernamePasswordCredentialsProvider(username, password))
|
||||
.call();
|
||||
|
||||
// 关闭 Git 对象,释放资源
|
||||
git.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试方法:遍历克隆下来的本地仓库文件,将每个文件读取为文档,拆分后存入向量库。
|
||||
* @throws IOException 在文件遍历或 IO 操作出错时抛出
|
||||
*/
|
||||
/** 遍历本地仓库,将文件内容读取、拆分并写入向量库 */
|
||||
@Test
|
||||
public void test_file() throws IOException {
|
||||
public void testFileUpload() throws Exception {
|
||||
Files.walkFileTree(Paths.get("./cloned-repo"), new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
|
||||
// 跳过 .git 目录
|
||||
if (dir.getFileName().toString().equals(".git")) {
|
||||
if (".git".equals(dir.getFileName().toString())) {
|
||||
return FileVisitResult.SKIP_SUBTREE;
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
log.info("文件路径:{}", file);
|
||||
|
||||
PathResource resource = new PathResource(file);
|
||||
TikaDocumentReader reader = new TikaDocumentReader(resource);
|
||||
|
||||
// 1) 先拿到不可变的列表
|
||||
List<Document> original = Collections.emptyList();
|
||||
try {
|
||||
original = reader.get();
|
||||
} catch (Exception ex) {
|
||||
log.warn("跳过无法读取的文件 {}: {}", file, ex.getMessage());
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
// 2) 复制到可变列表
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
||||
log.info("处理文件:{}", file);
|
||||
TikaDocumentReader reader = new TikaDocumentReader(new PathResource(file));
|
||||
List<Document> original = reader.get();
|
||||
List<Document> docs = new ArrayList<>(original);
|
||||
|
||||
// 3) 去除 content 为空或纯空白的 Document
|
||||
docs.removeIf(d -> d.getContent() == null || d.getContent().trim().isEmpty());
|
||||
docs.removeIf(d -> d.getText() == null || d.getText().trim().isEmpty());
|
||||
if (docs.isEmpty()) {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
// 4) 给文档打标签
|
||||
docs.forEach(d -> d.getMetadata().put("knowledge", "group-buy-market"));
|
||||
|
||||
// 5) 拆分成更小的段落
|
||||
List<Document> splits = tokenTextSplitter.apply(docs);
|
||||
splits.forEach(d -> d.getMetadata().put("knowledge", "group-buy-market"));
|
||||
|
||||
// 6) 写入向量存储
|
||||
pgVectorStore.accept(splits);
|
||||
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
// src/test/java/edu/whut/test/RAGTest.java
|
||||
package edu.whut.test;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
@ -5,21 +6,21 @@ import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.ai.chat.ChatResponse;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.reader.tika.TikaDocumentReader;
|
||||
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
|
||||
import org.springframework.ai.vectorstore.SimpleVectorStore;
|
||||
import org.springframework.ai.vectorstore.pgvector.PgVectorStore;
|
||||
import org.springframework.ai.vectorstore.SearchRequest;
|
||||
import org.springframework.ai.ollama.OllamaChatModel;
|
||||
import org.springframework.ai.ollama.api.OllamaOptions;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.ollama.OllamaChatClient;
|
||||
import org.springframework.ai.ollama.api.OllamaOptions;
|
||||
import org.springframework.ai.reader.tika.TikaDocumentReader;
|
||||
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
|
||||
import org.springframework.ai.vectorstore.PgVectorStore;
|
||||
import org.springframework.ai.vectorstore.SearchRequest;
|
||||
import org.springframework.ai.vectorstore.SimpleVectorStore;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -27,59 +28,44 @@ import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* RAG 测试类,验证文档上传、向量存储和 Ollama 回答流程
|
||||
* RAG 测试类:验证文档上传、向量检索和 Ollama 回答流程
|
||||
*/
|
||||
@Slf4j
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class RAGTest {
|
||||
|
||||
// 注入 Ollama 聊天客户端,用于直接测试模型调用
|
||||
@Resource
|
||||
private OllamaChatClient ollamaChatClient;
|
||||
private OllamaChatModel ollamaChatModel;
|
||||
|
||||
// 注入 TokenTextSplitter,用于将长文档拆分为小段
|
||||
@Resource
|
||||
private TokenTextSplitter tokenTextSplitter;
|
||||
|
||||
// 注入简单内存向量存储,用于快速测试
|
||||
@Resource
|
||||
private SimpleVectorStore simpleVectorStore;
|
||||
|
||||
// 注入 PostgreSQL 向量存储,用于持久化测试
|
||||
@Resource
|
||||
private PgVectorStore pgVectorStore;
|
||||
|
||||
/**
|
||||
* 测试方法:读取本地文件,将其拆分并上传到 pgVectorStore
|
||||
*/
|
||||
/** 测试上传:将本地文件拆分并写入 pgvector */
|
||||
@Test
|
||||
public void upload() {
|
||||
// 使用 TikaDocumentReader 读取本地文件并提取文档对象
|
||||
TikaDocumentReader reader = new TikaDocumentReader("./data/file.text");
|
||||
TikaDocumentReader reader = new TikaDocumentReader("./data/file.txt");
|
||||
List<Document> documents = reader.get();
|
||||
// 对提取的文档进行 Token 拆分
|
||||
List<Document> documentSplitterList = tokenTextSplitter.apply(documents);
|
||||
List<Document> splits = tokenTextSplitter.apply(documents);
|
||||
|
||||
// 为原文档和拆分文档设置 "knowledge" 标签
|
||||
documents.forEach(doc -> doc.getMetadata().put("knowledge", "测试知识库名称"));
|
||||
documentSplitterList.forEach(doc -> doc.getMetadata().put("knowledge", "测试知识库名称"));
|
||||
|
||||
// 将拆分的文档批量存入 PostgreSQL pgvector 存储
|
||||
pgVectorStore.accept(documentSplitterList);
|
||||
documents.forEach(d -> d.getMetadata().put("knowledge", "测试知识库名称"));
|
||||
splits.forEach(d -> d.getMetadata().put("knowledge", "测试知识库名称"));
|
||||
|
||||
pgVectorStore.accept(splits);
|
||||
log.info("上传完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试方法:构造用户消息和系统提示,检索向量存储并调用 Ollama 获取回答
|
||||
*/
|
||||
/** 测试检索+回答:查询向量库并调用 Ollama */
|
||||
@Test
|
||||
public void chat() {
|
||||
// 设置用户提问
|
||||
String message = "王大瓜,哪年出生";
|
||||
String message = "王大瓜,哪年出生?";
|
||||
|
||||
// 定义系统提示模板,将检索到的文档注入,要求中文回答
|
||||
String SYSTEM_PROMPT = """
|
||||
Use the information from the DOCUMENTS section to provide accurate answers but act as if you knew this information innately.
|
||||
If unsure, simply state that you don't know.
|
||||
@ -88,34 +74,32 @@ public class RAGTest {
|
||||
{documents}
|
||||
""";
|
||||
|
||||
// 构建向量检索请求,最大返回 5 条,按 "knowledge" 标签过滤
|
||||
SearchRequest request = SearchRequest.query(message)
|
||||
.withTopK(5)
|
||||
.withFilterExpression("knowledge == '测试知识库名称'");
|
||||
// 构建检索请求
|
||||
SearchRequest request = SearchRequest.builder()
|
||||
.query(message)
|
||||
.topK(5)
|
||||
.filterExpression("knowledge == '测试知识库名称'")
|
||||
.build();
|
||||
|
||||
// 执行相似度搜索,获取匹配的文档列表
|
||||
List<Document> documents = pgVectorStore.similaritySearch(request);
|
||||
// 拼接所有文档内容
|
||||
String documentsCollectors = documents.stream()
|
||||
.map(Document::getContent)
|
||||
// 执行相似度搜索
|
||||
List<Document> docs = pgVectorStore.similaritySearch(request);
|
||||
String docContent = docs.stream()
|
||||
.map(Document::getText)
|
||||
.collect(Collectors.joining());
|
||||
|
||||
// 创建带有文档内容的系统消息
|
||||
// 构建系统消息
|
||||
Message ragMessage = new SystemPromptTemplate(SYSTEM_PROMPT)
|
||||
.createMessage(Map.of("documents", documentsCollectors));
|
||||
.createMessage(Map.of("documents", docContent));
|
||||
|
||||
// 构造消息列表:用户消息在前,系统消息在后
|
||||
ArrayList<Message> messages = new ArrayList<>();
|
||||
// 发起对话
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new UserMessage(message));
|
||||
messages.add(ragMessage);
|
||||
|
||||
// 调用 Ollama 同步接口获取模型回答
|
||||
ChatResponse chatResponse = ollamaChatClient.call(
|
||||
new Prompt(messages, OllamaOptions.create().withModel("deepseek-r1:1.5b"))
|
||||
ChatResponse response = ollamaChatModel.call(
|
||||
new Prompt(messages, OllamaOptions.builder().model("deepseek-r1:1.5b").build())
|
||||
);
|
||||
|
||||
// 打印测试结果
|
||||
log.info("测试结果:{}", JSON.toJSONString(chatResponse));
|
||||
log.info("测试结果: {}", JSON.toJSONString(response));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,16 +3,16 @@ package edu.whut.trigger.http;
|
||||
import edu.whut.api.IAiService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.ChatResponse;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.ollama.OllamaChatClient;
|
||||
import org.springframework.ai.ollama.OllamaChatModel;
|
||||
import org.springframework.ai.ollama.api.OllamaOptions;
|
||||
import org.springframework.ai.vectorstore.PgVectorStore;
|
||||
import org.springframework.ai.vectorstore.SearchRequest;
|
||||
import org.springframework.ai.vectorstore.pgvector.PgVectorStore;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
@ -31,47 +31,49 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
public class OllamaController implements IAiService {
|
||||
|
||||
// 注入 Ollama 对话客户端,用于向 Ollama 模型发起对话请求
|
||||
private final OllamaChatClient chatClient;
|
||||
// 注入 Ollama 对话模型(替代旧版 OllamaChatClient)
|
||||
private final OllamaChatModel ollamaChatModel;
|
||||
|
||||
// 注入 PostgreSQL 向量存储,用于 RAG 检索
|
||||
private final PgVectorStore pgVectorStore;
|
||||
|
||||
/**
|
||||
* 普通生成接口,返回一次性 ChatResponse
|
||||
* 示例: GET /generate?model=deepseek-r1:1.5b&message=1+1
|
||||
* 示例: GET /api/v1/ollama/generate?model=deepseek-r1:1.5b&message=1+1
|
||||
*/
|
||||
@GetMapping("generate")
|
||||
@Override
|
||||
public ChatResponse generate(
|
||||
@RequestParam("model") String model,
|
||||
@RequestParam("message") String message) {
|
||||
// 构建 Prompt 并调用 OllamaClient 同步获取响应
|
||||
|
||||
log.info("generate called!");
|
||||
return chatClient.call(
|
||||
new Prompt(message, OllamaOptions.create().withModel(model))
|
||||
);
|
||||
return ollamaChatModel.call(new Prompt(
|
||||
message,
|
||||
OllamaOptions.builder().model(model).build()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 流式生成接口,返回 Flux<ChatResponse>
|
||||
* 示例: GET /generate_stream?model=deepseek-r1:1.5b&message=hi
|
||||
* 流式生成接口,返回 Flux<ChatResponse>
|
||||
* 示例: GET /api/v1/ollama/generate_stream?model=deepseek-r1:1.5b&message=hi
|
||||
*/
|
||||
@GetMapping("generate_stream")
|
||||
@Override
|
||||
public Flux<ChatResponse> generateStream(
|
||||
@RequestParam("model") String model,
|
||||
@RequestParam("message") String message) {
|
||||
// 调用 OllamaClient 的 stream 方法,开启 SSE 或分块传输
|
||||
|
||||
log.info("generate_stream called!");
|
||||
return chatClient.stream(
|
||||
new Prompt(message, OllamaOptions.create().withModel(model))
|
||||
);
|
||||
return ollamaChatModel.stream(new Prompt(
|
||||
message,
|
||||
OllamaOptions.builder().model(model).build()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* RAG 流式生成接口:先检索相关文档,再附加系统提示,最后流式调用模型
|
||||
* 示例: GET /generate_stream_rag?model=deepseek-r1:1.5b&ragTag=xxx&message=内容
|
||||
* 示例: GET /api/v1/ollama/generate_stream_rag?model=deepseek-r1:1.5b&ragTag=xxx&message=内容
|
||||
*/
|
||||
@GetMapping("generate_stream_rag")
|
||||
@Override
|
||||
@ -79,10 +81,10 @@ public class OllamaController implements IAiService {
|
||||
@RequestParam("model") String model,
|
||||
@RequestParam("ragTag") String ragTag,
|
||||
@RequestParam("message") String message) {
|
||||
log.info("generate_stream_rag called!");
|
||||
// 系统提示模板,嵌入检索到的文档内容,并要求中文回复
|
||||
String SYSTEM_PROMPT =
|
||||
"""
|
||||
|
||||
log.info("generate_stream_rag called!用户问题是:"+message);
|
||||
|
||||
String SYSTEM_PROMPT = """
|
||||
Use the information from the DOCUMENTS section to provide accurate answers but act as if you knew this information innately.
|
||||
If unsure, simply state that you don't know.
|
||||
Another thing you need to note is that your reply must be in Chinese!
|
||||
@ -90,31 +92,29 @@ public class OllamaController implements IAiService {
|
||||
{documents}
|
||||
""";
|
||||
|
||||
// 构建检索请求:基于用户 message 检索 TopK 文档,并使用 ragTag 过滤标签
|
||||
SearchRequest request = SearchRequest.query(message)
|
||||
.withTopK(5)
|
||||
.withFilterExpression("knowledge == '" + ragTag + "'");
|
||||
// 基于用户 message 检索 TopK 文档,并使用 ragTag 过滤标签
|
||||
SearchRequest request = SearchRequest.builder()
|
||||
.query(message)
|
||||
.topK(5)
|
||||
.filterExpression("knowledge == '" + ragTag + "'")
|
||||
.build();
|
||||
|
||||
// 执行相似度搜索,获取匹配文档列表
|
||||
List<Document> documents = pgVectorStore.similaritySearch(request);
|
||||
// 拼接文档内容
|
||||
// M6 文档对象推荐使用 getText()
|
||||
String documentContent = documents.stream()
|
||||
.map(Document::getContent)
|
||||
.map(Document::getText)
|
||||
.collect(Collectors.joining());
|
||||
|
||||
// 使用 SystemPromptTemplate 注入文档到系统消息
|
||||
Message ragMessage = new SystemPromptTemplate(SYSTEM_PROMPT)
|
||||
.createMessage(Map.of("documents", documentContent));
|
||||
|
||||
// 构造消息列表:先用户消息,再系统消息
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new UserMessage(message));
|
||||
messages.add(ragMessage);
|
||||
|
||||
// 发起流式调用
|
||||
return chatClient.stream(
|
||||
new Prompt(messages, OllamaOptions.create().withModel(model))
|
||||
);
|
||||
return ollamaChatModel.stream(new Prompt(
|
||||
messages,
|
||||
OllamaOptions.builder().model(model).build()
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package edu.whut.trigger.http;
|
||||
|
||||
import edu.whut.api.IRAGService;
|
||||
import edu.whut.api.response.Response;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
@ -11,11 +10,11 @@ import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
|
||||
import org.redisson.api.RList;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.ollama.OllamaChatClient;
|
||||
import org.springframework.ai.ollama.OllamaChatModel;
|
||||
import org.springframework.ai.reader.tika.TikaDocumentReader;
|
||||
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
|
||||
import org.springframework.ai.vectorstore.PgVectorStore;
|
||||
import org.springframework.ai.vectorstore.SimpleVectorStore;
|
||||
import org.springframework.ai.vectorstore.pgvector.PgVectorStore;
|
||||
import org.springframework.core.io.PathResource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@ -38,8 +37,8 @@ import java.util.List;
|
||||
@RequiredArgsConstructor
|
||||
public class RAGController implements IRAGService {
|
||||
|
||||
// Ollama 聊天客户端,用于后续可能的对话调用(此处暂无直接使用)
|
||||
private final OllamaChatClient ollamaChatClient;
|
||||
// Ollama 对话模型(此处暂无直接使用,但保留注入以便扩展)
|
||||
private final OllamaChatModel ollamaChatModel;
|
||||
|
||||
// 文本拆分器,将长文档切分为合适大小的段落或 Token 块
|
||||
private final TokenTextSplitter tokenTextSplitter;
|
||||
@ -62,6 +61,20 @@ public class RAGController implements IRAGService {
|
||||
public Response<List<String>> queryRagTagList() {
|
||||
// 从 Redis 列表获取所有标签
|
||||
RList<String> elements = redissonClient.getList("ragTag");
|
||||
// 读一个快照,便于安全日志与返回
|
||||
List<String> tags;
|
||||
try {
|
||||
tags = elements.readAll(); // Redisson 提供的批量读取
|
||||
} catch (Exception e) {
|
||||
// 兜底:某些客户端/版本没有 readAll 时使用迭代
|
||||
log.warn("读取 Redis ragTag 列表使用 readAll 失败,改用迭代读取。", e);
|
||||
tags = new ArrayList<>();
|
||||
for (String s : elements) {
|
||||
tags.add(s);
|
||||
}
|
||||
}
|
||||
// 打印查询到的标签(数量 + 内容)
|
||||
log.info("查询 RAG 标签列表,数量:{},内容:{}", tags.size(), tags);
|
||||
return Response.<List<String>>builder()
|
||||
.code("0000")
|
||||
.info("调用成功")
|
||||
@ -98,6 +111,7 @@ public class RAGController implements IRAGService {
|
||||
// 可选:调试日志
|
||||
log.debug("接收文件:{},相对路径:{}", file.getOriginalFilename(), relPath);
|
||||
|
||||
try {
|
||||
// 读取上传文件,提取文档内容
|
||||
TikaDocumentReader documentReader = new TikaDocumentReader(file.getResource());
|
||||
List<Document> documents = documentReader.get();
|
||||
@ -105,34 +119,33 @@ public class RAGController implements IRAGService {
|
||||
// 对文档进行 Token 拆分
|
||||
List<Document> documentSplitterList = tokenTextSplitter.apply(documents);
|
||||
|
||||
// 为原文档和拆分文档设置 ragTag 元数据
|
||||
// 为原文档和拆分文档设置 ragTag 元数据 + 相对路径
|
||||
documents.forEach(doc -> {
|
||||
doc.getMetadata().put("knowledge", ragTag);
|
||||
// ===== 新增:写入相对路径 =====
|
||||
doc.getMetadata().put("path", relPath);
|
||||
});
|
||||
documentSplitterList.forEach(doc -> {
|
||||
doc.getMetadata().put("knowledge", ragTag);
|
||||
// ===== 新增:写入相对路径 =====
|
||||
doc.getMetadata().put("path", relPath);
|
||||
});
|
||||
|
||||
// 存储拆分后的文档到 pgVectorStore
|
||||
pgVectorStore.accept(documentSplitterList);
|
||||
} catch (Exception e) {
|
||||
log.error("文件处理失败:{} - {}", file.getOriginalFilename(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新 Redis 标签列表,避免重复
|
||||
RList<String> elements = redissonClient.getList("ragTag");
|
||||
if (!elements.contains(ragTag)) {
|
||||
elements.add(ragTag);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("上传知识库完成:{}", ragTag);
|
||||
return Response.<String>builder().code("0000").info("调用成功").build();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 克隆并分析 Git 仓库:
|
||||
* - 克隆指定仓库到本地
|
||||
@ -179,7 +192,7 @@ public class RAGController implements IRAGService {
|
||||
|
||||
// 2.2 复制为可变列表并过滤掉空内容
|
||||
List<Document> docs = new ArrayList<>(raw);
|
||||
docs.removeIf(d -> d.getContent() == null || d.getContent().trim().isEmpty());
|
||||
docs.removeIf(d -> d.getText() == null || d.getText().trim().isEmpty());
|
||||
if (docs.isEmpty()) {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
@ -222,6 +235,42 @@ public class RAGController implements IRAGService {
|
||||
return Response.<String>builder().code("0000").info("调用成功").build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试接口
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("knowledge/_ping")
|
||||
public Response<String> ragPing() {
|
||||
log.info("RAG knowledge ping");
|
||||
return Response.<String>builder().code("0000").info("ok").build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除指定知识库(按 ragTag):
|
||||
* - 从 pgvector 向量库中删除所有 metadata.knowledge == ragTag 的文档
|
||||
* - 从 Redis ragTag 列表中删除该标签
|
||||
* DELETE /api/v1/rag/knowledge/{ragTag}
|
||||
*/
|
||||
@DeleteMapping("knowledge/{ragTag}")
|
||||
public Response<String> deleteKnowledge(@PathVariable("ragTag") String ragTag) {
|
||||
log.info("删除知识库开始:{}", ragTag);
|
||||
try {
|
||||
// 1) 删除向量库中对应知识库的所有向量(1.0.0-M6:支持过滤表达式删除)
|
||||
pgVectorStore.delete("knowledge == '" + ragTag + "'");
|
||||
|
||||
// 2) 从 Redis 标签列表移除
|
||||
RList<String> elements = redissonClient.getList("ragTag");
|
||||
elements.remove(ragTag);
|
||||
|
||||
log.info("删除知识库完成:{}", ragTag);
|
||||
return Response.<String>builder().code("0000").info("删除成功").build();
|
||||
} catch (Exception e) {
|
||||
log.error("删除知识库失败:{}", ragTag, e);
|
||||
return Response.<String>builder().code("9999").info("删除失败:" + e.getMessage()).build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Git 仓库 URL 提取项目名称(去除 .git 后缀)
|
||||
*/
|
||||
|
@ -115,7 +115,7 @@
|
||||
loadingOverlay.style.display = 'flex';
|
||||
document.getElementById('status').textContent = '';
|
||||
|
||||
fetch('http://localhost:8090/api/v1/rag/analyze_git_repository', {
|
||||
fetch('http://localhost:8095/api/v1/rag/analyze_git_repository', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
|
@ -128,7 +128,7 @@
|
||||
formData.append('ragTag', document.getElementById('title').value);
|
||||
files.forEach(file => formData.append('file', file));
|
||||
|
||||
axios.post('http://localhost:8090/api/v1/rag/file/upload', formData)
|
||||
axios.post('http://localhost:8095/api/v1/rag/file/upload', formData)
|
||||
.then(response => {
|
||||
if (response.data.code === '0000') {
|
||||
// 成功提示并关闭窗口
|
||||
|
@ -1,9 +1 @@
|
||||
# A. 测试 embeddings(nomic-embed-text)
|
||||
curl -s http://localhost:11434/api/embeddings \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"nomic-embed-text","input":"hello"}'
|
||||
|
||||
# B. 测试 generate(deepseek-r1:1.5b)
|
||||
curl -s http://localhost:11434/api/generate \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"deepseek-r1:1.5b","prompt":"say hi"}'
|
||||
curl http://localhost:8095/api/v1/rag/query_rag_tag_list
|
@ -1,20 +0,0 @@
|
||||
25-07-28.21:24:55.145 [main ] INFO Application - Starting Application v1.0 using Java 17.0.2 with PID 1 (/app.jar started by root in /)
|
||||
25-07-28.21:24:55.150 [main ] INFO Application - The following 1 profile is active: "dev"
|
||||
25-07-28.21:24:56.064 [main ] INFO RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode
|
||||
25-07-28.21:24:56.067 [main ] INFO RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
|
||||
25-07-28.21:24:56.097 [main ] INFO RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 14 ms. Found 0 Redis repository interfaces.
|
||||
25-07-28.21:24:56.879 [main ] INFO TomcatWebServer - Tomcat initialized with port 8095 (http)
|
||||
25-07-28.21:24:56.891 [main ] INFO Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8095"]
|
||||
25-07-28.21:24:56.893 [main ] INFO StandardService - Starting service [Tomcat]
|
||||
25-07-28.21:24:56.894 [main ] INFO StandardEngine - Starting Servlet engine: [Apache Tomcat/10.1.19]
|
||||
25-07-28.21:24:56.936 [main ] INFO [/] - Initializing Spring embedded WebApplicationContext
|
||||
25-07-28.21:24:56.936 [main ] INFO ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1688 ms
|
||||
25-07-28.21:24:57.400 [main ] INFO HikariDataSource - HikariCP - Starting...
|
||||
25-07-28.21:24:57.627 [main ] INFO HikariPool - HikariCP - Added connection org.postgresql.jdbc.PgConnection@1c2dd89b
|
||||
25-07-28.21:24:57.629 [main ] INFO HikariDataSource - HikariCP - Start completed.
|
||||
25-07-28.21:25:01.124 [main ] INFO Version - Redisson 3.44.0
|
||||
25-07-28.21:25:01.444 [redisson-netty-1-5] INFO ConnectionsHolder - 1 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-28.21:25:01.491 [redisson-netty-1-13] INFO ConnectionsHolder - 5 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-28.21:25:02.123 [main ] INFO Http11NioProtocol - Starting ProtocolHandler ["http-nio-8095"]
|
||||
25-07-28.21:25:02.135 [main ] INFO TomcatWebServer - Tomcat started on port 8095 (http) with context path ''
|
||||
25-07-28.21:25:02.146 [main ] INFO Application - Started Application in 7.872 seconds (process running for 8.608)
|
@ -1,229 +0,0 @@
|
||||
25-07-29.08:46:26.334 [main ] INFO Application - Starting Application v1.0 using Java 17.0.2 with PID 1 (/app.jar started by root in /)
|
||||
25-07-29.08:46:26.343 [main ] INFO Application - The following 1 profile is active: "dev"
|
||||
25-07-29.08:46:27.850 [main ] INFO RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode
|
||||
25-07-29.08:46:27.857 [main ] INFO RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
|
||||
25-07-29.08:46:27.927 [main ] INFO RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 27 ms. Found 0 Redis repository interfaces.
|
||||
25-07-29.08:46:29.637 [main ] INFO TomcatWebServer - Tomcat initialized with port 8095 (http)
|
||||
25-07-29.08:46:29.656 [main ] INFO Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8095"]
|
||||
25-07-29.08:46:29.661 [main ] INFO StandardService - Starting service [Tomcat]
|
||||
25-07-29.08:46:29.661 [main ] INFO StandardEngine - Starting Servlet engine: [Apache Tomcat/10.1.19]
|
||||
25-07-29.08:46:29.739 [main ] INFO [/] - Initializing Spring embedded WebApplicationContext
|
||||
25-07-29.08:46:29.741 [main ] INFO ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 3239 ms
|
||||
25-07-29.08:46:30.753 [main ] INFO HikariDataSource - HikariCP - Starting...
|
||||
25-07-29.08:46:31.046 [main ] INFO HikariPool - HikariCP - Added connection org.postgresql.jdbc.PgConnection@7dd45c93
|
||||
25-07-29.08:46:31.049 [main ] INFO HikariDataSource - HikariCP - Start completed.
|
||||
25-07-29.08:46:36.257 [main ] INFO Version - Redisson 3.44.0
|
||||
25-07-29.08:46:36.850 [redisson-netty-1-4] INFO ConnectionsHolder - 1 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-29.08:46:36.921 [redisson-netty-1-13] INFO ConnectionsHolder - 5 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-29.08:46:38.094 [main ] INFO Http11NioProtocol - Starting ProtocolHandler ["http-nio-8095"]
|
||||
25-07-29.08:46:38.110 [main ] INFO TomcatWebServer - Tomcat started on port 8095 (http) with context path ''
|
||||
25-07-29.08:46:38.124 [main ] INFO Application - Started Application in 13.451 seconds (process running for 14.471)
|
||||
25-07-29.08:51:25.055 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariCP - Shutdown initiated...
|
||||
25-07-29.08:51:25.059 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariCP - Shutdown completed.
|
||||
25-07-29.08:51:40.866 [main ] INFO Application - Starting Application v1.0 using Java 17.0.2 with PID 1 (/app.jar started by root in /)
|
||||
25-07-29.08:51:40.876 [main ] INFO Application - The following 1 profile is active: "dev"
|
||||
25-07-29.08:51:41.638 [main ] INFO RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode
|
||||
25-07-29.08:51:41.641 [main ] INFO RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
|
||||
25-07-29.08:51:41.665 [main ] INFO RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 11 ms. Found 0 Redis repository interfaces.
|
||||
25-07-29.08:51:42.318 [main ] INFO TomcatWebServer - Tomcat initialized with port 8095 (http)
|
||||
25-07-29.08:51:42.326 [main ] INFO Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8095"]
|
||||
25-07-29.08:51:42.328 [main ] INFO StandardService - Starting service [Tomcat]
|
||||
25-07-29.08:51:42.329 [main ] INFO StandardEngine - Starting Servlet engine: [Apache Tomcat/10.1.19]
|
||||
25-07-29.08:51:42.369 [main ] INFO [/] - Initializing Spring embedded WebApplicationContext
|
||||
25-07-29.08:51:42.370 [main ] INFO ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1395 ms
|
||||
25-07-29.08:51:42.702 [main ] INFO HikariDataSource - HikariCP - Starting...
|
||||
25-07-29.08:51:42.868 [main ] INFO HikariPool - HikariCP - Added connection org.postgresql.jdbc.PgConnection@7e3ee128
|
||||
25-07-29.08:51:42.869 [main ] INFO HikariDataSource - HikariCP - Start completed.
|
||||
25-07-29.08:51:46.030 [main ] INFO Version - Redisson 3.44.0
|
||||
25-07-29.08:51:46.329 [redisson-netty-1-5] INFO ConnectionsHolder - 1 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-29.08:51:46.371 [redisson-netty-1-13] INFO ConnectionsHolder - 5 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-29.08:51:47.114 [main ] INFO Http11NioProtocol - Starting ProtocolHandler ["http-nio-8095"]
|
||||
25-07-29.08:51:47.132 [main ] INFO TomcatWebServer - Tomcat started on port 8095 (http) with context path ''
|
||||
25-07-29.08:51:47.148 [main ] INFO Application - Started Application in 7.034 seconds (process running for 7.668)
|
||||
25-07-29.09:04:24.859 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariCP - Shutdown initiated...
|
||||
25-07-29.09:04:24.865 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariCP - Shutdown completed.
|
||||
25-07-29.09:04:35.408 [main ] INFO Application - Starting Application v1.0 using Java 17.0.2 with PID 1 (/app.jar started by root in /)
|
||||
25-07-29.09:04:35.412 [main ] INFO Application - The following 1 profile is active: "dev"
|
||||
25-07-29.09:04:36.258 [main ] INFO RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode
|
||||
25-07-29.09:04:36.261 [main ] INFO RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
|
||||
25-07-29.09:04:36.291 [main ] INFO RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 13 ms. Found 0 Redis repository interfaces.
|
||||
25-07-29.09:04:36.989 [main ] INFO TomcatWebServer - Tomcat initialized with port 8095 (http)
|
||||
25-07-29.09:04:36.999 [main ] INFO Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8095"]
|
||||
25-07-29.09:04:37.001 [main ] INFO StandardService - Starting service [Tomcat]
|
||||
25-07-29.09:04:37.001 [main ] INFO StandardEngine - Starting Servlet engine: [Apache Tomcat/10.1.19]
|
||||
25-07-29.09:04:37.043 [main ] INFO [/] - Initializing Spring embedded WebApplicationContext
|
||||
25-07-29.09:04:37.044 [main ] INFO ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1536 ms
|
||||
25-07-29.09:04:37.484 [main ] INFO HikariDataSource - HikariCP - Starting...
|
||||
25-07-29.09:04:37.701 [main ] INFO HikariPool - HikariCP - Added connection org.postgresql.jdbc.PgConnection@7e3ee128
|
||||
25-07-29.09:04:37.702 [main ] INFO HikariDataSource - HikariCP - Start completed.
|
||||
25-07-29.09:04:40.658 [main ] INFO Version - Redisson 3.44.0
|
||||
25-07-29.09:04:40.910 [redisson-netty-1-4] INFO ConnectionsHolder - 1 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-29.09:04:40.948 [redisson-netty-1-13] INFO ConnectionsHolder - 5 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-29.09:04:41.615 [main ] INFO Http11NioProtocol - Starting ProtocolHandler ["http-nio-8095"]
|
||||
25-07-29.09:04:41.624 [main ] INFO TomcatWebServer - Tomcat started on port 8095 (http) with context path ''
|
||||
25-07-29.09:04:41.632 [main ] INFO Application - Started Application in 7.051 seconds (process running for 7.813)
|
||||
25-07-29.09:26:52.922 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariCP - Shutdown initiated...
|
||||
25-07-29.09:26:52.925 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariCP - Shutdown completed.
|
||||
25-07-29.09:28:58.726 [main ] INFO Application - Starting Application v1.0 using Java 17.0.2 with PID 1 (/app.jar started by root in /)
|
||||
25-07-29.09:28:58.730 [main ] INFO Application - The following 1 profile is active: "dev"
|
||||
25-07-29.09:28:59.560 [main ] INFO RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode
|
||||
25-07-29.09:28:59.563 [main ] INFO RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
|
||||
25-07-29.09:28:59.595 [main ] INFO RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 14 ms. Found 0 Redis repository interfaces.
|
||||
25-07-29.09:29:00.294 [main ] INFO TomcatWebServer - Tomcat initialized with port 8095 (http)
|
||||
25-07-29.09:29:00.306 [main ] INFO Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8095"]
|
||||
25-07-29.09:29:00.308 [main ] INFO StandardService - Starting service [Tomcat]
|
||||
25-07-29.09:29:00.308 [main ] INFO StandardEngine - Starting Servlet engine: [Apache Tomcat/10.1.19]
|
||||
25-07-29.09:29:00.349 [main ] INFO [/] - Initializing Spring embedded WebApplicationContext
|
||||
25-07-29.09:29:00.349 [main ] INFO ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1535 ms
|
||||
25-07-29.09:29:00.755 [main ] INFO HikariDataSource - HikariCP - Starting...
|
||||
25-07-29.09:29:00.938 [main ] INFO HikariPool - HikariCP - Added connection org.postgresql.jdbc.PgConnection@8383a14
|
||||
25-07-29.09:29:00.940 [main ] INFO HikariDataSource - HikariCP - Start completed.
|
||||
25-07-29.09:29:04.144 [main ] INFO Version - Redisson 3.44.0
|
||||
25-07-29.09:29:04.413 [redisson-netty-1-5] INFO ConnectionsHolder - 1 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-29.09:29:04.451 [redisson-netty-1-13] INFO ConnectionsHolder - 5 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-29.09:29:05.056 [main ] INFO Http11NioProtocol - Starting ProtocolHandler ["http-nio-8095"]
|
||||
25-07-29.09:29:05.069 [main ] INFO TomcatWebServer - Tomcat started on port 8095 (http) with context path ''
|
||||
25-07-29.09:29:05.078 [main ] INFO Application - Started Application in 7.161 seconds (process running for 7.867)
|
||||
25-07-29.09:29:58.580 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariCP - Shutdown initiated...
|
||||
25-07-29.09:29:58.582 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariCP - Shutdown completed.
|
||||
25-07-29.09:30:20.134 [main ] INFO Application - Starting Application v1.0 using Java 17.0.2 with PID 1 (/app.jar started by root in /)
|
||||
25-07-29.09:30:20.139 [main ] INFO Application - The following 1 profile is active: "dev"
|
||||
25-07-29.09:30:20.995 [main ] INFO RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode
|
||||
25-07-29.09:30:20.998 [main ] INFO RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
|
||||
25-07-29.09:30:21.032 [main ] INFO RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 14 ms. Found 0 Redis repository interfaces.
|
||||
25-07-29.09:30:21.718 [main ] INFO TomcatWebServer - Tomcat initialized with port 8095 (http)
|
||||
25-07-29.09:30:21.728 [main ] INFO Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8095"]
|
||||
25-07-29.09:30:21.731 [main ] INFO StandardService - Starting service [Tomcat]
|
||||
25-07-29.09:30:21.732 [main ] INFO StandardEngine - Starting Servlet engine: [Apache Tomcat/10.1.19]
|
||||
25-07-29.09:30:21.771 [main ] INFO [/] - Initializing Spring embedded WebApplicationContext
|
||||
25-07-29.09:30:21.772 [main ] INFO ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1534 ms
|
||||
25-07-29.09:30:22.137 [main ] INFO HikariDataSource - HikariCP - Starting...
|
||||
25-07-29.09:30:22.308 [main ] INFO HikariPool - HikariCP - Added connection org.postgresql.jdbc.PgConnection@374b6e33
|
||||
25-07-29.09:30:22.310 [main ] INFO HikariDataSource - HikariCP - Start completed.
|
||||
25-07-29.09:30:22.970 [main ] INFO Version - Redisson 3.44.0
|
||||
25-07-29.09:30:23.231 [redisson-netty-1-5] INFO ConnectionsHolder - 1 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-29.09:30:23.271 [redisson-netty-1-13] INFO ConnectionsHolder - 5 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-29.09:30:24.044 [main ] INFO Http11NioProtocol - Starting ProtocolHandler ["http-nio-8095"]
|
||||
25-07-29.09:30:24.053 [main ] INFO TomcatWebServer - Tomcat started on port 8095 (http) with context path ''
|
||||
25-07-29.09:30:24.063 [main ] INFO Application - Started Application in 4.689 seconds (process running for 5.236)
|
||||
25-07-29.09:30:24.980 [http-nio-8095-exec-1] INFO [/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
|
||||
25-07-29.09:30:24.981 [http-nio-8095-exec-1] INFO DispatcherServlet - Initializing Servlet 'dispatcherServlet'
|
||||
25-07-29.09:30:24.982 [http-nio-8095-exec-1] INFO DispatcherServlet - Completed initialization in 1 ms
|
||||
25-07-29.09:31:54.384 [http-nio-8095-exec-2] INFO OllamaController - generate_stream called!
|
||||
25-07-29.09:44:47.629 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariCP - Shutdown initiated...
|
||||
25-07-29.09:44:47.633 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariCP - Shutdown completed.
|
||||
25-07-29.09:45:42.997 [main ] INFO Application - Starting Application v1.0 using Java 17.0.2 with PID 1 (/app.jar started by root in /)
|
||||
25-07-29.09:45:43.002 [main ] INFO Application - The following 1 profile is active: "dev"
|
||||
25-07-29.09:45:43.847 [main ] INFO RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode
|
||||
25-07-29.09:45:43.850 [main ] INFO RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
|
||||
25-07-29.09:45:43.880 [main ] INFO RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 12 ms. Found 0 Redis repository interfaces.
|
||||
25-07-29.09:45:44.636 [main ] INFO TomcatWebServer - Tomcat initialized with port 8095 (http)
|
||||
25-07-29.09:45:44.648 [main ] INFO Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8095"]
|
||||
25-07-29.09:45:44.649 [main ] INFO StandardService - Starting service [Tomcat]
|
||||
25-07-29.09:45:44.649 [main ] INFO StandardEngine - Starting Servlet engine: [Apache Tomcat/10.1.19]
|
||||
25-07-29.09:45:44.693 [main ] INFO [/] - Initializing Spring embedded WebApplicationContext
|
||||
25-07-29.09:45:44.693 [main ] INFO ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1578 ms
|
||||
25-07-29.09:45:45.142 [main ] INFO HikariDataSource - HikariCP - Starting...
|
||||
25-07-29.09:45:45.363 [main ] INFO HikariPool - HikariCP - Added connection org.postgresql.jdbc.PgConnection@6ba060af
|
||||
25-07-29.09:45:45.365 [main ] INFO HikariDataSource - HikariCP - Start completed.
|
||||
25-07-29.09:45:48.289 [main ] INFO Version - Redisson 3.44.0
|
||||
25-07-29.09:45:48.586 [redisson-netty-1-4] INFO ConnectionsHolder - 1 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-29.09:45:48.623 [redisson-netty-1-13] INFO ConnectionsHolder - 5 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-29.09:45:49.297 [main ] INFO Http11NioProtocol - Starting ProtocolHandler ["http-nio-8095"]
|
||||
25-07-29.09:45:49.310 [main ] INFO TomcatWebServer - Tomcat started on port 8095 (http) with context path ''
|
||||
25-07-29.09:45:49.321 [main ] INFO Application - Started Application in 7.098 seconds (process running for 7.853)
|
||||
25-07-29.09:45:51.712 [http-nio-8095-exec-1] INFO [/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
|
||||
25-07-29.09:45:51.713 [http-nio-8095-exec-1] INFO DispatcherServlet - Initializing Servlet 'dispatcherServlet'
|
||||
25-07-29.09:45:51.713 [http-nio-8095-exec-1] INFO DispatcherServlet - Completed initialization in 0 ms
|
||||
25-07-29.09:51:07.030 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariCP - Shutdown initiated...
|
||||
25-07-29.09:51:07.032 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariCP - Shutdown completed.
|
||||
25-07-29.09:51:16.935 [main ] INFO Application - Starting Application v1.0 using Java 17.0.2 with PID 1 (/app.jar started by root in /)
|
||||
25-07-29.09:51:16.940 [main ] INFO Application - The following 1 profile is active: "dev"
|
||||
25-07-29.09:51:17.677 [main ] INFO RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode
|
||||
25-07-29.09:51:17.680 [main ] INFO RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
|
||||
25-07-29.09:51:17.708 [main ] INFO RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 12 ms. Found 0 Redis repository interfaces.
|
||||
25-07-29.09:51:18.510 [main ] INFO TomcatWebServer - Tomcat initialized with port 8095 (http)
|
||||
25-07-29.09:51:18.521 [main ] INFO Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8095"]
|
||||
25-07-29.09:51:18.524 [main ] INFO StandardService - Starting service [Tomcat]
|
||||
25-07-29.09:51:18.524 [main ] INFO StandardEngine - Starting Servlet engine: [Apache Tomcat/10.1.19]
|
||||
25-07-29.09:51:18.572 [main ] INFO [/] - Initializing Spring embedded WebApplicationContext
|
||||
25-07-29.09:51:18.572 [main ] INFO ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1540 ms
|
||||
25-07-29.09:51:18.994 [main ] INFO HikariDataSource - HikariCP - Starting...
|
||||
25-07-29.09:51:19.185 [main ] INFO HikariPool - HikariCP - Added connection org.postgresql.jdbc.PgConnection@192b472d
|
||||
25-07-29.09:51:19.187 [main ] INFO HikariDataSource - HikariCP - Start completed.
|
||||
25-07-29.09:51:22.365 [main ] INFO Version - Redisson 3.44.0
|
||||
25-07-29.09:51:22.643 [redisson-netty-1-4] INFO ConnectionsHolder - 1 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-29.09:51:22.681 [redisson-netty-1-13] INFO ConnectionsHolder - 5 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-29.09:51:23.441 [main ] INFO Http11NioProtocol - Starting ProtocolHandler ["http-nio-8095"]
|
||||
25-07-29.09:51:23.451 [main ] INFO TomcatWebServer - Tomcat started on port 8095 (http) with context path ''
|
||||
25-07-29.09:51:23.461 [main ] INFO Application - Started Application in 7.317 seconds (process running for 7.862)
|
||||
25-07-29.09:51:35.181 [http-nio-8095-exec-1] INFO [/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
|
||||
25-07-29.09:51:35.181 [http-nio-8095-exec-1] INFO DispatcherServlet - Initializing Servlet 'dispatcherServlet'
|
||||
25-07-29.09:51:35.182 [http-nio-8095-exec-1] INFO DispatcherServlet - Completed initialization in 0 ms
|
||||
25-07-29.09:51:35.206 [http-nio-8095-exec-1] INFO OllamaController - generate_stream called!
|
||||
25-07-29.09:53:05.853 [http-nio-8095-exec-5] INFO RAGController - 上传知识库开始:草稿1
|
||||
25-07-29.09:53:06.647 [http-nio-8095-exec-5] INFO RAGController - 上传知识库完成:草稿1
|
||||
25-07-29.09:53:30.890 [http-nio-8095-exec-7] INFO OllamaController - generate_stream_rag called!
|
||||
25-07-29.10:26:15.821 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariCP - Shutdown initiated...
|
||||
25-07-29.10:26:15.826 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariCP - Shutdown completed.
|
||||
25-07-29.10:27:07.258 [main ] INFO Application - Starting Application v1.0 using Java 17.0.2 with PID 1 (/app.jar started by root in /)
|
||||
25-07-29.10:27:07.263 [main ] INFO Application - The following 1 profile is active: "dev"
|
||||
25-07-29.10:27:08.167 [main ] INFO RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode
|
||||
25-07-29.10:27:08.171 [main ] INFO RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
|
||||
25-07-29.10:27:08.202 [main ] INFO RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 14 ms. Found 0 Redis repository interfaces.
|
||||
25-07-29.10:27:08.986 [main ] INFO TomcatWebServer - Tomcat initialized with port 8095 (http)
|
||||
25-07-29.10:27:08.998 [main ] INFO Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8095"]
|
||||
25-07-29.10:27:09.000 [main ] INFO StandardService - Starting service [Tomcat]
|
||||
25-07-29.10:27:09.000 [main ] INFO StandardEngine - Starting Servlet engine: [Apache Tomcat/10.1.19]
|
||||
25-07-29.10:27:09.047 [main ] INFO [/] - Initializing Spring embedded WebApplicationContext
|
||||
25-07-29.10:27:09.048 [main ] INFO ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1672 ms
|
||||
25-07-29.10:27:09.446 [main ] INFO HikariDataSource - HikariCP - Starting...
|
||||
25-07-29.10:27:09.630 [main ] INFO HikariPool - HikariCP - Added connection org.postgresql.jdbc.PgConnection@374b6e33
|
||||
25-07-29.10:27:09.632 [main ] INFO HikariDataSource - HikariCP - Start completed.
|
||||
25-07-29.10:27:10.270 [main ] INFO Version - Redisson 3.44.0
|
||||
25-07-29.10:27:10.505 [redisson-netty-1-4] INFO ConnectionsHolder - 1 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-29.10:27:10.546 [redisson-netty-1-13] INFO ConnectionsHolder - 5 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-29.10:27:11.294 [main ] INFO Http11NioProtocol - Starting ProtocolHandler ["http-nio-8095"]
|
||||
25-07-29.10:27:11.308 [main ] INFO TomcatWebServer - Tomcat started on port 8095 (http) with context path ''
|
||||
25-07-29.10:27:11.324 [main ] INFO Application - Started Application in 4.858 seconds (process running for 5.691)
|
||||
25-07-29.10:27:27.925 [http-nio-8095-exec-1] INFO [/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
|
||||
25-07-29.10:27:27.925 [http-nio-8095-exec-1] INFO DispatcherServlet - Initializing Servlet 'dispatcherServlet'
|
||||
25-07-29.10:27:27.927 [http-nio-8095-exec-1] INFO DispatcherServlet - Completed initialization in 1 ms
|
||||
25-07-29.10:27:27.977 [http-nio-8095-exec-1] INFO RAGController - 上传知识库开始:测试01
|
||||
25-07-29.10:27:28.578 [http-nio-8095-exec-1] INFO TextSplitter - Splitting up document into 2 chunks.
|
||||
25-07-29.10:27:29.993 [http-nio-8095-exec-1] INFO TextSplitter - Splitting up document into 15 chunks.
|
||||
25-07-29.10:27:43.442 [http-nio-8095-exec-1] INFO TextSplitter - Splitting up document into 6 chunks.
|
||||
25-07-29.10:27:48.303 [http-nio-8095-exec-1] INFO TextSplitter - Splitting up document into 13 chunks.
|
||||
25-07-29.10:27:59.867 [http-nio-8095-exec-1] INFO TextSplitter - Splitting up document into 5 chunks.
|
||||
25-07-29.10:28:03.851 [http-nio-8095-exec-1] INFO TextSplitter - Splitting up document into 18 chunks.
|
||||
25-07-29.10:28:20.184 [http-nio-8095-exec-1] INFO TextSplitter - Splitting up document into 2 chunks.
|
||||
25-07-29.10:28:22.057 [http-nio-8095-exec-1] INFO RAGController - 上传知识库完成:测试01
|
||||
25-07-29.10:37:27.151 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariCP - Shutdown initiated...
|
||||
25-07-29.10:37:27.153 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariCP - Shutdown completed.
|
||||
25-07-29.10:37:37.830 [main ] INFO Application - Starting Application v1.0 using Java 17.0.2 with PID 1 (/app.jar started by root in /)
|
||||
25-07-29.10:37:37.835 [main ] INFO Application - The following 1 profile is active: "dev"
|
||||
25-07-29.10:37:38.660 [main ] INFO RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode
|
||||
25-07-29.10:37:38.662 [main ] INFO RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
|
||||
25-07-29.10:37:38.689 [main ] INFO RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 11 ms. Found 0 Redis repository interfaces.
|
||||
25-07-29.10:37:39.369 [main ] INFO TomcatWebServer - Tomcat initialized with port 8095 (http)
|
||||
25-07-29.10:37:39.378 [main ] INFO Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8095"]
|
||||
25-07-29.10:37:39.380 [main ] INFO StandardService - Starting service [Tomcat]
|
||||
25-07-29.10:37:39.380 [main ] INFO StandardEngine - Starting Servlet engine: [Apache Tomcat/10.1.19]
|
||||
25-07-29.10:37:39.416 [main ] INFO [/] - Initializing Spring embedded WebApplicationContext
|
||||
25-07-29.10:37:39.416 [main ] INFO ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1477 ms
|
||||
25-07-29.10:37:39.774 [main ] INFO HikariDataSource - HikariCP - Starting...
|
||||
25-07-29.10:37:39.967 [main ] INFO HikariPool - HikariCP - Added connection org.postgresql.jdbc.PgConnection@cdbe995
|
||||
25-07-29.10:37:39.969 [main ] INFO HikariDataSource - HikariCP - Start completed.
|
||||
25-07-29.10:37:42.629 [main ] INFO Version - Redisson 3.44.0
|
||||
25-07-29.10:37:42.905 [redisson-netty-1-4] INFO ConnectionsHolder - 1 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-29.10:37:42.940 [redisson-netty-1-13] INFO ConnectionsHolder - 5 connections initialized for 192.168.10.218/192.168.10.218:26379
|
||||
25-07-29.10:37:43.452 [main ] INFO Http11NioProtocol - Starting ProtocolHandler ["http-nio-8095"]
|
||||
25-07-29.10:37:43.460 [main ] INFO TomcatWebServer - Tomcat started on port 8095 (http) with context path ''
|
||||
25-07-29.10:37:43.468 [main ] INFO Application - Started Application in 6.441 seconds (process running for 7.065)
|
||||
25-07-29.10:37:55.291 [http-nio-8095-exec-1] INFO [/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
|
||||
25-07-29.10:37:55.291 [http-nio-8095-exec-1] INFO DispatcherServlet - Initializing Servlet 'dispatcherServlet'
|
||||
25-07-29.10:37:55.292 [http-nio-8095-exec-1] INFO DispatcherServlet - Completed initialization in 1 ms
|
||||
25-07-29.10:37:55.337 [http-nio-8095-exec-1] INFO RAGController - 上传知识库开始:测试02
|
||||
25-07-29.10:37:55.992 [http-nio-8095-exec-1] INFO RAGController - 上传知识库完成:测试02
|
||||
25-07-29.10:49:25.780 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariCP - Shutdown initiated...
|
||||
25-07-29.10:49:25.783 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariCP - Shutdown completed.
|
@ -33,9 +33,28 @@
|
||||
<option value="openai" model="gpt-4o">gpt-4o</option>
|
||||
</select>
|
||||
|
||||
<select id="ragSelect" class="px-3 py-2 border rounded-lg flex-1 max-w-xs">
|
||||
<div class="flex items-center gap-2">
|
||||
<select id="ragSelect" class="px-3 py-2 border rounded-lg max-w-xs">
|
||||
<option value="">选择一个知识库</option>
|
||||
</select>
|
||||
|
||||
<!-- 手动刷新 RAG 列表 -->
|
||||
<button id="refreshRagBtn"
|
||||
class="px-2 py-2 rounded-lg border hover:bg-gray-100"
|
||||
title="刷新知识库列表"
|
||||
aria-label="刷新知识库列表">
|
||||
↻
|
||||
</button>
|
||||
|
||||
<!-- 删除当前选中的 RAG(不可逆) -->
|
||||
<button id="deleteRagBtn"
|
||||
class="px-3 py-2 rounded-lg border text-red-600 hover:bg-red-50 disabled:opacity-50"
|
||||
title="删除所选知识库(不可恢复)"
|
||||
aria-label="删除所选知识库"
|
||||
disabled>
|
||||
🗑 删除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
|
@ -1,4 +1,4 @@
|
||||
// ===== index.js (modified to auto-refresh RAG list after upload) =====
|
||||
// ===== index.js (with delete RAG support & auto-refresh + upload menu toggle) =====
|
||||
|
||||
const chatArea = document.getElementById('chatArea');
|
||||
const messageInput = document.getElementById('messageInput');
|
||||
@ -8,16 +8,20 @@ const chatList = document.getElementById('chatList');
|
||||
const welcomeMessage = document.getElementById('welcomeMessage');
|
||||
const toggleSidebarBtn = document.getElementById('toggleSidebar');
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
|
||||
// 新增:与 RAG 操作相关的 DOM
|
||||
const ragSelect = document.getElementById('ragSelect');
|
||||
const deleteRagBtn = document.getElementById('deleteRagBtn');
|
||||
const refreshRagBtn = document.getElementById('refreshRagBtn');
|
||||
|
||||
let currentEventSource = null;
|
||||
let currentChatId = null;
|
||||
|
||||
/** -------------------------
|
||||
* RAG 列表加载(提升为顶层可复用)
|
||||
* RAG 列表加载(可复用)
|
||||
* 支持可选预选值 preselectTag
|
||||
* ------------------------- */
|
||||
function loadRagOptions(preselectTag) {
|
||||
const ragSelect = document.getElementById('ragSelect');
|
||||
|
||||
return fetch('/api/v1/rag/query_rag_tag_list')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
@ -32,31 +36,143 @@ function loadRagOptions(preselectTag) {
|
||||
ragSelect.add(option);
|
||||
});
|
||||
|
||||
// 可选:预选刚创建的 ragTag
|
||||
if (preselectTag) {
|
||||
// 可选:预选刚创建/删除后保留的 tag
|
||||
if (preselectTag !== undefined) {
|
||||
const exists = Array.from(ragSelect.options).some(o => o.value === preselectTag);
|
||||
if (!exists) {
|
||||
ragSelect.add(new Option(`Rag:${preselectTag}`, preselectTag));
|
||||
}
|
||||
if (exists || preselectTag === '') {
|
||||
ragSelect.value = preselectTag;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('获取知识库列表失败:', error);
|
||||
})
|
||||
.finally(() => {
|
||||
// 根据当前是否选中某个 RAG,决定删除按钮是否可点
|
||||
if (deleteRagBtn) {
|
||||
deleteRagBtn.disabled = !ragSelect.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 获取知识库列表(首屏)
|
||||
/** -------------------------
|
||||
* 广播 RAG 更新(删除/新增后用于同步其它页面)
|
||||
* ------------------------- */
|
||||
function broadcastRagUpdate(optionalTag) {
|
||||
try {
|
||||
// A) postMessage:同源且 opener 存在时生效(比如从 index 打开的 upload 页)
|
||||
if (window.opener && !window.opener.closed) {
|
||||
window.opener.postMessage(
|
||||
{ type: 'RAG_LIST_REFRESH', ragTag: optionalTag },
|
||||
window.location.origin
|
||||
);
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
try {
|
||||
// B) BroadcastChannel:同源标签页广播
|
||||
if ('BroadcastChannel' in window) {
|
||||
const bc = new BroadcastChannel('rag-updates');
|
||||
bc.postMessage({ type: 'rag:updated', ragTag: optionalTag });
|
||||
bc.close();
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
/** -------------------------
|
||||
* 删除当前选择的 RAG
|
||||
* 调用后端 DELETE /api/v1/rag/knowledge/{ragTag}
|
||||
* ------------------------- */
|
||||
async function deleteCurrentRag() {
|
||||
const tag = ragSelect.value;
|
||||
if (!tag) {
|
||||
alert('请先从下拉框选择一个知识库。');
|
||||
return;
|
||||
}
|
||||
|
||||
const ok = confirm(
|
||||
`确认删除知识库「${tag}」吗?\n此操作不可恢复,将删除向量库中该知识库的所有数据。`
|
||||
);
|
||||
if (!ok) return;
|
||||
|
||||
setDeleteBtnBusy(true);
|
||||
|
||||
try {
|
||||
const resp = await fetch(`/api/v1/rag/knowledge/${encodeURIComponent(tag)}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
|
||||
if (resp.ok && data.code === '0000') {
|
||||
alert('删除成功');
|
||||
|
||||
// 删除后刷新下拉框,并清空选择
|
||||
await loadRagOptions(''); // 传空字符串,强制清空选择
|
||||
// 广播给其它页面(比如已打开的 upload.html 或其他 index)
|
||||
broadcastRagUpdate();
|
||||
} else {
|
||||
throw new Error(data.info || `HTTP ${resp.status}`);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('删除失败:' + (e?.message || '未知错误'));
|
||||
} finally {
|
||||
setDeleteBtnBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
/** -------------------------
|
||||
* 设置删除按钮忙碌状态
|
||||
* ------------------------- */
|
||||
function setDeleteBtnBusy(busy) {
|
||||
if (!deleteRagBtn) return;
|
||||
if (busy) {
|
||||
deleteRagBtn.disabled = true;
|
||||
deleteRagBtn.dataset.originalText = deleteRagBtn.textContent;
|
||||
deleteRagBtn.textContent = '删除中…';
|
||||
} else {
|
||||
deleteRagBtn.textContent =
|
||||
deleteRagBtn.dataset.originalText || '🗑 删除';
|
||||
deleteRagBtn.disabled = !ragSelect.value;
|
||||
}
|
||||
}
|
||||
|
||||
/** -------------------------
|
||||
* 事件绑定:刷新/删除/选择变化
|
||||
* ------------------------- */
|
||||
if (refreshRagBtn) {
|
||||
refreshRagBtn.addEventListener('click', () => {
|
||||
// 保留现有选择刷新(若后端删除了该tag,刷新后它自然消失)
|
||||
const keep = ragSelect.value || undefined;
|
||||
loadRagOptions(keep);
|
||||
});
|
||||
}
|
||||
|
||||
if (deleteRagBtn) {
|
||||
deleteRagBtn.addEventListener('click', deleteCurrentRag);
|
||||
}
|
||||
|
||||
if (ragSelect) {
|
||||
ragSelect.addEventListener('change', () => {
|
||||
if (deleteRagBtn) {
|
||||
deleteRagBtn.disabled = !ragSelect.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** -------------------------
|
||||
* 首屏加载 RAG 列表
|
||||
* ------------------------- */
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
loadRagOptions();
|
||||
});
|
||||
|
||||
/** -------------------------
|
||||
* 接收来自 upload.html 的刷新通知
|
||||
* 方式 A:postMessage
|
||||
* 方式 B:BroadcastChannel
|
||||
* 兜底:页面从后台切回前台自动刷新
|
||||
* 接收来自其它页面的 RAG 刷新通知
|
||||
* A:postMessage
|
||||
* B:BroadcastChannel
|
||||
* C:页面激活时兜底刷新
|
||||
* ------------------------- */
|
||||
// A. postMessage(当 upload.html 有 opener 时)
|
||||
window.addEventListener('message', (event) => {
|
||||
@ -67,7 +183,7 @@ window.addEventListener('message', (event) => {
|
||||
}
|
||||
});
|
||||
|
||||
// B. BroadcastChannel(不依赖 opener,现代浏览器有效)
|
||||
// B. BroadcastChannel(不依赖 opener)
|
||||
if ('BroadcastChannel' in window) {
|
||||
const bc = new BroadcastChannel('rag-updates');
|
||||
bc.addEventListener('message', (event) => {
|
||||
@ -79,19 +195,18 @@ if ('BroadcastChannel' in window) {
|
||||
}
|
||||
|
||||
// C. 兜底:页面重新获得焦点/可见时刷新一次
|
||||
window.addEventListener('focus', () => loadRagOptions());
|
||||
window.addEventListener('focus', () => loadRagOptions(ragSelect.value || undefined));
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (!document.hidden) loadRagOptions();
|
||||
if (!document.hidden) loadRagOptions(ragSelect.value || undefined);
|
||||
});
|
||||
|
||||
/** -------------------------
|
||||
* 聊天逻辑
|
||||
* 聊天逻辑(原有内容保留)
|
||||
* ------------------------- */
|
||||
function createNewChat() {
|
||||
const chatId = Date.now().toString();
|
||||
currentChatId = chatId;
|
||||
localStorage.setItem('currentChatId', chatId);
|
||||
// 修改数据结构为包含name和messages的对象
|
||||
localStorage.setItem(`chat_${chatId}`, JSON.stringify({
|
||||
name: '新聊天',
|
||||
messages: []
|
||||
@ -125,7 +240,6 @@ function updateChatList() {
|
||||
let chatData = JSON.parse(localStorage.getItem(chatKey));
|
||||
const chatId = chatKey.split('_')[1];
|
||||
|
||||
// 数据迁移:将旧数组格式转换为新对象格式
|
||||
if (Array.isArray(chatData)) {
|
||||
chatData = {
|
||||
name: `聊天 ${new Date(parseInt(chatId)).toLocaleDateString()}`,
|
||||
@ -135,11 +249,14 @@ function updateChatList() {
|
||||
}
|
||||
|
||||
const li = document.createElement('li');
|
||||
li.className = `chat-item flex items-center justify-between p-2 hover:bg-gray-100 rounded-lg cursor-pointer transition-colors ${chatId === currentChatId ? 'bg-blue-50' : ''}`;
|
||||
li.className =
|
||||
`chat-item flex items-center justify-between p-2 hover:bg-gray-100 rounded-lg cursor-pointer transition-colors ${chatId === currentChatId ? 'bg-blue-50' : ''}`;
|
||||
li.innerHTML = `
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium">${chatData.name}</div>
|
||||
<div class="text-xs text-gray-400">${new Date(parseInt(chatId)).toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' })}</div>
|
||||
<div class="text-xs text-gray-400">
|
||||
${new Date(parseInt(chatId)).toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' })}
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-actions flex items-center gap-1 opacity-0 transition-opacity duration-200">
|
||||
<button class="p-1 hover:bg-gray-200 rounded text-gray-500" onclick="renameChat('${chatId}')">重命名</button>
|
||||
@ -162,7 +279,6 @@ function updateChatList() {
|
||||
}
|
||||
|
||||
let currentContextMenu = null;
|
||||
// 优化后的上下文菜单
|
||||
function showChatContextMenu(event, chatId) {
|
||||
event.stopPropagation();
|
||||
closeContextMenu();
|
||||
@ -192,32 +308,27 @@ function showChatContextMenu(event, chatId) {
|
||||
document.body.appendChild(menu);
|
||||
currentContextMenu = menu;
|
||||
|
||||
// 点击外部关闭菜单
|
||||
setTimeout(() => {
|
||||
document.addEventListener('click', closeContextMenu, { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
function closeContextMenu() {
|
||||
if (currentContextMenu) {
|
||||
currentContextMenu.remove();
|
||||
currentContextMenu = null;
|
||||
}
|
||||
}
|
||||
|
||||
function renameChat(chatId) {
|
||||
const chatKey = `chat_${chatId}`;
|
||||
const chatData = JSON.parse(localStorage.getItem(chatKey));
|
||||
const currentName = chatData.name || `聊天 ${new Date(parseInt(chatId)).toLocaleString()}`;
|
||||
const newName = prompt('请输入新的聊天名称', currentName);
|
||||
|
||||
if (newName) {
|
||||
chatData.name = newName;
|
||||
localStorage.setItem(chatKey, JSON.stringify(chatData));
|
||||
updateChatList();
|
||||
}
|
||||
}
|
||||
|
||||
function loadChat(chatId) {
|
||||
currentChatId = chatId;
|
||||
localStorage.setItem('currentChatId', chatId);
|
||||
@ -228,21 +339,19 @@ function loadChat(chatId) {
|
||||
});
|
||||
updateChatList();
|
||||
}
|
||||
|
||||
function clearChatArea() {
|
||||
chatArea.innerHTML = '';
|
||||
welcomeMessage.style.display = 'flex';
|
||||
}
|
||||
|
||||
function appendMessage(content, isAssistant = false, saveToStorage = true) {
|
||||
welcomeMessage.style.display = 'none';
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `max-w-4xl mx-auto mb-4 p-4 rounded-lg ${isAssistant ? 'bg-gray-100' : 'bg-white border'} markdown-body relative`;
|
||||
messageDiv.className =
|
||||
`max-w-4xl mx-auto mb-4 p-4 rounded-lg ${isAssistant ? 'bg-gray-100' : 'bg-white border'} markdown-body relative`;
|
||||
|
||||
const renderedContent = DOMPurify.sanitize(marked.parse(content));
|
||||
messageDiv.innerHTML = renderedContent;
|
||||
|
||||
// 添加复制按钮
|
||||
const copyBtn = document.createElement('button');
|
||||
copyBtn.className = 'absolute top-2 right-2 p-1 bg-gray-200 rounded-md text-xs';
|
||||
copyBtn.textContent = '复制';
|
||||
@ -257,9 +366,10 @@ function appendMessage(content, isAssistant = false, saveToStorage = true) {
|
||||
chatArea.appendChild(messageDiv);
|
||||
chatArea.scrollTop = chatArea.scrollHeight;
|
||||
|
||||
// 仅在需要时保存到本地存储
|
||||
if (saveToStorage && currentChatId) {
|
||||
const chatData = JSON.parse(localStorage.getItem(`chat_${currentChatId}`) || '{"name": "新聊天", "messages": []}');
|
||||
const chatData = JSON.parse(
|
||||
localStorage.getItem(`chat_${currentChatId}`) || '{"name": "新聊天", "messages": []}'
|
||||
);
|
||||
chatData.messages.push({ content, isAssistant });
|
||||
localStorage.setItem(`chat_${currentChatId}`, JSON.stringify(chatData));
|
||||
}
|
||||
@ -270,8 +380,7 @@ function startEventStream(message) {
|
||||
currentEventSource.close();
|
||||
}
|
||||
|
||||
// 组装流式接口
|
||||
const ragTag = document.getElementById('ragSelect').value;
|
||||
const ragTag = ragSelect.value;
|
||||
const aiModelSelect = document.getElementById('aiModel');
|
||||
const aiModelValue = aiModelSelect.value; // openai / ollama
|
||||
const aiModelModel = aiModelSelect.options[aiModelSelect.selectedIndex].getAttribute('model');
|
||||
@ -298,7 +407,6 @@ function startEventStream(message) {
|
||||
const newContent = data.result.output.content;
|
||||
accumulatedContent += newContent;
|
||||
|
||||
// 首次创建临时消息容器
|
||||
if (!tempMessageDiv) {
|
||||
tempMessageDiv = document.createElement('div');
|
||||
tempMessageDiv.className = 'max-w-4xl mx-auto mb-4 p-4 rounded-lg bg-gray-100 markdown-body relative';
|
||||
@ -306,7 +414,6 @@ function startEventStream(message) {
|
||||
welcomeMessage.style.display = 'none';
|
||||
}
|
||||
|
||||
// 直接更新文本内容(先不解析 Markdown)
|
||||
tempMessageDiv.textContent = accumulatedContent;
|
||||
chatArea.scrollTop = chatArea.scrollHeight;
|
||||
}
|
||||
@ -314,11 +421,9 @@ function startEventStream(message) {
|
||||
if (data.result?.output?.properties?.finishReason === 'STOP') {
|
||||
currentEventSource.close();
|
||||
|
||||
// 流式传输完成后进行最终渲染
|
||||
const finalContent = accumulatedContent;
|
||||
tempMessageDiv.innerHTML = DOMPurify.sanitize(marked.parse(finalContent));
|
||||
|
||||
// 添加复制按钮
|
||||
const copyBtn = document.createElement('button');
|
||||
copyBtn.className = 'absolute top-2 right-2 p-1 bg-gray-200 rounded-md text-xs';
|
||||
copyBtn.textContent = '复制';
|
||||
@ -330,9 +435,10 @@ function startEventStream(message) {
|
||||
};
|
||||
tempMessageDiv.appendChild(copyBtn);
|
||||
|
||||
// 保存到本地存储
|
||||
if (currentChatId) {
|
||||
const chatData = JSON.parse(localStorage.getItem(`chat_${currentChatId}`) || '{"name": "新聊天", "messages": []}');
|
||||
const chatData = JSON.parse(
|
||||
localStorage.getItem(`chat_${currentChatId}`) || '{"name": "新聊天", "messages": []}'
|
||||
);
|
||||
chatData.messages.push({ content: finalContent, isAssistant: true });
|
||||
localStorage.setItem(`chat_${currentChatId}`, JSON.stringify(chatData));
|
||||
}
|
||||
@ -391,7 +497,7 @@ if (savedChatId) {
|
||||
loadChat(savedChatId);
|
||||
}
|
||||
|
||||
// Handle window resize for responsive design
|
||||
// Responsive
|
||||
window.addEventListener('resize', () => {
|
||||
if (window.innerWidth > 768) {
|
||||
sidebar.classList.remove('-translate-x-full');
|
||||
@ -400,33 +506,70 @@ window.addEventListener('resize', () => {
|
||||
}
|
||||
});
|
||||
|
||||
// Initial check for mobile devices
|
||||
if (window.innerWidth <= 768) {
|
||||
sidebar.classList.add('-translate-x-full');
|
||||
}
|
||||
|
||||
updateSidebarIcon();
|
||||
|
||||
// 上传知识下拉菜单控制
|
||||
/** -------------------------
|
||||
* 上传知识下拉菜单(保持你现有 HTML 结构)
|
||||
* - 点击按钮展开/收起菜单
|
||||
* - 点击外部区域/按下 Esc 关闭
|
||||
* - 点击菜单项后自动关闭
|
||||
* ------------------------- */
|
||||
(function initUploadMenu() {
|
||||
const uploadMenuButton = document.getElementById('uploadMenuButton');
|
||||
const uploadMenu = document.getElementById('uploadMenu');
|
||||
if (!uploadMenuButton || !uploadMenu) return;
|
||||
|
||||
// 切换菜单显示
|
||||
// 切换菜单显示/隐藏
|
||||
const toggleMenu = () => {
|
||||
uploadMenu.classList.toggle('hidden');
|
||||
};
|
||||
|
||||
// 显示菜单
|
||||
const openMenu = () => {
|
||||
uploadMenu.classList.remove('hidden');
|
||||
};
|
||||
|
||||
// 隐藏菜单
|
||||
const closeMenu = () => {
|
||||
uploadMenu.classList.add('hidden');
|
||||
};
|
||||
|
||||
// 点击按钮展开/收起
|
||||
uploadMenuButton.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
uploadMenu.classList.toggle('hidden');
|
||||
toggleMenu();
|
||||
});
|
||||
|
||||
// 点击外部区域关闭菜单
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!uploadMenu.contains(e.target) && e.target !== uploadMenuButton) {
|
||||
uploadMenu.classList.add('hidden');
|
||||
// 键盘辅助:Enter/Space 展开,Esc 关闭
|
||||
uploadMenuButton.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
openMenu();
|
||||
} else if (e.key === 'Escape') {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// 菜单项点击后关闭菜单
|
||||
document.querySelectorAll('#uploadMenu a').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
uploadMenu.classList.add('hidden');
|
||||
// 点击菜单项后自动关闭
|
||||
uploadMenu.querySelectorAll('a').forEach(a => {
|
||||
a.addEventListener('click', () => closeMenu());
|
||||
});
|
||||
|
||||
// 点击外部任意区域关闭
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!uploadMenu.classList.contains('hidden')) {
|
||||
if (!uploadMenu.contains(e.target) && e.target !== uploadMenuButton) {
|
||||
closeMenu();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Esc 关闭(全局)
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') closeMenu();
|
||||
});
|
||||
})();
|
||||
|
2
pom.xml
2
pom.xml
@ -18,7 +18,7 @@
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<spring-ai.version>0.8.1</spring-ai.version>
|
||||
<spring-ai.version>1.0.0-M6</spring-ai.version>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
|
Loading…
x
Reference in New Issue
Block a user