
Sesiuni, trace-uri și span-uri în observabilitatea LLM: unul dintre ele nu e un nivel structural
Pagina de termeni Datadog, primul rezultat Google pentru „LLM observability sessions traces spans", definește două dintre aceste trei cuvinte. Nu trei. Cel care lipsește corespunde atributului gen_ai.conversation.id, iar motivul pentru care lipsește e că specificația OpenTelemetry nu l-a transformat niciodată într-un nivel structural. Dacă ai nevoie de argumentele pentru observabilitate în sine, începe de aici. Acest articol continuă exact de unde se oprește acela: modelul de date.
Pe scurt
- Span-urile se imbrică în trace-uri; trace-urile se grupează în sesiuni. Imbricarea merge din interior spre exterior: span, apoi trace, apoi sesiune.
- Un span e o singură operațiune cronometrată. Un trace e o singură cerere end-to-end. O sesiune e o singură conversație cu mai multe tururi.
- Convențiile GenAI din OpenTelemetry definesc span-urile și atributul
gen_ai.conversation.id. Ele nu definesc un nivel de sesiune. - ID-urile de trace și de span se propagă automat prin context. ID-ul de sesiune, nu. Pe acesta îl setezi tu, la fiecare tur.
Sesiuni vs. trace-uri vs. span-uri, dintr-o privire
În observabilitatea LLM, un span e o singură operațiune cronometrată (un apel de model, un pas de recuperare), un trace e arborele de span-uri produs de o cerere, iar o sesiune grupează mai multe trace-uri din aceeași conversație. Imbricarea merge spre interior: span-uri în trace-uri, trace-uri în sesiuni. A treia grupare e cea care nu e ceea ce pare.
| Nivel | Ce încadrează | Cât trăiește | Cine setează ID-ul | La ce răspunde | Număr tipic per conversație |
|---|---|---|---|---|---|
| Sesiune | Mai multe trace-uri dintr-o singură conversație de utilizator | De la minute la zile; se încheie la un timeout de inactivitate sau printr-o închidere explicită (definite de vendor) | Tu, manual, la fiecare tur | A reușit această conversație în ansamblu? | 1 |
| Trace | O singură cerere sau un singur tur end-to-end | De la milisecunde la secunde | Automat (SDK / OTel) | Ce s-a întâmplat în acest tur? | De obicei 5–20 |
| Span | O singură operațiune: o recuperare, un apel de model, un apel de instrument | De la sub-milisecunde la secunde | Automat (SDK / OTel) | Care pas a fost lent, greșit sau scump? | Aproximativ 3–30 per trace |
Aceste cifre de număr și durată de viață sunt intervale tipice la care te-ai aștepta într-un chatbot RAG sau într-o buclă de agent, nu măsurători dintr-un test controlat. Numerele tale vor diferi. Ce nu va diferi: rândul Sesiune e cel care nu e un nivel structural în specificație, iar secțiunea „Sesiuni: nivelul pe care instrumentul tău probabil l-a inventat" o dovedește.
Ce e un span și ce e un tip de span?
Un span e o singură operațiune cronometrată, cu un nume, un timestamp de început, un timestamp de sfârșit, un cod de status și un set de atribute cheie-valoare. În tracing-ul LLM, atributele sunt locul unde stau datele utile: gen_ai.usage.input_tokens, gen_ai.usage.output_tokens și gen_ai.request.model îți spun cât a costat operațiunea și ce model a rulat-o.
Un span e o operațiune, nu un apel de funcție
Fiecare span poartă un pointer către ID-ul span-ului părinte (gol în cazul span-ului rădăcină), care construiește arborele. Setul de atribute e deschis: atașezi orice context ai nevoie. Convențiile OpenTelemetry GenAI pentru span-uri (status: Development) cer gen_ai.operation.name și gen_ai.provider.name pe fiecare span GenAI și recomandă atributele de utilizare a token-urilor de mai sus.
O regulă practică de pe pagina de termeni Datadog: span-urile LLM, Workflow și Agent pot servi drept span rădăcină; span-urile Tool, Task, Embedding și Retrieval, nu. E regula Datadog, nu una universală, dar e singurul vendor care o formulează explicit și te scutește de a construi un trace care începe dintr-un apel de instrument fără părinte.
Tipuri de span-uri: aceeași idee, cinci vocabulare
Fiecare instrument are nevoie de un mod de a spune „acest span e un apel de model" versus „acest span e o recuperare". Pur și simplu nu cad de acord asupra cuvântului:
| Instrument | Cuvântul său pentru „tip de operațiune" | Valori |
|---|---|---|
| OpenTelemetry GenAI | atributul gen_ai.operation.name | 15 valori cunoscute (chat, embeddings, execute_tool, invoke_agent, retrieval și încă 10); una TREBUIE folosită dacă se aplică, iar valorile personalizate sunt permise când niciuna nu se potrivește |
| 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 | Tip de observație | generation, span, event |
| LangSmith | Tip de run | LLM, chain, tool, retriever |
Specificația OpenInference listează zece tipuri. Datadog listează șapte. OTel alege a treia cale: registrul său de atribute GenAI publică 15 valori cunoscute pentru 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) și precizează că, dacă una dintre ele se aplică, acea valoare TREBUIE folosită; o valoare personalizată POATE fi folosită doar când niciuna nu se potrivește. Deci e un enum semi-deschis, nu absența unuia. Trei liste, trei lungimi și nicio aliniere între ele. Dacă alegi un instrument, acest decalaj de vocabular contează mai mult decât lista de funcții, pentru că pe el vor fi cheate dashboard-urile și filtrele tale de alerte.
Ce e un trace și de ce contează forma de arbore?
Un trace e arborele de span-uri produs de o singură cerere. Un singur span rădăcină stă în vârf; orice alt span atârnă sub el prin muchii de ID-uri de span părinte. Forma de arbore e tot scopul: un log plat îți spune că ceva a fost lent, dar arborele îți spune care pas a fost lent și care pas a produs rezultatul greșit.
chat_request (root) 2,340ms
├── retrieval 410ms
│ └── rerank 85ms
├── chat gpt-4o 1,720ms
└── tool_call: search_calendar 190msCitești acel arbore și diagnosticul e imediat: 74% din latență a stat în apelul de model, nu în recuperare. Un log plat cu cinci timestamp-uri îți dă același total, dar nicio atribuire.
O buclă de agent face acest arbore mai adânc și mai lat decât o cerere RAG simplă. Fiecare apel de instrument își generează propriul sub-arbore; un tur de agent cu cinci pași poate produce ușor peste 30 de span-uri sub o singură rădăcină. E normal și tocmai de aceea există întrebarea despre granularitatea span-urilor de mai jos.
Distincția dintre tracing și logging contează și aici: logging-ul înregistrează evenimente, tracing-ul înregistrează cauzalitate. Dacă încă te decizi ce să loghezi și ce să trace-ui, articolul nostru despre practicile recomandate de logging LLM trasează exact această linie.
Sesiuni: nivelul pe care instrumentul tău probabil l-a inventat
Nu. O sesiune nu e un nivel structural în convențiile OpenTelemetry GenAI. Specificația definește span-urile și atributul gen_ai.conversation.id (cerut condițional, „când e disponibil", status: Development), descris ca identificatorul unic al unei conversații sau al unui thread, folosit pentru a corela mesajele. Vendorii construiesc apoi propriul obiect de sesiune deasupra acelui atribut. Nimeni altcineva din acest SERP nu spune răspicat care e statusul specificației, așa că iată-l.
Consecința e fraza pentru care există întregul acesta articol:
O sesiune e o cheie de grupare, nu un span părinte. Nu se propagă așa cum o face un ID de trace; o setezi tu însuți la fiecare tur.
Rată un tur și acel tur cade din sesiune. Nu există propagare automată a contextului pentru ea.
Când începe și când se termină o sesiune?
Definit de vendor. Unele instrumente deschid o sesiune la primul trace care poartă un ID de conversație nou și o închid la un timeout de inactivitate (Langfuse folosește implicit o fereastră configurabilă). Altele cer un apel explicit de închidere. Specificația nu spune nimic despre ciclul de viață, pentru că nu modelează sesiunea ca obiect.
Ce se transmite între tururi și ce nu?
Fereastra de context a modelului nu e sesiunea. Sesiunea e o cheie de grupare peste trace-uri independente. Fiecare tur primește propriul trace, propriul span rădăcină, propriile contorizări de token-uri. Ce se transmite e atributul cu ID-ul conversației, pe care l-ai aplicat pe fiecare span rădăcină. Ce nu se transmite: latența, utilizarea token-urilor, structura span-urilor. Acestea sunt per-trace.
Ce măsoară o metrică la nivel de sesiune?
Lucruri pe care un singur trace nu le poate măsura: rata de rezolvare (conversația a rezolvat problema utilizatorului?), tururi până la răspuns (câte trace-uri au trecut până când utilizatorul a primit ce îi trebuia?) și conversațiile abandonate (sesiuni fără semnal de închidere). Rulezi evaluări pe trace-uri live la nivel de sesiune tocmai ca să prinzi eșecurile multi-tur care arată bine tur cu tur.
Codul, neutru față de vendor
Acest fragment folosește doar primitive OTel stabile. Niciun SDK de vendor. Creează un span rădăcină pentru un tur, un span copil pentru recuperare, un copil pentru apelul de model și setează gen_ai.conversation.id, astfel încât trei tururi să ajungă într-o singură sesiune:
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 responseApelează handle_turn de trei ori cu același SESSION_ID și toate cele trei trace-uri se grupează într-o singură sesiune în orice backend care citește atributul. Schimbi ID-ul și ai început o sesiune nouă. Acesta e întregul mecanism.
Am citit documentația a cinci vendori, una lângă alta. Nu sunt de acord.
Pe 30.07.2026 am citit, una lângă alta, documentațiile actuale despre modelul de date pentru Langfuse, LangSmith, OpenInference / Phoenix și Datadog, plus specificația OpenTelemetry GenAI pentru span-uri. Patru dintre cei cinci numesc același obiect altfel. Doar unul tratează sesiunea ca obiect de prim rang, nu ca atribut. Pagina de termeni Datadog, primul rezultat Google pentru această căutare, nu definește deloc sesiunea.
| Concept | OTel GenAI semconv | Langfuse | LangSmith | OpenInference / Phoenix | Datadog |
|---|---|---|---|---|---|
| Conversația completă | atributul gen_ai.conversation.id | Session (grupare opțională a trace-urilor) | Thread (prin metadatele session_id / thread_id) | atributul de span session.id | Nedefinită pe pagina de termeni |
| O singură cerere | Trace | Trace | Trace („o colecție de run-uri") | Trace | Trace |
| O singură operațiune | Span | Observation (span / generation / event) | Run („un span care reprezintă o singură unitate de lucru") | Span cu un tip de span | Span cu un tip de span |
O notă despre sursă pentru primul rând: session.id din OpenInference nu se află în specificația de trace-uri legată mai sus, care acoperă cele zece tipuri de span-uri. E definit în fișierul înrudit OpenInference semantic conventions, ca identificatorul unic al unei sesiuni. Două fișiere, o singură specificație.
Nu am inventat comparația între vendori; și FutureAGI publică un tabel OTel-versus-vendori. Cele două adăugiri ale noastre sunt rândul sesiunii (FutureAGI îl sare) și capcana aceluiași cuvânt cu sens diferit: „observation" de la Langfuse și „run" de la LangSmith sunt același obiect cu un span, în timp ce tipurile de span-uri de la Datadog și OpenInference sunt vocabulare diferite pentru aceeași idee.
Langfuse îi spune observation, LangSmith îi spune run, Datadog îi spune span. Același obiect, trei dashboard-uri care se strică atunci când migrezi.
Asta e interpretarea noastră a costului de migrare, nu o afirmație a vreunui vendor. Dar e motivul pentru care filtrele salvate, configurațiile de evaluare și regulile de alertă cheate pe „observation" sau „run" nu mai funcționează în ziua în care schimbi instrumentul. Nu redenumești un câmp. Redenumești un nivel. Dacă cânțărești exact aceste două instrumente, comparația noastră Langfuse vs LangSmith intră mai adânc în divergență.
Cititorii pot avea deja în stack Opik, PostHog, Sentry sau Weights & Biases; Google asociază toate cele patru cu llm tracing și fiecare cartografiază aceste concepte puțin diferit. Alegi platforma potrivită? Clasamentul nostru al platformelor de observabilitate acoperă întregul peisaj.
O notă de actualitate: convențiile GenAI s-au mutat în propriul repository, în afara repo-ului principal semantic-conventions. Vechiul path opentelemetry.io/docs/specs/semconv/gen-ai/ nu mai conține acum decât un pointer.
Care ID unde merge?
Un trace ID identifică o singură cerere și se propagă automat prin context. Un span ID identifică o singură operațiune din acel trace, tot automat. Un correlation ID (sau request ID) vine de la stratul tău web înainte să înceapă tracing-ul și e cel pe care lumea îl confundă cel mai des cu trace ID-ul. ID-ul de sesiune e excepția: tu îl setezi, manual, la fiecare tur.
| ID | Setat de | Domeniu | Confundat cu |
|---|---|---|---|
| Trace ID | Automat | O singură cerere; se propagă prin context | Correlation ID-ul din stratul web |
| Span ID | Automat | O singură operațiune | , |
| ID span părinte | Automat | Construiește arborele; gol în span-ul rădăcină | , |
| ID de sesiune / conversație | Tu, manual, la fiecare tur | Mai multe trace-uri | Se presupune că se propagă. Nu o face. |
| User ID | Tu, manual | Mai multe sesiuni | ID-ul de sesiune |
| Request / correlation ID | Stratul tău web, înainte de tracing | O singură cerere HTTP | Trace ID-ul (asta e marea confuzie) |
Regula practică: atașează gen_ai.conversation.id ca atribut de span pe span-ul rădăcină al fiecărui tur și aplică user ID-ul alături de el. Sari peste un tur și metricile tale la nivel de sesiune pierd acel tur în tăcere.
Un avertisment despre cardinalitate: user ID-urile și session ID-urile sunt valori cu cardinalitate mare. Asta contează pentru factura de indexare a backend-ului tău, care e problema secțiunii următoare.
Cât de granular ar trebui să fie un span?
Două moduri de eșec, ambele frecvente:
Prea multe span-uri. Un span per apel de funcție îți dă un trace cu 400 de span-uri pe care nimeni nu-l poate citi și o factură per-span pe care nimeni n-a aprobat-o. Backend-urile găzduite (Datadog, Langfuse Cloud) taxează după volumul de span-uri. O buclă de agent „vorbăreață" care instrumentează fiecare concatenare de șiruri va arde un free tier într-o după-amiază.
Prea puține span-uri. Un singur span pentru „tot lanțul" îți spune că a fost lent, dar nu și unde. Ajungi să adaugi din nou instrucțiuni print, exact ceea ce tracing-ul ar fi trebuit să înlocuiască.
Regula empirică (și chiar e o regulă empirică, nu o măsurătoare): pune span-uri la granițele unde se ia o decizie sau se face un apel extern.
- Pasul de recuperare: pune-i span.
- Apelul de rerank: pune-i span.
- Fiecare apel de model: pune-i span.
- Fiecare apel de instrument: pune-i span.
- Fiecare verificare de guardrail: pune-i span.
- Transformările pure, în proces (formatare de șiruri, parsare JSON, asamblare de prompt): atribute pe span-ul părinte, nu span-uri proprii.
Despre cardinalitate, eșantionare și retenție:
- Atributele cu cardinalitate mare (user ID-uri, prompturi complete) umflă costurile de stocare. Eșantionează-le sau trunchiază-le.
- Majoritatea backend-urilor te lasă să eșantionezi la nivel de trace. Păstrează 100% din trace-urile cu erori; eșantionează calea fericită.
- Ferestrele de retenție variază: 7 zile pe planurile gratuite, 30–90 de zile pe cele plătite. Decide înainte să ai nevoie de date.
Pentru modelul real de cost din spatele volumului de span-uri și al taxării per-span, vezi ghidul nostru de monitorizare a costurilor LLM. Nu-l reconstruim aici.
Cum abordează Techsy asta
Pentru lucrul cu agenți pentru clienți, standardizăm pe trei reguli:
- Un trace per tur. Nu comasa niciodată două tururi de utilizator într-un singur trace, chiar dacă agentul face bucle interne.
- Un ID de sesiune aplicat pe fiecare span rădăcină, setat în codul aplicației, despre care nu presupunem niciodată că se propagă.
- Tipurile de span-uri ținute într-un set mic și fix (recuperare, inferență, instrument, guardrail), ca dashboard-urile să supraviețuiască unei schimbări de vendor.
A treia regulă e cea pe care echipele o sar și tot ea e cea care salvează o migrare. Dacă vocabularul tău de span-uri e legat de enum-ul unui singur vendor, fiecare alertă și fiecare vedere salvată se strică în ziua în care schimbi.
Dacă construiești un sistem cu agenți și vrei o a doua opinie despre arhitectura de tracing, solicită o consultație gratuită.
Despre autor
Mert Batur e co-fondator al Techsy.io, unde echipa livrează agenți AI, sisteme de automatizare și pipeline-uri de voce/SDR pentru clienți B2B. Scrie despre stack-ul de instrumente LLM pe care echipa Techsy îl folosește efectiv în producție. Conectează-te pe LinkedIn.
Întrebări frecvente
Ce e un span în tracing-ul distribuit?
Un span e o singură unitate de lucru cronometrată: are un nume, un timp de început, un timp de sfârșit, un status și un set de atribute. Span-urile se leagă între ele prin referințe către ID-ul span-ului părinte, formând un arbore. În aplicațiile LLM, un span încadrează de obicei un apel de model, o recuperare sau o invocare de instrument.
Ce e un span în Datadog?
În LLM Observability de la Datadog, un span e aceeași operațiune cronometrată, dar Datadog adaugă o taxonomie de span kind: LLM, Workflow, Agent, Tool, Task, Embedding și Retrieval. Doar tipurile LLM, Workflow și Agent pot servi drept span rădăcină. Taxonomia e specifică Datadog; nu face parte din standardul OpenTelemetry.
Care sunt cei patru piloni ai observabilității?
Cei patru piloni sunt log-urile, metricile, trace-urile și (în funcție de încadrarea fiecăruia) profilurile sau evenimentele. Trace-urile sunt pilonul în care trăiește acest articol. Cazul LLM adaugă o complicație: utilizarea token-urilor și identitatea modelului sunt atribute pe span-urile de trace, nu fluxuri separate de metrici, ceea ce reduce ceea ce ar fi doi piloni la o singură interogare.
Care sunt cele patru semnale de aur ale observabilității?
Latența, traficul, erorile și saturația. Pentru sistemele LLM, latența înseamnă time-to-first-token și timpul total de generare; traficul înseamnă cereri pe secundă per model; erorile înseamnă span-uri eșuate (cod de status ERROR); saturația înseamnă epuizarea bugetului de token-uri sau adâncimea cozii. Semnalele sunt aceleași; unitățile diferă.
Face parte sesiunea din specificația OpenTelemetry?
Nu ca nivel structural. Convențiile OTel GenAI pentru span-uri definesc gen_ai.conversation.id ca atribut cerut condițional („când e disponibil") pentru corelarea mesajelor dintr-o conversație sau dintr-un thread. El stă pe span-uri. Vendori precum Langfuse și LangSmith construiesc deasupra lui propriile obiecte de sesiune sau thread.
Care e diferența dintre un trace ID, un span ID și un correlation ID?
Un trace ID identifică o singură cerere și se propagă automat prin toate serviciile din aval. Un span ID identifică o singură operațiune din acel trace. Un correlation ID (sau request ID) e generat de stratul tău web înainte să înceapă tracing-ul și e valoarea pe care lumea o confundă cel mai des cu trace ID-ul. Li se suprapune domeniul, dar au origini diferite.
Câte span-uri ar trebui să aibă un trace?
Nu există un răspuns fix, dar intervalele tipice sunt 3–30 pentru o cerere RAG și 10–50+ pentru o buclă de agent cu mai multe apeluri de instrumente. Regula empirică: pune span-uri pe apelurile externe și pe punctele de decizie, nu pe transformările din proces. Dacă trace-ul tău depășește 100 de span-uri, probabil instrumentezi excesiv.
Sunt „observation"-urile din Langfuse același lucru cu span-urile?
Da. Un observation din Langfuse e același obiect cu un span OTel: o singură operațiune cronometrată, cu atribute. Langfuse împarte observation-urile în trei tipuri (generation, span, event), acolo unde OTel folosește gen_ai.operation.name. Dacă evaluezi instrumente care îți citesc trace-urile, clasamentul nostru al instrumentelor de evaluare LLM arată care acceptă ambele vocabulare.
Cum grupezi o conversație de chatbot cu mai multe tururi într-o singură sesiune?
Setează același identificator de conversație pe span-ul rădăcină al fiecărui tur. În termenii OTel, acesta e gen_ai.conversation.id. În Langfuse, trimiți un session_id când creezi trace-urile. În LangSmith, setezi metadatele session_id sau thread_id. Ratezi un tur și acel tur cade din grupare.
Am nevoie de sesiuni dacă tratez doar cereri cu un singur tur?
Probabil că nu. Sesiunile există pentru a corela mai multe trace-uri într-o singură conversație. Dacă fiecare cerere e independentă (un API de clasificare, un sumarizator one-shot), metricile la nivel de trace sunt suficiente. Adaugă sesiuni când ai nevoie de metrici între tururi: rată de rezolvare, tururi până la răspuns sau cost la nivel de conversație. Ghidul nostru de evaluare LLM arată când evaluările la nivel de sesiune își merită efortul.
Versiunea scurtă
Span-urile se imbrică în trace-uri; trace-urile se grupează în sesiuni. Imbricarea e reală, dar specificația structurează doar două dintre cele trei niveluri. gen_ai.conversation.id e un atribut pe care îl setezi tu, nu un span părinte care se propagă. Iar vendorul pe care îl alegi azi numește aceste obiecte altfel decât vendorul la care vei trece peste 18 luni, așa că păstrează-ți vocabularul de span-uri mic și portabil.
Dacă alegi o platformă, începe cu comparația noastră a platformelor de observabilitate. Dacă construiești evaluări deasupra trace-urilor tale, ghidul de evaluare LLM continuă de aici.