Docker 경험 제로에서 모든 LLM 호출 추적하기: SigNoz를 활용한 관찰 가능한 AI 어시스턴트 구축
요약
Docker와 SigNoz를 활용하여 LLM 호출 과정을 추적하고 관찰 가능한 AI 어시스턴트를 구축하는 과정을 다룹니다. OpenTelemetry와 SigNoz를 통해 API 에러와 성능 지표를 시각화하는 실전적인 개발 경험을 공유합니다.
핵심 포인트
- SigNoz와 OpenTelemetry를 통한 LLM 관찰 가능성(Observability) 확보
- Docker 및 WSL2 환경에서의 SigNoz 설치 및 트러블슈팅 경험
- Llama-3.3-70b와 Groq을 활용한 AI 에이전트 구축
- API 호출 에러 및 성능 지표(Trace) 시각화의 중요성
불행히도, 방금 제 어시스턴트가 완전히 실패했습니다. 크래시(crash)도 발생하지 않았고, 터미널에 오류도 표시되지 않았습니다. 대신, 아주 조용한 응답만이 돌아왔습니다. 만약 몇 주 전 이런 일이 발생했다면, 저는 여기서 무슨 일이 일어났는지 설명할 방법이 전혀 없었을 것입니다. 하지만 이번에는 다른 탭에 열려 있는 트레이스(trace)를 통해 무엇이 잘못되었는지 정확히 확인할 수 있었습니다. 거의 510밀리초(milliseconds)가 소요된 단일 API 호출 내에서 401 에러가 발생한 것이었습니다.
이 트레이스가 바로 제가 지금 이 글을 쓰는 이유입니다. 3일 전만 해도 저는 Docker, OpenTelemetry, 또는 SigNoz를 다뤄본 적조차 없었습니다. AI 및 데이터 과학 전공 2학년 학생으로서, SigNoz 해커톤(Agents of SigNoz hackathon)을 위해 저는 단순히 LLM을 사용하는 수준을 넘어선 무언가를 개발하기로 결심했습니다.
사용 도구:
UI 구축을 위한 Streamlit, 응답 생성을 위한 Llama-3.3-70b-versatile 기반의 Groq, 각 요청의 성능 지표를 수집하고 분석하기 위한 OpenTelemetry, 트레이스(traces), 비용 및 에러를 시각화하기 위한 SigNoz (Foundry를 사용하여 셀프 호스팅), 그리고 모든 것을 로컬 Windows 머신에서 실행하기 위한 Docker+WSL2를 사용했습니다. 저는 제가 달성하고자 하는 목표와 완벽하게 일치하는 Track 01, AI & Agent Observability를 선택했습니다. 이는 단순히 LLM을 호출하는 것에 관한 것이 아니라, 그 동작을 관찰(observing)하는 것에 관한 것이었습니다.
SigNoz 설정하기: Docker와의 첫 번째 실전 전투
저는 SigNoz의 자체 퀵스타트(quickstart) 도구인 Foundry로 시작했습니다. 이는 단 한 번의 명령으로 SigNoz를 설치하고 실행할 수 있다고 약속했습니다. 이론적으로는 말이죠. 실제로는 그 단계에 도달하기도 전에 벽에 부딪혔습니다.
저는 Windows를 사용 중이었고, 이는 Docker가 직접 실행되는 것이 아니라 WSL2 내부에서 실행되어야 함을 의미했습니다. WSL2 자체를 설치하는 것은 순조로웠지만, 나중에 Python 가상 환경(virtual environment)을 만들려고 시도한 순간 python3-venv 패키지가 누락되었다는 오류가 발생했습니다. sudo apt install python3.12-venv 명령어를 통해 빠르게 해결할 수 있었습니다.
그다음은 SigNoz를 배포하기 위해 Foundry가 사용하는 설정 파일인 casting.yaml 파일 차례였습니다. 첫 번째 시도에서 "mapping values are not allowed in this context"라는 YAML 오류가 발생했습니다. nano 편집기로 수정하던 중 실수로 Ctrl+S를 눌렀는데, nano에서는 이 키가 저장 기능을 수행하지 않고 대신 터미널을 멈추게(freeze) 만듭니다. 사소한 문제였지만, 실제로 몇 분 동안 저를 정말 당황하게 만들었습니다.
YAML 오류를 수정하고 컨테이너를 빌드한 후에도 docker ps 결과는 비어 있었습니다. 이미지는 다운로드되었지만, 배포 과정에서 실제로 아무것도 시작되지 못한 채 조용히 실패한 상태였습니다. 생성된 배포 폴더 내부에서 docker compose up -d를 직접 실행하고 나서야 마침내 5개의 SigNoz 컨테이너가 모두 올라오고 'healthy' 상태로 표시되었습니다.
이 과정을 진행하던 도중, 제가 실제로 변경한 내용과는 무관하게 WSL 자체가 "Catastrophic failure" 오류와 함께 충돌했습니다. 잠시 동안 무언가 심각한 것을 망가뜨린 줄 알았습니다. PowerShell에서 wsl --shutdown을 실행하고 Ubuntu를 다시 여니 1분도 채 되지 않아 해결되었지만, 어떤 오류들은 여러분의 코드와 전혀 상관이 없다는 점을 상기시켜 주는 좋은 계기가 되었습니다.
"작동한다"에서 "작동하는 것을 볼 수 있다"로
Groq에 API 요청을 예약하고 응답을 받는 것은 어렵지 않았습니다. 이는 작업의 절반에 불과했습니다. OpenTelemetry는 전체 작업을 트레이스 (trace)와 스팬 (span)으로 나눕니다. 여기서 트레이스 (trace)는 API 요청의 전체 생명 주기를 설명하며, 스팬 (span)은 그 생명 주기 내의 각 개별 동작을 설명합니다. 저는 지속 시간 (duration), 토큰 수 (token count), 모델 이름과 같은 속성들을 추가하여 Groq API 요청을 캡슐화하기 위해 스팬 (span)을 적용했습니다.
코드에서 그것이 어떤 모습인지 대략적인 예시는 다음과 같습니다:
with tracer.start_as_current_span("call_groq_api") as api_span:
response = client.chat.completions.create(
model="llama-3.3-70b-versatile",
messages=st.session_state.messages
)
api_span.set_attribute("llm.tokens.total", response.usage.total_tokens)
SigNoz의 로컬 인스턴스에서 이 모든 것을 구현하는 데는 단 몇 줄의 코드만 필요했습니다. 애플리케이션을 쿼리할 때 SigNoz의 Traces Explorer(추적 탐색기)에 해당 레이블들이 처음으로 나타나는 것을 보며, 이 챌린지의 마법이 내 안에서 진정으로 살아 움직이는 것을 느꼈습니다.
질문 한 번의 비용 관찰하기
LLM 호출은 항상 어느 정도의 비용을 수반하지만, 이는 항상 암묵적입니다. 즉, 호출이 발생하는 과정을 직접 관찰할 수는 없습니다. 저는 이를 직접 눈으로 확인하고 싶었습니다.
Groq는 제 모델에 대해 입력 및 출력 토큰 100만 개당 각각 $0.59와 $0.79의 비용을 부과합니다. API 자체가 응답당 정확한 토큰 수를 제공하므로, 제가 해야 할 일은 그 수치에 요율을 곱하고 총액을 스팬 속성(span attribute)인 llm.cost.usd로 포함하는 것뿐이었습니다. 제가 던진 질문의 맥락에서 $0.0004와 같은 명확한 수치를 관찰하는 것은 "AI 호출에는 비용이 든다"는 사실을 내면화하는 데 도움이 되었습니다.
실제로 무언가 잘못되는 것을 확인하기
지금까지 제가 한 모든 작업은 모든 것이 어떻게 작동하는지에 대해 가르쳐 주었습니다. 하지만 제가 답을 얻고 싶었던 질문이 하나 더 있었는데, 바로 "SigNoz에서 무언가 잘못되면 어떻게 되는가?"였습니다.
그래서 저는 API 호출이 try/except 블록에 의해 캡슐화되도록 하여, 무언가 잘못되더라도 시스템이 충돌(crash)하지 않고 실패 내용이 스팬(span) 자체에 기록되도록 했습니다. 그런 다음 저는 의도적으로 제 API 키를 무효화했습니다.
이 실패는 즉시 SigNoz에 나타났습니다. call_groq_api 스팬에 빨간색 상자가 표시되었고, 상단에는 "Errors: 1"이라고 적혀 있었으며 상태는 "Error code: 401"이었습니다. 실패는 510밀리초(ms) 내에 발생했으며, 저는 추측할 필요 없이 그 이유를 정확히 알 수 있었습니다. 이 지점에서 저는 "AI 에이전트를 관찰할 수 없다면, 당신은 그것들을 소유하고 있는 것이 아니다"라는 말의 의미를 완전히 이해했습니다.
모든 것을 하나의 대시보드로 통합하기
개별 추적(individual traces)은 개별 요청을 상세히 파고드는 데 매우 훌륭했지만, 제가 정말로 필요했던 것은 더 넓은 맥락을 제공하는 하나의 대시보드였습니다. 즉, "이 애플리케이션이 시간이 지남에 따라 어떻게 성능을 내는가?"에 대한 답이었습니다.
이 목표를 달성하기 위해, 저는 LLM 호출의 평균 지연 시간 (average latency), 시간에 따른 발생 비용 (cost incurred over time), 그리고 실패한 호출 횟수 (number of failed calls)라는 세 가지 그래프로 구성된 SigNoz 대시보드를 만들었습니다. 비용 그래프를 연결하는 과정에서는 제가 설정한 커스텀 속성인 llm.cost.usd를 찾아야 했습니다. 이는 SigNoz의 기본 필드가 아니었기 때문이지만, SigNoz는 이를 처리하는 데 아무런 문제가 없었습니다.
이전에 제가 API 키를 잘못 설정했을 때와 정확히 같은 순간에 에러 횟수가 갑자기 치솟는 것을 보는 것은 매우 만족스러운 경험이었습니다.
배운 점 (What I Learned)
우선, 저는 AI가 ChatGPT와 같은 모델이 제공하는 답변보다 질이 떨어지는, 다소 뻔한 답변만을 내놓을 것이라고 생각했습니다. 하지만 그렇지 않았습니다. Groq 프로세싱을 사용하는 Llama-3.3은 일관성 있고 논리적인 답변을 내놓았습니다.
과거의 저에게 한 가지 조언을 해줄 수 있다면, 도구를 미리 알지 못한다는 사실에 너무 개의치 말라는 것입니다. Docker, OpenTelemetry, 그리고 SigNoz는 해커톤 기간 동안 완전히 새로 배운 것들입니다. 이는 가능함을 증명했지만, 제가 희망했던 것만큼 빠르지는 않았습니다. 만약 여러분도 같은 계획을 세우고 있다면 한 가지 기억해야 할 점이 있습니다. nano 편집기에서는 Ctrl+S가 저장 기능을 하지 않고 터미널을 멈추게(freeze) 만듭니다. 이 때문에 5분 정도 혼란을 겪었습니다.
결론 (Conclusion)
불과 사흘 전만 해도 저는 스팬 (span)이 무엇인지 설명하지 못했을 것입니다. 하지만 이제 저는 LLM 호출의 추적 가능한 부분, 비용, 실패, 그리고 모든 질문이 어떻게 작동하는지에 대해 모든 것을 알고 있는 AI 어시스턴트를 갖게 되었습니다. 제 생각에 이 두 상태 사이의 차이야말로 이번 해커톤의 가장 멋진 점입니다.
SigNoz 설정 구성을 포함한 전체 코드는 GitHub에서 확인할 수 있습니다: https://github.com/saheli-roy-commits/ai-agent-observability-signoz
투명성을 위해 밝힙니다: 저는 이러한 도구들을 단계별로 배우고, 발생하는 에러를 디버깅하며, 이 글의 구조를 잡는 데 Claude의 도움을 받았습니다. 하지만 실수와 결정, 그리고 이를 구축한 실제 경험은 모두 저의 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기