20장 — 우선순위 결정

복잡하고 변화하는 환경에서 에이전트는 수많은 선택지, 상충하는 목표, 제한된 자원에 자주 직면합니다.

다음에 수행할 동작을 정하는 명확한 절차가 없으면 에이전트의 효율이 떨어지고, 운영이 지연되거나, 핵심 목표를 달성하지 못할 수 있다.

우선순위 결정 패턴은 이 문제를 해결하기 위해 에이전트가 작업, 목표, 행동을 중요도·긴급성·의존 관계·사전에 정한 기준에 따라 평가하고 순위를 매길 수 있도록 합니다. 이를 통해 에이전트는 가장 중요한 작업에 노력을 집중하고, 그 결과 효과가 높아지며 목표와도 더 잘 부합하게 됩니다.

패턴 개요

에이전트는 우선순위 결정을 통해 작업, 목표, 하위 목표를 효과적으로 관리하고 이후 행동의 방향을 정합니다. 이 과정은 여러 요구가 동시에 발생할 때 근거 있는 판단을 내릴 수 있게 하며, 덜 중요한 활동보다 핵심적이거나 긴급한 활동을 먼저 처리하도록 합니다. 자원이 부족하고 시간이 제한되어 있으며 목표가 서로 충돌할 수 있는 현실 환경에서 특히 중요합니다.

네 가지 기본 요소

① 기준 정의 — 작업을 평가할 규칙이나 지표를 마련하는 단계입니다.

기준 무엇을 보는가
긴급성 작업의 시간 민감도
중요도 주요 목표에 미치는 영향
의존 관계 해당 작업이 다른 작업의 선행 조건인지 여부
자원 가용성 필요한 도구나 정보가 준비되어 있는지
비용/편익 분석 투입 노력 대비 기대 결과
사용자 선호도 개인화된 에이전트의 경우

② 작업 평가 — 각 후보 작업을 미리 정한 기준에 비추어 판단하는 과정으로, 단순한 규칙부터 복잡한 점수 산정, 대규모 언어 모델의 추론까지 다양한 방식을 씁니다.

③ 스케줄링 또는 선택 로직 — 평가 결과를 바탕으로 최적의 다음 동작이나 작업 순서를 정하는 알고리즘으로, 큐나 고급 계획 수립 컴포넌트를 활용하기도 합니다.

④ 상황에 따른 재우선순위 결정새로운 긴급 이벤트 발생이나 마감 임박 등 여건이 달라질 때 에이전트가 우선순위를 수정할 수 있게 하여, 변화에 맞춰 유연하게 대응하도록 합니다.

세 가지 수준

우선순위 결정은 여러 수준에서 이루어질 수 있습니다.

상위 목표 우선순위 결정   전체를 아우르는 목표를 선택하는 수준
        ↓
하위 작업 우선순위 결정   계획 내 단계의 순서를 정하는 수준
        ↓
행동 선택               현재 가용한 선택지 중 바로 다음 행동을 고르는 수준

우선순위를 잘 정하면 에이전트는 더 지능적이고 효율적이며 안정적으로 행동할 수 있습니다. 이런 효과는 복잡한 다목표 환경에서 특히 두드러집니다.

이는 관리자가 모든 팀원의 의견을 종합해 작업 우선순위를 정하는 인간 팀 운영 방식과도 닮았다.

6장의 계획 수립이 무엇을 할지의 문제였고 16장의 자원 최적화가 어떻게 할지의 문제였다면, 이 장은 무엇을 먼저 할지의 문제입니다.

실제 적용 및 활용 사례

