
LLM 관찰성의 세션, 트레이스, 스팬: 이 중 하나는 구조적 수준이 아닙니다
Datadog의 용어 페이지는 LLM 관찰성 세션 트레이스 스팬에 대한 Google 검색 1위 결과인데, 이 세 단어 중 두 개만 정의하고 있습니다. 세 개 모두가 아니라 두 개입니다. 빠진 하나는 gen_ai.conversation.id에 해당하며, 이것이 빠진 이유는 OpenTelemetry 스펙이 이를 구조적 수준으로 정의한 적이 없기 때문입니다. 관찰성 자체에 대한 사례가 필요하다면 여기서 시작하세요. 이 글은 그 글이 끝나는 지점에서 계속됩니다: 데이터 모델입니다.
핵심 요약
- 스팬은 트레이스 내에 중첩되고, 트레이스는 세션으로 그룹화됩니다. 중첩은 가장 안쪽에서 바깥쪽으로 진행됩니다: 스팬, 트레이스, 세션.
- 스팬은 하나의 시간 측정된 작업입니다. 트레이스는 하나의 end-to-end 요청입니다. 세션은 하나의 다중 턴 대화입니다.
- OpenTelemetry의 GenAI 규약은 스팬과
gen_ai.conversation.id속성을 정의합니다. 세션 수준은 정의하지 않습니다. - 트레이스 및 스팬 ID는 컨텍스트를 통해 자동으로 전파됩니다. 세션 ID는 그렇지 않습니다. 매 턴마다 수동으로 설정합니다.
세션 vs 트레이스 vs 스팬, 한눈에 보기
LLM 관찰성에서 스팬은 하나의 시간 측정된 작업(모델 호출, 검색 단계), 트레이스는 하나의 요청이 생성하는 스팬의 트리, 세션은 같은 대화의 여러 트레이스를 그룹화합니다. 중첩은 내부로 진행됩니다: 트레이스 내 스팬, 세션 내 트레이스. 세 번째 그룹화가 보이는 그대로가 아닌 것입니다.
이러한 개수 및 수명 수치는 제어된 테스트의 측정값이 아니라 RAG 챗봇이나 에이전트 루프에서 예상할 수 있는 일반적인 범위입니다. 여러분의 수치는 다를 것입니다. 다르지 않을 것: 세션 행은 스펙에서 구조적 수준이 아닌 것이며, "세션: 도구가 아마 발명한 수준" 섹션이 이를 증명합니다.
스팬이란 무엇이고, 스팬 종류란 무엇입니까?
스팬은 이름, 시작 타임스탬프, 종료 타임스탬프, 상태 코드, 그리고 키-값 속성 모음을 가진 하나의 시간 측정된 작업입니다. LLM 추적에서 속성은 유용한 데이터가 있는 곳입니다: gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, gen_ai.request.model은 작업 비용이 얼마였는지와 어떤 모델이 실행했는지 알려줍니다.
스팬은 하나의 함수 호출이 아니라 하나의 작업입니다
모든 스팬은 부모 스팬 ID 포인터(루트 스팬에서는 비어있음)를 가지며 이는 트리를 구축합니다. 속성 모음은 열려 있습니다: 필요한 모든 컨텍스트를 첨부합니다. OpenTelemetry GenAI 스팬 규약(상태: 개발)은 모든 GenAI 스팬에 gen_ai.operation.name과 gen_ai.provider.name을 요구하며, 위의 토큰 사용 속성을 권장합니다.
Datadog의 용어 페이지의 실제 규칙: LLM, Workflow, Agent 스팬은 루트 스팬으로 제공될 수 있습니다. Tool, Task, Embedding, Retrieval 스팬은 그럴 수 없습니다. 이것은 Datadog의 규칙이지 보편적인 규칙은 아니지만, 이를 명시하는 유일한 벤더이며, 부모가 없는 도구 호출로 시작하는 트레이스를 구축하는 것을 방지해줍니다.
스팬 종류: 같은 개념, 다섯 가지 어휘
모든 도구는 "이 스팬은 모델 호출입니다"와 "이 스팬은 검색입니다"를 표현하는 방법이 필요합니다. 단지 어휘에 동의하지 않을 뿐입니다:
OpenInference 스펙은 10가지 종류를 나열합니다. Datadog은 7가지를 나열합니다. OTel은 세 번째 경로를 택합니다: 그 GenAI 속성 레지스트리는 gen_ai.operation.name의 15개의 잘 알려진 값(chat, create_agent, create_memory, create_memory_store, delete_memory, delete_memory_store, embeddings, execute_tool, generate_content, invoke_agent, invoke_workflow, plan, retrieval, search_memory, text_completion)을 발행하고, 그 중 하나가 적용된다면 그 값을 반드시 사용해야 하며, 맞는 것이 없을 때만 사용자 정의 값을 사용할 수 있다고 명시합니다. 그래서 그것은 개방형 열거형이지 부재입니다. 세 개의 목록, 세 가지 길이, 그리고 그들 사이에 정렬이 없습니다. 도구를 선택할 때 이 어휘 차이는 기능 목록보다 훨씬 더 중요합니다, 왜냐하면 그것이 여러분의 대시보드와 경고 필터가 기반하는 것이기 때문입니다.
트레이스란 무엇이고, 트리 구조가 중요한 이유는 무엇입니까?
트레이스는 하나의 요청으로 생성되는 스팬의 트리입니다. 하나의 루트 스팬이 맨 위에 있고, 다른 모든 스팬은 부모 스팬 ID 엣지를 통해 아래에 매달려 있습니다. 트리 구조가 전부입니다: 평면 로그는 무언가가 느렸다는 것을 알려주지만, 트리는 어떤 단계가 느렸는지와 어떤 단계가 나쁜 출력을 생성했는지를 알려줍니다.
그 트리를 읽으면 진단이 즉각적입니다: 지연의 74%가 검색이 아니라 모델 호출에 있었습니다. 다섯 개의 타임스탐프의 평면 로그는 동일한 합계를 제공하지만 어떤 귀인도 제공하지 않습니다.
에이전트 루프는 이 트리를 평면 RAG 요청보다 더 깊고 넓게 만듭니다. 각 도구 호출은 자신의 서브 트리를 생성합니다. 5단계 에이전트 턴은 하나의 루트 아래에서 쉽게 30개 이상의 스팬을 생성할 수 있습니다. 이것은 정상이며, 아래의 스팬 세분성 질문이 존재하는 이유입니다.
추적과 로깅 사이의 구별도 여기서 중요합니다: 로깅은 이벤트를 기록하고, 추적은 인과성을 기록합니다. 아직도 로깅할 것과 추적할 것을 결정하고 있다면, 우리의 LLM 로깅 모범 사례 게시물이 그 선을 그립니다.
세션: 도구가 아마 발명한 수준
아닙니다. 세션은 OpenTelemetry GenAI 규약의 구조적 수준이 아닙니다. 스펙은 스팬과 gen_ai.conversation.id 속성을 정의합니다(조건부 필수, "가용할 때", 상태: 개발), 대화 또는 스레드의 고유 식별자로 설명되며 메시지 상관관계를 위해 사용됩니다. 벤더는 그 속성 위에 자신들의 자체 세션 객체를 구축합니다. 이 SERP의 다른 누구도 스펙 상태를 명확히 명시하지 않으므로, 여기에 있습니다.
결과는 이 글 전체가 존재하는 이유인 문장입니다:
세션은 구조적 수준이 아니라 그룹화 키입니다. 트레이스 ID처럼 전파되지 않습니다. 매 턴마다 직접 설정합니다.
한 턴을 놓치면 그 턴은 세션 밖으로 떨어집니다. 이에 대한 자동 컨텍스트 전파가 없습니다.
세션은 언제 시작하고 언제 끝나나요?
벤더 정의. 일부 도구는 새로운 대화 ID를 가진 첫 번째 트레이스에서 세션을 열고 비활동 타임아웃에서 닫습니다(Langfuse는 구성 가능한 윈도우로 기본값 설정). 다른 도구는 명시적 종료 호출을 요구합니다. 스펙은 스펙이 세션을 객체로 모델링하지 않기 때문에 수명주기에 대해 아무것도 말하지 않습니다.
무엇이 턴을 넘어서 전달되고 무엇이 그렇지 않나요?
모델의 컨텍스트 윈도우는 세션이 아닙니다. 세션은 독립적인 트레이스에 대한 그룹화 키입니다. 각 턴은 자신의 트레이스, 자신의 루트 스팬, 자신의 토큰 카운트를 가집니다. 전달되는 것: 각 루트 스팬에 스탬프한 대화 ID 속성. 전달되지 않는 것: 지연, 토큰 사용, 스팬 구조. 이들은 트레이스 당입니다.
세션 수준 메트릭은 무엇을 측정하나요?
단일 트레이스가 측정할 수 없는 것: 해결율(대화가 사용자의 문제를 해결했나?), 답변까지의 턴(사용자가 필요한 것을 얻기 전에 얼마나 많은 트레이스가 필요했나?), 그리고 버려진 대화(종료 신호가 없는 세션). 세션 수준에서 라이브 트레이스에 대해 평가를 실행하는 것이 턴별로는 양호해 보이는 다중 턴 실패를 잡는 방법입니다.
코드, 벤더 중립
이 스니펫은 안정적인 OTel 원시만 사용합니다. 벤더 SDK가 없습니다. 한 턴을 위한 루트 스팬, 검색을 위한 자식 스팬, 모델 호출을 위한 자식을 생성하고, 세 턴이 하나의 세션에 들어가도록 gen_ai.conversation.id를 설정합니다:
SESSION_ID = "conversation-abc-123"
def handle_turn(user_message: str):
with tracer.start_as_current_span("turn") as root_span:
root_span.set_attribute("gen_ai.conversation.id", SESSION_ID)
with tracer.start_as_current_span("retrieval"):
documents = retrieve(user_message)
# attributes: prompt_tokens, etc
with tracer.start_as_current_span("model_call"):
response = model.generate(user_message, documents)
# attributes: output_tokens, etc
return response
같은 SESSION_ID로 handle_turn을 세 번 호출하면, 세 트레이스 모두 속성을 읽는 백엔드의 하나의 세션 아래에서 그룹화됩니다. ID를 변경하면 새로운 세션을 시작했습니다. 그것이 전부입니다.
다섯 벤더의 문서를 나란히 읽었습니다. 동의하지 않습니다.
2026-07-30에 우리는 Langfuse, LangSmith, OpenInference / Phoenix, Datadog의 현재 데이터 모델 문서를 나란히 읽었고, OpenTelemetry GenAI 스팬 스펙도 읽었습니다. 다섯 개 중 네 개가 같은 객체를 다르게 부릅니다. 오직 하나만 세션을 속성이 아닌 일급 객체로 취급합니다. Datadog의 용어 페이지는 이 쿼리의 1위 Google 결과인데, 세션을 정의하지 않습니다.
첫 번째 행에 대한 소스 참고: OpenInference의 session.id는 위에 링크된 추적 스펙에 없으며, 이것은 10가지 스팬 종류를 다룹니다. 이것은 형제 OpenInference 의미 규약 파일에서 세션의 고유 식별자로 정의됩니다. 두 파일, 하나의 스펙.
...