본문 바로가기
카테고리 없음

Embabel을 사용하여 AI Agent 만들기

by igooo 2025. 10. 4.
728x90

개요

Embabel은 JVM 환경에서 동작하는 AI Agent 프레임웍으로, LLM 기반의 상호작용을 기존의 코드 및 도메인 모델과 자연스럽게 결합하는데 초점을 두고 개발된 프레임워크다. 즉 Java, Kotlin 프로젝트 환경에서 AI Agent를 기존 프로젝트와 쉽게 통합하여 개발할 수 있게 해 준다.

핵심적인 특징은 사용자가 요청한 목표를 달성하기 위해 에이전트가 스스로 계획을 세우고 행동한다. 또한 AI 기반의 플래너가 상황에 따라 최적의 행동 순서를 결정하여 사용자의 요청을 처리한다. 또한 Kotlin으로 개발되어 Java, Kotlin으로 개발된 많은 시스템에 통합이 쉽다.

 

더 자세한 설명은 Rod Johnson이 작성한 블로그를 참고하자.

https://medium.com/@springrod/embabel-a-new-agent-platform-for-the-jvm-1c83402e0014

 

Emababel

구성요소

  • Action: 에이전트가 수행할 수 있는 작업
  • Goals: 달성하고자 하는 목표
  • Conditions: 작업 수행 전후의 조건
  • Plain: 목표 달성을 위한 작업 순서 (자동으로 생성됨)

 

Embabel와 Spring

AI를 개발하기 전 Gradle의 Dependencies를 확인해 보면 Spring Boot, Spring AI, Spring Shell, Jackson 등 Spring을 사용할 때 사용하는 대부분의 라이브러리가 포함되어 있는 것을 알 수 있다. 실제 AI Agent 개발도 Spring AI를 사용해 본 사람이라면 쉽게 개발할 수 있을 것이다. 

https://docs.embabel.com/embabel-agent/guide/0.1.3-SNAPSHOT/index.html#reference.spring

 

 

목표

최근 많은 IT 회사에서 생산성을 위해 AI를 적극적으로 도입하고 사용하고 있다. 단순 개발툴의 코드를 작성해 주는 기능 말고도 개발 프로세스에 많은 과정에 AI를 도입하여 생산성을 높이고 있다. 이번 포스팅에서는 개발 과정 중 코드 리뷰와 테스트 코드를 작성해 주는 AI Agent를 Embabel을 사용하여 간단하게 개발해 보려고 한다. Embabel의 다양한 기능(MCP, LLM Mixing,...)을 사용하면 실제 업무에서 사용할 수 있는 AI Agent를 개발 가능하겠지만 첫 번째 예제니까 간단하게 구현해 보도록 한다. 

 

AI Agent는 사용자의 개입 없이 자율적으로 작동하여, 환경을 인지하고 학습하여 주어진 목표를 달성하거나 문제를 해결하는 지능형 소프트웨어 시스템을 말한다.

 

 

Getting Started

AI Agent 전체 흐름

  1. Git에 PR이 생성되면 Agent가 PR에 등록된 소스 코드를 가져온다.
  2. AI를 사용하여 소스 코드를 코드 리뷰한다.
  3. 등록된 코드의 테스트 코드를 작성한다.
  4. 코드 리뷰의 코멘트와 테스트 코드를 문서로 작성한다.

 

사전 준비

  • LLM은 OpenAI를 사용할 예정으로 API_KEY를 준비한다.
  • 예제 프로젝트는 Kotlin을 사용하여 작성한다.

