LLMOps 25

Supervisor 패턴 실행 Trace와 Waterfall 읽기

Trace 목록만 보면 실행이 지나간 순서와 병목을 읽기 어렵다.

근거 · 교안 p86-p89

Supervisor 패턴 실행 Trace와 Waterfall 읽기 대표 이미지

1장: Trace 목록, 실행 순서의 미로 속에서 길을 잃다

솔라의 손가락이 노트북 트랙패드 위에서 가볍게 멈췄다. 화면에는 LangSmith 프로젝트의 실행 기록, 즉 ‘Trace 목록’이 길게 펼쳐져 있었다. 위에서부터 순서대로 AgentExecutor, ChatOpenAI, ToolNode 같은 이름들이 빽빽하게 들어차 있었다.

“음, 간단하네.”

솔라는 자신만만하게 중얼거렸다. Supervisor 패턴으로 만든 복잡한 Agent의 실행 과정을 디버깅해야 한다는 말을 들었을 땐 조금 막막했지만, 막상 결과를 보니 안심이 되었다. 언제, 무엇이, 얼마나 오래 실행되었는지 시간순으로 잘 정리된 목록이 있으니, 이걸 따라가기만 하면 흐름을 파악하는 건 시간문제라고 생각했다.

“이 순서대로 따라가면, 어디서 막혔는지 금방 찾을 수 있겠어.”

자신의 판단을 확인해보고 싶어진 솔라는 옆에 놓여 있던 빈 노트와 펜을 집어 들었다. 화면에 보이는 Trace 목록을 그대로 옮겨, 실행 흐름도를 직접 그려보기로 마음먹었다. 화면 맨 위에 있는 Supervisor 노드부터 시작했다. 노트에 ‘Supervisor 시작’이라고 적고 화살표를 그었다.

다음 목록은 ChatOpenAI. Supervisor가 첫 판단을 위해 LLM을 호출했을 것이다. ‘LLM 호출’이라고 적고 화살표를 이었다. 그다음은 Router 노드. LLM의 답변을 바탕으로 어떤 작업을 할지 결정하는 단계다. 여기까지는 순조로웠다.

하지만 목록을 아래로 내려갈수록 솔라의 펜 끝이 망설이기 시작했다. Router 다음에 research라는 이름의 노드가 나타났고, 그 아래에 다시 ChatOpenAITool이 여러 번 등장했다. 그러다 갑자기 Supervisor가 다시 나타났다.

“잠깐만… 이 research 노드는 Supervisor가 부른 건가, 아니면 Router가 부른 건가? 그리고 이 도구 실행 결과는 누구한테 돌아가는 거지?”

솔라는 목록을 위아래로 훑으며 호출 관계를 추측하려 애썼다. 노트 위에 그어진 화살표들은 이내 길을 잃고 복잡하게 얽히기 시작했다. 어떤 ChatOpenAI 호출은 Supervisor의 것이었고, 다른 것은 research 워커 Agent의 것이었다. 하지만 목록은 그저 ‘ChatOpenAI’라고만 표시할 뿐, 누가 누구의 지시를 받아 움직였는지 명확히 보여주지 않았다. 마치 연극의 모든 대사를 배우 이름 없이 시간 순서대로만 늘어놓은 대본 같았다.

“이게 아닌데. 흐름이 전혀 안 보이잖아.”

솔라는 결국 펜을 내려놓고 헝클어진 자신의 노트를 멍하니 바라보았다. 깔끔하게 정리된 줄 알았던 Trace 목록은, 파고들수록 길을 잃게 만드는 미로였다.

조용히 솔라의 어깨너머로 화면을 보고 있던 루나가 입을 열었다.

“그 목록에서, 중간쯤에 있는 Router가 내린 결정이 뭐야?”

루나의 목소리에 솔라는 다시 화면으로 고개를 돌렸다. Router 항목을 클릭하자, 이 노드가 다음 단계로 ‘research’ 노드를 지목했다는 정보가 나타났다.

“‘research’로 가라고 했어. 그럼 이 다음 실행된 research 노드가 바로 그거겠지.”

“응. 그럼 그 research 노드가 작업을 마친 뒤에, 그 결과는 목록 어디로 갔을까?”

솔라는 다시 스크롤바를 위아래로 움직였다. research 노드 아래에는 여러 도구와 LLM 호출이 뒤섞여 있었고, 한참 아래에야 다시 Supervisor 노드가 등장했다. research의 작업 결과가 정확히 어느 시점에, 어떻게 Supervisor에게 전달되었는지 그 연결 고리를 찾을 수 없었다. 그냥 시간 순서상 뒤에 있다는 것 외에는.

그 순간 솔라는 깨달았다. Trace 목록은 실행된 작업들을 시간 순서대로 찍어놓은 ‘기록 사진’들의 나열일 뿐, 작업들 사이의 관계나 전체적인 흐름을 보여주는 ‘지도’가 아니었다. 누가 누구를 호출하고, 그 결과가 어디로 돌아가는지 알려면 이 평면적인 목록 속에서 탐정처럼 단서를 조합해야 했다.

“아… 알겠다. 이건 그냥 시간 순으로 일어난 일들을 쭉 늘어놓은 거구나. 누가 부모고 누가 자식인지, 어떤 흐름으로 작업이 이어지는지는 전혀 알 수가 없네.”

솔라는 자신이 그리다 만 복잡한 화살표 다이어그램을 보며 말했다. 단순한 목록만으로는 길을 찾을 수 없다는 명백한 증거였다.

“이 뒤죽박죽인 순서를 제대로 보려면, 뭔가 다른 게 필요하겠어. 이 전체적인 흐름을 한눈에 보여주는 진짜 지도 같은 거 말이야.”

2장: Run Tree: Agent의 숨겨진 실행 순서를 드러내다

솔라의 시선은 자신이 그리다 만, 화살표들이 어지럽게 얽힌 노트와 노트북 화면을 번갈아 오갔다. 실행 기록을 시간순으로 나열한 ‘Trace 목록’은 탐정에게 흩어진 증거 사진들을 던져줄 뿐, 사건의 전체적인 개요를 보여주지 않았다. 명백한 한계였다.

그때, 조용히 지켜보던 루나가 트랙패드 위로 손을 뻗었다. 화면 왼쪽의 여러 탭 중에서 ‘Run’이라고 적힌 부분을 클릭하자, 아래로 ‘Tree’와 ‘Waterfall’이라는 하위 메뉴가 나타났다. 루나는 망설임 없이 ‘Tree’를 선택했다. 순식간에 빽빽하던 텍스트 목록이 사라지고, 그 자리에 들여쓰기로 구조화된 새로운 화면이 나타났다.

맨 위에는 Supervisor가 있었고, 그 아래로 ChatOpenAIRouter가 한 단계 들여쓰기 되어 있었다. Router 아래에는 다시 research가, 그 아래에는 또 다른 ChatOpenAIToolNode가 가지를 치며 뻗어 나가는 모양이었다.

솔라는 눈살을 살짝 찌푸렸다.

“이게 뭐야? 그냥 아까 그 목록에 들여쓰기만 좀 한 거 아니야?”

얼핏 보기에는 정보의 종류가 달라진 것 같지 않았다. 단순히 텍스트를 계단식으로 배열해서 조금 더 보기 좋게 꾸민 것일 뿐이라고, 솔라는 생각했다.

“아까 노트에 그리려 했던 그 흐름 말이야. 이번엔 이 화면 위에서 손가락으로 한번 따라가 볼래?”

루나의 말에 솔라는 반신반의하며 화면에 손가락을 가져갔다. 맨 위, 모든 것의 시작인 Supervisor에 손가락 끝을 올렸다.

첫 번째 자식 노드는 ChatOpenAI. ‘Supervisor가 판단을 위해 LLM을 호출했구나.’ 여기까지는 이전과 같았다. 다음 자식 노드인 Router로 손가락을 옮겼다. ‘그리고 그 결과로 Router를 호출했지.’

바로 다음 순간, 솔라의 손가락이 멈칫했다. Router 노드 아래, 한 단계 더 깊숙이 들여쓰기 된 곳에 research 노드가 있었다. 지난번 Trace 목록에서는 Router 다음에 research가 그냥 순서대로 나타났을 뿐이었다. 하지만 이 트리 구조에서는 달랐다. researchSupervisor와 같은 레벨의 형제가 아니라, 명백히 Router의 자식이었다.

“아…!”

