文章

Spring AI框架全面学习笔记

Spring AI框架全面学习笔记

一、Spring AI 简介

1.1 什么是 Spring AI

Spring AI 是 Spring 生态中专为 AI 工程设计的应用框架,于2025年5月正式发布1.0 GA版本,2025年11月发布1.1 GA版本。它的目标是将 Spring 生态的设计原则(如可移植性、模块化、POJO驱动)应用到 AI 领域。

Spring AI 解决的核心挑战:将企业的数据和API与AI模型连接起来

Spring AI 从 Python 生态的 LangChain、LlamaIndex 等项目中汲取灵感,但并非直接移植,而是为 Java 开发者打造的 AI 开发框架。

官网:https://spring.io/projects/spring-ai

文档:https://docs.spring.io/spring-ai/reference/

GitHub:https://github.com/spring-projects/spring-ai

1.2 核心特性一览

特性说明
Chat Completion支持同步/流式对话,兼容主流AI提供商
Embedding文本向量化表示
Text to Image文生图(DALL-E、Stability AI等)
Audio Transcription语音转文字
Text to Speech文字转语音
Moderation内容审核
Structured OutputAI输出映射为Java POJO
Vector Store支持20+向量数据库
Tool/Function Calling工具调用,连接外部API
RAG检索增强生成
Chat Memory对话记忆管理
Advisors API拦截/增强AI交互的中间件模式
MCPModel Context Protocol集成
Observability可观测性(Metrics/Tracing/Logging)
ETL Pipeline文档抽取-转换-加载管道
Model EvaluationAI输出评估,防幻觉
Agentic Patterns智能体模式(Chain、Routing、Parallel等)

1.3 版本演进

版本时间里程碑
0.8.x2024早期里程碑版本
1.0.0 GA2025-05首个正式发布版本
1.0.12025-07Bug修复
1.1.0 GA2025-11MCP集成、Prompt缓存、递归Advisor、推理模式
1.1.12025-1213个新特性、16个Bug修复
2.0.0-M12025-12基于Spring Boot 4.0 + Spring Framework 7.0 + Jakarta EE 11

二、快速入门

2.1 环境要求

  • JDK 17+(推荐21)
  • Spring Boot 3.3+
  • Maven 或 Gradle

2.2 创建项目

方式一:Spring Initializr

访问 https://start.spring.io/ ,选择依赖:

  • Spring Web
  • Spring AI (选择对应的模型提供商 Starter)

方式二:手动创建Maven项目

pom.xml 核心配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?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
         https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.3</version>
    </parent>

    <properties>
        <java.version>17</java.version>
        <spring-ai.version>1.0.0</spring-ai.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Spring AI OpenAI -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        </dependency>
    </dependencies>
</project>

技巧:使用 spring-ai-bom 管理版本,避免各starter版本不一致。

2.3 Hello World

application.yml:

1
2
3
4
5
6
7
8
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4o
          temperature: 0.7

Controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
public class HelloController {

    private final ChatClient chatClient;

    // Spring AI 自动注入 ChatClient.Builder
    public HelloController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    @GetMapping("/hello")
    public String hello(@RequestParam(defaultValue = "Tell me a joke") String message) {
        return chatClient.prompt()
                .user(message)
                .call()
                .content();
    }
}

启动应用,访问 http://localhost:8080/hello 即可体验。

实用技巧:API Key 不要硬编码,使用环境变量 ${OPENAI_API_KEY} 或配置中心管理。

三、核心概念

3.1 Models(模型)

AI模型是处理和生成信息的算法。Spring AI 按输入输出类型分类:

输入输出模型类型示例
文本文本Chat CompletionGPT-4o、Claude、通义千问
文本向量Embeddingtext-embedding-ada-002
文本图像Text to ImageDALL-E 3、Stable Diffusion
音频文本Audio TranscriptionWhisper
文本音频Text to SpeechOpenAI TTS
文本标签ModerationOpenAI Moderation

Spring AI 模型层次

1
2
3
4
5
6
7
8
9
10
Model (顶层接口)
  ├── ChatModel (对话模型)
  │     ├── OpenAiChatModel
  │     ├── OllamaChatModel
  │     ├── AnthropicChatModel
  │     └── ZhiPuChatModel 等
  ├── EmbeddingModel (嵌入模型)
  ├── ImageModel (图像模型)
  ├── AudioModel (音频模型)
  └── ModerationModel (审核模型)

3.2 Prompts(提示词)

Prompt是引导AI模型产生特定输出的语言输入。在Spring AI中,Prompt不仅是简单字符串,而是包含多个Message的结构化对象。

Message角色类型

角色说明
System系统指令,设定AI的行为和上下文
User用户输入
AssistantAI的回复
Tool工具调用的结果

示例

1
2
3
4
5
6
7
Prompt prompt = new Prompt(
    List.of(
        new SystemMessage("你是一个专业的Java开发助手,回答要简洁精准。"),
        new UserMessage("解释一下Spring AI的ChatClient")
    )
);
ChatResponse response = chatModel.call(prompt);

3.3 Prompt Templates(提示词模板)

Spring AI 使用 StringTemplate 引擎来管理提示词模板,类似Spring MVC中View的概念:

1
2
3
4
5
6
7
// 模板定义
String template = "Tell me a {adjective} joke about {content}";

// 使用PromptTemplate渲染
PromptTemplate promptTemplate = new PromptTemplate(template);
Map<String, Object> variables = Map.of("adjective", "funny", "content", "Java");
Prompt prompt = promptTemplate.create(variables);

ChatClient中使用模板

1
2
3
4
5
6
String response = chatClient.prompt()
    .user(u -> u.text("Tell me a {adjective} joke about {content}")
                  .param("adjective", "funny")
                  .param("content", "Java"))
    .call()
    .content();

3.4 Embeddings(嵌入/向量化)

Embedding是将文本、图像等转换为浮点数向量(数组),捕获语义信息。通过计算向量距离判断文本相似度。

1
2
3
4
5
6
7
8
// 调用嵌入模型
EmbeddingResponse response = embeddingModel.embedForResponse(
    List.of("Hello world", "你好世界")
);

// 获取向量
float[] embedding = response.getResult().getOutput();
// 如: [0.0023, -0.0145, 0.0378, ...]

通俗理解:把文本变成坐标系中的一个点,语义相近的文本在坐标系中距离也近。

3.5 Tokens(令牌)

Token是AI模型处理文本的基本单位:

  • 英文中1 Token ≈ 0.75个单词
  • 中文中1 Token ≈ 1-2个汉字
  • Token = 钱:API调用按Token数量计费
  • Context Window(上下文窗口):模型单次处理的最大Token数
模型上下文窗口
GPT-4o128K
Claude 3.5200K
通义千问-Max128K
GPT-3.54K(已过时)

3.6 Structured Output(结构化输出)

AI模型默认输出纯字符串,Structured Output将输出映射为Java POJO:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义输出结构
record ActorFilms(String actor, List<String> movies) {}

// 自动映射
ActorFilms films = chatClient.prompt()
    .user("Generate the filmography for a random actor.")
    .call()
    .entity(ActorFilms.class);

// 支持泛型集合
List<ActorFilms> filmList = chatClient.prompt()
    .user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
    .call()
    .entity(new ParameterizedTypeReference<List<ActorFilms>>() {});

原生结构化输出(需要模型支持):

1
2
3
4
5
ActorFilms films = chatClient.prompt()
    .user("Generate the filmography for a random actor.")
    .advisors(a -> a.param(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT, true))
    .call()
    .entity(ActorFilms.class);

企业级技巧:结构化输出是与业务系统集成的关键,确保AI返回的数据可以直接映射到业务对象,避免手动解析。

四、ChatClient API 详解

4.1 ChatClient 概述

ChatClient 是与AI模型通信的流畅API(Fluent API),设计灵感来自 WebClient 和 RestClient,是Spring AI中最常用的API。

1
2
3
4
5
6
7
8
// 创建ChatClient
ChatClient chatClient = ChatClient.create(chatModel);

