
Spring AI는 Java/Spring 생태계에서 AI 애플리케이션을 구축하기 위한 프레임워크다. LLM 호출, 문서 임베딩, 벡터 검색, 대화 이력 관리 등 AI 앱에 필요한 기능들을 Spring 스타일의 추상화로 제공된다.
기존 Spring 생태계와의 완벽한 조화: 익숙한 의존성 주입(DI), 설정 파일(application.properties), 스프링 시큐리티 등 기존 스프링 기술을 그대로 활용할 수 있다.
모델 독립성 (Portability): OpenAI, Anthropic(Claude), Ollama, Google Vertex AI 등 다양한 LLM(거대 언어 모델)을 코어 비즈니스 로직 수정 없이 설정만 바꿔가며 유연하게 교체할 수 있다.
보조 기능 표준화: RAG(검색 증강 생성) 구현을 위한 임베딩(Embedding), 벡터 저장소(Vector Store) 연동, 프롬프트 템플릿 등 복잡한 AI 기능을 일관된 코드로 쉽게 구현할 수 있다.
Spring AI는 Spring Boot 애플리케이션에 생성형 AI 기능을 연동하여 다양한 AI 서비스를 개발하는 데 사용된다.
RAG, 임베딩, Vector DB, Tool Calling 등을 활용해 AI가 외부 데이터나 애플리케이션의 기능을 활용할 수 있다.
이를 통해 단순한 챗봇뿐만 아니라 문서 검색 및 요약, 데이터 분석, 추천 시스템, AI 기반 업무 자동화 등의 기능을 구현할 수 있다.
특히 기존 Spring Boot의 REST API, 데이터베이스 등의 백엔드 기능과 AI를 결합하여 실제 서비스에 AI 기능을 적용하기에 적합하다.

프로젝트 생성 시 OpenAI를 라이브러리를 추가해준다.
application=properties에 해당 설정을 해준다.
spring.application.name=Spring-AI
// OpenAI API 키 설정
spring.ai.openai.api-key=sk-여기에_실제_본인_API키를_입력하세요
// 사용할 모델 지정 (기본값으로 두거나 지정 가능)
spring.ai.openai.chat.options.model=gpt-4o-mini
OpenAI API key는 이곳에서 발급받으면 된다.
🍃Spring AI
https://platform.openai.com/api-keys
그리고 Build해주면 세팅 완료다!

