SuperAPI AI Directory

spring-ai-integration

Coding & development

View repository

Updated 10-16 02:10

About this skill

Spring AI Integration

Dependencies

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>2.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<!-- For RAG / vector search -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
</dependency>
<!-- QuestionAnswerAdvisor lives here — 2.0 renamed spring-ai-advisors-vector-store -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-vector-store-advisor</artifactId>
</dependency>

Version pairing matters. Spring Boot 4 requires Spring AI 2.0 (spring-ai-bom 2.0.0); the 1.x line targets Boot 3 only. Starter coordinates follow spring-ai-starter-model-<provider> (e.g. -model-anthropic, -model-openai) and spring-ai-starter-vector-store-<store>. Agents trained on pre-1.0 Spring AI emit spring-ai-<x>-spring-boot-starter — those names resolve to nothing in Maven Central. Also gone in 2.0: spring-ai-starter-model-azure-openai (use the OpenAI starter with an Azure base URL instead).

ChatClient — Basic Usage

@Service
@RequiredArgsConstructor
public class DocumentSummaryService {

    private final ChatClient chatClient;

    public String summarize(String conversationId, String content) {
        return chatClient.prompt()
            .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
            .user(u -> u.text("Summarize the following document in 3 bullet points:\n\n{content}")
                .param("content", content))
            .call()
            .content();
    }

    // With system prompt
    public String analyzeFinancial(String conversationId, String document, String language) {
        return chatClient.prompt()
            .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
            .system("You are a financial analyst. Respond in {language}.")
            .system(s -> s.param("language", language))
            .user(document)
            .call()
            .content();
    }
}

Every call using the configured memory advisor must provide a user- or session-scoped ChatMemory.CONVERSATION_ID. Never use one shared conversation ID for all users.

ChatClient Bean Configuration

@Configuration
public class AiConfig {

    @Bean
    public ChatMemory chatMemory() {
        // InMemoryChatMemory is long gone. Use MessageWindowChatMemory —
        // it caps history to a sliding window and defaults to an in-memory repository.
        return MessageWindowChatMemory.builder()
            .maxMessages(20)
            .build();
    }

    @Bean
    public ChatClient chatClient(ChatClient.Builder builder, ChatMemory chatMemory) {
        return builder
            .defaultSystem("You are a helpful assistant for an e-commerce platform.")
            .defaultAdvisors(
                MessageChatMemoryAdvisor.builder(chatMemory).build(), // builder, not new(...)
                new SimpleLoggerAdvisor() // logs prompts/responses
            )
            .build();
    }
}
// 2.0: the conversation id is REQUIRED on every call that goes through a memory advisor.
// ChatMemory.DEFAULT_CONVERSATION_ID is removed — omitting the param throws IllegalArgumentException.
public String chat(String sessionId, String message) {
    return chatClient.prompt()
        .user(message)
        .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, sessionId))
        .call()
        .content();
}

Prompt Templates (externalized)

// src/main/resources/prompts/analyze-order.st
// Analyze this order and identify any anomalies:
// Customer: {customer}
// Items: {items}
// Total: {total}
// Flag any unusual patterns.

@Service
public class OrderAnalysisService {

    @Value("classpath:prompts/analyze-order.st")
    private Resource promptTemplate;

    public String analyzeOrder(String conversationId, Order order) {
        return chatClient.prompt()
            .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
            .user(u -> u.text(promptTemplate)
                .param("customer", order.getCustomerEmail())
                .param("items", order.getItems().toString())
                .param("total", order.getTotal()))
            .call()
            .content();
    }
}

Structured Output

// Define the target record
public record OrderClassification(
    String category,
    String priority,
    List<String> tags,
    boolean requiresManualReview
) {}

@Service
public class OrderClassifier {

    public OrderClassification classify(String orderDescription) {
        return chatClient.prompt()
            .user("Classify this order: " + orderDescription)
            .call()
            .entity(OrderClassification.class); // Spring AI handles JSON parsing
    }
}

