Spring AI ChatClient 사용법과 오류 해결
Spring AI에서 ChatClient를 org.springframework.ai.chat.client.ChatClient 못찾는 오류 해결법을 작성해본다.
기본 사용법부터 실전 예제, 그리고 그 유명한 클래스패스 오류까지 차근차근 풀어보자.
ChatClient 기본 개념과 생성
ChatClient는 Spring AI에서 AI 모델과 대화할 때 쓰는 fluent API다. ChatModel 위에 쌓여 있어서 Prompt를 메시지 단위로 쉽게 조립하고, 동기/스트리밍 둘 다 지원한다.
가장 간단한 생성법은 Spring Boot 자동 구성 쓰는 거. Controller에서 ChatClient.Builder를 주입받아서 build하면 된다.
@RestController
class MyController {
private final ChatClient chatClient;
public MyController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@GetMapping("/ai")
String generate(@RequestParam String message) {
return this.chatClient.prompt()
.user(message)
.call()
.content();
}
}
여기서 .prompt().user(message).call().content() 흐름이 핵심이다. user()로 사용자 메시지 넣고 call() 치면 AI 응답이 String으로 나온다.
시스템 메시지와 파라미터 넣기
실무에선 시스템 프롬프트 없으면 안 된다. 기본 시스템 메시지 설정하거나 동적으로 param 넣을 수 있다.
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem("너는 {voice}처럼 말하는 챗봇이야.")
.build();
}
Controller에서 쓰려면:
@GetMapping("/ai/chat")
Map<String, String> chat(@RequestParam String message, @RequestParam String voice) {
return Map.of("response",
chatClient.prompt()
.system(sp -> sp.param("voice", voice))
.user(message)
.call()
.content());
}
.system()에 람다로 param 주입하는 게 포인트. 공식 문서에 따르면 이렇게 하면 템플릿처럼 동작한다. voice가 "친근하게"면 그에 맞춰 응답 나온다.
스트리밍 응답 예제
Flux로 실시간 스트리밍 하려면 .stream() 쓰면 된다. 채팅 앱에서 딱이다.
@GetMapping("/stream")
Flux<String> stream(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.stream()
.content();
}
이거 연결해서 WebFlux 쓰면 토큰 단위로 응답이 쭉 흘러간다. 배포해보니 OpenAI 모델에서 특히 빠르게 느껴지더라.
"ChatClient that could not be found" 오류 해결
이 오류는 대체로 의존성 누락이나 버전 충돌, 자동 구성 실패 때 뜬다. 나도 OpenAI starter 추가했는데 뜨길래 pom.xml부터 확인했다.
1. 의존성 확인
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M1</version> <!-- 최신 버전으로 -->
</dependency>
spring-ai-openai만 넣으면 ChatClient 패키지가 안 잡힌다. starter 버전 써야 자동 구성 된다.^1_4
2. Builder 주입 문제
Controller에서 private ChatClient.Builder chatClientBuilder; 필드 선언하고 @Autowired 안 하면 NullPointer. 생성자 주입으로:
public MyController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
직접 필드에 넣지 말고 생성자에서 build.
3. 여러 ChatModel 있을 때
OpenAI + Ollama 두 개 쓰면 "No qualifying bean of type 'ChatModel'" 뜬다. 각 모델별 ChatClient 빈 따로 만들어:
@Bean
@Qualifier("openai")
ChatClient openAiClient(ChatClient.Builder builder) {
return ChatClient.builder(openAiChatModel).build();
}
@Bean
@Qualifier("ollama")
ChatClient ollamaClient(OllamaChatModel ollamaModel) {
return ChatClient.create(ollamaModel);
}
그리고 Controller에서 @Qualifier로 지정. 이게 제일 깔끔하다.^1_2
4. Milestone 리포지토리 추가
<repositories>
<repository>
<id>spring-milestones</id>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
M1, RC1 같은 버전은 여기서 끌어온다.
놓치기 쉬운 팁
- ChatClient는 프로토타입 빈이라 매번 새로 build 가능. 싱글톤으로 고정 안 해도 된다.
- .advisor()로 메모리나 RAG 추가 쉬움. 예:
.advisor(new ChatMemoryAdvisor(chatMemory)) - application.yml에
spring.ai.openai.api-key설정 잊으면 런타임에서 터짐.
이렇게 해보니 안정적으로 돌아가더라. 공식 문서 예제 따라 하다 막히는 부분 위주로 정리해봤다. 실제 프로젝트에 넣어보고 싶으면 OpenAI 키부터 세팅해보시라.