๋“œ๋””์–ด AI ๊ฐœ๋ฐœ์ด ๋” ์•ˆ์ •์ ์ด๊ณ  ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค: Spring AI 1.1.1 ๋ฆด๋ฆฌ์ฆˆ

์ตœ๊ทผ ๋ช‡๋…„๊ฐ„ AI ๊ธฐ์ˆ ์˜ ๋ฐœ์ „ ์†๋„๋Š” ์ •๋ง ๋†€๋ž๋‹ค. ํŠนํžˆ ๋ฐฑ์—”๋“œ ์˜์—ญ์—์„œ Java์™€ Spring Boot๋ฅผ ์‚ฌ์šฉํ•ด LLM๊ธฐ๋ฐ˜ ์„œ๋น„์Šค๋ฅผ ๊ตฌ์ถ•ํ•˜๋ ค๋Š” ์š”๊ตฌ๊ฐ€ ํญ๋ฐœ์ ์œผ๋กœ ์ฆ๊ฐ€ํ•˜๊ณ  ์žˆ๋‹ค. ์ด๋Ÿฌํ•œ ํ๋ฆ„์˜ ์ค‘์‹ฌ์—๋Š” ๋‹จ์—ฐ Spring AI๊ฐ€ ์žˆ๋‹ค.
Spring AI ํŒ€์—์„œ ์•ˆ์ •์„ฑ๊ณผ ๊ธฐ๋Šฅ์„ ๋Œ€ํญ ๋Œ์–ด์˜ฌ๋ฆฐ Spring AI 1.1.1 ๋ฒ„์ „์„ ์ •์‹ ๋ฐœํ‘œํ–ˆ๋‹ค. ์ด๋ฒˆ ๋ฆด๋ฆฌ์ฆˆ๋Š” ๋‹จ์ˆœํ•œ ๋ฒ„๊ทธ ์ˆ˜์ • ์ด์ƒ์˜ ์˜๋ฏธ๋ฅผ ๊ฐ€์ง€๋ฉฐ, ์‹ค๋ฌด์—์„œ AI ๊ธฐ๋Šฅ์„ ์•ˆ์ •์ ์œผ๋กœ ๊ตฌํ˜„ํ•˜๋ ค๋Š” ๊ฐœ๋ฐœ์ž๋“ค์—๊ฒŒ ๋งค์šฐ ๋ฐ˜๊ฐ€์šด ์†Œ์‹์ด ๋  ๊ฒƒ ๊ฐ™๋‹ค.
์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” Spring AI 1.1.1์˜ ์ฃผ์š” ๋ณ€๊ฒฝ์ ์„ ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ณ , ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ ์–ป์„ ์ˆ˜ ์žˆ๋Š” ์ด์ ์— ๋Œ€ํ•ด์„œ ์ •๋ฆฌํ•ด ๋ณด๊ณ ์ž ํ•œ๋‹ค.

์ด๋ฒˆ ์—…๋ฐ์ดํŠธ์˜ ํ•ต์‹ฌ์€ ๋ฌด์—‡์ธ๊ฐ€?

Spring AI 1.1.1์€ ๊ณต์‹ OpenAI Java SDK ํ†ตํ•ฉ ๋ฐ ๊ฐ•ํ™”๋œ ๊ตฌ์กฐํ™” ์ถœ๋ ฅ(Structured Output) ์ง€์›์„ ํ†ตํ•ด AI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์˜ ์•ˆ์ •์„ฑ๊ณผ ์ƒ์‚ฐ์„ฑ์„ ํฌ๊ฒŒ ๋†’์ด๊ณ  ์ฃผ์š” ๋ฒ„๊ทธ ๋ฐ ๋ณด์•ˆ ํŒจ์น˜๊ฐ€ ํฌํ•จ๋œ ํ•ต์‹ฌ ์•ˆ์ •ํ™” ๋ฆด๋ฆฌ์ฆˆ๋‹ค.

์ฃผ์š” ๋ณ€๊ฒฝ์‚ฌํ•ญ

