Embabel Agent 프레임워크 가이드(3) - Agent 동작 구조

bebeis·2025년 11월 26일

embabel

목록 보기
2/5
post-thumbnail

3.1. 에이전트 호출(Invoking an Agent)

에이전트는 코드에서 직접 호출할 수도 있고, 사용자 입력을 통해 호출할 수도 있다.

  • 프로그램적으로 호출
    “Invoking Embabel Agents” 문서를 참고하면, 코드에서 에이전트를 호출하는 다양한 방법을 볼 수 있다.
    이 경우 보통 단순 문자열이 아닌, 구조화된 타입(도메인 객체 등) 을 주고받는다.

  • 사용자 입력 기반 호출
    사용자 입력으로 호출할 때는, LLM이 Autonomy 클래스를 통해 적절한 에이전트를 고른다. 동작 방식은 설정 모드에 따라 달라진다.

    • Closed 모드:
      시스템에 등록된 에이전트 목록 안에서, 사용자 입력에 가장 잘 맞는 에이전트를 LLM이 선택한다.
    • Open 모드:
      LLM이 먼저 “달성해야 할 Goal”을 정하고, 현재 월드 상태(Blackboard 등)에 있는 액션들을 조합해 그 Goal을 달성할 수 있는 에이전트를 동적으로 구성한다.

3.2. 에이전트 프로세스 흐름 (Agent Process Flow)

에이전트를 호출하면 Embabel은 고유한 ID를 가진 AgentProcess 를 만들고,
이 객체가 전체 실행 라이프사이클을 관리한다.

3.2.1. AgentProcess 라이프사이클

AgentProcess는 실행 내내 상태를 유지하며 다음과 같은 상태로 전이된다.

프로세스 상태들:

  • NOT_STARTED : 아직 시작되지 않음
  • RUNNING : 문제 없이 실행 중
  • COMPLETED : 정상적으로 완료됨
  • FAILED : 실패하여 더 이상 진행 불가
  • TERMINATED : 조기 종료 정책에 의해 강제 종료됨
  • KILLED : 사용자 또는 플랫폼에 의해 종료됨
  • STUCK : 더 이상 진행할 계획을 세우지 못하는 상태(일시적일 수도 있음)
  • WAITING : 사용자 입력 또는 외부 이벤트를 기다리는 중
  • PAUSED : 스케줄링 정책에 의해 일시 정지됨

실행 메서드:

  • tick() :
    한 단계만 진행한다. (액션 하나 실행 후 반환)
  • run() :
    완료/실패/대기 상태에 도달할 때까지 가능한 한 계속 실행한다.

이 메서드들은 사용자 코드에서 직접 호출하는 경우는 드물고, 보통 프레임워크가 실행 흐름을 제어할 때 내부적으로 사용한다.

각 AgentProcess가 유지하는 정보:

  • 고유 ID : 추적 및 참조를 위한 영속적 식별자
  • History : 실행된 모든 액션과 그 시점에 대한 기록
  • Goal : 이 프로세스가 달성하려는 목표
  • Failure Info : 실패가 발생했다면 그 상세 정보
  • Parent ID : 부모 프로세스가 있을 경우 상위 프로세스 ID

3.2.2. Planning (계획 수립)

각 액션이 실행된 직후, Embabel은 GOAP(Goal-Oriented Action Planning)를 이용해 다시 계획을 세운다.

플래닝 과정:
1. 현재 상태 분석
블랙보드와 월드 상태를 보고 지금 어떤 데이터와 조건이 충족되어 있는지 확인한다.

  1. 실행 가능한 액션 탐색
    각 액션의 precondition 을 보고 지금 상태에서 실행 가능한 액션을 모두 찾는다.

  2. 액션 시퀀스 탐색
    A* 알고리즘을 사용해 목표를 달성할 수 있는 최적의 액션 시퀀스 를 찾는다.

  3. 최적 계획 선택
    비용(cost)과 성공 가능성을 고려해 가장 좋은 계획을 선택한다.

  4. 다음 액션 실행 → 다시 계획
    계획에서 첫 번째 액션을 실행한 뒤, 새로운 상태를 기준으로 다시 계획(planning)을 수행한다.

