Initial commit: Fintec AI Framework with Agent, RAG, and MCP modules
This commit is contained in:
BIN
fintec-framework-rag-spring-boot-autoconfigure/.DS_Store
vendored
Normal file
BIN
fintec-framework-rag-spring-boot-autoconfigure/.DS_Store
vendored
Normal file
Binary file not shown.
82
fintec-framework-rag-spring-boot-autoconfigure/pom.xml
Normal file
82
fintec-framework-rag-spring-boot-autoconfigure/pom.xml
Normal file
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.ccb.fintec</groupId>
|
||||
<artifactId>fintec-framework-parent</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>fintec-framework-rag-spring-boot-autoconfigure</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- 核心协议层 -->
|
||||
<dependency>
|
||||
<groupId>com.ccb.fintec</groupId>
|
||||
<artifactId>fintec-framework-ai-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot AutoConfigure -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Configuration Processor -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring AI Chat Client -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-client-chat</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring AI Advisors Vector Store(包含 QuestionAnswerAdvisor) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-advisors-vector-store</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Embedding 模型(以 OpenAI 为例,业务系统可替换) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-openai</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Vector Store(以 Chroma 为例,业务系统可替换) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-vector-store-chroma</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Vector Store(以 PGVector 为例,业务系统可替换) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- ETL: Document Readers (PDF, Word, etc.) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-pdf-document-reader</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-tika-document-reader</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.ccb.fintec.rag.autoconfigure.config;
|
||||
|
||||
import com.ccb.fintec.core.properties.ObservabilityProperties;
|
||||
import com.ccb.fintec.core.properties.RagProperties;
|
||||
import com.ccb.fintec.core.properties.SafetyProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* AI 核心配置属性绑定类(RAG 模块)
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "fintec.ai")
|
||||
public class AiCoreConfigProperties {
|
||||
|
||||
private RagProperties rag = new RagProperties();
|
||||
private SafetyProperties safety = new SafetyProperties();
|
||||
private ObservabilityProperties observability = new ObservabilityProperties();
|
||||
|
||||
public RagProperties getRag() {
|
||||
return rag;
|
||||
}
|
||||
|
||||
public void setRag(RagProperties rag) {
|
||||
this.rag = rag;
|
||||
}
|
||||
|
||||
public SafetyProperties getSafety() {
|
||||
return safety;
|
||||
}
|
||||
|
||||
public void setSafety(SafetyProperties safety) {
|
||||
this.safety = safety;
|
||||
}
|
||||
|
||||
public ObservabilityProperties getObservability() {
|
||||
return observability;
|
||||
}
|
||||
|
||||
public void setObservability(ObservabilityProperties observability) {
|
||||
this.observability = observability;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.ccb.fintec.rag.autoconfigure.config;
|
||||
|
||||
import com.ccb.fintec.rag.autoconfigure.image.ImageGenerationTemplate;
|
||||
import com.ccb.fintec.rag.autoconfigure.template.RagTemplate;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
|
||||
import org.springframework.ai.embedding.EmbeddingModel;
|
||||
import org.springframework.ai.image.ImageModel;
|
||||
import org.springframework.ai.vectorstore.SearchRequest;
|
||||
import org.springframework.ai.vectorstore.SimpleVectorStore;
|
||||
import org.springframework.ai.vectorstore.VectorStore;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* RAG 自动配置类
|
||||
*
|
||||
* 提供简化的 RAG 开发体验:
|
||||
* 1. 自动创建 RagTemplate,封装文档入库和知识库问答
|
||||
* 2. 内置公司级策略:分块大小、相似度阈值、TopK
|
||||
* 3. 支持图片生成能力
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass({VectorStore.class, EmbeddingModel.class, ChatClient.class})
|
||||
@EnableConfigurationProperties(AiCoreConfigProperties.class)
|
||||
public class RagAutoConfiguration {
|
||||
|
||||
/**
|
||||
* 默认向量存储:基于内存的 SimpleVectorStore
|
||||
* 适用于开发和测试环境,无需外部向量数据库
|
||||
* 生产环境可以替换为 Chroma、Milvus、PGVector 等
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(VectorStore.class)
|
||||
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
|
||||
return SimpleVectorStore.builder(embeddingModel).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 RetrievalAugmentationAdvisor(可选,供高级用户使用)
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public QuestionAnswerAdvisor questionAnswerAdvisor(VectorStore vectorStore,
|
||||
AiCoreConfigProperties properties) {
|
||||
return QuestionAnswerAdvisor.builder(vectorStore)
|
||||
.searchRequest(SearchRequest.builder()
|
||||
.topK(properties.getRag().getTopK())
|
||||
.similarityThreshold(properties.getRag().getSimilarityThreshold())
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* RagTemplate:业务系统的唯一入口
|
||||
* 封装了文档入库和知识库问答两个核心场景
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(RagTemplate.class)
|
||||
public RagTemplate ragTemplate(ChatClient chatClient,
|
||||
VectorStore vectorStore,
|
||||
AiCoreConfigProperties properties) {
|
||||
return new RagTemplate(chatClient, vectorStore, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* ImageGenerationTemplate:图片生成能力
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnBean(ImageModel.class)
|
||||
@ConditionalOnMissingBean
|
||||
public ImageGenerationTemplate imageGenerationTemplate(ImageModel imageModel) {
|
||||
return new ImageGenerationTemplate(imageModel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.ccb.fintec.rag.autoconfigure.image;
|
||||
|
||||
import org.springframework.ai.image.ImageModel;
|
||||
import org.springframework.ai.image.ImageOptions;
|
||||
import org.springframework.ai.image.ImageOptionsBuilder;
|
||||
import org.springframework.ai.image.ImagePrompt;
|
||||
import org.springframework.ai.image.ImageResponse;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* 图片生成模板 - 文本生成图片
|
||||
*/
|
||||
public class ImageGenerationTemplate {
|
||||
|
||||
private final ImageModel imageModel;
|
||||
|
||||
public ImageGenerationTemplate(ImageModel imageModel) {
|
||||
this.imageModel = imageModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文本描述生成图片
|
||||
* @param prompt 图片描述
|
||||
* @return 生成的图片URL字符串
|
||||
*/
|
||||
public String generate(String prompt) {
|
||||
ImageOptions options = ImageOptionsBuilder.builder()
|
||||
.build();
|
||||
|
||||
ImagePrompt imagePrompt = new ImagePrompt(prompt, options);
|
||||
ImageResponse response = imageModel.call(imagePrompt);
|
||||
|
||||
return response.getResult().getOutput().getUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文本描述生成图片(自定义参数)
|
||||
* @param prompt 图片描述
|
||||
* @param width 图片宽度(如: 1024)
|
||||
* @param height 图片高度(如: 1024)
|
||||
* @return 生成的图片URL字符串
|
||||
*/
|
||||
public String generateWithSize(String prompt, int width, int height) {
|
||||
ImageOptions options = ImageOptionsBuilder.builder()
|
||||
.width(width)
|
||||
.height(height)
|
||||
.build();
|
||||
|
||||
ImagePrompt imagePrompt = new ImagePrompt(prompt, options);
|
||||
ImageResponse response = imageModel.call(imagePrompt);
|
||||
|
||||
return response.getResult().getOutput().getUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量生成图片
|
||||
* @param prompt 图片描述
|
||||
* @param numImages 生成数量
|
||||
* @return 生成的图片URL数组
|
||||
*/
|
||||
public String[] generateMultiple(String prompt, int numImages) {
|
||||
ImageOptions options = ImageOptionsBuilder.builder()
|
||||
.N(numImages)
|
||||
.build();
|
||||
|
||||
ImagePrompt imagePrompt = new ImagePrompt(prompt, options);
|
||||
ImageResponse response = imageModel.call(imagePrompt);
|
||||
|
||||
return response.getResults().stream()
|
||||
.map(result -> result.getOutput().getUrl())
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package com.ccb.fintec.rag.autoconfigure.template;
|
||||
|
||||
import com.ccb.fintec.core.dto.AiResponse;
|
||||
import com.ccb.fintec.rag.autoconfigure.config.AiCoreConfigProperties;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
|
||||
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
|
||||
import org.springframework.ai.vectorstore.SearchRequest;
|
||||
import org.springframework.ai.vectorstore.VectorStore;
|
||||
import org.springframework.core.io.Resource;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* RAG 核心入口
|
||||
* 封装:文档入库 + 知识库问答两个核心场景
|
||||
*/
|
||||
public class RagTemplate {
|
||||
|
||||
private final ChatClient chatClient;
|
||||
private final VectorStore vectorStore;
|
||||
private final int chunkSize;
|
||||
private final int chunkOverlap;
|
||||
private final int topK;
|
||||
private final double similarityThreshold;
|
||||
|
||||
public RagTemplate(ChatClient chatClient, VectorStore vectorStore, AiCoreConfigProperties properties) {
|
||||
this.chatClient = chatClient;
|
||||
this.vectorStore = vectorStore;
|
||||
this.chunkSize = properties.getRag().getChunkSize();
|
||||
this.chunkOverlap = properties.getRag().getChunkOverlap();
|
||||
this.topK = properties.getRag().getTopK();
|
||||
this.similarityThreshold = properties.getRag().getSimilarityThreshold();
|
||||
}
|
||||
|
||||
// ========== 文档入库 ==========
|
||||
|
||||
/** PDF 文档入库 */
|
||||
public void ingest(Resource pdfResource) {
|
||||
var reader = new PagePdfDocumentReader(pdfResource);
|
||||
var splitter = new TokenTextSplitter(chunkSize, chunkOverlap, 5, 10000, true);
|
||||
List<Document> docs = splitter.apply(reader.read());
|
||||
vectorStore.add(docs);
|
||||
}
|
||||
|
||||
/** 直接传入 Document 列表入库(支持任意格式) */
|
||||
public void ingest(List<Document> documents) {
|
||||
var splitter = new TokenTextSplitter(chunkSize, chunkOverlap, 5, 10000, true);
|
||||
vectorStore.add(splitter.apply(documents));
|
||||
}
|
||||
|
||||
/** 直接传入纯文本入库 */
|
||||
public void ingest(String content, String source) {
|
||||
Document doc = new Document(content, Map.of("source", source));
|
||||
vectorStore.add(List.of(doc));
|
||||
}
|
||||
|
||||
// ========== 知识库问答 ==========
|
||||
|
||||
/** RAG 问答:自动检索相关文档注入 prompt */
|
||||
public String ask(String question) {
|
||||
return chatClient.prompt()
|
||||
.user(question)
|
||||
.advisors(QuestionAnswerAdvisor.builder(vectorStore)
|
||||
.searchRequest(SearchRequest.builder()
|
||||
.topK(topK)
|
||||
.similarityThreshold(similarityThreshold)
|
||||
.build())
|
||||
.build())
|
||||
.call()
|
||||
.content();
|
||||
}
|
||||
|
||||
/** RAG 问答 + 会话记忆 */
|
||||
public String ask(String question, String conversationId) {
|
||||
return chatClient.prompt()
|
||||
.user(question)
|
||||
.advisors(QuestionAnswerAdvisor.builder(vectorStore)
|
||||
.searchRequest(SearchRequest.builder()
|
||||
.topK(topK)
|
||||
.similarityThreshold(similarityThreshold)
|
||||
.build())
|
||||
.build())
|
||||
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
|
||||
.call()
|
||||
.content();
|
||||
}
|
||||
|
||||
/** RAG 流式输出 */
|
||||
public Flux<String> stream(String question) {
|
||||
return chatClient.prompt()
|
||||
.user(question)
|
||||
.advisors(QuestionAnswerAdvisor.builder(vectorStore)
|
||||
.searchRequest(SearchRequest.builder()
|
||||
.topK(topK)
|
||||
.similarityThreshold(similarityThreshold)
|
||||
.build())
|
||||
.build())
|
||||
.stream()
|
||||
.content();
|
||||
}
|
||||
|
||||
/** 自定义相似度阈值和 TopK */
|
||||
public String askWithConfig(String question, double similarityThreshold, int topK) {
|
||||
return chatClient.prompt()
|
||||
.user(question)
|
||||
.advisors(QuestionAnswerAdvisor.builder(vectorStore)
|
||||
.searchRequest(SearchRequest.builder()
|
||||
.similarityThreshold(similarityThreshold)
|
||||
.topK(topK)
|
||||
.build())
|
||||
.build())
|
||||
.call()
|
||||
.content();
|
||||
}
|
||||
|
||||
// ========== 元数据收集 ==========
|
||||
|
||||
/** RAG 问答返回 AiResponse(包含 Token、耗时、检索文档数) */
|
||||
public AiResponse askWithMetadata(String question) {
|
||||
String requestId = UUID.randomUUID().toString();
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
ChatResponse response = chatClient.prompt()
|
||||
.user(question)
|
||||
.advisors(QuestionAnswerAdvisor.builder(vectorStore)
|
||||
.searchRequest(SearchRequest.builder()
|
||||
.topK(topK)
|
||||
.similarityThreshold(similarityThreshold)
|
||||
.build())
|
||||
.build())
|
||||
.call()
|
||||
.chatResponse();
|
||||
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
|
||||
AiResponse aiResponse = new AiResponse();
|
||||
aiResponse.setRequestId(requestId);
|
||||
aiResponse.setContent(response.getResult().getOutput().getText());
|
||||
aiResponse.setDuration(duration);
|
||||
|
||||
// 设置 Token 使用量
|
||||
if (response.getMetadata() != null && response.getMetadata().getUsage() != null) {
|
||||
var usage = response.getMetadata().getUsage();
|
||||
AiResponse.TokenUsage tokenUsage = new AiResponse.TokenUsage(
|
||||
usage.getPromptTokens(),
|
||||
usage.getCompletionTokens(),
|
||||
usage.getTotalTokens()
|
||||
);
|
||||
aiResponse.setTokenUsage(tokenUsage);
|
||||
}
|
||||
|
||||
// 添加 RAG 特有的元数据:检索到的文档数
|
||||
aiResponse.getMetadata().put("retrievedDocuments",
|
||||
response.getResults() != null ? response.getResults().size() : 0);
|
||||
|
||||
return aiResponse;
|
||||
} catch (Exception e) {
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
AiResponse errorResponse = new AiResponse();
|
||||
errorResponse.setRequestId(requestId);
|
||||
errorResponse.setDuration(duration);
|
||||
|
||||
AiResponse.ErrorInfo errorInfo = new AiResponse.ErrorInfo(
|
||||
"RAG_CALL_FAILED",
|
||||
e.getMessage(),
|
||||
e.getClass().getSimpleName()
|
||||
);
|
||||
errorResponse.setError(errorInfo);
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 逃生通道 ==========
|
||||
|
||||
public VectorStore getVectorStore() {
|
||||
return vectorStore;
|
||||
}
|
||||
|
||||
public ChatClient getChatClient() {
|
||||
return chatClient;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
com.ccb.fintec.rag.autoconfigure.config.RagAutoConfiguration
|
||||
Reference in New Issue
Block a user