
무엇이 LLM을 에이전트로 만드는가: 모델이 제어 흐름(Control Flow)을 제어한다
요약
LLM 기반 시스템에서 '워크플로'와 '에이전트'를 구분하는 핵심 기준은 제어 흐름(Control Flow)의 주체가 누구인지에 있습니다. 모델이 실행 경로를 동적으로 결정하면 에이전트이며, 코드가 경로를 미리 정의하면 워크플로로 분류됩니다.
핵심 포인트
- 에이전트의 핵심은 모델이 제어 흐름을 소유하는 것
- 워크플로는 미리 정의된 코드 경로를 통해 LLM을 오케스트레이션함
- 에이전트는 추론 시간에 동적으로 실행 형태를 결정함
- Anthropic의 'Building effective agents' 개념을 바탕으로 함
- 도서: Agents in Production — Building, Tracing, and Shipping Multi-Step AI You Can Trust
- 저자의 다른 저서: Observability for LLM Applications — The AI Engineer's Library (2권 시리즈)의 동반 도서
- 나의 프로젝트: Hermes IDE | GitHub — Claude Code 및 기타 AI 코딩 도구를 사용하여 작업하는 개발자를 위한 IDE
- 나에 대하여: xgabriel.com | GitHub
당신은 agent-rewrite라는 이름의 PR(Pull Request)에 대한 세 번째 리뷰어입니다. 당신은 diff를 열고 ResearchAgent라는 클래스를 읽습니다. 이 클래스는 LLM을 호출하고, 응답을 파싱하며, 6개의 도구 중 하나로 디스패치(dispatch)하고, 결과를 리스트에 추가한 뒤 다시 LLM을 호출합니다. max_steps=12가 설정되어 있습니다. YAML에서 로드된 시스템 프롬프트(system prompt)가 있습니다. 모든 줄이 합리적입니다.
그다음, 이 PR이 교체하려는 기존 파이프라인(pipeline)을 엽니다. 여기에는 classify_intent라는 함수가 있습니다. 이 함수는 LLM을 호출하여 6개의 레이블 중 하나를 받아오고, switch 문이 각 레이블을 핸들러(handler)로 라우팅합니다. 루프(loop)는 없습니다. max_steps도 없습니다. 하지만 LLM은 여전히 맨 위에서 무엇을 할지 한 번 결정합니다.
두 파일 모두 LLM을 호출합니다. 둘 다 도구(tools)를 가지고 있습니다. 하지만 왜 하나는 "에이전트(agent)"이고 다른 하나는 "단순한 파이프라인(just a pipeline)"인지 한 문장으로 말할 수 없습니다. 그 차이가 이 포스트의 핵심입니다.
단 하나의 속성
에이전트는 모델이 제어 흐름(control flow)을 소유하는 시스템입니다.
이것이 유일한 차이점입니다. 만약 모델이 다음에 어떤 일이 일어날지를 결정한다면, 당신은 에이전트를 가진 것입니다. 만약 당신의 코드가 다음에 어떤 일이 일어날지를 결정하고 모델이 빈칸을 채우는 것이라면, 당신은 LLM 호출이 포함된 파이프라인을 가진 것입니다. 둘 다 실제로 사용됩니다. 둘 다 유용합니다. 하지만 둘을 동일한 방식으로 운영할 수는 없습니다.
이 기준은 제가 만든 것이 아닙니다. Anthropic의 2024년 12월 포스트인 Building effective agents에서 제시한 것입니다.
워크플로 (Workflows)는 미리 정의된 코드 경로 (code paths)를 통해 LLM과 도구들이 오케스트레이션 (orchestrated)되는 시스템입니다. 반면, 에이전트 (Agents)는 LLM이 자신의 프로세스와 도구 사용을 동적으로 (dynamically) 직접 지시하며, 작업을 완수하는 방식에 대해 제어권을 유지하는 시스템입니다.
핵심이 되는 단어는 바로 _동적으로 (dynamically)_입니다. 워크플로에서는 모델이 호출되기 전에 실행의 형태가 이미 존재합니다. 코드 경로를 grep으로 검색할 수 있습니다. 화이트보드에 그릴 수도 있으며, 그 그림은 실제 실행과 일치합니다. 에이전트에서는 모델이 가진 도구와 관찰하고 있는 내용으로부터, 추론 시간 (inference time)에 한 단계씩 형태가 조립됩니다. 모델이 형태를 결정하기 전까지는 실행 형태가 존재하지 않으며, 동일한 입력에 대해서도 오늘의 결정이 내일의 결정과 같지 않을 수 있습니다.
두 파일의 핵심 비교
다음은 뼈대만 남긴 기존의 파이프라인 (pipeline)입니다. 이것은 라우터 (router)입니다.
def handle(request):
label = classify(
request,
...
단 한 번의 LLM 호출. 단 하나의 분기 (branch). 모델은 6개 중 하나의 레이블 (label)을 선택합니다. 여러분의 코드가 핸들러 (handler)를 선택합니다. 실행 전 냅킨 위에 실행 그래프 (execution graph)를 그려 놓는다면, 매번 정확할 것입니다. 이것은 내부에 LLM을 포함하고 있지만, 에이전트는 아닙니다.
다음은 동일한 크기로 축소한 새로운 ResearchAgent입니다.
def handle(request):
state = {"request": request, "history": []}
for step in range(MAX_STEPS):
...
코드 줄 수는 같습니다. 하지만 차원이 다릅니다. 모델은 루프 (loop) 안에서 호출됩니다. 매 턴마다 모델은 상태 (state)를 읽고, 도구를 선택하거나 작업이 완료되었음을 선언하며, 루프가 진행됩니다. 그래프는 실행 전에 그려지는 것이 아니라, 실행이 끝난 결과물로서 나타나는 그래프입니다. 모델은 도구를 하나만 호출할 수도 있고, 12개를 호출할 수도 있습니다. 혹은 이전 결과를 잘못 읽어서 동일한 도구를 세 번 호출할 수도 있습니다. MAX_STEPS는 구조 (structure)가 아니라 안전벨트 (seatbelt) 역할을 합니다.
가장 중요한 질문 하나를 던져보십시오. 첫 번째 파일에서 다음 단계를 결정하는 것은 무엇입니까? DISPATCH가 결정합니다. 두 번째 파일에서 다음 단계를 결정하는 것은 무엇입니까? decide가 결정합니다. 답이 다르며, 시스템도 다릅니다.
decide의 실제 모습
그 decide 호출은 모델이 운전대를 잡는 지점입니다. Anthropic SDK를 사용하면, 이는 도구 (tools)가 첨부된 단일 메시지이며 매 턴마다 실행 이력 (running history)이 재현됩니다.
import anthropic
client = anthropic.Anthropic()
...
모델은 최종 텍스트 답변 또는 하나 이상의 tool_use 블록을 반환합니다. 귀하의 코드는 도구를 실행하고, 결과를 추가한 뒤, 다시 decide를 호출합니다. 귀하는 "검색 후에 요약하라"라고 말하는 분기 (branch)를 작성한 적이 없습니다. 모델이 이전 도구의 출력을 포함하는 이력을 바탕으로, 런타임 (runtime)에 직접 작성한 것입니다.
def run(user_input):
messages = [{"role": "user", "content": user_input}]
for _ in range(MAX_STEPS):
...
무엇이 빠져 있는지 주목하십시오. 인간이 두 번째 도구 호출이 무엇이 되어야 할지 선택하는 곳은 어디에도 없습니다. 그 선택은 추론 시간 (inference time)에 발생하며, 귀하가 훈련시키지 않은 모델이, 귀하가 부분적으로만 제어할 수 있는 프롬프트 (prompt)를 바탕으로, 다음 결정으로 피드백되는 도구 출력들을 읽으며 내리는 결정입니다.
이것이 귀하의 직관을 깨뜨리는 이유
파이프라인을 디버깅하며 쌓아온 모든 반사 신경은 귀하의 코드가 다음에 일어날 일을 선택했다는 가정하에 작동합니다. 그것이 동작을 재현 가능하게 만들고, 테스트를 의미 있게 만들며, 사후 분석 (postmortems)을 가능하게 했던 요소입니다. 귀하는 입력을 변경하고, 분기를 읽고, 버그를 찾아냈습니다.
모델이 제어 흐름 (control flow)을 소유하게 되면, 그 반사 신경은 멈춥니다. 동일한 입력이라도 실행할 때마다 서로 다른 실행 그래프 (execution graphs)를 생성할 수 있습니다. 버그는 시작된 단계에서는 보이지 않다가, 각각은 문제가 없어 보였던 네 번의 도구 호출을 거친 뒤 다섯 단계 후에야 비로소 드러날 수 있습니다. 귀하의 핸들러 단위 테스트 (handler unit tests)는 100% 통과할 수 있지만, 시스템은 엔드 투 엔드 (end to end)로 실행될 때 40%의 확률로 실패할 수 있습니다. 왜냐하면 실패는 개별 요소가 아니라, 모델이 선택한 _순서 (order)_에 존재하기 때문입니다.
이것들은 가설이 아닙니다. anthropics/claude-code의 GitHub 이슈 #44726에서 한 사용자는 일반적인 5:1에서 15:1 범위를 벗어나, 입력/출력 토큰 비율이 74:1 및 175:1에 달하는 세션을 보고했습니다. 이는 도구 호출 (tool calls) 과정에서 누적된 히스토리와 파일 컨텍스트가 무제한으로 성장하는 복합 루프 (compounding loop) 때문인 것으로 분석되었습니다. 동일한 리포지토리의 다른 사용자 이슈들은 무한 압축 루프 (infinite compaction loops)와 대량의 메모리를 소비하는 폭주하는 쓰기 루프 (runaway write loops consuming large amounts of memory)를 보고하고 있습니다. 이러한 실패 모드 (failure modes) 중 그 어느 것도 라우터 (router)에서는 존재하지 않습니다. 이들은 모델이 제어 흐름 (control flow)을 소유하는 시스템에서 발생합니다.
물론 파이프라인 (pipeline)도 무한 루프를 작성한다면 영원히 돌 수 있습니다. 차이점은 실패가 어디에 존재하느냐입니다. 파이프라인에서 실패는 작성 시점 (authoring time)에 존재합니다. 코드 리뷰 (code review) 단계에서 이를 발견하고 break 문으로 수정할 수 있습니다. 반면 에이전트 (agent)에서 루프는 당신이 의도적으로 작성한 기능이며, 종료 여부를 결정하는 주체는 모델입니다. 코드 리뷰만으로는 이 문제를 해결할 수 없습니다.
이미 보유한 코드에 대한 테스트
당신의 팀이 "에이전트"라고 부르는 것을 다음 세 가지 질문에 통과시켜 보십시오.
다음 단계를 누가 선택하는가? 실제 요청 하나를 추적해 보십시오. 도구가 반환된 후의 각 전이 (transition)마다, 당신의 Python 코드가 다음 도구를 선택했는지 아니면 모델이 선택했는지 물어보십시오. 매번 모델이 선택한다면 에이전트입니다. 만약 dispatch.py의 스위치 (switch)가 선택한다면 워크플로우 (workflow)입니다.
언제 멈출지를 누가 결정하는가? 워크플로우에서 종료는 그래프 (graph) 상의 하나의 에지 (edge)입니다. 에이전트에서 종료는 모델이 매 턴마다 내리는 결정이며, max_steps는 모델이 결정을 내리지 못할 때 이를 잡아주는 안전벨트입니다. 만약 운영 환경 (production)에서 이 안전벨트가 작동한 적이 있다면, 당신은 에이전트를 가지고 있는 것입니다.
실행 전(run)에 실행 그래프 (execution graph)를 그릴 수 있는가? 만약 그 형태가 당신의 코드에 의해 결정된다면 워크플로우입니다. 만약 당신이 할 수 있는 최선이 루프를 그리고 분기점 위에 "모델이 선택함"이라고 적는 것이라면, 에이전트입니다.
Anthropic은 주목할 만하게도, 이 분야에서 가장 보수적인 목소리를 내고 있습니다. 동일한 게시물에서 그들은 먼저 가장 단순한 해결책을 찾고, 필요할 때만 복잡성을 높이라고 조언하며, 이는 "에이전트 시스템을 아예 구축하지 않는 것을 의미할 수도 있습니다." 이름에 "에이전트"가 포함된 대부분의 시스템은 워크플로우 (Workflow)여야 합니다. 작업의 형태를 사전에 진정으로 알 수 없을 때만 에이전트를 구축하십시오. 그렇지 않다면 구축하지 마십시오. 그리고 당신이 무엇을 출시했는지에 대해 누구에게도 거짓말하지 마십시오.
당신의 팀이 에이전트라고 불러온 파일을 여십시오. 루프 (Loop)를 찾으십시오. 종료 조건 (Exit condition)을 읽으십시오. 누가 그것을 작성했는지 묻고, 그다음 런타임 (Runtime)에 실제로 언제 실행될지를 누가 결정하는지 물으십시오.
만약 당신이 Anthropic이 긋는 선을 따른다면, 그 대가는 그들이 예측하는 실패 목록입니다: 통제 불능의 루프 (Runaway loops), 컨텍스트 길이 (Context length)에 종속되는 비용, 레지스트리 (Registry)가 커짐에 따라 저하되는 도구 선택 (Tool selection). _Agents in Production_은 그 목록의 구축 및 출시 측면을 다룹니다 — 루프 예산 (Loop budgets), 안전벨트 (Seatbelts), 그리고 모델 주도 루프를 정직하게 유지하는 운영 습관들을 다룹니다. _Observability for LLM Applications_는 나머지 절반입니다: 이러한 비결정론적 (Non-deterministic) 실행을 추적하고, 재무 부서가 하기 전에 비용을 산정하는 것입니다. 두 권 모두 _The AI Engineer's Library_에 포함되어 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기