분야 무엇을 먼저 하는가
고객 지원 자동화 비밀번호 재설정 같은 일상적 문의보다 시스템 장애 보고 같은 긴급 요청을 우선 처리한다. 가치가 높은 고객에게 우선권을 부여하기도 한다
클라우드 컴퓨팅 AI가 자원을 관리하고 스케줄링하면서, 수요가 몰리는 시간대에는 핵심 애플리케이션에 자원을 우선 배분하고, 긴급하지 않은 배치 작업은 한가한 시간대로 미뤄 비용을 최적화한다
자율주행 시스템 안전과 효율을 위해 행동의 우선순위를 끊임없이 결정한다. 예를 들어 충돌 회피를 위한 제동은 차선 유지나 연비 최적화보다 우선한다
금융 트레이딩 봇은 시장 상황, 위험 허용 범위, 수익률, 실시간 뉴스 등을 분석해 거래 우선순위를 정하고, 우선순위가 높은 거래를 신속하게 체결한다
프로젝트 관리 AI 에이전트가 마감 기한, 의존 관계, 팀 가용성, 전략적 중요도를 기준으로 프로젝트 보드의 작업 우선순위를 정한다
사이버 보안 네트워크 트래픽을 모니터링하는 에이전트가 위협 심각도, 잠재적 영향, 자산 중요도를 평가해 경보 우선순위를 정하고, 가장 위험한 위협에 즉각 대응한다
개인용 도우미 AI 사용자가 정한 중요도, 다가오는 마감 기한, 현재 상황에 따라 캘린더 일정, 알림, 통지를 정리한다

실습 코드 예제

LangChain을 사용해 프로젝트 관리자 AI 에이전트를 개발하는 과정입니다. 이 에이전트는 작업을 생성하고, 우선순위를 정하고, 팀원에게 배정합니다.

작업 관리 시스템

import asyncio
from typing import Optional, Dict
from dotenv import load_dotenv
from pydantic import BaseModel, Field
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.tools import Tool
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_react_agent
from langchain.memory import ConversationBufferMemory

# --- 0. 환경 설정 ---
# .env 파일에서 OPENAI_API_KEY를 불러온다.
load_dotenv()

# ChatOpenAI 클라이언트는 환경 변수에서 API 키를 자동으로 가져온다.
llm = ChatOpenAI(temperature=0.5, model="gpt-4o-mini")

# --- 1. 작업 관리 시스템 ---
class Task(BaseModel):
    """시스템 내 개별 작업을 나타내는 모델."""
    id: str
    description: str
    priority: Optional[str] = None      # P0, P1, P2
    assigned_to: Optional[str] = None   # 워커 이름

class SuperSimpleTaskManager:
    """효율적이고 안정적인 인메모리 작업 관리자."""

    def __init__(self):
        # O(1) 조회, 갱신, 삭제를 위해 딕셔너리를 사용한다.
        self.tasks: Dict[str, Task] = {}
        self.next_task_id = 1

    def create_task(self, description: str) -> Task:
        """새 작업을 생성하고 저장한다."""
        task_id = f"TASK-{self.next_task_id:03d}"
        new_task = Task(id=task_id, description=description)
        self.tasks[task_id] = new_task
        self.next_task_id += 1
        print(f"DEBUG: Task created - {task_id}: {description}")
        return new_task

    def update_task(self, task_id: str, **kwargs) -> Optional[Task]:
        """Pydantic의 model_copy를 사용해 작업을 안전하게 갱신한다."""
        task = self.tasks.get(task_id)
        if task:
            # 타입 안전한 갱신을 위해 model_copy를 사용한다.
            update_data = {k: v for k, v in kwargs.items() if v is not None}
            updated_task = task.model_copy(update=update_data)
            self.tasks[task_id] = updated_task
            print(f"DEBUG: Task {task_id} updated with {update_data}")
            return updated_task
        print(f"DEBUG: Task {task_id} not found for update.")
        return None

    def list_all_tasks(self) -> str:
        """현재 시스템에 등록된 모든 작업을 조회한다."""
        if not self.tasks:
            return "No tasks in the system."
        task_strings = []
        for task in self.tasks.values():
            task_strings.append(
                f"ID: {task.id}, Desc: '{task.description}', "
                f"Priority: {task.priority or 'N/A'}, "
                f"Assigned To: {task.assigned_to or 'N/A'}"
            )
        return "Current Tasks:\n" + "\n".join(task_strings)

