欢迎光临
我们一直在努力

Spring AI 1.1.4 开发者使用手册

1. 快速开始

1.1 概述

Spring AI 是 Spring 生态系统的 AI 应用开发框架,提供统一的 API 抽象,支持 20+ AI 模型提供商和 19+ 向量数据库。

1.2 最小可运行示例

@SpringBootApplication
public class MyAiApplication {
   
   public static void main(String[] args) {
       SpringApplication.run(MyAiApplication.class, args);
  }
   
   @Bean
   CommandLineRunner demo(ChatClient chatClient) {
       return args -> {
           String response = chatClient.prompt()
              .user("Hello, Spring AI!")
              .call()
              .content();
           System.out.println(response);
      };
  }
}


2. 环境搭建与依赖配置

2.1 系统要求

组件最低版本推荐版本
Java 17 21
Spring Boot 3.2.x 3.5.x
Maven 3.6+ 3.9+

2.2 BOM 依赖管理

在 pom.xml 中添加 Spring AI BOM:

<properties>
   <spring-ai.version>1.1.4</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>

2.3 模型提供商依赖

OpenAI

<dependency>
   <groupId>org.springframework.ai</groupId>
   <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>

application.yml 配置:

spring:
ai:
  openai:
    api-key: ${OPENAI_API_KEY}
    base-url: https://api.openai.com  # 可选,用于代理
    chat:
      options:
        model: gpt-4
        temperature: 0.7
        max-tokens: 2000

智谱 AI (ZhiPuAI)

<dependency>
   <groupId>org.springframework.ai</groupId>
   <artifactId>spring-ai-starter-model-zhipuai</artifactId>
</dependency>

application.yml 配置:

spring:
ai:
  zhipuai:
    api-key: ${ZHIPUAI_API_KEY}
    chat:
      options:
        model: glm-4
        temperature: 0.7
        max-tokens: 2000

Ollama(本地模型)

<dependency>
   <groupId>org.springframework.ai</groupId>
   <artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>

application.yml 配置:

spring:
ai:
  ollama:
    base-url: http://localhost:11434
    chat:
      options:
        model: llama3
        temperature: 0.7

2.4 环境变量管理

开发环境 – .env 文件(配合 spring-dotenv):

# .env
OPENAI_API_KEY=sk-xxx
ZHIPUAI_API_KEY=xxx.xxx
PGVECTOR_HOST=localhost
PGVECTOR_PORT=5432
PGVECTOR_DATABASE=vectordb
PGVECTOR_USER=postgres
PGVECTOR_PASSWORD=secret

生产环境 – Kubernetes Secret:

apiVersion: v1
kind: Secret
metadata:
name: ai-config
type: Opaque
stringData:
OPENAI_API_KEY: "sk-xxx"
ZHIPUAI_API_KEY: "xxx"


3. ChatModel 集成指南

3.1 基础对话实现

方式一:使用 ChatClient(推荐)

@Service
public class ChatService {
   
   private final ChatClient chatClient;
   
   public ChatService(ChatClient.Builder chatClientBuilder) {
       this.chatClient = chatClientBuilder
          .defaultSystem("你是一个专业的技术助手")
          .defaultOptions(ChatOptions.builder()
              .temperature(0.7)
              .maxTokens(2000)
              .build())
          .build();
  }
   
   public String chat(String message) {
       return chatClient.prompt()
          .user(message)
          .call()
          .content();
  }
}

方式二:直接使用 ChatModel

@Service
public class AdvancedChatService {
   
   @Autowired
   private ChatModel chatModel;
   
   public String chatWithHistory(String message, List<Message> history) {
       List<Message> messages = new ArrayList<>(history);
       messages.add(new UserMessage(message));
       
       Prompt prompt = new Prompt(messages);
       ChatResponse response = chatModel.call(prompt);
       
       return response.getResult().getOutput().getText();
  }
}

3.2 系统提示与用户提示

public String chatWithContext(String userMessage) {
   return chatClient.prompt()
      .system("""
           你是一个专业的 Java 开发助手。
           请遵循以下原则:
           1. 提供清晰的代码示例
           2. 解释关键概念
           3. 指出最佳实践
           """)
      .user(userMessage)
      .call()
      .content();
}

3.3 参数配置详解

参数类型说明推荐值
temperature Double 创造性程度(0-2) 0.3-0.7
maxTokens Integer 最大生成 token 数 500-4000
topP Double 核采样概率 0.9-1.0
frequencyPenalty Double 频率惩罚(-2~2) 0.0
presencePenalty Double 存在惩罚(-2~2) 0.0

运行时动态配置:

