[인공지능 개발자] Spring AI로 LLM 연결하기(Feat: ollama)

들어가면서

올해 초 LLM에 관심이 생겨 이것저것 찾아보다가 Spring AI를 알게 되었고, 최근 정식 버전인 1.0.0이 출시되면서 본격적으로 공부하게 되었습니다.
제가 느끼기에 Spring AI는 LLM 연결을 매우 쉽게 도와주며, 다음과 같은 다양한 기능을 제공합니다:

  • 프롬프트 전처리/후처리를 위한 Advisor
  • JSON → DTO 구조화 출력 지원
  • LLM이 메서드를 직접 호출하는 Function Calling
  • RAG, MCP, 멀티모달 등 다양한 고급 기능 지원

덕분에 이제는 Python 기반 도구가 아니더라도, Java 환경에서도 LLM을 효과적으로 활용할 수 있게 되었습니다.
이 블로그 시리즈는 총 3편으로 구성될 예정이며, 각 주제는 다음과 같습니다:

  1. Spring AI로 LLM 연결하고 구조화된 출력하기
  2. Advisor로 LLM에 추가 정보 제공 및 응답 가공하기 (feat. RAG)
  3. Function Calling으로 LLM에게 메서드 제공하기 (feat. AI Agent)
    이번 포스팅은 그 첫 번째 주제인 “Spring AI로 LLM 연결하고 구조화된 출력하기”입니다.

기본적인 설정법

Spring AI는 다양한 LLM 제공자(OpenAI, Azure, Ollama 등)와 연동할 수 있습니다.
저는 이 포스팅에서 로컬에서 실행 가능한 오픈소스 플랫폼인 Ollama를 기준으로 설명하겠습니다.

  • 참고: Spring AI는 Java 17 이상부터 지원됩니다.

Ollama는 다양한 오픈소스 LLM을 PC에 직접 다운로드하여 실행할 수 있는 플랫폼입니다.
따라서 외부로 유출되면 안 되는 개인정보나 대외비 문서를 처리할 때 적합한 선택이 될 수 있습니다.

 - 의존성 추가 (Maven 기준)
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
 - 설정 파일 기본적인 설정
 # Ollama 서버 주소
spring.ai.ollama.base-url=http://localhost:11434 
# 모델 다운로드 전략(always, when_missing, never)
spring.ai.ollama.init.pull-model-strategy=when_missing
# 기본 셋팅 llm모델
spring.ai.ollama.chat.model= qwen3:1.7b 
# 창의성 조절: 높을수록 자유로운 응답
spring.ai.ollama.chat.temperature= 0.8
# 최대 응답 토큰 수
spring.ai.ollama.chat.max-tokens= 1024
# Embedding 모델
spring.ai.ollama.embedding.model=bona/bge-m3-korean

추가적인 설정에 대해서는 url에서 확인 가능합니다.
https://docs.spring.io/spring-ai/reference/api/chat/ollama-chat.html#_chat_properties


예제 코드

Spring AI에서는 LLM의 응답을 두 가지 방식으로 받을 수 있습니다.

  1. 단일 출력 방식: 한 번에 전체 응답을 받아 처리
  2. 스트리밍 출력 방식 (Flux): LLM 응답을 실시간으로 조각 단위로 받기
    예제를 통해 두 방식의 사용법과, JSON 구조화된 출력 방식까지 함께 살펴보겠습니다.

???? ChatModel과 ChatClient의 차이

Spring AI에는 ChatModel과 ChatClient라는 두 개의 주요 구성 요소가 있습니다.

  • ChatModel: LLM과의 저수준 통신 역할을 합니다.
  • ChatClient: ChatModel을 래핑한 상위 레벨 API로, 프롬프트 구성, 응답 처리, Advisor, RAG 등 다양한 확장 기능을 제공합니다.

단일 출력 방식

@AllArgsConstructor
@RestController
public class ModelClient {

    // Spring이 OllamaChatModel 구현체 자동 주입
    private final ChatModel chatModel;

    @GetMapping("/v1/model-client")
    public ChatResponse modelClientV1(@RequestParam("text") String text) {

        // 시스템 메시지는 LLM이 반드시 지켜야 할 지침
        // 유저 메시지는 사용자의 입력
        Prompt prompt = new Prompt(
                List.of(
                        new SystemMessage("너는 친절한 AI 비서야. 절대 추론 과정을 보여주지 말고 요약만 응답해."),
                        new UserMessage(text)
                ));

        return ChatClient.create(chatModel)
                .prompt(prompt)
                .call()
                .content(); // 전체 응답을 한 번에 반환
    }
}

