Spring AI 深度实践教程:从“能用”到“用好”

一、核心进阶能力全景图

text

复制代码
┌─────────────────────────────────────────────────────┐
│                 生产级 Spring AI 应用                 │
├─────────────────────────────────────────────────────┤
│ ① MCP (Model Context Protocol)   - 标准化工具集成    │
│ ② 动态工具注册                      - 运行时扩展能力    │
│ ③ LLM as Judge                     - 自评估与纠偏     │
│ ④ 高级 RAG                         - 混合检索+重排序  │
│ ⑤ 多模态 Embedding                  - 图文向量化       │
│ ⑥ 向量数据库高级特性                 - 过滤+HNSW+分区  │
│ ⑦ 自定义 ETL 管道                   - 生产级数据清洗   │
│ ⑧ Agent 编排器+工作流               - 复杂任务拆解     │
└─────────────────────────────────────────────────────┘

二、MCP(Model Context Protocol)深度实践

2.1 什么是 MCP?

MCP 是 Spring AI 1.0+ 引入的标准化工具协议,让 AI 模型能够以统一方式调用外部工具(数据库、API、文件系统等)。

2.2 实现一个 MCP Server(提供工具)

java

复制代码
// 1. 定义工具接口
@McpTool(name = "database-query", description = "查询数据库")
@Component
public class DatabaseQueryTool {
    
    @McpToolFunction(description = "根据 SQL 查询数据")
    public String query(@McpToolParam(description = "SQL 语句") String sql) {
        // 实际执行 SQL(带防注入)
        return jdbcTemplate.queryForList(sql).toString();
    }
    
    @McpToolFunction(description = "获取表结构")
    public List<String> getTableSchema(@McpToolParam String tableName) {
        return jdbcTemplate.queryForList("DESC " + tableName, String.class);
    }
}

// 2. 配置 MCP Server(application.yml)
spring:
  ai:
    mcp:
      server:
        enabled: true
        name: "database-mcp-server"
        version: "1.0"
        transport: "stdio"  # 或 sse, streamable-http

2.3 MCP Client 调用(在 ChatClient 中使用)

java

复制代码
@Service
public class McpAIService {
    
    @Autowired
    private McpClient mcpClient;
    
    public String askWithTools(String userQuestion) {
        // MCP Client 会自动发现并注册所有 @McpTool
        return chatClient.prompt()
            .user(userQuestion)
            .tools(mcpClient.getToolCallbacks())  // 注入 MCP 工具
            .call()
            .content();
    }
}

2.4 动态工具更新(运行时注册新工具)

java

复制代码
// 场景:用户上传自定义函数,动态注册
@PostMapping("/register-tool")
public void registerCustomTool(@RequestBody ToolDefinition toolDef) {
    McpTool dynamicTool = McpTool.builder()
        .name(toolDef.getName())
        .description(toolDef.getDesc())
        .inputSchema(toolDef.getSchema())
        .executor(params -> executeGroovyScript(toolDef.getScript(), params))
        .build();
    
    mcpClient.registerTool(dynamicTool);  // 运行时注册
}

三、LLM as Judge:自评估与质量保证

3.1 实现一个评估器(Judge)

java

复制代码
@Component
public class ResponseJudge {
    
    private final ChatClient judgeClient;
    
    public ResponseJudge(ChatClient.Builder builder) {
        this.judgeClient = builder
            .defaultSystem("""
                你是严格的回答质量评估专家。评估标准:
                1. 相关性(0-10分):是否直接回答问题
                2. 准确性(0-10分):事实是否正确
                3. 完整性(0-10分):是否遗漏关键信息
                4. 幻觉检测(是/否):是否存在编造内容
                
                输出格式 JSON:
                {"scores": {"relevance":0, "accuracy":0, "completeness":0}, 
                 "hallucination": false, "reason": "..."}
                """)
            .build();
    }
    
    public JudgeResult evaluate(String question, String answer, String context) {
        return judgeClient.prompt()
            .user("问题:%s\n上下文:%s\n答案:%s", question, context, answer)
            .call()
            .entity(JudgeResult.class);  // 结构化输出
    }
}

