Skip to main content
Innovation|Innovation

使用Spring AI构建AI驱动的Java应用程序:完整指南

使用Spring AI构建AI驱动Java应用程序的完整指南——涵盖ChatClient、提示模板、结构化输出、RAG、函数调用、顾问器、聊天记忆、嵌入向量、多模态以及提供商配置。

2026年4月10日4 min read

Spring AI为何改变Java开发者的一切

Spring AI为人工智能世界带来了Spring Boot开发者所喜爱的相同生产力和可移植性。针对干净的抽象编写AI代码一次,然后通过配置更改(而非代码重写)切换提供商——OpenAI、Anthropic Claude、Google Gemini、Ollama、AWS Bedrock。

于2025年5月以1.0 GA版本发布,Spring AI提供:统一的聊天API、映射到Java Record的结构化输出、内置RAG支持、函数/工具调用、聊天记忆、顾问器、嵌入向量、图像生成、多模态和评估——全部带有Spring Boot自动配置。

入门

将BOM和提供商starter添加到你的pom.xml

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

<!-- Pick ONE provider starter -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>

application.yml中配置:

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4o
          temperature: 0.7

核心抽象:ChatModel、ChatClient、Prompt

Spring AI的强大之处在于与提供商无关的接口。你的代码面向抽象编程;Spring Boot注入具体的提供商。

ChatClient — 流式API(推荐)

@RestController
class ChatController {
    private final ChatClient chatClient;

    ChatController(ChatClient.Builder builder) {
        this.chatClient = builder
            .defaultSystem("You are a helpful coding assistant.")
            .build();
    }

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

消息类型

SystemMessage设置指令。UserMessage携带用户输入(文本+多模态媒体)。AssistantMessage保存模型回复。ToolResponseMessage返回工具/函数结果。

流式响应

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

提示模板

使用Spring AI的PromptTemplate通过变量替换保持提示的可复用性:

// Inline template
String answer = chatClient.prompt()
    .user(u -> u
        .text("List {count} best practices for {topic}")
        .param("count", "5")
        .param("topic", "REST API design"))
    .call()
    .content();

// System prompt template
String systemText = """
    You are an expert in {domain}.
    Reply in the style of a {style}.
    """;

SystemPromptTemplate systemTemplate = new SystemPromptTemplate(systemText);
Message systemMessage = systemTemplate.createMessage(Map.of(
    "domain", "distributed systems",
    "style", "senior engineer"
));

// Load from classpath resource
@Value("classpath:/prompts/analysis.st")
private Resource analysisTemplate;
PromptTemplate template = new PromptTemplate(analysisTemplate);

提示工程最佳实践

要具体:告诉模型你确切需要的格式、语气和约束。使用系统消息:在系统提示中设置角色和规则,在用户提示中放用户内容。单样本/少样本:包含一个期望输出的示例。思维链:要求模型"逐步思考"以处理复杂推理。结构化输出:请求JSON并映射到Record(见下一节)。

结构化输出 — AI响应作为Java Record

将AI生成的文本直接映射到类型化的Java对象。无需手动JSON解析。

// Define your record
record BookRecommendation(String title, String author,
                          String genre, String summary) {}

// Single entity
BookRecommendation book = chatClient.prompt()
    .user("Recommend a classic science fiction novel.")
    .call()
    .entity(BookRecommendation.class);

// List of entities
List<BookRecommendation> books = chatClient.prompt()
    .user("Recommend 5 classic sci-fi novels.")
    .call()
    .entity(new ParameterizedTypeReference<List<BookRecommendation>>() {});

// Map output
Map<String, Object> data = chatClient.prompt()
    .user("List the population of Tokyo, London, and New York")
    .call()
    .entity(new ParameterizedTypeReference<Map<String, Object>>() {});

RAG:检索增强生成

RAG让你的AI使用你自己的数据回答问题,通过从向量存储中检索相关文档并将其作为上下文注入提示中。

步骤1:文档摄取(ETL)

@Component
class DocumentIngestionService {
    private final VectorStore vectorStore;

