에이전트(Agents) vs 스크립트(Scripts): 무엇을 선택해야 할까요?
요약
자동화 작업 수행 시 에이전트와 스크립트 중 무엇을 선택해야 하는지에 대한 가이드를 제공합니다. 제어 흐름을 개발자가 결정하는 스크립트와 모델이 실행 시점에 결정하는 에이전트의 차이점을 정의하고 비교합니다.
핵심 포인트
- 스크립트는 개발자가 제어 흐름을 직접 결정하며 예측 가능성이 높음
- 에이전트는 모델이 도구 사용 순서와 완료 시점을 스스로 결정함
- 단순 반복 작업에는 비용이 저렴하고 디버깅이 쉬운 스크립트가 유리함
- 규칙 기반 스크립트는 언어적 변형(예: dies on me) 대응에 한계가 있음
서론 (Intro)
지원 이메일을 분류하거나 파일 이름을 변경하는 것과 같이 지루하고 반복적인 작업을 자동화하고 싶을 때, 요즘 사람들의 본능은 "이걸 위해 에이전트(agent)를 만들자"입니다.
놀라운 점은, 2년 전만 해도 여러분은 30줄짜리 스크립트(script)를 작성하고 일을 끝냈을 것이라는 사실입니다.
오늘날 에이전트는 새롭게 만들어보고 싶은 재미있는 대상이 되었지만, 사람들은 단순한 스크립트가 많은 일을 처리할 수 있다는 사실을 잊은 듯합니다. 많은 이들이 이를 위해 에이전트를 만들고 있습니다. 작업을 달성하기 위해 어떤 것을 선택할지 아는 것은 시간과 비용, 그리고 수많은 디버깅(debugging)을 아껴줄 것입니다. 이 블로그에서는 자동화된 작업을 통해 에이전트와 스크립트 사이의 의사결정 과정을 안내해 드리겠습니다.
우리는 실제로 무엇이 필요한지 알아내기 위해 이 작업을 세 가지 방식으로 구축해 볼 것입니다. 아래의 작업을 검토해 봅시다:
여러분은 작은 SaaS 기업을 운영하고 있으며, 현재 지원 이메일 수신함에 메일이 쌓이고 있습니다. 여러분은 들어오는 이메일이 자동으로 분류되어 결제 관련 질문은 결제 팀으로, 버그 보고는 엔지니어링 팀으로 전달되고, "해지하고 싶어요"라는 메일은 고객이 이탈하기 전에 미리 표시되기를 원합니다.
정의 (Definitions)
사람들은 LLM(Large Language Model)과 관련된 것이라면 무엇이든 에이전트라고 부르곤 합니다. 따라서 비교를 시작하기 전에, 제어 흐름(control flow)을 누가 제어하느냐에 따라 스크립트와 에이전트가 어떻게 작동하는지 배워봅시다.
스크립트에서는 여러분이 제어 흐름을 결정합니다. 에이전트에서는 모델이 실행 시점(runtime)에 제어 흐름을 결정합니다.
스크립트는 LLM을 10번 호출하더라도 여전히 스크립트일 수 있습니다. 왜냐하면 여러분이 그 호출이 일어나는 순서를 작성했기 때문입니다. 하지만 에이전트의 경우, 모델에게 도구(tools) 세트와 목표를 전달하면 모델이 무엇을, 어떤 순서로 호출할지, 그리고 언제 작업이 완료될지를 결정합니다. 미리 정해진 엄격한 경로가 존재하지 않습니다.
스크립트 (The script)
작동할 수 있는 가장 단순한 것부터 시작해 봅시다.
import re
RULES = [
...
이것은 키워드 규칙(keyword rules) 목록으로, 각 규칙은 단어 세트와 해당 단어가 가리키는 팀을 보유하고 있습니다. 분류(Triage) 작업은 이메일을 위에서 아래로 각 규칙과 대조하며, 가장 먼저 일치하는 규칙의 팀을 반환하고, 어떤 것과도 일치하지 않는 것은 unsorted로 넘어갑니다.
이 스크립트는 마이크로초(microseconds) 단위로 실행되며, 비용이 거의 들지 않고, 매번 동일한 이메일을 동일한 방식으로 라우팅합니다. 즉, 만약 이메일이 잘못된 팀으로 전송된다면 어떤 규칙이 실행되었는지 정확히 파악하여 해당 한 줄을 수정할 수 있다는 의미입니다. 게다가, 우리는 이를 테스트할 수 있고 한눈에 읽을 수 있습니다.
이제 더 현실적인 시나리오를 살펴보겠습니다. 어느 날 아침, 한 고객이 다음과 같은 메시지를 보냅니다.
"저기요, 결제(pay) 버튼을 누를 때마다 전체 시스템이 그냥 죽어버려요(dies on me)."
이것은 실제 버그 보고 이메일이지만, 그 안에 담긴 단어들을 살펴보십시오. 고객은 "pay button"과 "dies on me"라고 말하고 있지만, 결제(billing) 규칙은 전체 단어인 "payment"만 일치시키며 "pay"는 절대 일치시키지 않습니다. 반면 엔지니어링(engineering) 규칙은 "crash", "broken", 또는 "error"를 감시하고 있지 "dies"는 감시하지 않습니다. 이 중 어떤 것도 규칙과 일치하지 않으므로, 결과적으로 이 충돌(crash) 보고서는 결제 팀이나 엔지니어링 팀의 누구도 검토할 수 없는 unsorted로 떨어지게 됩니다.
코드가 우리가 지시한 대로 정확히 작동했기 때문에, 이를 코드상의 버그로 분류할 수는 없습니다. 하지만 문제는 이 작업이 문맥(context)을 이해하여 상대방이 실제로 무엇을 의미하는지 파악하는 것과 같이, 규칙으로 써 내려갈 수 없는 단계들을 간과하고 있다는 점입니다.
동일한 요청을 표현하는 방법은 무궁무진하기 때문에, 정규 표현식(regex)은 당신이 입력한 몇 안 되는 패턴만을 알 뿐입니다. 새로운 키워드를 계속 덧붙일 수는 있겠지만, 그것은 영어라는 언어 전체를 상대로 두더지 잡기(whack-a-mole)를 하는 것과 같습니다. 한 번에 하나의 키워드만을 쫓는 것은 실질적인 이득 없이 계속해서 커져만 가는 코드베이스(codebase) 위에서 매우 지루한 수동 작업이 될 뿐입니다.
코드 내의 키워드는 결코 문맥을 인지(context aware)하거나 의미를 이해할 수 없지만, 사실 그것이 바로 모델이 잘 수행하는 단 하나의 작업입니다. 여기서 우리는 스크립트에서 에이전트(agent)로 전환하는 것을 고려하기 시작합니다.
에이전트 (The agent)
에이전트(agent)의 경우, 모델에게 일련의 도구(tools)를 전달하고 모델이 그 많은 도구들을 사용하여 티켓 프로세스 전체를 처음부터 끝까지 관리하도록 합니다. 도구(tool)란 고객 조회, 버그 티켓 생성 또는 답장 전송과 같이 여러분이 작성하는 일반적인 함수(function)일 뿐이며(이 또한 논쟁의 여지는 있지만 하나의 스크립트(script)일 수 있습니다), 모델이 필요하다고 판단할 때 스스로 해당 함수를 실행할 수 있도록 모델이 사용할 수 있게 제공합니다. 하지만 스크립트(script)에서 하는 것처럼 이러한 함수들을 고정된 순서로 호출하는 것이 아니라, 대신 함수들을 넘겨주고 모델이 어떤 것을 사용할지 선택하게 합니다.
tools = [
look_up_customer, # 발신자의 이메일로 계정 찾기
get_recent_orders, # 결제 내역 가져오기
...
위의 목록에서 각 줄은 그러한 함수 중 하나이며, 옆의 주석은 해당 함수가 무엇을 하는지 설명합니다. 따라서 모델은 이메일을 보낸 사람이 누구인지 조회하는 것부터 실제 환불을 처리하는 것까지 총 6개의 함수를 갖게 됩니다.
이제 모델은 자체적인 루프(loop)를 실행할 수 있습니다. 이메일을 읽고, look_up_customer를 호출하기로 결정하고, 결과를 읽고, 해당 청구가 실제 중복 결제임을 파악한 뒤, issue_refund를 호출하고, 이어서 reply_to_customer를 호출한 다음 멈춥니다.
또한 모델은 동일한 이메일을 읽고 자신의 권한 밖의 일이라고 판단하여 첫 번째 단계에서 바로 escalate_to_human을 호출할 수도 있습니다. 여기서 핵심은 여러분이 이 두 가지 경로 중 어느 것도 직접 지시하거나 하드 코딩(hard code)할 필요가 없었다는 점입니다. 모델이 런타임(runtime)에 이를 선택할 수 있도록 자율적으로 결정을 내릴 수 있었기 때문입니다.
모델은 패턴을 매칭(matching)하는 대신 메시지의 문맥(context)을 읽고 이해하기 때문에, 중복 결제 오류 이메일을 아무런 어려움 없이 처리합니다. 따라서 모든 분기(branch)를 사전에 나열할 수 없는 개방형 작업(open-ended task)의 경우, 이것이 에이전트(agent)에 더 적합한 접근 방식입니다.
이러한 접근 방식 또한 완벽하지는 않습니다. 이제 우리는 실제 돈을 조작하는 issue_refund라는 도구를 확률적 시스템(probabilistic system)에 넘겨주었기 때문입니다. 모델은 비결정론적 시스템(non-deterministic systems)이므로, 어느 정도의 확률로 호출해서는 안 될 티켓에 대해 해당 도구를 호출할 것입니다. 에이전트에게 부여하는 모든 능력은, 에이전트가 스스로 결정을 내릴 때 대규모로 확신에 찬 오류(confidently wrong)를 범할 수 있는 또 다른 경로가 되기 때문입니다.
에이전트(Agents) 사용의 함정
"에이전트는 비용이 많이 든다"라는 말은 자주 반복되지만, 실제 의사결정을 내리기에는 너무 모호합니다. 따라서 여러분이 실제로 무엇에 대해 비용을 지불하고 있는지 세분화해 보겠습니다.
첫 번째는 **지연 시간(latency)**입니다. 정규 표현식(regex) 스크립트는 마이크로초(microseconds) 단위로 실행되지만, 단일 모델 호출은 수백 밀리초에서 수 초 사이로 실행됩니다. 그러나 6번의 도구 호출(tool-calling) 왕복 과정을 거치는 에이전트의 경우, 각 단계마다 모델을 기다려야 하고, 데이터베이스 쿼리나 API와 같은 도구를 기다려야 하며, 결과를 다시 전달받은 후 또다시 모델을 기다려야 합니다.
이메일 하나를 분류(triage)하는 데 10초가 걸리는 것이 백그라운드 큐 작업(background queue job)이라면 세상이 무너질 일은 아니지만, 사람이 앉아서 기다려야 하는 작업이라면 이는 이상적이지 않습니다.
지연 시간과 밀접하게 연관된 것은 **비용(cost)**입니다. 에이전트는 단계마다 한 번만 비용을 지불하는 것이 아니라, 매 단계마다 전체 대화 내용에 대해 다시 비용을 지불합니다. 첫 번째 턴(turn)에서 모델은 이메일을 읽고, 두 번째 턴에서는 이메일과 첫 번째 결정 및 도구 결과값을 다시 읽으며, 여섯 번째 턴에 이르면 매 턴마다 이전의 모든 내용을 다시 처리하게 됩니다. 토큰 사용량은 단계 수에 따라 선형적(linearly)이 아니라 대략 이차 함수적(quadratically)으로 증가합니다. 따라서
다음으로는 결정론(determinism), 혹은 그 결여에 대한 문제입니다. 동일한 이메일이라도 두 번의 실행에서 서로 다른 두 가지 방식으로 라우팅될 수 있습니다. 온도를 0(temperature to zero)으로 설정하는 것이 도움이 될 수는 있지만, 이를 완전히 해결하지는 못합니다. 도구 결과(tool results)가 컨텍스트(context)로 다시 피드백되어 다음 결정을 변화시키기 때문이며, 모델 버전 업데이트가 하룻밤 사이에 동작을 바꿀 수도 있기 때문입니다. 단순한 분류(triaging) 작업이라면 이러한 현상을 무시할 수 있겠지만, 금전 거래나 데이터 삭제와 같이 중대한 작업의 경우 비결정론(non-determinism)은 리스크(liability)가 됩니다.
마지막은 "디버깅 가능성(debuggability)"입니다. 정규 표현식(regex)이 잘못 라우팅되면 특정 라인을 가리키는 스택 트레이스(stack trace)를 얻을 수 있지만, 에이전트가 잘못 라우팅하면 대화 기록(transcript)을 앉아서 해석해야 하며, 심지어 정확히 동일한 입력값으로 다음 실행을 해도 재현되지 않을 수도 있습니다. 또한 에이전트는 중단점(breakpoint)을 사용하여 디버깅하는 것이 아니라, 트레이싱(tracing), 평가 세트(eval sets), 가드레일(guardrails)을 통해 디버깅해야 하며, 이는 당신이 책임져야 할 하나의 온전한 전문 분야입니다.
이것이 에이전트를 피해야 한다는 뜻은 아닙니다. 다만 에이전트가 중요한 작업을 수행하는 시스템 내부에서 작동할 때, 당신이 무엇을 떠맡게 되는지를 이해해야 한다는 의미입니다.
절충안 (The middle ground)
스크립트가 왜 고장 났는지로 다시 돌아가 봅시다. 스크립트는 이메일의 의미를 이해하려고 시도할 때 고장 났습니다. 그 외의 모든 것, 즉 라우팅(routing), 우선순위 로직(priority logic), 누구에게 페이지를 보낼지 결정하는 작업 등은 일반적인 코드(plain code)로서 아무런 문제가 없었습니다. 따라서 이상적인 워크플로우를 위해 스크립트 전체를 교체할 필요는 없습니다. 단지 고장이 발생한 그 한 단계에만 모델을 교체하여 끼워 넣고, 나머지는 그대로 두면 됩니다.
from pydantic import BaseModel
from typing import Literal
from openai import OpenAI
...
이제 모델이 당신이 일일이 분석해야 하는 문단 형태로 답변하게 두는 대신, 채워 넣을 수 있는 형태(shape)를 제공하십시오. 팀, 우선순위, 그리고 짧은 이유를 전달하면, 정확히 해당 필드들을 가진 타입이 지정된 객체(typed object)를 돌려받게 됩니다.
여기서 모델은 이 이메일이 무엇에 관한 것인지라는 질문에만 답할 뿐, 단 하나의 도구(tool)도 건드리지 않습니다. 고객을 조회하거나, 티켓(ticket)을 열거나, 환불을 처리하지도 않습니다. 그저 텍스트를 읽고 세 개의 필드(field)를 반환하면, 그 이후부터는 여러분의 코드가 제어권을 이어받습니다.
모델은 이중 청구 오류(double-bill-crash) 이메일을 읽고, 이유와 함께 'engineering' 및 'high'를 반환합니다. 이번에는 키워드를 찾기 위해 'grep'하듯 훑는 대신 메시지를 실제로 이해했기 때문입니다. 이전에 unsorted로 사라져 버리곤 했던 회계 요청(accounting request) 또한 이제는 다른 모든 이메일과 마찬가지로 읽히고 분류됩니다.
우리는 스크립트(script)를 수정하여 모델을 통합했지만, 여기서 가장 중요한 점은 여러분이 여전히 제어 흐름(control flow)을 소유하고 있다는 것입니다.
result = triage(email)
route_to(result.team)
...
루프(loop)가 없기 때문에, 모델이 issue_refund를 수행할 방법은 없습니다. 모델에게 그런 능력을 부여한 적이 없으며, 오직 여러분이 정의한 필드들을 채울 수만 있기 때문입니다. 비용은 이메일당 한 번의 제한된 호출(bounded call)이며, 지연 시간(latency)은 한 번의 왕복(round trip)입니다.
완전히 결정론적(deterministic)이지는 않습니다. 모델이 여전히 잘못 분류할 수도 있지만, 잘못된 답변이 미치는 영향 범위(blast radius)는 "이메일이 잘못된 대기열(queue)로 전송됨"이지, "계좌에서 돈이 빠져나감"이 아닙니다. 모델이 틀렸을 때는 입력값과 출력값을 기록하고, 이를 나란히 비교한 뒤 프롬프트(prompt)나 스키마(schema)를 조정하면 됩니다. 이는 실제로 감당할 수 있는 디버깅(debugging) 프로세스입니다.
이것이 "에이전트(agent)를 사용해야 할까요?"라는 압도적 다수의 질문에 대한 해답입니다. 항상 모델이 주도권을 가져야 하는 것은 아니지만, 코드가 처리할 수 없는 단계들을 처리하기 위해 모델을 활용한 뒤, 즉시 제어권을 다시 돌려받을 수 있습니다.
그렇다면, 어떤 것을 선택해야 할까요?
제가 실제로 순서대로 검토하는 결정 기준은 다음과 같습니다:
-
모든 단계와 모든 분기(branch)를 사전에 작성할 수 있습니까? 만약 그렇다면, 스크립트 (script)를 작성하세요. 단순히 최신 기술처럼 보이고 싶어서 에이전트 (agent)를 추가하지 마세요.
-
코드로 표현할 수 없는 단계가 정확히 하나뿐입니까? 보통 "이 지저분한 입력을 이해하라" 또는 "이 텍스트를 생성하라"와 같은 단계입니다. 그렇다면 그것은 모델 호출 (model calls)이 포함된 스크립트이며, 구조화된 출력 (structured output)과 타입이 지정된 결과 (typed result)를 통해 다음 동작은 여전히 귀하의 코드가 결정하게 됩니다. 이것이 가장 이상적인 지점 (sweet spot)이며, 오늘날 의외로 제대로 활용되지 않고 있습니다.
-
런타임 (runtime) 전까지는 알 수 없는 정보에 기반하여, 여러 도구 (tools)를 가로질러 무엇을 어떤 순서로 할지 결정해야 하며, 그 경로를 열거할 수 없는 작업입니까? 이것이 바로 에이전트 (agent)를 구축해야 하는 지점입니다. 작동 가능한 가장 좁은 범위의 도구 세트만 제공하고, 되돌릴 수 없는 작업 앞에는 반드시 사람을 배치하며, 첫날부터 추적 (tracing) 및 평가 (evals) 환경을 설정하세요.
저희의 고객 지원 편지함 (support inbox)의 경우, 가장 좋은 답은 두 번째 옵션입니다. 모델이 이메일을 읽고 귀하의 코드가 나머지를 처리합니다. 시스템이 실제로 티켓을 해결하고, 주문을 조회하며, 환불이 정당한지 판단하고, 답장 초안을 작성하며, 언제 사람이 개입해야 하는지 결정하기를 원하는 날이 온다면, 그때는 에이전트 (agent)를 사용해야 하며, 그것도 매우 정교한 에이전트가 필요할 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기