task_manager = SuperSimpleTaskManager()

딕셔너리로 O(1) 조회를 보장하고, Pydantic의 model_copy로 타입 안전한 갱신을 수행하는 점이 눈에 띕니다. 원본을 제자리에서 바꾸지 않고 갱신된 사본을 만들어 교체하는 방식입니다.

도구 정의 — 인자를 Pydantic으로 못 박기

# --- 2. 프로젝트 관리자 에이전트용 도구 ---
# 도구 인자의 유효성 검증과 명확성을 위해 Pydantic 모델을 사용한다.
class CreateTaskArgs(BaseModel):
    description: str = Field(description="작업에 대한 상세 설명.")

class PriorityArgs(BaseModel):
    task_id: str = Field(description="갱신할 작업의 ID. 예: 'TASK-001'.")
    priority: str = Field(description="설정할 우선순위. 'P0', 'P1', 'P2' 중 하나여야 한다.")

class AssignWorkerArgs(BaseModel):
    task_id: str = Field(description="갱신할 작업의 ID. 예: 'TASK-001'.")
    worker_name: str = Field(description="작업을 배정할 워커의 이름.")

def create_new_task_tool(description: str) -> str:
    """주어진 설명으로 새 프로젝트 작업을 생성한다."""
    task = task_manager.create_task(description)
    return f"Created task {task.id}: '{task.description}'."

def assign_priority_to_task_tool(task_id: str, priority: str) -> str:
    """지정된 작업 ID에 우선순위(P0, P1, P2)를 배정한다."""
    if priority not in ["P0", "P1", "P2"]:
        return "Invalid priority. Must be P0, P1, or P2."
    task = task_manager.update_task(task_id, priority=priority)
    return f"Assigned priority {priority} to task {task.id}." if task else f"Task {task_id} not found."

def assign_task_to_worker_tool(task_id: str, worker_name: str) -> str:
    """작업을 특정 워커에게 배정한다."""
    task = task_manager.update_task(task_id, assigned_to=worker_name)
    return f"Assigned task {task.id} to {worker_name}." if task else f"Task {task_id} not found."

# 프로젝트 관리자 에이전트가 사용할 수 있는 모든 도구
pm_tools = [
    Tool(
        name="create_new_task",
        func=create_new_task_tool,
        description="새 작업을 생성하고 ID를 받을 때 사용한다. 다른 도구보다 먼저 호출해야 한다.",
        args_schema=CreateTaskArgs
    ),
    Tool(
        name="assign_priority_to_task",
        func=assign_priority_to_task_tool,
        description="작업 생성 후 해당 작업에 우선순위를 배정할 때 사용한다.",
        args_schema=PriorityArgs
    ),
    Tool(
        name="assign_task_to_worker",
        func=assign_task_to_worker_tool,
        description="작업 생성 후 해당 작업을 특정 워커에게 배정할 때 사용한다.",
        args_schema=AssignWorkerArgs
    ),
    Tool(
        name="list_all_tasks",
        func=task_manager.list_all_tasks,
        description="현재 등록된 모든 작업과 그 상태를 조회할 때 사용한다."
    ),
]

도구 설명에 “다른 도구보다 먼저 호출해야 한다”, “작업 생성 후” 같은 순서 제약이 직접 적혀 있다는 점이 중요합니다. 5장에서 본 것처럼, 도구 설명 자체가 에이전트에게 주는 지시문 역할을 합니다.

에이전트 정의 — 프롬프트에 우선순위 규칙을 담다

