
Sessioner, traces og spans i LLM-observability: Én af dem er ikke et strukturniveau
Datadogs begrebsside, Googles første resultat for LLM observability sessions traces spans, definerer to af de tre ord. Ikke tre. Det manglende ord svarer til gen_ai.conversation.id, og grunden til, at det mangler, er, at OpenTelemetry-specifikationen aldrig gjorde det til et strukturniveau. Hvis du har brug for argumenterne for selve observability, så start her. Dette indlæg tager over, hvor det slipper: datamodellen.
Vigtigste pointer
- Spans ligger indeni traces; traces grupperes i sessioner. Indlejringen løber indefra og ud: først span, så trace, så session.
- En span er én tidsmålt operation. En trace er én ende-til-ende-forespørgsel. En session er én samtale med flere ture.
- OpenTelemetrys GenAI-konventioner definerer spans og attributten
gen_ai.conversation.id. De definerer ikke et sessionsniveau. - Trace- og span-ID'er propageres automatisk gennem context. Det gør session-ID'et ikke. Du sætter det selv, på hver tur.
Sessioner vs. traces vs. spans ved første øjekast
I LLM-observability er en span én tidsmålt operation (et modelkald, et retrieval-trin), en trace er det træ af spans, som én forespørgsel producerer, og en session grupperer mange traces fra samme samtale. Indlejringen løber indad: spans indeni traces, traces indeni sessioner. Den tredje gruppering er den, der ikke er, hvad den ligner.
| Niveau | Hvad den omslutter | Hvor længe den lever | Hvem der sætter ID'et | Hvad den svarer på | Typisk antal pr. samtale |
|---|---|---|---|---|---|
| Session | Mange traces fra én brugersamtale | Minutter til dage; slutter ved en inaktivitetstimeout eller en eksplicit lukning (leverandørdefineret) | Dig, manuelt, på hver tur | Lykkedes hele samtalen? | 1 |
| Trace | Én ende-til-ende-forespørgsel eller tur | Millisekunder til sekunder | Automatisk (SDK / OTel) | Hvad skete der på denne tur? | Typisk 5–20 |
| Span | Én operation: et retrieval, et modelkald, et tool-kald | Under et millisekund til sekunder | Automatisk (SDK / OTel) | Hvilket trin var langsomt, forkert eller dyrt? | Cirka 3–30 pr. trace |
De tal for antal og levetid er typiske intervaller, du kan forvente i en RAG-chatbot eller et agent-loop, ikke målinger fra en kontrolleret test. Dine tal vil være anderledes. Det, der ikke ændrer sig: Session-rækken er den, der ikke er et strukturniveau i specifikationen, og afsnittet "Sessioner: niveauet, som dit værktøj sandsynligvis har opfundet" beviser det.
Hvad er en span, og hvad er en span-kind?
En span er én tidsmålt operation med et navn, et start-timestamp, et slut-timestamp, en statuskode og en pose af nøgle-værdi-attributter. I LLM-tracing er det i attributterne, de nyttige data ligger: gen_ai.usage.input_tokens, gen_ai.usage.output_tokens og gen_ai.request.model fortæller, hvad operationen kostede, og hvilken model der kørte den.
En span er én operation, ikke ét funktionskald
Hver span bærer en parent span-ID-pointer (tom på rod-span'en), som bygger træet. Attributposen er åben: du vedhæfter den kontekst, du har brug for. OpenTelemetry GenAI span-konventionerne (status: Development) kræver gen_ai.operation.name og gen_ai.provider.name på enhver GenAI-span og anbefaler token-forbrugsattributterne ovenfor.
En praktisk regel fra Datadogs begrebsside: LLM-, Workflow- og Agent-spans kan fungere som rod-span; Tool-, Task-, Embedding- og Retrieval-spans kan ikke. Det er Datadogs regel, ikke en universel én, men de er den eneste leverandør, der formulerer den, og den sparer dig for at bygge en trace, der starter på et tool-kald uden en parent.
Span-kinds: samme idé, fem ordforråd
Ethvert værktøj har brug for en måde at sige "denne span er et modelkald" kontra "denne span er et retrieval" på. De er bare ikke enige om ordet:
| Værktøj | Dets ord for "operationstype" | Værdier |
|---|---|---|
| OpenTelemetry GenAI | Attributten gen_ai.operation.name | 15 velkendte værdier (chat, embeddings, execute_tool, invoke_agent, retrieval og 10 mere); én SKAL bruges, hvis den passer, egne værdier er tilladt, når ingen gør |
| Datadog | Span kind | LLM, Workflow, Agent, Tool, Task, Embedding, Retrieval |
| OpenInference / Phoenix | Span kind | CHAIN, LLM, TOOL, RETRIEVER, RERANKER, EMBEDDING, AGENT, GUARDRAIL, EVALUATOR, PROMPT |
| Langfuse | Observation type | generation, span, event |
| LangSmith | Run type | LLM, chain, tool, retriever |
OpenInference-specifikationen lister ti kinds. Datadog lister syv. OTel tager en tredje vej: deres GenAI-attributregister publicerer 15 velkendte værdier for gen_ai.operation.name (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) og fastslår, at hvis én af dem passer, SKAL den værdi bruges; en egen værdi MÅ kun bruges, når ingen passer. Det er altså en halvåben enum, ikke fraværet af én. Tre lister, tre længder, og ingen sammenhæng mellem dem. Hvis du er ved at vælge et værktøj, betyder dette ordforrådsgab mere end funktionslisten, for det er det, dine dashboards og advarselsfiltre skal nøgles efter.
Hvad er en trace, og hvorfor betyder træformen noget?
En trace er det træ af spans, som én forespørgsel producerer. Én rod-span sidder øverst; alle andre spans hænger under den via parent-span-ID-kanter. Træformen er hele pointen: en flad log fortæller dig, at noget var langsomt, men træet fortæller, hvilket trin der var langsomt, og hvilket trin der producerede det forkerte output.
chat_request (root) 2,340ms
├── retrieval 410ms
│ └── rerank 85ms
├── chat gpt-4o 1,720ms
└── tool_call: search_calendar 190msLæs det træ, og diagnosen er øjeblikkelig: 74 % af latensen lå i modelkaldet, ikke i retrieval'et. En flad log med fem timestamps giver dig den samme total, men intet af tilskrivningen.
Et agent-loop gør dette træ dybere og bredere end en almindelig RAG-forespørgsel. Hvert tool-kald skaber sit eget deltræ; en agent-tur med fem trin kan snildt producere 30+ spans under én rod. Det er normalt, og det er grunden til, at spørgsmålet om span-granularitet nedenover findes.
Forskellen mellem tracing og logging betyder også noget her: logging registrerer hændelser, tracing registrerer kausalitet. Hvis du stadig overvejer, hvad du skal logge kontra trace, trækker vores indlæg om bedste praksis for LLM-logging den grænse.
Sessioner: niveauet, som dit værktøj sandsynligvis har opfundet
Nej. En session er ikke et strukturniveau i OpenTelemetry GenAI-konventionerne. Specifikationen definerer spans og attributten gen_ai.conversation.id (betinget påkrævet, "når tilgængelig", status: Development), beskrevet som den entydige identifikator for en samtale eller tråd, der bruges til at korrelere beskeder. Leverandørerne bygger derefter deres eget sessionsobjekt oven på den attribut. Ingen andre på denne SERP siger rent ud, hvilken status specifikationen har, så her er den.
Konsekvensen er den sætning, hele dette indlæg findes for at levere:
En session er en grupperingsnøgle, ikke en parent-span. Den propageres ikke, sådan som et trace-ID gør; du sætter den selv på hver tur.
Glemmer du én tur, falder den tur ud af sessionen. Der er ingen automatisk context-propagering for den.
Hvornår starter og slutter en session?
Leverandørdefineret. Nogle værktøjer åbner en session på den første trace med et nyt samtale-ID og lukker den ved en inaktivitetstimeout (Langfuse bruger som standard et konfigurerbart vindue). Andre kræver et eksplicit lukkekald. Specifikationen siger intet om livscyklus, fordi specifikationen ikke modellerer en session som et objekt.
Hvad bæres på tværs af ture, og hvad gør ikke?
Modellens context window er ikke sessionen. Sessionen er en grupperingsnøgle oven på uafhængige traces. Hver tur får sin egen trace, sin egen rod-span, sit eget tokenantal. Det, der bæres, er den samtale-ID-attribut, du satte på hver rod-span. Det, der ikke bæres: latens, tokenforbrug, span-struktur. De er pr. trace.
Hvad måler en metric på sessionsniveau?
Ting, som en enkelt trace ikke kan: løsningsrate (løste samtalen brugerens problem?), ture-til-svar (hvor mange traces, før brugeren fik det, de havde brug for?) og efterladte samtaler (sessioner uden lukkesignal). At køre evals på live traces på sessionsniveau er sådan, du fanger fejl med flere ture, der ser fine ud tur for tur.
Koden, leverandørneutral
Dette kodestykke bruger kun stabile OTel-primitiver. Ingen leverandør-SDK. Det opretter en rod-span for én tur, en child-span for retrieval, en child for modelkaldet og sætter gen_ai.conversation.id, så tre ture lander i én session:
from opentelemetry import trace
tracer = trace.get_tracer("my-llm-app")
SESSION_ID = "conv-8f3a2c" # same value on every turn
def handle_turn(user_message: str):
with tracer.start_as_current_span("chat_request") as root:
# You set this. It does not propagate automatically.
root.set_attribute("gen_ai.conversation.id", SESSION_ID)
with tracer.start_as_current_span("retrieval") as ret:
ret.set_attribute("gen_ai.operation.name", "retrieval")
docs = retrieve(user_message)
with tracer.start_as_current_span("chat gpt-4o") as llm:
llm.set_attribute("gen_ai.operation.name", "chat")
llm.set_attribute("gen_ai.provider.name", "openai")
llm.set_attribute("gen_ai.request.model", "gpt-4o")
response = call_model(user_message, docs)
llm.set_attribute("gen_ai.usage.input_tokens", 1_204)
llm.set_attribute("gen_ai.usage.output_tokens", 312)
return responseKald handle_turn tre gange med det samme SESSION_ID, og alle tre traces grupperes under én session i enhver backend, der læser attributten. Ændr ID'et, og du har startet en ny session. Det er hele mekanismen.
Vi læste fem leverandørers dokumentation side om side. De er ikke enige.
Den 30.07.2026 læste vi den aktuelle datamodeldokumentation for Langfuse, LangSmith, OpenInference / Phoenix og Datadog side om side plus OpenTelemetry GenAI span-specifikationen. Fire af de fem kalder det samme objekt noget forskelligt. Kun én behandler en session som et førsteklasses objekt i stedet for en attribut. Datadogs begrebsside, Googles første resultat for denne søgning, definerer slet ikke en session.
| Begreb | OTel GenAI semconv | Langfuse | LangSmith | OpenInference / Phoenix | Datadog |
|---|---|---|---|---|---|
| Hele samtalen | Attributten gen_ai.conversation.id | Session (valgfri gruppering af traces) | Thread (via session_id / thread_id metadata) | Span-attributten session.id | Ikke defineret på begrebssiden |
| Én forespørgsel | Trace | Trace | Trace ("en samling runs") | Trace | Trace |
| Én operation | Span | Observation (span / generation / event) | Run ("en span, der repræsenterer én arbejdsenhed") | Span med en span kind | Span med en span kind |
Én kilde-note om den første række: OpenInferences session.id findes ikke i den linkede traces-specifikation, som dækker de ti span-kinds. Den er defineret i søsterfilen OpenInference semantic conventions som den entydige identifikator for en session. To filer, én specifikation.
Vi har ikke opfundet sammenligningen på tværs af leverandører; FutureAGI publicerer også en OTel-mod-leverandør-tabel. Vores to tilføjelser er session-rækken (FutureAGI springer den over) og samme-ord-forskellig-betydelse-fælden: Langfuses "observation" og LangSmiths "run" er det samme objekt som en span, mens Datadogs og OpenInferences span-kinds er forskellige ordforråd for den samme idé.
Langfuse kalder det en observation, LangSmith kalder det et run, Datadog kalder det en span. Samme objekt, tre dashboards, der går i stykker, når du migrerer.
Det er vores læsning af migrationsomkostningen, ikke et leverandørudsagn. Men det er grunden til, at gemte filtre, eval-konfigurationer og advarselsregler nøglet efter "observation" eller "run" holder op med at virke den dag, du skifter værktøj. Du omdøber ikke et felt. Du omdøber et niveau. Hvis du vejer netop de to værktøjer mod hinanden, går vores Langfuse vs LangSmith-sammenligning dybere med uenigheden.
Læsere har måske allerede Opik, PostHog, Sentry eller Weights & Biases i deres stack; Google associerer alle fire med llm tracing, og hver især mapper disse begreber lidt anderledes. Vælger du den rigtige? Vores roundup af observability-platforme dækker feltet.
Én friskhedsnote: GenAI-konventionerne er flyttet til deres eget repository, ud af det primære semantic-conventions-repo. Den gamle sti opentelemetry.io/docs/specs/semconv/gen-ai/ indeholder nu kun en pointer.
Hvilket ID hører hvor?
Et trace-ID identificerer én forespørgsel og propageres automatisk gennem context. Et span-ID identificerer én operation i den trace, også automatisk. Et korrelations-ID (eller request-ID) kommer fra dit weblag, før tracingen starter, og det er det, folk oftest forveksler med trace-ID'et. Session-ID'et er det, der skiller sig ud: det sætter du selv, manuelt, på hver tur.
| ID | Sat af | Scope | Forveksles med |
|---|---|---|---|
| Trace-ID | Automatisk | Én forespørgsel; propageres gennem context | Korrelations-ID'et fra dit weblag |
| Span-ID | Automatisk | Én operation | , |
| Parent span-ID | Automatisk | Bygger træet; tomt på rod-span'en | , |
| Sessions- / samtale-ID | Dig, manuelt, hver tur | Mange traces | Antages at propageres. Det gør det ikke. |
| Bruger-ID | Dig, manuelt | Mange sessioner | Session-ID'et |
| Request- / korrelations-ID | Dit weblag, før tracingen starter | Én HTTP-forespørgsel | Trace-ID'et (det er den store) |
Den praktiske regel: vedhæft gen_ai.conversation.id som en span-attribut på rod-span'en for hver tur, og sæt bruger-ID'et ved siden af. Spring én tur over, og dine metrics på sessionsniveau mister den tur i stilhed.
Én advarsel om kardinalitet: bruger-ID'er og sessions-ID'er er værdier med høj kardinalitet. Det betyder noget for din backends indekseringsregning, som er næste afsnits problem.
Hvor granulær skal en span være?
To fejltilstande, begge almindelige:
Over-spanning. En span pr. funktionskald giver dig en trace med 400 spans, ingen kan læse, og en regning pr. span, ingen har godkendt. Hostede backends (Datadog, Langfuse Cloud) prissætter efter span-volumen. Et agent-loop med meget støj, der instrumenterer hver eneste strengsammenkædning, brænder et gratisabonnement af på en eftermiddag.
Under-spanning. Én span for "hele kæden" fortæller dig, at den var langsom, men ikke hvor. Du ender med at tilføje print-sætninger igen, hvilket er det, tracing skulle erstatte.
Tommelfingerreglen (og det er en tommelfingerregel, ikke en måling): span de grænser, hvor en beslutning eller et eksternt kald sker.
- Retrieval-trin: span det.
- Rerank-kald: span det.
- Hvert modelkald: span det.
- Hvert tool-kald: span det.
- Hvert guardrail-tjek: span det.
- Rene in-process-transformationer (strengformatering, JSON-parsing, prompt-samling): attributter på parent-span'en, ikke egne spans.
Om kardinalitet, sampling og retention:
- Attributter med høj kardinalitet (bruger-ID'er, fulde prompts) oppuster lageromkostningerne. Sampl eller beskær dem.
- De fleste backends lader dig sample på traceniveau. Behold 100 % af fejl-traces; sampl den glade sti.
- Retentionsvinduer varierer: 7 dage på gratisabonnementer, 30–90 dage på betalte. Beslut det, før du har brug for dataene.
For den faktiske omkostningsmodel bag span-volumen og pr.-span-priser, se vores guide til LLM-omkostningsovervågning. Vi genbygger den ikke her.
Hvordan Techsy griber det an
I agentarbejde for kunder standardiserer vi på tre regler:
- Én trace pr. tur. Flet aldrig to brugerture til én trace, selvom agenten looper internt.
- Et sessions-ID sat på hver rod-span, i applikationskoden, aldrig antaget at propageres.
- Span-kinds holdt til et lille fast sæt (retrieval, inference, tool, guardrail), så dashboards overlever et leverandørskifte.
Den tredje regel er den, teams springer over, og det er den, der redder en migration. Hvis dit span-ordforråd er bundet til én leverandørs enum, går hver advarsel og gemt visning i stykker den dag, du skifter.
Bygger du et agentsystem og vil have et second opinion på tracing-arkitekturen, så få en gratis konsultation.
Om forfatteren
Mert Batur er medstifter af Techsy.io, hvor teamet leverer AI-agenter, automatiseringssystemer og voice/SDR-pipelines til B2B-kunder. Han skriver om den LLM-værktøjsstack, Techsy-teamet faktisk bruger i produktion. Forbind på LinkedIn.
Ofte stillede spørgsmål
Hvad er en span i distribueret tracing?
En span er én tidsmålt arbejdsmængde: den har et navn, et starttidspunkt, et sluttidspunkt, en status og et sæt attributter. Spans kædes sammen via parent-span-ID-referencer og danner et træ. I LLM-applikationer omslutter en span typisk ét modelkald, ét retrieval eller én tool-eksekvering.
Hvad er en span i Datadog?
I Datadogs LLM Observability er en span den samme tidsmålte operation, men Datadog tilføjer en span kind-taksonomi: LLM, Workflow, Agent, Tool, Task, Embedding og Retrieval. Kun LLM-, Workflow- og Agent-kinds kan fungere som rod-span. Taksonomien er Datadog-specifik; den er ikke en del af OpenTelemetry-standarden.
Hvad er de fire søjler i observability?
De fire søjler er logs, metrics, traces og (afhængigt af hvem man spørger) profiler eller events. Traces er den søjle, dette indlæg lever i. LLM-tilfældet tilføjer en krølle: tokenforbrug og modelidentitet er attributter på trace-spans, ikke separate metric-strømme, hvilket kollapser det, der ellers var to søjler, til én forespørgsel.
Hvad er de fire gyldne signaler for observability?
Latens, trafik, fejl og mætning. For LLM-systemer betyder latens tid-til-første-token og samlet genereringstid; trafik betyder forespørgsler pr. sekund pr. model; fejl betyder mislykkede spans (statuskode ERROR); mætning betyder opbrugt tokenbudget eller kødybde. Signalerne er de samme; enhederne er forskellige.
Er en session en del af OpenTelemetry-specifikationen?
Ikke som strukturniveau. OTel GenAI span-konventionerne definerer gen_ai.conversation.id som en betinget påkrævet attribut ("når tilgængelig") til at korrelere beskeder i en samtale eller tråd. Den sidder på spans. Leverandører som Langfuse og LangSmith bygger deres egne sessions- eller trådobjekter oven på den.
Hvad er forskellen på et trace-ID, et span-ID og et korrelations-ID?
Et trace-ID identificerer én forespørgsel og propageres automatisk gennem alle downstream-tjenester. Et span-ID identificerer én operation i den trace. Et korrelations-ID (eller request-ID) genereres af dit weblag, før tracingen begynder, og er den værdi, folk oftest tager fejl af for trace-ID'et. De overlapper i scope, men har forskellig oprindelse.
Hvor mange spans bør én trace have?
Der er ikke noget fast svar, men typiske intervaller er 3–30 for en RAG-forespørgsel og 10–50+ for et agent-loop med flere tool-kald. Tommelfingerreglen: span eksterne kald og beslutningspunkter, ikke in-process-transformationer. Hvis din trace overstiger 100 spans, instrumenterer du sandsynligvis for meget.
Er Langfuse-"observationer" det samme som spans?
Ja. En Langfuse-observation er det samme objekt som en OTel-span: én tidsmålt operation med attributter. Langfuse opdeler observationer i tre typer (generation, span, event), der hvor OTel bruger gen_ai.operation.name. Hvis du evaluerer værktøjer, der læser dine traces, dækker vores roundup af LLM-evalueringsværktøjer, hvilke der accepterer begge ordforråd.
Hvordan grupperer man en chatbot-samtale med flere ture i én session?
Sæt den samme samtaleidentifikator på rod-span'en for hver tur. I OTel-termer er det gen_ai.conversation.id. I Langfuse sender du et session_id, når du opretter traces. I LangSmith sætter du session_id eller thread_id metadata. Glemmer du én tur, falder den tur ud af grupperingen.
Har jeg brug for sessioner, hvis jeg kun håndterer enkeltturs-forespørgsler?
Sandsynligvis ikke. Sessioner findes for at korrelere flere traces til én samtale. Hvis hver forespørgsel er uafhængig (et klassificerings-API, en one-shot-opsummering), er metrics på traceniveau tilstrækkelige. Tilføj sessioner, når du har brug for metrics på tværs af ture: løsningsrate, ture-til-svar eller omkostninger på samtaleniveau. Vores guide til LLM-evaluering dækker, hvornår evals på sessionsniveau er besværet værd.
Den korte version
Spans ligger indeni traces; traces grupperes i sessioner. Indlejringen er reel, men specifikationen strukturerer kun to af de tre niveauer. gen_ai.conversation.id er en attribut, du selv sætter, ikke en parent-span, der propageres. Og den leverandør, du vælger i dag, navngiver disse objekter anderledes end den leverandør, du skifter til om 18 måneder, så hold dit span-ordforråd lille og portabelt.
Vælger du en platform, så start med vores sammenligning af observability-platforme. Bygger du evals oven på dine traces, tager guiden til LLM-evaluering over herfra.