// 或使用Builder自定义
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultSystemPrompt("你是一个专业的技术助手")
    .defaultAdvisors(...)
    .build();

4.2 同步调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 基础调用 - 返回字符串
String content = chatClient.prompt()
    .user("What is Spring AI?")
    .call()
    .content();

// 返回完整ChatResponse(含元数据)
ChatResponse chatResponse = chatClient.prompt()
    .user("What is Spring AI?")
    .call()
    .chatResponse();

// 返回实体对象
record BookInfo(String title, String author, int year) {}
BookInfo book = chatClient.prompt()
    .user("推荐一本Java书籍")
    .call()
    .entity(BookInfo.class);

4.3 流式调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 流式响应
Flux<String> stream = chatClient.prompt()
    .user("Write a poem about Spring")
    .stream()
    .content();

// 在Controller中使用SSE
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chatStream(@RequestParam String message) {
    return chatClient.prompt()
        .user(message)
        .stream()
        .content();
}

4.4 System Message

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 构建时设置默认系统消息
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultSystemPrompt("""
        你是一个电商客服助手,请用礼貌专业的语气回答问题。
        回答要简洁,不超过200字。
        如果不知道答案,请说'我需要转接人工客服'。
        """)
    .build();

// 调用时动态设置
String response = chatClient.prompt()
    .system("请用JSON格式回答")
    .user("列出中国四大名著")
    .call()
    .content();

4.5 多模型切换

企业应用常需要在不同场景使用不同模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Configuration
public class ChatClientConfig {

    @Bean
    public ChatClient openAiChatClient(OpenAiChatModel chatModel) {
        return ChatClient.create(chatModel);
    }

    @Bean
    public ChatClient anthropicChatClient(AnthropicChatModel chatModel) {
        return ChatClient.create(chatModel);
    }
}

// 使用时按需注入
@Service
public class AiService {

    @Qualifier("openAiChatClient")
    private final ChatClient openAiClient;

    @Qualifier("anthropicChatClient")
    private final ChatClient anthropicClient;

    public String ask(String question, String modelType) {
        ChatClient client = "openai".equals(modelType) ? openAiClient : anthropicClient;
        return client.prompt().user(question).call().content();
    }
}

OpenAI兼容API多端点(使用mutate方法):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Service
public class MultiModelService {

    @Autowired
    private OpenAiChatModel baseChatModel;

    @Autowired
    private OpenAiApi baseOpenAiApi;

    public void multiClientFlow() {
        // Groq端点(Llama3)
        OpenAiApi groqApi = baseOpenAiApi.mutate()
            .baseUrl("https://api.groq.com/openai")
            .apiKey(System.getenv("GROQ_API_KEY"))
            .build();

        OpenAiChatModel groqModel = baseChatModel.mutate()
            .openAiApi(groqApi)
            .defaultOptions(OpenAiChatOptions.builder()
                .model("llama3-70b-8192")
                .temperature(0.5)
                .build())
            .build();

        String response = ChatClient.builder(groqModel).build()
            .prompt("What is the capital of France?")
            .call()
            .content();
    }
}

企业场景:复杂推理用GPT-4o,简单问答用轻量模型降低成本;不同区域使用不同合规模型。

五、模型提供商集成

5.1 OpenAI

Maven依赖

1
2
3
4
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>

配置

1
2
3
4
5
6
7
8
9
10
11
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4o
          temperature: 0.7
      image:
        options:
          model: dall-e-3

支持的能力:Chat、Embedding、Image Generation、Audio Transcription、TTS、Moderation

兼容OpenAI格式的国产API(如通义千问、DeepSeek、智谱等,通过修改base-url接入):

1
2
3
4
5
6
7
8
spring:
  ai:
    openai:
      chat:
        base-url: https://api.deepseek.com
        api-key: ${DEEPSEEK_API_KEY}
        options:
          model: deepseek-chat

5.2 Ollama(本地部署)

Ollama 是本地运行大模型的工具,适合隐私敏感场景和离线开发。

安装Ollama:https://ollama.ai/

1
2
3
4
# 拉取并运行模型
ollama run llama3
ollama run qwen2.5
ollama run deepseek-r1

Maven依赖

1
2
3
4
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>

配置

1
2
3
4
5
6
7
8
spring:
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        model: llama3
        options:
          temperature: 0.7

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@RestController
@RequestMapping("/ollama")
public class OllamaController {

    private final ChatClient chatClient;

    public OllamaController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    @GetMapping("/chat")
    public String chat(@RequestParam String message) {
        return chatClient.prompt()
            .user(message)
            .call()
            .content();
    }

    @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> stream(@RequestParam String message) {
        return chatClient.prompt()
            .user(message)
            .stream()
            .content();
    }
}

企业场景:金融、医疗等隐私敏感行业,数据不能上云,通过Ollama本地部署满足合规要求。

5.3 Anthropic Claude

1
2
3
4
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-anthropic-spring-boot-starter</artifactId>
</dependency>
1
2
3
4
5
6
7
spring:
  ai:
    anthropic:
      api-key: ${ANTHROPIC_API_KEY}
      chat:
        options:
          model: claude-sonnet-4-20250514

5.4 国产大模型集成

5.4.1 Spring AI Alibaba(通义千问)

Spring AI Alibaba 是阿里官方提供的Spring AI扩展:

1
2
3
4
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter</artifactId>
</dependency>
1
2
3
4
5
6
7
spring:
  ai:
    dashscope:
      api-key: ${DASHSCOPE_API_KEY}
      chat:
        options:
          model: qwen-max

5.4.2 DeepSeek

DeepSeek的API兼容OpenAI格式,可直接使用OpenAI starter:

1
2
3
4
5
6
7
8
spring:
  ai:
    openai:
      chat:
        base-url: https://api.deepseek.com
        api-key: ${DEEPSEEK_API_KEY}
        options:
          model: deepseek-chat

5.4.3 智谱AI(GLM)

1
2
3
4
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-zhipuai-spring-boot-starter</artifactId>
</dependency>
1
2
3
4
5
6
7
spring:
  ai:
    zhipuai:
      api-key: ${ZHIPU_API_KEY}
      chat:
        options:
          model: glm-4

5.4.4 百度文心一言

通过兼容OpenAI格式的代理API接入:

1
2
3
4
5
6
7
8
spring:
  ai:
    openai:
      chat:
        base-url: https://qianfan.baidubce.com/v1
        api-key: ${QIANFAN_API_KEY}
        options:
          model: ernie-4.0

5.5 模型提供商汇总

提供商ChatEmbeddingImageAudioTTS
OpenAI
Anthropic---
Ollama---
阿里通义千问-
智谱AI---
Google GenAI
Azure OpenAI
Mistral---
DeepSeek---
Minimax-

六、Prompt Engineering(提示词工程)

6.1 提示词工程基础

提示词工程是与AI模型有效沟通的艺术和科学。良好的Prompt可以显著提升输出质量。

关键技巧

  1. 角色设定:明确AI的角色和专业领域
  2. 任务描述:清晰定义要完成的具体任务
  3. 输出格式:指定期望的输出格式和结构
  4. 约束条件:设定回答的范围和限制
  5. 示例引导:给出1-2个期望输出的示例

6.2 System Prompt 最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultSystemPrompt("""
        你是一个{name},专注于{domain}领域的专业顾问。
        
        回答规则:
        1. 回答必须基于事实,不确定时明确说明
        2. 使用专业但易懂的语言
        3. 如果问题超出{domain}范围,建议用户咨询相关专家
        4. 回答长度控制在{maxLength}字以内
        """)
    .build();

// 运行时传入参数
String response = chatClient.prompt()
    .user("解释微服务架构")
    .system(s -> s
        .param("name", "架构师")
        .param("domain", "软件架构")
        .param("maxLength", "500"))
    .call()
    .content();

6.3 Few-Shot Prompting

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
String response = chatClient.prompt()
    .user(u -> u.text("""
        根据以下示例,分析用户情绪:
        
        示例1:
        用户输入:这个产品太棒了!
        输出:{{"sentiment": "positive", "confidence": 0.95}}
        
        示例2:
        用户输入:等了两天还没发货,太差了
        输出:{{"sentiment": "negative", "confidence": 0.90}}
        
        请分析:{userInput}
        """)
        .param("userInput", "还行吧,一般般"))
    .call()
    .content();