# --- 3. 프로젝트 관리자 에이전트 정의 ---
pm_prompt_template = ChatPromptTemplate.from_messages([
    ("system", """당신은 프로젝트 관리 전담 LLM 에이전트다.
목표는 프로젝트 작업을 효율적으로 관리하는 것이다.

새로운 작업 요청을 받으면 다음 단계를 순서대로 수행하라:
1. 먼저, `create_new_task` 도구를 사용해 주어진 설명으로 작업을 생성하라.
   `task_id`를 얻으려면 반드시 이 단계를 먼저 수행해야 한다.
2. 다음으로, 사용자의 요청을 분석해 우선순위나 담당자가 언급되어 있는지 확인하라.
   - 우선순위가 언급된 경우(예: "긴급", "즉시", "최우선"), P0으로 매핑하라.
     `assign_priority_to_task`를 사용하라.
   - 워커가 언급된 경우, `assign_task_to_worker`를 사용하라.
3. 누락된 정보(우선순위, 담당자)가 있으면 합리적인 기본값을 배정하라
   (예: 우선순위 P1, 담당자 'Worker A').
4. 작업 처리가 완료되면 `list_all_tasks`를 사용해 최종 상태를 표시하라.

사용 가능한 워커: 'Worker A', 'Worker B', 'Review Team'
우선순위 수준: P0(최고), P1(중간), P2(최저)
"""),
    ("placeholder", "{chat_history}"),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}")
])

# 에이전트 실행기를 생성한다
pm_agent = create_react_agent(llm, pm_tools, pm_prompt_template)
pm_agent_executor = AgentExecutor(
    agent=pm_agent,
    tools=pm_tools,
    verbose=True,
    handle_parsing_errors=True,
    memory=ConversationBufferMemory(memory_key="chat_history", return_messages=True)
)

프롬프트가 곧 우선순위 결정 정책입니다. 주목할 대목이 셋입니다.

  1. 자연어를 우선순위로 매핑 — “긴급”, “즉시”, “최우선” 같은 표현을 P0으로 번역하라고 명시한다.
  2. 누락 정보에 대한 기본값 — 정보가 없으면 P1 / Worker A라는 합리적 기본값을 배정한다. 사용자에게 되묻느라 멈추지 않는다.
  3. 17장의 ReAct 루프create_react_agentagent_scratchpad로 사고-행동-관찰을 반복하고, 8장ConversationBufferMemory로 맥락을 이어 간다.

두 가지 시나리오

# --- 4. 간단한 상호작용 흐름 ---
async def run_simulation():
    print("--- Project Manager Simulation ---")

    # 시나리오 1: 새로운 긴급 기능 요청 처리
    print("\n[User Request] I need a new login system implemented ASAP. "
          "It should be assigned to Worker B.")
    await pm_agent_executor.ainvoke(
        {"input": "새 로그인 시스템 구현 작업을 생성하라. 긴급하며 Worker B에게 배정해야 한다."}
    )
    print("\n" + "-"*60 + "\n")

    # 시나리오 2: 세부 정보가 적은 덜 긴급한 콘텐츠 업데이트 처리
    print("[User Request] We need to review the marketing website content.")
    await pm_agent_executor.ainvoke(
        {"input": "새 작업을 등록하고 관리하라: 마케팅 웹사이트 콘텐츠 검토."}
    )
    print("\n--- Simulation Complete ---")

# 시뮬레이션 실행
if __name__ == "__main__":
    asyncio.run(run_simulation())

두 시나리오가 대비를 이룹니다. 하나는 담당자가 지정된 긴급 작업(ASAP → P0, Worker B), 다른 하나는 최소한의 정보만 주어진 덜 긴급한 작업(기본값 P1, Worker A)입니다. AgentExecutor에서 verbose=True 가 활성화되어 있어 에이전트의 행동과 논리 과정이 콘솔에 출력됩니다.

정리

우선순위 결정이란 무엇인가

복잡한 환경에서 동작하는 AI 에이전트는 수많은 선택지, 상충하는 목표, 한정된 자원에 직면합니다. 다음 행동을 결정할 명확한 방법이 없으면 에이전트는 비효율적이 되고 효과도 떨어집니다. 그 결과 운영이 크게 지연되거나 주요 목표를 아예 달성하지 못할 수 있습니다.

우선순위 결정 패턴의 핵심 과제는 이처럼 압도적으로 많은 선택지를 관리해 에이전트가 목적에 맞고 논리적으로 행동하도록 만드는 데 있다.

