티스토리 뷰

오늘날 인공지능(AI)은 단순한 기술 트렌드를 넘어, 우리 삶과 비즈니스의 모든 영역을 혁신하는 핵심 동력으로 자리 잡았습니다. 특히 자연어 처리(NLP) 분야의 비약적인 발전과 거대 언어 모델(LLM, Large Language Model)의 등장은 이제 누구나 강력한 AI 기능을 자신의 애플리케이션에 통합할 수 있는 시대를 열었습니다. 하지만 다양한 모델 API, 프롬프트 관리, 그리고 복잡한 관련 기술 스택을 익히는 데 많은 시간과 노력이 필요하기 때문에, LLM을 활용한 AI 애플리케이션 개발은 여전히 높은 진입 장벽으로 느껴질 수 있습니다.

여기, 자바(Java) 개발자, 나아가 Spring 프레임워크 사용자들에게 이 모든 복잡성을 해소하고 AI 애플리케이션 개발의 문을 활짝 열어줄 강력한 도구가 등장했습니다. 바로 Spring AI입니다. Spring AI는 Spring 개발 생태계의 견고함과 편리함을 그대로 이어받아, LLM 연동부터 RAG(Retrieval Augmented Generation) 패턴 구현, 나아가 에이전트(Agent) 기반의 고급 AI 애플리케이션까지, AI 개발의 전 과정을 놀랍도록 직관적이고 효율적으로 만들어줍니다.

이 글은 AI에 관심 있는 일반인부터 기본적인 프로그래밍 지식이 있는 개발자, 그리고 이미 Spring 프레임워크에 능숙한 백엔드 개발자에 이르기까지, 모든 분들이 Spring AI의 잠재력을 이해하고 실제 애플리케이션에 적용할 수 있도록 안내합니다. 복잡한 AI 개념은 쉬운 비유와 함께 설명하고, 실제 코드 예제를 통해 Spring AI 시작하기의 모든 과정을 상세히 다룰 것입니다. 이제 Spring AI와 함께 AI 애플리케이션 개발이라는 흥미로운 여정을 시작해볼까요?


Spring AI란? 자바(Java) 개발자를 위한 AI 애플리케이션 개발 프레임워크

최근 몇 년간 인공지능 기술의 발전 속도는 눈부십니다. 특히 ChatGPT와 같은 거대 언어 모델(LLM)의 등장은 AI가 연구실을 넘어 일반 사용자들의 일상과 다양한 비즈니스에 직접적인 영향을 미치기 시작했음을 보여주었습니다. 하지만 이러한 강력한 AI 모델들을 실제 애플리케이션에 통합하는 과정은 생각보다 복잡합니다. 각 모델마다 다른 API 호출 방식, 프롬프트 관리의 어려움, 출력 결과 파싱(parsing)의 번거로움, 그리고 모델 간의 전환에 대한 고려 등 개발자가 신경 써야 할 부분이 한두 가지가 아닙니다.

여기서 Spring AI가 구세주처럼 등장합니다. Spring AI는 자바 개발자에게 가장 친숙한 엔터프라이즈급 프레임워크인 Spring Boot의 철학을 계승하여, AI 모델 연동을 위한 추상화 계층을 제공합니다. 즉, 개발자가 다양한 LLM(예: OpenAI, Google Gemini, Azure OpenAI, Hugging Face 등)의 복잡한 세부 사항에 일일이 신경 쓸 필요 없이, Spring AI가 제공하는 통일된 인터페이스를 통해 AI 기능을 쉽고 빠르게 애플리케이션에 통합할 수 있도록 돕는 것입니다.

Spring AI의 등장 배경

Spring AI는 크게 두 가지 핵심 목표를 가지고 탄생했습니다. 첫째, 자바 개발자들이 빠르게 변화하는 AI 생태계에 효과적으로 대응할 수 있도록 돕는 것입니다. 파이썬(Python)이 AI 개발의 주류 언어처럼 여겨지지만, 전 세계적으로 수많은 엔터프라이즈 시스템이 자바와 Spring으로 구축되어 있습니다. 이 시스템들에 AI 기능을 통합하려면 자바 환경에 최적화된 도구가 필수적입니다. 둘째, AI 애플리케이션 개발의 복잡성을 줄이고 개발 생산성을 극대화하는 것입니다. Spring 프레임워크가 웹 애플리케이션 개발에서 보여준 혁신적인 편리함을 AI 개발에도 적용하려는 시도입니다.

주요 특징과 핵심 구성 요소

Spring AI는 마치 Universal AI Adapter와 같습니다. 다양한 AI 서비스 제공자들의 복잡한 API들을 하나의 표준화된 방식으로 사용할 수 있도록 해주는 것이죠. 비유하자면, 전 세계 모든 플러그 타입에 맞는 하나의 만능 어댑터를 제공하여, 어느 나라를 가든 전자기기를 쉽게 사용할 수 있게 하는 것과 같습니다.

  • LLM 통합 추상화 (LLM Integration Abstraction): OpenAI, Google Gemini, Azure OpenAI, Hugging Face 등 여러 LLM 제공자들을 동일한 ChatClient 인터페이스로 제어할 수 있습니다. 모델을 바꾸고 싶을 때 코드의 큰 변경 없이 설정 파일만 수정하면 됩니다.
  • 프롬프트 관리 (Prompt Management): LLM에게 어떤 작업을 수행할지 지시하는 것이 프롬프트입니다. Spring AI는 PromptTemplate과 같은 기능을 통해 프롬프트를 동적으로 구성하고 관리하는 것을 돕습니다. 예를 들어, 사용자 이름이나 특정 정보를 프롬프트에 쉽게 삽입할 수 있습니다.
  • 출력 파싱 (Output Parsing): LLM은 주로 텍스트를 반환합니다. 이 텍스트를 개발자가 원하는 특정 형식(예: JSON, List 등)으로 변환하는 것이 출력 파싱입니다. Spring AI는 OutputParser를 제공하여 이 과정을 자동화하고 구조화된 데이터를 쉽게 얻을 수 있도록 합니다.
  • 임베딩 생성 (Embedding Generation): 텍스트나 이미지를 컴퓨터가 이해할 수 있는 숫자 벡터(vector)로 변환하는 과정을 임베딩이라고 합니다. 이는 텍스트 검색, 유사성 분석 등 다양한 AI 작업의 핵심입니다. Spring AI는 다양한 임베딩 모델을 통합하여 이 기능을 쉽게 활용할 수 있게 합니다.
  • RAG (Retrieval Augmented Generation) 지원: LLM의 가장 큰 한계 중 하나는 학습 데이터에 없는 최신 정보나 특정 기업의 내부 데이터를 모른다는 점입니다. RAG는 외부 데이터베이스에서 관련 정보를 검색(Retrieval)하여 LLM에 함께 제공함으로써(Augmented Generation) 이러한 한계를 극복하는 기술입니다. Spring AI는 VectorStore, DocumentLoader, TextSplitter 등의 컴포넌트를 제공하여 RAG 패턴을 쉽게 구현할 수 있도록 지원합니다.