3.2 在 RAG 中自动重试(低分重新生成)

java

复制代码
@PostMapping("/rag-with-judge")
public String robustRag(@RequestBody String question) {
    int maxRetries = 3;
    for (int i = 0; i < maxRetries; i++) {
        String answer = basicRag(question);
        JudgeResult judge = judge.evaluate(question, answer, retrievedContext);
        
        if (judge.scores().relevance() > 7 && !judge.hallucination()) {
            return answer;
        }
        log.warn("回答质量不达标,重试 {}/{},原因:{}", i+1, maxRetries, judge.reason());
    }
    return "无法生成高质量回答,请简化问题。";
}

四、高级 RAG:混合检索 + 重排序

4.1 架构设计

text

复制代码
用户问题 → 关键词检索(BM25) + 向量检索(Embedding) → 合并去重 → 重排序模型 → Top-K → LLM 生成

4.2 实现混合检索器

java

复制代码
@Component
public class HybridRetriever {
    
    @Autowired
    private VectorStore vectorStore;
    
    @Autowired
    private ElasticsearchRestTemplate esTemplate;
    
    public List<Document> hybridSearch(String query, int topK) {
        // 1. 向量检索
        List<Document> vectorResults = vectorStore.similaritySearch(
            SearchRequest.query(query).withTopK(topK * 2)
        );
        
        // 2. BM25 关键词检索
        List<Document> keywordResults = esTemplate.search(
            NativeQuery.builder()
                .withQuery(QueryBuilders.matchQuery("content", query))
                .withPageable(PageRequest.of(0, topK * 2))
                .build(),
            Document.class
        ).get().map(SearchHit::getContent).toList();
        
        // 3. 合并去重(基于文档ID)
        Map<String, Document> merged = new LinkedHashMap<>();
        vectorResults.forEach(d -> merged.put(d.getId(), d));
        keywordResults.forEach(d -> merged.putIfAbsent(d.getId(), d));
        
        // 4. 重排序(使用 Cross-Encoder)
        return rerank(query, new ArrayList<>(merged.values()), topK);
    }
    
    private List<Document> rerank(String query, List<Document> docs, int topK) {
        // 调用本地 Cross-Encoder 模型(如 BAAI/bge-reranker-base)
        List<ScorePair> scores = crossEncoderModel.encode(query, docs);
        return scores.stream()
            .sorted((a,b) -> Float.compare(b.score(), a.score()))
            .limit(topK)
            .map(ScorePair::document)
            .collect(Collectors.toList());
    }
}

4.3 在 Advisor 中使用混合检索

java

复制代码
@Configuration
public class RAGConfig {
    
    @Bean
    public QuestionAnswerAdvisor hybridRagAdvisor(HybridRetriever retriever) {
        return QuestionAnswerAdvisor.builder()
            .retrievalFunction(retriever::hybridSearch)
            .build();
    }
}

五、向量数据库高级特性(以 PGVector 为例)

5.1 元数据过滤 + HNSW 索引

java

复制代码
// 创建带元数据的文档
Document doc = new Document("内容", Map.of(
    "category", "法律",
    "publishYear", 2024,
    "author", "张三"
));

// 查询时使用 SQL-like 过滤
List<Document> results = vectorStore.similaritySearch(
    SearchRequest
        .query("劳动合同纠纷")
        .withTopK(5)
        .withSimilarityThreshold(0.7)
        .withFilterExpression("category == '法律' && publishYear >= 2020")
);

// 手动创建 HNSW 索引(性能提升 10x)
@Component
public class VectorIndexInitializer {
    @EventListener(ApplicationReadyEvent.class)
    public void initIndex() {
        jdbcTemplate.execute("""
            CREATE INDEX IF NOT EXISTS law_hnsw_idx 
            ON vector_store 
            USING hnsw (embedding vector_cosine_ops)
            WITH (m = 16, ef_construction = 64);
        """);
    }
}

5.2 分区表策略(按月/按类别)

sql

复制代码
-- 按月分区
CREATE TABLE documents_2024_01 PARTITION OF vector_store
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');