작은 탄성이 터져 나왔다. 솔라는 손가락을 들어 research 노드 아래로 펼쳐진 자식 노드들—또 다른 ChatOpenAI와 도구 호출—을 차례로 짚어 나갔다. 이 모든 복잡한 과정이 research라는 부모 노드 아래에 속해 있었다. 그리고 이 research 노드가 끝나자, 흐름은 다시 그 부모인 Router를 거쳐 최상위의 Supervisor로 돌아가는 구조가 한눈에 보였다.

자신이 그렸던 어지러운 다이어그램의 정답이 눈앞에 펼쳐진 기분이었다. 더 이상 ‘이 작업은 누가 호출했지?’라고 추측할 필요가 없었다. 들여쓰기의 깊이가 곧 호출의 깊이였고, 부모와 자식 관계가 명확한 위계질서를 이루고 있었다.

“알겠다! 들여쓰기가 그냥 모양이 아니었네. 누가 누구를 불렀는지, 어떤 작업이 어떤 작업에 속해 있는지, 이 전체적인 실행 구조를 보여주는 거였어. 이게 바로 내가 찾던 지도구나.”

솔라는 자신이 그리다 실패했던 화살표들을 떠올렸다. 그 복잡한 관계망이 이 ‘실행 트리’ 안에서는 이토록 간결하고 명확하게 정리되어 있었다. 이제 Supervisor Agent가 어떤 순서와 구조로 작업을 처리하는지 자신 있게 설명할 수 있었다.

흐름 파악에 대한 자신감을 얻은 솔라는 만족스러운 표정으로 트리를 다시 한번 훑어보았다. 각 노드의 이름 옆에는 실행에 걸린 시간이 작게 표시되어 있었다. Supervisor: 8.3s, Router: 0.8s, research: 5.1s

그런데 무언가 이상했다. 전체 흐름은 이제 알겠지만, 시선이 자꾸만 research 노드에 머물렀다. 다른 노드들에 비해 유독 실행 시간이 길었다.

“순서는 이제 확실히 알겠어. 그런데 왜 research 노드에서만 5초 넘게 걸린 거지? 이 트리만 봐서는… 그 5초 중에 정확히 어떤 단계가 시간을 다 잡아먹었는지는 한눈에 들어오지 않네.”

지도는 찾았지만, 지도 위 어느 길에서 교통체증이 발생했는지는 여전히 희미했다.

3장: Waterfall: 시간 축으로 병목을 찾고, 전체를 파악하다

솔라의 시선은 화면의 한 숫자에 꽂혀 있었다. research: 5.1s. Run Tree가 밝혀준 실행 구조는 명확했지만, 바로 그 명확함이 새로운 질문을 던지고 있었다. 전체 실행 시간 8.3초 중 절반 이상이 단 하나의 노드, research에서 소모되었다. 다른 노드들이 1초 미만인 것과 비교하면 유독 도드라지는 숫자였다.

솔라는 research 노드 아래에 있는 자식 노드들의 실행 시간을 더듬어 보았다. ChatOpenAI, ToolNode 등 여러 개의 자식 노드가 있었지만, Run Tree는 각자의 실행 시간만 보여줄 뿐, 5.1초라는 시간 속에서 이들이 어떻게 상호작용하며 시간을 나누어 가졌는지는 보여주지 않았다. 마치 전체 예산은 알지만, 어느 항목에 돈이 줄줄 새고 있는지 지출 명세서 없이는 알 수 없는 상황과 같았다.

“순서는 알겠는데… 시간은 모르겠네.”

솔라는 펜으로 research 노드 옆에 적힌 5.1s를 꾹 누르며 중얼거렸다. 지도(Run Tree)는 찾았지만, 그 지도 위 어느 구간이 상습 정체 구간인지 알아내지 못해 답답했다.

루나는 솔라의 혼잣말을 듣고는, 다시 한번 화면 왼쪽의 메뉴로 마우스를 옮겼다. 아까 ‘Tree’를 선택했던 바로 아래, ‘Waterfall’이라고 적힌 메뉴를 클릭했다.

화면이 바뀌는 순간, 솔라는 무심코 말했다.

“어? 이건 그냥… Run Tree를 막대그래프로 바꾼 거 아니야?”