    DocumentIngestionService(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    public void ingest(String pdfPath) {
        // 1. Read documents (PDF, JSON, HTML, Markdown, DOCX supported)
        PagePdfDocumentReader reader = new PagePdfDocumentReader(pdfPath,
            PdfDocumentReaderConfig.builder()
                .withPagesPerDocument(1).build());

        // 2. Split into chunks
        TokenTextSplitter splitter = TokenTextSplitter.builder()
            .withChunkSize(800)
            .withMinChunkSizeChars(350).build();

        // 3. Store — embeddings generated automatically
        vectorStore.write(splitter.apply(reader.read()));
    }
}

步骤2:使用QuestionAnswerAdvisor查询

ChatResponse response = chatClient.prompt()
    .advisors(QuestionAnswerAdvisor.builder(vectorStore)
        .searchRequest(SearchRequest.builder()
            .similarityThreshold(0.75)
            .topK(5).build())
        .build())
    .user("What does our refund policy say about digital products?")
    .call()
    .chatResponse();

高级RAG与查询改写

Advisor ragAdvisor = RetrievalAugmentationAdvisor.builder()
    .queryTransformers(RewriteQueryTransformer.builder()
        .chatClientBuilder(chatClientBuilder.build().mutate()).build())
    .documentRetriever(VectorStoreDocumentRetriever.builder()
        .vectorStore(vectorStore)
        .similarityThreshold(0.50)
        .topK(5).build())
    .build();

String answer = chatClient.prompt()
    .advisors(ragAdvisor)
    .user("How do I configure SSL?")
    .call()
    .content();

向量存储配置(PGVector)

# application.yml
spring:
  ai:
    vectorstore:
      pgvector:
        initialize-schema: true
        dimensions: 1536
        distance-type: cosine_distance

支持的向量存储:PGVector、Chroma、Pinecone、Redis、Milvus、Weaviate、Qdrant、Elasticsearch、MongoDB Atlas、Neo4j等。

函数调用 / 工具使用

让AI模型调用你的Java方法来获取实时数据或执行操作。

使用@Tool的声明式方法

class WeatherTools {

    @Tool(description = "Get current weather for a given city")
    String getWeather(
            @ToolParam(description = "City name") String city,
            @ToolParam(description = "Temperature unit", required = false) String unit) {
        // Call a real weather API here
        return "Weather in %s: 22 degrees %s, sunny."
            .formatted(city, unit != null ? unit : "Celsius");
    }
}

// Use it — the model decides when to call the function
String response = chatClient.prompt()
    .user("What's the weather like in London?")
    .tools(new WeatherTools())
    .call()
    .content();

作为Spring Bean的函数

public record CurrencyRequest(String from, String to, double amount) {}
public record CurrencyResponse(double convertedAmount, double rate) {}

@Bean
@Description("Convert an amount from one currency to another")
Function<CurrencyRequest, CurrencyResponse> convertCurrency() {
    return request -> {
        double rate = fetchExchangeRate(request.from(), request.to());
        return new CurrencyResponse(request.amount() * rate, rate);
    };
}

// Reference by bean name
String answer = chatClient.prompt()
    .user("Convert 100 USD to EUR")
    .toolNames("convertCurrency")
    .call()
    .content();

工具上下文 — 传递额外数据

class CustomerTools {
    @Tool(description = "Get customer by ID")
    Customer getCustomer(Long id, ToolContext ctx) {
        String tenantId = (String) ctx.getContext().get("tenantId");
        return customerRepo.findByIdAndTenant(id, tenantId);
    }
}

String answer = chatClient.prompt()
    .user("Tell me about customer #42")
    .tools(new CustomerTools())
    .toolContext(Map.of("tenantId", "acme-corp"))
    .call()
    .content();

顾问器 — AI调用的拦截器

顾问器在提示到达模型之前修改它们,并在响应返回时处理——就像Spring MVC拦截器用于AI。

聊天记忆 — 对话历史

ChatMemory memory = MessageWindowChatMemory.builder()
    .chatMemoryRepository(new InMemoryChatMemoryRepository())
    .maxMessages(20).build();

ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(MessageChatMemoryAdvisor.builder(memory).build())
    .build();

// First call
client.prompt().user("My name is Alice")
    .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "session-1"))
    .call().content();

// Second call — remembers the name
client.prompt().user("What is my name?")
    .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "session-1"))
    .call().content(); // "Your name is Alice"

自定义顾问器 — 延迟追踪

