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; |
|||
|
|||
@ -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