Agent Mentor Learn
관측 가능성과 디버깅: 에이전트가 밟는 모든 단계를 들여다보기 · 3 / 6강

레슨 3: 구조화된 로그와 메트릭: 모든 단계를 데이터로 바꾸기

학습 목표:

  • 프로덕션 관측 가능성이 반드시 답해야 하는 네 가지 질문을 설명하고, 그것이 평가 메트릭과 같은 숫자를 다르게 쓰는 것임을 알아보기
  • 하네스를 위한 구조화된 로그 설계하기: 모델 요청에 기록 하나, 도구 호출에 기록 하나, 소요 시간과 토큰과 도구 이름과 에러를 아우르는 필드
  • 진단적 읽기로 메트릭 패턴을 구체적인 수정에 대응시키고, '에러 0건' 같은 신호가 기록 방식 때문에 뒤틀리는 지점 짚어내기

전제: 레슨 1과 2를 마치고, 돌아가는 하네스 루프(이 시리즈의 7번째 코스)를 갖고 있어야 합니다 | 이전: << 레슨 2 | 다음: 레슨 4 >>

하나는 읽을 수 있고, 이백 개는 읽을 수 없다

레슨 2의 끝에서 값진 일을 했습니다. 원본 트랜스크립트 하나를 처음부터 끝까지 읽고, 에이전트가 한 번도 언급하지 않은 문제 세 개를 잡아냈습니다. 방법은 통하고 증거는 단단합니다. 문제는 그것이 한 번의 실행이었다는 점입니다.

이제 같은 에이전트를 프로덕션에 올려 봅시다. 하루 이백 번의 실행, 각각 열몇 바퀴의 루프, 합치면 이삼천 번의 도구 호출 왕복입니다. 월요일 아침 누군가 '금요일 오후 배치가 유난히 느렸던 것 같은데요'라고 말합니다. 어떻게 답하시겠습니까. 트랜스크립트 이백 개를 읽는 건 명백히 현실적이지 않습니다. 설령 읽는다 해도 어디가 느렸는지는 여전히 답할 수 없습니다. 그 판단에는 표본 하나를 읽는 것이 아니라 실행들 사이의 분포를 보는 일이 필요합니다. 사람의 눈은 '이 특정 실행에서 왜 이렇게 했는가'에는 답할 수 있지만, '이 배치가 지난 배치와 어떻게 달랐는가'에는 답할 수 없습니다.

그래서 이번 레슨의 일은 한 문장에 들어갑니다. 트랜스크립트를 읽어야 답할 수 있는 질문을 질의 하나로 답할 수 있는 질문으로 바꾼다. 앞엣것은 '이번에 왜 같은 단어를 반복해서 검색했는가'이고, 뒤엣것은 '지난 7일 동안 어느 도구가 가장 많이 불렸고, 에러는 전부 같은 파라미터에서 나는가'입니다. 레슨 2의 규칙은 그대로 유효합니다. 원본 트랜스크립트가 1차 증거이고, 에이전트의 자기 보고는 증거가 되지 않습니다. 이번 레슨은 같은 증거를 다른 형식으로 저장할 뿐입니다. 사람도 읽을 수 있으면서 동시에 걸러 내고, 집계하고, 분포를 분석할 수 있도록.

프로덕션 환경이 반드시 답해야 하는 네 가지 질문

공식 문서는 프로덕션 관측 가능성에서 또렷하게 보여야 할 네 가지를 나열합니다. 어떤 도구가 불렸는지, 각 모델 요청이 얼마나 걸렸는지, 토큰이 얼마나 들었는지, 그리고 실패가 어디서 일어났는지1. 설계 결정을 내릴 때마다 이 네 질문으로 되돌아오게 됩니다. 이 필드는 넷 중 하나에 답하는 데 도움이 되는가. 아니라면 잡음입니다.

낯익어 보일지도 모릅니다. 이 시리즈의 10번째 코스는 평가를 다루면서 같은 숫자 묶음을 썼습니다. 종료 상태 정확도 너머로, 개별 도구 호출과 작업 전체의 실행 시간, 총 도구 호출 수, 총 토큰 소비, 도구 에러를 수집할 것을 권했습니다2. 같은 메트릭, 두 번의 등장, 다른 쓰임:

이 숫자10번째 코스: 평가 트랙에서이번 레슨: 일상 모니터링에서
개별 도구 호출과 작업 전체의 실행 시간변경 때문에 작업이 느려졌는가오늘 어느 시간대가 느려졌고, 모델 쪽인가 도구 쪽인가
총 도구 호출 수어느 프롬프트 버전이 덜 에두르는가프로덕션에 새로 나타난 반복 호출 패턴 포착
총 토큰 소비전체 평가를 돌리는 비용 계산일별 지출 관찰, 토큰을 먹는 세션 찾기
도구 에러변경이 새 실패를 들여왔는가어느 도구가 불안정한지, 어느 파라미터에서 실패하는지 확인

차이는 숫자에 있지 않고 무엇과 견주느냐에 있습니다. 평가에서는 고정된 테스트 세트를 두고 '변경 전 대 변경 후'를 견주므로 숫자가 재현 가능해야 합니다. 모니터링에서는 '오늘 대 지난 7일'이나 '이 세션 대 다른 세션들'을 견주므로 기준점이 실행들 자신의 이력이고, 그래서 숫자가 연속적이고 타임스탬프가 붙어 있고 차원으로 잘라낼 수 있어야 합니다. 이번 레슨은 뒤쪽을 다룹니다.

구조화된 로그: 단계마다 기록 하나

이 절은 엔지니어링 관행을 서술합니다. 로그 필드의 이름을 어떻게 짓는지, 어떤 형식으로 디스크에 쓰는지에 대한 권위 있는 지침은 없습니다. 아래에 오는 것은 쓸 만한 기본 출발점이지 공식 사양이 아닙니다. 필드 이름은 공식 자료에 실제로 등장하는 용어(session id, prompt id, tool name, tool_input, tool_response, duration_ms, 토큰 수, error)를 빌려 왔으므로, 나중에 공식 텔레메트리와 통합할 때 어휘를 다시 대응시킬 필요가 없습니다.

기록 단위: 모델 요청에 하나, 도구 호출에 하나

에이전트 루프에는 자연스럽게 두 종류의 '단계'가 있습니다. 모델 요청 하나, 도구 실행 하나입니다. 둘은 속성이 아주 다릅니다. 모델 요청에는 토큰 수가 있지만 도구 이름이 없고, 도구 실행은 그 반대입니다. 그러나 같은 컨텍스트 필드 묶음(어느 세션인지, 어느 프롬프트인지, 얼마나 걸렸는지)을 공유합니다.

그래서 이렇게 합니다. 모델 요청마다 기록 하나, 도구 호출마다 기록 하나를 쓰고, type 필드로 구분합니다. 루프 한 바퀴를 기록 하나로 압축하지 마십시오. 그러면 모델 시간과 도구 시간의 분해를 영영 계산할 수 없습니다. 작업이 끝날 때 요약 기록 하나만 쓰는 것도 안 됩니다. 작업이 중간에 멈추면 어느 단계에서 멈췄는지조차 알 수 없습니다.

형식: JSON Lines, 한 줄에 객체 하나

JSON Lines(흔히 JSONL로 씁니다)는 이름 그대로입니다. 파일 하나, 각 줄이 완결된 JSON 객체 하나, 줄 사이에 쉼표가 없고 바깥을 감싸는 배열도 없습니다.

이것을 고르는 이유는 하나같이 평범하지만 하나같이 타당합니다. 덧붙여 쓰기만으로 동작하므로 파일 끝에 ] 를 붙이러 되돌아갈 필요가 없고, 그래서 프로세스가 강제 종료되어도 문법이 깨진 파일이 남지 않습니다(마지막 줄은 반쯤 쓰이다 말 수 있지만 그 앞의 모든 줄은 여전히 파싱됩니다. 이것이 '깨진 줄은 알리되 처리를 멈추지 않는다'는 연습 요구사항의 실제 유래이기도 합니다). 파일이 수백 메가바이트로 자라도 한 줄씩 스트리밍으로 처리할 수 있습니다. 각 줄이 자기완결적이라서 grep 으로 거를 수 있고, jq 로 다룰 수 있고, 사람 눈으로도 읽힙니다.

