Voice AI 트레이싱의 어려움: OpenTelemetry와 SigNoz를 사용하여 스트리밍 LLM에 계측(Instrumentation)을
요약
Voice AI 에이전트의 스트리밍 지연 문제를 해결하기 위해 OpenTelemetry와 SigNoz를 활용한 커스텀 계측 레이어 구축 방법을 다룹니다. 비동기 제너레이터 기반의 LLM 스트리밍 환경에서 발생하는 기존 APM의 한계를 극복하고, TTFA(Time To First Audio)와 같은 특화 메트릭을 추적하는 기술적 과정을 설명합니다.
핵심 포인트
- 비동기 스트리밍 LLM 환경에서는 일반적인 자동 계측이 작동하지 않음
- OpenTelemetry를 활용한 수동 계측으로 스트리밍 라이프사이클 추적 가능
- Voice AI 특화 메트릭(TTFA 등) 및 토큰 비용 추적 구현
- SigNoz와 ClickHouse를 연동한 커스텀 메트릭 시각화 방법
Voice AI 에이전트는 지연(lagging)이 발생하기 전까지는 마법 같습니다. 갑자기 Groq 기반의 제 음성 비서가 응답하는 데 3초가 걸리기 시작했고, 표준 APM 도구들은 무용지물이었습니다. 그 도구들은 HTTP 요청이 열려 있다는 것은 알려줄 수 있었지만, 병목 현상이 Speech-to-Text (STT) 전사(transcription) 때문인지, LLM의 첫 번째 토큰까지의 시간 (Time to First Token, TTFT) 때문인지, 아니면 Text-to-Speech (TTS) 합성 때문인지는 알려주지 못했습니다.
저는 스트리밍 Voice AI를 트레이싱(tracing)하고, FinOps 토큰 비용을 추적하며, 모든 것을 SigNoz에서 네이티브하게 시각화하는 커스텀 OpenTelemetry 계측(instrumentation) 레이어인 Zooid를 구축하여 이 문제를 해결했습니다.
과제: 웹 트레이싱인가, Voice 트레이싱인가
전형적인 CRUD 웹 애플리케이션은 트레이싱하기 쉽습니다. 요청이 들어오고, 데이터베이스를 거쳐, JSON 응답을 반환합니다. opentelemetry-instrumentation-flask 패키지를 추가하기만 하면 끝납니다.
Voice AI는 완전히 다른 차원의 문제입니다. 하나의 "Voice Turn"은 다음과 같이 구성됩니다:
- 원시 오디오 스트림 획득 (STT).
- LLM에서 생성되는 대로 텍스트 토큰을 스트리밍.
- 해당 텍스트 청크(chunks)를 다시 오디오 스트림으로 즉시 변환 (TTS).
- 사용자의 갑작스러운 중단(barge-in) 관리.
이 지점에서 일반적인 자동 계측(auto-instrumentation)은 실패합니다. LLM이 정적 응답이 아닌 비동기 제너레이터(async generator)를 반환하기 때문입니다. 단순히 함수 실행을 트레이싱하면, 스팬(span)은 스트림이 실제로 종료될 때가 아니라 제너레이터가 생성될 때 종료됩니다.
저는 스트리밍 대화의 라이프사이클을 수동으로 추적하고, Voice 특화 메트릭(예: 첫 번째 오디오까지의 시간 (Time To First Audio))을 측정하며, 이 데이터를 SigNoz로 가져올 방법이 필요했습니다.
1단계. 비동기 스트림 계측하기
저는 스트리밍 문제를 해결하기 위해 OpenTelemetry 트레이서(tracer)를 수동으로 관리하는 커스텀 Python 데코레이터(@instrument_voice_app)를 만들었습니다.
함수를 래핑(wrapping)하는 대신, 에이전트(agent)에 VoiceTurnContext 객체를 주입합니다. 이를 통해 내부 AI 로직이 초기 오디오 바이트가 사용자에게 스트리밍되는 시점과 같이 특정 마일스톤(milestone)이 달성되었을 때를 정확히 알려줄 수 있습니다.
스트림을 추적하기 위해 사용한 기본 로직은 다음과 같습니다:
from opentelemetry import trace
import time
...
이를 통해 전체 사용자 상호작용을 아우르는 아름답고 연속적인 트레이스(trace)를 얻을 수 있었지만, 새로운 문제에 직면했습니다: 이 커스텀 속성(custom attributes)들을 SigNoz에서 어떻게 시각화할 것인가?
2단계: ClickHouse의 "함정" (그리고 해결 방법)
OpenTelemetry Collector (localhost:4317)로 데이터를 보내는 것은 매우 쉬웠습니다. SigNoz의 "Traces" 탭에서 트레이스를 즉시 확인할 수 있었습니다.
하지만 "Dashboards" 탭으로 이동하여 voice.turn_cost_usd 및 voice.ttfa_ms에 대한 시계열(Time-Series) 패널을 생성하려고 시도했을 때, 대시보드에는 **"No Data"**라고 표시되었습니다.
문제점
이 과정에서 SigNoz가 ClickHouse에 OpenTelemetry 속성을 저장하는 방식에 대한 매우 중요한 정보를 발견했습니다.
Python에서 span.set_attribute("voice.ttfa_ms", 450.5)를 실행하면 OpenTelemetry는 이를 숫자(Number)로 전송합니다. 이는 새로운 SigNoz 트레이스 스키마(signoz_index_v3)에 깔끔하게 저장됩니다. 하지만 SigNoz의 커스텀 대시보드 쿼리 빌더(query builder)는 이러한 속성들을 효과적으로 집계(aggregate)하기 위해 속성들을 올바르게 매핑하는 구체화된 뷰(materialized views)에 의존합니다.
-- 구체화된 뷰(Materialized View)에서 누락된 결정적인 라인들
attributes_number as numberTagMap,
attributes_bool as boolTagMap
이 수정 사항을 적용하고 인덱스를 백필(backfill)하자, 쿼리 빌더가 즉시 저의 커스텀 메트릭(metrics)을 인식했습니다. SigNoz UI에서 numberTagMap.voice.turn_cost_usd를 숫자 데이터 타입으로 직접 쿼리할 수 있게 되었습니다.
3단계: Staff SRE 대시보드 구축
데이터가 올바르게 흐르기 시작하면서, 저는 Voice AI에 완전히 특화된 커스텀 JSON 대시보드를 SigNoz에 구축했습니다.
(함께 따라 하고 계신다면, Dashboards 탭에서 "Import JSON"을 클릭하여 이 내용을 SigNoz 인스턴스에 직접 가져올 수 있습니다).
일반적인 CPU 사용량을 보는 대신, 제 대시보드는 다음 항목들을 추적합니다:
- P99 TTFA (Time To First Audio): 대화형 AI (Conversational AI)에서 가장 중요한 지표입니다. 이 수치가 800ms를 넘어가면 사용자는 봇이 고장 났다고 생각합니다.
- FinOps / Turn당 비용 (Cost Per Turn):
voice.turn_cost_usd속성을 집계하여 Groq/OpenAI 토큰 지출을 실시간으로 추적합니다. - Barge-in Rate (끼어들기 비율):
voice.interrupted불리언 (Boolean) 속성을 추적하여 사용자가 얼마나 자주 AI의 말을 끊는지 확인합니다 (봇이 너무 말을 많이 하는지를 판단할 수 있는 훌륭한 지표입니다).
[IMG:1] (여기에 TTFA 시계열 및 FinOps 비용 패널을 보여주는 SigNoz 대시보드 스크린샷 삽입)
AI 환각(Hallucination) 및 지연 시간(Latency)에 대한 알림 설정
마지막으로, 저는 Staff SRE처럼 동작하기 위해 SigNoz의 알림 규칙 (Alert Rules)을 활용했습니다. P99 TTFA > 1000ms 상태가 5분 이상 지속될 경우 트리거되는 알림을 설정했습니다.
LLM_PROVIDER를 스팬 (Span) 속성(예: Groq vs OpenAI)으로 주입했기 때문에, 알림 페이로드(Payload)를 통해 어떤 제공자가 지연되고 있는지 즉시 알 수 있으며, 이를 통해 팀이 즉각적으로 트래픽을 폴백 (Fallback) 제공자로 라우팅할 수 있습니다.
배운 점
- OpenTelemetry는 믿을 수 없을 정도로 확장성이 뛰어납니다. HTTP 스팬 (Spans)에만 국한되지 않습니다. 컨텍스트 (Context)를 수동으로 관리함으로써, 사실상 거의 모든 비동기 스트리밍 워크플로우를 추적 (Trace)할 수 있습니다.
- ClickHouse 스키마 (Schema)를 파악하세요. 커스텀 속성이 SigNoz 대시보드에 나타나지 않는다면, 기반이 되는 ClickHouse 테이블에서 숫자 (Number) 및 불리언 (Boolean) 태그가 어떻게 매핑되고 있는지 확인하십시오.
- Voice AI에는 FinOps 트레이싱 (Tracing)이 필요합니다. 이제 지연 시간만 추적하는 것으로는 부족합니다. 토큰 비용(
voice.turn_cost_usd)을 트레이스 스팬 (Trace Span)에 직접 주입하면, 느린 성능과 높은 추론 비용 사이의 상관관계를 직접 연결할 수 있습니다.
결론
표준 APM (Application Performance Monitoring)은 생성형 AI (Generative AI) 시대를 위해 구축되지 않았지만, OpenTelemetry와 SigNoz는 여러분이 정확히 필요로 하는 것을 구축할 수 있는 기본 요소 (Primitives)를 제공합니다. 비동기 제너레이터 (Async Generators)를 수동으로 계측 (Instrumenting)하고 ClickHouse에서 커스텀 숫자 속성 (Custom Number Attributes)을 매핑함으로써, 우리는 블랙박스 상태였던 Voice AI 스크립트를 완전히 관측 가능한 (Observable) 엔터프라이즈급 시스템으로 탈바꿈시켰습니다.
여러분의 Voice AI 프로젝트에 직접 시도해보고 싶다면, PyPI에서 Python SDK를 가져오거나, npx create-zooid (npm)를 통해 전체 보일러플레이트 (Boilerplate) 앱을 생성하거나, GitHub에서 전체 코드를 확인할 수 있습니다.
WeMakeDevs & SigNoz Observability Hackathon을 위해 제작되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기