6.4 Chain of Thought(思维链)

1
2
3
4
5
6
7
8
9
String response = chatClient.prompt()
    .user("""
        请一步步推理以下问题:
        一个商店打8折促销,一件原价250元的商品,
        促销价是多少?如果再使用一张满200减30的优惠券,
        最终价格是多少?
        """)
    .call()
    .content();

研究证明:以”Take a deep breath and work on this step by step”开头的提示词是最有效的Prompt之一。

6.5 企业级Prompt管理

在企业应用中,Prompt通常需要版本管理、A/B测试和动态调整:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class PromptManager {

    // 从配置文件或数据库加载Prompt模板
    @Value("${app.prompts.customer-service}")
    private String customerServicePrompt;

    // 支持A/B测试的Prompt版本
    public String getPrompt(String scene, String version) {
        return promptRepository.findBySceneAndVersion(scene, version)
            .getTemplate();
    }
}

实战建议:将Prompt模板存储在外部(配置中心、数据库),而非硬编码在代码中,便于运营人员调整而无需重新部署。

七、结构化输出

7.1 基础用法

将AI的文本输出映射为Java对象,是与业务系统集成的关键能力:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义输出结构
record BookRecommendation(
    String title,
    String author,
    String reason,
    List<String> tags
) {}

// 自动映射
BookRecommendation book = chatClient.prompt()
    .user("推荐一本关于微服务的书")
    .call()
    .entity(BookRecommendation.class);

7.2 复杂嵌套结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
record OrderAnalysis(
    String orderId,
    double totalAmount,
    CustomerInfo customer,
    List<OrderItem> items,
    String riskLevel
) {}

record CustomerInfo(String name, String phone, String level) {}

record OrderItem(String product, int quantity, double price) {}

OrderAnalysis analysis = chatClient.prompt()
    .user("分析订单 ORD-20240101-001 的风险等级")
    .call()
    .entity(OrderAnalysis.class);

7.3 列表和泛型

1
2
3
4
List<BookRecommendation> books = chatClient.prompt()
    .user("推荐3本Java书籍")
    .call()
    .entity(new ParameterizedTypeReference<List<BookRecommendation>>() {});

7.4 枚举映射

1
2
3
4
5
6
7
8
enum Sentiment { POSITIVE, NEUTRAL, NEGATIVE }

record SentimentResult(Sentiment sentiment, double confidence, String reason) {}

SentimentResult result = chatClient.prompt()
    .user("分析这段评论的情感倾向:'这个产品真的很好用,推荐!'")
    .call()
    .entity(SentimentResult.class);

7.5 企业实战:智能表单提取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
record InvoiceInfo(
    String invoiceNumber,
    String supplierName,
    LocalDate invoiceDate,
    BigDecimal totalAmount,
    BigDecimal taxAmount,
    List<InvoiceItem> items
) {}

record InvoiceItem(String description, int quantity, BigDecimal unitPrice, BigDecimal amount) {}

@Service
public class InvoiceExtractService {

    private final ChatClient chatClient;

    public InvoiceInfo extract(String invoiceText) {
        return chatClient.prompt()
            .system("你是一个发票信息提取专家,请从文本中提取发票信息。日期格式为yyyy-MM-dd,金额保留两位小数。")
            .user("提取以下发票信息:\n" + invoiceText)
            .call()
            .entity(InvoiceInfo.class);
    }
}

实用技巧:结构化输出依赖模型能力,建议使用支持Function Calling的模型(如GPT-4o),准确率更高。

八、流式响应

8.1 流式输出原理

流式响应(Streaming)允许AI模型的输出逐步返回,而不是等待完整响应,用户体验更佳(类似ChatGPT的打字机效果)。

8.2 SSE打字机效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
public class StreamController {

    private final ChatClient chatClient;

    public StreamController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    @GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> streamChat(@RequestParam String message) {
        return chatClient.prompt()
            .user(message)
            .stream()
            .content();
    }
}

8.3 前端配合(Vue示例)

1
2
3
4
5
6
7
8
9
10
// 使用EventSource接收SSE流
const eventSource = new EventSource(`/chat/stream?message=${encodeURIComponent(msg)}`);

eventSource.onmessage = (event) => {
    this.reply += event.data;
};

eventSource.onerror = () => {
    eventSource.close();
};

8.4 Flux完整流式ChatResponse

1
2
3
4
Flux<ChatResponse> stream = chatClient.prompt()
    .user("Write a poem")
    .stream()
    .chatResponse();

企业级技巧:流式响应需要考虑超时、断连重试、背压控制等生产级问题。在Nginx等反向代理层需要关闭缓冲:proxy_buffering off;

九、Tool/Function Calling(工具调用)

9.1 什么是工具调用

Tool Calling 允许AI模型调用外部工具/函数,获取实时信息或执行操作。模型本身不直接执行工具,而是请求客户端应用执行并将结果返回。

两大用途

  • 信息检索:查询数据库、调用API获取实时数据
  • 执行操作:发送邮件、创建订单、触发工作流

9.2 @Tool注解快速使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class DateTimeTools {

    @Tool(description = "获取用户时区的当前日期时间")
    String getCurrentDateTime() {
        return LocalDateTime.now()
            .atZone(LocaleContextHolder.getTimeZone().toZoneId())
            .toString();
    }

    @Tool(description = "设置闹钟,时间格式为ISO-8601")
    void setAlarm(String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
        System.out.println("Alarm set for " + alarmTime);
    }
}

// 使用
String response = ChatClient.create(chatModel)
    .prompt("明天是星期几?")
    .tools(new DateTimeTools())
    .call()
    .content();

9.3 带参数的工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class WeatherTools {

    @Tool(description = "获取指定城市的当前天气信息")
    String getWeather(
        @ToolParam(description = "城市名称") String city,
        @ToolParam(description = "温度单位,celsius或fahrenheit") String unit
    ) {
        // 调用天气API
        return weatherService.getCurrentWeather(city, unit);
    }
}

// 使用
String response = chatClient.prompt()
    .user("北京今天天气怎么样?")
    .tools(new WeatherTools())
    .call()
    .content();

9.4 Spring Bean作为工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Component
public class OrderTools {

    @Autowired
    private OrderService orderService;

    @Tool(description = "根据订单号查询订单详情")
    public OrderDTO getOrder(
        @ToolParam(description = "订单编号") String orderId
    ) {
        return orderService.getById(orderId);
    }

    @Tool(description = "创建新订单")
    public String createOrder(
        @ToolParam(description = "商品ID") String productId,
        @ToolParam(description = "购买数量") int quantity
    ) {
        return orderService.create(productId, quantity);
    }
}

// 注册为工具
String response = chatClient.prompt()
    .user("帮我查一下订单 ORD-001 的详情")
    .tools(orderTools)  // 传入Spring Bean
    .call()
    .content();

9.5 工具调用流程

1
2
3
4
5
1. 客户端发送请求 → 包含工具定义(名称、描述、参数Schema)
2. 模型决定调用工具 → 返回工具名称和参数
3. 应用执行工具 → 识别并调用对应方法
4. 返回工具结果 → 将结果发给模型
5. 模型生成最终响应 → 结合工具结果回答用户

9.6 企业实战:智能客服系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Component
public class CustomerServiceTools {

    @Autowired
    private ProductService productService;
    @Autowired
    private LogisticsService logisticsService;
    @Autowired
    private RefundService refundService;

    @Tool(description = "查询商品信息")
    public ProductInfo getProduct(@ToolParam(description = "商品名称或ID") String keyword) {
        return productService.search(keyword);
    }

    @Tool(description = "查询物流信息")
    public LogisticsInfo getLogistics(@ToolParam(description = "快递单号") String trackingNo) {
        return logisticsService.track(trackingNo);
    }

