ChatClient는 AI 모델과의 통신을 위한 객체로fluent API를 제공하며 동기식 또는 스트리밍 방식을 모두 지원한다. 이 블로그에서는 동기식만 처리한다.
ChatClient 생성 1 - 자동 환경 설정으로 구성되는 ChatClient.Builder 활용
ChatClient를 구성하는 가장 간단한 방법은 자동 환경 설정으로 구성된 빈인 ChatClient.Builder를 사용하는 방법이다. 이 방식은 단일 모델을 이용하는 간단한 시나리오에서 사용하기 적합하다.
@RestController
class MyController {
private final ChatClient chatClient;
public MyController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@GetMapping("/ai")
String generation(String userInput) {
return this.chatClient.prompt() // fluent api: method chaining 방식
.user(userInput) // user message 구성
.call() // 모델 호출
.content(); // 결과 반환
}
}
위 방법은 기본적으로 Spring AI의 자동 환경 구성에 의해 하나의 ChatClient.Builder가 빈으로 제공될 때 유효하다. 따라서 다음 처럼 필요에 따라 여러개의 모델을 애플리케이션에서 사용해야 하는 경우는 다른 방법으로 접근해야 한다.
다양한 유형의 작업에 서로 다른 모델을 사용하는 경우(복잡한 추론 - 비싼 모델, 간단한 작업 - 저렴한 모델)
하나의 모델 서비스를 사용할 수 없을 때 fallback 메커니즘 구현
다양한 모델/구성에 대한 테스트
사용자에게 선호도에 따라 모델 선택권 제공
전문화된 모델의 결합(코드 생성, 이미지 생성, 동영상 생성)
ChatClient 생성 2 - 여러 개의 모델 활용
여러 개의 모델을 사용하는 경우는 ChatClient.Builder를 사용하지 않고 model을 별도로 구성하고 직접 ChatClient를 구성한다.
// AiConfig.java
@Value("${quietjun.ai.system-prompt}")
private String systemPrompt;
@Value("${spring.ai.ollama.base-url}")
String baseUrl;
private ChatClient getOllamaChatClient(String name) {
// baseUrl 설정을 통해 Ollama 서비스와 연결
OllamaApi api = OllamaApi.builder().baseUrl(baseUrl).build();
// OllamaApi와 연결된 모델 생성
ChatModel model = OllamaChatModel.builder().ollamaApi(api)
.defaultOptions(OllamaOptions.builder().model(name).build()).build();
// 모델을 통해서 생성되는 ChatClient에 기본 속성 적용
return ChatClient.builder(model)
.defaultOptions(OllamaOptions.builder().temperature(0.2).build())
.defaultSystem(systemPrompt)
.defaultSystem(c -> c.param("language", "korean").param("character", "chill"))
.defaultAdvisors(new SimpleLoggerAdvisor()).build();
}
@Bean
ChatClient ollamaGemma3ChatClient() {
return getOllamaChatClient("gemma3:4b-it-qat");
}
@Bean
ChatClient ollamaQwenChatClient() {
return getOllamaChatClient("qwen3:8b");
}
@Bean // 미리 만들어진 ChatModel을 사용하는 경우
ChatClient openAiChatClientDefault(OpenAiChatModel model) {
return ChatClient.builder(model).defaultSystem(systemPrompt).build();
}
@Bean
ChatClient openAiChatClient() {
// api key를 이용해 OpenAi 서버와 연결
OpenAiApi api = OpenAiApi.builder().apiKey("API_KEY").build();
// 필요 시 옵션 재정의
OpenAiChatOptions options = OpenAiChatOptions.builder().model("gpt-4o").build();
OpenAiChatModel model = OpenAiChatModel.builder().openAiApi(api).defaultOptions(options).build();
return ChatClient.builder(model).defaultSystem(systemPrompt).build();
}
ChatClient Prompt 구성
Prompt
Spring AI에서 모델에게 질문 하기 위해서 Prompt라는 것을 사용한다. Prompt는 AI 모델이 특정 출력을 생성하도록 안내하는 입력으로 프롬프트의 디자인과 문구는 모델의 응답에 큰 영향을 미친다.
Spring AI에서 프롬프트를 처리하는 것은 마치 Spring MVC에서 'View'를 관리하는 것과 유사하다고 볼 수 있다. view를 우리가 template이라고 부르는 것 처럼 prompt는 질문의 틀이 되며 placeholder를 이용해서 런타임에 사용자의 입력 값으로 대체된다.
# application.properties
# system prompt
quietjun.ai.system-prompt=You are an artificial intelligence known as an omniscient scholar.
When you speak, use {language} and answer with a {character} personality.
Message API
Prompt는 Message의 목록으로 구성된다.
public class Prompt implements ModelRequest<List<Message>> {
private final List<Message> messages;
private ChatOptions chatOptions;
}
Message의 구조는 다음과 같다.
Spring AI Message API: 출처: spring.io
Message는 Role(Message Type)을 기준으로 4가지로 나뉜다.
메시지
설명
SystemMessage (System Role)
- 대화를 시작하기 전에 AI에게 지침을 제공하는 역할로 AI의 행동과 응답 스타일 안내 - AI가 입력을 해석하고 응답하는 방식에 대한 매개변수 또는 규칙을 설정 예) 친절하게 단계별로 질문에 답변하고 표 형식으로 결과를 반환해줘.
UserMessage (User Role)
- AI 모델에게 사용자가 제공하는 질문으로 모델이 특정 작업을 수행하거나 응답을 생성하기 위해 필요한 정보 예) 오늘 날씨 어때? 한강 작가의 도서 목록을 알려줘.
AssistantMessage (Assistant Role)
- 사용자의 입력에 대한 AI의 응답 - 이 응답을 다시 다음 질문에 사용함으로 써 시스템은 일관성 있고 맥락에 맞는 상호 작용 보장 가능 - 모델이 사용자 시스템과 소통하기 위한 Tool 호출 요청 정보도 포함