public String chatWithOptions(String message, double temperature) {
   return chatClient.prompt()
      .user(message)
      .options(ChatOptions.builder()
          .temperature(temperature)
          .maxTokens(1000)
          .build())
      .call()
      .content();
}

3.4 结构化输出

POJO 定义

public record ActorFilms(
   String actor,
   List<String> movies
) {}

public record WeatherResponse(
   String city,
   double temperature,
   String condition,
   @JsonProperty("humidity_percent") int humidity
) {}

实体映射

public ActorFilms getActorFilms(String actorName) {
return chatClient.prompt()
.user("列出 " + actorName + " 主演的5部电影")
.call()
.entity(ActorFilms.class);
}

列表输出

public List<String> getMovieRecommendations(String genre) {
return chatClient.prompt()
.user("推荐5部" + genre + "类型的电影")
.call()
.entity(new ParameterizedTypeReference<List<String>>() {});
}

3.5 多轮对话管理

@Service
public class ConversationService {
   
   private final ChatClient chatClient;
   private final ChatMemory chatMemory;
   
   public String chat(String conversationId, String message) {
       return chatClient.prompt()
          .advisors(new MessageChatMemoryAdvisor(chatMemory, conversationId, 10))
          .user(message)
          .call()
          .content();
  }
}

// ChatMemory 配置
@Bean
public ChatMemory chatMemory() {
   // 内存存储(开发/测试)
   return new InMemoryChatMemory();
   
   // 或 Redis 存储(生产)
   // return new RedisChatMemory(redisTemplate, Duration.ofHours(24));
}


4. StreamingChatModel 流式响应

4.1 基础流式实现

@RestController
@RequestMapping("/api/chat")
public class ChatStreamController {
   
   @Autowired
   private ChatClient chatClient;
   
   @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
   public Flux<String> streamChat(@RequestParam String message) {
       return chatClient.prompt()
          .user(message)
          .stream()
          .content();
  }
}

4.2 带上下文的流式响应