    @Tool(description = "提交退款申请")
    public RefundResult applyRefund(
        @ToolParam(description = "订单号") String orderId,
        @ToolParam(description = "退款原因") String reason
    ) {
        return refundService.apply(orderId, reason);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Service
public class CustomerServiceAgent {

    private final ChatClient chatClient;

    public CustomerServiceAgent(ChatClient.Builder builder, CustomerServiceTools tools) {
        this.chatClient = builder
            .defaultSystemPrompt("你是电商客服,可以查询商品、物流和退款。用中文回答。")
            .defaultTools(tools)
            .build();
    }

    public String serve(String userMessage) {
        return chatClient.prompt()
            .user(userMessage)
            .call()
            .content();
    }
}

安全提示:模型永远不会直接访问你的API,它只能请求调用。应用端负责权限校验和参数验证。

十、RAG(检索增强生成)

10.1 什么是RAG

RAG(Retrieval Augmented Generation)是解决大模型知识过时和幻觉问题的核心技术。流程:

  1. 用户提问 → 从知识库检索相关文档
  2. 将检索结果作为上下文 → 与用户问题一起发给模型
  3. 模型基于检索到的上下文 → 生成回答
1
2
用户问题 → [检索] → 向量数据库 → [相关文档] → 
与问题拼接 → [AI模型] → 基于文档的回答

10.2 QuestionAnswerAdvisor(简单RAG)

1
2
3
4
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>
1
2
3
4
5
6
7
// 假设已有VectorStore和数据
ChatResponse response = ChatClient.builder(chatModel)
    .build().prompt()
    .advisors(QuestionAnswerAdvisor.builder(vectorStore).build())
    .user("Spring AI支持哪些模型?")
    .call()
    .chatResponse();

自定义搜索参数

1
2
3
4
5
6
var qaAdvisor = QuestionAnswerAdvisor.builder(vectorStore)
    .searchRequest(SearchRequest.builder()
        .similarityThreshold(0.8)  // 相似度阈值
        .topK(6)                   // 返回前6条
        .build())
    .build();

动态过滤表达式

1
2
3
4
5
String content = chatClient.prompt()
    .user("Please answer my question XYZ")
    .advisors(a -> a.param(QuestionAnswerAdvisor.FILTER_EXPRESSION, "type == 'Spring'"))
    .call()
    .content();

10.3 RetrievalAugmentationAdvisor(高级RAG)

1
2
3
4
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-rag</artifactId>
</dependency>

Naive RAG(基础版):

1
2
3
4
5
6
7
8
9
10
11
12
Advisor ragAdvisor = RetrievalAugmentationAdvisor.builder()
    .documentRetriever(VectorStoreDocumentRetriever.builder()
        .similarityThreshold(0.50)
        .vectorStore(vectorStore)
        .build())
    .build();

String answer = chatClient.prompt()
    .advisors(ragAdvisor)
    .user("Spring AI如何使用?")
    .call()
    .content();

Advanced RAG(查询改写+检索):

1
2
3
4
5
6
7
8
9
Advisor ragAdvisor = RetrievalAugmentationAdvisor.builder()
    .queryTransformers(RewriteQueryTransformer.builder()
        .chatClientBuilder(chatClientBuilder.build().mutate())
        .build())
    .documentRetriever(VectorStoreDocumentRetriever.builder()
        .similarityThreshold(0.50)
        .vectorStore(vectorStore)
        .build())
    .build();

10.4 查询改写(Query Transformation)

压缩查询:将对话历史压缩为独立查询

1
2
3
4
5
6
7
8
9
10
11
12
Query query = Query.builder()
    .text("那它的第二大城市呢?")
    .history(new UserMessage("丹麦的首都是什么?"),
             new AssistantMessage("哥本哈根是丹麦的首都。"))
    .build();

QueryTransformer transformer = CompressionQueryTransformer.builder()
    .chatClientBuilder(chatClientBuilder)
    .build();

Query transformedQuery = transformer.transform(query);
// 结果: "丹麦的第二大城市是什么?"

改写查询:让查询更适合检索

1
2
3
QueryTransformer rewriteTransformer = RewriteQueryTransformer.builder()
    .chatClientBuilder(chatClientBuilder)
    .build();

10.5 文档后处理

1
2
3
4
5
6
Advisor ragAdvisor = RetrievalAugmentationAdvisor.builder()
    .documentRetriever(VectorStoreDocumentRetriever.builder()
        .vectorStore(vectorStore)
        .build())
    .documentPostProcessors(new RankingDocumentPostProcessor())  // 重排序
    .build();

10.6 自定义RAG模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PromptTemplate customTemplate = PromptTemplate.builder()
    .renderer(StTemplateRenderer.builder()
        .startDelimiterToken('<').endDelimiterToken('>')
        .build())
    .template("""
        <query>

        Context information is below.
        ---------------------
        <question_answer_context>
        ---------------------

        Given the context information and no prior knowledge, answer the query.
        Follow these rules:
        1. If the answer is not in the context, just say that you don't know.
        2. Avoid statements like "Based on the context...".
        """)
    .build();

QuestionAnswerAdvisor qaAdvisor = QuestionAnswerAdvisor.builder(vectorStore)
    .promptTemplate(customTemplate)
    .build();

10.7 企业实战:知识库问答系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Service
public class KnowledgeBaseService {

    private final ChatClient chatClient;
    private final VectorStore vectorStore;

    public KnowledgeBaseService(ChatClient.Builder builder, VectorStore vectorStore) {
        this.vectorStore = vectorStore;
        this.chatClient = builder
            .defaultSystemPrompt("你是企业知识库助手,仅根据提供的文档内容回答问题。如果文档中没有相关信息,请明确告知用户。")
            .defaultAdvisors(
                QuestionAnswerAdvisor.builder(vectorStore)
                    .searchRequest(SearchRequest.builder()
                        .similarityThreshold(0.7)
                        .topK(5)
                        .build())
                    .build())
            .build();
    }

    public String ask(String question) {
        return chatClient.prompt()
            .user(question)
            .call()
            .content();
    }
}

十一、ETL Pipeline(数据管道)

11.1 ETL概述

ETL(Extract-Transform-Load)是RAG的数据处理基础,将原始文档转换为向量存储的格式。

1
原始文档 → [DocumentReader读取] → [DocumentTransformer转换/切分] → [DocumentWriter写入向量库]

11.2 三大核心接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 读取:Supplier<List<Document>>
public interface DocumentReader {
    List<Document> read();
}

// 转换:Function<List<Document>, List<Document>>
public interface DocumentTransformer {
    List<Document> transform(List<Document> documents);
}

// 写入:Consumer<List<Document>>
public interface DocumentWriter {
    void write(List<Document> documents);
}

11.3 DocumentReader(文档读取)

PDF读取

1
2
3
4
5
6
7
8
9
10
11
12
13
// 添加PDF依赖
// spring-ai-pdf-document-reader

PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(
    new ClassPathResource("company-policy.pdf"),
    PdfDocumentReaderConfig.builder()
        .withPageExtractedTextFormatter(
            new ExtractedTextFormatter.Builder()
                .withNumberOfPagesAtEnd(true)
                .build())
        .withPagesPerDocument(1)
        .build()
);

JSON读取

1
2
3
4
5
JsonReader jsonReader = new JsonReader(
    new ClassPathResource("products.json"),
    "description", "content"  // 指定JSON中作为文本内容的key
);
List<Document> documents = jsonReader.read();

文本读取

1
2
3
TextReader textReader = new TextReader(new ClassPathResource("knowledge.txt"));
textReader.getCustomMetadata().put("filename", "knowledge.txt");
List<Document> documents = textReader.read();

HTML读取

1
2
3
4
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-jsoup-document-reader</artifactId>
</dependency>
1
2
3
JsoupDocumentReader htmlReader = new JsoupDocumentReader(
    new ClassPathResource("page.html")
);

11.4 DocumentTransformer(文档转换/切分)

TokenTextSplitter:按Token数切分

1
2
3
4
5
6
7
8
TokenTextSplitter splitter = new TokenTextSplitter(
    800,   // 每个块的目标Token数
    200,   // 相邻块的重叠Token数
    5,     // 最大块数
    10000, // 最大字符数
    true   // 是否保留分隔符
);
List<Document> chunks = splitter.split(documents);

技巧:重叠(overlap)设置很重要,太低会丢失上下文,太高会增加冗余和Token消耗。通常设置为块大小的10-25%。

11.5 完整ETL示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Service
public class DocumentIngestionService {

    private final VectorStore vectorStore;

    public void ingestPdf(String filePath) {
        // 1. 读取PDF
        PagePdfDocumentReader reader = new PagePdfDocumentReader(
            new FileSystemResource(filePath)
        );

        // 2. 切分文档
        TokenTextSplitter splitter = new TokenTextSplitter(800, 200, 5, 10000, true);

        // 3. 写入向量库
        vectorStore.accept(splitter.apply(reader.get()));
        // 或使用更语义化的方法名
        // vectorStore.write(splitter.split(reader.read()));
    }

    public void ingestDirectory(String dirPath) {
        // 批量导入目录下所有PDF
        File dir = new File(dirPath);
        for (File file : dir.listFiles((d, name) -> name.endsWith(".pdf"))) {
            ingestPdf(file.getAbsolutePath());
        }
    }
}

11.6 企业实战:多源数据导入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Service
public class EnterpriseDataIngestion {

    private final VectorStore vectorStore;
    private final EmbeddingModel embeddingModel;

    /**
     * 导入数据库数据到知识库
     */
    public void ingestFromDatabase(DataSource dataSource, String sql) {
        // 从数据库查询数据
        // 转换为Document
        // 写入向量库
        List<Document> documents = jdbcTemplate.query(sql, (rs, rowNum) -> {
            Document doc = new Document(rs.getString("content"));
            doc.getMetadata().put("source", "database");
            doc.getMetadata().put("table", rs.getString("table_name"));
            return doc;
        });

        TokenTextSplitter splitter = new TokenTextSplitter(800, 200, 5, 10000, true);
        vectorStore.accept(splitter.apply(documents));
    }
}

十二、向量数据库

12.1 向量数据库概述

向量数据库是RAG的核心组件,存储文档的Embedding向量,支持相似度搜索。

Spring AI 支持的向量数据库

向量数据库特点适用场景
PGVector基于PostgreSQL,运维简单已有PG基础设施的团队
Redis高性能,低延迟实时检索
Milvus分布式,高性能大规模数据
Chroma开源轻量开发测试
Pinecone全托管无运维需求
Elasticsearch全文+向量混合搜索已有ES基础设施
MongoDB Atlas文档+向量已有MongoDB的团队
QdrantRust实现,高性能性能敏感场景
Weaviate丰富的模块化功能复杂搜索需求
Oracle企业级数据库已有Oracle基础设施
MariaDBMySQL兼容已有MariaDB的团队
Neo4j图+向量知识图谱场景
Apache Cassandra高可用,分布式大规模分布式场景

12.2 VectorStore接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface VectorStore extends DocumentWriter {

    // 添加文档
    void add(List<Document> documents);

    // 删除文档
    void delete(List<String> idList);

    // 相似度搜索
    List<Document> similaritySearch(String query);

    // 带条件的相似度搜索
    List<Document> similaritySearch(SearchRequest request);
}

12.3 PGVector示例

1
2
3
4
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/vectordb
    username: postgres
    password: postgres
  ai:
    vectorstore:
      pgvector:
        index-type: HNSW
        distance-type: COSINE_DISTANCE
        dimensions: 1536
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Service
public class VectorSearchService {

    private final VectorStore vectorStore;

    // 搜索相似文档
    public List<Document> search(String query) {
        return vectorStore.similaritySearch(
            SearchRequest.builder()
                .query(query)
                .topK(5)
                .similarityThreshold(0.7)
                .build()
        );
    }

    // 带过滤条件搜索
    public List<Document> searchByType(String query, String type) {
        return vectorStore.similaritySearch(
            SearchRequest.builder()
                .query(query)
                .topK(5)
                .similarityThreshold(0.7)
                .filterExpression("type == '" + type + "'")
                .build()
        );
    }
}

12.4 Redis向量库示例

1
2
3
4
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-redis-store-spring-boot-starter</artifactId>
</dependency>
1
2
3
4
5
6
7
spring:
  ai:
    vectorstore:
      redis:
        uri: redis://localhost:6379
        index: spring-ai-index
        prefix: "doc:"

12.5 SQL-like元数据过滤

Spring AI 提供了创新的SQL-like元数据过滤API,可跨所有VectorStore使用:

1
2
3
4
5
6
7
8
9
10
11
// 等于
SearchRequest.builder().filterExpression("type == 'Spring'").build()

// 比较
SearchRequest.builder().filterExpression("year > 2023").build()

// 逻辑运算
SearchRequest.builder().filterExpression("type == 'Spring' AND year > 2023").build()

// IN
SearchRequest.builder().filterExpression("type IN ['Spring', 'Java']").build()

选型建议:开发阶段用Chroma或PGVector,生产环境根据团队技术栈选择。已有PostgreSQL选PGVector,已有Redis选Redis,大规模数据选Milvus。

十三、对话记忆(Chat Memory)

13.1 为什么需要对话记忆

大模型是无状态的,每次请求都是独立的。对话记忆让AI能够记住上下文,实现多轮对话。

注意区分:Chat Memory(对话记忆,维持上下文) vs Chat History(完整聊天记录,用于审计回溯)

13.2 MessageChatMemoryAdvisor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ChatMemory chatMemory = MessageWindowChatMemory.builder()
    .maxMessages(20)  // 保留最近20条消息
    .build();

ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        MessageChatMemoryAdvisor.builder(chatMemory).build()
    )
    .build();

// 使用时指定会话ID
String response = chatClient.prompt()
    .user("我叫张三")
    .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "user-001"))
    .call()
    .content();

