에이전트 트레이스는 왜 로그가 아니라 실행 그래프일까
에이전트가 틀렸을 때 로그를 더 찍자만으로는 왜 원인을 찾기 어려울까?
에이전트 트레이스는 왜 로그가 아니라 실행 그래프일까
- 카테고리: ai_dev
- 예상 읽기 시간: 10분
- 오늘의 질문: 에이전트가 틀렸을 때 “로그를 더 찍자”만으로는 왜 원인을 찾기 어려울까?
- 핵심 출처:
- Tracing - OpenAI Agents SDK - 게시일 미표기, 2026-07-09 확인
- OpenTelemetry GenAI Semantic Conventions: GenAI spans - 개발 중 문서, 2026-07-09 확인
- LangSmith Observability - 게시일 미표기, 2026-07-09 확인
- Observability for agents - Google Agent Development Kit - 게시일 미표기, 2026-07-09 확인
- Instrument ADK applications with OpenTelemetry - 게시일 미표기, 2026-07-09 확인
1. 왜 지금 봐야 하나
LLM 앱이 단순 “질문 하나 → 답변 하나”일 때는 요청 로그와 응답 로그만으로도 꽤 많은 문제를 찾을 수 있다. 하지만 에이전트가 들어가면 상황이 달라진다. 하나의 사용자 요청 안에서 모델 호출, 검색, 도구 실행, guardrail 검사, handoff, 재시도, 스트리밍, 백그라운드 작업이 섞인다. 사용자는 “답이 이상하다”고 말하지만, 개발자는 어느 단계가 잘못됐는지 알아야 한다.
최근 주요 에이전트 도구들이 공통적으로 강조하는 기능이 바로 tracing이다. OpenAI Agents SDK는 agent run 중 LLM generation, tool call, handoff, guardrail, custom event를 trace와 span으로 기록한다고 설명한다. Google ADK도 agent observability에서 reasoning trace, tool call, latent model output을 보기 위해 logging, metrics, traces를 제공한다고 설명한다. LangSmith는 개별 trace부터 production-wide performance metrics까지 LLM 앱을 관찰하는 도구라고 소개한다. OpenTelemetry는 GenAI용 span 의미 규칙을 따로 정리하며 chat, embeddings, retrieval, execute_tool, invoke_agent, invoke_workflow, search_memory 같은 operation 이름을 표준화하려고 한다.
이 흐름의 핵심은 “로그를 많이 남기자”가 아니다. 에이전트의 실행을 시간 순서 문자열이 아니라 부모-자식 관계가 있는 실행 그래프로 남기자는 것이다.
2. 핵심 개념
로그(log)는 보통 한 시점에 발생한 사건의 문장이다. “tool A 호출”, “모델 응답 완료”, “검색 결과 5개”처럼 남긴다. 로그는 필요하지만, 에이전트 디버깅에는 약점이 있다. 사건들이 어떤 사용자 요청에 속했는지, 어떤 모델 호출이 어떤 도구 호출을 유발했는지, 재시도가 같은 논리 작업 안에 포함되는지, 병렬 작업 중 어느 가지가 느렸는지를 로그 문자열만으로 재구성해야 한다.
트레이스(trace)는 하나의 end-to-end 작업을 나타낸다. 예를 들어 “고객 문의 답변 생성”이라는 워크플로 전체가 하나의 trace다. 스팬(span)은 그 안의 개별 작업이다. “RAG 검색”, “LLM 초안 생성”, “정책 guardrail 검사”, “CRM 조회 도구 실행”, “최종 답변 생성”이 각각 span이 된다. span에는 시작/종료 시간, parent id, trace id, 오류, 입력·출력 또는 요약 메타데이터가 붙는다.
그래서 trace를 보면 단순히 “무슨 일이 있었는가”가 아니라 다음을 볼 수 있다.
- 이 사용자 요청이 어떤 하위 작업으로 쪼개졌는가
- 어떤 span이 어떤 span을 호출했는가
- 어디서 시간이 많이 걸렸는가
- 어디서 실패했거나 재시도했는가
- 어떤 모델·도구·검색 소스가 사용됐는가
- 같은 conversation의 여러 trace를 어떻게 묶을 수 있는가
OpenAI Agents SDK 문서의 trace/span 설명도 이 구조와 같다. Trace는 workflow 하나를 나타내고, span은 시작·종료 시간과 parent 관계를 가진 operation이다. 기본적으로 agent 실행, LLM generation, function tool call, guardrail, handoff가 각각 span으로 잡힌다. Google ADK 문서도 span의 계층적 waterfall view가 agent reasoning loop와 tool execution의 관계를 보여준다고 설명한다.
3. 최신 이슈와 연결
중요한 변화는 provider별 tracing 기능이 생기는 동시에, OpenTelemetry 같은 공통 규칙도 같이 커지고 있다는 점이다.
OpenAI Agents SDK는 자체 Traces dashboard로 agent workflow를 디버그하고 모니터링할 수 있게 한다. 동시에 custom trace processor를 통해 OpenAI backend 대신 또는 추가로 다른 destination에 trace를 보낼 수 있다고 설명한다. 즉 “SDK 안의 디버그 기능”으로 시작하지만, 운영 환경에서는 기존 observability stack과 연결해야 할 가능성이 높다.
Google ADK는 OpenTelemetry를 더 전면에 둔다. ADK observability 문서는 OTel 기반 tracing을 설정하고, Google Cloud 문서는 ADK 1.17.0 이상에서 OpenTelemetry와 Google Cloud Observability로 telemetry를 보낼 수 있다고 설명한다. 특히 OTEL_SEMCONV_STABILITY_OPT_IN='gen_ai_latest_experimental' 설정으로 최신 GenAI semantic conventions를 사용하라고 안내한다. 또 ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS='false'를 권장하면서 span attribute 크기 제한과 PII 노출 위험을 줄이라고 말한다.
OpenTelemetry GenAI semantic conventions 문서는 아직 development 상태지만 방향은 분명하다. GenAI span은 호출자 관점에서 본 “논리 작업”이어야 하고, 자동 재시도가 있으면 같은 span이 논리 작업 전체와 모든 retry 시간을 포함해야 한다. 또 gen_ai.provider.name, gen_ai.operation.name, gen_ai.request.model, gen_ai.tool.name 같은 attribute를 통해 provider, model, tool, retrieval, memory 작업을 비교 가능한 형태로 남기려 한다.
LangSmith도 같은 문제를 제품 관점에서 푼다. trace를 보고, production metrics를 모니터링하고, feedback과 online evaluation으로 이어지게 만든다. 여기서 관찰성은 단순 장애 대응이 아니라 eval과 운영 품질 루프의 입력 데이터가 된다.
4. 개발자 관점 해석
에이전트 trace를 설계할 때 가장 먼저 버려야 할 생각은 “모든 프롬프트와 응답을 그대로 저장하면 디버깅이 쉬워진다”이다. 단기적으로는 편해 보이지만 운영에서는 비용, 개인정보, 보안, 보존 기간, sampling 문제가 바로 터진다. Google Cloud 문서가 메시지 내용을 span attribute에 붙이지 말라고 권장하는 이유도 여기에 있다. trace는 원문 저장소가 아니라 실행 구조와 판단 단서를 남기는 장치여야 한다.
개발자에게 실용적인 기준은 네 가지다.
첫째, trace boundary를 정한다. 사용자 메시지 하나가 trace인지, 백그라운드 job 하나가 trace인지, 여러 run을 하나의 workflow trace로 묶을지 결정해야 한다. OpenAI 문서처럼 여러 run()을 하나의 withTrace()로 묶어야 하는 경우가 있다. 예를 들어 “초안 작성 → 자기 평가 → 수정”은 세 번의 모델 호출이지만 하나의 논리 작업일 수 있다.
둘째, span taxonomy를 정한다. 최소한 model call, retrieval, tool execution, guardrail, handoff, memory operation, custom business step은 분리하는 것이 좋다. OpenTelemetry가 retrieval, execute_tool, invoke_agent, invoke_workflow, search_memory 같은 operation 이름을 제안하는 이유는 나중에 provider와 프레임워크가 바뀌어도 같은 대시보드에서 비교하기 위해서다.
셋째, payload와 metadata를 분리한다. span에 원문 prompt 전체를 넣을지, hash·길이·토큰 수·template version만 넣을지 정책이 필요하다. 운영 기본값은 보수적이어야 한다. 사용자 원문, 문서 chunk, 도구 결과에는 PII와 secret이 섞일 수 있다. 대신 다음 같은 metadata는 상대적으로 안전하고 유용하다.
- workflow name
- conversation/session id 또는 group id
- model name
- prompt template name/version
- input/output token 수
- retrieval top_k, data_source id
- tool name, tool type, success/failure
- guardrail decision
- retry count
- latency_ms
- error.type
넷째, trace를 eval과 연결한다. 실패한 답변만 모아도 부족하다. 어떤 retrieval span에서 recall이 낮았는지, 어떤 tool span에서 validation error가 났는지, 어떤 model span에서 output format이 깨졌는지 trace 단위로 샘플링해야 다음 eval dataset이 만들어진다. trace는 사후 디버깅 화면이면서 미래 테스트 케이스의 원천이다.
5. 내 프로젝트에 적용할 체크포인트
작은 팀이라면 처음부터 거대한 observability 플랫폼을 붙일 필요는 없다. 대신 아래 다섯 가지를 먼저 정하면 된다.
- Trace 이름: 제품 기능 기준으로 붙인다. 예:
support_answer_workflow,code_review_agent,invoice_extraction. - Group id: 같은 대화나 같은 job에 속한 여러 trace를 묶을 id를 남긴다.
- 필수 span:
model.generate,retrieval.search,tool.execute,guardrail.check,memory.search/upsert정도부터 시작한다. - 민감정보 정책: 원문 저장은 기본 off로 두고, 필요하면 개발 환경 또는 consent가 있는 샘플에서만 켠다.
- 실패 라벨: span error뿐 아니라 “정답은 나왔지만 근거가 약함”, “도구는 성공했지만 느림”, “guardrail이 과하게 차단함” 같은 제품 품질 라벨을 붙인다.
흔한 실패 모드는 다음과 같다.
- 로그만 있고 관계가 없음: tool call 로그는 있는데 어떤 model output이 그 tool을 요청했는지 모른다.
- trace가 너무 잘게 쪼개짐: 모델 호출마다 별도 trace가 생겨 workflow 전체 지연과 실패 원인을 못 본다.
- 원문을 과하게 저장함: 디버깅 편의 때문에 PII, customer document, secret이 trace backend에 흘러간다.
- 표준 attribute를 안 씀: provider가 바뀌면 대시보드와 쿼리를 다시 만들어야 한다.
- eval과 분리됨: trace는 많은데 실패 케이스를 평가 데이터로 승격하는 루프가 없다.
6. 오늘 10분 액션
오늘은 코드를 많이 바꾸지 말고, “내 에이전트의 실행 그래프”를 종이에 그린다고 생각하면 된다.
- 운영 중이거나 만들고 싶은 AI 기능 하나를 고른다.
- 사용자 요청 하나가 끝날 때까지의 단계를 5~8개 span으로 나눈다.
- 각 span에 붙일 metadata를 5개 이하로 고른다.
- 절대 저장하면 안 되는 원문/필드를 따로 표시한다.
- 마지막으로 실패했을 때 보고 싶은 질문 3개를 적는다.
예시는 이렇게 시작할 수 있다.
| Span | Parent | 꼭 남길 metadata | 저장 금지/주의 |
|---|---|---|---|
support_answer_workflow | root | workflow_name, group_id, user_tier | 사용자 전체 대화 원문 |
retrieval.search | workflow | data_source_id, top_k, result_count, latency_ms | 검색 문서 원문 전체 |
model.generate_draft | workflow | model, prompt_version, input_tokens, output_tokens | prompt 원문, 고객 PII |
tool.execute_crm_lookup | draft | tool_name, success, error.type, latency_ms | access token, 고객 상세정보 |
guardrail.check | workflow | policy_name, decision, reason_code | 민감 원문 |
model.final_answer | workflow | model, output_tokens, finish_reason | 최종 답변 원문은 정책에 따라 |
10분 액션의 목표는 “관측성을 붙였다”가 아니다. 목표는 다음 장애 때 “왜 틀렸지?”라는 막막한 질문을 “검색이 틀렸나, 도구가 실패했나, 모델이 지시를 무시했나, guardrail이 막았나?”로 쪼갤 수 있게 만드는 것이다.
7. 더 볼 자료
- OpenAI Agents SDK Tracing 문서: trace와 span의 기본 구조, agent/tool/guardrail/handoff span, custom processor와 flush 이슈를 확인할 수 있다.
- OpenTelemetry GenAI Semantic Conventions: provider, model, operation, retrieval, tool, memory 관련 attribute를 표준 이름으로 남기려는 방향을 볼 수 있다.
- LangSmith Observability: trace, monitoring, feedback, online evaluation을 운영 루프로 연결하는 제품 관점을 볼 수 있다.
- Google ADK Observability 및 Google Cloud ADK instrumentation 문서: OpenTelemetry 기반 agent tracing과 민감정보 캡처 정책을 확인할 수 있다.
중복 회피 메모
기존 ai_dev 글은 tool calling의 왕복 프로토콜, subagent context isolation, background job, memory/context management, guardrail checkpoint, prompt caching, token counting, LLM router, serving latency 등을 다뤘다. 이번 글은 특정 도구 호출 설계나 guardrail 정책을 반복하지 않고, 에이전트 실행 전체를 trace/span 실행 그래프로 관측하고 eval·디버깅·민감정보 정책과 연결하는 관점에 집중한다.
핵심 출처
로그인하면 이 글을 북마크하고, 나만 보는 한 줄 메모를 남길 수 있어요.
댓글 0
최신순 ▾혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)