이 과정은 전형적인 OODA 루프(Observe–Orient–Decide–Act) 를 구현한다.

  • Observe: 현재 블랙보드와 액션 결과를 살핀다.
  • Orient: 이전 플래닝 이후 무엇이 달라졌는지 이해한다.
  • Decide: 새로운 정보에 맞게 계획을 조정하거나 다시 세운다.
  • Act: 다음 액션을 실행한다.

재계획(replanning) 접근 덕분에 에이전트는:

  • 예기치 않은 액션 결과에 적응하고
  • 외부 환경이 동적으로 변하는 상황을 다루며
  • 일부 실패에서 회복하고
  • 새로 생긴 기회를 활용할 수 있다.

3.2.3. Blackboard

Blackboard 는 에이전트 프로세스 실행 동안 상태를 유지하는 공유 메모리 시스템이다. KB에서 말하는 블랙보드 패턴을 구현한 것이다.

일반적으로 사용자 코드는 블랙보드를 직접 다룰 필요가 거의 없다. Embabel이 다음을 자동으로 처리하기 때문이다.

  • 액션의 입력은 블랙보드에서 꺼내고
  • 액션의 출력은 블랙보드에 자동으로 추가되며
  • 플래닝의 조건 평가 역시 블랙보드를 기반으로 수행된다.

주요 특징

  • 중앙 저장소: 도메인 객체, 중간 결과, 프로세스 상태 모두 저장
  • 타입 기반 접근: 객체는 타입별로 인덱싱되어 검색된다.
  • 삽입 순서 유지: 나중에 추가된 객체가 기본값으로 선택된다.
  • 불변 객체: 한 번 추가된 객체는 수정되지 않고, 새 버전이 추가되는 방식
  • 조건 추적: 플래너가 사용하는 boolean 조건 상태를 저장

핵심 연산 예시 (Kotlin 스타일):

// 객체 추가
blackboard += person
blackboard["result"] = analysis

// 타입으로 조회
val person = blackboard.last<Person>()
val allPersons = blackboard.all<Person>()

// 조건 설정 및 조회
blackboard.setCondition("userVerified", true)
val verified = blackboard.getCondition("userVerified")

**데이터 흐름:**
1. **입력 처리**: 초기 사용자 입력이 블랙보드에 올라간다.
2. **액션 실행**: 각 액션은 블랙보드에서 입력을 읽어 결과를 추가한다.
3. **상태 진화**: 블랙보드는 시간에 따라 "상태의 히스토리"를 축적한다.
4. **플래닝 입력**: 현재 블랙보드 상태가 다음 플래닝의 입력이 된다.
5. **결과 반환**: 최종 결과는 블랙보드에서 꺼내서 사용자/호출자에게 전달된다.

**Blackboard가 제공하는 이점:**

  • **느슨한 결합**: 액션들은 서로를 직접 참조하지 않아도 된다. 블랙보드에서 가져다 쓰고 결과를 기록해주면 된다.
  • **유연한 데이터 흐름**: 필요한 타입의 데이터만 있으면 어느 액션에서든 사용 가능하다.
  • **상태 보존**: 실행의 전체 히스토리가 남는다.
  • **디버깅 지원**: 상태 변화 과정을 그대로 확인할 수 있다.

3.2.4. Binding

기본적으로 블랙보드의 항목은 **타입 기준**으로 매칭된다. 여러 후보가 있을 경우 **가장 최근에 추가된 객체**가 사용된다.

또한 블랙보드 항목에 **이름(key)** 를 줄 수도 있다.

@Action
public Person extractPerson(UserInput userInput, OperationContext context) {
    PersonImpl maybeAPerson =
        context.promptRunner()
            .withLlm(LlmOptions.fromModel(OpenAiModels.GPT_41))
            .createObjectIfPossible(
                """
                Create a person from this user input, extracting their name:
                %s""".formatted(userInput.getContent()),
                PersonImpl.class
            );

    if (maybeAPerson != null) {
        context.bind("user", maybeAPerson);
    }
    return maybeAPerson;
}
  • context.bind("user", maybeAPerson): 블랙보드의 PersonImpl에 “user”라는 이름을 부여한다.
  • 타입 기반 라우팅에서는 이름이 중요하지 않지만, 참고용 기본 이름은 it 이다.