// 同一会话的后续对话会记住上下文
String response2 = chatClient.prompt()
    .user("我叫什么名字?")
    .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "user-001"))
    .call()
    .content();
// AI回答:你叫张三

13.3 记忆存储后端

内存存储(默认)

1
2
3
4
5
ChatMemoryRepository repository = new InMemoryChatMemoryRepository();
ChatMemory chatMemory = MessageWindowChatMemory.builder()
    .chatMemoryRepository(repository)
    .maxMessages(20)
    .build();

注意:内存存储在应用重启后丢失,仅适合开发测试。

JDBC存储

1
2
3
4
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>
1
2
3
4
5
6
7
spring:
  ai:
    chat:
      memory:
        repository:
          jdbc:
            initialize-schema: always  # embedded/always/never
1
2
3
4
5
6
7
@Autowired
JdbcChatMemoryRepository chatMemoryRepository;

ChatMemory chatMemory = MessageWindowChatMemory.builder()
    .chatMemoryRepository(chatMemoryRepository)
    .maxMessages(20)
    .build();

支持数据库:PostgreSQL、MySQL/MariaDB、SQL Server、HSQLDB、Oracle

Cassandra存储

1
2
3
4
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-cassandra</artifactId>
</dependency>

Neo4j存储

1
2
3
4
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-neo4j</artifactId>
</dependency>

13.4 VectorStoreChatMemoryAdvisor(向量检索记忆)

将对话历史存入向量库,通过语义相似度检索相关记忆:

1
2
3
4
5
6
7
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        VectorStoreChatMemoryAdvisor.builder(vectorStore)
            .topK(10)
            .build()
    )
    .build();

13.5 企业实战:多会话客服系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Service
public class ChatSessionService {

    private final ChatClient chatClient;
    private final ChatMemory chatMemory;

    public ChatSessionService(ChatClient.Builder builder, JdbcChatMemoryRepository repository) {
        this.chatMemory = MessageWindowChatMemory.builder()
            .chatMemoryRepository(repository)
            .maxMessages(50)  // 保留最近50条
            .build();

        this.chatClient = builder
            .defaultSystemPrompt("你是专业客服,请礼貌解答用户问题。")
            .defaultAdvisors(
                MessageChatMemoryAdvisor.builder(chatMemory).build()
            )
            .build();
    }