스트리밍 출력 방식 (Flux)

스트리밍 방식은 채팅처럼 실시간으로 응답이 필요한 UI에 적합합니다.

@AllArgsConstructor
@RestController
public class ModelClient {

  private final ChatModel chatModel;

  @GetMapping(value = "/v2/model-client", produces = "text/plain;charset=UTF-8")
  public Flux<String> getModelClientV2(@RequestParam("text") String text) {

      // 시스템 메시지는 LLM이 반드시 지켜야 할 지침
      // 유저 메시지는 사용자의 입력
      Prompt prompt = new Prompt(
              List.of(
                      new SystemMessage("너는 친절한 AI 비서야"),
                      new UserMessage(text)
              ));

      return ChatClient.create(this.chatModel)
              .prompt(prompt)
              .stream() // 스트리밍 응답 시작
              .content(); // Flux<String>으로 실시간 조각 응답 수신
    }
}

구조화된 출력 받기 (JSON → DTO)

내부적으로는 LLM에게 "이런 구조로 응답해줘"라고 명시한 후 결과를 DTO에 바인딩합니다.
하지만 LLM이 해당 형식대로 정확하게 응답하지 않으면 파싱 에러가 발생할 수 있습니다.
안정성을 높이기 위해 시스템 메시지에 형식을 반복적으로 명확히 명시하거나, 구조 유도 프롬프트 예제를 함께 제공하는 것이 좋습니다.

@AllArgsConstructor
@RestController
public class ModelClient {

    private final ChatModel chatModel;

    @GetMapping("/v3/model-client")
    public ActorFilms modelClientV3(@RequestParam("text") String text) {

        // 사용자 입력을 이용해 프롬프트 템플릿 구성
        String template = """
            Generate the filmography of 5 movies for {actor}.
        """;

        Prompt prompt = new Prompt(
                List.of(
                        new SystemMessage("너는 친절한 AI 비서야. 절대 추론 과정을 보여주지 말고 요약만 응답해."),
                        new UserMessage(new PromptTemplate(template).render(Map.of("actor", text)))
                ));

        ChatModel gemma = OllamaChatModel.builder()
                .ollamaApi(OllamaApi.builder().build())
                .defaultOptions(
                        OllamaOptions.builder()
                                .model("gemma3n:e4b")
                                .temperature(0.4)
                                .build())
                .build();

        return ChatClient.create(gemma)
                .prompt(prompt)
                .call()
                .entity(ActorFilms.class); // 결과를 DTO로 구조화
    }

    record ActorFilms(String actor, List<String> movies) {}
}

프롬프트 요청 예시 (내부 동작 확인용)
Spring AI가 구조화 출력을 위해 내부적으로 LLM에게 다음과 같이 요청을 구성합니다.

