notebook
Anthropic · 2024.12 학습 노트 · 읽는 시간 12분

Building
Effective Agents

"성공한 팀들이 공통적으로 발견한 것은 — 복잡한 프레임워크가 아니라, 단순하고 조합 가능한 패턴이었다."

저자 Schluntz· Zhang
발행 Dec 2024
핵심 패턴 5 + 1workflows · agent
교훈 Keep it simple
작성 2026.05.14
01

한 줄로 이해하기

복잡한 에이전트 프레임워크는 대부분 필요 없다. 성공한 LLM 시스템들은 단순한 빌딩 블록을 조합해서 만들었고, 가장 어려운 결정은 "에이전트를 쓸지 말지"가 아니라 "어디까지 단순하게 둘 수 있는지"였다.

i

워크플로우 ≠ 에이전트

워크플로우는 코드가 LLM을 정해진 경로로 흘려보낸다. 에이전트는 LLM이 스스로 다음 행동을 결정한다. 예측 가능성이 필요하면 워크플로우, 개방형 문제면 에이전트.

ii

모든 것의 출발점: Augmented LLM

LLM에 검색 · 도구 · 메모리를 붙인 단위가 기본 블록. 이것 하나만 잘 다뤄도 80%의 use case는 해결된다. 추가 레이어는 필요할 때만 쌓아라.

iii

다섯 가지 워크플로우 패턴

Prompt Chaining · Routing · Parallelization · Orchestrator-Workers · Evaluator-Optimizer — 대부분의 LLM 시스템은 이 다섯 가지 조합으로 표현된다.

iv

에이전트는 비용을 정직하게 청구한다

자율적인 만큼 토큰 비용 · 지연 · 실수의 누적이 따라온다. 잘못된 도구 호출 한 번이 전체 궤도를 망친다. 그래서 믿을 만한 환경 + 검증된 도구 + 인간 체크포인트가 필수.

v

관건은 ACI (Agent-Computer Interface)

프롬프트 엔지니어링만큼 중요한 게 도구 설계다. 도구 이름, 파라미터 형식, 에러 메시지 — 사람에게 좋은 API가 모델에게도 좋은 API다. 도구 문서는 주니어 개발자에게 설명하듯 써라.

vi

단순함 → 투명성 → 신중한 ACI

저자들이 꼽은 단 세 가지 원칙. 단순하게 설계하고, 계획 단계를 노출하고, 도구를 정성껏 만들어라. 프레임워크는 디버깅을 어렵게 만든다 — 기본 API로 직접 호출하는 것을 권장.

02
패턴 도감

그림으로 보는 6가지 구조

00 · 빌딩블록Augmented LLM

모든 패턴의 원자 단위. LLM 호출에 세 가지 능력을 부착한다 — 외부 지식을 가져오는 retrieval, 세상에 작용하는 tools, 그리고 상태를 기억하는 memory.

이 한 덩어리를 견고하게 만드는 게 먼저다. 모델이 검색 쿼리를 잘 짜고, 도구를 적절히 고르고, 결과를 컨텍스트에 잘 녹이는가 — 여기서 무너지면 위층 패턴은 모두 무너진다.

언제 쓰나

거의 모든 LLM 앱의 출발점. 단일 턴 Q&A부터 복잡한 agent까지 이 블록을 반복·중첩한 형태다.

Retrieval외부 지식
Memory상태 유지
LLM
Tools행동 실행
Input / OutputI/O

01 · 워크플로우Prompt Chaining

하나의 작업을 순차적 단계로 쪼개고, 각 단계 사이에 검증 게이트를 넣는다. 앞 단계의 출력이 뒤 단계의 입력이 되는 직선형 파이프라인.

지연이 늘어나는 대신 단계별로 정확도가 올라간다. 실패 지점을 단계 단위로 격리할 수 있어 디버깅도 쉬워진다.

언제 쓰나

작업을 명확하고 고정된 하위 작업으로 분해할 수 있을 때. 예: 마케팅 카피 작성 → 번역, 문서 개요 → 검증 → 본문 작성.

In
LLM 1
LLM 2
LLM 3
Out

02 · 워크플로우Routing

입력을 먼저 분류해서 전문화된 후속 프롬프트로 보낸다. 모든 케이스를 하나의 거대한 프롬프트에 욱여넣지 않고, 분기마다 최적의 프롬프트·모델·도구를 쓴다.

간단한 질문은 작은 모델로, 복잡한 질문은 큰 모델로 — 라우팅만으로도 비용과 품질이 동시에 개선되는 경우가 많다.