화면에는 Run Tree와 똑같은 계층 구조를 가진 노드 목록이 보였다. 하지만 각 노드의 이름 옆에는 텍스트 대신, 길이가 제각각인 색깔 막대가 그려져 있었다. 솔라의 눈에는 그저 보기 좋게 디자인을 바꾼 것처럼 보일 뿐이었다. 정보의 본질은 같다고 생각했다.

루나는 아무 말 없이 화면을 가리켰다.

“가장 긴 막대를 찾아봐. 그리고 그 막대가 누구의 자식인지 한번 따라가 볼래?”

솔라는 루나의 말에 시큰둥하게 화면을 훑었다. 여러 막대그래프가 왼쪽에서 오른쪽으로 시간의 흐름을 따라 늘어서 있었다. 눈으로 대충 훑어봐도 유독 긴 막대가 하나 눈에 띄었다. research 노드 안에 중첩된, 아주 긴 연보라색 막대였다. 그 막대 위로 마우스를 가져가자 ChatOpenAI: 4.5s라는 정보가 나타났다.

바로 그 순간, 솔라는 숨을 멈췄다.

단순한 막대그래프가 아니었다. 모든 것이 한 번에 이해되기 시작했다. Run Tree에서 봤던 그 익숙한 계층 구조가 그대로 유지되어 있었다. Supervisor 막대 안쪽에 Router 막대가 있고, 그 Router 막대 안쪽에 research 막대가 있는 식이었다. 호출 관계가 막대의 포함 관계로 시각화되어 있었다.

그리고 가장 결정적인 것은 시간이었다. 각 막대의 시작점은 작업이 시작된 시간을, 막대의 길이는 작업에 소요된 시간을 의미했다. 어떤 작업이 끝나고 다음 작업이 시작되는지, 혹은 여러 작업이 동시에 진행되는지 그 모든 시간의 흐름이 파노라마처럼 펼쳐져 있었다.

“아…! 알겠다! 이건 시간 축이 더해진 Run Tree였구나!”

솔라는 방금 자신이 가리켰던 ChatOpenAI 막대를 다시 보았다. 그 막대는 research 막대의 시작점에서 조금 뒤에 시작해서, research 막대가 끝나기 직전에 끝났다. research 노드가 잡아먹던 5.1초의 행방이 드디어 밝혀진 것이다. 그중 4.5초를 이 LLM 호출이 혼자서 다 쓰고 있었던 것이다.

Run Tree에서는 그저 research의 자식 노드 중 하나로 보였던 ChatOpenAI가, Waterfall 뷰에서는 전체 성능을 좌우하는 핵심 병목 지점이라는 것이 명백하게 드러났다.

“Run Tree는 작업의 ‘구조’를 보여주는 설계도였고, 이 Waterfall은 그 설계도 위에 시간의 흐름을 그려 넣은 ‘공정 계획표’ 같은 거였어.”

솔라는 이제 두 도구가 어떻게 다른지, 그리고 왜 둘 다 필요한지 완벽하게 이해했다. 하나는 뼈대를, 다른 하나는 그 뼈대 위를 흐르는 혈액의 속도를 보여준다.

더 이상 망설일 필요가 없었다. 솔라는 키보드에 손을 올리고 새 문서를 열었다. 그리고 망설임 없이 보고서를 작성하기 시작했다.

<Supervisor Agent 실행 분석 보고서>

  1. 실행 흐름 분석 (Run Tree 기반)

    • Agent는 SupervisorRouterresearch의 계층적 순서로 작업을 호출함.
    • research 노드는 자체적으로 ChatOpenAIToolNode를 호출하여 작업을 수행함.
  2. 성능 병목 진단 (Waterfall 기반)

    • 주요 병목: research 노드 (총 실행 시간 8.3초 중 5.1초 소요).
    • 근본 원인: research 노드 내부에서 호출된 ChatOpenAI API가 4.5초를 소요, 대부분의 지연 시간을 발생시킴.

솔라는 보고서의 마지막 문장을 입력하고 만족스럽게 화면을 바라보았다. 더 이상 복잡하게 얽힌 실행 기록의 미로에서 헤매던 솔라는 없었다. 구조를 파악하고, 시간의 흐름 속에서 병목을 찾아내는 분석가가 되어 있었다. 어지럽게 흩어져 있던 점들이 마침내 하나의 선명한 그림으로 완성되는 순간이었다.