Multi-Agent 19
Supervisor와 Orchestrator가 필요한 순간
복잡한 workflow도 Router를 여러 번 붙이면 충분하다고 생각하기 쉽다.
근거 · 교안 p74-p77
1장: Router는 왜 복잡한 워크플로우를 제어하기 어려운가요?
솔라가 화면 속 다이어그램을 손가락으로 짚어가며 중얼거렸다. 여러 개의 에이전트들이 화살표로 복잡하게 연결된 그림이었다. 마치 잘 짜인 도시의 교통망 같았다.
“언니, 멀티 에이전트 워크플로우 만드는 거, 생각보다 간단할지도 몰라.”
솔라의 목소리에는 새로운 발견을 한 듯한 흥분이 실려 있었다. 옆에서 조용히 책을 읽던 루나가 고개를 들었다.
“이거 봐봐. 결국 복잡한 요청이 들어와도, 적절한 전문가 에이전트에게 보내주는 ‘라우터’만 잘 만들면 되는 거 아닐까? 길이 막히면 다른 길로 안내하는 것처럼, 라우터를 여러 개 이어서 만들면 아무리 복잡한 문제라도 해결할 수 있을 것 같아.”
솔라는 자신만만하게 말했다. 라우터가 요청의 성격을 파악해 가장 적합한 에이전트에게 작업을 넘겨주는 ‘분기점’ 역할을 한다는 것은 이미 알고 있었다. 그렇다면 복잡한 작업이란, 여러 개의 분기점이 이어진 것에 불과하지 않을까? 솔라의 머릿속에서는 이미 여러 개의 라우터가 척척 연쇄적으로 작동하며 거대한 문제를 해결하는 그림이 그려지고 있었다.
루나는 솔라의 화면을 잠시 들여다보더니, 말없이 자리에서 일어나 깨끗한 종이 한 장과 펜을 가져왔다.
“네 아이디어, 재미있네. 그럼 우리 간단한 워크플로우를 하나 같이 설계해 볼까? ‘스마트 레시피 봇’ 같은 거 어때?”
“스마트 레시피 봇?”
“응. 사용자한테 요리 이름을 받아서 레시피를 찾아주고, 사용자의 냉장고에 없는 재료가 있으면 자동으로 쇼핑 목록에 추가해 주는 봇이야. 두 단계밖에 없는 간단한 작업이지.”
루나는 종이 위에 네모 하나를 그려 ‘사용자 요청’이라고 적었다. 솔라는 신이 나서 펜을 이어받았다.
“좋아! 그럼 먼저 사용자 요청이 ‘레시피 검색’인지 확인해야 하니까, 첫 번째 라우터가 필요하겠네.”
솔라는 ‘사용자 요청’ 네모에서 화살표를 그려 ‘라우터 1’이라고 적은 동그라미로 연결했다. 그리고 ‘라우터 1’에서 나온 화살표를 ‘레시피 검색 에이전트’라는 네모로 이었다.
“‘라우터 1’은 요청이 들어오면 ‘레시피 검색 에이전트’에게 작업을 토스하는 거야. 아주 완벽해.”
지금까지는 솔라의 생각대로였다. 라우터는 요청을 분류하고 적절한 전문가에게 보내는 역할을 훌륭하게 수행했다.
“좋아. ‘레시피 검색 에이전트’가 ‘토마토 파스타’ 레시피를 성공적으로 찾았어. 그다음은?”
루나의 질문에 솔라는 막힘없이 답했다.
“이제 냉장고를 확인할 차례지. ‘레시피 검색 에이전트’가 찾은 결과를… 음, 또 다른 라우터에게 보내면 되겠다! ‘라우터 2’는 레시피가 넘어왔다는 사실을 보고 ‘냉장고 확인 에이전트’를 호출하는 거야.”
솔라는 자신 있게 ‘레시피 검색 에이전트’에서 ‘라우터 2’로, 다시 ‘냉장고 확인 에이전트’로 이어지는 화살표를 그렸다. 하지만 그림을 완성한 솔라의 손이 순간 멈칫했다. 루나는 그 미세한 망설임을 놓치지 않고 물었다.
“‘냉장고 확인 에이전트’가 확인해 보니, 토마토소스는 있는데 바질이 없대. 이제 어떻게 해야 할까? 워크플로우는 아직 안 끝났잖아. 쇼핑 목록에 바질을 추가해야지.”
“그야… ‘냉장고 확인 에이전트’가 ‘쇼핑 목록 에이전트’에게 정보를 넘겨주면…”
솔라의 목소리가 점점 작아졌다. 무언가 이상했다. 스스로 그린 그림 위에서 솔라의 눈동자가 길을 잃고 헤매기 시작했다.
루나가 펜으로 ‘라우터 1’을 톡톡 두드렸다.
“이 첫 번째 라우터는 ‘레시피 검색 에이전트’에게 일을 보낸 뒤에 뭘 하고 있지?”
“어? 그냥… 자기 일은 끝났으니까… 아무것도 안 하지 않을까?”
“맞아. ‘레시피 검색 에이전트’는?”
“자기가 찾은 레시피를 다음 에이전트에게 넘겨주고… 역할 끝.”
그 순간, 솔라는 자신의 생각이 어디서부터 잘못되었는지 깨달았다. 라우터는 작업을 보낼 대상을 ‘한 번’ 결정하고 나면 그걸로 끝이었다. 그 뒤에 이어진 에이전트가 일을 성공했는지, 실패했는지, 아니면 그 결과에 따라 또 다른 작업이 필요한지는 전혀 알지 못했다. 각 에이전트는 그저 자기에게 주어진 일을 처리하고 다음 주자에게 바통을 넘길 뿐, 전체적인 경주의 흐름을 읽거나 통제하지 못했다.
“아… 라우터는 그냥 길만 알려주는 교통경찰 같은 거구나. 일단 저쪽 길로 가라고 손짓하고 나면, 그 차가 목적지에 잘 도착했는지, 중간에 타이어가 터져서 멈췄는지는 전혀 모르는.”
솔라가 그린 복잡한 화살표들은 한 방향으로만 흐르는 일방통행 도로였다. 되돌아가거나, 옆길로 새거나, 상황을 보고 멈추는 제어 기능이 전혀 없었다. 만약 ‘레시피 검색 에이전트’가 적절한 레시피를 못 찾아서 다시 검색해야 한다면? 바질이 없어서 ‘쇼핑 목록 추가’라는 전혀 다른 갈래의 작업을 시작해야 한다면? 지금 이 설계로는 불가능했다.
“라우터를 여러 개 붙여도, 각자 자기 앞의 길만 알려줄 뿐이구나. 작업의 순서를 정하거나, 결과가 만족스럽지 않을 때 다시 시키거나, 이제 모든 일이 끝났으니 멈추라고 말해주는 역할은 아무도 하지 않아.”
솔라는 펜을 내려놓았다. 처음의 자신감은 온데간데없었다. 눈앞의 ‘스마트 레시피 봇’ 설계도는 이제 해결 불가능한 문제처럼 보였다.
“그럼 언니, 이런 식으로 여러 단계가 얽혀있고, 중간 결과에 따라 다른 일을 해야 하는 복잡한 작업은 대체 어떻게 만들어? 레시피를 다시 검색하게 하거나, 쇼핑 목록을 만들게 하는 ‘누군가’가 필요하잖아. 전체 상황을 지켜보면서 지시를 내리는 존재가.”
2장: Supervisor는 어떻게 워크플로우의 ‘중앙 사령관’이 되나요?
솔라가 펜을 내려놓은 뒤에도, 두 사람 사이에는 한동안 말이 없었다. 실패한 ‘스마트 레시피 봇’ 설계도만이 책상 위에 덩그러니 놓여 있었다. 일방통행 화살표들로 가득 찬 그림은 솔라가 마주한 한계 그 자체였다. 루나는 말없이 그 종이를 자기 앞으로 끌어당겼다.
루나는 잠시 그림을 들여다보더니, 펜을 들어 모든 에이전트 상자들 위에 커다란 원을 하나 그렸다. 그리고 그 안에 물음표 하나를 큼지막하게 그려 넣었다. 다음으로, ‘라우터 1’, ‘라우터 2’ 같은 분기점들을 지우고, 에이전트들 사이에 이어져 있던 화살표들도 모두 지워버렸다. 텅 빈 공간에 루나는 새로운 규칙을 그리기 시작했다. 새로 그린 물음표 원에서 ‘레시피 검색 에이전트’, ‘냉장고 확인 에이전트’ 등 모든 작업자 에이전트를 향해 실선 화살표를 그었다. 그리고 각각의 작업자 에이전트들로부터는 다시 물음표 원을 향해 점선으로 된 화살표를 그었다. 이전의 직선적인 흐름은 사라지고, 모든 길이 중앙의 물음표로 모였다가 다시 뻗어나가는 ‘허브 앤 스포크(hub-and-spoke)’ 구조의 새로운 그림이 탄생했다.
“어? 이건… 아까랑 완전히 다른 그림이잖아.”
새로운 설계도를 본 솔라가 놀라 물었다.
“모든 길이 저 물음표로 통하네. 작업이 끝나면 다시 돌아오는 것처럼 점선으로 이어져 있고.”
“네가 말한 ‘전체 상황을 지켜보면서 지시를 내리는 존재’를 그려본 거야.”
루나가 물음표 원을 가리키며 말했다.
“이런 식으로 전체 워크플로우의 판단과 조율을 전담하는 에이전트를 ‘슈퍼바이저(Supervisor)’ 혹은 ‘오케스트레이터(Orchestrator)’라고 불러.”
“슈퍼바이저…”
솔라는 새로운 단어를 입안에서 굴려보았다. 중앙에서 모든 것을 통제하는 그림을 보니 한 가지 생각이 떠올랐다.
“아! 그럼 결국 더 똑똑하고 상황 판단을 잘하는 라우터 같은 거네? 여러 갈래 길을 전부 다 파악하고 있다가, 그때그때 가장 좋은 길 하나를 딱 정해주는 초강력 라우터!”
“단순히 똑똑한 라우터라고 생각하면 중요한 걸 놓치게 돼. 슈퍼바이저는 라우터와 일하는 방식 자체가 근본적으로 달라.”
루나는 책상 한편의 깨끗한 공간을 손으로 가리켰다.
“슈퍼바이저는 이걸 보고 판단하거든. 바로 ‘상태(State)’라는 거.”
루나는 ‘상태(State)’라는 제목 아래, ‘스마트 레시피 봇’의 작업 과정을 시뮬레이션하기 시작했다.
“자, 사용자가 ‘토마토 파스타’를 요청했어. 맨 처음 상태는 뭘까?”
“음… 그냥 요청 내용 하나만 덩그러니 있지 않을까?”
루나가 종이 위에 적었다.
상태 = { "요청": "토마토 파스타", "메시지들": [] }
“맞아. 슈퍼바이저는 이 상태를 보고, 첫 번째 작업으로 ‘레시피 검색 에이전트’를 호출하기로 결정해. 그리고는 기다리는 거야. 에이전트가 일을 끝내고 결과를 보고할 때까지.”
루나는 ‘레시피 검색 에이전트’가 일을 마친 상황을 가정했다.
“에이전트가 레시피를 찾아서 보고했어. 그럼 슈퍼바이저는 그 결과를 기존 상태에 추가해서 업데이트해.”
종이 위의 상태 정보가 풍부해졌다.
상태 = { "요청": "토마토 파스타", "메시지들": [("레시피 검색 결과", "토마토 파스타 레시피 내용...")] }
“이제 슈퍼바이저가 다시 이 상태를 들여다봐. 여기가 라우터와의 결정적인 차이야. 라우터는 한번 길을 알려주면 그걸로 임무가 끝나지만, 슈퍼바이저는 매번 작업이 끝날 때마다 전체 상황이 기록된 이 상태를 처음부터 다시 읽고 다음 할 일을 결정해.”
솔라의 눈이 가늘어졌다. 아직은 그 차이가 선명하게 와닿지 않았다. 루나는 계속해서 시뮬레이션을 이어갔다.
“상태를 보니 레시피가 성공적으로 찾아졌네. 슈퍼바이저의 규칙에는 ‘레시피가 있으면 냉장고를 확인하라’고 되어 있어. 그래서 ‘냉장고 확인 에이전트’를 호출하지. 그 에이전트가 일을 마치고 ‘바질 없음’이라고 보고하면, 상태는 또다시 업데이트돼.”
상태 = { "요청": "토마토 파스타", "메시지들": [..., ("냉장고 확인 결과", "부족한 재료: 바질")] }
“자, 솔라. 이제 슈퍼바이저는 이 최신 상태를 보고 뭘 결정할까?”
루나의 질문에 솔라는 종이 위에 적힌 상태 정보를 꼼꼼히 들여다봤다. ‘부족한 재료: 바질’이라는 대목이 눈에 들어왔다.
“부족한 재료가 있으니까… ‘쇼핑 목록 에이전트’를 불러서 바질을 추가하라고 시키겠네!”
“정확해. 그럼 질문을 바꿔볼게. 만약 맨 처음 ‘레시피 검색 에이전트’가 레시피를 못 찾고 실패했다면, 상태는 어떻게 기록됐을까?”
“음… ‘레시피 검색 결과’가 ‘실패’ 또는 ‘결과 없음’ 같은 내용으로 기록됐겠지?”
“맞아. 그럼 슈퍼바이저는 그 상태를 보고 ‘냉장고 확인 에이전트’를 호출할까?”
그 순간, 솔라의 머릿속에서 무언가 ‘탁’ 하고 끊어졌던 회로가 이어지는 느낌이 들었다.
“아니! 레시피도 없는데 냉장고를 확인할 리가 없잖아! 아마… 검색어에 오타가 있었을 수 있으니 사용자에게 되묻거나, 아니면 그냥 작업이 실패했다고 알리고 전체 과정을 끝내버리겠지. 아…!”
솔라는 비로소 깨달았다. 라우터는 그저 다음 목적지를 가리키는 일방통행 표지판이었다. 한번 그 길로 차를 보내고 나면, 그 차가 과속을 하든 고장이 나든 전혀 신경 쓰지 않았다. 하지만 슈퍼바이저는 달랐다.
“슈퍼바이저는 중앙 관제탑 같은 거구나! 모든 에이전트라는 비행기가 이륙하고 임무를 수행할 때마다, 반드시 관제탑으로 돌아와 현재 위치와 상태를 보고해야 하는 거야. 그럼 관제탑은 공항 전체의 상황판(State)을 보고, ‘좋아, 다음엔 저쪽 활주로로 가’라고 지시를 내리거나, ‘연료가 부족하니 재급유부터 받아’라고 하거나, ‘기상 악화로 더 이상 비행은 무리니 착륙하라’고 명령하는 거지.”
솔라는 흥분하며 루나가 그린 그림 위에서 펜으로 흐름을 따라갔다. 작업자가 일을 한다. 중앙의 슈퍼바이저에게 보고한다. 슈퍼바이저는 그 보고를 포함한 전체 ‘상태’를 읽는다. 그리고 다음 지시를 내린다. 이 순환 구조야말로 반복, 조건 분기, 재시도, 종료 등 복잡한 제어를 가능하게 하는 핵심이었다. ‘State 기반 제어’라는 개념이 손에 잡힐 듯 선명해졌다.
“이제 슈퍼바이저가 라우터랑 근본적으로 어떻게 다른지, 얼마나 강력한
3장: 언제, 어떤 복잡한 워크플로우에 Supervisor가 필요한가요?
솔라의 머릿속은 방금 깨달은 ‘State 기반 제어’라는 개념으로 가득 차 있었다. 관제탑이 공항의 모든 비행기 상태를 실시간으로 파악하며 다음 지시를 내리듯, 슈퍼바이저가 전체 작업의 상태를 보고 흐름을 통제하는 모습은 경이롭기까지 했다. 라우터의 일방통행식 제어와는 차원이 다른 힘이었다. 솔라는 이제 어떤 복잡한 문제든 이 슈퍼바이저 패턴으로 해결할 수 있을 것 같은 자신감에 찼다.
루나는 아무 말 없이 새로운 종이를 책상 위에 올려놓았다. 거기에는 ‘자동 뉴스 기사 생성 봇’이라는 제목과 함께 몇 가지 요구사항이 적혀 있었다.
- 주어진 주제와 관련된 최신 뉴스 3건 이상을 웹에서 수집한다.
- 수집한 정보를 바탕으로 500자 내외의 요약 기사를 작성한다.
- 기사 초안에 사실 오류나 부자연스러운 문장이 없는지 검증한다.
- 검증 결과, 품질이 낮으면 초안을 수정하여 다시 검증한다. (최대 2회 재시도)
- 최종적으로 완성된 기사만 사용자에게 보여준다.
솔라는 요구사항을 천천히 읽어 내려갔다. ‘레시피 봇’보다 훨씬 복잡했다. 특히 3번과 4번 항목, ‘검증’과 ‘재시도’라는 단어가 눈에 밟혔다. 솔라는 자신 있게 입을 열려다가 순간 망설였다. 슈퍼바이저가 필요하다는 것은 알겠는데, 이 복잡한 규칙들을 정확히 어떻게 구현해야 할지 눈앞이 막막했다.
“언니, 이것도 당연히 슈퍼바이저를 써야겠지? 그런데… 기사 품질이 낮은지 판단하고, 다시 쓰게 시키는 건 어떻게 해야 할지 감이 잘 안 와. ‘State’에 뭘 담아야 하고, 슈퍼바이저는 그걸 보고 뭘 해야 하는 거지?”
“바로 그 지점이야. 슈퍼바이저가 언제, 왜 강력한 힘을 발휘하는지.”
루나는 종이 한쪽에 ‘상태(State)’라고 적고 중괄호를 열었다.
“‘자동 뉴스 기사 생성 봇’의 첫 번째 임무는 뉴스 수집이야. 슈퍼바이저가 ‘리서처 에이전트’를 호출했고, 에이전트가 성공적으로 뉴스 3건을 찾아왔다고 해보자. 그럼 상태는 어떻게 될까?”
솔라는 이전의 경험을 떠올리며 답했다. “음… 상태 안에 ‘수집된 뉴스’ 목록이 생기겠지.”
루나가 종이에 적었다.
상태 = { "뉴스 목록": [...], "초안": null, "검토 의견": null }
“좋아. 다음은 기사 작성이네. 슈퍼바이저는 상태에 ‘뉴스 목록’이 있는 걸 보고 ‘작가 에이전트’를 호출해. 작가가 일을 마치면 상태는 또 업데이트되겠지.”
상태 = { "뉴스 목록": [...], "초안": "엉망진창인 첫 번째 기사...", "검토 의견": null }
“자, 이제 3번 요구사항. ‘사실 오류나 부자연스러운 문장이 없는지 검증’할 차례야. 라우터 기반 시스템이었다면 이 엉망인 초안이 그대로 사용자에게 갔을 거야. 하지만 슈퍼바이저는 상태를 보고 다음 행동을 결정하지. 이 상태를 본 슈퍼바이저는 뭘 해야 할까?”
솔라는 잠시 고민했다. “초안이 생겼으니, 그걸 검증할 누군가를 불러야 해. ‘비평가 에이전트’ 같은 거?”
“훌륭한 생각이야. 슈퍼바이저는 ‘비평가 에이전트’를 호출해서 초안을 넘겨줘. 비평가는 기사를 읽고 ‘초안의 논점이 불분명하고, 핵심 수치가 틀렸음’이라는 의견을 내놓았어. 이 의견은 어디로 갈까?”
“당연히 상태로! ‘검토 의견’ 항목에 기록되겠지.”
루나가 상태를 마지막으로 업데이트했다.
상태 = { "뉴스 목록": [...], "초안": "엉망진창인 첫 번째 기사...", "검토 의견": "논점 불분명, 핵심 수치 오류" }
“이제 가장 중요한 순간이야.” 루나가 펜으로 ‘검토 의견’ 부분을 가리켰다. “슈퍼바이저는 매 단계마다 이 상태 전체를 다시 읽어. ‘검토 의견’이 비어있지 않고, 심지어 부정적인 내용이 담겨 있는 걸 봤어. 그럼 슈퍼바이저는 무슨 결정을 내릴까? 작업을 끝낼까?”
그 순간, 솔라의 머릿속에 흩어져 있던 조각들이 완벽하게 맞춰졌다.
“아니! 끝내면 안 되지! 4번 요구사항, ‘품질이 낮으면 초안을 수정하여 다시 검증한다’를 수행해야 해! 슈퍼바이저는 ‘작가 에이전트’를 다시 호출하는 거야. 그런데 그냥 호출하는 게 아니야. ‘이 뉴스 목록이랑 엉망인 초안, 그리고 여기 비평가가 준 의견 참고해서 다시 써!’라고 모든 정보를 함께 넘겨주면서!”
이것이 바로 ‘반복과 개선’의 핵심이었다. 슈퍼바이저는 단순히 에이전트를 호출하는 것을 넘어, 작업의 실패 기록과 개선 방향까지 상태에 담아 관리했다. 그리고 그 상태를 기반으로 동일한 작업을 ‘더 나은 조건’으로 재시도하게 만드는, 정교한 제어 루프를 만들 수 있었다. 만약 비평가의 검토 의견이 ‘좋음’이었다면, 슈퍼바이저는 루프를 멈추고 ‘작업 종료’를 결정했을 것이다.
솔라는 종이 위에 나열된 요구사항들을 다시 한번 훑어보았다. 이제는 그저 복잡한 목록이 아니었다. 언제 슈퍼바이저가 필요한지를 알려주는 명확한 신호들로 보였다.
“알겠다…! 슈퍼바이저가 필요한 순간은 명확하구나.”
솔라는 펜을 들어 요구사항 옆에 자신만의 언어로 주석을 달기 시작했다.
- 품질 검증 및 반복 개선이 필요한 작업: ‘작가’가 쓴 글을 ‘비평가’가 검토하고, 결과가 나쁘면 피드백과 함께 다시 쓰게 하는 것처럼, 결과물의 품질을 보장해야 할 때.
- 단계적 파이프라인 제어: 기사 작성 과정처럼, ‘리서치 -> 초안 작성 -> 검증 -> 수정’ 순서가 반드시 지켜져야 하고, 중간 단계의 성공 여부에 따라 다음 단계가 결정될 때.
- 비용/실패 제어: ‘최대 2회 재시도’처럼, 무한 루프에 빠져 비용이 계속 발생하는 것을 막거나, 특정 횟수 이상 실패하면 작업을 중단시켜야 할 때.
- 역할 기반 협업: ‘리서처’, ‘작가’, ‘비평가’처럼 각자 다른 역할을 가진 에이전트들이 하나의 목표를 위해 협력해야 할 때, 이들의 작업을 조율하는 지휘자가 필요할 때.
주석 작성을 마친 솔라는 만족스러운 표정으로 숨을 내쉬었다. 더 이상 슈퍼바이저는 ‘만능 해결사’도, ‘어려운 개념’도 아니었다. 어떤 종류의 문제를 해결하기 위한 구체적이고 명확한 ‘도구’였다.
그녀는 문득 자신이 처음 그렸던 ‘스마트 레시피 봇’의 실패한 설계도를 떠올렸다. 책상 한구석에 버려져 있던 종이를 집어 들었다. 일방통행 화살표들로 가득 찬, 이제는 부끄러워진 자신의 첫 아이디어였다. 솔라는 빨간 펜으로 그림 중앙의 ‘라우터’들을 과감하게 지워버렸다. 그리고 그 자리에 커다란 원을 그리고 ‘슈퍼바이저’라고 적었다. 모든 에이전트로부터 슈퍼바이저를 향해 점선 화살표를, 슈퍼바이저에서 에이전트로 실선 화살표를 그렸다. 그리고 ‘레시피 검색 에이전트’ 옆에 작은 메모를 남겼다.
‘결과 없음’ 보고 시 -> 슈퍼바이저는 사용자에게 재질문 또는 관련 레시피 추천을 결정.