언제 쓰나

입력이 뚜렷한 카테고리로 나뉘고, 카테고리별로 처리 방식이 다를 때. 예: 고객 지원 분기(환불/기술/일반), 쉬운 질문은 Haiku, 어려운 질문은 Opus.

Classifier
LLM
전문화 LLM Arefund
전문화 LLM Btechnical
전문화 LLM Cgeneral

03 · 워크플로우Parallelization

하나의 작업을 병렬 LLM 호출로 분해해 합친다. 두 가지 형태가 있다 — Sectioning (독립된 부분을 동시에 처리)과 Voting (같은 작업을 여러 번 시켜 다양성/신뢰도 확보).

지연을 줄이거나, 다양한 관점이 필요할 때 효과적. 특히 voting은 가드레일/안전성 검사에서 false negative를 잡는 데 강하다.

언제 쓰나

하위 작업이 독립적일 때(Sectioning), 또는 한 답의 신뢰도가 낮아 다중 표본이 필요할 때(Voting). 예: 코드 보안 리뷰를 서로 다른 프롬프트로 3회 → 합집합.

In
fan-out
LLM · 시각 A
LLM · 시각 B
LLM · 시각 C
aggregate
Out

04 · 워크플로우Orchestrator — Workers

중앙의 오케스트레이터 LLM이 작업을 동적으로 쪼개서 워커들에게 위임하고, 결과를 종합한다. Parallelization과 닮았지만 결정적 차이는 — 하위 작업이 런타임에 결정된다는 점.

몇 단계로 나눌지, 무엇으로 나눌지를 코드가 아닌 LLM이 정한다. 그래서 더 유연하지만, 그만큼 검증과 비용 통제가 어렵다.

언제 쓰나

하위 작업이 미리 정해지지 않을 때. 예: 여러 파일에 걸친 코드 변경, 한 토픽에 대해 여러 소스를 수집·요약하는 리서치.

Orchestrator
Worker · 1
Worker · 2
Worker · 3
Synthesizer

05 · 워크플로우Evaluator — Optimizer

한 LLM이 결과를 생성하고, 다른 LLM이 평가·피드백을 준다. 피드백을 받아 다시 생성 — 만족할 때까지 루프. 사람의 글쓰기 ↔ 편집자 관계의 LLM 버전.

평가 기준을 명확히 글로 쓸 수 있고, LLM이 그 기준을 일관되게 적용할 수 있을 때 효과가 크다. 반복마다 측정 가능한 개선이 보여야 한다 — 그렇지 않으면 루프가 의미 없다.

언제 쓰나

품질 기준이 명확하고 반복으로 개선 가능한 작업. 예: 문학 번역(뉘앙스), 다단계 검색 + 정보 종합.

Generator
LLM · draft
Evaluator
LLM · critique
feedback loop until passes

06 · 자율형Agent

LLM이 스스로 계획하고, 도구를 호출하고, 결과를 보고, 다음 행동을 결정한다. 종료 조건이 만족될 때까지(또는 사람이 멈출 때까지) 반복. 워크플로우가 레일 위 기차라면, 에이전트는 지도를 보며 운전하는 차다.

강력하지만 비용·지연·실수의 누적이 모두 커진다. 그래서 안전한 샌드박스 환경 · 명확한 종료 조건 · 인간 체크포인트가 사실상 필수다.

언제 쓰나

단계 수와 경로를 미리 예측하기 어려운 개방형 문제, 모델의 판단을 신뢰할 수 있는 도메인. 예: SWE-bench 류 코딩 에이전트, 컴퓨터 사용 에이전트.

LLM
Plan
Act
Observe
Reflect

프레임워크(LangGraph, AutoGen 등)는 학습 곡선을 낮춰주지만, 동시에 추상화 레이어 뒤에 동작을 숨겨 디버깅을 어렵게 만든다. 저자들은 "먼저 LLM API를 직접 호출해보고, 그래도 추상화가 필요할 때만 프레임워크로 옮기라"고 권한다.

그래서 — 언제 무엇을 고를까?

워크플로우를 골라라

  • 작업이 잘 정의되어 있다
  • 실행 경로가 예측 가능하다
  • 지연·비용·정확도를 통제해야 한다
  • 프로덕션 안정성이 최우선이다
  • 대부분의 실제 비즈니스 use case

에이전트를 골라라

  • 단계 수를 미리 예측할 수 없다
  • 고정된 경로로 충분히 풀 수 없다
  • 모델의 판단을 신뢰할 환경이 있다
  • 실수를 회복하거나 사람이 개입할 수 있다
  • 비용·지연 증가를 받아들일 수 있다