request: ChatClientRequest[
- 저희가 입력한 명령 프롬프트 입력입니다.
prompt=Prompt{
    messages=[SystemMessage{textContent='너는 친절한 AI 비서야. 절대 추론과정을 보여주지 말고 요약만 응답해', messageType=SYSTEM, metadata={messageType=SYSTEM}}, 
    UserMessage{content='Generate the filmography of 5 movies for 하정우.', properties={messageType=USER}, messageType=USER}], modelOptions=org.springframework.ai.ollama.api.OllamaOptions@48e29a2c}, 

- 여기에 해당 DTO 객체의 정의가 들어갑니다.
context={spring.ai.chat.client.output.format=Your response should be in JSON format.
Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.
Do not include markdown code blocks in your response.
Remove the ```json markdown from the output.
Here is the JSON Schema instance your output must adhere to:
```{
  "$schema" : "https://json-schema.org/draft/2020-12/schema",
  "type" : "object",
  "properties" : {
    "actor" : {
      "type" : "string"
    },
    "movies" : {
      "type" : "array",
      "items" : {
        "type" : "string"
      }
    }
  },
  "additionalProperties" : false
}```
}]

마치며

AI를 실제 프로젝트에 도입하는 것은 어렵고 복잡할 것 같다는 막연한 생각이 있었지만,
Spring AI를 통해 Java 환경에서도 LLM을 손쉽게 연결하고 활용할 수 있다는 가능성을 확인할 수 있었습니다.

물론 성능, 속도, 프롬프트 설계, 적절한 모델 선택 등 실무에서 고려할 요소들은 많지만,
이번 실습을 통해 LLM을 실제 서비스에 적용하는 출발점을 잡을 수 있었습니다.

다음 포스팅에서는 Advisor 기능을 활용해 LLM 응답을 가공하고,
간단한 RAG(Retrieval-Augmented Generation) 기능을 구현해보겠습니다.

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

이 기능은 Logging, 크롤링된 문서, RDBMS 정보 등 다양한 외부 지식 소스를 LLM과 연결해
더 정확하고 신뢰도 높은 응답을 생성하도록 도와주는 방식입니다.

해당 예시는 github에 있습니다.
url: https://github.com/Zero-Human/springAi

참고자료

 

[출처] https://zero-human.tistory.com/9

 

 

 

 

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
22 [인공지능 개발자] 성공적인 바이브 코딩을 위해서는 AI가 명확하게 이해할 수 있는 기획서(PRD)가 필수 졸리운_곰 2026.05.03 27
21 [인공지능 개발자] 프로덕션 환경에서 바이브 코딩을 책임감 있게 하는 법 - Vibe coding in prod 졸리운_곰 2026.04.25 35
20 [인공지능 개발자] AI 코딩 시대, 성장이 멈추는 개발자의 뇌에서 일어나는 일 졸리운_곰 2026.04.21 45
19 [인공지능 개발자] AI 코딩 시대, 성장이 멈추는 개발자의 뇌에서 일어나는 일 졸리운_곰 2026.04.21 34
18 [인공지능 개발자] gemma4 e2b 파인튜닝 졸리운_곰 2026.04.19 35
17 [인공지능 개발자] LLM을 서빙하는 프레임워크, vLLM 사용법 file 졸리운_곰 2026.04.14 36
16 [인공지능 개발자] LLM을 서빙하는 프레임워크, vLLM 사용법 file 졸리운_곰 2026.04.14 34
15 [인공지능 개발자] 찐 개발자의 바이브 코딩은 다릅니다 - 클로드코드 바이브코딩 실전 워크플로우 모두 공개 졸리운_곰 2026.03.23 38
14 [인공지능 개발자] LLM은 올바른 코드를 작성하지 않는다. 그럴듯한 코드를 작성할 뿐이다 졸리운_곰 2026.03.08 49
13 [인공지능 개발자] microgpt - 200줄 순수 파이썬으로 구현한 GPT 학습 및 추론 file 졸리운_곰 2026.02.18 97
12 [인공지능 개발자] Claude Code 완전 가이드: 해커톤 우승자의 70가지 파워 팁 [54p PDF] file 졸리운_곰 2026.02.16 97
11 [인공지능 개발자] 소프트웨어 서바이벌 3.0 – 무엇을 만들어야 살아남는가 졸리운_곰 2026.02.16 128
10 [인공지능 개발자] [알아봅시다] AI로 고품질 코드를 효과적으로 작성하는 방법 졸리운_곰 2026.02.08 124
9 [인공지능 개발자] [알아봅시다] Ollama Python에서 사용하기 file 졸리운_곰 2025.12.17 110
8 [인공지능 개발자] Next.js에 Ollama를 통합하는 방법 / How to Integrate Ollama in Next.js 졸리운_곰 2025.12.14 141
» [인공지능 개발자] Spring AI로 LLM 연결하기(Feat: ollama) 졸리운_곰 2025.12.10 121
6 [인공지능 개발자] Ollama 설치부터 모델 커스텀까지, 비개발자도 이해하는 이용 가이드 file 졸리운_곰 2025.12.02 122
5 [인공지능 개발자] 챗GPT로 개발하다가 망했습니다 (개발자 90%가 모르는 바이브 코딩의 함정) 졸리운_곰 2025.12.01 128
4 [인공지능 개발자][pytorch] 예제로 배우는 파이토치(PyTorch) 졸리운_곰 2025.08.12 88
3 [인공지능 개발자] Cursor AI에서 Python으로 프로그램 처음 시작하기 졸리운_곰 2025.08.05 117
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED