Add getting-started module: ChatClient fluent API, system/user prompts, streaming, and provider switching via spring.ai.model.chat

This commit is contained in:
2026-09-23 04:20:52 +00:00
committed by Claude
parent 1d4625a1c2
commit c1fc41e0de
22 changed files with 697 additions and 0 deletions
@@ -0,0 +1,12 @@
package com.ankurm.gettingstarted;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GettingStartedApplication {
public static void main(String[] args) {
SpringApplication.run(GettingStartedApplication.class, args);
}
}
@@ -0,0 +1,24 @@
package com.ankurm.gettingstarted.config;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* The one bean this module exists to explain. {@link ChatClient.Builder} arrives
* autoconfigured and already pointed at whichever {@code ChatModel} bean
* {@code spring.ai.model.chat} selected — this class never mentions OpenAI or Ollama
* by name, and that is the whole point of the properties-based switch.
*/
@Configuration
public class ChatClientConfig {
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem("""
You are a terse Java and Spring assistant. Answer in at most two \
sentences unless the user asks for code.""")
.build();
}
}
@@ -0,0 +1,51 @@
package com.ankurm.gettingstarted.web;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
/**
* Three endpoints, three corners of the {@link ChatClient} fluent API:
* a plain call, a call with a templated system prompt, and a streamed call.
* See {@code docs} on ankurm.com for the walkthrough — the endpoints
* themselves carry no comment beyond what the method name says, deliberately,
* since the interesting part is the one-line body.
*/
@RestController
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/api/chat")
public String chat(@RequestParam String message) {
return this.chatClient.prompt()
.user(message)
.call()
.content();
}
@GetMapping("/api/chat/as")
public String chatAs(@RequestParam String voice, @RequestParam String message) {
return this.chatClient.prompt()
.system(s -> s.text("Answer in the voice of a {voice}, still in two sentences.")
.param("voice", voice))
.user(message)
.call()
.content();
}
@GetMapping(value = "/api/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chatStream(@RequestParam String message) {
return this.chatClient.prompt()
.user(message)
.stream()
.content();
}
}
@@ -0,0 +1,18 @@
spring:
application:
name: getting-started
ai:
# Which provider's ChatModel bean gets built. Flip this one line, keep the code
# in ChatClientConfig and ChatController unchanged, and add the matching provider
# properties below.
model:
chat: ${CHAT_PROVIDER:openai}
openai:
api-key: ${OPENAI_API_KEY:}
chat:
model: gpt-4o
temperature: 0.1
ollama:
base-url: ${OLLAMA_BASE_URL:http://localhost:11434}
chat:
model: llama3.2