-- 查询时自动分区裁剪
SELECT * FROM vector_store 
WHERE publish_date >= '2024-01-01' AND publish_date < '2024-02-01'
ORDER BY embedding <=> '[0.1, 0.2, ...]' LIMIT 10;

六、生产级 ETL 管道

6.1 自定义 DocumentTransformer(敏感信息脱敏)

java

复制代码
@Component
public class SensitiveInfoTransformer implements DocumentTransformer {
    
    private final Pattern idCardPattern = Pattern.compile("\\d{17}[\\dXx]");
    private final Pattern phonePattern = Pattern.compile("1[3-9]\\d{9}");
    
    @Override
    public List<Document> transform(List<Document> documents) {
        return documents.stream()
            .map(doc -> {
                String content = doc.getText();
                content = idCardPattern.matcher(content).replaceAll("***");
                content = phonePattern.matcher(content).replaceAll("***");
                doc.setText(content);
                return doc;
            })
            .collect(Collectors.toList());
    }
    
    @Override
    public int getOrder() {
        return 0;  // 优先执行
    }
}

6.2 完整 ETL 流程(生产级)

java

复制代码
@Component
public class ProductionETLPipeline {
    
    @Autowired
    private List<DocumentReader> readers;       // PDF/Word/HTML
    @Autowired
    private List<DocumentTransformer> transformers; // 清洗/脱敏/去重
    @Autowired
    private TokenTextSplitter splitter;
    @Autowired
    private EmbeddingModel embeddingModel;
    @Autowired
    private VectorStore vectorStore;
    
    @Scheduled(cron = "0 0 2 * * ?")  // 每天凌晨2点执行
    public void runFullPipeline() {
        // 1. 读取
        List<Document> allDocs = readers.stream()
            .flatMap(reader -> reader.get().stream())
            .collect(Collectors.toList());
        
        // 2. 转换(链式调用)
        for (DocumentTransformer transformer : transformers) {
            allDocs = transformer.transform(allDocs);
        }
        
        // 3. 切分
        List<Document> chunks = splitter.apply(allDocs);
        
        // 4. 向量化(批量,控制并发)
        List<Document> embedded = embeddingModel.embed(chunks);
        
        // 5. 写入(幂等:先删后加)
        vectorStore.delete(chunks.stream().map(Document::getId).toList());
        vectorStore.add(embedded);
        
        log.info("ETL 完成,处理文档数:{},生成块数:{}", allDocs.size(), chunks.size());
    }
}

七、Agent 编排器深度实践(多步骤复杂任务)

7.1 实现一个"智能数据分析 Agent"

java

复制代码
@Component
public class DataAnalysisOrchestrator {
    
    @Autowired
    private ChatClient chatClient;
    
    public AnalysisReport analyze(String userRequest) {
        // Step 1: 意图识别与任务拆解
        TaskPlan plan = decomposeTask(userRequest);
        
        // Step 2: 并行执行子任务
        List<CompletableFuture<SubTaskResult>> futures = plan.getSubTasks().stream()
            .map(task -> CompletableFuture.supplyAsync(() -> executeSubTask(task)))
            .collect(Collectors.toList());
        
        List<SubTaskResult> results = futures.stream()
            .map(CompletableFuture::join)
            .collect(Collectors.toList());
        
        // Step 3: 结果聚合与反思
        String draft = aggregateResults(results);
        String finalReport = reflectAndPolish(draft);
        
        return new AnalysisReport(finalReport, results);
    }
    
    private TaskPlan decomposeTask(String request) {
        return chatClient.prompt()
            .user("将以下任务拆解为 2-4 个可并行的子任务:\n" + request)
            .call()
            .entity(TaskPlan.class);
    }
    
    private String reflectAndPolish(String draft) {
        return chatClient.prompt()
            .system("你是批判性思维专家,请检查以下报告的逻辑漏洞、数据矛盾,并给出改进版本")
            .user(draft)
            .call()
            .content();
    }
}

7.2 结合 MCP 工具的 Agent

java

复制代码
@Component
public class ToolAwareAgent {
    