Spring AI를 익힐 겸 자주 사용할 수 있는 간단한 챗봇을 만들어보기로 했다.
처음에는 가장 익숙한 OpenAI API를 사용해서 Spring AI와 LLM을 연결해보기로 했다.
Spring AI를 사용하기 위해 OpenAI Starter 의존성을 추가했다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter:1.0.0-M6'
// 기타 의존성
}
그리고 OpenAI API Key를 코드에 직접 작성하지 않고 환경변수로 관리하도록 구성했다.
spring.ai.openai.api-key=${OPENAI_API_KEY}
Windows PowerShell에서 환경변수를 설정했다.
$env:OPENAI_API_KEY="..."
실제 API Key는 소스 코드나 Git 저장소에 포함하지 않는 것이 중요하다.
처음에는 Spring Boot 프로젝트에 JPA 의존성도 함께 추가되어 있었다.
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
하지만 이번 프로젝트에서는 데이터베이스를 사용하지 않았다.
그 결과 Spring Boot가 JPA와 DataSource를 자동으로 설정하려고 하면서 데이터베이스 설정과 관련된 오류가 발생했다.
이번 프로젝트의 목적은 DB가 아니라 Spring AI와 LLM API 연동을 학습하는 것이었기 때문에 JPA 의존성을 제거했다.
Spring Boot의 자동 설정은 편리하지만, 프로젝트에 필요하지 않은 Starter를 추가하면 오히려 예상하지 못한 설정이나 오류가 발생할 수 있다.
따라서 현재 프로젝트에서 실제로 사용하는 기능만 의존성에 추가하는 것이 좋다는 것을 알게 되었다.
프로젝트에서 시스템에 설치된 Gradle 버전과 Spring Boot/프로젝트에서 사용하는 Gradle 버전 사이에 문제가 발생했다.
특히 Gradle 9.x 환경에서 Spring Boot의 bootJar 관련 호환성 문제가 발생했다.
이후 시스템에 설치된 Gradle을 직접 사용하는 대신 프로젝트에 포함되어 있는 Gradle Wrapper를 사용하기로 했다.
Windows에서는 다음과 같이 실행했다.
.\gradlew bootRun
Gradle Wrapper를 사용하면 프로젝트가 요구하는 Gradle 버전을 기준으로 빌드할 수 있기 때문에 프로젝트마다 환경 차이로 발생하는 문제를 줄일 수 있다.
간단한 AI 요청을 처리하기 위해 AiController를 만들었다.
@RestController
public class AiController {
private final ChatClient chatClient;
public AiController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@GetMapping("/ai")
public String ai(@RequestParam String message) {
return chatClient
.prompt()
.user(message)
.call()
.content();
}
}
이 코드에서 Spring AI가 제공하는 ChatClient를 이용하면 복잡한 HTTP 요청을 직접 작성하지 않고도 LLM에게 프롬프트를 전달할 수 있다.
요청은 다음과 같이 테스트했다.
http://localhost:8080/ai?message=안녕하세요
브라우저에서 /ai를 호출했을 때 다음과 같은 오류가 발생했다.
GET http://localhost:8080/ai?message=안녕하세요
500 (Internal Server Error)
처음에는 Spring Boot 코드 자체에 문제가 있는 것으로 생각했다.
하지만 로그를 자세히 확인해보니 Spring Boot가 실행되지 않은 문제가 아니었다.
Spring Boot → Controller → ChatClient → OpenAI API까지 요청이 전달되고 있었다.
로그를 확인하면서 실제 원인을 찾을 수 있었다.
429
insufficient_quota
credit_balance_exhausted
그리고 OpenAI API에서 다음과 같은 의미의 메시지를 반환하고 있었다.
You have no credits remaining.
즉, 500 Internal Server Error는 근본적인 원인이 아니었다.
실제 원인은 OpenAI API에서 사용할 수 있는 크레딧이 없었던 것이었다.
흐름을 정리하면 다음과 같다.
Browser
↓
Spring Boot
↓
AiController
↓
ChatClient
↓
Spring AI OpenAI
↓
OpenAI API
↓
429 insufficient_quota
↓
Spring Boot에서 최종적으로 500 응답
이 과정을 통해 HTTP 상태 코드만 보고 문제를 판단하면 안 되고, 서버 로그의 실제 예외 원인을 확인해야 한다는 것을 배웠다.
처음에는 API Key의 Permission 설정이 All인데도 왜 동작하지 않는지 의문이 들었다.
하지만 Permission과 API 사용 크레딧은 서로 다른 문제였다.
API Key Permission
→ API Key가 어떤 작업을 수행할 수 있는가?
API Credit
→ 실제 API를 호출해서 사용할 수 있는 잔액이 있는가?
이번 오류는 Permission 문제가 아니라 API Credit 부족 문제였다.
오히려 OpenAI API에서 429 insufficient_quota 응답을 받았다는 것은 API Key를 이용한 요청 자체가 OpenAI 서버까지 도달했다는 것을 의미했다.
따라서 다음과 같은 부분은 정상적으로 동작하고 있었다.
문제를 해결하는 가장 직접적인 방법은 OpenAI API에 사용할 수 있는 크레딧을 추가하는 것이다.
하지만 이번 프로젝트의 목적을 다시 생각해봤다.
"Spring AI를 공부하기 위해 간단한 챗봇을 만들어본다."
이 프로젝트에서 중요한 것은 특정 유료 API를 사용하는 것 자체가 아니라 Spring AI를 이용해서 LLM과 애플리케이션을 연결하는 방법을 배우는 것이다.
따라서 단순 학습을 위해 계속 API 비용을 지불해야 하는 구조가 과연 적절한지 생각해보게 되었다.
이 과정에서 Ollama와 같은 로컬 LLM 실행 환경을 알게 되었다.
OpenAI API를 사용하는 경우에는 다음과 같다.
Spring Boot
↓
Spring AI
↓
OpenAI API
↓
인터넷
↓
외부 LLM 서버
반면 로컬 LLM을 사용하는 경우에는 다음과 같은 구조가 된다.
Spring Boot
↓
Spring AI
↓
Ollama
↓
Local LLM
↓
내 컴퓨터
이 경우 외부 API의 크레딧을 사용하지 않고 내 컴퓨터에서 모델을 실행할 수 있다.
단순히 "OpenAI가 안 돼서 다른 것을 찾았다"라고 생각하기보다는 다음과 같은 이유가 있다.
이번 프로젝트의 핵심은 OpenAI 자체를 사용하는 것이 아니라 Spring AI의 사용 방법을 익히는 것이다.
따라서 로컬 LLM을 사용하더라도 다음과 같은 Spring AI의 핵심 개념을 학습할 수 있다.
ChatClient외부 LLM API를 계속 사용하면 요청을 보낼 때마다 비용이 발생할 수 있다.
반면 로컬 LLM은 모델을 PC에서 실행하기 때문에 OpenAI API의 크레딧을 사용할 필요가 없다.
학습 과정에서 여러 번 테스트하거나 코드를 수정하면서 반복적으로 요청하는 경우 특히 유용할 수 있다.
외부 API를 사용하면 API Key를 안전하게 관리해야 한다.
이번에도 API Key를 코드에 직접 넣지 않고 환경변수로 관리했다.
spring.ai.openai.api-key=${OPENAI_API_KEY}
이 과정 자체도 중요한 보안 학습이었지만, 로컬 LLM을 사용하면 최소한 해당 LLM 호출에 OpenAI API Key를 사용할 필요가 없어진다.
이번 경험을 통해 중요한 점을 알게 되었다.
Spring AI는 OpenAI 그 자체가 아니다.
Spring AI는 애플리케이션에서 다양한 AI 모델과 쉽게 통신할 수 있도록 도와주는 Spring 기반의 프레임워크/추상화 계층이다.
따라서 사용하는 모델 제공자에 따라 구조가 달라질 수 있다.
┌─ OpenAI
│
Spring AI ───────┼─ Ollama
│
├─ 기타 Chat Model
│
└─ 기타 AI Provider
이 때문에 OpenAI API를 사용한 경험 이후 Ollama 같은 로컬 모델을 연결해보는 것도 Spring AI를 이해하는 데 좋은 학습이 될 것이라고 생각했다.
이번 작업에서 단순히 챗봇 하나를 만드는 것보다 오히려 환경 설정과 오류 분석 과정에서 많은 것을 배웠다.
사용하지 않는 JPA를 추가하면 DataSource 자동 설정처럼 불필요한 문제가 발생할 수 있다.
프로젝트의 Gradle 버전과 개발 환경의 Gradle 버전 차이로 발생하는 문제를 줄일 수 있다.
.\gradlew bootRun
환경변수나 별도의 안전한 설정 방법을 사용해야 한다.
spring.ai.openai.api-key=${OPENAI_API_KEY}
브라우저에서는 단순히
500 Internal Server Error
라고 보였지만 실제 원인은 서버 로그에 있었다.
429 insufficient_quota
credit_balance_exhausted
따라서 항상 서버 로그의 가장 근본적인 예외를 확인해야 한다.
API Key에 권한이 충분하더라도 사용할 크레딧이 없다면 API 호출은 실패할 수 있다.
Spring AI는 애플리케이션과 AI 모델을 연결하기 위한 프레임워크이고, OpenAI는 그중 하나의 모델 제공자다.
따라서 OpenAI뿐만 아니라 로컬 LLM을 이용해서도 Spring AI를 학습할 수 있다.
로컬 LLM 모델을 이용하기 위해 Ollama를 설치해준다.
🐫Ollama
https://ollama.com/
설치 후 PowerShell에서 해당 명령어로 설치되었는지 확인이 가능하다.
ollama --version
모델이 있는지 확인하려면 해당 명령어를 실행해주면 되는데 초기 설치 시에 모델이 없다면 뜨지 않을테니 로컬 LLM을 하나 다운로드 받아준다.
ollama list
ollama pull llama3.2
ollama list

