에이전트와 워크플로우는 뭐가 다른가
"AI 에이전트를 만들어 보고 싶다"는 요청을 받으면 저는 항상 되묻습니다. 그거 워크플로우 아니에요? 실제로 팀에서 "에이전트"라고 부르는 것의 절반 이상은 그냥 if문 몇 개로 이어 붙인 파이프라인입니다. 이름을 잘못 붙였다고 나쁜 건 아니지만, 이름을 잘못 붙이면 설계를 잘못하게 됩니다.
워크플로우: 사람이 순서를 정한다
고객 문의를 분류하고, 관련 문서를 검색하고, 답변 초안을 만드는 시스템을 생각해 봅시다. 워크플로우로 짠다면 이렇게 됩니다.
def handle_ticket(ticket: str) -> str:
category = classify(ticket) # LLM 호출 1
docs = search_docs(category, ticket) # 검색 (LLM 아님)
draft = draft_reply(ticket, docs) # LLM 호출 2
return draftclassify 다음에 search_docs가 오고, 그다음 draft_reply가 온다는 순서는 코드에 고정되어 있습니다. LLM은 각 단계 안에서만 판단할 뿐, "다음에 뭘 할지"는 전혀 정하지 않습니다. 이게 핵심입니다. 워크플로우에서 LLM은 부품이고, 흐름은 사람이 짭니다.
이 방식의 장점은 명확합니다. 실행 경로가 유한하고 예측 가능하니 디버깅이 쉽고, 각 단계마다 비용과 지연시간을 예산으로 잡을 수 있습니다. 장애가 나도 "3단계에서 죽었다"고 바로 말할 수 있습니다.
에이전트: LLM이 순서를 정한다
같은 문제를 에이전트로 짜면 코드가 아니라 프롬프트가 흐름을 결정합니다.
tools = [classify_tool, search_docs_tool, draft_reply_tool]
messages = [{"role": "user", "content": ticket}]
while True:
response = model.run(messages, tools=tools)
if response.stop_reason != "tool_use":
return response.text
result = execute(response.tool_call)
messages.append(response.to_message())
messages.append({"role": "tool", "content": result})여기서는 모델이 classify_tool을 먼저 부를지, 바로 search_docs_tool을 부를지, 아니면 사람에게 되물을지를 그때그때 정합니다. 티켓이 애매하면 검색을 두 번 할 수도 있고, 아예 분류를 건너뛸 수도 있습니다. 유연하지만 대가가 있습니다 — 같은 입력을 넣어도 실행 경로가 매번 다를 수 있고, 그만큼 비용도 요동칩니다.
구분이 왜 실무에 중요한가
이 둘을 섞어서 부르면 생기는 문제는 구체적입니다. "에이전트를 만들자"고 시작한 프로젝트가 사실은 3단계 워크플로우로 충분한데, 괜히 도구 호출 루프를 얹어서 디버깅만 어려워지는 경우를 여러 번 봤습니다. 반대로 "그냥 파이프라인 하나 짜죠"라고 시작했다가, 요구사항이 계속 늘어 결국 분기가 열 개 넘게 생기면 — 그때는 차라리 모델에게 판단을 맡기는 게 코드가 더 짧아집니다.
판단 기준은 하나로 좁힐 수 있습니다. 다음 단계가 미리 정해질 수 있는가? 정해질 수 있다면 워크플로우로 짜는 게 낫습니다. 디버깅 가능성과 비용 예측 가능성을 공짜로 얻기 때문입니다. 입력에 따라 다음 단계 자체가 달라져야 한다면, 그때 에이전트 루프가 필요해집니다.
이 책 전체는 후자, 즉 진짜 에이전트 루프를 프레임워크 없이 직접 짜는 이야기입니다. 다음 장에서는 그 루프의 재료가 되는 LLM API를 에이전트 설계자의 눈으로 다시 보겠습니다.