LLMOps 21

LLM 디버깅의 핵심: Snapshot과 State 이력

프롬프트와 최종 답변만 저장하면 디버깅에 충분하다고 생각하기 쉽다.

근거 · 교안 p62-p64

LLM 디버깅의 핵심: Snapshot과 State 이력 대표 이미지

1장: 비결정성과 과정의 중요성

1. 비결정성과 과정의 중요성

솔라가 노트북 화면에서 눈을 떼며 길게 한숨을 쉬었다. 화면 한쪽에는 사용자의 질문이, 다른 한쪽에는 LLM이 생성한 엉뚱한 답변이 나란히 떠 있었다.

“언니, 이거 좀 이상해. 분명히 지난번엔 이 프롬프트에 잘 대답했는데, 오늘은 왜 이러지?”

솔라는 자신이 만든 작은 여행 계획 봇을 가리켰다. 사용자가 입력한 프롬프트는 ‘서울에서 당일치기로 갈 수 있는 조용한 바다’였다. 지난주 테스트에서는 ‘인천 을왕리’를 추천하며 그럴듯한 설명을 내놓았는데, 방금 똑같은 질문을 다시 입력하자 ‘강릉 경포해변’을 추천하며 ‘1박 2일 코스’를 짜주고 있었다.

“흠. 당일치기라 물었는데 1박 2일을 추천했네.”

루나가 솔라의 노트북 화면을 들여다보며 말했다.

“응. 그래서 버그를 잡으려고 하는데 좀 막막해. 입력값이랑 결과값은 이렇게 있는데, 왜 이런 결과가 나왔는지 중간 과정을 알 수가 없으니까. 그냥 다시 실행하면 또 괜찮게 나올 때도 있고. 그래도 결국은 프롬프트랑 최종 답변 기록만 잘 모아두면 패턴을 찾아서 고칠 수 있지 않을까?”

솔라는 자신만만하게 말했다. 그것이 시스템의 문제를 파악하는 가장 정석적인 방법이라고 생각했다. 입력(X)과 결과(Y)를 모으다 보면, 언젠가는 그 사이의 함수(f)를 역추적할 수 있을 거라고.

루나는 잠시 생각에 잠기더니, 솔라의 책상에 놓여있던 빈 노트와 펜을 가져왔다.

“솔라, 우리 간단한 상상 실험 하나만 해볼까? 계산기라고 생각해 봐.”

루나는 노트에 ‘2 + 2 =’ 라고 적었다.

“이 계산기에 ‘2 + 2’를 입력하면 뭐가 나올까?”

“당연히 4지.”

“내일 다시 입력하면?”

“그래도 4겠지. 백 번을 해도 4가 나올 거고.”

솔라는 너무나 당연한 질문에 어깨를 으쓱했다.

“맞아. 이건 ‘결정론적’ 시스템이야. 입력이 같으면 결과는 언제나 같지. 그래서 만약 어느 날 ‘5’라는 결과가 나왔다면, 우린 ‘계산기 내부 로직에 버그가 생겼구나’라고 확신하고 그 지점을 파고들 수 있어.”

루나는 ‘2 + 2 = 4’ 옆에 ‘(결정적)’이라고 써넣었다. 그리고 그 아래에 새로운 질문을 적었다.

‘냉장고에 달걀, 양파, 햄이 있어. 10분 안에 만들 수 있는 아침 메뉴 추천해줘.’

“자, 이번엔 이게 우리가 만든 LLM 요리 봇에 들어간 프롬프트야. 처음 실행했을 때, 봇이 ‘햄 양파 볶음밥’을 추천했어. 완벽한 답변이지. 그런데 한 사용자가 ‘가끔 이 질문에 엉뚱한 답변이 나와요’라는 버그를 제보했어. 그래서 솔라 네가 디버깅을 위해 똑같은 프롬프트를 다시 입력해봤어.”

“응, 똑같이 입력했어.”

“그런데 이번에는 봇이 ‘달걀찜’을 추천하는 거야. 이것도 10분 안에 만들 수 있고, 재료도 다 있으니 틀린 답은 아니지. 자, 이제 어떻게 할 거야? 첫 번째 실행에서 잠재적으로 발생했을 ‘엉뚱한 답변’의 원인을 어떻게 찾을 수 있을까?”

솔라의 손가락이 허공에서 멈칫했다.

“어… 일단 ‘달걀찜’은 괜찮은 답변이니까… 다시 한번 더 실행해 봐야지. ‘엉뚱한 답변’이 나올 때까지.”

“만약 세 번째는 ‘길거리 토스트’ 레시피가 나오고, 네 번째는 다시 ‘햄 양파 볶음밥’이 나온다면? 매번 그럴듯하지만 다른 답변이 나온다면, 사용자가 마주쳤던 바로 그 ‘엉뚱한 답변’의 순간을 우리는 어떻게 재현하고 분석할 수 있을까?”

“……재현할 수가 없네.”

솔라는 나지막이 중얼거렸다. 입력과 결과만으로는 부족했다. 처음 ‘햄 양파 볶음밥’이 나왔던 순간과, 두 번째 ‘달걀찜’이 나왔던 순간은 비록 입력은 같았지만 LLM 내부에서는 전혀 다른 경로를 거쳐 나온 결과물이었다. 사용자가 겪었던 오류는 그 수많은 가능성의 경로 중 하나였을 뿐이고, 그 경로는 이미 사라지고 없었다. 입력과 출력이라는 점 두 개만 찍어서는 그 사이에 그려졌던 구불구불한 선을 복원할 수 없었다.

루나는 솔라가 골똘히 생각에 잠긴 것을 보고 노트에 한 문장을 덧붙였다.

LLM 시스템은 비결정적(Non-deterministic)이다.

“바로 그게 LLM 디버깅의 가장 큰 어려움이야. LLM은 계산기처럼 결정론적이지 않아. 같은 질문에도 내부적인 확률 분포나 그 순간의 컨텍스트에 따라 다른 추론 과정을 거쳐 다른 답변을 내놓을 수 있어. 그래서 운영 중인 시스템에서 중요한 건 ‘어떤 결과가 나왔다’는 사실보다, ‘어떤 과정을 거쳐 그 결과에 도달했는가’하는 변화의 이력 전체야.”

솔라는 아까 보았던 자신의 여행 계획 봇을 다시 떠올렸다. ‘당일치기’라는 키워드를 놓치고 ‘1박 2일’ 코스를 제안했던 그 순간, 봇의 내부에서는 어떤 단어에 더 높은 가중치를 두었을까? 어떤 중간 생각의 고리가 ‘강릉’이라는 결론으로 이어졌을까? 지금 다시 같은 질문을 던져봐야 그 순간의 내면을 들여다볼 수는 없었다.

“그렇구나… 입력이랑 결과만 가지고는 범인을 잡을 수 없는 거였어. 범행 현장은 이미 사라졌고, 내가 가진 건 피해자와 사건 결과뿐인 셈이네.”

솔라의 시선이 루나가 노트에 적은 ‘비결정적’이라는 단어에 머물렀다. 처음에는 막연하게만 느껴졌던 개념이, 재현 불가능한 버그라는 구체적인 경험과 연결되자 선명하게 다가왔다.

“알겠어, 언니. 결과가 아니라 과정의 이력을 봐야 한다는 건 이제 확실히 와닿아. 그런데… 그 ‘과정’이라는 게 대체 뭐야? 우리가 들여다봐야 할 LLM의 속마음이라는 게 구체적으로 뭔데? 그냥 대화 기록 말고 또 뭘 기록해야 하는 거지? 아까 슬쩍 본 자료에 ‘State’라는 말도 있던데, 그건 또 뭐고?”

2장: Snapshot: ‘전체 State’의 의미

솔라의 질문이 끝나기 무섭게, 루나는 대답 대신 행동했다. 그녀는 솔라와 자신 사이에 놓인 노트를 빙글 돌려 펼쳤다. ‘비결정적’이라는 단어가 적힌 페이지 바로 다음 장이었다. 루나는 펜을 들어 페이지 중앙에 네모난 상자를 하나 그렸다. ‘여행 계획 봇’이라고 작게 이름을 붙인 뒤, 상자 왼쪽에는 ‘입력’, 오른쪽에는 ‘출력’ 화살표를 그렸다. 지난 장에서 나눈 대화의 요약도 같았다.

그리고 루나는 그 상자 아래에 새로운 표를 그리기 시작했다. 두 개의 열을 가진 간단한 표였다. 첫 번째 열의 제목은 ‘대화 기록 (Messages)’, 두 번째 열의 제목은 ‘내부 정보 (Internal Data)’였다. 솔라가 궁금해했던 ‘대화 기록 말고 또 뭘 기록해야 하는가’에 대한 답을 글자가 아닌 구조로 보여주려는 듯했다.

“언니, 이게 바로 그 ‘과정’을 기록하는 방법이야? 대화 기록 말고 내부 정보까지 전부 다?”

솔라가 표를 가리키며 물었다. 그녀의 눈에는 호기심과 함께 약간의 의구심이 서려 있었다.

“내부 정보라… 그럼 그냥 봇이 하는 모든 생각, 모든 중간 단계를 전부 텍스트로 저장하라는 뜻인가? 대화 기록을 훨씬 더 상세하게 남기는 거랑 비슷한 거 아니야?”

솔라의 생각은 ‘기록’이라는 단어에 갇혀 있었다. 과정의 이력을 남긴다는 것이 결국은 더 많은 텍스트 로그를 쌓는 일이라고 여겼다.

루나는 고개를 저으며 펜 끝으로 ‘내부 정보’라고 쓰인 열을 톡톡 쳤다.

“단순히 양의 문제가 아니야. 종류가 다른 정보를 기록해야 해. 자, 여기 ‘여행 계획 봇’이 하나의 임무를 받았다고 해보자. ‘내일 서울 날씨에 맞춰 점심 메뉴를 추천하고, 근처 식당을 예약해줘’라는 임무야.”

루나는 ‘입력’ 화살표 옆에 작은 글씨로 임무를 적었다.

“이 봇이 똑똑하게 작동한다면, 먼저 계획을 세우겠지.”

루나는 ‘내부 정보’ 열 첫 번째 칸에 ‘1. 계획 수립’이라고 적고, 그 아래에 들여쓰기해서 썼다.

- 날씨 정보 확인 - 날씨 기반 메뉴 선정 - 메뉴에 맞는 식당 검색 - 식당 예약

“이건 봇이 사용자에게 보여주는 대화가 아니야. 자기 혼자 마음속으로 세운 작업 순서지.”

솔라는 말없이 고개를 끄덕였다.

“자, 다음 단계. 봇이 계획대로 날씨 정보를 확인했어.”

루나는 ‘내부 정보’ 열 다음 칸에 ‘2. 도구 사용: 날씨 API’라고 적고, 그 결과값을 간략하게 덧붙였다.

결과: { "날짜": "내일", "도시": "서울", "날씨": "흐림", "기온": 15 }

“이것도 사용자와의 대화는 아니네.”

솔라가 중얼거렸다. 이건 기계와 기계 사이의 대화에 가까웠다.

“맞아. 그리고 이 결과를 바탕으로 봇은 내부적으로 판단을 내려. ‘흐리고 쌀쌀하니까 따뜻한 국물이 좋겠다. 메뉴는 칼국수로 하자.’ 이것도 역시 봇의 속마음이지.”

루나는 ‘3. 중간 결론: 칼국수’라고 적었다. 이 모든 과정이 진행되는 동안, ‘대화 기록’ 열은 여전히 텅 비어 있었다. 봇은 아직 사용자에게 한마디도 하지 않았다. 마지막으로 봇이 식당 예약까지 마친 뒤, 드디어 사용자에게 첫 메시지를 보낸다.

루나는 ‘대화 기록’ 열 첫 칸에 드디어 내용을 채워 넣었다.

“내일 서울은 흐리고 쌀쌀할 예정입니다. 따뜻한 칼국수는 어떠세요? 근처 ‘명동교자’에 2인석을 예약해 두었습니다.”

루나는 펜을 내려놓고 솔라를 바라보았다.

“자, 이제 봐. 만약 이 봇이 갑자기 ‘내일은 화창하니 냉면을 드세요’라는 엉뚱한 답변을 내놨다고 가정해보자. 우리가 가진 기록이 왼쪽의 ‘대화 기록’뿐이라면 원인을 알 수 있을까?”

“아니. 그냥 ‘봇이 이상하다’고만 알 수 있겠지.”

“그럼 오른쪽 ‘내부 정보’까지 전부 기록되어 있다면 어떨까? 우리는 과정의 어느 부분에서 문제가 생겼는지 정확히 짚어낼 수 있어. 날씨 API가 ‘화창함’이라는 잘못된 정보를 줬는지, 아니면 날씨 정보는 ‘흐림’이었는데 메뉴를 선정하는 중간 결론 단계에서 뭔가 꼬였는지 말이야.”

그제야 솔라의 눈이 번쩍 뜨였다. 그녀가 막연히 ‘더 상세한 기록’이라고 생각했던 것이 완전히 다른 차원의 정보라는 사실을 깨달았다. 대화 기록이 연극의 최종 대본이라면, 내부 정보는 그 대본이 나오기까지의 모든 동선, 조명 계획, 배우의 감정선, 소품 목록까지 포함하는 연출 노트였다.

“아…! 알겠다. ‘과정의 이력’이라는 건 단순히 대화를 더 남기는 게 아니었어. 봇의 머릿속에서 일어나는 모든 사건, 그러니까 계획, 외부에서 가져온 데이터, 그걸 해석한 중간 생각까지 통째로 기록하는 거구나. 우리가 밖에서 보는 건 ‘출력’이라는 메시지 하나지만, 그 안에서는 이런 일들이 순서대로 벌어지고 있었던 거네.”

솔라는 루나가 그린 표의 ‘내부 정보’ 열을 손가락으로 가리켰다.

“이게 바로 언니가 말한 ‘State(상태)’라는 거야?”

“정확해. 특정 시점의 시스템에 관한 모든 정보의 총합. 그게 바로 ‘State’야. 그리고 방금 우리가 나열한 이 모든 것—대화 기록, 계획, 도구 사용 결과, 중간 결론—을 한 순간에 사진 찍듯이 캡처하는 걸 ‘스냅샷(Snapshot)’이라고 불러.”

루나는 표 전체에 커다란 원을 그리며 말했다.

“디버깅을 위해 우리가 남겨야 하는 건 단순히 메시지(messages)가 아니야. 바로 이 ‘State 전체’를 담은 스냅샷의 이력이지.”

‘State 전체를 저장한다’는 문장이 솔라의 머릿속에 명확한 그림으로 자리 잡았다. 그것은 단순한 로그 저장을 넘어, 특정 순간의 시스템을 완벽하게 재현할 수 있는 박제된 현실과도 같았다.

“그렇구나. 내 여행 계획 봇 하나를 위해서라도 이 정도의 기록은 필수적이겠네. 마치 비행기 사고가 났을 때 블랙박스를 열어보는 것처럼.”

솔라는 자신 있게 말했다. 이제 단일 에이전트의 문제를 해결할 명확한 실마리를 잡은 듯했다. 그러다 문득 새로운 질문이 떠올랐다.

“그런데 언니, 만약에… 에이전트가 하나가 아니라 여러 개라면 어떡하지? 예를 들어, 여행 계획 봇이 항공권 예약 봇이랑 숙소 예약 봇이랑 서로 대화하면서 일을 처리하는 시스템이라면? 그때도 그냥 각자 스냅샷만 잘 찍어두면 되는 걸까?”

3장: 멀티 에이전트 디버깅과 Snapshot

솔라의 질문은 이전 장에서 그린 표 위에 머물렀다. 비행기 블랙박스처럼 단일 에이전트의 모든 것을 기록하는 스냅샷. 그녀의 자신감 넘치는 표정과 달리, 그녀의 마지막 질문은 이 완벽해 보이는 해결책에 작은 균열을 만들고 있었다. 여러 에이전트가 얽히면 문제는 어떻게 달라지는가?

루나는 솔라의 질문에 말로 답하는 대신, 펜을 들어 이전 장에서 그렸던 ‘여행 계획 봇’ 상자 옆에 두 개의 상자를 더 그렸다. 하나에는 ‘항공권 예약 봇’, 다른 하나에는 ‘숙소 예약 봇’이라고 이름을 붙였다. 그리고 세 개의 상자를 화살표로 연결했다. 중앙의 여행 계획 봇이 양옆의 두 봇에게 각각 명령을 내리고, 그 결과를 다시 받는 모양새였다. 방금 전까지 단일 배우가 독백하던 무대가 순식간에 세 명의 배우가 상호작용하는 복잡한 연극 무대로 변했다.

“이렇게 되면… 복잡해지네.”

솔라가 그림을 보며 중얼거렸다. 그녀는 금방 상황을 파악하려 애썼다.

“그래도 원리는 같지 않을까? 최종적으로 엉뚱한 여행 계획이 나왔다면, 항공권 예약 봇의 최종 메시지랑 숙소 예약 봇의 최종 메시지를 확인하면 되잖아. 둘 중 하나가 이상한 답을 줬겠지. 각자 스냅샷도 찍어뒀을 테니, 범인인 봇을 찾아서 그 봇의 블랙박스를 열어보면 되는 거 아니야?”

솔라의 논리는 명쾌했다. 각자 잘못은 각자의 기록에서 찾으면 된다. 여러 명이라고 해서 본질이 달라질 건 없다고 생각했다.

루나는 솔라의 말을 가만히 듣고 나서, 새로 그린 세 개의 상자 아래에 아주 간단한 시나리오를 적기 시작했다.

임무: 제주도행 오전 비행기와 그 근처 숙소를 예약해줘.

“자, 상상해 봐. 여행 계획 봇이 이 임무를 받고 일을 시작했어. 먼저 항공권 예약 봇에게 ‘제주도, 오전, 저렴한 항공권’을 찾아달라고 요청했지.”

루나는 여행 계획 봇에서 항공권 예약 봇으로 향하는 화살표 위에 ‘요청: 제주(Jeju), 오전’이라고 썼다.

“그런데 항공권 예약 봇이 작업을 하다가, 아주 잠깐 실수를 했어. ‘제주(Jeju)’와 발음이 비슷한 중국 ‘지난(Jinan)’의 특가 항공권을 찾은 거야. 그리고 ‘아, 사용자가 이걸 원했구나’ 하고 스스로 결론을 내렸지. 그래서 여행 계획 봇에게 이렇게 보고했어: ‘지난(Jinan)행 오전 특가 항공권을 찾았습니다.’”

“말도 안 돼. 그런 실수를 한다고?”

“LLM이니까. 충분히 일어날 수 있는 일이지. 자, 이제 여행 계획 봇은 ‘지난’ 항공권 정보를 받았어. 그리고 그 정보를 그대로 숙소 예약 봇에게 넘겨주면서 ‘이 목적지 근처에 있는 숙소를 예약해줘’라고 지시했지.”

루나는 항공권 예약 봇에서 여행 계획 봇으로, 다시 숙소 예약 봇으로 이어지는 화살표들 위에 데이터의 흐름을 그려 넣었다. ‘제주(Jeju)’가 어느새 ‘지난(Jinan)’으로 바뀌어 있었다.

“숙소 예약 봇은 아무 잘못이 없어. ‘지난’이라는 정보를 받았으니, 충실하게 중국 지난시에 있는 호텔을 예약하고 보고했지. 이제 모든 일이 끝나고 사용자에게 최종 결과가 나갔어. ‘지난행 항공권과 지난시 호텔 예약이 완료되었습니다!’”

루나는 펜을 내려놓았다.

“사용자는 당연히 화가 났겠지. 이제 솔라 네가 디버거야. 네 손에 있는 건 최종 결과뿐이야. 원인을 찾아봐. 누구의 잘못이지?”

솔라는 잠시 입을 다물었다. 상황은 명확했지만 범인을 지목하기는 애매했다.

“일단… 숙소 예약 봇은 잘못이 없어. 받은 대로 일했으니까. 그럼 항공권 예약 봇이 범인이네. 제주를 지난으로 잘못 알아들었으니까.”

“확신할 수 있어? 어쩌면 맨 처음 여행 계획 봇이 항공권 예약 봇에게 요청할 때부터 ‘지난’이라고 잘못 보냈을 수도 있잖아. 항공권 예약 봇은 받은 대로 충실하게 일한 것뿐일 수도 있고.”

“아…”

솔라는 허를 찔린 표정을 지었다. 최종 결과만 봐서는 ‘언제’ 데이터가 오염되었는지, 그 책임이 ‘누구’에게 있는지 특정할 수가 없었다. 각 봇이 독립적으로는 완벽하게 작동한 것처럼 보일 수도 있었다. 숙소 봇은 주어진 목적지로 예약했고, 항공권 봇도 (자체 기준으로는) 가장 저렴한 표를 찾았을 뿐이다. 범죄는 일어났지만, 용의자들은 각자 완벽한 알리바이를 가지고 있는 셈이었다. 서로에게 책임을 떠넘기는 상황. ‘누가, 언제’ 정보를 망가뜨렸는지에 대한 증거가 없었다.

루나는 솔라가 부딪힌 벽을 확인하고 나서, 다시 펜을 들었다. 그리고 각 화살표 사이의 빈 공간에 ‘스냅샷’을 의미하는 번개 모양 아이콘을 그려 넣기 시작했다. 각 봇이 서로 대화하고, 혼자 일하는 모든 순간마다.

“이제 스냅샷이 있다고 상상해보자. 모든 상호작용의 전체 State를 기록한 거야.”

루나는 각 스냅샷의 내용을 읊어주었다.

스냅샷 1 (여행 계획 봇 → 항공권 예약 봇): { 요청 데이터: { 목적지: '제주' } }” “스냅샷 2 (항공권 예약 봇 내부): { 받은 데이터: { 목적지: '제주' }, 도구 검색 결과: '지난 항공권', 중간 결론: '목적지를 지난으로 확정' }” “스냅샷 3 (항공권 예약 봇 → 여행 계획 봇): { 보낸 데이터: { 목적지: '지난' } }

“여기까지.”

루나가 펜으로 스냅샷 2번을 가리켰다.

“범인이 나왔네.”

솔라가 나지막이 말했다. 범행의 순간이 명확하게 포착되었다. 책임 소재는 항공권 예약 봇에 있었고, 시점은 ‘중간 결론’을 내리는 바로 그 순간이었다. 여행 계획 봇이 잘못된 데이터를 넘겨준 것도 아니었고, 숙소 예약 봇이 멋대로 행동한 것도 아니었다. 여러 에이전트가 복잡하게 얽힌 작업의 흐름 속에서, 각 상호작용 지점의 ‘전체 State’를 담은 스냅샷의 이력은 마치 CCTV 영상처럼 문제의 근원지를 정확하게 비추고 있었다.

“이제 알겠어. 단일 에이전트에서 스냅샷이 블랙박스라면, 멀티 에이전트 시스템에서 스냅샷의 이력은 각 용의자의 동선을 모두 파악할 수 있는 CCTV 관제 시스템 같은 거구나. 누가 누구와 만나 무슨 정보를 주고받았는지 전부 다.”

솔라의 눈빛이 달라졌다. 그녀는 루나의 노트를 자기 앞으로 끌어당겼다. 그리고 깨끗한 새 페이지를 펼쳤다.

그녀는 더 이상 질문하지 않았다. 대신 직접 그리기 시작했다. 맨 위에는 ‘나의 여행 계획 봇’이라고 적었다. 그리고 그 아래에 ‘맛집 추천 봇’과 ‘날씨 정보 봇’이라는 두 개의 상자를 더 그렸다.

스스로 가상의 버그 시나리오를 상상하는 듯했다. ‘날씨에 맞는 근처 맛집 추천’이 엉뚱하게 나오는 상황. 솔라는 각 봇을 연결하는 화살표를 그리고, 그 사이사이에 루나가 했던 것처럼 번개 모양 아이콘을 그려 넣었다. 그리고 그 옆에 자신이 반드시 확인해야 할 체크리스트를 적어 내려가기 시작했다.

  • [Snapshot Point 1: Planner → Weather]
    • 요청한 지역/날짜 정보가 정확한가?
  • [Snapshot Point 2: Weather Bot 내부]
    • API에서 받은 날씨 데이터 원본은 무엇인가?
    • 날씨를 ‘맑음’, ‘흐림’ 등으로 판단한 내부 결론은 무엇인가?
  • [Snapshot Point 3: Weather → Planner]
    • Planner에게 최종적으로 전달한 날씨 상태 값은 무엇인가?
  • [Snapshot Point 4: Planner → Restaurant]
    • 날씨 정보를 바탕으로 Restaurant 봇에게 넘긴 음식 카테고리(예: 국물, 면)는 무엇인가?

솔라는 펜을 놓고 자신이 만든 ‘멀티 에이전트 원인 추적 맵’을 바라보았다. 막연하게 ‘로그를 많이 남기면 되겠지’라고 생각했던 처음의 자신과, 이제는 시스템의 어느 지점에서 어떤 정보를 찍어야 하는지 설계하는 자신 사이의 거리를 실감했다. LLM 시스템의 디버깅은 단 하나의 결과물이 아닌, 살아 움직이는 상태들의 연쇄를 추적하는 일이었다. 그리고 스냅샷은 그 흐름을 잡을 수 있는 유일한 도구였다.