레슨 1: 무엇이 잘못됐는지 말할 수 없는 이유
학습 목표:
- 사용자에게 보이는 하나의 증상 아래에 밖에서는 구별되지 않는 여러 근본 원인이 숨을 수 있는 이유 이해하기
- 비결정성이 '재현해서 브레이크포인트를 건다'는 전통적인 디버깅 직관을 어떻게 무너뜨리는지 설명하기
- 에이전트 시스템에서 에러가 취하는 서로 다른 형태 알아보기: 연쇄 실패, 궤적 분기, 턴을 넘어 쌓이는 누적, 멀티 에이전트의 창발
전제: 이 시리즈의 첫 10개 코스를 마치고, stop_reason 이 구동하는 하네스 루프를 손으로 쓸 수 있으며, 평가 트랙은 pass/fail 만 알려준다는 것을 이해하고 있어야 합니다 | 다음: 레슨 2 >>
설명할 수 없는 어느 화요일 오후
사내 리서치 에이전트가 프로덕션에 올라간 지 2주째입니다. 화요일 오후, 운영팀이 사용자 제보를 하나 전달해 옵니다:
작년 요금제를 찾아 달라고 했더니 아무것도 못 찾겠다고 하더라고요. 그런데 그 문서, 지식 베이스에 멀쩡히 있어요. 제가 직접 열어 보는 데 2초 걸렸습니다.
세션을 엽니다. 화면에 있는 것은 두 가지뿐입니다. 사용자의 질문, 그리고 에이전트의 최종 응답 '관련 자료를 찾지 못했습니다'. 그 사이에 무슨 일이 있었을까요. 손에 쥔 것이 없습니다.
그래서 추측이 시작됩니다.
검색 질의를 잘못 만들었을까요. 키워드를 뽑아내지 않고 사용자의 자연어 질문을 통째로 검색에 밀어 넣었다든가. 아니면 결과는 찾았는데 출처를 잘못 골랐을까요. 여덟 건의 히트 중 가장 관련이 옅은 두 건만 읽고 '여기엔 아무것도 없다'고 결론지었다든가. 그것도 아니면 검색 도구가 에러를 던졌고, 에이전트가 그 실패를 '이 방향에는 정보가 없다'로 해석하고 그냥 넘어갔을까요.
세 가지 추측은 모두 사용자 쪽에서 보면 똑같습니다. 뻔히 거기 있는 정보를 에이전트가 찾지 못한다.
이건 여러분만의 문제가 아닙니다. Anthropic 팀이 멀티 에이전트 리서치 시스템을 회고했을 때, 그들은 똑같은 문제를 그대로 적어 두었습니다. 사용자들은 에이전트가 "not finding obvious information"(뻔히 있는 정보를 찾지 못한다)이라고 제보해 오는데, 왜인지는 보이지 않았습니다. 에이전트가 나쁜 검색 질의를 쓴 것일까요. 출처를 잘못 고른 것일까요. 도구 실패를 밟은 것일까요1. 똑같은 세 가지 질문, 그리고 답은 없습니다.
평가 트랙은 '망가졌는가'에만 답한다
첫 반사는 아마 앞 코스(이 시리즈의 10번째 코스)에서 만든 시스템을 여는 것이겠지요. 종료 상태 채점, 검증기, 평가 세트. 그것은 올바른 첫걸음입니다. 이 실제 사용자 질문을 평가 세트에 추가하고, '답변은 요금제 문서를 인용해야 한다'는 기준을 쓰고, 돌립니다.
결과: 빨간 줄 하나. fail.
그 빨간 줄에는 쓸모가 있습니다. 주관적인 불만을 재현 가능하고 회귀 테스트에 걸 수 있는 판정으로 바꿔 줍니다. 하지만 지금 정말로 답이 필요한 질문에는 답하지 못합니다. 왜인가, 입니다. 채점기가 보는 것은 종료 상태입니다. 종료 상태는 '문서를 인용하지 않았다'입니다. 그 '인용하지 않았다'가 나쁜 질의에서 왔는지, 출처 선택에서 왔는지, 삼켜진 도구 에러에서 왔는지 채점기는 관심이 없고 관심을 가질 방법도 없습니다. 결승선에 서서 점수판을 들고 있을 뿐입니다.
이 코스가 채우는 것은 그 중간 구간입니다. 이 코스가 직접 만든 틀을 씁니다:
검증은 망가졌는지를 알려 줍니다. 관측 가능성은 왜인지를 알려 줍니다.
이것은 어떤 공식 문서에도 실려 있는 문장이 아닙니다. 이어지는 다섯 레슨을 하나로 묶기 위해 이 코스가 세운 틀이고, 뒷받침은 두 가지 실제 경험입니다. 하나는 그 멀티 에이전트 시스템 회고에서 옵니다. 프로덕션 전체에 완전한 트레이싱을 추가한 덕분에 에이전트가 왜 실패했는지 진단하고 문제를 체계적으로 고칠 수 있었습니다1. 다른 하나는 도구 엔지니어링 쪽에서 옵니다. 평가용 에이전트가 남긴 원본 트랜스크립트를 분석하면 에이전트가 특정 도구를 왜 부르는지, 왜 부르지 않는지를 파고들 수 있습니다2. 두 문장은 같은 곳을 가리킵니다. 프로세스가 흔적을 남겨야만 비로소 '왜'를 물을 수 있습니다.
왜 여기서는 '재현해서 브레이크포인트'가 통하지 않는가
먼저 정의부터. 결정론적(deterministic) 시스템이란 같은 입력을 주면 매번 같은 출력을 내놓는 시스템을 말합니다. 비결정적(non-deterministic) 시스템은 그 반대이고, 에이전트가 이쪽입니다. 컴퓨팅에서 결정론적 시스템은 동일한 입력에 대해 매번 같은 출력을 만들어 내지만, 에이전트 같은 비결정적 시스템은 시작 조건이 같아도 서로 다른 응답을 생성할 수 있습니다2.
이것이 전통적인 디버깅의 발판을 통째로 빼 버립니다. 에이전트는 동적으로 판단을 내리고, 프롬프트가 동일해도 실행마다 비결정적입니다. 이 때문에 디버깅이 더 어려워집니다1. 전통적인 평가는 종종 AI가 매번 같은 단계를 밟는다고 전제합니다. 입력 X를 주면 시스템은 경로 Y를 따라 출력 Z를 내야 한다는 식입니다. 그러나 멀티 에이전트 시스템은 그렇게 동작하지 않습니다. 출발점이 동일해도 에이전트는 목표에 이르기까지 완전히 다른, 그러나 타당한 경로를 밟을 수 있습니다1.
실제로는 이렇게 보입니다:
어느 경로도 틀리지 않았고, 두 종료 상태 모두 통과할 것입니다. 그런데 실패가 실행 A의 2단계에서 일어났다면, 열 번을 다시 돌려 아홉 번이 실행 B로 나왔을 때 그 아홉 번은 아무 도움이 되지 않습니다.
브레이크포인트도 똑같이 쓸모가 없습니다. 브레이크포인트는 코드의 줄 위에 앉고, '두 번째 실행도 같은 컨텍스트로 이 줄을 지난다'가 전제 조건입니다. 그런데 에이전트가 어긋나는 지점은 대개 여러분의 코드 안이 아니라 모델의 판단 안입니다. 게다가 멈추고 싶어도 몇 번째 턴에서 멈춰야 할지 알 수 없습니다. 이번에는 3번째 턴에서 어긋나고, 다음에는 11번째 턴일 수도, 아예 어긋나지 않을 수도 있습니다.
샘플링 온도를 낮추거나 도구 응답을 기록해 두었다가 재생하는 방법이 떠오를지도 모릅니다. 이런 엔지니어링 기법은 흔들림을 어느 정도 줄여 주고, 이 코스도 그것을 말리지 않습니다. 하지만 그 기법이 바꾸는 것은 지금 실험실에서 돌리는 실행이지, 프로덕션에서 이미 실패해 버린 그 실행이 아닙니다. 그 실행은 이미 사라졌고, 남긴 것은 기록뿐입니다.
Anthropic 팀은 다른 길을 택했습니다. 그들은 그것을 "think like your agents"(에이전트처럼 생각하기)라고 부릅니다. 시스템에서 쓰는 것과 완전히 동일한 프롬프트와 도구로 시뮬레이션을 꾸리고, 에이전트가 일하는 모습을 한 단계씩 지켜보는 것입니다. 그러자 실패 모드가 곧바로 드러났습니다. 이미 충분한 결과를 얻었는데도 계속 진행하는 에이전트, 지나치게 장황한 검색 질의를 쓰는 에이전트, 잘못된 도구를 고르는 에이전트1.
핵심은 '같은 실행을 재현한다'가 아니라 '각 단계를 본다'입니다. 관측 가능성이 전통적인 디버깅과 갈라지는 지점이 여기입니다.
에이전트가 던지는 에러는 형태가 다르다
'기록이 필요하다'를 받아들였다 해도 한 겹 더 이해할 것이 남습니다. 에이전트 시스템에서 에러의 형태는 전통적인 버그와 같지 않습니다.
전통적인 소프트웨어에서 버그는 기능을 망가뜨리거나, 성능을 떨어뜨리거나, 장애를 일으킵니다. 에이전트 시스템에서는 작은 변경이 큰 동작 변화로 연쇄하고, 그 때문에 장시간 도는 프로세스 안에서 상태를 유지해야 하는 복잡한 에이전트의 코드를 쓰는 일이 현저히 어려워집니다1.
한 번의 실행 안에서 가장 전형적인 형태는 궤적 분기입니다. 에이전트는 상태를 가지고 있고 에러는 누적됩니다. 에이전트 시스템에서 에러가 갖는 누적적 성질은, 전통적인 소프트웨어라면 사소했을 문제가 에이전트를 통째로 탈선시킬 수 있다는 뜻입니다. 한 단계의 실패가 에이전트로 하여금 완전히 다른 궤적을 탐색하게 만들고, 예측할 수 없는 결과로 이어집니다1.
이 사슬의 첫 고리가 얼마나 작은지 보십시오. 시간 초과 한 번입니다. 전통적인 서비스라면 재시도 로그가 한 줄 늘어나는 정도일 수 있습니다. 여기서는 그것이 이어지는 열두 턴을 다시 쓰고, 종료 상태의 보고서는 에러도 내지 않고, 크래시도 나지 않고, 읽는 맛이 완벽하게 매끄럽습니다.
두 번째 형태는 턴을 넘어 쌓이는 누적입니다. 에이전트는 상태를 가지고 있고 에러는 누적됩니다. 에이전트는 오랜 시간 돌면서 수많은 도구 호출에 걸쳐 상태를 유지할 수 있습니다. 그래서 코드를 지속성 있게 실행하고 그 도중에 에러를 처리해야 합니다1. 에러가 났을 때 처음부터 다시 시작할 수 없는 이유도 여기 있습니다. 다시 시작하는 것은 비싸고 사용자에게는 짜증스럽습니다. 그래서 그들은 에러가 났던 지점의 에이전트 상태에서 재개할 수 있는 시스템을 만들었습니다1. 여러분에게 주는 함의는 직접적입니다. 실패 시점의 상태가 기록되지 않았다면 '어디서부터 재개할 것인가'라는 질문에는 답이 없습니다.
세 번째 형태는 멀티 에이전트 시스템에서만 나타납니다. 창발적 동작입니다. 멀티 에이전트 시스템에는 따로 프로그래밍하지 않아도 생겨나는 창발적 동작이 있습니다. 예컨대 리드 에이전트에 가한 작은 변경이 서브에이전트의 행동을 예측할 수 없게 바꿔 놓습니다. 성공하려면 개별 에이전트의 동작만이 아니라 상호작용 패턴을 이해해야 합니다1.
이 형태들이 한꺼번에 겹치면 어떻게 되는지, 그 팀의 초기 버전이 아주 직접적인 답을 내놓았습니다. 초기 에이전트들은 단순한 질의에 서브에이전트 50개를 띄우고, 존재하지도 않는 출처를 찾아 웹을 끝없이 뒤지고, 지나친 업데이트로 서로의 주의를 흩뜨리는 오류를 범했습니다1.
이 세 가지 에러 유형에는 공통점이 있습니다. 밖에서 보면 종료 상태는 그저 '답변 품질이 그저 그렇다'로만 보일 수 있다는 것입니다. 어느 쪽인지 가려낼 수 없습니다.
추상화 계층이 증거를 가린다
한 겹의 골칫거리가 더 있는데, 이것은 모델에서 오지 않습니다. 여러분의 도구 체인에서 옵니다.
에이전트 프레임워크는 LLM 호출, 도구 정의와 파싱, 호출 연결 같은 표준적인 저수준 작업을 단순화해서 시작을 쉽게 만들어 줍니다. 그러나 흔히 추가적인 추상화 계층을 만들어 내고, 그 계층이 밑에 깔린 프롬프트와 응답을 가려서 디버깅을 더 어렵게 만듭니다3. 그래서 개발자에게 주는 조언은 이렇습니다. LLM API를 직접 쓰는 것부터 시작하라. 많은 패턴은 몇 줄의 코드로 구현할 수 있습니다. 프레임워크를 쓴다면 그 밑에 깔린 코드를 이해하십시오. 내부에 무엇이 있는지에 대한 잘못된 가정은 고객 측 에러의 흔한 발생원입니다3. (해당 글에는 도구 생태계에 관한 서술이 오래되었다는 편집 주석이 붙어 있으므로, 여기서는 원칙을 인용하는 것이지 현시점의 선정 가이드로 다루는 것이 아닙니다.)
여러분에게 이것은 오히려 좋은 소식입니다. 이 시리즈의 7번째 코스에서 하네스 루프를 손으로 썼으니까요. 그 추상화 계층이 여러분에게는 없습니다:
증거는 매번 어김없이 이 루프를 통과합니다. 문제는 통과한 다음에는 사라진다는 것입니다. 가로막는 것은 없지만 남는 것도 없습니다. 사후에 증거를 요구하는 관점에서 보면 그 둘은 똑같이 고통스럽습니다.
그래서 이 코스의 진단은 이렇습니다. 에이전트 디버깅의 어려움 절반은 비결정성이라는 객관적 사실에서 오고, 나머지 절반은 '기록할 수 있었던 것을 기록하지 않았다'에서 옵니다. 앞의 절반은 바꿀 수 없습니다. 뒤의 절반은 여러분 손에 달려 있습니다.
앞으로 갈 길: 이어지는 다섯 레슨이 주는 것
그 멀티 에이전트 시스템 팀은 이것을 프롬프트 엔지니어링, 도구 설계와 같은 층위에 놓았습니다. 이걸 제대로 하려면 세심한 프롬프팅과 도구 설계, 탄탄한 휴리스틱, 관측 가능성, 그리고 촘촘한 피드백 루프가 필요합니다1. 회고에는 더 직접적인 문장도 있습니다. 그들은 관측 가능성과 테스트 케이스를 갖춘 빠른 이터레이션 루프에 집중했습니다1.
'테스트 케이스'라는 절반에 주목하십시오. 관측 가능성은 평가 트랙을 대체하러 온 것이 아닙니다. 둘은 하나의 멜대에 매달린 양 끝입니다. 평가는 이번 실행이 망가졌는지, 그리고 변경이 상황을 나아지게 했는지를 알려 줍니다. 관측은 이번 실행이 왜 망가졌는지, 그리고 어느 부분을 바꿔야 하는지를 알려 줍니다.
이어지는 다섯 레슨은 이 순서로 갑니다:
- 레슨 2는 토대를 놓습니다. 원본 기록이 1차 증거입니다. 에이전트가 자신에 대해 하는 말은 증거가 되지 않고, 빠뜨린 것이 담은 것보다 중요한 경우가 많습니다.
- 레슨 3은 각 단계를 데이터로 바꿉니다. 구조화된 로그와 메트릭입니다. 10번째 코스가 채점에 쓰던 그 메트릭을, 이 코스는 진단에 씁니다.
- 레슨 4는 흩어진 기록을 하나의 트리로 꿰맵니다. 하나의 프롬프트가 촉발한 모든 모델 요청과 도구 실행을 한 덩어리로 읽고, 서브에이전트 호출은 부모 안에 중첩시킵니다.
- 레슨 5는 루프의 생애 주기 체크포인트에 탐침을 답니다. 그리고 비결정성 아래에서 위치를 짚어내는 작업 흐름, 즉 기록 더미에서 첫 분기점을 찾아내는 과정을 훑습니다.
- 레슨 6은 실습입니다. 7번째 코스에서 쓴 하네스에 완전한 관측 계층을 얹고, '설명할 수 없는' 증상에서 실제로 어긋난 그 단계까지 따라갑니다.
이번 레슨은 이름만 불러 두고 펼치지는 않습니다. 기록을 읽는 법은 레슨 2의 몫이고, 메트릭을 설계하는 법은 레슨 3, 트레이스가 어떤 구조인지는 레슨 4입니다.
풀 스택이 필요하지 않을 때
모든 에이전트에 이 전부가 필요하지는 않습니다. 한 번 돌리고 출력을 훑어보고 지워 버릴 일회성 스크립트에 로그, 메트릭, 트레이스를 달아 주는 것은 순전한 낭비입니다.
판단 기준은 단순합니다. 관측 가능성에 들이는 투자는 '무언가 망가진 뒤 왜인지를 설명하는 데 얼마나 걸릴 것인가'에 비례해야 합니다. 직접 돌리고 직접 지켜보며, 실패해도 다시 돌리는 데 비용이 들지 않는다면 기록하지 마십시오. 다른 사람이 쓴다면, 오래 돈다면, 실패했을 때 대화 기록을 반나절씩 뒤지게 될 것 같다면 첫날부터 기록하십시오.
규모와 무관한 선이 하나 있습니다. 에이전트가 행동을 취할 수 있다면, 즉 파일을 쓰고 요청을 보내고 돈을 쓸 수 있다면 테스트를 건너뛰지 마십시오. 에이전트의 자율적인 성격은 더 높은 비용과 에러가 누적될 가능성을 뜻합니다. 적절한 가드레일과 함께 샌드박스 환경에서 광범위하게 테스트할 것을 권장합니다3.
제대로 했는지 아는 방법은, 다른 모든 LLM 기능과 마찬가지로 성능을 측정하고 구현을 반복 개선하는 것이 성공의 열쇠라는 것입니다3. 그리고 측정의 전제 조건은 측정할 대상이 있는 것이고, 그것이 다시 이 코스가 풀고 있는 문제로 돌아옵니다.
💻 연습
정리
- 사용자에게 보이는 하나의 증상 아래에는 밖에서 보면 전혀 구별되지 않는 여러 원인이 숨어 있는 경우가 많다. 사용자가 에이전트는 "not finding obvious information"(뻔히 있는 정보를 찾지 못한다)이라고 제보해 올 때, 그것이 나쁜 검색 질의인지, 출처 선택 실패인지, 도구 실패를 밟은 것인지 가려낼 수 없다1
- 평가 트랙은 '망가졌는가'에 답한다. 이 코스는 분업을 또렷하게 하려고 직접 만든 틀을 쓴다. 검증은 망가졌는지를 알려 주고, 관측 가능성은 왜인지를 알려 준다. 뒷받침은 두 가지 실제 경험이다. 프로덕션 전체에 완전한 트레이싱을 추가하자 실패를 진단하고 체계적으로 고칠 수 있었고1, 원본 트랜스크립트를 읽으면 에이전트가 특정 도구를 왜 부르는지 왜 부르지 않는지 파고들 수 있다2
- '재현해서 브레이크포인트'가 통하지 않는 것은 에이전트가 동적으로 판단을 내리고 프롬프트가 동일해도 실행마다 비결정적이기 때문이다1. 결정론적 시스템은 같은 입력에 같은 출력을 내지만, 비결정적 시스템인 에이전트는 그것을 보장하지 않는다2. '입력 X는 경로 Y를 따라 출력 Z를 낸다'는 전통적 평가의 전제도 성립하지 않는다. 출발점이 동일해도 완전히 다른 타당한 경로가 나올 수 있다1
- 에이전트 에러의 형태는 전통적인 버그와 다르다. 전통적인 소프트웨어에서 버그는 기능을 망가뜨리지만, 에이전트 시스템에서는 작은 변경이 큰 동작 변화로 연쇄해서 상태를 유지해야 하는 복잡한 에이전트의 코드를 쓰는 일을 현저히 어렵게 만든다1. 한 단계의 실패가 에이전트로 하여금 완전히 다른 궤적을 탐색하게 하고 예측할 수 없는 결과로 이어진다1. 에이전트는 상태를 가지고 있고 에러는 누적되므로, 코드를 지속성 있게 실행하고 도중에 에러를 처리해야 한다1. 멀티 에이전트 시스템에는 창발적 동작도 있어서, 리드 에이전트의 작은 변경이 서브에이전트의 행동을 예측할 수 없게 바꾼다. 이해해야 하는 것은 개별이 아니라 상호작용 패턴이다1. 초기 버전은 단순한 질의에 서브에이전트 50개를 띄우고, 존재하지 않는 출처를 찾아 웹을 끝없이 뒤지고, 지나친 업데이트로 서로의 주의를 흩뜨린 적이 있다1
- 추상화 계층은 증거를 가린다. 프레임워크는 흔히 추가적인 추상화 계층을 만들어 내고, 그것이 밑에 깔린 프롬프트와 응답을 가려서 디버깅을 더 어렵게 만든다3. 조언은 LLM API를 직접 쓰는 것부터 시작하고, 프레임워크를 쓴다면 그 밑의 코드를 이해하라는 것이다3. 손으로 쓴 하네스에는 가로막는 것이 없지만 남는 것도 없다
- 앞으로 갈 길은 관측 가능성과 촘촘한 피드백 루프를 1급 요구사항으로 다루고1, 관측 가능성과 테스트 케이스를 갖춘 빠른 이터레이션 루프로 짜는 것이다1. 이어지는 다섯 레슨의 순서는 원본 기록, 로그와 메트릭, 트레이스, 훅과 위치 특정 절차, 직접 얹어 보기다
- 범위에 대한 판단: 일회성 스크립트에는 풀 스택이 필요 없다. 다만 에이전트가 행동을 취할 수 있다면 적절한 가드레일과 함께 샌드박스 환경에서 광범위하게 테스트하라3. 제대로 했는지 아는 것은 성능을 측정하고 구현을 반복 개선하는 데 달려 있다3
레슨 2 >>