Koog vs Spring AI — JVM 위에서 AI 에이전트를 만드는 두 가지 길
JVM 진영에서 AI 에이전트를 진지하게 만들려고 하면 곧 두 이름과 마주칩니다. JetBrains가 만든 Koog와 Spring 팀이 만든 Spring AI입니다. 둘 다 "JVM에서 LLM을 잘 다루자"는 목표를 공유하지만, 출발점과 강조점이 매우 다릅니다. 이 글은 두 프레임워크의 핵심 추상화, 코드 스타일, 운영 기능, 적합한 사용처를 같은 다이어그램 위에서 비교해, 팀이 어떤 도구를 어떤 상황에서 선택해야 하는지 판단할 수 있도록 정리합니다.
두 프레임워크가 풀려는 문제
LLM 자체는 텍스트 입출력을 받는 함수에 가깝습니다. 그러나 운영 환경에서 "AI 기능"을 의미 있게 만들려면 다음과 같은 부수 작업이 따라옵니다.
- 여러 모델 제공자(OpenAI, Anthropic, Google, Bedrock, Ollama 등) 사이 전환
- 외부 데이터 소스 검색 후 프롬프트에 주입(RAG)
- 모델이 직접 호출할 수 있는 도구(tool) 등록
- 대화 메모리 관리와 토큰 한도 안에서의 압축
- 재시도, 영속화, 관측, 비용 추적
- 외부 도구 서버와의 표준화된 연결(MCP)
이 공통 영역을 어떻게 추상화하느냐에 따라 프레임워크의 성격이 결정됩니다. Koog는 그래프 기반 에이전트 런타임으로, Spring AI는 Spring 생태계에 녹아든 모델 추상화 + Advisor 체인으로 같은 문제를 풀어냅니다.
flowchart LR
A[LLM Provider APIs] --> K[Koog]
A --> S[Spring AI]
K --> KAgent[AIAgent + Strategy Graph]
S --> SClient[ChatClient + Advisors]
KAgent --> Multi[JVM / Android / iOS / JS]
SClient --> Boot[Spring Boot Application]
Koog — JetBrains가 만든 멀티플랫폼 에이전트 프레임워크
Koog는 JetBrains가 자체 AI 제품(JetBrains AI Assistant 등)을 개발하면서 축적한 노하우를 오픈소스화한 프레임워크입니다. 가장 큰 특징은 두 가지입니다.
- Kotlin DSL로 그래프 기반 에이전트를 선언합니다. 노드와 엣지를 정의해 LLM 호출, 도구 실행, 결과 라우팅을 명시적으로 그립니다.
- Kotlin Multiplatform을 기반으로 동일 코드를 JVM, Android, iOS, JS, WasmJS에서 실행할 수 있습니다.
의존성은 다음과 같이 추가합니다.
dependencies {
implementation("ai.koog:koog-agents:0.7.3")
}
요구사항은 JDK 17 이상, Kotlin 2.3.10 이상입니다.
가장 단순한 형태 — Single-run agent
가장 간단한 사용은 그래프 없이 한 번 호출하는 형태입니다.
fun main() = runBlocking {
val apiKey = System.getenv("OPENAI_API_KEY")
val agent = AIAgent(
promptExecutor = simpleOpenAIExecutor(apiKey),
systemPrompt = "You are a helpful assistant.",
llmModel = OpenAIModels.Chat.GPT4o
)
val result = agent.run("Hello! How can you help me?")
println(result)
}
promptExecutor가 모델 호출 채널을, llmModel이 어떤 모델을 쓸지를 정합니다. Koog는 OpenAI, Anthropic, Google, DeepSeek, OpenRouter, Ollama, Bedrock을 공식 지원합니다.
핵심 추상화 — Strategy / Subgraph / Node / Edge
복잡한 워크플로우를 만들 때 Koog의 진가가 드러납니다. 에이전트의 동작은 strategy { } DSL 안에서 노드와 엣지로 명시됩니다.
val calculatorAgentStrategy = strategy<String, String>("Simple calculator") {
val nodeSendInput by nodeLLMRequest()
val nodeExecuteTool by nodeExecuteTool()
val nodeSendToolResult by nodeLLMSendToolResult()
edge(nodeStart forwardTo nodeSendInput)
edge(nodeSendInput forwardTo nodeFinish onAssistantMessage { true })
edge(nodeSendInput forwardTo nodeExecuteTool onToolCall { true })
edge(nodeExecuteTool forwardTo nodeSendToolResult)
edge(nodeSendToolResult forwardTo nodeFinish onAssistantMessage { true })
edge(nodeSendToolResult forwardTo nodeExecuteTool onToolCall { true })
}
여기서 사용된 개념을 정리하면 이렇습니다.
- Strategy: 그래프의 최상위 컨테이너. 입력·출력 타입을 가집니다.
- Subgraph: 자체 도구 집합과 컨텍스트를 가질 수 있는 그래프 일부분.
- Node: 한 단위 연산.
nodeLLMRequest,nodeExecuteTool같은 빌트인 노드가 제공되며 사용자 정의도 가능합니다. - Edge: 노드 간 전이.
forwardTo인픽스 함수와onAssistantMessage { ... }같은 조건 람다로 분기를 표현합니다.
이 그래프는 ReAct 형태의 "LLM 호출 → 도구 호출 여부 판단 → 도구 실행 → 결과를 다시 LLM에" 루프를 그대로 코드로 옮긴 것입니다.
flowchart LR
Start[nodeStart] --> Send[nodeSendInput]
Send -->|onAssistantMessage| End[nodeFinish]
Send -->|onToolCall| Exec[nodeExecuteTool]
Exec --> Result[nodeSendToolResult]
Result -->|onAssistantMessage| End
Result -->|onToolCall| Exec
서브그래프로 책임을 분리할 수도 있습니다. 다음 예시는 웹 검색 도구만 가진 리서치 서브그래프입니다.
val researchSubgraph by subgraph<String, String>(
"research_subgraph",
tools = listOf(WebSearchTool())
) {
val nodeCallLLM by nodeLLMRequest("call_llm")
val nodeExecuteTool by nodeExecuteTool()
val nodeSendToolResult by nodeLLMSendToolResult()
edge(nodeStart forwardTo nodeCallLLM)
edge(nodeCallLLM forwardTo nodeExecuteTool onToolCall { true })
edge(nodeExecuteTool forwardTo nodeSendToolResult)
edge(nodeSendToolResult forwardTo nodeExecuteTool onToolCall { true })
edge(nodeCallLLM forwardTo nodeFinish onAssistantMessage { true })
}
운영 친화 기능
Koog 공식 문서가 강조하는 운영 기능은 다음과 같습니다.
- 재시도와 에이전트 영속화(persistence): 실행 도중 실패해도 특정 지점에서 상태를 복원할 수 있습니다.
- 이력 압축(history compression): 장기 대화에서 토큰 사용량을 줄이기 위한 빌트인 압축 전략을 제공합니다.
- OpenTelemetry 익스포터: W&B Weave, Langfuse 같은 관측 플랫폼으로 트레이스를 보낼 수 있습니다.
- MCP / ACP: Model Context Protocol과 Agent Client Protocol 통합을 기본 제공합니다.
- 모델 전환: 대화 이력을 잃지 않고 다른 LLM으로 갈아탈 수 있습니다.
Spring 생태계와의 통합도 별도 모듈로 제공됩니다.
koog-spring-boot-starter: Spring Boot 애플리케이션에 Koog 에이전트를 임베드koog-ktor: Ktor 통합koog-spring-ai: Spring AI 추상화 위에 Koog를 얹는 어댑터
즉 "Spring Boot 위에서 Koog를 쓴다"는 시나리오도 1급 시민입니다.
Spring AI — Spring 생태계의 AI 통합 레이어
Spring AI는 Spring Framework가 늘 해온 일을 AI 영역에서 그대로 합니다. 다양한 공급자(OpenAI, Anthropic, Microsoft, Amazon, Google, Ollama 등)와 다양한 벡터 데이터베이스(PGVector, Redis, Qdrant, Chroma, Pinecone, Weaviate 등)를 포터블한 추상화 뒤로 숨기고, Spring Boot 자동 설정으로 한 줄에 가까운 사용 경험을 제공합니다.
2026년 5월 시점 기준 릴리스 라인은 다음과 같습니다(Spring AI 공식 블로그 기준).
| 라인 | 호환 Spring Boot | 비고 |
|---|---|---|
| 1.0.x | Spring Boot 3.4.x | 2025년 5월 GA |
| 1.1.x | Spring Boot 3.5.x | 2025년 11월 GA |
| 2.0.x | Spring Boot 4.x | M 시리즈 진행 중 |
핵심 추상화 — ChatClient / ChatModel / Advisor
Spring AI에서 LLM과의 상호작용은 ChatClient라는 fluent API를 통해 이뤄집니다. WebClient/RestClient와 의도적으로 닮은 모양입니다.
String answer = ChatClient.builder(chatModel)
.build()
.prompt()
.user("Spring AI로 RAG는 어떻게 만드나요?")
.call()
.content();
ChatClient는 내부적으로 ChatModel 위에서 동작하며, 그 사이를 Advisor 체인이 감쌉니다. Advisor는 요청과 응답을 가로채 변형하는 인터셉터로, RAG·메모리·로깅 같은 패턴을 모듈화하기 위한 핵심 도구입니다.
flowchart LR
User[User Prompt] --> Client[ChatClient]
Client --> A1[Memory Advisor]
A1 --> A2[QuestionAnswerAdvisor]
A2 --> Model[ChatModel]
Model --> A2
A2 --> A1
A1 --> Client
Client --> Out[Response]
A2 -.->|retrieve| VS[(VectorStore)]
A1 -.->|read/write| Mem[(ChatMemory)]
RAG는 Advisor 한 줄
Spring AI의 RAG 구현은 QuestionAnswerAdvisor를 ChatClient에 끼우는 한 줄로 시작합니다.
ChatResponse response = ChatClient.builder(chatModel)
.build().prompt()
.advisors(QuestionAnswerAdvisor.builder(vectorStore).build())
.user(userText)
.call()
.chatResponse();
기본 advisor는 다음을 수행합니다. 사용자 메시지로 벡터 스토어에서 유사 문서를 검색하고, 결과를 시스템 프롬프트에 컨텍스트로 주입합니다. 검색 조건은 SearchRequest로 제어합니다.
var qaAdvisor = QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(SearchRequest.builder()
.similarityThreshold(0.8d)
.topK(6)
.build())
.build();
대화 메모리도 advisor로 표현됩니다. 빌드 시점에 디폴트 advisor를 묶어 두면 호출마다 같은 동작이 적용됩니다.
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(),
QuestionAnswerAdvisor.builder(vectorStore).build()
)
.build();
런타임에 검색 필터를 바꾸고 싶으면 advisor 컨텍스트 파라미터를 지정합니다.
String content = chatClient.prompt()
.user("Please answer my question XYZ")
.advisors(a -> a.param(QuestionAnswerAdvisor.FILTER_EXPRESSION, "type == 'Spring'"))
.call()
.content();
Tool Calling과 MCP
Spring AI는 Tool Calling을 1급 기능으로 제공하며, 2025년 9월에 발표된 MCP Boot Starter를 통해 Model Context Protocol 양쪽(클라이언트·서버)에 모두 참여할 수 있습니다.
- MCP Client Boot Starter: 외부 MCP 서버를 도구로 사용
- MCP Server: Spring 빈을 MCP 서버로 노출 (STDIO / Spring MVC SSE / Spring WebFlux SSE 세 가지 전송 지원)
이는 "기존 Spring 빈으로 노출되어 있던 비즈니스 로직을 그대로 AI 에이전트의 도구로 공개"하는 시나리오를 자연스럽게 만듭니다.
그 밖의 주요 능력
- Vector Store 추상화: PGVector, Redis, Qdrant, Chroma, Pinecone, Weaviate, Neo4j, Milvus, MongoDB Atlas, Cassandra, Oracle, Azure Vector Search 등 약 20종을 포터블한 인터페이스로 사용
- ETL 프레임워크: 로컬 파일, 웹 페이지, GitHub, S3/GCS/Azure Blob, Kafka, MongoDB, JDBC 등에서 데이터를 읽어 임베딩 후 벡터 스토어로 적재
- 구조화 출력: 모델 응답을 POJO로 매핑
- Embedding / Image / Audio / Moderation 모델: ChatModel 외에도 다양한 모달리티
두 프레임워크의 본질적 차이
같은 결과를 만들 수 있다고 해서 둘이 같은 도구는 아닙니다. 정렬해서 보면 다음과 같습니다.
| 관점 | Koog | Spring AI |
|---|---|---|
| 만든 곳 | JetBrains | Spring (VMware Tanzu) |
| 1급 언어 | Kotlin (Java API도 제공) | Java (Kotlin 사용 가능) |
| 핵심 추상화 | AIAgent + Strategy graph (Node/Edge) | ChatClient + Advisor 체인 |
| 워크플로우 표현 | 명시적 그래프 DSL | fluent API + 인터셉터 체인 |
| 플랫폼 | JVM, Android, iOS, JS, WasmJS | JVM(Spring Boot) |
| 상태 관리 | Agent persistence, history compression | ChatMemory advisor |
| 도구 표준 | MCP + ACP 빌트인 | MCP Boot Starter (client/server) |
| 관측 | OpenTelemetry → Weave/Langfuse | Spring Boot Actuator + Micrometer |
| Spring Boot 통합 | koog-spring-boot-starter 어댑터 |
자동 설정이 곧 디폴트 |
| 적합 시나리오 | 그래프가 자연스러운 복잡 에이전트, 멀티플랫폼 클라이언트 | Spring Boot 백엔드 안의 AI 기능 |
차이의 뿌리는 세계관에 있습니다.
- Koog는 "에이전트는 상태 머신이다"라고 봅니다. 노드와 엣지로 흐름을 명시하고, 그 위에 영속화와 관측을 얹습니다. Kotlin Multiplatform 덕에 동일 에이전트가 모바일과 데스크톱, 백엔드를 가로지를 수 있다는 것이 큰 차별점입니다.
- Spring AI는 "에이전트도 결국 Spring 빈이다"라고 봅니다. ChatClient 호출을 인터셉터(Advisor) 체인으로 둘러싸고, 모든 외부 자원(VectorStore, ChatMemory, Tool, MCP)도 빈으로 표현합니다. 결과적으로 기존 Spring Boot 애플리케이션에 가장 적은 마찰로 녹아듭니다.
같은 RAG 루프를 그려 보면
같은 RAG 루프를 두 프레임워크가 어떻게 표현하는지 나란히 보면 차이가 분명해집니다.
flowchart TB
subgraph K[Koog: explicit graph]
K1[nodeStart] --> K2[nodeRetrieve]
K2 --> K3[nodeLLMRequest]
K3 -->|onToolCall| K4[nodeExecuteTool]
K3 -->|onAssistantMessage| K5[nodeFinish]
K4 --> K6[nodeSendToolResult]
K6 --> K3
end
subgraph S[Spring AI: advisor chain]
S1[ChatClient.prompt] --> S2[MessageChatMemoryAdvisor]
S2 --> S3[QuestionAnswerAdvisor]
S3 --> S4[ChatModel]
S4 --> S3
S3 --> S2
S2 --> S1
end
같은 흐름이지만 표현 방식이 다릅니다. Koog는 "흐름"을 일급 시민으로, Spring AI는 "인터셉터"를 일급 시민으로 다룹니다.
어떤 상황에 어느 쪽을 골라야 할까
선택 기준을 정리하면 다음과 같습니다.
Koog가 더 자연스러운 경우
- 팀이 Kotlin을 주력으로 사용하거나, 적어도 코드 베이스에 Kotlin이 깔려 있다.
- 에이전트의 상태 전이가 복잡하고, 분기·루프·서브그래프를 코드로 또렷하게 그리고 싶다.
- 모바일(Android/iOS)이나 데스크톱 같은 비-서버 환경에서 동일 로직을 돌려야 한다. 예를 들어 IDE 플러그인이나 Compose Multiplatform 앱.
- 영속화와 시간 여행(time travel) 같은 에이전트 운영 기능을 빌트인으로 쓰고 싶다.
Spring AI가 더 자연스러운 경우
- 이미 Spring Boot로 구축된 백엔드에 AI 기능을 얹는다.
- 팀이 Java 위주이고, 의존성 주입과 자동 설정 패러다임을 그대로 쓰고 싶다.
- 다양한 벡터 스토어 후보를 비교하면서 갈아끼우거나, 운영 관점의 ETL 파이프라인을 표준화하고 싶다.
- MCP 서버를 Spring 빈으로 노출해 기존 비즈니스 로직을 에이전트 친화적으로 공개하고 싶다.
둘 다 쓸 수도 있다
koog-spring-ai 어댑터가 존재하므로, Spring AI의 ChatModel 추상화 위에 Koog 에이전트를 얹어 양쪽의 장점을 결합하는 구성도 가능합니다. Spring Boot로 구성한 인프라(빈, 설정, 관측) 위에서 Koog의 그래프 기반 워크플로우를 운영하는 형태입니다.
도구 등록 방식 비교
도구(tool) 등록 방식은 두 프레임워크의 디자인 차이가 가장 잘 드러나는 지점 중 하나입니다.
Koog의 도구
Koog에서는 도구를 명시적으로 정의하고 strategy/subgraph에 연결합니다. 서브그래프 단위로 사용 가능한 도구 집합을 좁힐 수 있어, "리서치 단계에서는 웹 검색만, 실행 단계에서는 DB 호출만" 같은 구획화가 자연스럽습니다.
val researchSubgraph by subgraph<String, String>(
"research",
tools = listOf(WebSearchTool())
) { /* nodes and edges */ }
val actionSubgraph by subgraph<String, String>(
"action",
tools = listOf(DbWriteTool(), NotifyTool())
) { /* nodes and edges */ }
도구 호출 자체는 nodeExecuteTool 빌트인 노드가 처리하며, 결과는 nodeLLMSendToolResult로 다시 LLM에 돌려보냅니다. 어떤 도구가 어떤 단계에서 노출되는지가 그래프 위에 명시됩니다.
Spring AI의 도구
Spring AI는 도구를 평범한 Spring 빈 메서드로 표현하는 쪽을 선호합니다. ChatClient 호출에 도구를 등록하면, 모델이 호출을 요청했을 때 프레임워크가 메서드를 찾아 실행합니다.
String answer = chatClient.prompt()
.user("서울 날씨와 다음 미팅 일정을 알려줘")
.tools(weatherService, calendarService)
.call()
.content();
내부적으로 도구 메서드의 시그니처(파라미터 이름, 타입, 설명)를 모델이 호출 가능한 함수 정의(JSON 스키마)로 자동 변환합니다. 즉 "기존 비즈니스 로직 빈에 어노테이션을 붙이면 곧 LLM 도구가 된다"는 흐름입니다. 더 나아가 MCP Server Boot Starter를 쓰면 같은 빈을 외부 에이전트가 사용할 수 있는 표준 도구 서버로도 노출할 수 있습니다.
요약하면 Koog는 "그래프의 어느 영역에서 어떤 도구를 쓸지"가 일급 시민이고, Spring AI는 "도구도 다른 빈과 마찬가지"라는 입장입니다.
실전에서 마주칠 만한 함정
이론적인 차이는 위에서 정리했지만, 실제로 두 프레임워크를 도입할 때 부딪히는 함정도 다릅니다.
Koog 쪽 함정
- 그래프 과설계: 단순 한 번 호출이면 충분한 작업을 strategy 그래프로 짜다 보면 보일러플레이트가 늘어납니다. 단발성 호출은 single-run AIAgent로 충분합니다.
- Multiplatform 빌드 셋업: JVM만 쓸 거라면
koog-agents-jvm만 import해도 되지만, 진짜 멀티플랫폼을 노린다면 Kotlin Multiplatform 빌드 설정과 expect/actual 클래스 경계를 이해해야 합니다. - Kotlin 버전 요구사항: JDK 17+, Kotlin 2.3.10+가 필요합니다. 레거시 자바 8 환경에는 들어가지 못합니다.
Spring AI 쪽 함정
- 버전 라인 혼란: 2026년 5월 현재 1.0.x(Spring Boot 3.4), 1.1.x(Spring Boot 3.5), 2.0.x(Spring Boot 4.x M 시리즈)가 동시에 살아 있습니다. 사용 중인 Spring Boot 버전과 Spring AI 라인이 맞아야 하므로 의존성 BOM을 반드시 확인해야 합니다.
- Advisor 순서 의존성: 메모리 advisor와 RAG advisor를 같이 쓸 때 둘의 순서에 따라 "메모리에 들어가는 텍스트가 검색 결과를 포함하느냐"가 달라집니다. 의도한 메모리 상태가 만들어지는지 확인이 필요합니다.
- VectorStore 일관성 환상: 추상화는 같아도 각 백엔드의 필터 표현 능력, 메타데이터 인덱싱 정책, 거리 함수가 다릅니다. 한 곳에서 잘 돌아가던 검색 품질이 다른 곳에서 그대로 재현되리라 가정하면 안 됩니다.
- Tool 메서드 시그니처: 도구 메서드는 모델에게 노출되는 인터페이스이기도 합니다. 파라미터 이름과 설명이 모델이 잘못 호출하지 않도록 명확해야 합니다.
정리
- Koog는 JetBrains가 만든 멀티플랫폼 에이전트 프레임워크입니다. Kotlin DSL로 그래프 기반 에이전트를 또렷하게 표현하며, JVM뿐 아니라 Android/iOS/JS/WasmJS까지 같은 코드를 옮길 수 있다는 점이 가장 큰 차별점입니다.
- Spring AI는 Spring 생태계가 만든 AI 통합 레이어입니다. ChatClient + Advisor 체인을 중심으로 RAG, 메모리, 도구, MCP를 인터셉터처럼 끼워 넣고, Spring Boot 자동 설정으로 가장 짧은 거리로 백엔드와 통합됩니다.
- 두 프레임워크는 경쟁자라기보다 다른 사용 시나리오에 최적화된 도구입니다. 그래프가 자연스러운 복잡 에이전트와 멀티플랫폼 클라이언트는 Koog, Spring Boot 백엔드 안의 AI 기능과 표준 ETL/Vector Store 통합은 Spring AI가 더 적합합니다.
선택의 기준은 결국 코드 베이스가 어디에 있느냐입니다. 그 위에서 Koog와 Spring AI는 서로 다른 방식으로 같은 문제를 풀고 있을 뿐입니다.
참고자료
- Koog 공식 문서: https://docs.koog.ai/
- Koog GitHub: https://github.com/JetBrains/koog
- Koog 소개 페이지: https://www.jetbrains.com/koog/
- Koog 그래프 기반 에이전트 가이드: https://docs.koog.ai/agents/graph-based-agents/
- Koog 커스텀 strategy 그래프: https://docs.koog.ai/custom-strategy-graphs/
- Koog 커스텀 서브그래프: https://docs.koog.ai/custom-subgraphs/
- Spring AI 공식 문서: https://docs.spring.io/spring-ai/reference/index.html
- Spring AI 프로젝트 페이지: https://spring.io/projects/spring-ai/
- Spring AI ChatClient API: https://docs.spring.io/spring-ai/reference/api/chatclient.html
- Spring AI Advisors API: https://docs.spring.io/spring-ai/reference/api/advisors.html
- Spring AI RAG 가이드: https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html
- Spring AI MCP 개요: https://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html
- Spring AI 1.0 GA 발표: https://spring.io/blog/2025/05/20/spring-ai-1-0-GA-released/
- Spring AI 1.1 GA 발표: https://spring.io/blog/2025/11/12/spring-ai-1-1-GA-released/
- Spring AI 1.0.7 / 1.1.6 / 2.0.0-M6 릴리스: https://spring.io/blog/2026/05/08/spring-ai-1-0-7-1-1-6-2-0-0-M6-available-now/

