
실전 AI 에이전트 — 파트 4: 다섯 가지 에이전트 패턴과 안전성을 보장하는 제어 표면 (Control Surfaces)
요약
AI 에이전트의 실무적인 구현을 위해 다섯 가지 에이전트 패턴과 시스템의 안전성을 보장하는 제어 표면(Control Surfaces)의 개념을 다룹니다. 에이전트 루프의 형태와 프로덕션 환경에서 경계를 설정하는 조절 장치에 대해 설명합니다.
핵심 포인트
- 에이전트 패턴은 루프의 명명된 형태를 의미함
- 제어 표면은 에이전트의 호출 범위와 중단 조건을 결정하는 경계임
- 안전한 에이전트 구현을 위해 호출 배열, 데이터 전달, 중단 방식, 상태 관리가 필수적임
AI Agents in Practice 시리즈의 8개 파트 중 4번째 파트입니다.
이전 글 — 제어 루프(Control Loop)가 실제로 작동하는 방식 (파트 3)
파손된 노트북
TechNova의 한 고객이 메시지를 보냈습니다:
"노트북이 파손된 상태로 도착했습니다. 환불을 원합니다."
단 한 문장입니다. 하지만 실제로는 두 가지 요청이 담겨 있습니다. 하나는 명시적이고, 하나는 암시적입니다. 고객은 환불을 원합니다. 시스템은 이 환불이 실제로 적절한지, 그리고 적절하다면 지금 즉시 처리할지 아니면 다른 단계를 거친 후에 처리할지를 결정해야 합니다.
두 번째 작업에서 상황이 복잡해집니다. 응답이 나가기 전에 여러 가지 일이 일어나야 합니다. 주문 내역을 조회해야 합니다. 배송 상태와 파손 증거를 확인해야 합니다. 환불 및 교환 정책을 가져와야 합니다. 교환 가능한 재고를 확인해야 합니다. 시스템은 환불과 교환 중 무엇을 할지 결정해야 합니다. 만약 환불 금액이 특정 임계값(Threshold)을 넘는다면 사람이 승인해야 합니다. 그런 다음 정책상 허용되지 않는 내용을 약속하지 않도록 응답 초안을 작성해야 합니다.
이 사례에서 수행해야 할 일곱 가지 작업은 다음과 같습니다: 주문 조회, 배송 및 파손 증거 확인, 환불/교환 정책 검색, 재고 확인, 환불 대 교환 선택, 필요 시 승인 획득, 그리고 안전한 응답 초안 작성입니다.
파트 1에서는 시스템이 이 모든 것을 하나의 프롬프트(Prompt)로 처리하려고 할 때 어떤 일이 발생하는지 보여주었습니다. 에이전트는 확신에 차서 환불을 결정했지만, 일곱 가지 작업 중 네 가지를 놓쳤습니다. 파트 2에서는 무엇이 에이전트를 만드는지 정의했습니다. 즉, 모델이 다음 단계를 결정하고 언제 멈출지를 결정할 수 있는 루프(Loop)입니다. 파트 3에서는 루프(Loop), 상태(State), 컨텍스트(Context), 그리고 중단 조건(Stopping conditions)에 대해 살펴보았습니다.
이 글에서는 다음 질문을 던집니다. 그 루프가 취할 수 있는 일반적인 '형태(Shapes)'는 무엇인가? 그리고 그 형태들이 제품으로 출시할 만큼 충분히 안전한지를 결정하는 '노브(Knobs, 조절 장치)'는 무엇인가?
요약하자면: 에이전트 패턴(Agent patterns)은 루프의 명명된 형태입니다. 제어 표면(Control surfaces)은 그 형태가 얼마나 안전하고, 경계가 명확하며, 프로덕션(Production)에 투입할 준비가 되었는지를 결정합니다.
우리가 말하는 _제어 표면 (control surface)_이란 시스템이 에이전트 주변에 경계를 설정하는 지점을 의미합니다. 즉, 에이전트가 무엇을 호출할 수 있는지, 어떤 컨텍스트 (context)를 사용할 수 있는지, 언제 멈춰야 하는지, 그리고 언제 도움을 요청해야 하는지를 결정하는 곳입니다. 각 제어 표면은 해당 내용이 나올 때마다 정의하겠습니다.
각 패턴에 대해 다음 네 가지 실무적인 질문이 배경에 깔리게 됩니다: 호출이 어떻게 배열되는가, 호출 사이에 무엇이 전달되는가, 패턴이 어떻게 중단되는가, 그리고 어떤 상태 (state)나 메모리 (memory)를 앞으로 전달하는가입니다. 이 네 가지 질문을 심도 있게 다루지는 않겠지만, 각 패턴별 섹션에서 자연스럽게 답변될 것입니다.
우리가 살펴볼 다섯 가지 형태는 Anthropic의 Building Effective Agents 포스트에서 가져왔습니다. 여기서는 손상된 노트북 케이스가 요구하는 순서대로 등장합니다.
용어 참고. 출처마다 이러한 개념을 다르게 명명하기도 합니다. 이 글에서 라우팅 (Routing)은 일부 출처에서 에이전트 라우터 (Agent Router)라고 부르는 것을 포함합니다. 오케스트레이터-워커 (Orchestrator-workers)는 감독자 아키텍처 (Supervisor Architecture)와 멀티 에이전트 계획 (multi-agent planning)을 포함합니다. Human-in-the-loop, 메모리 (memory), RAG, 그리고 도구 라우팅 (tool routing)은 별도의 최상위 패턴이 아닌 제어 표면 (control surfaces)으로서 여기에 등장합니다.
패턴 1 — 프롬프트 체이닝 (Prompt chaining)
시작하기 가장 쉬운 지점은 최종 응답입니다. 시스템이 사실 관계를 수집하고 결정을 내리면, 응답 자체는 알려진 시퀀스 (sequence)를 거칩니다: 사례 요약, 답장 초안 작성, 어조 확인, 채널에 맞는 형식 지정. 각 단계의 출력값은 다음 단계의 입력값이 됩니다. 이 단계들은 모델이 선택하는 것이 아니라 개발자에 의해 고정됩니다.
단순 정의: 각 호출이 이전 호출의 출력을 처리하는, 모델 호출의 고정된 시퀀스.
TechNova 예시. 최종 답변을 보내기 전에 다음과 같은 체인 (Chain)이 실행됩니다: (1) 수집된 사실로부터 사례를 요약하고, (2) 관련 정책을 인용하는 답변 초안을 작성하며, (3) 지원 채널에 맞게 답변 형식을 지정합니다. 각 출력값은 다음 프롬프트 (Prompt)의 입력값이 됩니다.
발생할 수 있는 문제. 체인은 단계 간의 인계 (Handoff) 과정이 얼마나 견고하냐에 따라 결정됩니다. 만약 1단계에서 잘못된 형식의 요약이 생성된다면, 2단계는 아무런 문제 없이 쓰레기 데이터 (Garbage)를 가지고 작업을 계속합니다. 해결책은 게이트 (Gate) 입니다. 이는 단계 사이에 위치하여 출력이 올바른 형태인지 확인한 후 다음 단계로 전달하는 작은 코드 조각입니다.
중요한 제어 표면 (Control surface). 종료 (Termination). 체인은 개발자가 설정한 목록이 끝날 때 종료됩니다. 이 경계를 설정하는 것이 핵심입니다.
종료 (Termination): 고정 단계 (Fixed-step) — 개발자가 정의한 단계 목록이 끝나면 체인이 종료됩니다. 메모리 (Memory): 최신 항목 전용 (Latest-only) — 각 프롬프트는 전체 이력이 아닌 이전 단계의 출력값만을 확인합니다.
패턴 2 — 라우팅 (Routing)
일곱 가지 작업 중 어떤 것이든 시작하기 전에, 시스템은 이 사례를 누가 처리해야 할지 결정해야 합니다. 고객의 메시지는 환불 요청, 주문 상태 문의, 기술적 문제, 불만 사항, 사기 신호일 수 있으며, 각각은 서로 다른 전문 에이전트 (Specialist agent)에게 전달됩니다. 라우팅 (Routing)은 요청을 분류하는 첫 번째 모델 호출 (Model call)이자, 그 뒤에 따르는 배정 (Dispatch) 과정입니다.
단순 정의: 첫 번째 호출이 입력을 N개의 사전 정의된 카테고리 중 하나로 분류하면, 코드가 해당 카테고리의 전문가에게 배정합니다.
TechNova 사례. 고객의 메시지가 라우터(router)로 전달됩니다. 라우터는 _파손된 제품, 환불 요청_이라는 결과를 반환합니다. 시스템은 이를 지원 오케스트레이터(support orchestrator)로 배정(dispatch)합니다. 만약 라우터의 신뢰도(confidence)가 낮았거나 의도(intent)가 인식되지 않았다면, 배정은 대신 사람의 검토 대기열(human review queue)로 이루어졌을 것입니다.
프로덕션 관점. 라우팅(Routing)은 대부분의 사람들이 멈추는 지점입니다. 모델이 분류하고, 코드가 배정하면 끝. 이러한 프레임워크는 더 중요한 점을 놓치고 있습니다. 프로덕션 환경에서 라우팅은 단순한 분류가 아닙니다. 그것은 바로 **역량 제어 (capability control)**입니다.
에이전트를 위한 API 게이트웨이(API gateway)라고 생각해보세요. 일반적인 백엔드(backend)에서는 하나의 서비스가 모든 책임을 갖도록 두지 않습니다. 대신 시스템을 명확한 역량을 가진 서비스들로 분해합니다. 라우팅은 에이전트에게도 동일한 엔지니어링 본능을 적용합니다. 요청이 분류되면, 해당 종류의 작업을 처리할 수 있도록 허용된 등록된 전문가(registered specialist)에게 전달됩니다. 모델이 요청을 이해하는 데 도움을 줄 수는 있지만, 어떤 등록된 전문가가 행동할 수 있는지를 결정하는 것은 모델이 아니라 _시스템_입니다. 라우터는 잘못된 의도를 추출하여 잘못된 전문가에게 라우팅할 수 있습니다. 하지만 라우터는 존재하지 않는 전문가를 만들어내거나, 등록되지 않은 역량을 부여할 수는 없습니다. 그래프 제약 라우팅(Graph-constrained routing)이 라우팅을 완벽하게 만드는 것은 아닙니다. 그것은 라우팅을 제한된 범위 내로(bounded) 만듭니다.
그러한 제한(bounding)은 전문가(specialists) 자체가 제한되어 있을 때만 의미가 있습니다. ShippingAgent는 추적 정보를 조회할 수는 있지만 환불을 처리할 수는 없습니다. RefundPolicyAgent는 자격 요건을 평가할 수는 있지만 돈을 이동시킬 수는 없습니다. BillingAgent는 환불을 처리할 수 있지만, 오케스트레이터(orchestrator)가 증거와 승인을 수집했을 때만 가능합니다. 전문화(Specialization)는 프롬프트(prompt)에 적힌 내용이 아니라, *각 에이전트가 호출할 수 있는 도구(tools)*에 의해 강제됩니다. 이 글에서 ShippingAgent나 BillingAgent와 같은 이름은 제한된 전문 컴포넌트(specialist components)를 의미합니다. 어떤 것들은 LLM 기반의 에이전트일 수 있고, 다른 것들은 결정론적 서비스(deterministic services)나 API를 감싸는 얇은 래퍼(thin wrappers)일 수도 있습니다. 안전성(safety)에 대한 아이디어는 동일합니다. 각 전문가는 허용된 도구만을 사용합니다. 우리는 이것을 제어 표면(control surface)으로서 다시 다룰 것이지만, 지금 중요한 점은 전문가 자체가 범위가 지정(scoped)되어 있어야만 라우팅(routing)이 안전 메커니즘으로서 작동한다는 것입니다.
무엇이 잘못될 수 있는가. 확신에 찬 잘못된 분류(classification)가 케이스를 잘못된 전문가에게 라우팅합니다. 만약 해당 전문가에게 범위가 지정된 도구(scoped tools)가 있다면, *지원되지 않음(unsupported)*을 반환하고 케이스는 재라우팅되거나 에스컬레이션(escalation)됩니다. 만약 해당 전문가에게 범위가 지정되지 않은 도구(unscoped tools)가 있다면, 스스로 임기응변식으로 처리하게 되며, 시스템은 모델의 실수를 전체 영향 범위(full blast radius)로 그대로 물려받게 됩니다.
중요한 제어 표면. 도구 접근(Tool access) 및 에스컬레이션(escalation). 라우팅이 앞문이라면, 잠금장치는 안쪽에 있습니다.
종료(Termination): dispatch-complete — 라우터가 요청을 분류하고 등록된 전문가에게 전달한 후 멈춥니다. 다음 단계에서 무엇이 일어날지는 전문가 자신의 패턴이 결정합니다. 메모리(Memory): pass-through — 라우터는 원본 메시지와 라우팅 결과를 전달합니다. 전문가는 주어진 컨텍스트(context)만을 가지고 시작합니다.
패턴 3 — 병렬화 (Parallelization)
지원 오케스트레이터(support orchestrator)로 라우팅되면 네 가지 확인 사항이 수행되어야 합니다: 주문 상태, 배송 및 파손 증거, 정책, 재고. 이 중 어느 것도 서로의 출력에 의존하지 않습니다. 주문 조회는 정책이 무엇이라고 말하는지에 신경 쓰지 않습니다. 재고 확인은 배송 상태에 의존하지 않습니다. 이 작업들을 하나씩 순차적으로 수행할 이유가 없습니다.
단순한 정의: 동일한 형태의 호출이 독립적인 입력값들에 동시에 적용되거나 (sectioning), 동일한 입력값이 여러 프롬프트(prompts)를 거쳐 다양한 출력값을 집계(voting)하는 방식입니다.
TechNova 예시. 오케스트레이터(orchestrator)는 네 개의 호출을 병렬로 실행합니다: OrderAgent는 주문 상태를 확인하고, ShippingAgent는 배송 및 파손 증거를 확인하며, RefundPolicyAgent는 관련 정책을 검색하고, InventoryAgent는 교환 가능 여부를 확인합니다. 네 가지 결과가 모두 반환되면, 오케스트레이터는 결과들을 결합(join)하여 다음 작업을 결정합니다.
결합(join)의 모습. 팬아웃(fan-out)은 쉬운 부분입니다. 진정한 규율(discipline)은 그 다음에 일어나는 일에 있습니다.
parallel checks:
order -> OrderAgent.check(case) # 환불 불가
shipping -> ShippingAgent.check(case) # 환불 불가
...
팬아웃은 결코 변하지 않습니다. 제대로 작동하는 것처럼 보이는 시스템과 실제로 제대로 동작하는 시스템의 차이는 결합(join)에 있습니다: 한 분기(branch)에서 타임아웃이 발생하거나, unknown을 반환하거나, 다른 분기와 의견이 다를 때 시스템은 무엇을 합니까?
이 예시에서는 에스컬레이션(Escalation)이 보수적인 기본값(default)입니다. 프로덕션 시스템(production system)에서는 정책이 허용하는 경우 재시도(retry)하거나, 대기하거나, 부분적인 결과로 진행할 수도 있지만, 그러한 선택은 명시적이어야 합니다.
무엇이 잘못될 수 있는가. 병렬화(parallelization)의 거의 모든 실패 모드(failure mode)는 결합(join) 단계에서 발생합니다. 한 분기에서 타임아웃이 발생하면 — 오케스트레이터는 기다릴까요, 재시도할까요, 세 개의 결과만 가지고 진행할까요, 아니면 해당 케이스를 실패 처리할까요? 두 분기가 상충하는 사실을 반환하면 — 어느 쪽이 승리할까요? 한 분기가 _unknown_을 반환하면 — 시스템은 이를 완곡한 거절(soft no)로 취급할까요, 아니면 에스컬레이션(escalate)해야 할 이유로 취급할까요? 병렬화는 겉보기에는 그럴싸해 보이지만 실제로는 잘못 동작하기 가장 쉬운 패턴입니다. 팬아웃은 사소한 작업인 반면, 모든 규율은 결합(join) 단계에 집중되어 있기 때문입니다.
필요한 각 분기(branch)는 워크플로우가 실패할 수 있는 또 다른 지점을 추가합니다. 병렬화(Parallelization)는 지연 시간(latency)을 개선하지만, 신뢰성(reliability)을 자동으로 개선하지는 않습니다. 시스템은 가장 취약한 필수 분기만큼만 강력할 뿐입니다.
중요한 제어 표면 (Control surface). 종료(Termination) — 모든 분기에는 타임아웃(timeout)이 필요하며, 결합(join) 단계에는 분기가 영원히 반환되지 않을 경우에 대비한 문서화된 동작 방식이 필요합니다.
종료 (Termination): 결합 제어형 (join-controlled) — 각 분기에는 타임아웃이 있으며, 병렬 단계는 결합(join)이 정책에 따라 충분한 유효 결과(valid results)를 확보했을 때 종료되거나, 해당 케이스를 재시도(retry) 또는 에스컬레이션(escalation)으로 보냅니다. 메모리 (Memory): 분기 격리형 (branch-isolated) — 각 워커(worker)는 케이스와 자신의 작업만을 확인하며, 오케스트레이터(orchestrator)는 반환된 결과만을 결합합니다.
패턴 4 — 오케스트레이터-워커 (Orchestrator-workers)
이 시점에서, 노트북이 파손된 케이스에는 담당자가 필요합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기

