15 changed files with 320 additions and 51 deletions
-
26pom.xml
-
6src/main/java/com/wok/supportbot/SupportBotApplication.java
-
34src/main/java/com/wok/supportbot/app/AssistantApp.java
-
4src/main/java/com/wok/supportbot/app/ProductInfoApp.java
-
26src/main/java/com/wok/supportbot/demo/invoke/SpringAiAiInvoke.java
-
2src/main/java/com/wok/supportbot/entity/ProductInfo.java
-
53src/main/java/com/wok/supportbot/extract/MarkdownDocumentLoader.java
-
37src/main/java/com/wok/supportbot/extract/MyJsonReader.java
-
25src/main/java/com/wok/supportbot/load/InMemoryVectorStoreConfig.java
-
38src/main/java/com/wok/supportbot/load/PgVectorStoreConfig.java
-
14src/main/java/com/wok/supportbot/record/AssistantReport.java
-
29src/main/java/com/wok/supportbot/transform/MyKeywordEnricher.java
-
34src/main/java/com/wok/supportbot/transform/MyTokenTextSplitter.java
-
32src/test/java/com/wok/supportbot/PgVectorVectorStoreConfigTest.java
-
11src/test/java/com/wok/supportbot/SupportBotApplicationTests.java
@ -1,26 +0,0 @@ |
|||||
package com.wok.supportbot.demo.invoke; |
|
||||
|
|
||||
import jakarta.annotation.Resource; |
|
||||
import org.springframework.ai.chat.messages.AssistantMessage; |
|
||||
import org.springframework.ai.chat.model.ChatModel; |
|
||||
import org.springframework.ai.chat.prompt.Prompt; |
|
||||
import org.springframework.boot.CommandLineRunner; |
|
||||
import org.springframework.stereotype.Component; |
|
||||
|
|
||||
/** |
|
||||
* Spring AI 框架调用 AI 大模型(阿里) |
|
||||
*/ |
|
||||
@Component |
|
||||
public class SpringAiAiInvoke implements CommandLineRunner { |
|
||||
|
|
||||
@Resource |
|
||||
private ChatModel dashscopeChatModel; |
|
||||
|
|
||||
@Override |
|
||||
public void run(String... args) throws Exception { |
|
||||
AssistantMessage assistantMessage = dashscopeChatModel.call(new Prompt("hello")) |
|
||||
.getResult() |
|
||||
.getOutput(); |
|
||||
System.out.println(assistantMessage.getText()); |
|
||||
} |
|
||||
} |
|
||||
@ -1,4 +1,4 @@ |
|||||
package com.wok.supportbot.record; |
|
||||
|
package com.wok.supportbot.entity; |
||||
|
|
||||
import lombok.Data; |
import lombok.Data; |
||||
|
|
||||
@ -0,0 +1,53 @@ |
|||||
|
package com.wok.supportbot.extract; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.ai.document.Document; |
||||
|
|
||||
|
import org.springframework.ai.reader.markdown.MarkdownDocumentReader; |
||||
|
import org.springframework.ai.reader.markdown.config.MarkdownDocumentReaderConfig; |
||||
|
import org.springframework.core.io.Resource; |
||||
|
import org.springframework.core.io.support.ResourcePatternResolver; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* 文档加载器 |
||||
|
*/ |
||||
|
@Component |
||||
|
@Slf4j |
||||
|
public class MarkdownDocumentLoader { |
||||
|
|
||||
|
private final ResourcePatternResolver resourcePatternResolver; |
||||
|
|
||||
|
public MarkdownDocumentLoader(ResourcePatternResolver resourcePatternResolver) { |
||||
|
this.resourcePatternResolver = resourcePatternResolver; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 加载多篇 Markdown 文档 |
||||
|
* @return |
||||
|
*/ |
||||
|
public List<Document> loadMarkdowns() { |
||||
|
List<Document> allDocuments = new ArrayList<>(); |
||||
|
try { |
||||
|
Resource[] resources = resourcePatternResolver.getResources("classpath:document/*.md"); |
||||
|
for (Resource resource : resources) { |
||||
|
String filename = resource.getFilename(); |
||||
|
MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder() |
||||
|
.withHorizontalRuleCreateDocument(true) |
||||
|
.withIncludeCodeBlock(false) |
||||
|
.withIncludeBlockquote(false) |
||||
|
.withAdditionalMetadata("filename", filename) |
||||
|
.build(); |
||||
|
MarkdownDocumentReader markdownDocumentReader = new MarkdownDocumentReader(resource, config); |
||||
|
allDocuments.addAll(markdownDocumentReader.get()); |
||||
|
} |
||||
|
} catch (IOException e) { |
||||
|
log.error("Markdown 文档加载失败", e); |
||||
|
} |
||||
|
return allDocuments; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
package com.wok.supportbot.extract; |
||||
|
|
||||
|
import org.springframework.ai.document.Document; |
||||
|
import org.springframework.ai.reader.JsonReader; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.core.io.Resource; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
// 从 classpath 下的 JSON 文件中读取文档 |
||||
|
@Component |
||||
|
class MyJsonReader { |
||||
|
private final Resource resource; |
||||
|
|
||||
|
MyJsonReader(@Value("classpath:products.json") Resource resource) { |
||||
|
this.resource = resource; |
||||
|
} |
||||
|
|
||||
|
// 基本用法 |
||||
|
List<Document> loadBasicJsonDocuments() { |
||||
|
JsonReader jsonReader = new JsonReader(this.resource); |
||||
|
return jsonReader.get(); |
||||
|
} |
||||
|
|
||||
|
// 指定使用哪些 JSON 字段作为文档内容 |
||||
|
List<Document> loadJsonWithSpecificFields() { |
||||
|
JsonReader jsonReader = new JsonReader(this.resource, "description", "features"); |
||||
|
return jsonReader.get(); |
||||
|
} |
||||
|
|
||||
|
// 使用 JSON 指针精确提取文档内容 |
||||
|
List<Document> loadJsonWithPointer() { |
||||
|
JsonReader jsonReader = new JsonReader(this.resource); |
||||
|
return jsonReader.get("/items"); // 提取 items 数组内的内容 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
package com.wok.supportbot.load; |
||||
|
|
||||
|
import com.wok.supportbot.extract.MarkdownDocumentLoader; |
||||
|
import jakarta.annotation.Resource; |
||||
|
import org.springframework.ai.document.Document; |
||||
|
import org.springframework.ai.embedding.EmbeddingModel; |
||||
|
import org.springframework.ai.vectorstore.SimpleVectorStore; |
||||
|
import org.springframework.ai.vectorstore.VectorStore; |
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* 向量数据库配置(初始化基于内存的向量数据库 Bean) |
||||
|
*/ |
||||
|
@Configuration |
||||
|
public class InMemoryVectorStoreConfig { |
||||
|
|
||||
|
@Bean |
||||
|
VectorStore inMemoryVectorStore(EmbeddingModel dashscopeEmbeddingModel) { |
||||
|
SimpleVectorStore simpleVectorStore = SimpleVectorStore.builder(dashscopeEmbeddingModel).build();; |
||||
|
return simpleVectorStore; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
package com.wok.supportbot.load; |
||||
|
|
||||
|
import com.wok.supportbot.extract.MarkdownDocumentLoader; |
||||
|
import jakarta.annotation.Resource; |
||||
|
import org.springframework.ai.document.Document; |
||||
|
import org.springframework.ai.embedding.EmbeddingModel; |
||||
|
import org.springframework.ai.vectorstore.VectorStore; |
||||
|
import org.springframework.ai.vectorstore.pgvector.PgVectorStore; |
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.context.annotation.Primary; |
||||
|
import org.springframework.jdbc.core.JdbcTemplate; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
import static org.springframework.ai.vectorstore.pgvector.PgVectorStore.PgDistanceType.COSINE_DISTANCE; |
||||
|
import static org.springframework.ai.vectorstore.pgvector.PgVectorStore.PgIndexType.HNSW; |
||||
|
/** |
||||
|
* 向量数据库配置(初始化基于pgsql的向量数据库 Bean) |
||||
|
*/ |
||||
|
@Configuration |
||||
|
public class PgVectorStoreConfig { |
||||
|
|
||||
|
@Bean |
||||
|
@Primary |
||||
|
public VectorStore pgVectorVectorStore(JdbcTemplate jdbcTemplate, EmbeddingModel dashscopeEmbeddingModel) { |
||||
|
VectorStore vectorStore = PgVectorStore.builder(jdbcTemplate, dashscopeEmbeddingModel) |
||||
|
.dimensions(1536) // Optional: defaults to model dimensions or 1536 |
||||
|
.distanceType(COSINE_DISTANCE) // Optional: defaults to COSINE_DISTANCE |
||||
|
.indexType(HNSW) // Optional: defaults to HNSW |
||||
|
.initializeSchema(true) // Optional: defaults to false |
||||
|
.schemaName("public") // Optional: defaults to "public" |
||||
|
.vectorTableName("vector_store") // Optional: defaults to "vector_store" |
||||
|
.maxDocumentBatchSize(10000) // Optional: defaults to 10000 |
||||
|
.build(); |
||||
|
return vectorStore; |
||||
|
} |
||||
|
} |
||||
@ -1,14 +0,0 @@ |
|||||
package com.wok.supportbot.record; |
|
||||
|
|
||||
import java.util.List; |
|
||||
|
|
||||
/** |
|
||||
* @Classname AssistantReport |
|
||||
* @Description |
|
||||
* @Version 1.0.0 |
|
||||
* @Date 2025/06/27 14:21 |
|
||||
* @Author lyx |
|
||||
*/ |
|
||||
|
|
||||
public record AssistantReport(String title, List<String> suggestions) { |
|
||||
} |
|
||||
@ -0,0 +1,29 @@ |
|||||
|
package com.wok.supportbot.transform; |
||||
|
|
||||
|
import jakarta.annotation.Resource; |
||||
|
import org.springframework.ai.chat.model.ChatModel; |
||||
|
import org.springframework.ai.document.Document; |
||||
|
import org.springframework.ai.transformer.KeywordMetadataEnricher; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* 基于 AI 的文档元信息增强器(为文档补充元信息) |
||||
|
*/ |
||||
|
@Component |
||||
|
public class MyKeywordEnricher { |
||||
|
|
||||
|
@Resource |
||||
|
private ChatModel dashscopeChatModel; |
||||
|
|
||||
|
/** |
||||
|
* 使用 AI 提取关键词并添加到元数据 |
||||
|
* @param documents |
||||
|
* @return |
||||
|
*/ |
||||
|
public List<Document> enrichDocuments(List<Document> documents) { |
||||
|
KeywordMetadataEnricher keywordMetadataEnricher = new KeywordMetadataEnricher(dashscopeChatModel, 5); |
||||
|
return keywordMetadataEnricher.apply(documents); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
package com.wok.supportbot.transform; |
||||
|
|
||||
|
import org.springframework.ai.document.Document; |
||||
|
import org.springframework.ai.transformer.splitter.TokenTextSplitter; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* 自定义基于 Token 的切词器 |
||||
|
*/ |
||||
|
@Component |
||||
|
class MyTokenTextSplitter { |
||||
|
|
||||
|
/** |
||||
|
* 使用默认设置创建分割器。 |
||||
|
* @param documents |
||||
|
* @return |
||||
|
*/ |
||||
|
public List<Document> splitDocuments(List<Document> documents) { |
||||
|
TokenTextSplitter splitter = new TokenTextSplitter(); |
||||
|
return splitter.apply(documents); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 使用自定义参数创建分割器,通过调整参数,可以控制分割的粒度和方式,适应不同的应用场景。 |
||||
|
* @param documents |
||||
|
* @return |
||||
|
*/ |
||||
|
public List<Document> splitCustomized(List<Document> documents) { |
||||
|
TokenTextSplitter splitter = new TokenTextSplitter(200, 100, 10, 5000, true); |
||||
|
return splitter.apply(documents); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
package com.wok.supportbot; |
||||
|
|
||||
|
import jakarta.annotation.Resource; |
||||
|
import org.junit.jupiter.api.Assertions; |
||||
|
import org.junit.jupiter.api.Test; |
||||
|
import org.springframework.ai.document.Document; |
||||
|
import org.springframework.ai.vectorstore.SearchRequest; |
||||
|
import org.springframework.ai.vectorstore.VectorStore; |
||||
|
import org.springframework.boot.test.context.SpringBootTest; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
@SpringBootTest |
||||
|
public class PgVectorVectorStoreConfigTest { |
||||
|
|
||||
|
@Resource |
||||
|
VectorStore pgVectorVectorStore; |
||||
|
|
||||
|
@Test |
||||
|
void test() { |
||||
|
List<Document> documents = List.of( |
||||
|
new Document("Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!!", Map.of("meta1", "meta1")), |
||||
|
new Document("The World is Big and Salvation Lurks Around the Corner"), |
||||
|
new Document("You walk forward facing the past and you turn back toward the future.", Map.of("meta2", "meta2"))); |
||||
|
// 添加文档 |
||||
|
pgVectorVectorStore.add(documents); |
||||
|
// 相似度查询 |
||||
|
List<Document> results = pgVectorVectorStore.similaritySearch(SearchRequest.builder().query("Spring").topK(5).build()); |
||||
|
Assertions.assertNotNull(results); |
||||
|
} |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue