멋진 데모에서 실서비스까지: New Relic으로 AI 여행 에이전트를 신뢰하게 만든 비결
출처: Dev.to
모든 AI 데모를 무너뜨리는 질문
장면을 상상해 보세요. 여러분은 여행 계획 스타트업을 막 설립했습니다—이름은 WanderAI라고 합시다. 피치는 간단하면서도 매력적입니다: 고객이 “일본에서 10일, 중간 예산, 미식가, 인파를 싫어함”이라고 입력하면 AI 에이전트가 몇 초 만에 완벽한 일정표를 만들어 줍니다. 데모는 눈부시게 빛납니다. 투자자들은 귀를 기울이고, 공동 창업자는 이미 출시 트윗을 초안하고 있습니다.
그때 방 뒤쪽에 있던 누군가—운영팀일 수도, 아니면 조심스러운 플랫폼 책임자일 수도—가 모든 AI 데모를 무너뜨리는 질문을 던집니다:
“실제로 제대로 동작하고 있는지 어떻게 알 수 있나요?”
“서버가 켜져 있는가?”도, “모델이 응답하고 있는가?”도 아니라, 그 아래에 숨겨진 네 가지 불편한 질문입니다:
🔍 에이전트가 좋은 추천을 하고 있나요?
⚡ 응답 속도는 어떨까요?
🚨 문제가 발생했을 때 디버깅할 수 있나요?
✅ 일정이 실제로 괜찮은가요?
데모는 이 질문들에 답할 필요가 없습니다. 하지만 프로덕션 AI 서비스는 반드시 답해야 합니다.
이 글은 WanderAI에 네 가지 질문 모두에 답하도록 관측성을 입힌 과정—Microsoft Agent Framework, OpenTelemetry, New Relic을 사용한 사례—을 이야기합니다. 또한 오후에 직접 실행해 볼 수 있는 오픈소스 “What The Hack” 랩의 흐름이기도 합니다. 여덟 가지 도전 과제, 여섯 개의 행위. 시작합니다.
WanderAI의 첫 번째 버전은 Flask 웹 앱입니다. 고객은 양식(여행 날짜, 기간, 관심사, 특수 요청)을 작성하고, Microsoft Agent Framework의 ChatAgent가 일정을 만들어 줍니다. 에이전트는 세 가지 도구를 사용할 수 있습니다:
get_random_destination()– 목적지를 검증하거나 선택get_weather()– 특정 위치의 현재 날씨를 가져옴get_datetime()– “현재” 시점을 일정에 고정
작동합니다. 심지어 꽤 잘 동작합니다. 하지만 에이전트를 사용자 앞에 두면 관측성의 중요성이 달라집니다. 에이전트는 단일 LLM 호출이 아니라, 언제 도구를 호출할지, 어떤 도구를 사용할지, 도구의 출력을 어떻게 해석할지, 사용자에게 무엇을 말할지 결정하는 작은, 의견이 강한 추론 엔진이기 때문입니다.
즉,
- 지연 시간은 여러 출처에서 발생합니다. LLM 때문인가? 도구 호출 때문인가? 콜드 스타트 때문인가? 네트워크 홉 때문인가?
- 출력은 비결정적입니다. 같은 입력이 두 개의 다른 일정을 만들 수 있습니다. “깨졌다”와 “그냥 안 좋은 날”은 겉으로는 구분이 안 됩니다.
- 실패가 숨겨집니다.
get_weather()가 엉터리 데이터를 반환하면, 에이전트는 그 데이터를 바탕으로 일정을 만들어 버릴 수 있습니다. 예외가 발생하지 않으며, 단지 더 나쁜 여행이 될 뿐입니다.
print()만으로는 해결되지 않습니다. 추적(trace), 메트릭, 구조화된 로그가 필요하고, 이들이 서로 연관돼야 합니다. 이제 불을 켤 시간입니다.
놀라웠던 점: Agent Framework 앱에 기본 관측성을 적용하는 데는 두 줄의 코드만 필요합니다.
Microsoft Agent Framework는 이미 OpenTelemetry GenAI 시맨틱 규약을 따르는 추적, 로그, 메트릭을 내보냅니다. 에이전트 오케스트레이션, 도구 호출, 모델 호출—모두 자동으로 계측됩니다. 여러분은 단지 익스포터를 연결하면 됩니다.
from agent_framework.observability import configure_otel_providers
# 먼저 콘솔 익스포터 – 로컬에서 확인
configure_otel_providers()
요청을 실행하면 콘솔에 구조화된 스팬이 가득 출력됩니다. 터미널에서 먼저 확인하는 데 30초면 충분합니다; 나중에 OTLP 엔드포인트가 없어서 고생하는 것보다 훨씬 빠릅니다.
이게 정상 작동한다면 OTLP로 전환하고 New Relic을 지정합니다:
# .env
OTEL_SERVICE_NAME=WanderAI
OTEL_EXPORTER_OTLP_ENDPOINT=
OTEL_EXPORTER_OTLP_HEADERS=api-key=YOUR_NEW_RELIC_LICENSE_KEY
몇 분 뒤, WanderAI가 APM & Services → Services → OpenTelemetry 뷰에 나타납니다. Distributed Tracing을 열면 invoke_agent travel_planner 같은 트레이스 그룹을 찾을 수 있습니다. 클릭하면 전체 에이전트 여정이 펼쳐집니다: 오케스트레이션 스팬, 각 도구 호출, LLM 왕복, 응답까지. 로그는 자동으로 스팬에 연결되고, 메트릭도 곧 들어옵니다.
이것이 전체 기본선이며, 대부분의 프로덕션 AI 앱이 제공하는 것보다 이미 더 많은 가시성을 제공합니다. 하지만 기본선만으로는 부족합니다. 자동 계측은 에이전트가 무엇을 했는지만 알려줍니다. 비즈니스에 대한 정보는 전혀 알 수 없습니다.
자동 계측은 전체 그림의 약 60% 정도만 제공합니다. 나머지 40%는 여러분의 코드에 있습니다: 라우트 핸들러, 도구 래퍼, 검증, “에이전트가 실행됨”을 “3.2초 안에 미식가를 위한 7일 도쿄 여행이 계획됨”으로 바꾸는 속성 등.
우리는 각 도구 함수와 /plan 라우트 주위에 비즈니스 의미가 있는 속성을 포함한 커스텀 스팬을 추가했습니다:
from agent_framework.observability import get_tracer
tracer = get_tracer(**name**)
def get_weather(location: str) -> dict:
with tracer.start_as_current_span("get_weather") as span:
span.set_attribute("travel.location", location)
weather = fetch_weather(location)
span.set_attribute("weather.condition", weather["condition"])
span.set_attribute("weather.temp_c", weather["temp_c"])
return weather
몇 가지 강조하고 싶은 점:
- 속성은 금입니다.
travel.location,weather.condition,trip.duration_days등은 나중에 필터링, 그룹화, 알림을 설정할 때 사용되는 차원입니다. 넉넉히 추가하세요. 비용은 거의 들지 않으며 복리 효과를 냅니다. - 스팬 상태도 중요합니다. 도구가 실패하면 스팬을 오류 상태로 표시해 주세요. 그래야 오류율 대시보드가 HTTP 500만이 아니라 도구 수준 실패까지 반영합니다.
- Trace‑correlated logging이 루프를 닫아줍니다. 로거가 현재 스팬 컨텍스트를 잡으면 모든 로그 라인에
trace_id와span_id가 자동으로 붙습니다. New Relic에서 스팬을 클릭하면 관련 로그가 인라인으로 표시됩니다. 디버깅이 “로그를 grep”하는 것이 아니라 “트레이스를 따라가는” 방식으로 바뀝니다.
이 레이어가 배포되면 New Relic에 새로운 트레이스 그룹 plan_trip이 나타납니다. 단일 트레이스를 파고들면 Agent Framework 스팬 안에 여러분의 커스텀 스팬이 중첩된 모습을 볼 수 있습니다—속성까지 모두 포함해서. 이제 에이전트가 실행되는 모습을 보는 것이 아니라, 여러분의 애플리케이션이 에이전트를 실행하는 모습을 보는 겁니다.
Telemetry ≠ Observability. 데이터베이스에 저장된 텔레메트리는 비싼 잡담에 불과합니다. 관측성은 그 위에 구축되는 시스템—당직자가 보는 대시보드, 알림, 사용자에게 약속한 SLO를 충족하는지 판단하는 지표—을 의미합니다.
그래서 우리는 다음 레이어를 만들었습니다:
“WanderAI Agent Performance” 대시보드
시작을 위한 다섯 개 위젯: 요청률, 오류율, p95 응답 시간, 도구 사용 비율, 토큰‑비용 집계. 모든 위젯은 NRQL로 구동됩니다. 예를 들어, 도구별 사용량은 다음과 같습니다:
SELECT count(*) FROM Span
WHERE service.name = 'WanderAI'
AND name IN ('get_weather', 'get_random_destination', 'get_datetime')
FACET name TIMESERIES
신호에만 반응하는 알림
노이즈가 아닌 실제 신호에만 알림이 울리도록 설정했습니다. 시작 단계에서는 두 개만 만들었습니다: 5분 동안 오류가 5건 이상 발생, p95 지연 시간이 25초 초과. 처음엔 보수적인 임계값을 두고, 시스템 정상 범위를 파악하면서 점점 조정했습니다.
대화를 바꾸는 SLO
가용성 SLO를 99.5% (