
최근 오랜만에 Spring AI를 사용해 작업을 하면서, 이전과는 다르게 빌드 단계부터 다양한 시행착오를 겪게 되었다. 특히 프록시 서버를 통한 연동이 필요해 예상보다 많은 어려움이 있었고, 이를 정리해 두려 한다.
Spring AI를 활용해 ChatModel을 각각의 모델에 맞는 클래스로 구성하고 애플리케이션에 통합하려는 작업을 진행했다.
빠르게 변화하는 모델 분야만큼이나, Spring AI는 비교적 신생 프로젝트이며 업데이트 주기가 매우 빠른 편이다. 그로 인해 버전 간 API 변경, AutoConfiguration 구조 변경, 아티팩트 분리 등이 빈번하게 발생하고 있으며,
나 역시 초기 설정 단계에서 여러 가지 문제를 경험하게 되었다.
문제상황1. Spring AI 버전 불일치로 인한 의존성 로딩 실패
Spring AI는 정식 릴리즈 이외에도 Milestone(M), Snapshot 버전이 활발히 배포되고 있다.
또한 버전 별로 인터페이스의 패키지 경로 변경, 클래스명 변경, Spring Starter 아티팩트 분리 또는 재구성, 내부 모듈 간의 의존성 버전 불일치 등의 문제들이 존재한다.
이로 인해 Class를 찾을 수 없다는 문제가 잦게 일어났는데,
나의 상황 같은 경우에는 특정 버전에서의 Gradle을 빌드 하였는데 ClassNotFoundException 에러가 뜨면서 AutoConfiguration exclude 설정이 더이상 동작하지 않았다. 특정 모델사의 starter(google-genai, open-ai, ...)를 추가했음에도 Bean이 등록되지 않았다.
이는 명시한 Spring AI 버전과 실제 참조되는 내부 모듈 버전이 서로 일치하지 않았기 때문 이었다.
Spring AI는 반드시 다음을 전제로 설정해야 한다.
- Spring Boot 버전과 Spring AI 버전의 호환성 매트릭스 확인
- 개별 의존성 버전 지정이 아닌 BOM 기반 버전 관리
- Milestone / Snapshot 사용 시 Repository 명시 필수
해결 방법. Spring AI BOM 기반 의존성 관리
나의 경우에는 위에서 서술했듯이 단순히 Starter 의존성만 추가했다.
사용한 모델은 GoogleGen AI였는데, 다음 import를 추가했다.
implementation 'org.springframework.ai:spring-ai-starter-model-google-genai'
위와 같이 간단한 gradle을 추가하면 되는 줄 알았는데
그러나 이 방식은 내부 모듈 버전을 통제하지 못하기 때문에,
다른 Spring AI 모듈과의 버전 불일치 문제가 발생하였다.
이를 해결하기 위해 Spring AI BOM을 명시적으로 적용하였다.
dependencyManagement {
imports {
mavenBom "org.springframework.ai:spring-ai-bom:1.1.1"
}
}
또한 Spring AI는 일부 버전이 Spring 공식 저장소에만 배포되므로,
다음과 같은 Repository 설정이 필요하다.
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
}
이는 Gradle이 의존성을 조회할 수 있는 저장소 범위를 확장하기 위함이며,
Milestone 또는 Snapshot 버전을 사용할 경우 필수적인 설정이다.
문제상황2. Spring 생명주기 문제로 인한 Property 주입 실패
application.yaml에 API Key 및 모델 정보를 정상적으로 설정했음에도 불구하고,
@Configuration 클래스에서 해당 값이 null로 주입되는 문제가 발생했다.
로그 상으로는 다음과 같은 경고가 확인되었다.
Cannot enhance @Configuration bean definition since its singleton instance has been created too early
디버깅 결과 알아낸 원인은 다음과 같다:
- @Value 또는 필드 주입 방식을 사용했는데, 이 설정 값이 Bean 초기화 타이밍보다 늦게 로딩되었다.
- 반면, Spring AI의 ChatModel 설정을 포함한 Configuration 클래스는 더 빠르게 생성됨! Property Binding 이전 단계에서 초기화됨 ( 스프링 AI 관련 설정 Bean이 ApplicationContext 초기 단계에서 생성됨 )
- 그 결과 Property 값들이 정상적으로 Binding 되기 전에 Configuration Bean이 초기화되어 NPE 발생
이 문제는 전형적인 Spring Boot Property Binding 시점 불일치 문제였다.
해결 방법: 생성자 주입 + @ConfigurationProperties 사용
그래서 @Value 기반 필드로 주입을 제거하고, 설정 클래스를 @ConfigurationProperties로 분리햇다.
Bean 생성 시점에 생성자 주입 방식으로 설정 객체를 전달했다.
@Configuration
@EnableConfigurationProperties(GoogleGenAiProperties.class)
public class GoogleGenAiConfig {
@Bean
public GoogleGenAiChatModel googleGenAiChatModel(GoogleGenAiProperties props) {
// props.getApiKey(), props.getModel() 등을 안정적으로 사용 가능
return new GoogleGenAiChatModel(props.getApiKey(), props.getModel());
}
}
이 방식은 다음과 같은 장점이 있다:
- Spring Boot의 Property Binding이 보장된 이후에 객체가 주입됨! Bean 주입 제대로 보장
- 설정 값 누락을 컴파일 시점에 더 명확히 파악 가능
- 테스트 및 외부 설정 관리 측면에서 일관된 구조 확보
- 또한 Spring 권장 설정 패턴에 부합함
문제상황3. 자동 설정(Auto Configuration) 충돌
이제 정말 해결하나 보다 싶었으나, 다음과 같은 새로운 문제가 일어났다.
원인은 Spring AI의 AutoConfiguration이 기본 ChatModel Bean을 자동으로 생성하면서,
직접 정의한 ChatModel Bean과 충돌이 발생한 것이었다.
Spring Boot는 조건이 충족되면 AutoConfiguration을 통해 Bean을 자동 등록하는데,
이 경우 동일한 역할의 Bean이 중복 생성되며 초기화 충돌이 발생하게 된다.
해결 방법. 충돌하는 AutoConfiguration 명시적 제외
@SpringBootApplication(
exclude = {
GoogleGenAiChatAutoConfiguration.class // 충돌하는 자동 설정 제외
}
)
public class BackendApplication {
public static void main(String[] args) {
SpringApplication.run(BackendApplication.class, args);
}
}
이를 통해 직접 구성한 ChatModel 설정만 정상적으로 Bean으로 적용되고,
Spring AI의 기본 설정이 중복 로딩되며 생성되던 Bean 충돌을 제거할 수 있었다.
애플리케이션 초기화 과정 안정화한 것이다.
Spring AI는 빠르게 성장하는 라이브러리인 만큼, 다음 사항을 반드시 고려해야 한다.
- 버전 호환성 확인 → Spring Boot 버전과 Spring AI 버전의 매트릭스 확인 + BOM 기반 의존성 관리함으로써 한번에 버전 관리
- Property Binding 시점 관리 → @ConfigurationProperties 기반의 생성자 주입 우선하고, @Value 필드 주입은 지양하자
- AutoConfiguration 충돌 방지 → 필요 시 자동 설정 명시적 exclude
- ChatModel 변화 모니터링 → minor 버전에서도 인터페이스 변경이 잦음
오랜만에 적용하는 Spring AI에서 생각보다 많은 시행착오를 겪어서 다시 정리해 작성해보았다
'백엔드 > SpringBoot' 카테고리의 다른 글
| [SpringBoot] 통합 테스트(Integeration Test) 알아보기 (2) | 2025.11.10 |
|---|---|
| [SpringBoot] Spring MVC와 Spring WebFlux를 요청 처리 방식 비교해보기 (0) | 2025.10.05 |
| [SpringBoot] 스프링에서의 Service 와 Bean의 차이점 (1) | 2025.10.04 |
| [Spring Boot] Thread 설정 (Spring Boot) (1) | 2025.09.27 |
| [SpringBoot] SpringBoot 트랜잭션과 이벤트 발행에서의 전파 관련 트러블 슈팅 (0) | 2025.09.15 |