왜 Spring AI가 중요한 기술인가요?

Spring AI는 단순히 AI 기능을 Spring Boot 애플리케이션에 추가하는 것을 넘어섭니다. 이는 기존 Spring 개발자들이 새로운 AI 기술을 습득하고 적용하는 데 드는 시간과 노력을 획기적으로 줄여줍니다. 익숙한 Spring 프레임워크의 개념과 패턴을 사용하여 AI 애플리케이션을 개발할 수 있기 때문에, 기존 백엔드 개발자들도 AI 개발자로 빠르게 전환하거나 AI 기능을 자신의 서비스에 통합할 수 있게 되는 것입니다. 이는 AI 기술의 대중화를 촉진하고, 더욱 다양한 분야에서 혁신적인 AI 애플리케이션이 탄생하는 기반을 마련해 줄 것입니다. 비전공자나 AI 입문자들에게도 Spring Boot의 친숙한 환경을 통해 AI 개발의 첫걸음을 떼는 데 큰 도움을 줄 것입니다.


왜 Spring AI를 선택해야 할까요? 생산성, 유연성, 그리고 견고한 AI 아키텍처 구축

"AI를 사용해야 한다는 건 알겠는데, 어디서부터 어떻게 시작해야 할지 모르겠어요."
"파이썬으로 구현된 AI 모델을 자바 백엔드에 어떻게 통합해야 할까요?"
"다양한 LLM 서비스들의 API가 너무 달라서 모델을 바꿀 때마다 코드를 다 바꿔야 하나요?"

이러한 고민들은 AI 애플리케이션 개발을 시도하는 많은 개발자들이 공통적으로 겪는 어려움입니다. Spring AI는 바로 이러한 문제들을 해결하고, 개발자들이 AI 기술의 복잡성에 얽매이지 않고 핵심 비즈니스 로직에 집중할 수 있도록 돕는 강력한 솔루션입니다. Spring AI를 사용해야 하는 명확한 이유들을 개발 생산성 측면과 아키텍처 측면에서 자세히 살펴보겠습니다.

1. 개발 생산성 향상: 익숙함 속의 혁신

Spring AI의 가장 큰 장점 중 하나는 바로 개발 생산성 향상입니다. Spring 프레임워크에 익숙한 개발자라면, Spring AI가 제공하는 인터페이스와 컴포넌트들을 마치 기존 Spring Boot 애플리케이션을 개발하듯이 자연스럽게 사용할 수 있습니다.

  • 기존 Spring 기술 스택 활용: 여러분은 이미 Spring Boot의 의존성 주입(Dependency Injection), 설정 관리(Configuration Management), AOP(Aspect-Oriented Programming) 등 강력한 기능에 익숙할 것입니다. Spring AI는 이러한 Spring 생태계에 완벽하게 통합되어, 새로운 AI 개발 패러다임을 배우기 위한 추가적인 오버헤드를 최소화합니다. 새로운 언어나 프레임워크를 학습할 필요 없이, 이미 숙련된 자바 개발 기술을 AI 영역으로 확장할 수 있는 것입니다.
  • 빠른 프로토타이핑 및 MVP (Minimum Viable Product) 개발: Spring AI는 LLM과의 연동을 위한 상용구 코드(boilerplate code)를 크게 줄여줍니다. 몇 줄의 코드와 간단한 설정만으로 다양한 LLM과 연동하여 텍스트 생성, 요약, 번역, 분류 등 기본적인 AI 기능을 구현할 수 있습니다. 이는 아이디어를 빠르게 검증하고 시장에 출시할 수 있는 MVP 개발에 매우 유리합니다.
  • 모델 전환의 용이성: OpenAI의 GPT 모델을 사용하다가 비용 문제나 특정 기능 때문에 Google Gemini나 Azure OpenAI로 전환해야 할 때, Spring AI는 ChatClient와 같은 추상화된 인터페이스 덕분에 코드의 대대적인 수정 없이 설정 파일만 변경하는 것으로 모델을 쉽게 교체할 수 있습니다. 이는 개발 유연성을 극대화하고, 특정 LLM 제공자에 대한 종속성을 낮춥니다.

2. 간결하고 유연한 아키텍처: LLM 및 벡터 데이터베이스 연동의 이점