@GetMapping(value = "/stream-with-context", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> streamWithContext(@RequestParam String message) {
   return chatClient.prompt()
      .user(message)
      .stream()
      .chatResponse()
      .map(response -> {
           String content = response.getResult().getOutput().getText();
           Usage usage = response.getMetadata().getUsage();
           
           return ServerSentEvent.<String>builder()
              .data(content)
              .id(response.getMetadata().getId())
              .build();
      });
}

4.3 WebFlux 响应式实现

@Configuration
public class ChatRouter {

@Bean
public RouterFunction<ServerResponse> chatRoutes(ChatClient chatClient) {
return RouterFunctions.route()
.GET("/stream/{message}", request -> {
String message = request.pathVariable("message");

Flux<String> stream = chatClient.prompt()
.user(message)
.stream()
.content();

return ServerResponse.ok()
.contentType(MediaType.TEXT_EVENT_STREAM)
.body(stream, String.class);
})
.build();
}
}

4.4 前端消费示例

// JavaScript EventSource 消费流式响应
function streamChat(message) {
const eventSource = new EventSource(`/api/chat/stream?message=${encodeURIComponent(message)}`);

eventSource.onmessage = (event) => {
appendToChatWindow(event.data);
};

eventSource.onerror = (error) => {
console.error('Stream error:', error);
eventSource.close();
};

eventSource.onclose = () => {
console.log('Stream closed');
};
}

4.5 流式工具调用

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


5. 向量存储接入配置

5.1 PostgreSQL + pgvector

依赖

<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>

配置

spring:
datasource:
  url: jdbc:postgresql://localhost:5432/vectordb
  username: postgres
  password: secret
ai:
  vectorstore:
    pgvector:
      index-type: hnsw          # 索引类型: hnsw, ivfflat
      distance-type: cosine_distance  # 距离度量: cosine_distance, l2_distance, inner_product
      dimensions: 1536          # 向量维度
      initialize-schema: true   # 自动创建表结构
      batching-strategy: token-count  # 批处理策略

Docker Compose

version: '3.8'
services:
postgres:
  image: ankane/pgvector:latest
  environment:
    POSTGRES_USER: postgres
    POSTGRES_PASSWORD: secret
    POSTGRES_DB: vectordb
  ports:
    – "5432:5432"
  volumes:
    – pgdata:/var/lib/postgresql/data
     
volumes:
pgdata:

5.2 Milvus 向量数据库

依赖

<dependency>
   <groupId>org.springframework.ai</groupId>
   <artifactId>spring-ai-starter-vector-store-milvus</artifactId>
</dependency>

配置

spring:
ai:
  vectorstore:
    milvus:
      client:
        host: localhost
        port: 19530
      database-name: default
      collection-name: document_store
      embedding-dimension: 1536
      index-type: IVF_FLAT
      metric-type: COSINE

5.3 向量存储基础操作

@Service
public class VectorStoreService {

@Autowired
private VectorStore vectorStore;

@Autowired
private EmbeddingModel embeddingModel;

// 添加文档
public void addDocuments(List<Document> documents) {
vectorStore.add(documents);
}

// 相似度搜索
public List<Document> search(String query, int topK) {
SearchRequest request = SearchRequest.builder()
.query(query)
.topK(topK)
.similarityThreshold(0.7)
.build();

return vectorStore.similaritySearch(request);
}

// 带过滤条件的搜索
public List<Document> searchWithFilter(String query, String category) {
SearchRequest request = SearchRequest.builder()
.query(query)
.topK(5)
.filterExpression(String.format("category == '%s'", category))
.build();

return vectorStore.similaritySearch(request);
}

// 删除文档
public void deleteDocuments(List<String> ids) {
vectorStore.delete(ids);
}

// 根据过滤条件删除
public void deleteByCategory(String category) {
vectorStore.delete(String.format("category == '%s'", category));
}
}

5.4 文档处理与存储

@Service
public class DocumentService {

@Autowired
private VectorStore vectorStore;

// 从文件加载文档
public void loadDocumentFromFile(String filePath) {
TextReader reader = new TextReader(new FileSystemResource(filePath));
reader.getCustomMetadata().put("source", filePath);

List<Document> documents = reader.get();

// 文档切分
TokenTextSplitter splitter = new TokenTextSplitter(
800, // chunk size
100, // min chunk size
200, // chunk overlap
5000, // max character count
true // keep separator
);

List<Document> splitDocuments = splitter.apply(documents);

// 添加元数据
splitDocuments.forEach(doc -> {
doc.getMetadata().put("upload_time", Instant.now().toString());
doc.getMetadata().put("category", "technical_docs");
});

// 存入向量数据库
vectorStore.add(splitDocuments);
}

// 从 PDF 加载
public void loadPdfDocument(String filePath) {
PagePdfDocumentReader reader = new PagePdfDocumentReader(
filePath,
PdfDocumentReaderConfig.builder()
.withPageTopMargin(0)
.withPageExtractedTextFormatter(
ExtractedTextFormatter.builder()
.withNumberOfTopTextLinesToDelete(0)
.build()
)
.withPagesPerDocument(1)
.build()
);

List<Document> documents = reader.get();
vectorStore.add(documents);
}
}

5.5 过滤器表达式语法

运算符示例说明
== category == 'tech' 等于
!= status != 'deleted' 不等于
> year > 2020 大于
>= score >= 0.8 大于等于
< price < 100 小于
<= quantity <= 10 小于等于
AND category == 'tech' AND year >= 2023 逻辑与
OR status == 'active' OR status == 'pending' 逻辑或
IN category IN ['tech', 'science'] 包含
NOT NOT (status == 'deleted') 逻辑非

6. RAG 检索增强生成实战

6.1 基础 RAG 实现

@Service
public class RagService {
   
   @Autowired
   private ChatClient chatClient;
   
   @Autowired
   private VectorStore vectorStore;
   
   public String ask(String question) {
       // 1. 检索相关文档
       SearchRequest request = SearchRequest.builder()
          .query(question)
          .topK(5)
          .similarityThreshold(0.6)
          .build();
       
       List<Document> documents = vectorStore.similaritySearch(request);
       
       // 2. 构建上下文
       String context = documents.stream()
          .map(Document::getText)
          .collect(Collectors.joining("\\n\\n—\\n\\n"));
       
       // 3. 生成回答
       return chatClient.prompt()
          .system("""
               你是一个专业的问答助手。
               请基于以下提供的上下文信息回答用户问题。
               如果上下文中没有相关信息,请明确告知用户。
               
               上下文信息:
              {context}
               """)
          .user(question)
          .call()
          .content();
  }
}

6.2 使用 QuestionAnswerAdvisor

@Service
public class AdvancedRagService {

@Autowired
private ChatClient chatClient;

@Autowired
private VectorStore vectorStore;

public String askWithAdvisor(String question) {
return chatClient.prompt()
.advisors(new QuestionAnswerAdvisor(
vectorStore,
SearchRequest.defaults()
.withTopK(5)
.withSimilarityThreshold(0.6)
))
.user(question)
.call()
.content();
}
}

6.3 完整 RAG Pipeline

@Configuration
public class RagConfig {

@Bean
public RetrievalAugmentationAdvisor retrievalAugmentationAdvisor(
VectorStore vectorStore,
ChatClient chatClient) {

return RetrievalAugmentationAdvisor.builder()
// 1. 查询转换(可选)
.queryTransformers(
new TranslationQueryTransformer(chatClient, "zh")
)

// 2. 查询扩展(可选)
.queryExpander(new MultiQueryExpander(chatClient, 3))

// 3. 文档检索器
.documentRetriever(VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.searchRequest(SearchRequest.defaults()
.withTopK(5)
.withSimilarityThreshold(0.6))
.build())

// 4. 文档合并器
.documentJoiner(new ConcatenationDocumentJoiner())

// 5. 文档后处理器(可选)
.documentPostProcessors(
new DocumentRerankerPostProcessor(chatClient)
)

// 6. 查询增强器
.queryAugmenter(ContextualQueryAugmenter.builder()
.promptTemplate("""
基于以下上下文信息回答问题。

上下文:
{context}

问题:{query}

请仅基于提供的上下文回答。如果无法从上下文中找到答案,
请明确说明\\"根据提供的信息,我无法回答这个问题\\"。
""")
.build())

.build();
}
}

// 使用
@Service
public class ModularRagService {

@Autowired
private ChatClient chatClient;

@Autowired
private RetrievalAugmentationAdvisor ragAdvisor;

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

6.4 文档上传与索引

@RestController
@RequestMapping("/api/documents")
public class DocumentController {

@Autowired
private VectorStore vectorStore;

@Autowired
private EmbeddingModel embeddingModel;

@PostMapping("/upload")
public ResponseEntity<?> uploadDocument(
@RequestParam("file") MultipartFile file,
@RequestParam("category") String category) {

try {
// 保存临时文件
Path tempFile = Files.createTempFile("upload-", ".tmp");
file.transferTo(tempFile);

// 读取文档
List<Document> documents;
String contentType = file.getContentType();

if (contentType != null && contentType.contains("pdf")) {
PagePdfDocumentReader reader = new PagePdfDocumentReader(
tempFile.toString());
documents = reader.get();
} else {
TextReader reader = new TextReader(new FileSystemResource(tempFile));
documents = reader.get();
}

// 文档切分
TokenTextSplitter splitter = new TokenTextSplitter(800, 100, 200, 5000, true);
List<Document> chunks = splitter.apply(documents);

// 添加元数据
chunks.forEach(doc -> {
doc.getMetadata().put("source", file.getOriginalFilename());
doc.getMetadata().put("category", category);
doc.getMetadata().put("upload_time", Instant.now().toString());
});

// 存入向量数据库
vectorStore.add(chunks);

// 清理临时文件
Files.delete(tempFile);

return ResponseEntity.ok(Map.of(
"status", "success",
"chunks", chunks.size()
));

} catch (Exception e) {
return ResponseEntity.badRequest().body(Map.of(
"status", "error",
"message", e.getMessage()
));
}
}
}

6.5 RAG 性能优化

@Configuration
public class OptimizedRagConfig {

@Bean
public RetrievalAugmentationAdvisor optimizedRagAdvisor(
VectorStore vectorStore) {

return RetrievalAugmentationAdvisor.builder()
.queryExpander(new MultiQueryExpander(
ChatClient.create(new OpenAiChatModel(…)),
3 // 生成3个相关查询
))
.documentRetriever(VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.searchRequest(SearchRequest.defaults()
.withTopK(10) // 检索更多文档
.withSimilarityThreshold(0.5)) // 降低阈值
.build())
.documentJoiner(new ConcatenationDocumentJoiner())
.documentPostProcessors(
// 去重处理器
new De-duplicationDocumentPostProcessor(),
// 重排序处理器
new RerankDocumentPostProcessor()
)
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(false) // 不允许空上下文
.build())
// 使用线程池并行处理
.taskExecutor(new ThreadPoolTaskExecutor() {{
setCorePoolSize(4);
setMaxPoolSize(8);
setQueueCapacity(100);
setThreadNamePrefix("rag-");
initialize();
}})
.build();
}
}


7. MCP 模型上下文协议应用

7.1 MCP 简介

Model Context Protocol (MCP) 是标准化的 AI 工具集成协议,允许 AI 模型发现和调用外部工具。

7.2 MCP Client 配置

依赖

<dependency>
   <groupId>org.springframework.ai</groupId>
   <artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>

配置

spring:
ai:
  mcp:
    client:
      enabled: true
      name: my-mcp-client
      version: 1.0.0
      type: sync  # sync 或 async
      request-timeout: 30s
      servers:
        filesystem:
          command: npx
          args:
            – "-y"
            – "@modelcontextprotocol/server-filesystem"
            – "/path/to/allowed/directory"
        sqlite:
          command: uvx
          args:
            – "mcp-server-sqlite"
            – "–db-path"
            – "/path/to/database.db"

7.3 使用 MCP 工具

@Service
public class McpToolService {
   
   @Autowired
   private List<ToolCallback> mcpTools;
   
   @Autowired
   private ChatClient chatClient;
   
   public String chatWithMcpTools(String message) {
       return chatClient.prompt()
          .user(message)
          .tools(mcpTools.toArray(new ToolCallback[0]))
          .call()
          .content();
  }
}

7.4 自定义 MCP Server

@Component
public class WeatherMcpServer {

@Tool(description = "获取指定城市的天气信息")
public WeatherInfo getWeather(
@ToolParam(description = "城市名称,如:北京、上海") String city,
@ToolParam(description = "日期,格式:yyyy-MM-dd,默认为今天")
@Nullable String date) {

String actualDate = date != null ? date : LocalDate.now().toString();
return weatherService.fetch(city, actualDate);
}

@Tool(description = "查询历史天气记录")
public List<WeatherInfo> getHistoricalWeather(
@ToolParam(description = "城市名称") String city,
@ToolParam(description = "开始日期") String startDate,
@ToolParam(description = "结束日期") String endDate) {

return weatherService.fetchHistory(city, startDate, endDate);
}
}

// 数据模型
public record WeatherInfo(
String city,
String date,
double temperature,
String condition,
int humidity,
double windSpeed
) {}

7.5 MCP Server 配置类

@Configuration
public class McpServerConfig {

@Bean
public SyncMcpToolCallbackProvider weatherToolProvider(
WeatherMcpServer weatherServer) {

return new SyncMcpToolCallbackProvider(
new MethodToolCallbackProvider(weatherServer)
);
}
}


8. 工具调用 (Function Calling)

8.1 基础工具定义

@Component
public class CalculatorTools {

@Tool(description = "执行数学计算")
public double calculate(
@ToolParam(description = "数学表达式,如:2 + 2") String expression) {
// 使用表达式引擎计算
return new ExpressionParser().parse(expression).evaluate();
}

@Tool(description = "转换温度单位")
public double convertTemperature(
@ToolParam(description = "温度值") double value,
@ToolParam(description = "源单位:celsius, fahrenheit, kelvin") String from,
@ToolParam(description = "目标单位:celsius, fahrenheit, kelvin") String to) {

return TemperatureConverter.convert(value, from, to);
}
}

8.2 注册工具到 ChatClient

@Configuration
public class ToolConfig {

@Bean
public ChatClient chatClientWithTools(
ChatClient.Builder builder,
CalculatorTools calculatorTools,
WeatherTools weatherTools) {

return builder
.defaultTools(calculatorTools, weatherTools)
.build();
}
}

8.3 运行时动态工具

@Service
public class DynamicToolService {

@Autowired
private ChatClient chatClient;

public String chatWithSpecificTools(String message, List<Object> tools) {
return chatClient.prompt()
.user(message)
.tools(tools.toArray())
.call()
.content();
}
}

8.4 工具调用结果处理

@Service
public class ToolResultHandler {
   
   @Autowired
   private ChatClient chatClient;
   
   public String handleToolCalls(String message) {
       ChatClient.ChatClientRequestSpec spec = chatClient.prompt()
          .user(message)
          .tools(new DatabaseQueryTool());
       
       ChatClientResponse response = spec.call();
       
       // 检查是否有工具调用
       if (response.getResult().getOutput() instanceof AssistantMessage assistantMsg) {
           List<ToolCall> toolCalls = assistantMsg.getToolCalls();
           
           if (!toolCalls.isEmpty()) {
               // 处理工具调用
               List<ToolResponseMessage> toolResponses = toolCalls.stream()
                  .map(this::executeTool)
                  .toList();
               
               // 继续对话,传入工具结果
               return spec.prompt(new Prompt(toolResponses))
                  .call()
                  .content();
          }
      }
       
       return response.content();
  }
   
   private ToolResponseMessage executeTool(ToolCall toolCall) {
       // 执行工具逻辑
       String result = toolExecutor.execute(toolCall.name(), toolCall.arguments());
       return new ToolResponseMessage(result, toolCall.name(), toolCall.id());
  }
}


9. 多模态支持

9.1 图像输入

@Service
public class ImageAnalysisService {
   
   @Autowired
   private ChatClient chatClient;
   
   public String analyzeImage(String imageUrl, String question) {
       return chatClient.prompt()
          .user(userSpec -> userSpec
              .text(question)
              .media(MimeTypeUtils.IMAGE_JPEG, imageUrl)
          )
          .call()
          .content();
  }
   
   public String analyzeLocalImage(Resource imageResource, String question) {
       return chatClient.prompt()
          .user(userSpec -> userSpec
              .text(question)
              .media(MimeTypeUtils.IMAGE_PNG, imageResource)
          )
          .call()
          .content();
  }
}

9.2 图像生成

@Service
public class ImageGenerationService {

@Autowired
private ImageModel imageModel;

public Resource generateImage(String prompt) {
ImagePrompt imagePrompt = new ImagePrompt(
prompt,
ImageOptionsBuilder.builder()
.withModel("dall-e-3")
.withWidth(1024)
.withHeight(1024)
.withResponseFormat("url")
.build()
);

ImageResponse response = imageModel.call(imagePrompt);
String imageUrl = response.getResult().getOutput().getUrl();

// 下载并返回图像资源
return downloadImage(imageUrl);
}
}

9.3 音频转录

@Service
public class AudioService {

@Autowired
private OpenAiAudioTranscriptionModel transcriptionModel;

public String transcribeAudio(Resource audioFile) {
OpenAiAudioTranscriptionOptions options = OpenAiAudioTranscriptionOptions.builder()
.withLanguage("zh")
.withResponseFormat(TranscriptResponseFormat.TEXT)
.withTemperature(0.0)
.build();

AudioTranscriptionPrompt prompt = new AudioTranscriptionPrompt(audioFile, options);
AudioTranscriptionResponse response = transcriptionModel.call(prompt);

return response.getResult().getOutput();
}
}


10. 性能优化指南

10.1 连接池配置

@Configuration
public class ConnectionPoolConfig {

@Bean
public RestClient.Builder restClientBuilder() {
return RestClient.builder()
.requestFactory(new JdkClientHttpRequestFactory() {
{
setReadTimeout(Duration.ofSeconds(60));
}
});
}
}

10.2 批处理嵌入

@Service
public class BatchEmbeddingService {

@Autowired
private EmbeddingModel embeddingModel;

public List<float[]> embedDocuments(List<Document> documents) {
// 使用批处理策略
return embeddingModel.embed(
documents,
EmbeddingOptions.builder().build(),
new TokenCountBatchingStrategy(8191) // OpenAI 的 token 限制
);
}
}

10.3 响应缓存

@Configuration
@EnableCaching
public class CacheConfig {
   
   @Bean
   public CacheManager cacheManager() {
       CaffeineCacheManager cacheManager = new CaffeineCacheManager();
       cacheManager.setCaffeine(Caffeine.newBuilder()
          .maximumSize(1000)
          .expireAfterWrite(Duration.ofMinutes(30)));
       return cacheManager;
  }
}

@Service
public class CachedAiService {
   
   @Autowired
   private ChatClient chatClient;
   
   @Cacheable(value = "ai-responses", key = "#message.hashCode()")
   public String getCachedResponse(String message) {
       return chatClient.prompt()
          .user(message)
          .call()
          .content();
  }
}

10.4 限流控制

@Component
public class RateLimitedAiService {
   
   private final RateLimiter rateLimiter = RateLimiterBuilder.newBuilder()
      .withRate(10, TimeUnit.SECONDS)  // 每秒10个请求
      .withTimeout(Duration.ofSeconds(5))
      .build();
   
   @Autowired
   private ChatClient chatClient;
   
   public String limitedChat(String message) {
       rateLimiter.acquire();
       
       return chatClient.prompt()
          .user(message)
          .call()
          .content();
  }
}

10.5 异步处理

@Service
public class AsyncAiService {
   
   @Autowired
   private ChatClient chatClient;
   
   @Async("aiTaskExecutor")
   public CompletableFuture<String> asyncChat(String message) {
       String response = chatClient.prompt()
          .user(message)
          .call()
          .content();
       
       return CompletableFuture.completedFuture(response);
  }
}

@Configuration
public class AsyncConfig {
   
   @Bean("aiTaskExecutor")
   public TaskExecutor aiTaskExecutor() {
       ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
       executor.setCorePoolSize(10);
       executor.setMaxPoolSize(20);
       executor.setQueueCapacity(100);
       executor.setThreadNamePrefix("ai-async-");
       executor.initialize();
       return executor;
  }
}


11. 常见陷阱与故障排查

11.1 API Key 配置问题

问题: IllegalArgumentException: API key must not be empty

解决方案:

// 检查环境变量
@EventListener(ApplicationReadyEvent.class)
public void checkApiKeys() {
   String openAiKey = System.getenv("OPENAI_API_KEY");
   if (StringUtils.isEmpty(openAiKey)) {
       log.error("OPENAI_API_KEY not set!");
  }
}

// 或使用 @PostConstruct
@PostConstruct
public void validateConfig() {
   Assert.hasText(apiKey, "OpenAI API key must be configured");
}

11.2 超时问题

问题: ReadTimeoutException 或 SocketTimeoutException

解决方案:

spring:
ai:
  openai:
    chat:
      options:
        timeout: 120s  # 增加超时时间

@Bean
public OpenAiApi openAiApi() {
   return OpenAiApi.builder()
      .apiKey(apiKey)
      .restClientBuilder(RestClient.builder()
          .requestFactory(new SimpleClientHttpRequestFactory() {{
               setReadTimeout(120000);  // 2分钟
               setConnectTimeout(10000); // 10秒
          }}))
      .build();
}

11.3 向量维度不匹配

问题: Dimension mismatch 或向量搜索返回空结果

解决方案:

@Configuration
public class EmbeddingConfig {
   
   @Bean
   public EmbeddingModel embeddingModel() {
       return new OpenAiEmbeddingModel(OpenAiApi.builder()
          .apiKey(apiKey)
          .build(),
           OpenAiEmbeddingOptions.builder()
              .withModel("text-embedding-ada-002")  // 1536 维
              .build());
  }
   
   @Bean
   public VectorStore vectorStore(EmbeddingModel embeddingModel) {
       return PgVectorStore.builder(embeddingModel)
          .dimensions(1536)  // 必须与模型输出维度一致
          .initializeSchema(true)
          .build();
  }
}

11.4 工具调用循环

问题: 工具调用无限循环

解决方案:

@Service
public class SafeToolService {
   
   private static final int MAX_TOOL_CALLS = 5;
   
   public String safeChatWithTools(String message) {
       int toolCallCount = 0;
       ChatClient.ChatClientRequestSpec spec = chatClient.prompt()
          .user(message)
          .tools(availableTools);
       
       ChatClientResponse response = spec.call();
       
       while (hasToolCalls(response) && toolCallCount < MAX_TOOL_CALLS) {
           response = handleToolCallsAndContinue(spec, response);
           toolCallCount++;
      }
       
       if (toolCallCount >= MAX_TOOL_CALLS) {
           log.warn("Max tool calls reached");
      }
       
       return response.content();
  }
}

11.5 内存溢出

问题: OutOfMemoryError 处理大文档

解决方案:

@Service
public class SafeDocumentService {
   
   private static final int BATCH_SIZE = 100;
   
   public void addLargeDocument(List<Document> documents) {
       // 分批处理
       for (int i = 0; i < documents.size(); i += BATCH_SIZE) {
           List<Document> batch = documents.subList(
               i,
               Math.min(i + BATCH_SIZE, documents.size())
          );
           vectorStore.add(batch);
           
           // 触发 GC
           System.gc();
      }
  }
}

11.6 故障排查检查清单

问题检查项
连接失败 API Key 是否正确、网络是否通畅、代理配置
响应慢 超时配置、模型选择、流式响应
质量差 Temperature 设置、系统提示优化、RAG 上下文
向量搜索差 维度匹配、相似度阈值、文档切分策略
工具调用失败 工具定义是否正确、参数格式、异常处理

12. 生产环境最佳实践

12.1 项目结构推荐

my-ai-service/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/company/ai/
│   │   │       ├── config/
│   │   │       │   ├── AiConfig.java
│   │   │       │   ├── VectorStoreConfig.java
│   │   │       │   ├── SecurityConfig.java
│   │   │       │   └── CacheConfig.java
│   │   │       ├── controller/
│   │   │       │   ├── ChatController.java
│   │   │       │   └── DocumentController.java
│   │   │       ├── service/
│   │   │       │   ├── ChatService.java
│   │   │       │   ├── RagService.java
│   │   │       │   └── DocumentService.java
│   │   │       ├── tools/
│   │   │       │   ├── WeatherTools.java
│   │   │       │   └── DatabaseTools.java
│   │   │       ├── advisor/
│   │   │       │   └── LoggingAdvisor.java
│   │   │       └── security/
│   │   │           └── ApiKeyFilter.java
│   │   └── resources/
│   │       ├── application.yml
│   │       ├── application-prod.yml
│   │       └── prompts/
│   │           ├── system-prompt.st
│   │           └── rag-template.st
│   └── test/
├── k8s/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── configmap.yaml
├── docker-compose.yml
└── Dockerfile

12.2 配置分离

# application.yml – 公共配置
spring:
ai:
  chat:
    client:
      enabled: true


# application-dev.yml – 开发环境
spring:
ai:
  openai:
    api-key: ${OPENAI_API_KEY:dev-key}
    chat:
      options:
        model: gpt-3.5-turbo


# application-prod.yml – 生产环境
spring:
ai:
  openai:
    api-key: ${OPENAI_API_KEY}
    chat:
      options:
        model: gpt-4
        temperature: 0.3  # 更稳定的输出
  vectorstore:
    pgvector:
      initialize-schema: false  # 生产不自动建表

12.3 监控与告警

@Component
public class AiMetrics {
   
   private final MeterRegistry meterRegistry;
   
   public void recordChatMetrics(ChatResponse response, long durationMs) {
       Usage usage = response.getMetadata().getUsage();
       
       meterRegistry.counter("ai.chat.requests").increment();
       meterRegistry.timer("ai.chat.duration").record(durationMs, TimeUnit.MILLISECONDS);
       meterRegistry.counter("ai.chat.tokens.input").increment(usage.getPromptTokens());
       meterRegistry.counter("ai.chat.tokens.output").increment(usage.getGenerationTokens());
  }
}

12.4 安全最佳实践

@Configuration
public class SecurityConfig {
   
   @Bean
   public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
       http
          .csrf(csrf -> csrf.disable())
          .authorizeHttpRequests(auth -> auth
              .requestMatchers("/api/chat/**").authenticated()
              .anyRequest().permitAll()
          )
          .oauth2ResourceServer(oauth2 -> oauth2.jwt());
       
       return http.build();
  }
}