์ด๋ฒˆ 1.1.1 ๋ฆด๋ฆฌ์ฆˆ๋Š” 13๊ฐœ์˜ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ๊ณผ 16๊ฐœ์˜ ๋ฒ„๊ทธ ์ˆ˜์ •, ๊ทธ๋ฆฌ๊ณ  2๊ฑด์˜ CVE ๋ณด์•ˆ ํŒจ์น˜๋ฅผ ํฌํ•จํ•œ 5๊ฐœ์˜ ์˜์กด์„ฑ ์—…๋ฐ์ดํŠธ๊ฐ€ ์ ์šฉ๋˜์—ˆ๋‹ค. ์ด์ค‘์—์„œ ํŠนํžˆ ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ฃผ๋ชฉํ•ด์•ผ ํ•  3๊ฐ€์ง€ ํ•ต์‹ฌ ๋ณ€๊ฒฝ์ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

OpenAI Java SDK์™€์˜ ๊ณต์‹ ํ†ตํ•ฉ (Type-Safe API)

OpenAI๋ฅผ ์‚ฌ์šฉํ•˜๋Š” Spring AI ๊ฐœ๋ฐœ์ž๋“ค์—๊ฒŒ ๊ฐ€์žฅ ํฐ ๋ณ€ํ™”์ด์ž ํ™˜์˜ํ•  ๋งŒํ•œ ์†Œ์‹์ด๋‹ค.

  • ๋ณ€๊ฒฝ๋‚ด์šฉ: ๊ธฐ์กด์—๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ OpenAI API๋ฅผ ์ง์ ‘ ๋‹ค๋ฃจ๋Š” ๋ฐฉ์‹์ด์—ˆ์ง€๋งŒ ์ด๋ฒˆ ๋ฆด๋ฆฌ์ฆˆ๋ถ€ํ„ฐ ๊ณต์‹ OpenAI Java SDK์™€์˜ ๋„ค์ดํ‹ฐ๋ธŒ ํ†ตํ•ฉ์„ ์ œ๊ณตํ•œ๋‹ค.
  • ๊ฐœ๋ฐœ์ž์ด์ : ๊ณต์‹ SDK๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ํƒ€์ž… ์•ˆ์ •์„ฑ์ด ๋Œ€ํญ ๊ฐœ์„ ๋˜์—ˆ๋‹ค. API์‘๋‹ต๊ณผ ์š”์ฒญ ๊ฐ์ฒด๋ฅผ ๋” ์•ˆ์ „ํ•˜๊ฒŒ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ณต์‹ SDK๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ๋น ์ง์—†์ด ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์–ด ๊ฐœ๋ฐœ ํŽธ์˜์„ฑ์ด ๊ทน๋Œ€ํ™”๋  ์ „๋ง์ด๋‹ค.

ChatClient์˜ ๊ตฌ์กฐํ™” ์ถœ๋ ฅ (Structured Output) ๊ธฐ๋ณธ ์ง€์› ๊ฐ•ํ™”

LLM์„ ์‚ฌ์šฉํ•  ๋•Œ ๊ฐ€์žฅ ๊นŒ๋‹ค๋กœ์šด ๋ถ€๋ถ„ ์ค‘ ํ•˜๋‚˜๋Š” ํ…์ŠคํŠธ๋กœ ๋ฐ›์€ ์‘๋‹ต์„ ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ์ •ํ˜•ํ™”๋œ ๋ฐ์ดํ„ฐ(ex. JSON)๋กœ ์ •ํ™•ํ•˜๊ฒŒ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

  • ๋ณ€๊ฒฝ๋‚ด์šฉ: ChatClient๊ฐ€ ๊ตฌ์กฐํ™” ์ถœ๋ ฅ(Structured Output)์„ ๋”์šฑ ์•ˆ์ •์ ์œผ๋กœ ์ง€์›ํ•˜๋„๋ก ๊ฐœ์„ ๋˜์—ˆ๋‹ค.
  • ๊ฐœ๋ฐœ์ž์ด์ : AI ๋ชจ๋ธ๋กœ๋ถ€ํ„ฐ JSON์ด๋‚˜ YAML๊ฐ™์€ ๊ตฌ์กฐํ™”๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•  ๋•Œ ์˜ค๋ฅ˜ ์—†์ด ์‹ ๋ขฐ์„ฑ ์žˆ๊ฒŒ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋Š” ํŠนํžˆ ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—์„œ AI์‘๋‹ต์„ DTO๋‚˜ ์—”ํ‹ฐํ‹ฐ๋กœ ๋ณ€ํ™˜ํ•ด์•ผ ํ•  ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ํŒŒ์‹ฑ ์˜ค๋ฅ˜๋ฅผ ์ค„์—ฌ์ฃผ์–ด ๊ฐœ๋ฐœ ์ƒ์ƒ์„ฑ์„ ํฌ๊ฒŒ ๋†’์—ฌ์ค€๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ChatClient๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ณต์žกํ•œ ํ”„๋กฌํ”„ํŠธ ์—”์ง€๋‹ˆ์–ด๋ง ์—†์ด๋„ Java record ํƒ€์ž…์ด๋‚˜ ํด๋ž˜์Šค๋กœ ๊น”๋”ํ•˜๊ฒŒ ๋งคํ•‘๋œ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

// 1. ์›ํ•˜๋Š” ์‘๋‹ต ๊ตฌ์กฐ๋ฅผ Record๋กœ ์ •์˜
public record ActorFilms(String actor, List<String> movies) {}
Java

๋‹ค์Œ์€ ChatClient API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ BeanOutputConverter๋ฅผ ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

ActorsFilms actorsFilms = ChatClient.create(chatModel).prompt()
        .user(u -> u.text("Generate the filmography of 5 movies for {actor}.")
                    .param("actor", "Tom Hanks"))
        .call()
        .entity(ActorsFilms.class);  //์ž๋™ ๋งคํ•‘ ์ง€์›
Java

ํ˜น์€ ๋กœ์šฐ ๋ ˆ๋ฒจ์˜ ChatModel API๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

BeanOutputConverter<ActorsFilms> beanOutputConverter =
    new BeanOutputConverter<>(ActorsFilms.class);

String format = this.beanOutputConverter.getFormat();

String actor = "Tom Hanks";

String template = """
        Generate the filmography of 5 movies for {actor}.
        {format}
        """;

Generation generation = chatModel.call(
    PromptTemplate.builder().template(this.template).variables(Map.of("actor", this.actor, "format", this.format)).build().create()).getResult();

ActorsFilms actorsFilms = this.beanOutputConverter.convert(this.generation.getOutput().getText());
Java

์ตœ์‹  LLM ๋ชจ๋ธ ๋ฐ ์ธํ”„๋ผ ๊ธฐ๋Šฅ ์ง€์› ํ™•๋Œ€

Spring AI๋Š” ํŠน์ • ๋ชจ๋ธ์— ์ข…์†๋˜์ง€ ์•Š๊ณ  ๋‹ค์–‘ํ•œ ํด๋ผ์šฐ๋“œ์™€ LLM์„ ์ง€์›ํ•˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•œ๋‹ค. ์ด๋ฒˆ ์—…๋ฐ์ดํŠธ ์—์„œ๋Š” ํ•ต์‹ฌ ์ œ๊ณต์—…์ฒด๋“ค์˜ ์ตœ์‹  ๊ธฐ๋Šฅ์ด ๋น ๋ฅด๊ฒŒ ๋ฐ˜์˜๋˜์—ˆ๋‹ค.

  • Claude ๊ฐ•ํ™”: Claude 4.5 ๋ชจ๋ธ(Opus, Haiku)์ง€์›์ด ์ถ”๊ฐ€๋˜์—ˆ์œผ๋ฉฐ, Skills API ๋ฐ Files API ํ†ตํ•ฉ์ด ์ ์šฉ๋˜์–ด ํŒŒ์ผ์„ ํ™œ์šฉํ•œ ๊ณ ๊ธ‰ ์ƒํ˜ธ์ž‘์šฉ์ด ๊ฐ€๋Šฅํ•ด์กŒ๋‹ค.
  • Google Gemini ๊ฐœ์„ : ThinkingLevel ์„ค์ • ์ง€์›์ด ์ถ”๊ฐ€๋˜์–ด ์ถ”๋ก  ๊ณผ์ •์— ๋Œ€ํ•œ ๋” ์„ธ๋ฐ€ํ•œ ์ œ์–ด๊ฐ€ ๊ฐ€๋Šฅํ•ด ์กŒ๊ณ  Vertex Gemini ํ†ตํ•ฉ ์‹œ ์‘๋‹ต ๋ฐ์ดํ„ฐ์— Safety Ratings(์•ˆ์ „ ๋“ฑ๊ธ‰) ์ •๋ณด๊ฐ€ ํฌํ•จ๋˜์–ด ์šด์˜ ์•ˆ์ •์„ฑ์ด ๋†’์•„์กŒ๋‹ค.
  • RAG ์ธํ”„๋ผ ๊ฐœ์„ : ๋ฒกํ„ฐ ์Šคํ† ์–ด ํ•„๋“œ์— ‘ISNULL’, ‘ISNOTNULL’ ํ‘œํ˜„์‹์ด ์ถ”๊ฐ€๋˜์–ด RAG(๊ฒ€์ƒ‰ ์ฆ๊ฐ• ์ƒ์„ฑ) ๊ตฌ์„ฑ ์‹œ ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง์˜ ์œ ์—ฐ์„ฑ์ด ํ–ฅ์ƒ๋˜์—ˆ๋‹ค.

์ด ์—…๋ฐ์ดํŠธ๋ฅผ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•œ๋‹ค๋ฉด?

Spring AI 1.1.1์„ ๋„์ž…ํ–ˆ์„ ๋•Œ ์‹ค์งˆ์ ์ธ ์ด์ ์€ ๋ฐ”๋กœ ๊ฐœ๋ฐœ ์ƒ์„ฑ์„ฑ๊ณผ ์‹œ์Šคํ…œ ์•ˆ์ •์„ฑ์˜ ๋™์‹œ ํ™•๋ณด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ฒ„๊ทธ ์—†๋Š” AI ์‘๋‹ต ํŒŒ์‹ฑ ํ™˜๊ฒฝ ๊ตฌ์ถ•

๊ฐ€์žฅ ํฐ ์‹ค๋ฌด์  ์ด๋“์€ ๊ตฌ์กฐํ™” ์ถœ๋ ฅ์˜ ์‹ ๋ขฐ์„ฑ ํ–ฅ์ƒ์ด๋‹ค.
์ด์ „์—๋Š” LLM์ด JSON์„ ์™„๋ฒฝํ•˜๊ฒŒ ์ƒ์„ฑํ•˜์ง€ ๋ชปํ•  ๊ฒฝ์šฐ, ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํ›„์ฒ˜๋ฆฌ ๋กœ์ง์ด๋‚˜ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ 1.1.1 ๋ฒ„์ „์—์„œ ๊ตฌ์กฐํ™” ์ถœ๋ ฅ์ด ๊ฐ•ํ™”๋˜๋ฉด์„œ ๊ฐœ๋ฐœ์ž๋Š” LLM์˜ ์‘๋‹ต์„ ์‹ ๋ขฐํ•˜๊ณ  Java ๊ฐ์ฒด๋กœ ๋ฐ”๋กœ ๋งคํ•‘ํ•˜์—ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค. ์ด๋Š” ์ฝ”๋”ฉ๋Ÿ‰ ๊ฐ์†Œ์™€ ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ ์ฆ๋Œ€๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค.

์—”ํ„ฐํ”„๋ผ์ด์ฆˆ ํ™˜๊ฒฝ์—์„œ์˜ ์•ˆ์ •์„ฑ ํ™•๋ณด