03
실전 코드

패턴별 Python 구현

Anthropic SDK 기준의 최소 구현. 프레임워크 없이 — 모든 흐름은 일반 Python 코드와 LLM 호출로 표현 가능하다는 글의 핵심을 그대로 보여주는 형태로 정리했다.

00 · Augmented LLM

building block
from anthropic import Anthropic

client = Anthropic()

def augmented_llm(user_msg: str, tools: list, memory: list):
    # memory: 이전 turns / tools: 함수 스키마 리스트
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        system="You are a precise assistant. Use tools when helpful.",
        messages=memory + [{"role": "user", "content": user_msg}],
        tools=tools,
    )
    return response
포인트. 이 한 함수가 다른 모든 패턴의 원자다. 도구·메모리·시스템 프롬프트를 깔끔하게 분리해 두면, 위층 패턴에서 그대로 조합할 수 있다.

01 · Prompt Chaining

workflow
def llm_call(prompt: str, system: str = "") -> str:
    msg = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        system=system,
        messages=[{"role": "user", "content": prompt}],
    )
    return msg.content[0].text

def chain(input_text: str, prompts: list[str]) -> str:
    out = input_text
    for i, p in enumerate(prompts):
        out = llm_call(f"{p}\n\n<input>\n{out}\n</input>")
        # 검증 게이트: 형식이 깨졌으면 중단
        if "<error>" in out.lower():
            raise RuntimeError(f"Step {i} failed: {out}")
    return out

# 사용 예: 개요 → 검증 → 본문
result = chain(
    "신년 마케팅 캠페인 주제: 친환경 패션",
    prompts=[
        "3문장 개요를 만든다.",
        "개요가 톤(미니멀, 진정성)에 맞는지 점검하고 다듬어라.",
        "개요를 200자 본문으로 확장하라.",
    ],
)
포인트. 단계마다 검증 게이트가 핵심. 형식이 무너지면 즉시 끊어서 잘못된 출력이 다음 단계로 전염되는 걸 막는다.

02 · Routing

workflow
import json

def classify(query: str) -> str:
    out = llm_call(
        system="분류기. 다음 중 하나로만 답: refund | technical | general",
        prompt=f"고객 문의: {query}\n\n카테고리만 출력.",
    )
    return out.strip().lower()

ROUTES = {
    "refund":    ("claude-haiku-4-5", "환불 정책에 따라 절차를 안내하라."),
    "technical": ("claude-sonnet-4-6", "기술 지원 엔지니어처럼 단계별로 진단하라."),
    "general":   ("claude-haiku-4-5", "친절하게 응답하라."),
}

def route(query: str) -> str:
    category = classify(query)
    model, system = ROUTES.get(category, ROUTES["general"])
    msg = client.messages.create(
        model=model, max_tokens=1024,
        system=system,
        messages=[{"role": "user", "content": query}],
    )
    return msg.content[0].text
포인트. 분기마다 모델·시스템 프롬프트를 다르게 가져가는 게 본질. 쉬운 케이스는 Haiku로 빠르고 싸게, 복잡한 케이스만 Sonnet으로 풀어 비용을 통제한다.

03 · Parallelization

workflow
import asyncio
from anthropic import AsyncAnthropic

aclient = AsyncAnthropic()

async def acall(prompt: str, system: str) -> str:
    msg = await aclient.messages.create(
        model="claude-sonnet-4-6", max_tokens=512,
        system=system,
        messages=[{"role": "user", "content": prompt}],
    )
    return msg.content[0].text

async def vote_safety_check(code: str) -> bool:
    # Voting: 같은 작업을 다른 프롬프트로 3번 → 하나라도 위험 판정이면 reject
    perspectives = [
        "보안 엔지니어 관점에서 위험 식별",
        "악의적 사용자 관점에서 악용 경로 식별",
        "코드 리뷰어 관점에서 의심스러운 패턴 식별",
    ]
    results = await asyncio.gather(*[
        acall(f"코드:\n{code}\n\n'UNSAFE' 또는 'SAFE'만 답하라.", p)
        for p in perspectives
    ])
    return all("SAFE" in r.upper() for r in results)
포인트. asyncio.gather 한 줄로 지연을 N→1로 줄인다. Voting은 false negative가 치명적인 가드레일에서 특히 강하다.

04 · Orchestrator–Workers

workflow
def orchestrate(task: str) -> str:
    # 1) 오케스트레이터가 하위 작업 목록을 동적으로 생성
    plan_json = llm_call(
        system="작업을 독립 하위 작업 JSON 배열로 분해. 각 원소: {id, goal}",
        prompt=task,
    )
    subtasks = json.loads(plan_json)

    # 2) 워커들이 하위 작업을 수행
    results = []
    for st in subtasks:
        out = llm_call(
            system="하위 작업을 정확히 수행하고 결과만 반환.",
            prompt=f"{st['goal']}",
        )
        results.append({"id": st["id"], "out": out})

    # 3) 합성
    return llm_call(
        system="하위 결과를 일관된 최종 산출물로 통합.",
        prompt=json.dumps(results, ensure_ascii=False),
    )
포인트. Parallelization과의 결정적 차이 — 하위 작업 자체가 LLM에 의해 런타임에 결정된다. 그래서 더 강력하지만, 비용·예측 불가능성이 함께 늘어난다.

05 · Evaluator–Optimizer

workflow
def eval_optimize(task: str, max_iters: int = 3) -> str:
    draft = llm_call(system="초안을 작성.", prompt=task)

    for i in range(max_iters):
        critique = llm_call(
            system=("엄격한 평가자. 기준: 명료성, 사실성, 톤. "
                    "마지막 줄에 PASS 또는 REVISE만 출력."),
            prompt=f"<task>{task}</task>\n<draft>{draft}</draft>",
        )
        if critique.strip().splitlines()[-1].strip() == "PASS":
            return draft

        draft = llm_call(
            system="피드백을 반영해 다시 작성.",
            prompt=f"<draft>{draft}</draft>\n<critique>{critique}</critique>",
        )
    return draft  # 한도 도달: 마지막 draft 반환
포인트. 평가 기준은 구체적이고 글로 쓸 수 있어야 의미가 있다. "더 좋게 만들어"는 루프를 무의미하게 만든다.

06 · Agent Loop

autonomous
TOOLS = [
    {
        "name": "read_file",
        "description": "파일 내용을 읽는다. 주니어 개발자에게 설명하듯 명확히.",
        "input_schema": {
            "type": "object",
            "properties": {"path": {"type": "string"}},
            "required": ["path"],
        },
    },
    # ... 다른 도구들
]

def run_tool(name: str, args: dict) -> str:
    if name == "read_file":
        with open(args["path"]) as f:
            return f.read()
    raise ValueError(f"unknown tool: {name}")

def agent(goal: str, max_steps: int = 10):
    messages = [{"role": "user", "content": goal}]

    for step in range(max_steps):
        resp = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=1024,
            tools=TOOLS,
            messages=messages,
        )

        if resp.stop_reason == "end_turn":
            return resp.content[0].text

        # 도구 호출 처리
        messages.append({"role": "assistant", "content": resp.content})
        tool_results = []
        for block in resp.content:
            if block.type == "tool_use":
                result = run_tool(block.name, block.input)
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": result,
                })
        messages.append({"role": "user", "content": tool_results})

    raise RuntimeError("max_steps reached without completion")
포인트. 에이전트의 90%는 루프 + 도구 사용 + 종료 조건이다. max_steps는 비용 폭주 방지의 안전핀. 도구의 description·input_schema 품질이 곧 에이전트 품질이다.

프로덕션에서는 여기에 관측(로깅·트레이싱) · 가드레일(입출력 필터링) · 휴먼 인 더 루프(중요 행동 전 승인)를 더해야 한다. 패턴 자체보다 이 주변 인프라가 시스템 신뢰도를 결정한다.

04
Practical Adoption

실무 도입 가이드

7가지 패턴 중 어디부터 도입할지는 ROI 순서로 결정한다. 비용 절감 효과가 즉시 나타나는 단순 패턴부터 시작하고, 자율성이 큰 패턴은 가장 마지막에. 같은 작업도 패턴 선택에 따라 비용·지연·정확도가 크게 달라진다.

Tier 1 비용 대비 효과가 가장 큰 두 가지 시작점 · 1~2일 작업

Routing

비용 즉시 절감

쉬운 질문은 Haiku, 어려운 것만 Sonnet/Opus. 분류기 한 번 추가만으로 LLM 비용 30~70% 절감.

  • 고객 지원 챗봇 — 환불 / 기술 / 일반 분기
  • 사내 봇 — 코드 질문 / 문서 검색 / 운영 가이드 자동 분기
  • 데이터 파이프라인 — 입력 포맷(JSON·CSV·PDF·이미지)별 다른 처리기
  • 비용 캡 — 토큰 길이 임계치 기반 모델 다운그레이드

Prompt Chaining

정확도 개선

하나의 거대 프롬프트를 단계로 쪼개고 게이트 추가. 디버깅 가능 + 단계별 모델 선택 가능.

  • PR 리뷰 — 변경 요약 → 영향 범위 → 리스크 평가 → 코멘트
  • 회의록 — 발화 정리 → 결정사항 → 액션 → Jira/Linear 티켓 초안
  • 장애 회고서 — 타임라인 → 근본 원인 → 재발 방지 액션
  • 다국어 처리 — 번역 → 톤 검수 → 용어 통일
Tier 2 품질이 중요한 작업 중간 투자 · 1~2주

Parallelization · Voting

신뢰도 강화

False negative 비용이 큰 가드레일에서 강력. 서로 다른 관점으로 N회 호출 → 만장일치만 통과.

  • 보안 리뷰 — 보안 엔지니어 / 공격자 / 규정 준수 3개 관점 병렬
  • 컨텐츠 모더레이션 — 한 번이라도 위험 판정이면 reject
  • 면접 답변 평가 — 다양한 평가자 시뮬레이션 후 합의

Parallelization · Sectioning

지연 단축

독립적 하위 작업을 동시 호출. 직렬 처리 5초 → 병렬 0.8초 수준 단축이 흔하다.

  • 장문 요약 — 50쪽 문서를 청크별 병렬 요약 → 통합
  • 로그 분석 — 시간대별 병렬 파싱 후 머지
  • 멀티모달 — 이미지 / OCR / 메타데이터 추출 동시

Evaluator–Optimizer

반복 개선

평가 기준이 글로 쓸 수 있고 측정 가능할 때만 의미. "더 좋게" 같은 모호한 기준이면 루프만 돈다.

  • 글쓰기 — 작가 ↔ 편집자 사이클 (톤 · 명료성 · 사실성)
  • SQL 최적화 — 생성 → EXPLAIN 평가 → 인덱스 힌트 개선
  • 번역 품질 — 뉘앙스 검수 → 재번역
  • 메타 사용 — 프롬프트 자체 최적화

선택지

Tier 2는 한 번에 다 도입하지 말고, 가장 통증 큰 곳 하나만 골라 PoC 후 확장하라. 다음 분기 KPI 한 줄에 매핑되는 패턴부터.

  • 품질 문제 → Voting
  • 지연 문제 → Sectioning
  • 창작·반복 개선 → Evaluator
Tier 3 정교한 설계가 필요 고비용 · 1개월+

Orchestrator–Workers

동적 분해

하위 작업이 런타임에 정해질 때만 의미. 비용·예측 불가능성이 동시에 증가하므로 max_tokens · max_workers 가드 필수.

  • 다중 파일 리팩토링 — 어디까지 변경될지 미리 모름
  • 리서치 작업 — "이 회사의 기술 스택과 채용 동향 분석"
  • 마이그레이션 플랜 — 스키마 진단 → 단계 자동 생성

Agent Loop

진짜 자율 실행

샌드박스 + 명확한 종료 조건 + 인간 체크포인트 없이는 운영하지 마라. 한 번의 잘못된 도구 호출이 전체 궤도를 망친다.

  • 코딩 에이전트 — Claude Code가 그 예
  • 데이터 분석 — 질문 → 쿼리 → 해석 → 다음 질문
  • 옵스 트리아지 — 알람 → 로그 수집 → 가설 검증 → 자동 복구 시도
  • QA 자동화 — 명세 → 테스트 케이스 생성 → 실행 → 회귀 보고

도입 권장 순서

전체를 한꺼번에 적용하지 말고, 단계별 ROI를 확인하면서 다음 단계로 넘어간다.

  1. Routing 부터 시작

    코드 한두 시간, 비용 즉시 30~70% 절감. 분류기 + 모델 라우팅 테이블만 있으면 된다.

  2. Chaining 으로 정확도 개선

    기존 단일 프롬프트 작업을 단계로 쪼개고 검증 게이트 추가. 단계별로 다른 모델·온도 사용 가능.

  3. Voting 을 가드레일에 추가

    보안·규정·모더레이션처럼 false negative 비용이 큰 영역. 3중 검사 → 만장일치 게이트.

  4. Evaluator–Optimizer 는 측정 가능한 곳만

    평가 기준을 글로 쓸 수 없으면 의미 없음. 반드시 max_iters로 무한 루프 차단.

  5. Agent 는 가장 마지막

    비용·지연·실수가 누적되므로 sandbox 환경 + max_steps + 사람 체크포인트는 협상 불가.

안티패턴 — 쓰지 말아야 할 곳caution