모델이 성공적으로 추가가 되었다.
ollama run llama3.2
이 명령어로 간단하게 테스트용(?) 대화가 가능하다.

이번에는 build.gradle 파일에서 기존 OpenAI 의존성 대신 Ollama Starter를 추가한다.
dependencies {
implementation 'org.springframework.ai:spring-ai-starter-model-ollama'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
기존에 사용하던 OpenAI Starter가 있다면 제거하고 Ollama Starter를 사용하면 된다.
Spring AI가 실행 중인 Ollama 서버와 사용할 모델을 알 수 있도록 application.yml을 설정한다.
spring:
ai:
ollama:
base-url: http://localhost:11434
chat:
options:
model: llama3.2
Ollama는 기본적으로 11434 포트를 사용한다.
따라서 Spring AI가 다음 주소의 Ollama 서버에 요청을 보내게 된다.
그리고 model에는 앞에서 설치한 llama3.2 모델을 지정했다.
그 후 controller로 Spring AI + 로컬 LLM 연결하여 호출해주는 controller를 하나 만들어 준다.
@RestController
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@GetMapping("/chat")
public String chat(@RequestParam String message) {
return chatClient
.prompt()
.user(message)
.call()
.content();
}
}
해당 브라우저로 실행 후 접속해주면

성공적으로 응답이 잘 되는 것을 확인할 수 있다.
Spring AI를 통해 Ollama의 로컬 LLM을 직접 연결해보면서 AI 모델과 애플리케이션이 연동되는 과정을 이해할 수 있었다.
직접 설정부터 API 호출까지 해보니 Spring AI가 AI 모델을 애플리케이션에 연결해주는 역할을 한다는 것을 이해할 수 있었다.
다음엔 방식을 좀 더 발전시켜서 POST 요청으로 Request DTO를 통해 ChatClient에 전달하여 JSON으로 반환하게 하는 API를 만들어봄으로서 Spring AI를 공부해보려고 한다.