AI 애플리케이션, 특히 LLM 기반의 애플리케이션은 아키텍처적으로 복잡해질 수 있습니다. LLM과의 통신, 프롬프트 관리, 임베딩 생성, 벡터 데이터베이스 연동 등 다양한 컴포넌트들이 유기적으로 연결되어야 합니다. Spring AI는 이러한 복잡성을 해소하고 간결하며 유연한 아키텍처를 구축하는 데 큰 도움을 줍니다.

  • LLM 연동의 단순화: Spring AI는 다양한 LLM의 API를 표준화된 방식으로 추상화합니다. 이는 개발자가 LLM 제공자별로 다른 요청/응답 형식을 파악하고 처리하는 데 드는 시간을 절약하게 해줍니다. 개발자는 ChatClient 인터페이스 하나만 알고 있으면 어떤 LLM이든 동일한 방식으로 호출할 수 있습니다.
  • 벡터 데이터베이스 (Vector Database) 연동의 이점: 현대 AI 애플리케이션, 특히 RAG 패턴을 구현할 때 벡터 데이터베이스는 필수적인 요소입니다. 벡터 데이터베이스는 임베딩된 데이터를 저장하고 유사도 검색을 효율적으로 수행합니다. Spring AI는 VectorStore 인터페이스를 통해 Chroma, Pinecone, Redis, PostgreSQL(pgvector) 등 다양한 벡터 데이터베이스를 쉽게 연동할 수 있도록 지원합니다. 개발자는 복잡한 데이터베이스 연동 로직을 직접 구현할 필요 없이, Spring AI가 제공하는 추상화된 인터페이스를 통해 데이터를 저장하고 검색할 수 있습니다.
    • 예시: "사내 문서에서 특정 질문에 대한 답변을 찾아줘"와 같은 시나리오에서, 사내 문서를 임베딩하여 벡터 데이터베이스에 저장하고, 사용자의 질문도 임베딩하여 벡터 데이터베이스에서 가장 유사한 문서를 찾아 LLM에 전달하는 과정이 Spring AI를 통해 매우 간결해집니다.
  • 모듈화되고 확장 가능한 디자인: Spring AI는 각 기능을 모듈화하여 제공하므로, 필요한 컴포넌트만 선택적으로 사용하여 애플리케이션의 크기와 복잡성을 조절할 수 있습니다. 예를 들어, 단순히 챗봇 기능을 구현한다면 ChatClient만 사용하고, RAG 기능을 추가하고 싶다면 VectorStoreEmbeddingModel을 추가적으로 사용하면 됩니다. 이러한 모듈성은 애플리케이션의 유지보수성을 높이고 향후 기능 확장에도 유리합니다.

Spring AI는 단순히 AI 기능을 추가하는 도구를 넘어, 자바 개발자들이 AI 시대를 선도하고 혁신적인 애플리케이션을 만들 수 있도록 돕는 전략적인 프레임워크입니다. 익숙한 Spring 환경에서 강력한 AI 기능을 손쉽게 다루며, 개발 생산성을 극대화하고 견고한 AI 아키텍처를 구축할 수 있다는 점에서 Spring AI는 현대 AI 애플리케이션 개발의 필수적인 선택지가 될 것입니다.


Spring AI 시작하기: 첫 LLM 기반 AI 애플리케이션 구축 가이드

이제 Spring AI가 무엇이고 왜 사용해야 하는지 충분히 이해하셨을 것입니다. 이론은 충분하니, 이제 실제로 Spring AI를 사용하여 첫 LLM 연동 애플리케이션을 만들어보겠습니다. 이 섹션에서는 Spring AI 프로젝트 설정 방법부터 간단한 LLM API 연동 예시, 그리고 핵심 컴포넌트인 Prompt, Output Parser 사용법을 코드를 통해 상세히 설명합니다.

1. 프로젝트 설정: Spring Initializr로 시작하기