많은 하네스가 이미 갖고 있는 산문형 로그와 견줘 보십시오. [09:12:05] search_docs returned 3 results, took 412ms. 읽기는 좋지만 사람만 읽을 수 있습니다. '지난 7일 search_docs 의 평균 소요 시간'에 답하려면 그 412ms 를 뽑아내는 정규식을 써야 하고, 누군가 'took' 을 'elapsed' 로 바꾸면 정규식은 조용히 0을 돌려주기 시작합니다. 산문형 로그는 구조를 자연어에 인코딩하고, 자연어는 사람이 디코딩하라고 있는 것입니다. 구조화된 로그는 그것을 뒤집습니다. 구조는 필드에 앉고, 기계는 그것을 모호함 없이 읽습니다. 거를 수 있고, 집계할 수 있고, 분포를 분석할 수 있다. 이 세 가지 능력이야말로 실행 하나에서 이백 개로 넘어갈 때 실제로 필요한 것입니다.

필드 어휘

모든 기록에 붙는 컨텍스트: ts(밀리초와 시간대가 붙은 ISO 8601 타임스탬프), type(model_call 또는 tool_call), session_id(한 세션의 식별자, 여러 턴에 걸쳐 그대로 유지), prompt_id(사용자 프롬프트 하나의 식별자, 그것이 촉발한 모든 모델 요청과 도구 호출이 이 값을 공유), duration_ms. 모델 요청에는 model, stop_reason, input_tokens / output_tokens 가 더해집니다. 도구 호출에는 tool, tool_use_id(응답과 짝짓기용), error(실패할 때만 존재)가 더해집니다.

prompt_id 는 여기서 가장 눈에 띄지 않는 필드이지만 나중에 가장 쓸모 있어집니다. 지금은 그저 모든 기록에 써 넣을 뿐이고, 레슨 4에서 이것으로 흩어진 기록을 '같은 프롬프트에서 나온 이벤트'로 동그라미 치고, 그다음 부모-자식 관계로 트리에 꿰맵니다. 도구의 입력과 반환값 자체(tool_input / tool_response)는 기본적으로 전문을 쓰지 마십시오. 길이나 바이트 수만 기록합니다. 그 근거는 뒤에서 두 번째 절에 나옵니다.

하네스에 붙이기

아래 조각은 이 시리즈 7번째 코스의 stop_reason 구동 루프 위에 얹습니다. 먼저 로거:

logRecord() 는 한 가지 일만 합니다. 공통 컨텍스트와 호출자의 필드를 합쳐 JSON 한 줄로 만들고 파일에 덧붙입니다. 판단하지 않고, 서식을 꾸미지 않고, '똑똑한' 짓을 아무것도 하지 않습니다. 로거는 멍청할수록 좋습니다. 로거가 망가지면 확인할 로그가 없기 때문입니다. 그다음 루프 안의 계측 지점 두 곳:

짚어 둘 세부가 둘 있습니다.

타이머를 어디서 시작하고 멈추느냐가 이 숫자의 의미를 결정합니다. t1runTool 앞에서 시작해 반환된 뒤에 멈추므로, duration_ms 는 도구 자체의 재시도, 백오프 대기, 네트워크 왕복을 포함하지만 호출 전 파라미터 검증은 포함하지 않습니다. 이 경계는 여러분이 직접 정의하고, 정의했으면 적어 두십시오. 반년 뒤 30초짜리 duration_ms 를 노려보게 됐을 때 그것이 재시도를 포함하는지 알아야 합니다.

에러는 로그와 모델 컨텍스트 양쪽으로 갑니다. catch 블록은 에러 메시지를 tool_result 로 되돌려 넣어서 에이전트가 다음 턴에 그것을 보게 합니다. 공식 지침이 여기에 딱 들어맞습니다. 도구 호출이 에러를 낼 때, 응답은 불투명한 에러 코드나 트레이스백이 아니라 구체적이고 실행 가능한 개선점을 명확하게 전달하도록 프롬프트 엔지니어링되어야 합니다2. 로그에는 ETIMEDOUT 을 써도 되지만, 모델에게 돌아가는 것은 '요청이 시간 초과되었다(30초). 이 엔드포인트는 범위가 넓은 질의에서 시간 초과가 잦다. date_range 를 7일 이하로 좁혀서 다시 시도하라' 여야 합니다.

메트릭을 진단적으로 읽기

메트릭의 값어치는 '오늘 도구 호출 1,283건'이라는 숫자에 있지 않습니다. 특정 패턴이 특정 수정을 가리킨다는 데 있습니다. 공식적으로 제시된 상관관계는 모두 먼저 확인해 볼 만한 실마리입니다:

중복 호출이 많다 → 페이지네이션이나 토큰 상한 파라미터를 조정해야 할 수 있다. 중복된 도구 호출이 많다는 것은 페이지네이션이나 토큰 상한 파라미터의 크기를 다시 맞출 필요가 있다는 신호일 수 있습니다2. 모델은 문서에서 한 단락을 찾아야 하는데 여러분의 search_docs 는 한 페이지에 5건만 돌려주니, 28페이지를 넘겨야 합니다. 그 28번의 호출은 하나같이 합법이고, 하나같이 성공하고, 메트릭에는 '에러'가 없지만, 전부 낭비입니다. 페이지당 결과를 25로 올리면 이 패턴은 사라집니다.

잘못된 파라미터 에러가 많다 → 도구 설명에 명료함이나 예시가 필요할 것이다. 잘못된 파라미터로 인한 도구 에러가 많다는 것은 도구에 더 명확한 설명이나 더 나은 예시가 필요하다는 신호일 수 있습니다2. 이것은 에러가 같은 파라미터에 몰릴 때 특히 강력합니다. invalid parameter: date_range 라고 말하는 에러가 일곱 건이라면, 이 파라미터가 어떤 형식을 기대하는지 설명이 밝히고 있는지부터 확인해야 합니다. 조사의 방향은 모델이 아니라 도구 설명입니다.

도구 호출을 추적하면 다른 것도 드러납니다. 도구 호출을 추적하면 에이전트가 밟는 흔한 워크플로가 드러나고 도구를 통합할 기회가 보일 수 있습니다2. 예컨대 read_file 호출의 90%가 뒤이어 parse_config 로 이어진다면, 한 번에 끝나는 read_config 를 제공해야 할지도 모릅니다. 이런 발견은 어떤 단일 실행에서도 나오지 않고 오직 집계에서만 나옵니다. 유용한 읽기가 하나 더 있습니다. 도구 호출 메트릭을 분석해서 가장 자주 쓰인 도구, 도구 성공률, 평균 도구 실행 시간, 도구 유형별 에러 패턴을 식별하십시오3.

어떤 문제는 본질적으로 규모의 문제여서, 집계를 보지 않고는 '어디가 많다'를 말할 수 없습니다. Anthropic이 기록해 둔 초기 문제도 이런 종류였습니다. 존재하지 않는 출처를 찾아 웹을 끝없이 뒤지는 것4. 단일 검색 하나만 봐서는 에러 표식이 뜨지 않습니다. 수십 번의 호출을 늘어놓아야 '제자리에서 돌고 있다'가 보입니다.

일반적인 경험 하나(권위 있는 출처는 없습니다). 평균 소요 시간은 거의 항상 거짓말을 합니다. 80ms짜리 99건에 30초짜리 1건을 더하면 평균 379ms가 되고, 조금 느리지만 괜찮아 보입니다. 실상은 빠른 호출 99건에 완전히 멈춘 것 하나입니다. 소요 시간을 읽을 때는 최소한 중앙값과 상위 백분위를 보거나, 아니면 가장 느린 몇 건의 기록으로 곧장 가십시오.

숫자를 읽을 때의 함정: 여러분의 신호는 실제로 무엇을 세고 있는가

메트릭이 거짓말을 하는 가장 쉬운 방식은 잘못 세는 것이 아니라, 세고 있는 것이 여러분이 생각하는 그것이 아닐 때입니다.

실제 제품 설계를 봅시다. Claude Code는 실패한 API 요청을 내부적으로 재시도하고, 포기한 뒤에야 api_error 이벤트를 하나 내보냅니다. 이 이벤트는 그 요청에 대한 종단 신호이고, 중간의 재시도는 별도 이벤트로 기록되지 않습니다3. 이 설계는 말이 됩니다. 재시도마다 에러를 남긴다면 에러 그래프는 자동 복구가 처리한 일시적 딸꾹질로 넘쳐나서, 실제로 실패한 요청이 몇 건인지가 가려질 것입니다. 대가는 그 의미론을 기억해야 한다는 것입니다. '오늘 api_error 3건'은 '요청 3건이 최종적으로 실패했다'는 뜻이지 '네트워크 딸꾹질 3번'이 아니고, 그 아래에 성공한 재시도가 몇 건 숨어 있는지에 대해서는 아무 말도 하지 않습니다.

같은 문서 페이지가 아주 실용적인 읽기를 하나 제공합니다. 세션이 에러에서 회복했는지 아니면 완전히 멈췄는지를 가르려면, 이벤트를 session id로 묶고 에러 이후에 뒤따르는 API 요청 이벤트가 존재하는지 확인하십시오3. 후속이 있으면 계속 간 것이고, 없으면 거기서 멈춘 것입니다. 이 판정은 묶기 한 번과 '에러 뒤에 기록이 있는지 훑기' 한 번이면 되므로 품 대비 값어치가 대단히 높습니다. 레벨 2 연습에서 바로 이것을 쓰게 됩니다. (덧붙여 쓰기로 기록된 JSONL은 자연히 시간순이므로 단일 파일 안에서는 명시적인 정렬이 필요 없습니다. 로그가 여러 프로세스에서 올 때는 ts 로 먼저 정렬하십시오.)

이 함정에서 일반적인 관행 하나를 뽑아낼 수 있습니다. 메트릭마다 '이것이 무엇을 센다'를 한 문장으로 써 두십시오. 코드 주석이나 필드 문서에 쓰면 됩니다. '도구 에러 수 = 재시도가 모두 실패한 뒤 1건 계상'과 '= 예외가 던져질 때마다 1건 계상'은 완전히 다른 메트릭이지만 이름은 똑같을 수 있고, 반년 뒤 대시보드를 읽는 사람은 숫자만으로는 가려낼 수 없습니다.

비용과 토큰: 가장 볼 만한 숫자 하나

숫자 하나만 볼 수 있다면, 토큰을 보십시오.

먼저 규모. Anthropic의 데이터에서 에이전트는 보통 채팅 상호작용보다 약 4배 많은 토큰을 쓰고, 멀티 에이전트 시스템은 채팅보다 약 15배 많은 토큰을 씁니다4. 이것은 그들이 자기 시스템에서 관찰한 것이지 보편 상수가 아니지만, 기대치를 잡아 줍니다. 채팅 기능을 에이전트로 바꿀 때 청구서는 '조금' 오르지 않습니다. 그들에게는 또 하나의 통계적 관찰이 있습니다. 토큰 사용량 하나가 분산의 80%를 설명하고, 도구 호출 수와 모델 선택이 나머지 두 설명 요인입니다4. 이것은 평가 성능을 분석한 문단에서 나온 것으로, '실행들 사이의 차이를 어떤 양이 가장 잘 설명하는가'라는 뜻이고, 토큰이 1위입니다. 둘을 함께 읽으십시오. 토큰은 청구서에서 가장 큰 부분이면서 실행 간 분산의 최상위 설명 요인이므로, 후보 메트릭 중 가장 먼저 볼 만한 것입니다.

실무적인 주의가 둘 있습니다. 비용 숫자는 근사치입니다. 공식 문서는 비용 메트릭이 근사치이며 공식 청구 데이터는 API 제공자를 참조하라고 말합니다3. 그러니 쓰임은 '이상을 포착하고 추세를 견주는' 것이지 '재무와 대사하는' 것이 아닙니다. 귀속에는 차원별 쪼개기가 필요합니다. 사용량 메트릭은 팀이나 개인 단위의 추세를 추적하고, 사용량이 많은 세션을 식별하고, 나아가 스킬 이름, 플러그인 이름, 서브에이전트 유형 같은 구체적인 것에 지출을 귀속시키는 데 쓸 수 있습니다3. 직접 만든 하네스에 주는 함의는 직접적입니다. 그 차원들을 처음부터 로그 기록에 써 넣고, 나중에 조인할 생각을 하지 마십시오. 사후에 차원을 붙이는 것은 사실상 다시 돌리는 일입니다. 그리고 토큰 수는 모델 응답의 usage 필드에서 그대로 복사하십시오. 글자 수를 4로 나누는 식으로 추정하지 마십시오. 한영 혼용, 코드 비중이 큰 경우, 이미지가 포함된 경우에 눈에 띄게 어긋납니다.

절제: 임계치를 지어내지 말고, 전문을 기록하지 마라

메트릭이 생기면 다음 충동은 자연스럽게 알림을 거는 것입니다. 에러율 5% 초과하면 알림, 상위 백분위 소요 시간 10초 초과하면 알림.

멈추십시오. 이번 레슨은 임계치 숫자를 하나도 주지 않습니다. 권위 있는 자료에 그런 숫자가 없기 때문입니다. 공식 문서는 알림이 누군가 해야 할 일이라고 언급하지만, 구체적인 값을 준 적은 한 번도 없습니다. 에러 예산도, SLO 목표도, 알림 임계치도, 숫자는 하나도 없습니다. 여기에 '5% 권장'이라고 쓴다면 그건 제가 지어낸 것이고, 여러분은 그것을 쓰게 될 것입니다. 임계치는 오직 여러분 자신의 기준선에서만 자라날 수 있습니다. 먼저 2주 치 데이터를 기록하고, 정상적인 변동의 범위를 보고, 그다음 무엇이 비정상인지를 정의하십시오. 순서를 뒤집으면 하루에 세 번 오탐을 내고 2주 뒤 모두에게 음소거당하는 규칙을 얻게 됩니다.

책임의 분담도 공식 제품에서 베낄 만합니다. Claude Code는 원본 이벤트 스트림만 내보내고, 이상 탐지, 기준선 수립, 세션 간 상관 분석, 알림은 여러분의 SIEM이나 관측 백엔드의 책임입니다3. 직접 만든 하네스에 대해 이것이 뜻하는 바는 이렇습니다. 관측되는 쪽 시스템은 스스로 판단하지 않습니다. '도구 에러가 연속 3회면 메일 발송' 같은 로직을 하네스 안에 쓰지 마십시오. 그 로직은 에이전트와 함께 배포되고, 에이전트와 함께 재시작되고, 에이전트와 함께 망가지며, 비교할 이력 데이터도 갖고 있지 않습니다.

마지막 하나이자, 출시 석 달 뒤 사고로 번지기 가장 쉬운 것. 기본적으로 내용을 기록하지 마십시오. Claude Code는 사용자 프롬프트 내용을 기본적으로 수집하지 않고 프롬프트 길이만 기록하며, 내용을 포함하려면 환경 변수를 명시적으로 설정해야 합니다3. Agent SDK의 텔레메트리도 마찬가지로 구조 우선입니다. 모든 스팬이 소요 시간, 모델 이름, 도구 이름을 기록하고, 토큰 수는 API가 usage 데이터를 반환할 때 기록되지만, 에이전트가 읽고 쓰는 내용은 기본적으로 기록되지 않습니다1.

이 두 기본값은 같은 판단을 반영합니다. 구조적 정보(누가, 언제, 얼마나 오래, 어느 도구, 토큰 몇 개)만으로 운영 질문의 대다수에 답할 수 있고, 내용은 그렇지 않습니다. 내용이 한번 로그에 들어가면 로그를 따라 백업으로, 장기 보관으로, 읽기 권한을 가진 모든 사람의 시야로 들어갑니다. 그러니 여러분의 하네스는 tool_input: {...} 이 아니라 input_bytes: 137 을 기록하는 것을 기본으로 삼아야 하고, 특정 호출의 정확한 파라미터를 정말로 확인해야 할 때 그 한 건에 대해서만 전문 기록을 켜십시오. 이것은 레슨 2의 '원본 트랜스크립트가 1차 증거다'와 충돌하지 않습니다. 디버깅할 때는 여러분이 통제하는 환경에서 특정 실행에 대해 완전한 왕복을 반드시 봐야 하고, 읽고 나면 끝입니다. 프로덕션 로그는 기본적으로 장기 보관되고 여러 사람에게 보입니다. 그건 다른 이야기입니다.

