워크플로우 ≠ 에이전트
워크플로우는 코드가 LLM을 정해진 경로로 흘려보낸다. 에이전트는 LLM이 스스로 다음 행동을 결정한다. 예측 가능성이 필요하면 워크플로우, 개방형 문제면 에이전트.
"성공한 팀들이 공통적으로 발견한 것은 — 복잡한 프레임워크가 아니라, 단순하고 조합 가능한 패턴이었다."
복잡한 에이전트 프레임워크는 대부분 필요 없다. 성공한 LLM 시스템들은 단순한 빌딩 블록을 조합해서 만들었고, 가장 어려운 결정은 "에이전트를 쓸지 말지"가 아니라 "어디까지 단순하게 둘 수 있는지"였다.
워크플로우는 코드가 LLM을 정해진 경로로 흘려보낸다. 에이전트는 LLM이 스스로 다음 행동을 결정한다. 예측 가능성이 필요하면 워크플로우, 개방형 문제면 에이전트.
LLM에 검색 · 도구 · 메모리를 붙인 단위가 기본 블록. 이것 하나만 잘 다뤄도 80%의 use case는 해결된다. 추가 레이어는 필요할 때만 쌓아라.
Prompt Chaining · Routing · Parallelization · Orchestrator-Workers · Evaluator-Optimizer — 대부분의 LLM 시스템은 이 다섯 가지 조합으로 표현된다.
자율적인 만큼 토큰 비용 · 지연 · 실수의 누적이 따라온다. 잘못된 도구 호출 한 번이 전체 궤도를 망친다. 그래서 믿을 만한 환경 + 검증된 도구 + 인간 체크포인트가 필수.
프롬프트 엔지니어링만큼 중요한 게 도구 설계다. 도구 이름, 파라미터 형식, 에러 메시지 — 사람에게 좋은 API가 모델에게도 좋은 API다. 도구 문서는 주니어 개발자에게 설명하듯 써라.
저자들이 꼽은 단 세 가지 원칙. 단순하게 설계하고, 계획 단계를 노출하고, 도구를 정성껏 만들어라. 프레임워크는 디버깅을 어렵게 만든다 — 기본 API로 직접 호출하는 것을 권장.
모든 패턴의 원자 단위. LLM 호출에 세 가지 능력을 부착한다 — 외부 지식을 가져오는 retrieval, 세상에 작용하는 tools, 그리고 상태를 기억하는 memory.
이 한 덩어리를 견고하게 만드는 게 먼저다. 모델이 검색 쿼리를 잘 짜고, 도구를 적절히 고르고, 결과를 컨텍스트에 잘 녹이는가 — 여기서 무너지면 위층 패턴은 모두 무너진다.
거의 모든 LLM 앱의 출발점. 단일 턴 Q&A부터 복잡한 agent까지 이 블록을 반복·중첩한 형태다.
하나의 작업을 순차적 단계로 쪼개고, 각 단계 사이에 검증 게이트를 넣는다. 앞 단계의 출력이 뒤 단계의 입력이 되는 직선형 파이프라인.
지연이 늘어나는 대신 단계별로 정확도가 올라간다. 실패 지점을 단계 단위로 격리할 수 있어 디버깅도 쉬워진다.
작업을 명확하고 고정된 하위 작업으로 분해할 수 있을 때. 예: 마케팅 카피 작성 → 번역, 문서 개요 → 검증 → 본문 작성.
입력을 먼저 분류해서 전문화된 후속 프롬프트로 보낸다. 모든 케이스를 하나의 거대한 프롬프트에 욱여넣지 않고, 분기마다 최적의 프롬프트·모델·도구를 쓴다.
간단한 질문은 작은 모델로, 복잡한 질문은 큰 모델로 — 라우팅만으로도 비용과 품질이 동시에 개선되는 경우가 많다.
입력이 뚜렷한 카테고리로 나뉘고, 카테고리별로 처리 방식이 다를 때. 예: 고객 지원 분기(환불/기술/일반), 쉬운 질문은 Haiku, 어려운 질문은 Opus.
하나의 작업을 병렬 LLM 호출로 분해해 합친다. 두 가지 형태가 있다 — Sectioning (독립된 부분을 동시에 처리)과 Voting (같은 작업을 여러 번 시켜 다양성/신뢰도 확보).
지연을 줄이거나, 다양한 관점이 필요할 때 효과적. 특히 voting은 가드레일/안전성 검사에서 false negative를 잡는 데 강하다.
하위 작업이 독립적일 때(Sectioning), 또는 한 답의 신뢰도가 낮아 다중 표본이 필요할 때(Voting). 예: 코드 보안 리뷰를 서로 다른 프롬프트로 3회 → 합집합.
중앙의 오케스트레이터 LLM이 작업을 동적으로 쪼개서 워커들에게 위임하고, 결과를 종합한다. Parallelization과 닮았지만 결정적 차이는 — 하위 작업이 런타임에 결정된다는 점.
몇 단계로 나눌지, 무엇으로 나눌지를 코드가 아닌 LLM이 정한다. 그래서 더 유연하지만, 그만큼 검증과 비용 통제가 어렵다.
하위 작업이 미리 정해지지 않을 때. 예: 여러 파일에 걸친 코드 변경, 한 토픽에 대해 여러 소스를 수집·요약하는 리서치.
한 LLM이 결과를 생성하고, 다른 LLM이 평가·피드백을 준다. 피드백을 받아 다시 생성 — 만족할 때까지 루프. 사람의 글쓰기 ↔ 편집자 관계의 LLM 버전.
평가 기준을 명확히 글로 쓸 수 있고, LLM이 그 기준을 일관되게 적용할 수 있을 때 효과가 크다. 반복마다 측정 가능한 개선이 보여야 한다 — 그렇지 않으면 루프가 의미 없다.
품질 기준이 명확하고 반복으로 개선 가능한 작업. 예: 문학 번역(뉘앙스), 다단계 검색 + 정보 종합.
LLM이 스스로 계획하고, 도구를 호출하고, 결과를 보고, 다음 행동을 결정한다. 종료 조건이 만족될 때까지(또는 사람이 멈출 때까지) 반복. 워크플로우가 레일 위 기차라면, 에이전트는 지도를 보며 운전하는 차다.
강력하지만 비용·지연·실수의 누적이 모두 커진다. 그래서 안전한 샌드박스 환경 · 명확한 종료 조건 · 인간 체크포인트가 사실상 필수다.
단계 수와 경로를 미리 예측하기 어려운 개방형 문제, 모델의 판단을 신뢰할 수 있는 도메인. 예: SWE-bench 류 코딩 에이전트, 컴퓨터 사용 에이전트.
프레임워크(LangGraph, AutoGen 등)는 학습 곡선을 낮춰주지만, 동시에 추상화 레이어 뒤에 동작을 숨겨 디버깅을 어렵게 만든다. 저자들은 "먼저 LLM API를 직접 호출해보고, 그래도 추상화가 필요할 때만 프레임워크로 옮기라"고 권한다.
Anthropic SDK 기준의 최소 구현. 프레임워크 없이 — 모든 흐름은 일반 Python 코드와 LLM 호출로 표현 가능하다는 글의 핵심을 그대로 보여주는 형태로 정리했다.
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
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자 본문으로 확장하라.", ], )
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
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가 치명적인 가드레일에서 특히 강하다.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), )
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 반환
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")
description·input_schema 품질이 곧 에이전트 품질이다.프로덕션에서는 여기에 관측(로깅·트레이싱) · 가드레일(입출력 필터링) · 휴먼 인 더 루프(중요 행동 전 승인)를 더해야 한다. 패턴 자체보다 이 주변 인프라가 시스템 신뢰도를 결정한다.
7가지 패턴 중 어디부터 도입할지는 ROI 순서로 결정한다. 비용 절감 효과가 즉시 나타나는 단순 패턴부터 시작하고, 자율성이 큰 패턴은 가장 마지막에. 같은 작업도 패턴 선택에 따라 비용·지연·정확도가 크게 달라진다.
쉬운 질문은 Haiku, 어려운 것만 Sonnet/Opus. 분류기 한 번 추가만으로 LLM 비용 30~70% 절감.
하나의 거대 프롬프트를 단계로 쪼개고 게이트 추가. 디버깅 가능 + 단계별 모델 선택 가능.
False negative 비용이 큰 가드레일에서 강력. 서로 다른 관점으로 N회 호출 → 만장일치만 통과.
독립적 하위 작업을 동시 호출. 직렬 처리 5초 → 병렬 0.8초 수준 단축이 흔하다.
평가 기준이 글로 쓸 수 있고 측정 가능할 때만 의미. "더 좋게" 같은 모호한 기준이면 루프만 돈다.
Tier 2는 한 번에 다 도입하지 말고, 가장 통증 큰 곳 하나만 골라 PoC 후 확장하라. 다음 분기 KPI 한 줄에 매핑되는 패턴부터.
하위 작업이 런타임에 정해질 때만 의미. 비용·예측 불가능성이 동시에 증가하므로 max_tokens · max_workers 가드 필수.
샌드박스 + 명확한 종료 조건 + 인간 체크포인트 없이는 운영하지 마라. 한 번의 잘못된 도구 호출이 전체 궤도를 망친다.
전체를 한꺼번에 적용하지 말고, 단계별 ROI를 확인하면서 다음 단계로 넘어간다.
코드 한두 시간, 비용 즉시 30~70% 절감. 분류기 + 모델 라우팅 테이블만 있으면 된다.
기존 단일 프롬프트 작업을 단계로 쪼개고 검증 게이트 추가. 단계별로 다른 모델·온도 사용 가능.
보안·규정·모더레이션처럼 false negative 비용이 큰 영역. 3중 검사 → 만장일치 게이트.
평가 기준을 글로 쓸 수 없으면 의미 없음. 반드시 max_iters로 무한 루프 차단.
비용·지연·실수가 누적되므로 sandbox 환경 + max_steps + 사람 체크포인트는 협상 불가.