왜 사용하는가?

우선순위 결정 패턴은 에이전트가 작업과 목표 순위를 매길 수 있게 하여 이 문제를 해결하는 표준화된 방법을 제시합니다. 이를 위해 긴급성, 중요도, 의존 관계, 자원 비용 같은 명확한 기준을 세웁니다. 그런 다음 에이전트는 각 선택지를 이 기준에 비추어 평가하고, 가장 중요하고 시의적절한 행동 방침을 정합니다. 이 능력 덕분에 시스템은 상황 변화에 그때그때 적응하면서 제한된 자원을 효과적으로 관리할 수 있습니다.

언제 사용해야 하는가?

에이전틱 시스템이 자원 제약 아래에서, 흔히 서로 상충하는 여러 작업이나 목표를 자율적으로 관리하면서 변화하는 환경에서도 효과적으로 동작해야 할 때 우선순위 결정 패턴을 사용합니다.

우선순위 결정 패턴 개요도

 ┌───────────┐      ┌─────────────┐      ┌──────────────┐
 │  프롬프트   ├─────▶│  에이전트    ├─────▶│  우선순위 1   │
 └─────▲─────┘      └──────┬──────┘      │  우선순위 2   │
       │                   │             │  우선순위 3   │
       │                   ▼             │  우선순위 4   │
 ┌─────┴─────┐      ┌─────────────┐      │  우선순위 n   │
 │   사용자   │◀─────┤    출력      │      └──────────────┘
 └───────────┘      └─────────────┘
                 그림 20.1 우선순위 결정 디자인 패턴

핵심 정리

  • 우선순위 결정은 AI 에이전트가 복잡하고 다면적인 환경에서 효과적으로 동작할 수 있게 한다.
  • 에이전트는 긴급성, 중요도, 의존 관계 같은 미리 정한 기준을 사용해 작업을 평가하고 순위를 매긴다.
  • 상황에 따른 재우선순위 결정을 통해 에이전트는 실시간 변화에 대응해 운영 초점을 조정할 수 있다.
  • 우선순위 결정은 상위 전략 목표부터 즉각적인 전술 판단까지 다양한 수준에서 이루어진다.
  • 우선순위를 잘 정하면 AI 에이전트의 효율이 높아지고 운영 안정성도 높아진다.

마치며

우선순위 결정 패턴은 효과적인 에이전틱 AI의 핵심 축이며, 시스템이 변화하는 환경의 복잡성을 분명한 목적의식과 판단력을 바탕으로 헤쳐 나갈 수 있게 합니다. 이 패턴을 통해 에이전트는 수많은 상충하는 작업과 목표를 자율적으로 평가하고, 제한된 자원을 어디에 집중할지 합리적으로 판단할 수 있습니다.

이 능력은 단순히 주어진 작업을 실행하는 수준을 넘어, 시스템이 주도적이고 전략적인 결정을 내리게 한다. 긴급성, 중요도, 의존 관계 같은 기준을 종합적으로 따져 보면서 에이전트는 사람에 가까운 정교한 추론 과정을 보여 준다.

이 에이전틱 행동의 핵심 특징은 상황에 따른 재우선순위 결정입니다. 이를 통해 상황이 바뀌면 에이전트가 실시간으로 초점을 조정할 수 있습니다. 코드 예제에서 확인할 수 있듯이, 에이전트는 모호한 요청을 해석하고, 적절한 도구를 스스로 골라 사용하며, 목표 달성을 위해 동작 순서를 논리적으로 정합니다.

이처럼 워크플로를 스스로 관리하는 능력이 바로 진정한 에이전틱 시스템과 단순한 자동화 스크립트를 가르는 차이다. 결국 어떤 복잡한 현실 환경에서도 효과적이고 안정적으로 동작하는 견고하고 지능적인 에이전트를 만들려면, 우선순위 결정을 능숙하게 다룰 수 있어야 한다.

참고 문헌