Agent Mentor Learn
루프에서 그래프로: 에이전트 시스템을 위한 오케스트레이션 엔지니어링 · 2 / 6강

레슨 2: 이어 붙이고, 갈라 보내기: 체이닝과 라우팅

학습 목표:

  • 네 가지 일을 하는 프롬프트를 체인으로 쪼개고, 무엇을 내주고 무엇을 얻는지 말로 옮기기
  • 체인의 단계 사이에 프로그램적 게이트를 설치해, 기준 미달의 중간 결과가 멈춰야 할 자리에서 멈추게 하기
  • 어떤 작업에 체이닝이 필요한지, 라우팅이 필요한지, 둘 다인지, 아무것도 아닌지 정하고, 라우팅 호출을 출력이 조여진 값싼 한 번의 호출로 쓰기

전제: 레슨 1을 읽었고, stop_reason이 모는 하네스 루프를 손으로 쓸 수 있으며(이 시리즈의 코스 7), 결정론적 검증기를 이해하고 있어야 합니다(이 시리즈의 코스 10) | 이전: << 레슨 1 | 다음: 레슨 3 >>

하나의 프롬프트가 네 가지 일을 하면 — 하나는 떨어집니다

새 기능의 도움말 문서를 써야 합니다. 제품 요구사항 읽기 → 개요 초안 잡기 → 개요를 바탕으로 본문 쓰기 → 문서에 비권장 용어가 있는지 점검하기.

첫 버전은 아마 이 네 단계를 하나의 프롬프트에 몽땅 밀어 넣고 하네스 루프에 건네줄 것입니다. 첫 실행은 괜찮아 보입니다. 문제는 두 번째 실행, 그리고 세 번째 실행에서 드러납니다. 이번에는 개요는 좋은데 본문에 한 절이 빠져 있고, 다음번에는 본문은 온전한데 지난 버전에 폐기된 '사용자 그룹'이 섞여 있고, 그다음에는 중간에 개요를 다시 쓰고서 본문과 맞지 않는 개요를 내놓습니다.

이 실패들은 겉모습은 다르지만 뿌리는 같습니다. 한 번의 호출 안에서 모델은 요구사항 이해, 구조 설계, 본문 생성, 일관성 점검을 동시에 저글링해야 합니다. 어느 것이 밀려날지 여러분은 통제할 수 없고, 그것이 일어나는 것을 볼 수도 없습니다. 더 나쁘게는 개입할 자리가 없습니다. 출력을 손에 쥐었을 때는 네 가지가 모두 끝난 뒤이고, '개요가 규격에 안 맞았다'는 사실은 최종 원고 안에 묻혀 있습니다.

레슨 1은 '누가 계획을 쥐고 있는가'라는 축을 다뤘습니다. 이 한 방짜리 프롬프트는 계획을 통째로 모델에게 넘깁니다. 이 레슨이 가장 먼저 하는 일은 그것을 되찾아 오는 것입니다.

체이닝: 레이턴시를 정확성과 맞바꾸기

이 패턴의 공식 정의는 두 문장뿐이고, 모든 단어가 무게를 지고 있습니다.

프롬프트 체이닝은 작업을 일련의 단계로 분해하며, 각 LLM 호출은 앞선 호출의 출력을 처리합니다. 프로세스가 여전히 궤도 위에 있는지 확인하기 위해, 임의의 중간 단계에 프로그램적 점검(아래 도식의 "gate"를 참고)을 더할 수 있습니다1. 이 워크플로를 쓸 때: 이 워크플로는 작업을 쉽고 깔끔하게 고정된 하위 작업으로 분해할 수 있는 상황에 이상적입니다. 주된 목표는 각 LLM 호출을 더 쉬운 작업으로 만들어, 레이턴시를 더 높은 정확성과 맞바꾸는 것입니다1.

'각 LLM 호출을 더 쉬운 작업으로 만든다' — 그 반 문장이 진단과 처방을 함께 줍니다. 한 번의 호출이 네 가지를 하는 것은 어려운 작업이고, 한 번의 호출이 개요만 잡고 아무것도 쓰지 않는 것은 쉬운 작업입니다. 체이닝은 일의 양을 줄이지 않습니다. 모델이 한 번의 호출에서 완수해야 하는 일을 더 단순하게 만듭니다.

비용은 가격표에 그대로 찍혀 있습니다. 레이턴시입니다. 단계가 하나 늘 때마다 완전한 왕복이 하나 붙습니다. Anthropic은 같은 글의 첫머리에 이 회계를 펼쳐 놓았습니다 — 에이전트 시스템은 더 나은 작업 성능을 위해 레이턴시와 비용을 맞바꾸는 일이 잦으며, 여러분은 이 맞바꿈이 언제 말이 되는지 따져 봐야 합니다1. '느리고 비싸다'는 체이닝의 우발적 부작용이 아닙니다.

text
요구사항 ──▶ 개요 초안 ──▶ 본문 집필 ──▶ 용어 점검 ──▶ 전달

각 구간의 입력은 앞 구간의 출력입니다. 한 고리가 비뚤어지면 그 아래로 전부 따라갑니다.

체인의 한 단계는 완전한 하네스 루프입니다

체인의 한 단계는 'API 호출 한 번'이 아니라 완전한 하네스 루프 하나입니다. 이 시리즈의 코스 7에서 손으로 쓴 그 while 루프와 같습니다. messages를 보내고, stop_reason을 확인하고, tool_use면 도구를 실행해 결과를 되돌려 보내고, 아니면 텍스트를 돌려줍니다.

이 용법에는 출처가 있습니다. Anthropic이 에이전트를 평가하는 방법을 서술했을 때 권장한 구성이 정확히 이 형태였습니다. 직접적인 LLM API 호출, 단순한 에이전트 루프(LLM API 호출과 도구 호출을 번갈아 감싸는 while 루프), 평가 작업 하나당 루프 하나, 각 평가 에이전트에게는 하나의 작업 프롬프트와 여러분의 도구를 준다2. 그 글은 평가에 관한 것이었지만, 구성 요소 자체는 범용입니다. 작업 하나, 루프 하나, 코드가 모는 것. 체이닝은 이 블록들을 코드가 순서를 정하는 방식으로 꿰는 것입니다.

나머지 레슨은 같은 표기를 씁니다:

체인 전체는 한눈에 읽히는 순차 코드입니다:

이 줄들에 없는 것에 주목하세요. '모델이 다음에 무엇을 할지 정하는' 자리가 없습니다. 순서는 하드코딩되어 있고, 중간 결과 outlinedoc은 평범한 스크립트 변수입니다. 모델은 각 단계 안에서는 여전히 자율적이지만(도구를 원하는 만큼 호출할 수 있습니다), 단계 사이의 통제권은 코드의 손에 있습니다. 덤으로 얻는 이득이 있습니다. 각 단계의 프롬프트가 한 가지에만 엄격해질 수 있다는 것입니다. 개요 프롬프트는 제목만 요구하고 본문을 금지하며, 집필 프롬프트는 문체와 금지 용어에 집중합니다 — 이 두 벌의 요구사항은 하나의 프롬프트에 담기면 서로 부딪칩니다.

이 형태는 제품에서도 나타납니다. Claude Code의 공식 문서는 여러 단계짜리 워크플로에 대해 Claude가 서브에이전트를 순차적으로 쓰게 하라고 권합니다. 각 서브에이전트가 자기 작업을 마치고 결과를 Claude에게 돌려주면, Claude가 관련 컨텍스트를 다음 서브에이전트에게 넘깁니다3. 차이는 레슨 1의 축에 착지합니다. 제품 형태에서는 '무엇을 넘길지'를 Claude가 정하고, 여러분이 스크립트를 쓸 때는 그것이 여러분의 코드입니다.

게이트: 코스 10의 검증기를 단계 사이로 옮기기

정의의 마지막 반 문장이야말로 '하나의 큰 프롬프트'를 넘어 체이닝이 실제로 더해 주는 것입니다. 프로세스가 여전히 궤도 위에 있는지 확인하기 위해 임의의 중간 단계에 프로그램적 점검을 더할 수 있다1. 원문은 이 점검을 "gate"라 부르며, 게다가 따옴표에 넣어 놓았습니다. (see "gate" in the diagram below) — 한쪽은 곧은 따옴표, 한쪽은 굽은 따옴표로, 출처 그대로입니다. 여기서의 오타가 아닙니다.

'프로그램적'이 핵심입니다. 이것은 코드이지 또 한 번의 모델 호출이 아니며, if 몇 줄이면 됩니다.

이 시리즈의 코스 10은 결정론적 검증기를 가르쳤습니다. 코드로 옳고 그름을 가릴 수 있는 것에는 돈을 써서 모델에게 묻지 말라는 것입니다. 그 코스는 검증기를 종단 상태에 설치했습니다. 모든 것이 돌아간 뒤에 출력이 받아들일 만한지 확인하는 것이죠. 체이닝은 같은 점검에 새 자리를 하나 줍니다. 단계 사이입니다.

일곱 줄, 모델 호출은 한 번도 없고, 같은 입력이면 언제나 같은 판정이 나옵니다. 바로 그 '본문에 비권장 용어가 섞이는' 실패를 막아 줍니다. 개요에 챕터 제목이 몇 개인지 세는 게이트도 그만큼 단순합니다.

게이트가 실패했을 때 무엇을 할지는 설계 결정입니다. 멈추고 에러를 보고하기(이 체인을 아직 조율하는 중이라면 가장 낫지만, 실패 메시지가 어느 단계에서 실패했는지 말해 줘야 합니다. 그러지 않으면 '안 됐다'만 알 뿐 어느 단계의 프롬프트를 고쳐야 할지 모릅니다), 실패 이유를 같은 단계의 프롬프트에 되먹여 재시도하기(재시도 상한과 함께), 아니면 기록해 두고 대체값으로 계속 진행하기(이 단계가 하중을 지지 않을 때만). 레벨 2 연습은 첫 번째 방식을 씁니다.

이것은 또한 이 시리즈 코스 6의 회계를 정리해 줍니다. 여러분이 위임하는 일에는 자기완결적인 프롬프트가 있어야 합니다 — 목표, 출력 형식, 쓸 수 있는 도구, 경계, 이 네 가지를 모두 적어야 합니다. 각 단계의 작업은 정확히 그렇게 생긴 위임 프롬프트입니다. 이 네 요소에는 정확한 1차 출처가 있고, 레슨 4가 오케스트레이터의 위임을 다룰 때 항목별로 펼쳐 볼 것입니다.

라우팅: 먼저 분류하고, 그다음 갈라 보내기

체이닝은 '하나의 작업을 단계로 쪼개는 것'을 다룹니다. 완전히 다른 형태의 작업 부류가 하나 더 있습니다. 들어오는 것이 한 가지가 아니라 여러 종류이고, 각각 자기만의 처리를 갖는 경우입니다.

공식 정의는 이렇습니다. 라우팅은 입력을 분류해 전문화된 후속 작업으로 보냅니다. 이 워크플로는 관심사의 분리와 더 전문화된 프롬프트의 구축을 가능하게 합니다. 이 워크플로가 없으면 한 종류의 입력에 맞춘 최적화가 다른 입력에서의 성능을 해칠 수 있습니다1.

마지막 문장이 라우팅이 존재하는 이유입니다. 고객 메일이 환불, 장애, 청구 세 범주로 나뉜다고 합시다. 여러분은 하나의 프롬프트로 전부 처리합니다. 환불을 잘 처리하려고 '먼저 주문 번호와 결제 수단을 확인할 것'이라는 줄을 더합니다. 이 규칙은 장애 메일에는 순수한 잡음이고, 모델은 이것을 써서 화면이 하얗게 됐다고 신고한 사람에게 결제 수단을 묻습니다. 여러분은 '장애면 주문 번호를 묻지 말 것'이라는 줄을 또 더하고, 프롬프트는 덧대기 위에 덧대기를 키우기 시작합니다.

이 워크플로를 쓸 때: 라우팅은 따로 다루는 편이 나은 뚜렷한 범주들이 있고, 분류 자체를 LLM으로든 더 전통적인 분류 모델이나 알고리즘으로든 정확하게 다룰 수 있는 복잡한 작업에 잘 맞습니다1. 그 마지막 전제 조건은 케이크 위의 장식이 아닙니다. 분류가 틀리면 은밀한 방식으로 틀립니다. 장애 흐름으로 라우팅된 환불 메일은 성실한 문제 해결 답변을 받게 됩니다.

코드에서 라우팅은 체이닝보다 단순합니다:

주목할 것이 세 가지입니다.

분류 호출의 출력을 한 단어로 조이세요 — 이 시리즈의 코스 10이 LLM 판정자를 논할 때 같은 수법을 썼습니다. 허용되는 값을 나열하고, 설명하지 말라고 하는 것. 출력을 조이면 파싱 단계가 결정론적이 됩니다. 표에 없으면 폴백으로LABELS.includes(label) ? label : 'other' 이 한 줄은 방어적인 현학이 아닙니다. 모델은 이따금 '아마 incident 같은데, 아니면 다른 것일 수도'라고 돌려주고, 그러면 HANDLERS[그 문자열 전체]undefined이며 다음 줄에서 터집니다. 폴백 가지를 하나 남겨 두면 분류의 불확실성이 이 한 줄 안에 갇힙니다.

분류기가 꼭 모델일 필요는 없습니다 — 정의는 전통적인 분류 모델이나 알고리즘도 해당한다고 분명히 말합니다1. 메일이 고정된 주문 번호 형식을 달고 오거나 전용 폼 진입점에서 온다면, 정규식 하나로 충분하고 훨씬 빠릅니다.

갈라 보낸 뒤에는 각 핸들러가 무엇이든 될 수 있습니다. 하네스 루프여도 되고, 체인이어도 되고, 모델이 전혀 없는 코드 조각이어도 됩니다.

현행 API 어휘 속의 라우팅 두 변종

위의 라우팅 정의는 2024년 말의 패턴 글에서 왔고, 그 글에는 도구 생태계 서술이 낡았다는 배너가 달려 있습니다. 그러니 확인해 볼 값어치가 있습니다. 이 패턴은 현행 퍼스트파티 어휘 안에서도 살아 있는가? 그렇습니다, 게다가 이름으로 불립니다. Claude 플랫폼의 멀티 에이전트 오케스트레이션 문서에는 라우팅에 해당하는 항목이 둘 있습니다:

  • 전문화(Specialization): 하나의 에이전트에 모든 능력을 싣는 대신, 보안 에이전트나 문서 에이전트처럼 도메인에 집중한 시스템 프롬프트와 도구를 가진 에이전트로 라우팅합니다4. 이것이 HANDLERS 표의 공식 표현입니다.
  • 에스컬레이션(Escalation): 복잡한 하위 작업의 일부에 대해 더 유능한 에이전트나 모델에 자문합니다4.

두 번째 것은 따로 언급할 값어치가 있습니다. 이것은 주제가 아니라 난이도로 갈라 보냅니다. 분류기가 판정하는 것은 '이것이 환불인가 장애인가'가 아니라 '이 메일을 내 값싼 티어로 처리할 수 있는가'입니다. 이것은 주제 분류보다 정확하게 판정하기가 더 어려우므로, 에스컬레이션 경로에는 더 안정적인 쓰기 방식이 있습니다. 값싼 티어를 먼저 돌리고, 출력이 게이트를 통과하지 못하면 에스컬레이트하는 것입니다 — 판정하기 어려운 분류 문제를 점검 가능한 검증 문제로 바꾸는 것이죠.

쪼개지 말아야 할 때를 아는 것

체인의 고리 하나하나가 레이턴시를 더합니다. 이것은 최적화되지 않은 구현이 아니라 공식 정의가 매겨 둔 가격입니다. 레이턴시를 더 높은 정확성과 맞바꾸는 것1. 사용자는 모든 구간의 합만큼 기다립니다. 사용자가 UI에서 결과를 동기적으로 기다리고 있다면, 체인에 고리를 하나 더 붙이기 전에 그 사람이 아직 거기 있을지 생각해 보세요.

범주가 하나뿐일 때 라우팅은 순수한 간접비입니다. 라우팅의 이득은 관심사의 분리에서 옵니다1. 입력이 정말 한 종류뿐이라면, 분류 호출의 비용과 레이턴시를 치르고 돌려받는 것은 아무것도 없으며, 잘못 분류할 기회만 하나 더 생깁니다.

작업을 깔끔하게 쪼갤 수 없을 때는 억지로 쪼개지 마세요. '쉽고 깔끔하게 고정된 하위 작업으로 분해할 수 있어야 한다'는 조건에는 이빨이 있습니다1. 잘 고치려면 전체 그림을 앞뒤로 봐야 하는 초안을 '먼저 구조를 고치고, 그다음 표현을 고친다'로 쪼개면, 두 번째 단계는 첫 번째 단계가 적어 두지 않은 이유들에 접근할 수 없고 문자 그대로의 텍스트만 보고 고칠 것입니다. 이 경우에는 하나의 루프, 하나의 컨텍스트가 실제로 더 낫습니다 — 이것이 정확히 레슨 1의 역방향 조건이 쓰이는 자리입니다.

확신이 없으면 먼저 재세요. Anthropic은 이 말을 두 번 했습니다. 복잡성을 더하는 것은 그것이 결과를 명백히 개선할 때에만 고려하라1. 이 시리즈 코스 10의 평가 트랙은 이것을 위해 만들어졌습니다. 단일 프롬프트 버전을 돌려 점수를 하나 얻고, 쪼갠 버전을 돌려 또 하나를 얻고, 차이가 얼마나 나는지 그리고 그 몇 초의 추가 레이턴시만 한지 보는 것 — 그것이 동료와의 논쟁에 들고 갈 수 있는 증거입니다.

마지막으로 경계를 그어 둡니다. 체이닝과 라우팅은 둘 다 형태가 고정된 오케스트레이션입니다. 체인에 단계가 몇 개인지, 라우터에 범주가 무엇인지, 모두 여러분이 코드를 쓸 때 정해집니다. 여러 단계를 동시에 돌리고 그다음 집계하는 것은 레슨 3의 병렬화이고, 입력을 보기 전에는 하위 작업이 몇 개인지조차 모르는 것은 레슨 4의 오케스트레이터-워커입니다.

💻 연습

정리

  • 체이닝은 작업을 일련의 단계로 분해해 각 호출이 앞선 출력을 처리하게 하며, 각 호출을 더 쉬운 작업으로 만든다1. 값은 명시적으로 매겨져 있다. 주된 목표는 레이턴시를 더 높은 정확성과 맞바꾸는 것이다1
  • 체인의 한 단계는 API 호출 한 번이 아니라 완전한 하네스 루프 하나다. Anthropic이 평가를 위해 권장한 구성이 바로 이 구성 요소다. 작업 하나, while 루프 하나, 코드가 직접 API를 호출하는 것2
  • 쪼갠 뒤에 늘어나는 자리가 핵심이다. 프로세스가 여전히 궤도 위에 있는지 확인하기 위해 임의의 중간 단계에 프로그램적 점검을 더할 수 있다1. 이것은 코스 10의 결정론적 검증기를 설치 위치만 바꿔 옮긴 것이다. 종단 상태에서 단계 사이로. 제품 형태로는 서브에이전트를 순차 실행하고 각자가 마치면 상위 층이 관련 컨텍스트를 다음으로 넘기는 모습이 된다3
  • 라우팅은 입력을 분류해 전문화된 후속 작업으로 갈라 보내며, 관심사의 분리와 더 전문화된 프롬프트를 얻는다. 그것이 없으면 한 종류의 입력에 맞춘 최적화가 다른 입력에서의 성능을 해칠 수 있다1. 전제 조건은 뚜렷한 범주가 있고 분류 자체를 LLM으로든 전통적인 분류 알고리즘으로든 정확하게 다룰 수 있다는 것이다1
  • 분류 호출의 출력은 라벨 하나로 조이고, 표에 없는 답을 받아 낼 폴백 가지를 하나 남긴다
  • 이 패턴은 현행 퍼스트파티 어휘 안에서 살아 있다. 전용 프롬프트와 도구를 가진 에이전트로 도메인에 따라 갈라 보내는 것을 전문화라 부르고, 복잡한 하위 작업의 일부에 대해 더 유능한 에이전트나 모델에 자문하는 것을 에스컬레이션이라 부른다4. 뒤엣것은 난이도로 갈라 보내는 라우팅이다
  • 억지로 하지 말 것: 범주가 하나뿐일 때 라우팅은 순수한 간접비이고, 작업을 깔끔하게 쪼갤 수 없을 때 억지로 쪼개면 단계 사이에서 정보를 잃는다1. 확신이 없으면 먼저 재라 — 복잡성은 '결과를 명백히 개선한다'는 문턱을 넘어야 한다1

>> 레슨 3: 병렬화: 섹셔닝과 투표

Footnotes

  1. Building Effective AI Agents — Anthropic Engineering — https://www.anthropic.com/engineering/building-effective-agents 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

  2. Writing effective tools for agents — with agents — Anthropic Engineering — https://www.anthropic.com/engineering/writing-tools-for-agents 2

  3. Create custom subagents — Claude Code official documentation — https://code.claude.com/docs/en/sub-agents 2

  4. Multiagent orchestration — Claude API documentation (Managed Agents) — https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration 2 3

연습

01

아래 네 작업 각각에 대해 체이닝을 쓸지, 라우팅을 쓸지, 둘 다일지, 아무것도 아닐지 정하세요. 결론 말고도 이것을 분명히 쓰세요. 체이닝이라 판단했다면 단계 사이의 프로그램적 게이트는 무엇을 점검합니까? 라우팅이라 판단했다면 분류기는 어디에 놓이고, 범주는 몇 개이며, 모델입니까 코드입니까?

레벨 1: 체이닝인지, 라우팅인지, 아무것도 아닌지 정하기(코드 없음)
  1. 고객 메일함에 매일 수백 통이 들어오고, 환불/장애/청구 세 범주의 처리가 완전히 다른데, 지금은 하나의 프롬프트를 함께 쓰고 있어서 한 범주의 표현을 바꾸면 다른 범주에 영향을 준다.
  2. 영문 계약서를 먼저 구조화된 리스크 의견서로 검토해야 하고(각 항목에 조항 위치, 리스크 등급, 설명 포함), 그다음 이 의견서를 팀별로 한국어와 일본어 버전으로 번역해야 한다.
  3. 뒤섞인 티켓 큐: 한 단계로 답할 수 있는 '비밀번호 재설정'도 있고, 로그를 확인하고 진단하고 해결책을 제안하고 답변을 써야 하는 '데이터 마이그레이션 실패' 같은 여러 단계짜리 작업도 있다.
  4. 사용자가 입력창에 한 문장을 치면, 오타를 고쳐서 교정된 문장을 돌려줘야 한다.
완료 기준 · 로컬에서 확인
02

첫머리의 문서 집필 작업을 3단계 체인으로 구현하는 chain.mjs를 쓰세요. 개요 초안 → 개요를 바탕으로 본문 집필 → 용어 일관성 점검, 그 사이에 게이트 둘. 요구사항:

레벨 2: 3단계 체인을 써서 돌려 보기(코드 작성)
  1. 스텁 client를 쓸 것(이 시리즈의 코스 8~11에서 쓴 방식): 고정된 응답 큐, 네트워크 없음, 비용 없음, 같은 입력이면 언제나 같은 결과.
  2. runAgent(client, task, tools)는 진짜 하네스 루프일 것 — stop_reason으로 판정하고, tool_use면 도구를 실행해 결과를 되돌려 보낼 것. 3단계는 도구 호출을 실제로 한 번 거쳐야 함(용어집 읽기).
  3. 게이트 1: 개요에 ## 로 시작하는 챕터 제목이 정확히 3개 있어야 함. 게이트 2: 본문에 금지된 비권장 용어가 없어야 함(두세 단어짜리 표를 직접 정의). 둘 다 순수 코드, 모델 호출 추가 없음.
  4. 게이트가 실패하면 어느 단계에서 왜 실패했는지 출력하고 0이 아닌 코드로 종료할 것. 세 단계가 모두 통과하면 0으로 종료.
  5. 고정 응답을 두 벌 준비해 두 번 돌릴 것. 한 번은 전부 통과, 한 번은 두 번째 게이트에서 멈춤. 두 실행의 실제 출력과 종료 코드를 모두 붙여 넣을 것.
완료 기준 · 로컬에서 확인