RAG Pipeline

@Configuration
public class RagConfig {

    // No manual VectorStore bean — the spring-ai-starter-vector-store-pgvector
    // starter auto-configures one. Just inject it. (The old `new PgVectorStore(...)`
    // constructor is removed; if you must build one, use PgVectorStore.builder(...).)

    @Bean
    public ChatClient ragChatClient(ChatClient.Builder builder, VectorStore vectorStore) {
        return builder
            .defaultAdvisors(
                QuestionAnswerAdvisor.builder(vectorStore)
                    .searchRequest(SearchRequest.builder().topK(5).build()) // builder, not defaults().withTopK()
                    .build()
            )
            .build();
    }
}

@Service
@RequiredArgsConstructor
public class KnowledgeService {

    private final VectorStore vectorStore;
    private final ChatClient ragChatClient;

    // Ingest documents
    public void ingest(List<String> documents) {
        List<Document> docs = documents.stream()
            .map(content -> new Document(content))
            .toList();
        vectorStore.add(docs);
    }

    // Query with RAG
    public String ask(String question) {
        return ragChatClient.prompt()
            .user(question)
            .call()
            .content();
    }
}

Streaming Responses

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

application.yml

spring:
  ai:
    anthropic:
      api-key: ${ANTHROPIC_API_KEY}
      chat:
        # 2.0 flattened the properties — the old chat.options.* nesting is dead
        model: ${ANTHROPIC_MODEL}
        max-tokens: 2048
        temperature: 0.7   # 2.0 removed the 0.7 default — set it explicitly if you rely on it
    # OR for OpenAI:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        model: ${OPENAI_MODEL}
    vectorstore:
      pgvector:
        initialize-schema: true
        dimensions: 1536

Gotchas

  • Agent uses Spring AI 1.x (spring-ai-bom 1.0.x) on Spring Boot 4 — 1.x targets Boot 3 only; Boot 4 requires Spring AI 2.0
  • Agent uses pre-1.0 artifact names (spring-ai-anthropic-spring-boot-starter) — the pattern is spring-ai-starter-model-anthropic
  • Agent configures spring.ai.anthropic.chat.options.model — 2.0 flattened properties; drop the .options segment (spring.ai.anthropic.chat.model)
  • Agent passes built options to .options(...) — 2.0 takes the builder: .options(AnthropicChatOptions.builder().maxTokens(2048)), no .build()
  • Agent writes new MessageChatMemoryAdvisor(new InMemoryChatMemory()) — both long removed; use MessageChatMemoryAdvisor.builder(chatMemory) + MessageWindowChatMemory
  • Agent omits the conversation id on a memory-advisor call — mandatory in 2.0 (ChatMemory.DEFAULT_CONVERSATION_ID removed); pass a.param(ChatMemory.CONVERSATION_ID, ...) or get IllegalArgumentException
  • Agent uses PromptChatMemoryAdvisor — removed in 2.0; use MessageChatMemoryAdvisor
  • Agent adds spring-ai-advisors-vector-store for QuestionAnswerAdvisor — renamed to spring-ai-vector-store-advisor in 2.0
  • Agent writes SearchRequest.defaults().withTopK(n) — use SearchRequest.builder().topK(n).build()
  • Agent hardcodes API keys — always use environment variables / ${...}
  • Agent hardcodes provider model IDs - configure them externally because model catalogs change
  • Agent builds prompts with string concatenation — use .param() template variables
  • Agent puts prompts inline in code — externalize to src/main/resources/prompts/
  • Agent ignores structured output — use .entity(MyClass.class) instead of parsing manually
  • Agent uses .entity(List.class) for a list — generics erase; pass new ParameterizedTypeReference<List<X>>() {}
  • Agent skips error handling for API calls — wrap in try/catch, handle NonTransientAiException (don't retry) vs TransientAiException (retry)
  • Agent uses wrong model string — verify model names against provider docs