가장 먼저 할 일은 Spring AI 프로젝트를 설정하는 것입니다. Spring Boot 프로젝트 생성 도구인 Spring Initializr (opens in a new tab: https://start.spring.io/)를 사용하는 것이 가장 쉽습니다.

  1. Spring Initializr 접속: https://start.spring.io/ 에 접속합니다.
  2. 프로젝트 정보 입력:
    • Project: Maven Project 또는 Gradle Project (여기서는 Maven을 기준으로 설명합니다.)
    • Language: Java
    • Spring Boot: 최신 안정 버전 (예: 3.2.x)
    • Group: com.example
    • Artifact: spring-ai-quickstart
    • Packaging: Jar
    • Java: 최신 LTS 버전 (예: 17 또는 21)
  3. 의존성 추가 (Add Dependencies):
    • Spring Web: 웹 애플리케이션 기본을 위해 추가합니다.
    • Spring AI OpenAI Chat (또는 Spring AI Google Gemini Chat, Spring AI Azure OpenAI Chat 등 사용할 LLM 제공자에 맞춰 선택)
    • Spring AI OpenAI Embeddings (RAG 등을 위해 임베딩이 필요한 경우 추가, 여기서는 일단 건너뛰어도 좋습니다.)
  4. 프로젝트 생성 및 다운로드: 'Generate' 버튼을 클릭하여 프로젝트를 다운로드하고, 선호하는 IDE(IntelliJ IDEA, VS Code 등)로 압축을 해제한 후 엽니다.

2. LLM API 키 설정

다운로드한 프로젝트의 src/main/resources/application.properties 파일에 사용할 LLM 서비스의 API 키를 설정해야 합니다. 예를 들어, OpenAI를 사용하는 경우 다음과 같이 추가합니다.

spring.ai.openai.api-key=YOUR_OPENAI_API_KEY
# 로컬 개발 시 프록시 설정이 필요한 경우 (예: 해외망 제한 시)
# spring.ai.openai.base-url=https://api.openai.com/v1
# spring.ai.openai.chat.options.model=gpt-4o # 사용할 모델 지정 (선택 사항, 기본은 gpt-4o 또는 gpt-3.5-turbo)

주의: YOUR_OPENAI_API_KEY 부분은 반드시 본인의 OpenAI API 키로 교체해야 합니다. API 키는 외부에 노출되지 않도록 주의하고, 실제 서비스에서는 환경 변수나 비밀 관리 서비스를 이용하는 것이 좋습니다.

3. 첫 LLM 연동 애플리케이션 만들기: 간단한 챗봇

이제 기본적인 설정을 마쳤으니, ChatClient를 사용하여 간단한 질문에 답변하는 챗봇 기능을 구현해보겠습니다.

ChatClient로 메시지 주고받기

ChatClient는 Spring AI의 핵심 인터페이스로, LLM과 대화하는 기능을 제공합니다.

// src/main/java/com/example/springaiquickstart/ChatController.java
package com.example.springaiquickstart;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ChatController {

    private final ChatClient chatClient;

    // ChatClient는 Spring AI가 자동으로 주입해줍니다.
    public ChatController(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    @GetMapping("/chat")
    public String chat(@RequestParam(value = "message", defaultValue = "Hello, Spring AI!") String message) {
        // chatClient.prompt().user(message).call().content() 는
        // 사용자 메시지를 LLM에 보내고, LLM의 응답 텍스트를 반환합니다.
        return chatClient.prompt()
                .user(message)
                .call()
                .content();
    }
}

이 코드는 /chat 엔드포인트로 들어오는 메시지를 ChatClient를 통해 LLM에 전달하고, LLM의 응답을 그대로 반환합니다.

실행 및 테스트:

  1. 애플리케이션을 실행합니다 (IDE에서 SpringAiQuickstartApplication.java를 실행하거나 터미널에서 mvn spring-boot:run).
  2. 웹 브라우저나 curl을 사용하여 http://localhost:8080/chat?message=Spring AI는 무엇인가요? 와 같이 접속합니다.
  3. LLM이 "Spring AI"에 대한 설명을 응답으로 반환하는 것을 볼 수 있습니다.

핵심 컴포넌트: Prompt 이해하기

위 예제에서는 chatClient.prompt().user(message)를 사용했습니다. 여기서 prompt()는 LLM에게 전달할 프롬프트(Prompt) 객체를 구성하는 시작점입니다. 프롬프트는 LLM에게 특정 작업을 수행하도록 지시하는 명령어 또는 문맥 정보입니다.

Spring AI는 더욱 정교한 프롬프트 관리를 위해 PromptTemplate을 제공합니다. 이는 플레이스홀더를 사용하여 동적으로 프롬프트 내용을 변경할 수 있게 해줍니다.

// ChatController에 새로운 메서드 추가
import org.springframework.ai.chat.prompt.PromptTemplate;
import java.util.Map;

// ... (기존 코드 생략)

@RestController
public class ChatController {

    private final ChatClient chatClient;

    public ChatController(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    // ... (기존 /chat 메서드 생략)

    @GetMapping("/joke")
    public String generateJoke(@RequestParam(value = "topic", defaultValue = "Spring Framework") String topic) {
        // PromptTemplate을 사용하여 동적으로 프롬프트를 구성합니다.
        PromptTemplate promptTemplate = new PromptTemplate("Please tell me a short joke about {topic}.");
        Map<String, Object> model = Map.of("topic", topic); // 플레이스홀더에 들어갈 값

        return chatClient.prompt()
                .user(promptTemplate.render(model)) // 템플릿 렌더링 후 메시지 전달
                .call()
                .content();
    }
}

이제 http://localhost:8080/joke?topic=Java 로 접속하면 "자바"에 대한 농담을 들을 수 있습니다. PromptTemplate을 사용하면 프롬프트를 미리 정의하고 필요에 따라 변수를 삽입하여 재사용성을 높일 수 있습니다. 이는 복잡한 대화 흐름이나 다양한 시나리오에 대응하는 데 매우 유용합니다.

핵심 컴포넌트: Output Parser 사용법

LLM은 기본적으로 텍스트를 반환합니다. 하지만 때로는 LLM의 응답을 구조화된 데이터(예: JSON 객체, 리스트)로 받고 싶을 때가 있습니다. 이때 OutputParser를 사용합니다. OutputParser는 LLM의 텍스트 응답을 개발자가 정의한 특정 형식으로 변환해주는 역할을 합니다.

예를 들어, "도시 이름과 그 도시의 특징을 JSON 형식으로 알려줘"라고 요청하고 싶을 때 BeanOutputParser를 사용할 수 있습니다.

먼저, LLM의 응답을 담을 Java Bean 클래스를 정의합니다.

// src/main/java/com/example/springaiquickstart/CityInfo.java
package com.example.springaiquickstart;

public class CityInfo {
    private String cityName;
    private String description;
    private String famousLandmark;

    // 기본 생성자 (필수)
    public CityInfo() {}

    public CityInfo(String cityName, String description, String famousLandmark) {
        this.cityName = cityName;
        this.description = description;
        this.famousLandmark = famousLandmark;
    }

    // Getter와 Setter (필수)
    public String getCityName() { return cityName; }
    public void setCityName(String cityName) { this.cityName = cityName; }
    public String getDescription() { return description; }
    public void setDescription(String description) { this.description = description; }
    public String getFamousLandmark() { return famousLandmark; }
    public void setFamousLandmark(String famousLandmark) { this.famousLandmark = famousLandmark; }

    @Override
    public String toString() {
        return "CityInfo{" +
               "cityName='" + cityName + '\'' +
               ", description='" + description + '\'' +
               ", famousLandmark='" + famousLandmark + '\'' +
               '}';
    }
}

이제 ChatController에서 BeanOutputParser를 사용하여 이 CityInfo 객체로 응답을 파싱하는 메서드를 추가합니다.

// ... (기존 코드 생략)

import org.springframework.ai.parser.BeanOutputParser;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.chat.messages.UserMessage;

@RestController
public class ChatController {

    private final ChatClient chatClient;

    public ChatController(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    // ... (기존 /chat, /joke 메서드 생략)

    @GetMapping("/city-info")
    public CityInfo getCityInfo(@RequestParam(value = "city", defaultValue = "Seoul") String city) {
        BeanOutputParser<CityInfo> parser = new BeanOutputParser<>(CityInfo.class);

        // SystemPrompt: LLM에게 역할과 출력 형식을 지시합니다.
        String systemMessage = "You are a helpful AI assistant. Your task is to provide information about a city " +
                               "in a structured JSON format. " +
                               "Please respond strictly in JSON using the following format: \n{format}";

        // 사용자 메시지: 특정 도시에 대한 정보를 요청합니다.
        String userMessage = "Tell me about " + city;

        PromptTemplate systemPromptTemplate = new PromptTemplate(systemMessage, Map.of("format", parser.getFormat()));
        Prompt prompt = chatClient.prompt()
                .messages(systemPromptTemplate.createMessage(), new UserMessage(userMessage))
                .build();

        String response = chatClient.prompt(prompt).call().content();
        return parser.parse(response);
    }
}

BeanOutputParsergetFormat() 메서드는 LLM이 이해할 수 있는 형식 가이드라인을 제공합니다. 이를 systemMessage에 포함시켜 LLM이 정확한 JSON 형식으로 응답하도록 유도하는 것이 핵심입니다.

실행 및 테스트:

  1. 애플리케이션을 재실행합니다.
  2. http://localhost:8080/city-info?city=Paris 로 접속합니다.
  3. LLM이 파리에 대한 정보를 JSON 형식의 CityInfo 객체로 파싱하여 반환하는 것을 볼 수 있습니다.

이처럼 Spring AI는 ChatClient, PromptTemplate, OutputParser와 같은 컴포넌트들을 통해 LLM과의 상호작용을 매우 쉽고 유연하게 만들어줍니다. 이제 여러분은 기본적인 AI 애플리케이션을 구축할 준비가 되었습니다. 다음 섹션에서는 더 복잡한 시나리오인 RAG 패턴 구현 방법을 살펴보겠습니다.


실전 Spring AI: RAG(Retrieval Augmented Generation) 패턴으로 외부 데이터 연동하기

LLM은 방대한 데이터를 학습하여 놀라운 텍스트 생성 능력을 보여주지만, 몇 가지 한계가 있습니다. 첫째, 학습 데이터에 포함되지 않은 최신 정보나 특정 조직의 내부 정보는 알지 못합니다. 둘째, 때로는 사실과 다른 정보를 그럴듯하게 지어내는 환각(Hallucination) 현상을 보이기도 합니다. 이러한 한계를 극복하고 LLM의 유용성을 극대화하기 위해 등장한 것이 바로 RAG (Retrieval Augmented Generation) 패턴입니다.

RAG 패턴이란 무엇인가요?: '스마트 도서관 사서' 비유

RAG 패턴은 마치 '스마트 도서관 사서'와 같습니다. 여러분이 어떤 질문을 했을 때, 일반적인 LLM은 자신이 읽었던 책(학습 데이터)만을 기반으로 답변을 생성합니다. 하지만 스마트 도서관 사서(RAG)는 다릅니다.

  1. 질문 이해: 먼저 여러분의 질문을 정확히 이해합니다.
  2. 관련 자료 검색 (Retrieval): 질문과 가장 관련성이 높은 책(외부 문서, 데이터베이스)들을 도서관에서 능숙하게 찾아냅니다. (예: 특정 회사 매뉴얼, 최신 뉴스 기사)
  3. 정보 보강 및 답변 생성 (Augmented Generation): 찾아낸 자료들의 내용을 참고하여, 여러분의 질문에 대한 가장 정확하고 상세한 답변을 생성합니다.

이 과정에서 LLM은 단순히 기억을 더듬는 것이 아니라, 외부에서 검색된 '실시간' 또는 '특정' 정보를 기반으로 답변을 생성하므로, 정보의 정확성과 최신성을 크게 높일 수 있습니다.

RAG 패턴의 핵심 구성 요소

RAG 패턴을 구현하려면 다음과 같은 단계와 구성 요소들이 필요합니다.

  1. 문서 로더 (Document Loader): PDF, Word, HTML, 텍스트 파일 등 다양한 형식의 원본 문서를 읽어오는 역할입니다.
  2. 텍스트 분할기 (Text Splitter): 로드된 문서는 보통 너무 길어서 LLM에 한꺼번에 전달하기 어렵습니다. 이 때문에 문서를 의미 있는 작은 덩어리(chunk)로 나누는 작업이 필요합니다.
  3. 임베딩 모델 (Embedding Model): 분할된 텍스트 덩어리들을 컴퓨터가 이해할 수 있는 숫자 벡터(Embedding Vector)로 변환합니다. 이 벡터들은 텍스트의 의미를 함축적으로 담고 있어, 유사한 의미의 텍스트끼리는 벡터 공간에서 가깝게 위치하게 됩니다.
  4. 벡터 저장소 (Vector Store): 생성된 임베딩 벡터들을 저장하고, 나중에 사용자의 질문 벡터와 비교하여 유사한 문서 덩어리들을 빠르게 찾아내는 데이터베이스입니다. (예: Chroma, Pinecone, Redis, PgVector 등)
  5. 검색기 (Retriever): 사용자의 질문을 임베딩하고, 이 임베딩을 벡터 저장소에 질의하여 가장 관련성 높은 문서 덩어리들을 가져오는 역할을 합니다.
  6. LLM (Large Language Model): 검색된 문서 덩어리와 사용자의 원본 질문을 함께 입력으로 받아 최종 답변을 생성합니다.

Spring AI는 이 모든 구성 요소를 추상화하고 통합하여, RAG 패턴을 놀랍도록 쉽게 구현할 수 있도록 지원합니다.

Spring AI를 활용한 RAG 구현 예시: 자체 데이터 기반 질문 답변 시스템

이제 Spring AI를 사용하여 간단한 RAG 기반 질문 답변 시스템을 구현해봅시다. 여기서는 로컬에 저장된 간단한 텍스트 파일을 기반으로 질문에 답변하는 시스템을 만들어 보겠습니다.

사전 준비:

  • OpenAI API Key 설정: application.propertiesspring.ai.openai.api-key 설정은 필수입니다. (임베딩 모델과 LLM 모두 OpenAI를 사용할 것입니다.)
  • 의존성 추가: pom.xml에 다음 의존성을 추가합니다. spring-ai-openai-embedding은 임베딩 모델을, spring-ai-vectorstore-simple은 간단한 인메모리 벡터 저장소를 사용하기 위함입니다.
    <!-- ... (기존 의존성) ... -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-embedding</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-vectorstore-simple</artifactId>
    </dependency>
    참고: 실제 서비스에서는 spring-ai-vectorstore-chroma, spring-ai-vectorstore-pinecone 등 외부 벡터 데이터베이스 의존성을 추가하고 설정합니다.

구현 단계:

1. 문서 로드 및 임베딩, 벡터 저장소에 저장

먼저, 우리가 질문할 문서를 준비하고, 이를 임베딩하여 벡터 저장소에 저장하는 로직을 구현합니다. 여기서는 간단하게 String 리소스를 문서로 사용합니다.

// src/main/java/com/example/springaiquickstart/RagConfig.java
package com.example.springaiquickstart;

import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.transformer.splitter.TextSplitter;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;

@Configuration
public class RagConfig { // 설정 클래스 이름

    // 사용할 문서 파일 (resources/data/spring-ai-info.txt 파일을 가정)
    @Value("classpath:/data/spring-ai-info.txt")
    private Resource resource;

    // 1. 임베딩 모델과 벡터 저장소를 빈으로 등록
    // SimpleVectorStore는 인메모리 벡터 저장소로 개발 용도로 적합합니다.
    @Bean
    public VectorStore vectorStore(EmbeddingModel embeddingModel) throws IOException {
        SimpleVectorStore vectorStore = new SimpleVectorStore(embeddingModel);

        // 문서 로더 (DocumentReader) 역할을 하는 부분. 여기서는 Resource에서 텍스트를 읽습니다.
        String content = new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
        Document document = new Document(content); // 하나의 큰 문서 생성

        // 2. 텍스트 분할기 (Text Splitter) 설정
        // TokenTextSplitter는 토큰 수를 기준으로 텍스트를 나눕니다.
        TextSplitter textSplitter = new TokenTextSplitter();
        List<Document> chunks = textSplitter.split(List.of(document)); // 문서를 작은 덩어리로 분할

        // 3. 분할된 문서 덩어리들을 벡터 저장소에 저장 (임베딩 과정 포함)
        vectorStore.add(chunks);
        System.out.println("문서 " + chunks.size() + "개가 벡터 저장소에 저장되었습니다.");

        return vectorStore;
    }
}

src/main/resources/data/spring-ai-info.txt 파일을 생성하고 다음과 같은 내용을 추가합니다 (이 내용은 LLM이 학습하지 않았을 법한, 특정한 정보를 담고 있어야 RAG의 효과를 체감할 수 있습니다).

Spring AI는 Spring Framework 위에 구축된 인공지능 애플리케이션 개발을 위한 프레임워크입니다.
이 프레임워크는 OpenAI, Google Gemini, Azure OpenAI 등 다양한 LLM(Large Language Models)과의
통합을 간소화하여 자바 개발자들이 AI 기능을 쉽게 자신의 애플리케이션에 추가할 수 있도록 돕습니다.

주요 기능으로는 프롬프트 관리, 출력 파싱, 임베딩 생성, 그리고 RAG(Retrieval Augmented Generation) 패턴 지원이 있습니다.
Spring AI는 개발자가 LLM의 복잡성에 신경 쓰지 않고, 비즈니스 로직에 집중할 수 있게 하여 개발 생산성을 크게 향상시킵니다.
특히 RAG는 LLM의 최신 정보 부재나 환각 현상을 해결하기 위해 외부 데이터를 활용하는 강력한 패턴입니다.
벡터 저장소와 임베딩 모델을 사용하여 자체 데이터를 기반으로 정확한 답변을 생성할 수 있습니다.

2. 질문에 대한 RAG 기반 답변 생성

이제 ChatClientVectorStore를 사용하여 RAG 기반의 질문 답변 로직을 구현합니다.

// src/main/java/com/example/springaiquickstart/RagController.java
package com.example.springaiquickstart;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@RestController
public class RagController {

    private final ChatClient chatClient;
    private final VectorStore vectorStore; // 우리가 생성한 벡터 저장소

    public RagController(ChatClient chatClient, VectorStore vectorStore) {
        this.chatClient = chatClient;
        this.vectorStore = vectorStore;
    }

    @GetMapping("/rag-chat")
    public String ragChat(@RequestParam(value = "message", defaultValue = "Spring AI의 RAG 패턴은 무엇인가요?") String message) {
        // 1. 사용자 질문과 유사한 문서를 벡터 저장소에서 검색 (Retrieval)
        List<String> relevantDocuments = vectorStore.retrieve(SearchRequest.query(message).withTopK(2))
                .stream()
                .map(Document::getContent) // 검색된 문서의 내용을 가져옵니다.
                .collect(Collectors.toList());

        // 검색된 문서들을 하나의 문자열로 결합합니다.
        String documentContent = String.join("\n", relevantDocuments);

        // 2. 시스템 프롬프트를 사용하여 LLM에 역할과 컨텍스트를 제공
        // 검색된 문서를 LLM에 "컨텍스트"로 제공하여 답변을 보강합니다 (Augmented Generation).
        String systemPrompt = """
            당신은 유용한 AI 어시스턴트입니다. 다음 '참조 문서'를 기반으로 질문에 답변해주세요.
            만약 참조 문서에 답변이 없다면, '참조 문서에 해당 내용이 없습니다.'라고 답변해주세요.

            참조 문서:
            {documents}
            """;

        Prompt systemPromptWithDocuments = new Prompt(
                List.of(new SystemPromptTemplate(systemPrompt, Map.of("documents", documentContent)).createMessage())
        );

        // 3. LLM에 질문과 함께 검색된 문서 컨텍스트를 전달하여 답변 생성
        return chatClient.prompt()
                .messages(systemPromptWithDocuments.getContents().get(0), new UserMessage(message))
                .call()
                .content();
    }
}

실행 및 테스트:

  1. 애플리케이션을 재실행합니다. (IDE에서 SpringAiQuickstartApplication.java를 실행하거나 터미널에서 mvn spring-boot:run).
  2. 웹 브라우저나 curl을 사용하여 http://localhost:8080/rag-chat?message=Spring AI의 RAG 패턴은 무엇인가요? 와 같이 접속합니다.
    • 기대 결과: spring-ai-info.txt 파일에 있는 RAG에 대한 설명을 기반으로 답변이 생성됩니다.
  3. 이제 http://localhost:8080/rag-chat?message=대한민국의 수도는 어디인가요? 와 같이 spring-ai-info.txt에 없는 내용을 질문해봅니다.
    • 기대 결과: "참조 문서에 해당 내용이 없습니다." 와 유사한 답변을 받을 것입니다. (정확한 답변은 LLM의 지식과 시스템 프롬프트에 따라 약간 다를 수 있지만, 중요한 것은 외부 문서를 우선한다는 것입니다.)

이 예제는 Spring AI를 통해 RAG 패턴을 구현하는 방법을 보여줍니다. VectorStore를 사용하여 자체 데이터를 쉽게 임베딩하고 검색하며, 이를 ChatClient를 통해 LLM에 전달하여 더욱 정확하고 신뢰할 수 있는 답변을 얻을 수 있습니다. 이로써 기업 내부 문서, 최신 뉴스, 특정 도메인 지식 등을 LLM에 통합하여 비즈니스 가치를 창출하는 AI 애플리케이션을 구축할 수 있게 됩니다.


Spring AI의 고급 기능과 미래: 에이전트, 함수 호출, 옵저버빌리티

Spring AI는 단순히 LLM을 연동하는 것을 넘어, 현대 AI 애플리케이션이 요구하는 다양한 고급 기능과 아키텍처 패턴을 지원하며 지속적으로 발전하고 있습니다. 이 마지막 섹션에서는 Spring AI의 고급 주제들을 살펴보고, 앞으로 AI 애플리케이션이 어떤 방향으로 진화할지, 그리고 실제 비즈니스에 어떻게 적용될 수 있을지 논의해 보겠습니다.

1. 에이전트(Agent) 및 함수 호출(Function Calling): LLM의 지능 확장

현재 LLM은 텍스트 생성에는 탁월하지만, 외부 시스템과 상호작용하거나 복잡한 로직을 수행하는 데는 한계가 있습니다. 이러한 한계를 극복하기 위해 등장한 개념이 바로 에이전트(Agent)함수 호출(Function Calling)입니다.

  • 에이전트: 에이전트는 LLM이 스스로 판단하여 특정 목표를 달성하기 위해 필요한 도구(Tools)를 선택하고 사용하는 개념입니다. 마치 사람이 특정 작업을 위해 검색 엔진을 사용하거나 계산기를 사용하는 것처럼, LLM이 외부 도구를 활용하여 자신의 능력을 확장하는 것입니다.
  • 함수 호출 (Function Calling): LLM이 사용자의 요청을 이해하고, 이를 처리하기 위해 특정 함수(개발자가 정의한 API 또는 메서드)를 호출해야 할 때, 그 함수의 이름과 필요한 인자들을 JSON 형식으로 '제안'하는 기능입니다. Spring AI는 이 함수 호출 기능을 자연스럽게 통합하여, LLM이 직접 외부 시스템과 연동할 수 있도록 돕습니다.

Spring AI에서의 에이전트/함수 호출:

Spring AI는 FunctionCallback을 통해 개발자가 정의한 자바 메서드를 LLM이 호출할 수 있는 '도구'로 노출시킵니다.

예시 시나리오: "오늘 서울 날씨는 어때?" 라는 질문을 받았을 때, LLM은 스스로 판단하여 '날씨 정보를 가져오는 함수'가 필요하다고 인식하고, 해당 함수를 호출하여 실제 날씨 데이터를 얻은 후 사용자에게 답변을 생성합니다.

// 날씨 정보를 가져오는 가상의 서비스
import org.springframework.stereotype.Service;

@Service
public class WeatherService {
    public String getCurrentWeather(String location) {
        // 실제로는 외부 API 호출 등의 로직이 들어갑니다.
        if ("Seoul".equalsIgnoreCase(location)) {
            return "서울의 현재 날씨는 맑고 기온은 25도입니다.";
        } else if ("Busan".equalsIgnoreCase(location)) {
            return "부산의 현재 날씨는 흐리고 비가 오고 있습니다.";
        }
        return location + "의 날씨 정보를 찾을 수 없습니다.";
    }
}

// Function Calling을 사용하는 Controller (일부 코드)
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;

// ...

@RestController
public class AgentController {

    private final ChatClient chatClient;
    private final WeatherService weatherService;

    public AgentController(ChatClient chatClient, WeatherService weatherService) {
        this.chatClient = chatClient;
        this.weatherService = weatherService;
    }

    @GetMapping("/ask-weather")
    public String askWeather(@RequestParam(value = "question", defaultValue = "오늘 서울 날씨는 어때?") String question) {
        // FunctionCallback 등록: LLM이 호출할 수 있는 함수를 정의
        FunctionCallback weatherFunction = FunctionCallback.builder(weatherService)
                .name("getCurrentWeather") // LLM이 인식할 함수 이름
                .description("Get the current weather for a given location") // 함수 설명
                .inputConverter(args -> (String) args.get("location")) // 입력 파라미터 변환
                .outputConverter(String.class) // 출력 파라미터 타입
                .build();

        // LLM에 함수 호출 옵션과 함께 프롬프트 전달
        Prompt prompt = chatClient.prompt()
                .user(question)
                .options(FunctionCallingOptions.builder()
                        .withFunctionCallbacks(List.of(weatherFunction))
                        .build())
                .build();

        return chatClient.prompt(prompt).call().content();
    }
}

LLM은 askWeather 엔드포인트에 "오늘 서울 날씨는 어때?"라는 질문이 들어오면, getCurrentWeather 함수가 날씨 정보를 가져오는 데 사용될 수 있음을 인지합니다. 그리고 이 함수를 호출하기 위한 적절한 인자(여기서는 location="Seoul")를 생성하여 Spring AI에 전달합니다. Spring AI는 이 요청을 받아 실제 WeatherServicegetCurrentWeather("Seoul") 메서드를 호출하고, 그 결과를 LLM에 다시 전달하여 최종 답변을 생성하게 하는 것입니다.

이러한 에이전트 기반의 접근 방식은 LLM을 단순한 텍스트 생성기를 넘어, 복잡한 비즈니스 로직을 수행하고 외부 시스템과 연동하는 '지능적인 컨트롤러'로 진화시킵니다.

2. 옵저버빌리티 (Observability): AI 애플리케이션의 모니터링 및 디버깅

AI 애플리케이션은 기존 애플리케이션보다 예측하기 어려운 동작을 하거나, 예상치 못한 비용이 발생할 수 있습니다 (예: 토큰 사용량 증가). 따라서 AI 애플리케이션의 동작을 면밀히 관찰하고 분석하는 옵저버빌리티(Observability)는 매우 중요합니다.

Spring AI는 Spring Boot의 강력한 모니터링 기능(Micrometer, Actuator)과 통합되어 AI 애플리케이션의 옵저버빌리티를 지원합니다.

  • 토큰 사용량 모니터링: LLM 호출 시 사용된 입력/출력 토큰 수를 추적하여 비용을 관리하고 성능을 최적화할 수 있습니다.
  • 응답 시간 측정: LLM 호출 및 관련 처리의 응답 시간을 모니터링하여 병목 현상을 식별하고 사용자 경험을 개선할 수 있습니다.
  • 프롬프트 및 응답 로깅: 어떤 프롬프트가 LLM에 전달되었고, 어떤 응답이 왔는지 기록하여 디버깅 및 프롬프트 엔지니어링 개선에 활용할 수 있습니다.

이러한 옵저버빌리티 기능은 AI 애플리케이션이 프로덕션 환경에서 안정적으로 운영되고 지속적으로 개선될 수 있도록 돕습니다.

3. 향후 발전 방향 및 확장 가능성

Spring AI는 아직 초기 단계에 있지만, 그 발전 가능성은 무궁무진합니다.

  • 멀티모달 (Multi-modal) 지원 강화: 현재는 텍스트 기반 LLM에 집중되어 있지만, 이미지, 오디오, 비디오 등 다양한 형태의 데이터를 이해하고 생성하는 멀티모달 AI 모델과의 통합이 더욱 강화될 수 있습니다.
  • 스트리밍 (Streaming) API 표준화: LLM의 응답을 한 번에 받는 것이 아니라, 마치 타이핑하듯이 실시간으로 스트리밍 받는 기능은 사용자 경험을 크게 향상시킵니다. Spring AI는 이러한 스트리밍 API의 활용을 더욱 용이하게 할 것입니다.
  • 더 많은 LLM/임베딩 모델 통합: 현재 지원되는 주요 LLM 외에도, 더욱 다양한 오픈소스 및 상용 AI 모델들과의 연동이 확대되고 있습니다.
  • 커뮤니티 기반의 성장: Spring 프레임워크가 그랬듯이, Spring AI도 활발한 커뮤니티 기여를 통해 빠르게 발전하고 다양한 유스케이스에 최적화된 기능들을 추가해 나갈 것입니다.

4. 실제 비즈니스에 적용할 수 있는 아이디어

Spring AI가 제공하는 강력한 기능들을 활용하면 다음과 같은 다양한 AI 애플리케이션을 비즈니스에 적용할 수 있습니다.

  • 지능형 고객 서비스 챗봇: RAG 패턴을 사용하여 기업의 방대한 FAQ 문서, 제품 매뉴얼, 서비스 약관 등을 학습하여 고객 문의에 대한 정확하고 개인화된 답변을 제공합니다.
  • 개인화된 콘텐츠 추천 시스템: 사용자의 행동 데이터와 선호도를 LLM에 입력하여 맞춤형 상품, 뉴스 기사, 미디어 콘텐츠를 추천합니다.
  • 자동화된 데이터 분석 및 보고서 생성: 복잡한 데이터를 LLM이 이해하기 쉬운 형태로 변환하고, 이를 기반으로 보고서 초안을 작성하거나 주요 인사이트를 도출합니다.
  • 개발자 생산성 도구: 코드 생성, 코드 리뷰, 문서화 자동화 등 개발 과정의 반복적인 작업을 AI가 지원하여 개발자의 생산성을 높입니다.
  • 지능형 검색 시스템: 기존 키워드 기반 검색을 넘어, 사용자의 자연어 질문의 의도를 파악하고 RAG를 통해 가장 관련성 높은 정보를 찾아 제공합니다.
  • 내부 지식 관리 시스템: 기업 내부의 비정형 데이터를 LLM과 벡터 저장소로 관리하여, 직원들이 필요한 정보를 빠르게 찾고 업무에 활용할 수 있도록 돕습니다.

Spring AI는 이러한 아이디어들을 현실로 만들 수 있는 강력한 기반을 제공합니다. 자바 개발자라면 누구나 Spring AI를 통해 AI 애플리케이션 개발에 도전하고, 비즈니스에 혁신적인 가치를 더할 수 있습니다.


마무리하며: AI 시대, Spring AI와 함께 혁신을 이끄세요!

지금까지 Spring AI의 개념부터 시작하기, RAG 패턴 구현, 그리고 에이전트와 같은 고급 기능 및 미래 발전 방향까지 폭넓게 살펴보았습니다. Spring AI는 복잡하게 느껴졌던 AI 애플리케이션 개발의 진입 장벽을 낮추고, Spring 프레임워크의 견고함과 편리함을 AI 영역으로 확장하는 데 핵심적인 역할을 합니다.

이 블로그 포스트를 통해 Spring AI가 자바 개발자들에게 얼마나 강력하고 유용한 도구인지 충분히 이해하셨기를 바랍니다. 단순히 LLM을 호출하는 것을 넘어, RAG를 통해 자체 데이터를 활용하고, 함수 호출을 통해 LLM의 지능을 확장하며, 안정적인 운영을 위한 옵저버빌리티까지 갖춘 현대적인 AI 애플리케이션을 Spring AI로 구축할 수 있습니다.

AI는 더 이상 먼 미래의 기술이 아닙니다. 지금 바로 여러분의 Spring Boot 애플리케이션에 AI의 힘을 불어넣을 때입니다. Spring AI는 여러분이 그 여정을 성공적으로 시작하고 지속적으로 혁신해나갈 수 있도록 든든한 동반자가 되어줄 것입니다.

지금 바로 Spring AI 공식 문서를 참고하여 (opens in a new tab: https://docs.spring.io/spring-ai/reference/) 여러분의 첫 Spring AI 프로젝트를 시작해보세요! 궁금한 점이나 의견은 언제든지 댓글로 남겨주시면 함께 고민하고 해결해나가겠습니다. Spring AI와 함께 AI 시대의 주인공이 되세요!

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/01   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함