3.3. Goals, Actions, Conditions

Embabel에서 에이전트는 크게 세 가지 개념으로 모델링된다.

  • **Goals**: 에이전트가 달성해야 할 목적(목표 상태)
  • **Actions**: 목표를 향해 나아가기 위해 취할 수 있는 단계
  • **Conditions**: 어떤 액션을 수행할지, 목표가 달성되었는지를 판단하는 조건식

이들은 모두 어노테이션(@AchievesGoal, @Action, @Condition) 기반으로 정의된다. 자세한 내용은 아래에서 다시 다룬다.

3.4. 도메인 객체 (Domain Objects)

Embabel에서 도메인 객체는 단순한 DTO를 넘어선다. **상태 + 행동(Behavior)** 을 함께 가지며, 이 행동 일부를 LLM에 도구로 노출할 수 있다.

3.4.1. 행동을 가진 객체

도메인 객체는 비즈니스 로직을 포함하고, 그 일부를 @Tool 을 통해 LLM이 호출할 수 있게 한다.

@Entity
public class Customer {
    private String name;
    private LoyaltyLevel loyaltyLevel;
    private List orders;

    @Tool(description = "Calculate the customer's loyalty discount percentage")
    public BigDecimal getLoyaltyDiscount() {
        return loyaltyLevel.calculateDiscount(orders.size());
    }

    @Tool(description = "Check if customer is eligible for premium service")
    public boolean isPremiumEligible() {
        return orders.stream()
            .mapToDouble(Order::getTotal)
            .sum() > 1000.0;
    }

    *// LLM에는 노출되지 않는 내부 로직*
    private void updateLoyaltyLevel() {
        *// ...*
    }
}

3.4.2. 선택적 Tool 노출

@Tool 애노테이션을 사용하면, 도메인 객체의 메서드 중 **어떤 것만 LLM에 노출할지 선택**할 수 있다.

  • 비즈니스 로직
  • 계산된 값(파생 속성)
  • 도메인 규칙
  • 내부 구현 세부사항(노출 X)

3.4.3. 액션에서의 사용

도메인 객체는 액션 메서드에서 자연스럽게 사용되며, LLM과 전통적인 OOP 코드를 섞어 사용하는 중심이 된다.

@Action
public Recommendation generateRecommendation(Customer customer, OperationContext context) {
    *// LLM은 customer.getLoyaltyDiscount(), isPremiumEligible() 등을 툴로 사용할 수 있다.*
    String prompt = String.format(
        "Generate a personalized recommendation for %s based on their profile",
        customer.getName()
    );

    return context.ai()
        .withDefaultLlm()
        .createObject(prompt, Recommendation.class);
}

3.4.4. 도메인 이해의 중요성

Rod Johnson이 DICE(Domain-Integrated Context Engineering) 개념에서 강조하듯, **도메인 이해는 좋은 컨텍스트 엔지니어링의 핵심**이다.

도메인 객체는 다음을 연결하는 다리 역할을 한다.

  • **비즈니스 도메인**: 실제 세계의 개념과 관계
  • **에이전트 행동**: LLM이 도메인을 이해하고 상호작용하는 방식
  • **코드 액션**: 도메인 객체를 다루는 기존 로직

3.4.5. 이점 정리

  • **풍부한 컨텍스트**: LLM은 데이터 구조와 행동 문맥을 함께 받는다.
  • **캡슐화**: 비즈니스 로직은 도메인 객체 내부에 유지된다.
  • **재사용성**: 도메인 객체는 여러 에이전트에서 공통으로 사용 가능하다.
  • **테스트 용이성**: 도메인 로직은 독립적으로 단위 테스트 가능하다.
  • **진화**: 도메인 객체에 @Tool 메서드를 추가하는 것만으로 에이전트 능력을 확장할 수 있다.

지금까지 다룬 내용을 그림으로 그려보면 다음과 같다.

profile
No Trying. Just Doing. 시도에 머무르지 않고, 개발이라는 '일'을 확실하게 해냅니다.

0개의 댓글