public class LatencyAdvisor implements CallAdvisor {
    public String getName() { return "LatencyAdvisor"; }
    public int getOrder() { return 0; }

    public ChatClientResponse adviseCall(ChatClientRequest request,
                                          CallAdvisorChain chain) {
        long start = System.currentTimeMillis();
        ChatClientResponse response = chain.nextCall(request);
        log.info("AI call took {}ms", System.currentTimeMillis() - start);
        return response;
    }
}

组合多个顾问器

ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(
        MessageChatMemoryAdvisor.builder(memory).build(),
        QuestionAnswerAdvisor.builder(vectorStore).build(),
        new SimpleLoggerAdvisor(),
        new LatencyAdvisor()
    ).build();

嵌入向量

将文本转换为数值向量,用于相似性搜索、聚类和RAG。

@Service
class EmbeddingService {
    private final EmbeddingModel embeddingModel;

    // Single text
    float[] embed(String text) {
        return embeddingModel.embed(text);
    }

    // Batch
    List<float[]> embedBatch(List<String> texts) {
        return embeddingModel.embed(texts);
    }

    // Vector dimensions
    int dimensions() { return embeddingModel.dimensions(); }
}

多模态 — 图像 + 文本

与GPT-4o、Claude 3或Gemini等模型一起发送图像和文本:

String description = chatClient.prompt()
    .user(u -> u
        .text("Describe what you see in this image.")
        .media(MimeTypeUtils.IMAGE_PNG,
               new ClassPathResource("/images/diagram.png")))
    .call()
    .content();

// From URL
String analysis = chatClient.prompt()
    .user(u -> u
        .text("What's in this image?")
        .media(MimeTypeUtils.IMAGE_JPEG,
               URI.create("https://example.com/photo.jpg")))
    .call()
    .content();

图像生成

@RestController
class ImageController {
    private final ImageModel imageModel;

    @GetMapping("/generate-image")
    String generateImage(@RequestParam String description) {
        ImageResponse response = imageModel.call(
            new ImagePrompt(description,
                OpenAiImageOptions.builder()
                    .quality("hd").N(1)
                    .height(1024).width(1024).build()));
        return response.getResult().getOutput().getUrl();
    }
}

评估 — 测试你的AI

Spring AI提供评估器来检查相关性和捕获幻觉:

// Is the response relevant to the question and context?
RelevancyEvaluator evaluator = new RelevancyEvaluator(ChatClient.builder(chatModel));
EvaluationResponse eval = evaluator.evaluate(
    new EvaluationRequest(question, context, aiResponse));
assertThat(eval.isPass()).isTrue();

// Fact-checking — detect hallucinations
FactCheckingEvaluator factChecker = new FactCheckingEvaluator(
    ChatClient.builder(chatModel));
EvaluationResponse result = factChecker.evaluate(
    new EvaluationRequest(knownFacts, Collections.emptyList(), claim));
assertFalse(result.isPass()); // claim contradicts known facts

提供商配置速查表

提供商Starter组件关键配置
OpenAIspring-ai-starter-model-openaispring.ai.openai.api-key
Anthropic Claudespring-ai-starter-model-anthropicspring.ai.anthropic.api-key
Ollama(本地)spring-ai-starter-model-ollamaspring.ai.ollama.base-url
AWS Bedrockspring-ai-starter-model-bedrockspring.ai.bedrock.aws.region
Azure OpenAIspring-ai-starter-model-azure-openaispring.ai.azure.openai.api-key
Google Geminispring-ai-starter-model-vertex-aispring.ai.vertex.ai.gemini.project-id
PGVectorspring-ai-starter-vector-store-pgvectorspring.ai.vectorstore.pgvector.*

核心要点

提供商无关:编写一次,通过配置切换AI提供商。没有供应商锁定。

Spring原生:自动配置、依赖注入、配置文件——Spring开发者所期望的一切。

生产就绪模式:RAG、工具调用、聊天记忆、顾问器、评估和结构化输出都已内置。

从简单开始:ChatClient.prompt().user("...").call().content()——这就是你的第一个AI调用。根据需要添加RAG、工具和记忆。

Spring AI让AI集成感觉就像任何其他Spring依赖一样——导入starter、配置、注入并使用。

More from Innovation