// 输入过滤
@Service
public class InputSanitizationService {
   
   public String sanitizeInput(String input) {
       return Jsoup.clean(input, Safelist.basic());
  }
}

12.5 完整示例应用

@SpringBootApplication
@EnableCaching
@EnableAsync
public class AiServiceApplication {
   
   public static void main(String[] args) {
       SpringApplication.run(AiServiceApplication.class, args);
  }
}

@RestController
@RequestMapping("/api/v1")
@Validated
public class AiController {
   
   @Autowired
   private ChatService chatService;
   
   @Autowired
   private RagService ragService;
   
   @PostMapping("/chat")
   public ResponseEntity<ChatResponseDto> chat(@Valid @RequestBody ChatRequestDto request) {
       String response = chatService.chat(request.getMessage());
       return ResponseEntity.ok(new ChatResponseDto(response));
  }
   
   @PostMapping("/chat/stream")
   public Flux<ServerSentEvent<String>> chatStream(@Valid @RequestBody ChatRequestDto request) {
       return chatService.chatStream(request.getMessage())
          .map(content -> ServerSentEvent.builder(content).build());
  }
   
   @PostMapping("/ask")
   public ResponseEntity<ChatResponseDto> askWithRag(@Valid @RequestBody ChatRequestDto request) {
       String response = ragService.ask(request.getMessage());
       return ResponseEntity.ok(new ChatResponseDto(response));
  }
}

