LLMOps 24
LangSmith로 LLM Agent 실행 관측: 정확하고 신뢰성 있는 데이터 수집의 기본 원칙
관측 도구를 켜면 자동으로 모든 문제가 해결된다고 생각하기 쉽다.
근거 · 교안 p77-p85
1장: LangSmith 관측의 시작: 최소한의 연결과 확인
솔라는 노트북 화면에 떠 있는 한 문장을 소리 내어 읽었다. 개발자 커뮤니티에서 방금 발견한 글귀였다.
“LangSmith는 LLM Agent 시스템을 위한 Observability와 Evaluation 플랫폼…”
솔라는 의자 등받이에 몸을 기댔다. “언니, 이것 봐. LangSmith라는 거, 그냥 켜기만 하면 에이전트가 어떻게 돌아가는지 다 보여주는 도구인가 봐.”
옆에서 자신의 코드를 들여다보던 루나가 고개를 돌려 솔라의 화면을 보았다. 간결한 정의가 선명하게 적혀 있었다.
“보통 이런 모니터링 도구들은 한번 설치하고 활성화하면 알아서 데이터를 수집하고 분류해주잖아. 그럼 분석만 하면 되니까 편하지. LangSmith도 그렇겠지?” 솔라는 이미 답을 정해놓은 듯 말했다. 시스템을 켜는 것과, 그 시스템이 유용한 데이터를 모으는 것은 거의 같은 일이라고 생각하는 듯했다. 마치 전등 스위치를 올리면 방이 환해지는 것처럼, 관측 도구를 ‘활성화’하면 복잡한 에이전트의 내부가 저절로 환히 드러날 거라는 기대였다.
루나는 잠시 생각에 잠겼다가, 솔라의 노트북 옆에 놓인 작은 코드 조각이 담긴 창을 가리켰다. 둘이서 테스트용으로 만들어 둔 간단한 LLM 에이전트였다.
“그럴 수도 있겠네.” 루나가 조용히 말했다. “그럼 우리 에이전트를 LangSmith에 한번 연결해 볼까? 가장 먼저 뭘 해야 할지, 공식 문서를 한번 확인해 봐.”
루나의 말은 ‘네 생각이 틀렸어’가 아니라, ‘그 생각이 맞는지 직접 확인해 보자’는 제안이었다. 솔라는 신이 나서 문서를 뒤적이기 시작했다. API 키를 발급받아 환경 변수로 설정하는 부분은 이미 마쳐둔 상태였다. 그 다음으로 눈에 띈 것은 몇 줄의 설정 코드였다.
“찾았어. API 키 다음으로 중요한 게 이거네.” 솔라가 말했다.
import os
os.environ["LANGSMITH_TRACING"] = "true"
“LANGSMITH_TRACING… 추적을 한다는 뜻이겠지? 이걸 ‘true’로 설정하면 되나 봐.” 솔라는 망설임 없이 코드를 복사해 에이전트 실행 파일 맨 위에 붙여 넣었다. 그녀에게 이 한 줄은 그저 관측 기능을 켜는 ‘스위치’에 불과했다.
“자, 이제 실행해 볼게.”
솔라는 에이전트에 간단한 질문을 던지는 코드를 실행했다. 컴퓨터가 잠시 응답하더니, 이내 결과물을 출력했다. 외적으로는 아무것도 달라진 게 없었다. 진짜 변화는 다른 곳에서 일어나고 있을 터였다. 솔라는 재빨리 LangSmith 웹사이트로 돌아가 대시보드를 새로고침했다.
로딩 아이콘이 잠시 돌고, 화면이 번쩍였다. 그리고 그곳에, 방금 실행한 에이전트의 활동 기록이 한 줄 나타났다.
“와! 진짜 되네!” 솔라의 목소리가 들떴다. “여기 봐, 방금 내가 실행한 기록이 바로 떴어. 클릭하니까 에이전트가 어떤 도구를 썼고, LLM을 몇 번 호출했는지 전부 다 보여줘.”
솔라는 마법 같은 광경에 잠시 빠져들었다. 복잡하게 얽혀 있던 에이전트의 내부 동작이 한눈에 들어오는 투명한 상자처럼 펼쳐져 있었다. 그녀의 생각이 맞았던 것이다. 스위치를 켜자, 모든 것이 보이기 시작했다.
루나는 말없이 솔라가 화면을 탐색하는 모습을 지켜봤다. 솔라는 신나서 이것저것 눌러보다가, 문득 화면 한쪽 구석에서 고개를 갸웃거렸다.
“어? 그런데 이상하다.”
“뭐가?”
“이 기록, ‘default’라는 프로젝트 안에 들어가 있네.” 솔라가 화면의 프로젝트 이름을 손가락으로 가리켰다. “나는 프로젝트를 만든 적이 없는데. 이건 어디서 온 거지?”
솔라의 밝았던 표정에 미세한 균열이 생겼다. 분명 데이터는 수집되고 있었다. 하지만 그 데이터가 어디로, 어떤 기준으로 흘러가고 있는지는 전혀 알 수 없었다. 만약 여러 에이전트를 동시에 테스트한다면, 그 기록들이 전부 이 정체 모를 ‘default’ 폴더에 뒤섞여 버릴 터였다.
‘스위치를 켜는 것’만으로는 충분하지 않았다. 전등은 켜졌지만, 그 불빛이 어디를 비춰야 하는지는 아직 정해주지 않은 셈이었다. 솔라는 방금 전까지 당연하게 여겼던 자신의 생각이 흔들리는 것을 느꼈다.
“데이터가 보이긴 하는데… 이게 내가 원하는 곳에, 원하는 방식으로 쌓이고 있는 건지는 모르겠어.”
솔라의 시선은 더 이상 화려한 추적 그래프에 머물지 않았다. 대신, 화면 구석에 조용히 자리 잡은 ‘default’라는 단어에 고정되어 있었다. 이제 막 관측의 문을 열었을 뿐인데, 문 너머에는 새로운 질문이 기다리고 있었다.
2장: 데이터의 목적지: 프로젝트 설정과 정확한 분류
솔라는 더 이상 LangSmith 대시보드의 ‘default’ 프로젝트를 보고만 있지 않았다. 화면 왼쪽의 ’+ New Project’ 버튼을 눌러 ‘My-Awesome-Agent’라는 이름의 새 프로젝트를 만들었다. 이제 데이터가 흘러 들어갈 자신만의 깔끔한 서랍이 생긴 셈이었다. 흐뭇한 미소와 함께, 솔라는 다시 코드 편집기로 돌아왔다.
그녀는 지난번에 추가했던 LANGSMITH_TRACING 설정 바로 아래에 새로운 줄을 추가했다. ‘default’라는 이름 없는 서랍 대신, 방금 만든 반짝이는 새 서랍에 데이터가 정확히 들어가도록 주소를 지정해 줄 참이었다. 솔라는 이것이 단순히 데이터에 이름표를 붙여주는, 정리 정돈을 위한 가벼운 작업이라 생각했다.
import os
os.environ["LANGSMITH_TRACING"] = "true"
# 내 멋진 에이전트를 위한 프로젝트!
os.environ["LANGSMITH_PROJECT"] = "My-Awesome-Agent-Test"
솔라는 주석까지 달며 만족스럽게 코드를 바라보았다. “좋았어. 이제 ‘My-Awesome-Agent-Test’라는 이름으로 내 에이전트의 모든 기록이 착착 쌓이겠지.” 이름 끝에 ‘-Test’를 붙인 것은 단순한 변덕이었다. 어차피 ‘My-Awesome-Agent’라는 맥락만 맞으면 알아서 잘 찾아 들어갈 것이라고, 그녀는 조금도 의심하지 않았다.
“언니, 나 이제 프로젝트도 설정했어. 이러면 여러 에이전트를 돌려도 기록이 섞일 일 없겠지?”
솔라의 자신감 넘치는 목소리에 루나가 화면을 들여다보았다. 루나는 코드에 적힌 프로젝트 이름과, 솔라가 방금 만들었던 LangSmith UI의 프로젝트 이름을 번갈아 보았지만 아무 말도 하지 않았다. 그저 솔라가 직접 확인하도록 조용히 지켜볼 뿐이었다.
솔라는 다시 한번 에이전트 실행 코드를 돌렸다. 잠시 후, 그녀는 기대에 차 LangSmith의 ‘My-Awesome-Agent’ 프로젝트 페이지를 새로고침했다.
…텅 비어 있었다. ‘No traces found.‘라는 메시지만 덩그러니 놓여 있었다.
“어?”
솔라의 손가락이 멈칫했다. 몇 번이고 새로고침을 눌렀지만 결과는 같았다. 당황한 그녀는 혹시나 하는 마음에 지난번 기록이 쌓였던 ‘default’ 프로젝트를 확인해 보았다. 그곳에도 새로운 기록은 없었다. 데이터가 증발하기라도 한 걸까?
“이상하다. 추적은 켜져 있는데… 왜 기록이 안 보이지? 데이터가 어디로 간 거야?”
혼란에 빠진 솔라가 화면 전체를 샅샅이 훑기 시작했다. 바로 그때, 그녀의 눈에 익숙하면서도 낯선 이름이 들어왔다. 프로젝트 목록, 그녀가 만들었던 ‘My-Awesome-Agent’ 바로 아래에 새로운 프로젝트가 생겨나 있었다.
[ My-Awesome-Agent-Test ]
솔라는 자기도 모르게 “아니, 왜?” 하고 소리쳤다. “난 ‘My-Awesome-Agent’에 넣으려고 했는데, 왜 마음대로 새 프로젝트를 만들어서 거기에 넣은 거야? 그냥 비슷한 이름의 프로젝트에 넣어주면 안 되나?”
그녀의 목소리에는 황당함이 가득했다. LangSmith가 자신의 의도를 전혀 이해하지 못했다는 배신감마저 느껴졌다. ‘Test’라는 꼬리표 하나 붙였을 뿐인데, 시스템은 그걸 완전히 다른 목적지로 인식해 버렸다.
그때 루나가 조용히 솔라의 노트북 화면을 손가락으로 나누어 가리켰다. 한쪽은 코드 편집기 창, 다른 한쪽은 LangSmith의 프로젝트 목록이었다.
“솔라, 네가 LangSmith에게 보낸 주소를 다시 봐.”
“주소? ‘My-Awesome-Agent-Test’라고 보냈지.”
“그리고 네가 데이터를 받으려고 만들어 둔 서랍의 이름은 뭐지?”
“‘My-Awesome-Agent’… 아.”
솔라는 그제야 깨달았다. 두 이름은 비슷했지만, 엄연히 달랐다. 그녀는 LangSmith가 자신의 의도를 ‘해석’해 줄 것이라 기대했지만, 시스템은 그저 주어진 ‘문자열’을 정확하게 따를 뿐이었다. ‘My-Awesome-Agent-Test’라는 주소에 해당하는 서랍이 없자, 시스템은 친절하게도 그 이름으로 새 서랍을 만들어 버린 것이었다. 프로젝트 이름은 단순한 이름표가 아니라, 데이터의 목적지를 결정하는 유일하고 절대적인 주소였던 것이다.
“정확하게 일치해야만 하는 거였구나. 조금이라도 다르면 그냥 새 주소로 알아듣는 거네.”
솔라는 허탈하게 웃으며 코드의 프로젝트 이름을 수정했다. ‘-Test’ 부분을 말끔히 지워 LangSmith UI의 프로젝트 이름과 글자 하나 틀리지 않게 맞췄다.
# 정확한 프로젝트 이름으로 수정!
os.environ["LANGSMITH_PROJECT"] = "My-Awesome-Agent"
그녀는 수정된 코드로 다시 에이전트를 실행했다. 그리고 떨리는 마음으로 ‘My-Awesome-Agent’ 프로젝트 페이지를 새로고침했다. 이번에는 달랐다. 방금 실행한 에이전트의 추적 기록이 선명하게 목록에 나타났다. 데이터는 마침내 제자리를 찾았다.
“됐어! 이제 제대로 들어온다.” 솔라는 안도의 한숨을 내쉬었다. 이제 데이터의 목적지를 완벽하게 통제할 수 있다는 자신감이 생겼다.
자신감을 회복한 솔라는 한 걸음 더 나아가기로 했다. “좋아, 그럼 이제 에이전트 버전 2를 테스트할 차례야. 프로젝트를 새로 만들어서 관리해야지.”
그녀는 다시 LangSmith UI로 가서 ‘My-Awesome-Agent-v2’라는 새 프로젝트를 생성했다. 그리고 조금 전과 똑같은 방식으로, 코드의 LANGSMITH_PROJECT 환경 변수 값을 “My-Awesome-Agent-v2”로 변경했다. 이제 실행만 하면 새로운 기록은 ‘v2’ 프로젝트에 쌓일 터였다. 솔라는 망설임 없이 코드 실행 버튼을 눌렀다.
하지만 그녀가 ‘My-Awesome-Agent-v2’ 프로젝트 페이지를 확인했을 때, 그곳은 또다시 텅 비어 있었다.
“어? 또?”
설마 하는 마음에 이전 프로젝트인 ‘My-Awesome-Agent’를 클릭하자, 방금 실행한 기록이 보란 듯이 그곳에 쌓여 있었다. 솔라는 머리를 감싸 쥐었다. 이번엔 프로젝트 이름을 분명히 똑같이 맞췄는데, 도대체 무엇이 잘못된 걸까? 마치 한번 정해진 목적지를 절대 바꾸지 않으려는 고집 센 택배 기사와 마주한 기분이었다.
3장: 설정 변경의 함정: ‘세션 다시 시작’의 필수성
솔라의 노트북 화면에는 두 개의 브라우저 탭이 나란히 열려 있었다. 왼쪽 탭은 ‘My-Awesome-Agent-v2’ 프로젝트 페이지로, ‘No traces found’라는 글씨만 덩그러니 떠 있었다. 오른쪽 탭은 이전 프로젝트인 ‘My-Awesome-Agent’ 페이지였고, 방금 솔라가 실행한 최신 기록이 보란 듯이 목록 맨 위에 자리 잡고 있었다.
솔라는 마우스를 움직여 두 탭을 번갈아 클릭했다. 왼쪽, 오른쪽, 다시 왼쪽. 현실을 부정하려는 듯한 반복적인 움직임이었다. 코드 편집기에는 분명히 새로운 목적지가 적혀 있었다.
# 에이전트 버전 2 테스트!
os.environ["LANGSMITH_PROJECT"] = "My-Awesome-Agent-v2"
그녀는 심지어 코드 셀을 한 번 더 실행해 보았다. 그리고 다시 에이전트를 실행했다. 결과는 같았다. 새로운 기록은 어김없이 ‘My-Awesome-Agent’ 프로젝트에 추가되었다. 코드 속의 주소 변경을 시스템이 완전히 무시하고 있었다.
“말도 안 돼….” 솔라는 혼잣말을 내뱉었다. “프로젝트 이름도 정확히 맞췄고, 코드도 바꿨는데 왜 계속 예전 프로젝트로 기록을 보내는 거야? 캐시 같은 게 있나?”
그녀의 초기 모델, 즉 코드를 수정하고 실행하면 변경 사항이 즉시 적용될 것이라는 믿음이 세차게 흔들리고 있었다. 마치 한번 입력된 주소는 절대 바꿀 수 없다고 선언하는 완고한 시스템 앞에서 어찌할 바를 모르는 기분이었다.
옆에서 이 모든 과정을 지켜보던 루나가 조용히 입을 열었다. “솔라, 그 설정 코드가 언제 컴퓨터의 기억 속으로 들어가는 걸까?”
“언제라니? 코드를 실행하는 순간 아니야?” 솔라가 즉답했다.
“그럼 첫 번째 실행을 생각해 봐. 네가 프로젝트를 ‘My-Awesome-Agent’로 설정하고 코드를 실행했지. 그때 컴퓨터는 그 주소를 기억했을 거야.” 루나는 코드 편집기의 실행 기록을 가리켰다. “그리고 넌 코드의 내용을 ‘v2’로 바꿨어. 그리고 다시 실행했지. 하지만 컴퓨터의 기억이 이미 기록된 주소를 지우고 새 주소로 덮어썼을까? 아니면 첫 번째 주소를 계속 붙들고 있는 걸까?”
루나의 질문은 솔라의 생각을 다른 방향으로 이끌었다. 그녀는 지금까지 코드의 ‘내용’에만 집중했다. 하지만 문제는 코드가 아니라, 그 코드를 읽어 들이는 ‘환경’ 자체에 있을지도 모른다는 생각이 들었다.
“한번 정해진 설정이… 바뀌지 않고 계속 유지된다는 거야?” 솔라가 되물었다. “마치 이 노트북 파일이 처음 열렸을 때의 설정을 그대로 간직하고 있는 것처럼?”
“그럴 가능성이 높지.” 루나가 고개를 끄덕였다. “우리가 사용하는 이런 개발 환경은 하나의 ‘세션(Session)‘으로 움직여. 세션이 시작될 때 환경 변수 같은 설정들을 한번 읽어서 메모리에 올려두고, 세션이 끝날 때까지 그 값을 유지하는 경우가 많아. 코드 셀을 다시 실행하는 건, 이미 시작된 세션 안에서의 작은 행동일 뿐, 세션 자체의 근본적인 설정을 바꾸진 못하는 거지.”
세션. 그 단어가 솔라의 머릿속에 박혔다. 그녀는 지금껏 독립된 코드 조각들을 실행한다고 생각했지만, 사실은 ‘세션’이라는 보이지 않는 커다란 방 안에서 모든 것을 실행하고 있었던 것이다. 그리고 그 방의 규칙(환경 설정)은 방에 처음 들어올 때 정해지는 것이었다.
“그럼 이 방을 나갔다가 다시 들어와야 한다는 뜻이네.”
솔라는 마침내 문제의 핵심을 파악했다. 그녀는 자신이 사용하던 코랩(Colab) 환경의 메뉴를 찾아 헤맸다. 그리고 ‘런타임’이라는 메뉴 아래에서 익숙하지만 한 번도 진지하게 생각해 본 적 없는 옵션을 발견했다.
[ 런타임 다시 시작 ]
이것이 바로 방을 나갔다가 다시 들어오는 버튼이었다. 솔라는 망설임 없이 ‘런타임 다시 시작’을 클릭했다. 실행 중이던 모든 것이 초기화되고, 깨끗한 상태의 세션이 새로 시작되었다.
이제 솔라의 움직임은 침착하고 단호했다. 그녀는 LANGSMITH_PROJECT가 “My-Awesome-Agent-v2”로 설정된 코드 셀을 실행했다. 새로운 세션은 이 설정을 첫 번째 기억으로 받아들였을 것이다. 이어서 그녀는 에이전트 실행 코드를 돌렸다.
그리고 떨리는 마음으로 LangSmith의 ‘My-Awesome-Agent-v2’ 프로젝트 탭을 새로고침했다.
화면 중앙에 새로운 추적 기록이 나타났다. 마침내 데이터가 올바른 목적지에 도착한 것이다.
“됐다!” 솔라가 외쳤다. “환경 설정은 세션 안에서 고정되는 거였어. 그래서 설정을 바꾸려면 반드시 세션을 다시 시작해야만 새로운 설정이 적용되는 거구나.”
이제야 모든 조각이 맞춰졌다. LangSmith로 데이터를 정확하고 신뢰성 있게 수집하기 위한 세 가지 핵심 원칙이 선명하게 보였다.
솔라는 키보드를 당겨 새로운 코드 셀을 열고, 주석을 달며 자신만의 규칙을 정리하기 시작했다. 이것은 미래의 자신이 같은 실수를 반복하지 않도록 남기는 이정표였다.
# [ LangSmith 관측을 위한 최종 체크리스트 ]
# 1. 추적 기능 활성화
# os.environ["LANGSMITH_TRACING"] = "true"
# 2. 데이터 목적지 지정 (UI의 프로젝트 이름과 100% 일치시킬 것!)
# os.environ["LANGSMITH_PROJECT"] = "Your-Project-Name"
# 3. **가장 중요**: 위 설정 값을 변경했다면,
# 반드시 [세션 다시 시작]을 통해 환경을 초기화하고 다시 실행해야 함!
그녀는 만족스러운 표정으로 자신이 작성한 체크리스트를 바라보았다. 더 이상 ‘스위치를 켜면 알아서 되겠지’라고 막연히 기대하지 않았다. 대신, 데이터가 자신이 원하는 바로 그곳에, 원하는 방식으로 정확하게 쌓이도록 만드는 구체적인 방법을 손에 쥐게 된 것이다. 이제 어떤 LLM 에이전트를 만들든, 관측 데이터를 길 잃게 만들 일은 없을 터였다.