์ด๋ฒˆ ๋ฆด๋ฆฌ์ฆˆ์—๋Š” ์ค‘์š”ํ•œ ๋ณด์•ˆํŒจ์น˜๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋‹ค. ํŠนํžˆ Apache Commons ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ CVE ์ทจ์•ฝ์ (๋ฌดํ•œ๋ฃจํ”„, ์ž์›ํ• ๋‹น๋“ฑ)์— ๋Œ€ํ•œ ์—…๋ฐ์ดํŠธ๋Š” ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ ํ™˜๊ฒฝ์—์„œ ํ•„์ˆ˜์ ์ธ ์กฐ์น˜๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค.
๋˜ํ•œ Azure Cosmos DB Chat Memory ์ž๋™ ๊ตฌ์„ฑ ์ง€์›์ด ์ถ”๊ฐ€๋˜์–ด ์ด๋ฏธ Azure ํ™˜๊ฒฝ์„ ์‚ฌ์šฉ ์ค‘์ธ ๊ธฐ์—…์€ ์„ธ์…˜ ๋ฉ”๋ชจ๋ฆฌ ๊ธฐ๋Šฅ์„ ๋ณ„๋„์˜ ์„ค์ • ์—†์ด Spring Boot Starter๋ฅผ ํ†ตํ•ด ์‰ฝ๊ฒŒ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋Š” ํด๋ผ์šฐ๋“œ ์ธํ”„๋ผ ํ™œ์šฉ๋„๋ฅผ ๋†’์—ฌ์ค€๋‹ค.

๋ฏธ๋ž˜ ์ง€ํ–ฅ์ ์ธ ์•„ํ‚คํ…์ฒ˜ ์ค€๋น„ (2.0.0 ๋Œ€๋น„)

Spring AIํŒ€์€ ๋‹ค์Œ ์ค‘์— Spring AI 2.0.0-M1 ๋ฆด๋ฆฌ์ฆˆ๋ฅผ ์˜ˆ๊ณ ํ–ˆ๋‹ค. 1.1.1์€ 1.X ๋ผ์ธ์˜ ์•ˆ์ •ํ™”์™€ ๊ธฐ๋Šฅ ํ™•์žฅ ๋งˆ๋ฌด๋ฆฌ ๋‹จ๊ณ„๋กœ ๋ณผ ์ˆ˜ ์žˆ์œผ๋ฉฐ ์ด ๋ฒ„์ „์„ ์‚ฌ์šฉํ•ด ์ฃผ์š” ๊ธฐ๋Šฅ์„ ๋ฏธ๋ฆฌ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์•ˆ์ •ํ™”ํ•˜๋Š” ๊ฒƒ์€ ๊ณง 2.0.0 ๋ฒ„์ „์œผ๋กœ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์œ„ํ•œ ํ›Œ๋ฅญํ•œ ๋””๋”ค๋Œ์ด ๋  ๊ฒƒ์ด๋‹ค.


Spring AI 1.1.1 ๋ฆด๋ฆฌ์ฆˆ๋Š” LLM์„ ํ™œ์šฉํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์ด ์ ์  ์„ฑ์ˆ™ํ•ด์ง€๊ณ  ์žˆ๋‹ค๋Š” ์ฆ๊ฑฐ๋‹ค. LLM ์ œ๊ณต์‚ฌ(OpenAI, Anthropi, Google)์˜ ์ตœ์‹  ๊ธฐ์ˆ ์„ ๋น ๋ฅด๊ฒŒ ์ˆ˜์šฉํ•˜๊ณ  ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ฐ€์žฅ ๋ถˆํŽธํ–ˆ๋˜ ํƒ€์ž… ์•ˆ์ •์„ฑ๊ณผ ๊ตฌ์กฐํ™” ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜ํ–ˆ๋‹ค.
AI ๊ธฐ๋Šฅ์„ ๋„์ž…ํ•˜๋ ค๋Š” ๋ชจ๋“  Spring Boot ๊ฐœ๋ฐœ์ž ๋ถ„๋“ค์€ ์ด๋ฒˆ 1.1.1 ๋ฒ„์ „์œผ๋กœ ์—…๋ฐ์ดํŠธํ•˜์—ฌ ๋”์šฑ ๊ฐ•๋ ฅํ•˜๊ณ  ์•ˆ์ •์ ์ธ AI ๋ฐฑ์—”๋“œ ์„œ๋น„์Šค๋ฅผ ๊ตฌ์ถ•ํ•˜๊ธฐ ๋ฐ”๋ž€๋‹ค.


์ฐธ๊ณ 

https://spring.io/blog/2025/12/05/spring-ai-1-1-1-available-now