OpenAI Key 발급은 이전 포스팅을 참고한다.(https://blog.igooo.org/150)

 

프로젝트 생성

프로젝트는 일반적인 Gradle 프로젝트로 생성한다.

embable의 repository와 embabel-agent-starter를 추가한다.

예제에서는 CLI 타입의 애플리케이션을 개발할 예정으로 embabel-agent-starter-shell을 추가한다.

plugins {
    kotlin("jvm") version "2.2.20"
    kotlin("plugin.spring") version "2.2.20"
    id("org.springframework.boot") version "3.5.6"
    id("io.spring.dependency-management") version "1.1.7"
}

group = "org.igooo.ai"
version = "0.0.1-SNAPSHOT"
description = "code-review-agent"

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

extra["embabelAgentVersion"] = "0.1.2"
val embabelAgentVersion: String by extra


repositories {
    mavenCentral()
    maven {
        name = "embabel-releases"
        url = uri("https://repo.embabel.com/artifactory/libs-release")
        mavenContent {
            releasesOnly()
        }
    }
}

dependencies {
    implementation("com.embabel.agent:embabel-agent-starter:${embabelAgentVersion}")
    implementation("com.embabel.agent:embabel-agent-starter-shell:${embabelAgentVersion}")

    implementation("org.jetbrains.kotlin:kotlin-reflect")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

kotlin {
    compilerOptions {
        freeCompilerArgs.addAll("-Xjsr305=strict")
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

 

환경 변수 설정

OpenAI를 사용하기 위해서 환경 변수로 OPENAI_API_KEY를 설정한다.

intellij를 사용한다면 아래와 같이 환경변수에 추가해 준다.

 

다른 LLM 사용은 공식 문서를 참고한다.

https://docs.embabel.com/embabel-agent/guide/0.1.3-SNAPSHOT/index.html#environment-setup

 

애플리케이션을 실행하면 아래와 같이 로그가 출력되고 마지막에 embabel>이라는 메시지가 나오면 정상적으로 LLM과 연동되었다.

   ___  ___    _______ .___  ___. .______        ___      .______    _______  __         ___  ___  /  / /  /   |   ____||   \/   | |   _  \      /   \     |   _  \  |   ____||  |        \  \ \  \
 /  / /  /    |  |__   |  \  /  | |  |_)  |    /  ^  \    |  |_)  | |  |__   |  |         \  \ \  \
<  < <  <     |   __|  |  |\/|  | |   _  <    /  /_\  \   |   _  <  |   __|  |  |          >  > >  >
 \  \ \  \    |  |____ |  |  |  | |  |_)  |  /  _____  \  |  |_)  | |  |____ |  `----.    /  / /  /
  \__\ \__\   |_______||__|  |__| |______/  /__/     \__\ |______/  |_______||_______|   /__/ /__/

Powered by Spring Boot 3.5.6
02:09:18.399 [main] INFO  CodeReviewAgentApplicationKt - Starting CodeReviewAgentApplicationKt using Java 21.0.2 with PID 1630 (/Users/igooo/Documents/workspace/code-review-agent/build/classes/kotlin/main started by igooo in /Users/igooo/Documents/workspace/code-review-agent)
02:09:18.400 [main] INFO  CodeReviewAgentApplicationKt - No active profile set, falling back to 1 default profile: "default"
02:09:18.996 [main] INFO  OllamaModels - Ollama models will not be queried as the 'ollama' profile is not active
02:09:19.001 [main] INFO  DockerLocalModels - Docker local models will not be queried as the 'docker' profile is not active
02:09:19.012 [main] INFO  BedrockModels - Bedrock models will not be queried as the 'bedrock' profile is not active
02:09:19.058 [main] INFO  OpenAiModels - Open AI compatible models are available at default OpenAI location. API key is set
02:09:19.141 [main] INFO  OpenAiModels - Open AI models are available: OpenAiProperties(maxAttempts=10, backoffMillis=5000, backoffMultiplier=5.0, backoffMaxInterval=180000)
02:09:19.150 [main] INFO  ConfigurableModelProvider - Default LLM: gpt-4.1-mini
Available LLMs:
	name: gpt-4.1, provider: OpenAI
	name: gpt-4.1-mini, provider: OpenAI
	name: gpt-4.1-nano, provider: OpenAI
	name: gpt-5, provider: OpenAI
	name: gpt-5-mini, provider: OpenAI
	name: gpt-5-nano, provider: OpenAI
Default embedding service: text-embedding-3-small
Available embedding services:
	name: text-embedding-3-small, provider: OpenAI
02:09:19.170 [main] INFO  ToolGroupsConfiguration - MCP is available. Found 0 clients: 
02:09:19.213 [main] INFO  RegistryToolGroupResolver - RegistryToolGroupResolver: name='SpringBeansToolGroupResolver', 6 available tool groups:
	  role:AppleScript, artifact:com.embabel.agent.tools.osx.AppleScriptTools, version:0.1.0, provider:embabel - Run AppleScript commands
  runAppleScript
  role:browser_automation, artifact:docker-puppeteer, version:0.1.0, provider:Docker - Browser automation tools  
❌ No tools found
  role:github, artifact:docker-github, version:0.1.0, provider:Docker - Integration with GitHub APIs  
❌ No tools found
  role:maps, artifact:docker-google-maps, version:0.1.0, provider:Docker - Mapping tools  
❌ No tools found
  role:math, artifact:com.embabel.agent.tools.math.MathTools, version:0.1.0, provider:embabel - Math tools: use when you need to perform calculations
  add, ceiling, divide, floor, max, mean, min, multiply, round, subtract
  role:web, artifact:docker-web, version:0.1.0, provider:Docker - Tools for web search and scraping  
❌ No tools found
02:09:19.214 [main] INFO  AgentPlatformConfiguration - Creating default ToolDecorator with toolGroupResolver: RegistryToolGroupResolver(name='SpringBeansToolGroupResolver', 6 tool groups), observationRegistry: io.micrometer.observation.SimpleObservationRegistry@6c225adb
02:09:19.246 [main] INFO  SSEController - SSEController initialized, ready to stream AgentProcessEvents...
02:09:19.263 [main] INFO  LlmRanker - Using auto LLM for ranking
02:09:19.658 [main] WARN  jline - Unable to create a system terminal, creating a dumb terminal (enable debug logging for more information)
02:09:19.749 [main] INFO  MultiIngester - Using text splitter org.springframework.ai.transformer.splitter.TokenTextSplitter@1f13276a
02:09:19.752 [main] INFO  AgentPlatformAutoConfiguration - AgentPlatformAutoConfiguration about to be processed...
02:09:19.757 [main] INFO  AgentPlatformPropertiesLoader - Agent platform properties loaded from classpath:agent-platform.properties and embabel-agent-properties
02:09:19.760 [main] INFO  AgentDeployer - AgentDeployer scanning disabled: not looking for agents defined as Spring beans
02:09:19.773 [main] INFO  AgentPlatformAutoConfiguration - AgentPlatformAutoConfiguration about to be processed...
02:09:24.932 [main] INFO  DelegatingAgentScanningBeanPostProcessor - Application context has been refreshed and all beans are initialized.
02:09:24.933 [main] INFO  DelegatingAgentScanningBeanPostProcessor - All deferred beans were post-processed.
02:09:24.933 [main] INFO  CodeReviewAgentApplicationKt - Started CodeReviewAgentApplicationKt in 6.686 seconds (process running for 6.925)
embabel>

 

사용 가능 명령어

embabel-agent-starter-shell을 사용하여 CLI 형태로 사용하게 되면 내부적으로는 Spring Shell 기반으로 되어있어서 Spring Shell의 기능을 사용할 수 있다.

다음 예제에는 Web Application에서 사용할 수 있는 방법으로 포스팅할 예정이다.

 

help 명령을 사용하여 사용 가능한 명령어를 확인할 수 있다.

embabel> help
AVAILABLE COMMANDS

Built-In Commands
       help: Display help about available commands
       stacktrace: Display the full stacktrace of the last error.
       clear: Clear the shell screen.
       history: Display or save the history of previously run commands
       version: Show version info
       script: Read and execute commands from a file.

Shell Commands
       set-options: Set options
       blackboard, bb: Show last blackboard: The final state of a previous operation
       models: List available models
       tool-stats: Show tool stats
       profiles: List all active Spring profiles
       rag-service: List available rag services
       tools: List available tool groups
       execute, x: Execute a task. Put the task in double quotes. For example:
       	x "Lynda is a scorpio. Find news for her" -p
       platform: Information about the AgentPlatform
       ingest: ingest
       choose-goal: Try to choose a goal for a given intent. Show all goal rankings
       agents: List agents
       exit, quit, bye: Exit the application
       show-options: Show options
       chat: Chat
       rag: Perform a RAG query
       conditions: List conditions
       actions: List actions
       runs: Show recent agent process runs. This is what actually happened, not just what was planned.
       goals: List goals

 

models 명령을 사용하여 사용 가능한 AI 모델을 확인할 수 있다.

embabel> models
Default LLM: gpt-4.1-mini
Available LLMs:
	name: gpt-4.1, provider: OpenAI
	name: gpt-4.1-mini, provider: OpenAI
	name: gpt-4.1-nano, provider: OpenAI
	name: gpt-5, provider: OpenAI
	name: gpt-5-mini, provider: OpenAI
	name: gpt-5-nano, provider: OpenAI
Default embedding service: text-embedding-3-small
Available embedding services:
	name: text-embedding-3-small, provider: OpenAI

 

execute(alias x) 명령을 사용하면 등록된 agent에게 요청을 보낼 수 있다.

아직 Agent를 추가하지 않았기 때문에 embabel을 아무것도 하지 않는다.
embabel> x "Lynda is a scorpio. Find news for her" -p
05:00:16.867 [main] INFO  Embabel - Created process options: {"contextId":null,"identities":{"forUser":null,"runAs":null},"blackboard":null,"test":false,"verbosity":{"showPrompts":true,"showLlmResponses":false,"debug":false,"showPlanning":true,"showLongPlans":true},"allowGoalChange":true,"budget":{"cost":2.0,"actions":50,"tokens":1000000},"control":{"toolDelay":"NONE","operationDelay":"NONE","earlyTerminationPolicy":{"name":"MaxActionsEarlyTerminationPolicy"}},"prune":false,"listeners":[]}
05:00:16.868 [main] INFO  Embabel - Executing in closed mode: Trying to find appropriate agent
05:00:16.868 [main] INFO  Embabel - Choosing Agent based on UserInput(content=Lynda is a scorpio. Find news for her, timestamp=2025-10-03T20:00:16.868100Z)
05:00:16.869 [main] INFO  Embabel - Failed to choose Agent based on UserInput(content=Lynda is a scorpio. Find news for her, timestamp=2025-10-03T20:00:16.868100Z).
  Choices:
  .
  Confidence cutoff: 0.6
I'm sorry. I don't know how to do that.

 

Agent 추가하기 

@EnableAgents 어노테이션을 추가하여 auto-configuration과 함께 Embabel Agent 프레임워크를 활성화한다.

@SpringBootApplication
@EnableAgents
class CodeReviewAgentApplication

fun main(args: Array<String>) {
    runApplication<CodeReviewAgentApplication>(*args)
}

 

코드 리뷰와 테스트 코드를 작성할 agent를 정의한다.

@Agent 어노테이션을 사용하여 agent를 선언한다.

description을 영어로 작성해야 토큰을 절약할 수 있지만 코드 이해를 쉽게 하기 위해 예제에서는 한국어로 작성한다. 
package org.igooo.ai.codereview.agent

import com.embabel.agent.api.annotation.Agent
import org.slf4j.LoggerFactory

@Agent(description = "코드를 리뷰하고 테스트 코드를 작성하세요")
class CodeReviewAgent {
    private val log = LoggerFactory.getLogger(this.javaClass)
}

 

코드 리뷰를 위한 Action 추가하기

@Action 어노테이션을 사용하여 코드를 리뷰하는 action을 정의한다.

  • RoleGoalBackstory는 Crew AI 스타일에 backstory 프롬프트를 정의한다.(필수는 아님)
  • @Action 어노테이션을 추가하고 AI를 사용하여 사용자가 입력한 코드를(UserInput) 기반으로 코드 리뷰를 요청한다.
  • CodeReviewContent를 action에 응답 결과로 리턴 받는다.
val CodeReviewer =
    RoleGoalBackstory(
        role = "시니어 개발자로 전문적으로 코드를 리뷰한다",
        goal = "성능과 논리적 오류가 없는지 확인한다",
        backstory = "안전하고 성능이 뛰어난 코드를 작성하는 개발자 입니다",
    )
    

@Agent(description = "코드를 리뷰하고 테스트 코드를 작성하세요")
class CodeReviewAgent {
    private val log = LoggerFactory.getLogger(this.javaClass)

    @Action
    fun reviewCode(
        userInput: UserInput,
        context: OperationContext,
    ): CodeReviewContent =
        context.ai().withDefaultLlm().withPromptContributor(CodeReviewer).createObject(
            """
            100 단어 이하로 코드를 리뷰하세요.
            입력된 코드를 바탕으로 논리적 오류나 성능에 문제가 없는지 확인하고 답변하세요.
            수정이 필요한 부분에 대해서도 답변하세요.
            코드에 문제가 없으면 완벽하다고 답변하세요.
             
            # Source Code
            ${userInput.content}
            """.trimIndent(),
        )
    ......
}

data class CodeReviewContent(
    val content: String,
)

 

테스트 코드 작성을 위한 Action 추가하기

코드 리뷰 action과 동일하게 테스트 코드 작성 Action을 추가한다.

    @Action
    fun writeTestCode(
        userInput: UserInput,
        context: OperationContext,
    ): TestCodeContent =
        context.ai().withDefaultLlm().withPromptContributor(TestCodeWriter).createObject(
            """
            테스트 코드는 Junit5 기반으로 작성하세요. 테스트 코드는 모든 컨디션을 커버해야 합니다.
            Mock처리가 필요한 경우 Mockito를 사용해서 작성하고, BDD 스타일로 Given/Whn/Then으로 나눠서 작성하세요.
            테스트 코드만 답변에 포함하고 설명이 필요한 부분은 주석으로 추가해주세요.
             
            # Source Code
            ${userInput.content}
            """.trimIndent(),
        )

 

Goal 정의하기

@AchievesGoal 어노테이션을 사용하여 agent의 최종 goal을 정의한다.

위에서 정의한 코드 리뷰(CodeReviewContent), 테스트 코드(TestCodeContent) action에 객체를 사용한다.

    @AchievesGoal(description = "코드 리뷰와 테스트 코드를 사용하여 코멘트를 작성하세요")
    @Action
    fun writeComment(
        codeReviewContent: CodeReviewContent,
        testCodeContent: TestCodeContent,
        context: OperationContext,
    ): WriteUp {
        val comment =
            context.ai().withDefaultLlm().generateText(
                """
                코드 리뷰에 대하여 내용을 정리하고 수정이 필요한 부분을 200단어 이하로 정리해주세요.
                테스트 코드는 모두 답변에 추가하고 Markdown의 코드 블럭을 사용해서 표현해주세요.
                코드 리뷰와 테스트 코드는 분리해서 답변하세요.
                
                코드 리뷰:
                <comment>${codeReviewContent.content}</comment>
                
                테스트 코드:
                <test_code>${testCodeContent.content}</test_code>
                
                Markdown 포멧으로 답변하세요.
                """.trimIndent(),
            )
        return WriteUp(comment)
    }

 

 

Demo

성능에 문제가 있는 아래 코드에 대하여 코드 리뷰와 테스트 코드를 요청한다.

    public String proecessString(int value) {
        String result = "";
        for(int i = 0; i < 1000; i++) {
            result += "Hello";
        }
        return result;
    }
    
    public void processData(int value) {
        try {
            if(value < 0) {
                thow new IllegalArgumentException("value cannot be negative");
            }
        } catch(IllegalArgumentException e) {
            System.out.println("Error: " + e.message);
        }
    }

 

embable을 사용하여 코드 리뷰에 테스트 코드를 작성하면 아래처럼 만족스러운 결과를 얻을 수 있다.

최종 결과에는 사용된 토큰과 비용도 같이 확인 할 수 있다.

embabel> x "입력한 코드를 리뷰하고 테스트 코드를 작성해줘
{코드 내용}
"
05:57:46.554 [main] INFO  Embabel - Created process options: {"contextId":null,"identities":{"forUser":null,"runAs":null},"blackboard":null,"test":false,"verbosity":{"showPrompts":false,"showLlmResponses":false,"debug":false,"showPlanning":true,"showLongPlans":true},"allowGoalChange":true,"budget":{"cost":2.0,"actions":50,"tokens":1000000},"control":{"toolDelay":"NONE","operationDelay":"NONE","earlyTerminationPolicy":{"name":"MaxActionsEarlyTerminationPolicy"}},"prune":false,"listeners":[]}
05:57:46.554 [main] INFO  Embabel - Executing in closed mode: Trying to find appropriate agent

......

You asked: UserInput(content=입력한 코드를 리뷰하고 테스트 코드를 작성해줘 ......, timestamp=2025-10-03T20:57:46.554703Z)


markdown
### 코드 리뷰 요약 및 수정 필요 사항 (200단어 이하)

1. 메서드명 오타: 'proecessString' →
'processString' 으로 수정 필요.  
2. 문자열 연결 성능: 문자열을 반복 연결할 때 StringBuilder 사용 권장 (기존은 + 연산자 사용 가능성
있음).  
3. processData 내 예외 처리: throw 키워드 오타('thow' → 'throw') 수정 필요.  
4.
catch 블록에서 예외 메시지 접근: e.message → e.getMessage() 로 변경해야 함.  
5. 예외 처리 방식: 단순 출력만 하는 대신 로깅 프레임워크
사용 또는 예외 재발생을 고려.  
6. 논리적 오류 및 성능 최적화도 요구됨. 특히 반복문이나 불필요한 연산 최적화 필요.  

---


### 수정된 테스트
코드

java
import static org.assertj.core.api.Assertions.assertThat;
import static
org.junit.jupiter.api.Assertions.assertThrows;

import org.junit.jupiter.api.Test;

public class YourClassTest {

    private final
YourClass yourClass = new YourClass();

    @Test
    void processString_shouldReturnConcatenatedStringOfHelloRepeated1000Times() {
       
// Given
        int inputValue = 10; // any integer since input doesn't affect output

        // When
        String result =
yourClass.processString(inputValue);

        // Then
        assertThat(result).hasSize(5 * 1000); // "Hello" length is
5
        assertThat(result).startsWith("Hello");
        assertThat(result).endsWith("Hello");
    }

    @Test
    void
processData_shouldNotThrowException_whenValueIsPositive() {
        // Given
        int positiveValue = 5;

        // When / Then
       
yourClass.processData(positiveValue);
    }

    @Test
    void processData_shouldPrintError_whenValueIsNegative() {
        // Given
      
int negativeValue = -1;

        // When / Then
        yourClass.processData(negativeValue);
    }

    @Test
    void
processData_shouldNotThrow_whenValueIsZero() {
        // Given
        int zeroValue = 0;

        // When / Then
       
yourClass.processData(zeroValue);
    }

    @Test
    void processString_shouldReturnStringWithExactly1000HelloOccurrences() {
        //
Given
        int inputValue = 123;

        // When
        String result = yourClass.processString(inputValue);

        // Then
       
int count = result.split("Hello", -1).length - 1;
        assertThat(count).isEqualTo(1000);
    }
}
`


LLMs used: [gpt-4.1-mini] across 3 calls
Prompt tokens: 1,429,
Completion tokens: 1,182
Cost: $0.0025

Tool usage:

 

마무리

embabel을 사용하여 코드 리뷰와 테스트 코드를 작성하는 AI agent를 간단하게 구현해 봤다.

실제 업무에서 사용하려면 Web Application으로 개발하여 PR 등록 시 자동으로 동작하도록 구현할 수도 있고, MCP의 기능을 사용하여 실제 Github이나 사용하는 저장소에 코멘트를 등록하거나 테스트 코드를 commit에 추가하는 것도 가능하다.

embable을 사용하여 업무에 필요한 AI agent 하나쯤은 작성해 보도록 하자.

이후에는 Web Application 형태로 MCP나 다른 도구를 사용한 예제도 작성해 보겠다.

 

 

728x90