경계: 이번 레슨이 멈추는 곳

여기까지 오면 구조화된 기록 한 무더기와 읽을 만한 메트릭 한 벌이 생겼습니다. 이번 레슨이 하지 않는 것이 셋입니다. 기록들 사이에 부모-자식 관계를 꿰는 것(하나의 프롬프트가 어떤 모델 요청을 촉발했는지, 어느 도구 호출이 어느 서브에이전트 아래에 중첩되는지)은 트리를 짓기 위한 상관관계 ID가 필요하고, 그것이 레슨 4입니다. 하네스 코드를 고치지 않고 생애 주기 체크포인트에 탐침을 거는 것은 레슨 5의 훅입니다. 이 계층 전체를 7번째 코스의 하네스에 얹고 완전한 디버깅 훈련을 한 바퀴 도는 것은 레슨 6입니다.

💻 연습

정리

  • 프로덕션 관측 가능성은 네 가지 질문에 답해야 한다. 어떤 도구가 불렸는지, 각 모델 요청이 얼마나 걸렸는지, 토큰이 얼마나 들었는지, 실패가 어디서 일어났는지1. 이 넷과 이 시리즈 10번째 코스의 평가 메트릭(개별 도구 호출과 작업 전체의 실행 시간, 총 도구 호출 수, 총 토큰 소비, 도구 에러)2은 같은 숫자 묶음이다. 평가에서는 변경이 나아지게 했는지 판정하는 데 쓰고, 모니터링에서는 실행의 건강 상태를 지켜보는 데 쓴다
  • 로그 필드 설계와 JSONL 선택에는 권위 있는 사양이 없다. 그것은 여러분의 엔지니어링 결정이다. 기본 출발점은 모델 요청마다 기록 하나, 도구 호출마다 기록 하나, 한 줄에 JSON 객체 하나, 그리고 session id, prompt id, 소요 시간, 토큰 수, 도구 이름, 에러다. 산문형 로그는 사람만 읽을 수 있지만, 구조화된 것은 거를 수 있고 집계할 수 있고 분포를 분석할 수 있다
  • 메트릭의 값어치는 패턴이 곧바로 수정에 대응한다는 데 있다. 중복 호출이 많다는 것은 페이지네이션이나 토큰 상한 파라미터를 조정해야 한다는 뜻이고, 잘못된 파라미터 에러가 많다는 것은 도구 설명에 명료함이나 예시가 필요하다는 뜻이다2. 도구 호출을 추적하면 에이전트가 밟는 흔한 워크플로와 도구를 통합할 기회도 드러난다2. 도구 호출이 에러를 낼 때 응답 자체는 불투명한 에러 코드가 아니라 구체적이고 실행 가능한 안내로 쓰여야 한다2
  • 신호의 의미론은 기록 방식이 정의한다. Claude Code는 실패한 API 요청을 내부적으로 재시도하고 포기한 뒤에야 api_error 이벤트를 하나 내보낸다. 그것은 그 요청에 대한 종단 신호이고 중간 재시도는 따로 기록되지 않는다3. 그래서 '에러 건수' 하나가 보이지 않는 재시도를 잔뜩 감출 수 있다. 세션이 회복했는지 멈췄는지 가르려면 이벤트를 session id로 묶고 에러 이후에 뒤따르는 요청 이벤트가 있는지 확인한다3
  • 토큰은 가장 볼 만한 단일 메트릭이다. Anthropic의 데이터에서 에이전트는 채팅의 약 4배, 멀티 에이전트 시스템은 약 15배의 토큰을 쓴다4. 평가 성능을 분석할 때 그들은 토큰 사용량 하나가 분산의 80%를 설명하고 도구 호출 수와 모델 선택이 나머지 두 설명 요인임을 발견했다4. 비용 메트릭은 근사치이며 공식 청구는 API 제공자에게서 온다3. 지출은 스킬 이름, 플러그인 이름, 서브에이전트 유형 같은 구체적인 것에 귀속시킬 수 있다3
  • 절제의 원칙이 둘이다. 관측되는 쪽 시스템은 원본 이벤트 스트림만 내보내고, 이상 탐지와 기준선 수립과 알림은 백엔드의 책임이다3. 로그는 기본적으로 내용을 기록하지 않아야 한다. 공식 제품은 기본적으로 프롬프트 내용을 수집하지 않고 길이만 남기며3, 텔레메트리도 기본적으로 구조적 정보만 기록하고 에이전트가 읽고 쓰는 것은 기록하지 않는다1. 알림 임계치와 SLO는 권위 있는 자료에 숫자가 없다. 지어내지 말고 먼저 2주 치 기준선을 기록하라