    public String chat(String sessionId, String userMessage) {
        return chatClient.prompt()
            .user(userMessage)
            .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, sessionId))
            .call()
            .content();
    }
}

十四、Advisors API

14.1 Advisor概述

Advisors API 是Spring AI的核心中间件模式,类似于Servlet Filter或Spring Interceptor,可以拦截、修改和增强AI交互。

核心能力

  • 封装常见AI模式
  • 转换发送到/来自LLM的数据
  • 提供跨模型和用例的可移植性

14.2 Advisor链式执行

1
2
3
用户请求 → [Advisor1(request)] → [Advisor2(request)] → ... → [LLM]
                                                                  ↓
用户响应 ← [Advisor1(response)] ← [Advisor2(response)] ← ... ← [LLM Response]
  • Advisor按 getOrder() 值排序,值越小优先级越高
  • 链式处理采用栈模式:第一个处理请求的Advisor,最后处理响应

14.3 内置Advisor

Advisor作用
MessageChatMemoryAdvisor对话记忆管理
QuestionAnswerAdvisor简单RAG
RetrievalAugmentationAdvisor高级模块化RAG
VectorStoreChatMemoryAdvisor向量检索记忆

14.4 自定义Advisor

日志Advisor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class SimpleLoggerAdvisor implements CallAdvisor, StreamAdvisor {

    private static final Logger logger = LoggerFactory.getLogger(SimpleLoggerAdvisor.class);

    @Override
    public String getName() {
        return this.getClass().getSimpleName();
    }

    @Override
    public int getOrder() {
        return 0;  // 最高优先级
    }

    @Override
    public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
        logger.debug("Request: {}", request);
        ChatClientResponse response = chain.nextCall(request);
        logger.debug("Response: {}", response);
        return response;
    }

    @Override
    public Flux<ChatClientResponse> adviseStream(ChatClientRequest request, StreamAdvisorChain chain) {
        logger.debug("Request: {}", request);
        Flux<ChatClientResponse> responses = chain.nextStream(request);
        return new ChatClientMessageAggregator()
            .aggregateChatClientResponse(responses, this::logResponse);
    }
}

内容审核Advisor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ContentModerationAdvisor implements CallAdvisor {

    private final ChatModel moderationModel;

    @Override
    public String getName() {
        return "contentModeration";
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;  // 最先执行
    }

    @Override
    public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
        // 检查用户输入是否包含敏感内容
        String userInput = request.prompt().getUserMessage();
        if (containsSensitiveContent(userInput)) {
            // 阻止请求
            return new ChatClientResponse(
                new ChatResponse(List.of(new Generation(new AssistantMessage("抱歉,您的问题包含敏感内容。")))),
                request.context()
            );
        }
        return chain.nextCall(request);
    }
}

14.5 递归Advisor(1.1新增)

Spring AI 1.1引入递归Advisor,允许Advisor调用其他Advisor链,实现自我修正推理:

1
2
// 递归Advisor可以在内部调用ChatClient形成循环
// 适用于自我纠错、多步推理等场景

企业场景:利用Advisor模式实现统一的日志记录、权限校验、内容审核、Token计数等横切关注点。

十五、多模态处理

15.1 图像理解

Spring AI支持GPT-4o等模型的图像理解能力:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 分析URL图片
String response = chatClient.prompt()
    .user(u -> u.text("描述这张图片的内容")
                  .media(MimeTypeUtils.IMAGE_PNG,
                         new URL("https://example.com/photo.png")))
    .call()
    .content();

// 分析本地图片
String response = chatClient.prompt()
    .user(u -> u.text("分析这张图片")
                  .media(MimeTypeUtils.IMAGE_JPEG,
                         new FileSystemResource("/path/to/image.jpg")))
    .call()
    .content();

15.2 文生图

OpenAI DALL-E

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
@RequestMapping("/image")
public class ImageController {

    @Resource
    private OpenAiImageModel imageModel;

    @GetMapping("/generate")
    public ResponseEntity<String> generate(@RequestParam String prompt) {
        ImageResponse response = imageModel.call(new ImagePrompt(prompt));
        String imageUrl = response.getResult().getOutput().getUrl();
        return ResponseEntity.ok(imageUrl);
    }
}

Stability AI

1
2
3
4
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-stabilityai-spring-boot-starter</artifactId>
</dependency>
1
2
3
4
spring:
  ai:
    stabilityai:
      api-key: ${STABILITY_API_KEY}

15.3 语音转文字

1
2
3
4
5
6
// 使用OpenAI Whisper
String transcription = transcriptionModel.call(
    new AudioTranscriptionRequest(
        new FileSystemResource("audio.mp3")
    )
).getResult().getOutput();

15.4 文字转语音

1
2
3
4
// 使用OpenAI TTS
byte[] audio = speechModel.call(
    new SpeechRequest("你好,欢迎使用Spring AI")
).getResult().getOutput();

15.5 企业实战:智能图片审核

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class ImageModerationService {

    private final ChatClient chatClient;

    public ImageModerationResult moderate(byte[] imageData) {
        return chatClient.prompt()
            .system("你是一个图片审核员,检查图片是否包含违规内容。")
            .user(u -> u.text("请审核这张图片,返回审核结果")
                          .media(MimeTypeUtils.IMAGE_JPEG,
                                 new ByteArrayResource(imageData)))
            .call()
            .entity(ImageModerationResult.class);
    }
}

record ImageModerationResult(
    boolean approved,
    String reason,
    String riskLevel
) {}

十六、MCP(模型上下文协议)

16.1 MCP概述

Model Context Protocol(MCP)是Anthropic提出的标准协议,让AI模型以结构化方式与外部工具和资源交互。Spring AI 1.1全面支持MCP。

核心架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌─────────────────┐     ┌─────────────────┐
│    MCP Client    │ ←→ │    MCP Server    │
│  (AI应用端)      │     │  (工具/资源端)    │
└────────┬────────┘     └────────┬────────┘
         │                       │
    ┌────┴────┐            ┌────┴────┐
    │Session  │            │Session  │
    └────┬────┘            └────┬────┘
         │                       │
    ┌────┴────┐            ┌────┴────┐
    │Transport│            │Transport│
    │(STDIO/  │            │(STDIO/  │
    │HTTP/SSE)│            │HTTP/SSE)│
    └─────────┘            └─────────┘

16.2 MCP Client配置

1
2
3
4
5
6
7
8
9
10
11
<!-- STDIO + HTTP/SSE 支持 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>

<!-- WebFlux支持 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
</dependency>
1
2
3
4
5
6
spring:
  ai:
    mcp:
      client:
        stdio:
          servers-configuration: classpath:mcp-servers.json

mcp-servers.json:

1
2
3
4
5
6
7
8
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"]
    }
  }
}

16.3 MCP Server配置

1
2
3
4
5
<!-- WebMVC Server -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
1
2
3
4
5
spring:
  ai:
    mcp:
      server:
        protocol: STREAMABLE  # SSE / STREAMABLE / STATELESS

16.4 MCP注解

Spring AI 1.1提供声明式MCP注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class MyMcpTools {

    @McpTool(description = "获取用户信息")
    public UserInfo getUserInfo(
        @ToolParam(description = "用户ID") String userId
    ) {
        return userService.getById(userId);
    }

    @McpResource(uri = "config://app-settings")
    public String getAppSettings() {
        return "App Settings...";
    }

    @McpPrompt(description = "生成问候语")
    public String greeting(@ToolParam(description = "名字") String name) {
        return "请为 " + name + " 生成一段问候语";
    }
}

企业价值:MCP让AI应用和工具提供方解耦,企业可以为AI应用提供统一的MCP工具服务,多个AI应用可以复用同一套工具。

十七、Agentic Patterns(智能体模式)

17.1 什么是AI Agent

AI Agent是能自主决策、调用工具、多步推理的智能体。Spring AI 1.1提供了5种基础Agent模式:

17.2 Chain Workflow(链式工作流)

将复杂任务分解为顺序执行的步骤,前一步的输出作为下一步的输入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Service
public class ChainWorkflow {

