Initial commit: Fintec AI Framework with Agent, RAG, and MCP modules

This commit is contained in:
limqsh
2026-04-27 17:23:58 +08:00
parent a9a1441537
commit 69c5aacdc8
85 changed files with 7143 additions and 0 deletions

Binary file not shown.

View 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>

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
com.ccb.fintec.rag.autoconfigure.config.RagAutoConfiguration