// DTOs
public record ChatRequestDto(
   @NotBlank @Size(max = 4000) String message,
   String conversationId
) {}

public record ChatResponseDto(
   String response,
   Instant timestamp
) {
   public ChatResponseDto(String response) {
       this(response, Instant.now());
  }
}


附录

A. 依赖速查表

功能MAVEN 依赖
OpenAI spring-ai-starter-model-openai
智谱 AI spring-ai-starter-model-zhipuai
Ollama spring-ai-starter-model-ollama
pgvector spring-ai-starter-vector-store-pgvector
Milvus spring-ai-starter-vector-store-milvus
MCP Client spring-ai-starter-mcp-client
MCP Server spring-ai-starter-mcp-server

B. 模型参数对照表

模型提供商上下文长度特点
gpt-4 OpenAI 8K/32K 能力强,价格高
gpt-3.5-turbo OpenAI 16K 性价比高
glm-4 智谱 AI 128K 中文能力强
glm-4-flash 智谱 AI 128K 速度快,价格低
llama3 Ollama 8K 本地运行,隐私好

C. 向量数据库选择指南

场景推荐方案
小规模/原型 SimpleVectorStore
企业应用 PostgreSQL + pgvector
大规模检索 Milvus / Pinecone
现有 ES 集群 Elasticsearch
云原生 Redis / MongoDB Atlas
赞(0)
未经允许不得转载:171主机测评 » Spring AI 1.1.4 开发者使用手册
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址