>> 레슨 4: 트레이싱: 한 번의 실행을 트리로 꿰기

Footnotes

  1. Observability with OpenTelemetry — Claude Agent SDK Official Documentation — https://code.claude.com/docs/en/agent-sdk/observability 2 3 4

  2. Writing effective tools for agents — with agents — Anthropic Engineering — https://www.anthropic.com/engineering/writing-tools-for-agents 2 3 4 5 6 7 8 9

  3. Monitoring — Claude Code Official Documentation — https://code.claude.com/docs/en/monitoring-usage 2 3 4 5 6 7 8 9 10 11 12 13

  4. How we built our multi-agent research system — Anthropic Engineering — https://www.anthropic.com/engineering/multi-agent-research-system 2 3 4 5

연습

01

코드는 필요 없습니다. 아래는 어제 여러분의 에이전트가 돌린 작업 다섯 건의 메트릭 요약입니다:

레벨 1: 메트릭 표 하나에서 세 종류의 문제 읽어내기
작업총 시간도구 호출 수총 토큰도구 에러 수
T1 실패하는 테스트 고치기42s938,4000
T2 문서에서 API 사용처 찾기186s41214,0000
T3 주간 리포트 생성71s1244,9007
T4 모듈 리팩터링402s16806,0001
T5 설정 질문에 답하기55s831,2000

로그에서 끌어온 추가 맥락:

  • T2의 41건 중 28건이 search_docs 호출이고 offset 파라미터만 바뀝니다. 0, 20, 40, 60…
  • T3의 에러 7건은 모두 search_issues 에서 왔고 에러 텍스트가 동일합니다. invalid parameter: date_range
  • T4의 16건 중 4건이 같은 3,000줄짜리 파일을 읽는 read_file 이고, 에러 1건은 run_tests 시간 초과입니다
  • T1과 T5에는 같은 도구를 반복 호출한 흔적이 없습니다

세 가지 질문에 답하되, 각각 어떤 숫자(또는 맥락 항목)에 근거했는지 밝히십시오. 어느 패턴이 '페이지네이션이나 토큰 상한 파라미터 조정'을 가리키는가? 어느 것이 '도구 설명에 명료함이나 예시가 필요함'을 가리키는가? 어느 토큰 값을 먼저 조사할 만하고, 왜 총합 2위가 아니라 그것인가? 여기에 참/거짓 하나 더. T4의 에러 1건은 '도구 설명을 고쳐야 한다'는 신호가 되는가?

완료 기준 · 로컬에서 확인
02

이번에는 실제로 돌아가는 코드가 필요합니다. 아래는 여러분의 하네스에서 나온 JSONL 로그 한 토막입니다(기록 20건, 프롬프트 4개, 세션 2개). agent.jsonl 로 저장하십시오:

레벨 2: 로그 집계 스크립트 작성하기

node stats.mjs agent.jsonl 로 돌아가는 stats.mjs 를 쓰되 네 가지 일을 하게 하십시오. JSONL 읽기(한 줄에 객체 하나, 빈 줄 건너뛰기, 깨진 줄은 잡아서 알리되 멈추지 않기), type 별 집계로 건수, 총 소요 시간, 총 토큰, 에러 건수 내기, prompt_id 로 묶어 '에러가 있고 그 뒤에 기록이 없는' 실행을 찾아 prompt_id, session_id, 에러 메시지 출력하기, 종료 코드 1은 멈춤 의심 발견, 0은 없음(그래야 CI나 cron에 꽂을 수 있습니다). Node 표준 라이브러리만 쓰고 의존성은 두지 마십시오.

완료 기준 · 로컬에서 확인