    private final ChatClient chatClient;

    public String execute(String input) {
        // Step 1: 提取关键信息
        String extracted = chatClient.prompt()
            .system("从用户输入中提取关键实体和信息,以JSON格式返回")
            .user(input)
            .call()
            .content();

        // Step 2: 基于提取信息生成方案
        String plan = chatClient.prompt()
            .system("根据提取的信息,生成解决方案")
            .user(extracted)
            .call()
            .content();

        // Step 3: 优化方案
        String refined = chatClient.prompt()
            .system("优化和完善方案,使其更具体可行")
            .user(plan)
            .call()
            .content();

        return refined;
    }
}

17.3 Routing Workflow(路由工作流)

根据输入内容路由到不同的处理逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Service
public class RoutingWorkflow {

    private final ChatClient chatClient;

    enum Route { TECHNICAL, BILLING, GENERAL }

    public String handle(String userQuery) {
        // Step 1: 分类
        Route route = chatClient.prompt()
            .system("将用户问题分类为:TECHNICAL(技术)、BILLING(账单)、GENERAL(一般)")
            .user(userQuery)
            .call()
            .entity(Route.class);

        // Step 2: 根据分类路由到不同处理
        return switch (route) {
            case TECHNICAL -> technicalHandler(userQuery);
            case BILLING -> billingHandler(userQuery);
            case GENERAL -> generalHandler(userQuery);
        };
    }
}

17.4 Parallelization Workflow(并行工作流)

多个子任务并行执行,最后汇总结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Service
public class ParallelWorkflow {

    private final ChatClient chatClient;

    public String analyze(String text) {
        // 并行执行多个分析任务
        CompletableFuture<String> sentimentFuture =
            CompletableFuture.supplyAsync(() ->
                chatClient.prompt()
                    .system("分析情感倾向")
                    .user(text)
                    .call().content());

        CompletableFuture<String> keywordFuture =
            CompletableFuture.supplyAsync(() ->
                chatClient.prompt()
                    .system("提取关键词")
                    .user(text)
                    .call().content());

        CompletableFuture<String> summaryFuture =
            CompletableFuture.supplyAsync(() ->
                chatClient.prompt()
                    .system("生成摘要")
                    .user(text)
                    .call().content());

        // 汇总结果
        String combined = String.join("\n",
            sentimentFuture.join(),
            keywordFuture.join(),
            summaryFuture.join());

        return chatClient.prompt()
            .system("整合以下分析结果,生成综合报告")
            .user(combined)
            .call().content();
    }
}

17.5 Orchestrator-Worker(编排者-工作者)

编排者分解任务,工作者并行执行,编排者汇总:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Service
public class OrchestratorWorker {

    private final ChatClient chatClient;

    record TaskPlan(String task, List<String> subtasks) {}

    public String execute(String task) {
        // 编排者:分解任务
        TaskPlan plan = chatClient.prompt()
            .system("将任务分解为可并行的子任务列表")
            .user(task)
            .call()
            .entity(TaskPlan.class);

        // 工作者:并行执行子任务
        List<CompletableFuture<String>> futures = plan.subtasks().stream()
            .map(subtask -> CompletableFuture.supplyAsync(() ->
                chatClient.prompt()
                    .system("执行子任务")
                    .user(subtask)
                    .call().content()))
            .toList();

        List<String> results = futures.stream()
            .map(CompletableFuture::join)
            .toList();

        // 编排者:汇总
        return chatClient.prompt()
            .system("整合所有子任务的结果")
            .user(String.join("\n", results))
            .call().content();
    }
}

17.6 Evaluator-Optimizer(评估者-优化者)

循环评估和优化,直到满足质量标准:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Service
public class EvaluatorOptimizer {

    private final ChatClient chatClient;
    private static final int MAX_ITERATIONS = 3;

    record Evaluation(boolean pass, String feedback) {}

    public String execute(String task) {
        String current = chatClient.prompt()
            .user(task)
            .call().content();

        for (int i = 0; i < MAX_ITERATIONS; i++) {
            // 评估
            Evaluation eval = chatClient.prompt()
                .system("评估输出质量,返回pass(是否合格)和feedback(改进建议)")
                .user("任务: " + task + "\n输出: " + current)
                .call()
                .entity(Evaluation.class);

            if (eval.pass()) break;

            // 优化
            current = chatClient.prompt()
                .system("根据反馈改进输出")
                .user("原输出: " + current + "\n反馈: " + eval.feedback())
                .call().content();
        }
        return current;
    }
}

企业场景:代码生成+自动测试评估循环、文案创作+质量审核循环、数据分析+结果校验循环。

十八、可观测性(Observability)

18.1 概述

Spring AI 提供了完整的可观测性支持,包括Metrics、Traces和Logs,基于Micrometer和OpenTelemetry实现。

18.2 核心指标

组件Metrics说明
ChatModelai.chat.model.token.usageToken使用量
ChatModelai.chat.model.latency响应延迟
EmbeddingModelai.embedding.model.latency嵌入延迟
VectorStoreai.vectorstore.latency向量搜索延迟
Advisorai.advisor.latencyAdvisor执行延迟

18.3 接入Prometheus + Grafana

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-zipkin</artifactId>
</dependency>
1
2
3
4
5
6
7
8
management:
  endpoints:
    web:
      exposure:
        include: prometheus
  tracing:
    sampling:
      probability: 1.0

18.4 OpenTelemetry集成

Spring Boot 4.0原生支持OpenTelemetry,Spring AI的Span和Metric遵循Semantic Conventions:

1
2
3
4
5
6
7
# application.yml
spring:
  ai:
    chat:
      observations:
        include-completion: true   # 记录完整响应
        include-prompt: true       # 记录完整提示词

生产建议:监控Token消耗(成本控制)、P99延迟(用户体验)、错误率(稳定性),设置告警阈值。

十九、评估与测试

19.1 RelevancyEvaluator(相关性评估)

评估AI响应是否与提供的上下文相关:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Test
void evaluateRelevancy() {
    String question = "Spring AI支持哪些模型?";

    // 获取RAG响应
    ChatResponse chatResponse = chatClient.prompt()
        .advisors(ragAdvisor)
        .user(question)
        .call()
        .chatResponse();

    // 构建评估请求
    EvaluationRequest evalRequest = new EvaluationRequest(
        question,                                                          // 用户问题
        chatResponse.getMetadata().get(RetrievalAugmentationAdvisor.DOCUMENT_CONTEXT),  // 检索的上下文
        chatResponse.getResult().getOutput().getText()                    // AI的响应
    );

    // 评估
    RelevancyEvaluator evaluator = new RelevancyEvaluator(
        ChatClient.builder(chatModel)
    );
    EvaluationResponse evalResponse = evaluator.evaluate(evalRequest);

    assertThat(evalResponse.isPass()).isTrue();
}

19.2 FactCheckingEvaluator(事实核查)

检测AI输出中的幻觉(hallucination):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
void testFactChecking() {
    // 使用专用的小模型做事实核查,降低成本
    OllamaApi ollamaApi = new OllamaApi("http://localhost:11434");
    ChatModel factCheckModel = new OllamaChatModel(ollamaApi,
        OllamaChatOptions.builder()
            .model("bespoke-minicheck")
            .temperature(0.0)
            .build());

    FactCheckingEvaluator evaluator = new FactCheckingEvaluator(
        ChatClient.builder(factCheckModel)
    );

    String context = "Spring AI 1.0于2025年5月发布。";
    String claim = "Spring AI 1.0于2024年发布。";  // 错误的声明

    EvaluationRequest request = new EvaluationRequest(
        context, Collections.emptyList(), claim
    );
    EvaluationResponse response = evaluator.evaluate(request);

    assertFalse(response.isPass());  // 应该检测到不一致
}

19.3 自动化测试策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@SpringBootTest
class AiApplicationTests {

    @Autowired
    private ChatClient chatClient;

