ChatClient, İstemler ve Yapılandırılmış Çıktı
İstem şablonları ve türü belirli yapılandırılmış çıktı eşlemesiyle sohbet modellerini ChatClient üzerinden çağırın.
ChatClient, İstemler ve Yapılandırılmış Çıktı, CoddyKit'te ücretsiz bir Spring Boot 4 Complete Guide dersidir. Bu, 4 dersinin 1. dersidir. Aşağıdan dersin tamamını ücretsiz okuyabilir, sonra tarayıcıda yerleşik kod editörü ve 7/24 yapay zeka koçu ile uygulamalı olarak pratik yapabilirsin. Bu, Spring Boot 4 Complete Guide öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. Spring Boot 4 Complete Guide kursu toplamda 4 dersten oluşur.
Bu dersin bazı bölümleri henüz çevrilmemiş olup İngilizce olarak gösterilmektedir.
Why ChatClient?
Spring AI gives you a fluent, high-level API for talking to LLMs: the ChatClient. Instead of hand-building HTTP requests to OpenAI, Anthropic, or Ollama, you describe what you want and let the framework handle transport, retries, and message assembly.
- ChatClient — fluent builder for one-shot or streaming calls.
- Prompt — a list of messages (system, user, assistant) plus options.
- Structured output — map the model's text reply directly into a typed Java object.
It is portable: swap the underlying ChatModel (OpenAI → Anthropic) and your ChatClient code stays the same.
Auto-configuration and dependencies
Add a Spring AI model starter and Spring Boot auto-configures a ChatModel bean plus a ChatClient.Builder you can inject.
- The starter (e.g.
spring-ai-starter-model-openai) reads your API key and model from properties. - You never construct
ChatClientdirectly — you inject the builder and call.build().
Typical configuration in application.yml:
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4o
temperature: 0.2Creating a ChatClient
Inject the auto-configured ChatClient.Builder and build a client once, usually in your service constructor. You can attach default system prompts or options here so every call inherits them.
Building per-service (not per-request) keeps configuration in one place and is cheap.
@Service
public class AssistantService {
private final ChatClient chatClient;
public AssistantService(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("You are a concise Spring expert. Answer in one sentence.")
.build();
}
}A first call: prompt().user().content()
The fluent chain reads like a sentence. Start with prompt(), add a user(...) message, then terminate with call() for a blocking response and content() to get the plain text.
call()— synchronous request/response.content()— extracts the assistant's text.chatResponse()— returns metadata (token usage, finish reason) instead.
public String ask(String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}System vs user messages
An LLM prompt is a sequence of messages with roles:
- System — instructions and persona; sets behavior.
- User — the actual request from the end user.
- Assistant — prior model replies (for multi-turn context).
You can override the default system message per call. Keep untrusted user input in user(...), never inside the system instruction, to reduce prompt-injection risk.
String answer = chatClient.prompt()
.system("You are a senior Java reviewer. Be blunt and specific.")
.user("Review this code: " + snippet)
.call()
.content();Prompt templates with variables
Hard-coding strings does not scale. Spring AI uses template placeholders (default {name} syntax via StringTemplate) that you fill with param(...). The framework substitutes values before the request is sent.
- Define the template text once with
{placeholders}. - Bind values with
.user(u -> u.text(...).param(...)).
This separates wording from data and keeps user values clearly bound.
String reply = chatClient.prompt()
.user(u -> u
.text("Summarize the topic {topic} for a {level} audience.")
.param("topic", "reactive streams")
.param("level", "beginner"))
.call()
.content();How template substitution works
Under the hood Spring AI builds a PromptTemplate and renders it. You can also use the template directly when you want to reuse it or load it from a resource file.
Here is the same idea as a plain Java program you can run to see substitution — no Spring needed, just string formatting that mirrors what the renderer does:
import java.util.Map;
public class TemplateDemo {
static String render(String tmpl, Map<String, String> vars) {
String out = tmpl;
for (var e : vars.entrySet()) {
out = out.replace("{" + e.getKey() + "}", e.getValue());
}
return out;
}
public static void main(String[] args) {
String t = "Summarize {topic} for a {level} audience.";
System.out.println(render(t, Map.of("topic", "reactive streams", "level", "beginner")));
}
}Structured output: entity()
Often you do not want prose — you want a typed object. Spring AI's structured output converters do three things: inject a format instruction into the prompt, receive the model's text, and deserialize it into your type.
Define a plain Java record, then call .entity(MyType.class) instead of .content().
public record MovieReview(String title, int year, double rating, String verdict) {}
public MovieReview review(String movie) {
return chatClient.prompt()
.user("Give a short structured review of the movie: " + movie)
.call()
.entity(MovieReview.class);
}Generic types with ParameterizedTypeReference
For collections or generic containers, Java erases the type parameter at runtime, so List.class is not enough. Pass a ParameterizedTypeReference so Spring AI knows the element type and generates the right JSON schema instruction.
import org.springframework.core.ParameterizedTypeReference;
import java.util.List;
public record Actor(String name, List<String> films) {}
public List<Actor> castOf(String movie) {
return chatClient.prompt()
.user("List the main cast of " + movie)
.call()
.entity(new ParameterizedTypeReference<List<Actor>>() {});
}What entity() does to the prompt
It is worth understanding the mechanism: entity() uses a BeanOutputConverter that generates a JSON Schema from your record and appends a format instruction telling the model to reply with matching JSON only.
- The model returns JSON text.
- The converter parses it (via Jackson) into your record.
- If the model adds stray prose, parsing can fail — lower
temperatureand keep records flat for reliability.
You can call the converter yourself to inspect the injected instruction:
import org.springframework.ai.converter.BeanOutputConverter;
record Weather(String city, double celsius) {}
var converter = new BeanOutputConverter<>(Weather.class);
String formatInstruction = converter.getFormat();
// This text is appended to your user prompt by entity()
System.out.println(formatInstruction);Streaming and response metadata
For long answers, stream tokens as they arrive using stream() instead of call(), which returns a reactive Flux<String>. For observability, grab the full ChatResponse to read token usage and the finish reason.
.stream().content()—Flux<String>of incremental chunks..call().chatResponse().getMetadata().getUsage()— prompt/completion tokens.
import reactor.core.publisher.Flux;
public Flux<String> streamAnswer(String question) {
return chatClient.prompt()
.user(question)
.stream()
.content();
}Quick Check
You need the model's reply mapped into a List<Actor>. Which terminal call is correct?
Recap
You learned to call LLMs the Spring way with ChatClient:
- Inject
ChatClient.Builder, setdefaultSystem, andbuild()once per service. - Use the fluent chain:
prompt().system(...).user(...).call().content(). - Keep system instructions and untrusted user input in separate messages.
- Use
{placeholder}templates with.param(...)to separate wording from data. - Map replies to typed records with
.entity(Type.class), and useParameterizedTypeReferencefor generics likeList<T>. - Stream with
.stream().content()and inspect token usage viachatResponse().getMetadata().
For reliable structured output, keep records flat and lower the temperature.
Sıkça Sorulan Sorular
“ChatClient, İstemler ve Yapılandırılmış Çıktı” dersi ücretsiz mi?
Evet — “ChatClient, İstemler ve Yapılandırılmış Çıktı” dersin tüm metni burada web'de ücretsiz olarak okunabilir. Etkileşimli olarak pratik yapmak (yerleşik kod editörü ve 7/24 yapay zeka koçu) ve Spring Boot 4 Complete Guide kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. Spring Boot 4 Complete Guide kursu toplamda 4 dersten oluşur.
“ChatClient, İstemler ve Yapılandırılmış Çıktı” dersinde ne öğreneceğim?
İstem şablonları ve türü belirli yapılandırılmış çıktı eşlemesiyle sohbet modellerini ChatClient üzerinden çağırın. Spring Boot 4 Complete Guide ile uygulamalı kodu tarayıcıda doğrudan çalıştırarak pratik yaparsın ve 7/24 yapay zeka koçu dersi çalışırken sorularını yanıtlar.
Spring Boot 4 Complete Guide öğrenmeye başlamak için deneyim gerekli mi?
Önceden deneyim gerekmez. CoddyKit'te Spring Boot 4 Complete Guide, başlangıçtan ileri seviyeye kadar yapılandırıldığı için buradan başlayabilir veya başından başlayıp kendi hızında ilerleme yapabilirsin. Bu, 4 dersinin 1. dersidir.
“ChatClient, İstemler ve Yapılandırılmış Çıktı” dersi ne kadar sürer?
Çoğu CoddyKit dersi yaklaşık 5–10 dakika sürer. Her biri kısa ve etkileşimli olduğu için sabit ilerleme yaparsın ve web ile uygulama arasında tam olarak bıraktığın yerden devam edebilirsin.
Bu Spring Boot 4 Complete Guide dersinde kod yazıp çalıştırabilir miyim?
Evet. Her Spring Boot 4 Complete Guide dersi yerleşik bir kod editörü içerir, bu sayede tarayıcıda gerçek kod yazıp çalıştırabilir ve anlık yapay zeka geri bildirimi alırsın — yerel kurulum gerekli değildir.
Bu kursun tüm dersleri
- ChatClient, İstemler ve Yapılandırılmış Çıktı
- Gömüler ve Vektör Deposu Sorgulama
- Getirmeyle Zenginleştirilmiş Üretim İş Akışları
- Araç Çağırma ve Aracı Danışmanları