    public String execute(String instruction) {
        return chatClient.prompt()
            .user(instruction)
            .tools(
                databaseQueryTool,      // MCP 工具1
                fileSystemTool,         // MCP 工具2
                webSearchTool           // MCP 工具3
            )
            .advisors(new ReReadingAdvisor())  // 增强推理
            .call()
            .content();
    }
}

八、完整项目:企业智能客服(含全部进阶特性)

技术栈

  • Spring AI 1.0.5 + MCP + PGVector

  • 混合检索(BM25 + Vector)

  • LLM as Judge(自动重试)

  • ETL 定时管道

  • Agent 编排(复杂问题拆解)

核心代码结构

text

复制代码
com.enterprise.customer/
├── mcp/
│   ├── OrderQueryTool.java       # MCP 工具:查订单
│   ├── ReturnApplyTool.java      # MCP 工具:处理退货
│   └── McpServerConfig.java
├── rag/
│   ├── HybridRetriever.java      # 混合检索
│   ├── ResponseJudge.java        # LLM as Judge
│   └── RAGAdvisorConfig.java
├── etl/
│   ├── KnowledgeBaseETL.java     # 定时管道
│   └── SensitiveFilter.java
├── agent/
│   └── CustomerServiceOrchestrator.java
└── controller/
    └── CustomerAIController.java

运行效果

  • 简单问题:直接 RAG 回答(<500ms)

  • 复杂问题:Agent 自动拆解 + 调用 MCP 工具(2-3秒)

  • 低分回答:自动重试(最多3次)

  • 新知识入库:每天凌晨自动 ETL


九、性能调优 Checklist

优化点 具体措施 预期提升
向量检索 HNSW 索引 + 分区表 10x QPS
并发处理 自定义线程池 + 分批 embedding 3x 吞吐量
缓存 Caffeine 缓存常见问题 50% 延迟降低
流式响应 stream() + SSE 首字延迟 <200ms
批处理 BatchProcessor 批量 embedding 5x ETL 速度

十、总结:从基础到精通的 Checklist

  • 掌握 ChatClient 所有 API(entity、stream、advisor)

  • 实现至少 2 个自定义 Advisor(如 Re-Reading、日志)

  • 完成一个完整的 RAG 项目(文档问答)

  • 集成 MCP Server/Client,动态注册工具

  • 实现 LLM as Judge 并接入生产流程

  • 搭建混合检索(向量+关键词)+ 重排序

  • 配置向量数据库高级索引(HNSW/IVF)

  • 编写生产级 ETL 管道(含清洗/脱敏)

  • 实现 Agent 编排器(任务拆解+并行执行)

  • 添加可观测性(Micrometer + Tracing)

  • 压测与性能调优(缓存、线程池、连接池)

完成以上 11 项,您就已经是 Spring AI 生产级实践专家

相关推荐
Wyz201210242 小时前
怎么在MongoDB中实现动态轮换证书(Certificate Rotation)而不停机
jvm·数据库·python
2301_782659182 小时前
CSS如何制作悬停时图片加深的覆盖层_利用transition控制rgba
jvm·数据库·python
(Charon)2 小时前
【Qt/C++】Qt/C++ 中 :: 和 . 到底有什么区别?
开发语言·c++·qt
我的xiaodoujiao2 小时前
API 接口自动化测试详细图文教程学习系列12--Requests模块4--测试实践操作
python·学习·测试工具·pytest
REDcker2 小时前
C++跨平台与跨语言绑定工具:SWIG、Djinni 等选型
开发语言·c++
m0_514520572 小时前
HTML5中Vuex持久化插件中WebStorage的底层配置
jvm·数据库·python
傻啦嘿哟2 小时前
Python 操作 Word 文档属性与字数统计方法详解
开发语言·c#
a9511416422 小时前
Redis如何利用Redisson处理并发击穿
jvm·数据库·python
郝学胜-神的一滴2 小时前
[ 力扣 1124 ] 解锁最长良好时段问题:前缀和+哈希表的优雅解法
java·开发语言·数据结构·python·算法·leetcode·散列表