    @Test
    void testStructuredOutput() {
        BookInfo book = chatClient.prompt()
            .user("推荐一本Spring书籍")
            .call()
            .entity(BookInfo.class);

        assertNotNull(book.title());
        assertNotNull(book.author());
    }

    @Test
    void testToolCalling() {
        String response = chatClient.prompt()
            .user("现在几点了?")
            .tools(new DateTimeTools())
            .call()
            .content();

        // 验证响应包含时间信息
        assertNotNull(response);
        assertTrue(response.contains("202"));  // 包含年份
    }
}

最佳实践:生成用强模型,评估用小模型(降本);评估是持续过程,不是一次性任务;建立评估基准线(baseline)。

二十、Spring AI 1.1 新特性

20.1 Prompt缓存

缓存重复Prompt的响应,可降低90%的成本:

1
2
3
4
5
6
spring:
  ai:
    openai:
      chat:
        options:
          cache-prompt: true

20.2 推理和思考模式

支持DeepSeek-R1等推理模型的思考过程:

1
2
3
4
5
6
// 推理模型会返回thinking内容
ChatResponse response = chatClient.prompt()
    .user("Solve: 25 * 37 + 148")
    .call()
    .chatResponse();
// response中包含reasoning_content

20.3 递归Advisor

允许Advisor内部调用ChatClient,形成自我修正的闭环:

1
2
// 1.1新增的递归Advisor
// 可用于:自我纠错、多步推理、质量提升循环

20.4 新增模型提供商

  • Google GenAI(Gemini系列)
  • ElevenLabs(TTS)
  • 更多向量数据库支持

二十一、企业级最佳实践

21.1 架构设计

1
2
3
4
5
6
7
8
9
10
11
┌──────────┐     ┌──────────────┐     ┌──────────────┐
│  前端UI   │ ←→ │ API Gateway  │ ←→ │ AI Service   │
│ (Vue/React)│     │  (Nginx)     │     │  (Spring AI) │
└──────────┘     └──────────────┘     └───────┬──────┘
                                              │
                              ┌───────────────┼───────────────┐
                              │               │               │
                        ┌─────┴────┐  ┌──────┴─────┐  ┌─────┴────┐
                        │ChatModel │  │VectorStore │  │ToolService│
                        │(GPT-4o)  │  │(PGVector)  │  │(业务系统)  │
                        └──────────┘  └────────────┘  └──────────┘

21.2 安全实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1. API Key管理 - 使用Vault或配置中心
@Value("${spring.ai.openai.api-key}")
private String apiKey;  // 从环境变量或配置中心获取

// 2. 输入校验 - 防止Prompt注入
public String chat(String userInput) {
    // 限制输入长度
    if (userInput.length() > 2000) {
        throw new IllegalArgumentException("输入过长");
    }
    // 过滤敏感词
    String sanitized = sanitize(userInput);
    return chatClient.prompt().user(sanitized).call().content();
}

// 3. 输出审核 - 检查AI响应是否合规

21.3 成本优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1. 模型分级使用
@Service
public class CostOptimizedService {

    // 简单任务用轻量模型
    private final ChatClient lightModel;  // gpt-4o-mini
    // 复杂任务用强力模型
    private final ChatClient heavyModel;  // gpt-4o

    public String ask(String question, Complexity complexity) {
        ChatClient client = complexity == Complexity.HIGH ? heavyModel : lightModel;
        return client.prompt().user(question).call().content();
    }
}

// 2. 缓存重复查询
// 3. 控制maxTokens
// 4. 使用Prompt缓存(1.1新特性)

21.4 高可用部署

1
2
3
4
5
6
7
8
# 多模型故障转移
spring:
  ai:
    retry:
      max-attempts: 3
      backoff:
        initial-interval: 1s
        multiplier: 2
1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class FailoverConfig {

    @Bean
    @Primary
    public ChatModel failoverChatModel(
            OpenAiChatModel openAi,
            OllamaChatModel ollama) {
        // 主模型不可用时切换到备用模型
        return new FailoverChatModel(openAi, ollama);
    }
}

21.5 生产环境检查清单

项目要求
API Key安全不硬编码,使用密钥管理服务
输入校验限制长度、过滤敏感词、防注入
输出审核内容合规检查
Token监控设置消耗告警
模型降级主模型不可用时有备选
对话记忆使用持久化存储(JDBC/Cassandra)
日志脱敏不记录敏感用户信息
响应缓存减少重复调用
速率限制防止滥用
评估测试定期运行自动化评估

21.6 企业实战案例汇总

场景技术方案核心能力
智能客服ChatClient + Tool Calling + Memory多轮对话、查询订单、申请退款
知识库问答RAG + VectorStore + ETL文档导入、语义搜索、精准回答
合同审核多模态 + Structured Output图片/文本理解、结构化提取
代码助手Tool Calling + Agent Pattern代码生成、执行验证、自我纠错
数据分析Structured Output + Chart自然语言查询、图表生成
邮件助手Agent + Tool Calling起草邮件、发送、日程管理
智能审核Evaluator-Optimizer多轮评估优化、质量保障
呼叫中心TTS + STT + RAG语音交互、知识检索

二十二、常见问题与踩坑

22.1 常见报错

API Key无效

1
401 Unauthorized - Check your API key

解决:检查key是否正确、是否过期、是否与环境变量对应。

Token超限

1
400 Bad Request - This model's maximum context length is 128000 tokens

解决:减少输入长度,使用TokenTextSplitter分块,选择更大上下文的模型。

模型不可用

1
429 Too Many Requests - Rate limit exceeded

解决:实现重试机制,添加限流,使用多key轮换。

22.2 性能优化

  1. 流式响应:使用stream()替代call(),提升用户体验
  2. 批处理嵌入:批量调用embedding接口,减少API调用次数
  3. 缓存:对相同问题缓存响应
  4. 异步:使用WebFlux + 虚拟线程提升并发
  5. 分块策略:合理设置chunk size和overlap

22.3 开发技巧

  1. 开发时用Ollama本地模型,省API费用
  2. 使用Spring AI BOM统一版本管理
  3. 对话记忆用内存存储开发,上线切JDBC
  4. RAG开发先用简单模式,再升级高级模式
  5. 工具调用先在ChatClient级别测试,再封装成Advisor
  6. 结构化输出搭配record类,简洁高效

二十三、学习资源

  • 官方文档:https://docs.spring.io/spring-ai/reference/
  • GitHub仓库:https://github.com/spring-projects/spring-ai
  • Spring Blog:https://spring.io/blog/category/spring-ai
  • Awesome Spring AI:https://github.com/spring-ai-community/awesome-spring-ai
  • Spring AI Alibaba:https://java2ai.com/
  • Baeldung教程:https://www.baeldung.com/spring-ai
  • Spring Academy:https://spring.academy/

附录:快速参考

A. Spring Boot Starter清单

Starter说明
spring-ai-openai-spring-boot-starterOpenAI
spring-ai-ollama-spring-boot-starterOllama
spring-ai-anthropic-spring-boot-starterAnthropic Claude
spring-ai-zhipuai-spring-boot-starter智谱AI
spring-ai-stabilityai-spring-boot-starterStability AI
spring-ai-pgvector-store-spring-boot-starterPGVector
spring-ai-redis-store-spring-boot-starterRedis Vector
spring-ai-advisors-vector-storeRAG Advisor
spring-ai-rag高级RAG
spring-ai-starter-mcp-clientMCP客户端
spring-ai-starter-mcp-server-webmvcMCP服务端(WebMVC)
spring-ai-starter-model-chat-memory-repository-jdbcJDBC对话记忆

B. 常用配置属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        base-url: https://api.openai.com
        options:
          model: gpt-4o
          temperature: 0.7
          max-tokens: 2048
          top-p: 1.0
    ollama:
      base-url: http://localhost:11434
      chat:
        model: llama3
    vectorstore:
      pgvector:
        index-type: HNSW
        distance-type: COSINE_DISTANCE
    chat:
      memory:
        repository:
          jdbc:
            initialize-schema: always
    mcp:
      client:
        stdio:
          servers-configuration: classpath:mcp-servers.json
本文由作者按照 CC BY 4.0 进行授权