RAG peste tickete de proiect: câmpul minte, comentariul știe

Statusul e neschimbat de trei luni, iar adevărul stă în ultimul comentariu. Cum tratezi joncțiunea dintre câmpuri, proză și istoric de stări.

Andrei Badulescu
Andrei Badulescu25 iulie 2026 · Actualizat 28 iulie 2026 · 16 min citit
Ilustrație abstractă: un ticket cu câmpuri stale în prim-plan și firul de comentarii recent care îl contrazice

Cineva întreabă sistemul de ce a întârziat livrarea modulului de facturare. Răspunsul vine cu citat dintr-un ticket: descrierea spune că integrarea cu ANAF e blocată de un certificat expirat, statusul e „In Progress”, assignee-ul e cineva care a plecat din firmă în februarie.

Toate trei sunt scrise în ticket. Două din trei sunt false.

Certificatul s-a rezolvat acum unsprezece săptămâni — scrie în al șaptelea comentariu, pe care nu l-a mai citit nimeni. Statusul a rămas neschimbat fiindcă nimeni nu închide tichete pe care le-a uitat. Iar ticketul a fost redeschis de două ori între timp, ceea ce nu apare nicăieri în text: stă într-un jurnal de modificări pe care indexul nu-l atinge.

Un sistem de RAG peste un tracker de proiecte lucrează cu prima sursă parțial structurată din serie — și cu prima care își contrazice propriile câmpuri. Mecanismul rămâne același. Ce se schimbă e că adevărul nu stă într-un strat, ci în relația dintre trei: câmpuri, proză și istoric de stări.

Două straturi care nu se văd unul pe altul

Retrieval-ul ignoră câmpul, filtrul ignoră proza

Un ticket are, fizic, două naturi. Un strat structurat — status, assignee, prioritate, etichete, componentă, sprint — cu valori dintr-o mulțime închisă. Și un strat de proză liberă: titlul, descrierea, firul de comentarii.

Uneltele pe care le ai tratează câte unul singur. Căutarea semantică vectorizează textul și nu are ce face cu un câmp de tip enum: „In Progress” și „Done” sunt, ca vecinătate lexicală, două expresii scurte de dicționar, și complet opuse ca înțeles operațional. Invers, filtrul din tracker știe exact ce tichete au prioritatea maximă, dar nu poate răspunde la „ce blochează de fapt integrarea”.

Fiecare strat, luat separat, e o problemă rezolvată. Nimeni nu are dificultăți cu un filtru pe câmpuri.

Problema e joncțiunea

Întrebările reale traversează. „Ce probleme de integrare am avut cu clienții din retail anul trecut și cum s-au rezolvat” cere un filtru pe etichetă și client, o regăsire pe descriere și comentarii, apoi o agregare pe rezoluție. Nici un pas singur nu răspunde.

Un sistem care indexează textul concatenat cu câmpurile — tiparul implicit — pierde exact aici. Câmpurile devin cuvinte, prioritatea devine un token, iar ce ar fi trebuit să fie un filtru dur se transformă într-o preferință slabă de similaritate. La retrieval, un tichet cu câmpul greșit și textul potrivit bate un tichet cu câmpul corect și textul aproximativ. În operațiuni vrei fix inversul.

Unitatea de indexare: ticketul, proiectat pe trei fațete

Regula practică e să nu forțezi un singur document. Un ticket produce trei artefacte cu funcții diferite:

  • Fațeta structurată — câmpurile, ca metadate filtrabile, niciodată ca text căutabil. Se aplică înainte de retrieval, nu după.
  • Fațeta narativă — titlu, descriere, comentarii, segmentate ca text. Aici se face regăsirea semantică.
  • Fațeta temporală — istoricul de modificări, derivat în semnale numerice. Nu se indexează ca text deloc.

Pregătirea stratului narativ urmează regulile din pregătirea documentelor pentru o bază de cunoștințe. Ce se adaugă e că celelalte două fațete nu sunt documente și nu trebuie tratate ca atare.

Ticket descompus în trei fațete: câmpuri structurate, proză liberă și istoric de stări, fiecare cu alt mod de interogare

Câmpul minte, comentariul știe

Statusul e o intenție veche, nu o stare

Un câmp de status descrie ce s-a intenționat la ultima atingere, nu ce e adevărat acum. Distanța dintre cele două crește cu cât ticketul stă deschis, fiindcă actualizarea câmpului e o acțiune separată, pe care nimeni nu o face gratis.

Comentariul, în schimb, se scrie când se întâmplă ceva. E costisitor să nu-l scrii — cineva așteaptă un răspuns. Fluxul de comentarii are deci o prospețime pe care câmpurile nu o au, chiar dacă e mai greu de citit.

Asta inversează regula de la cazul precedent. La RAG peste e-mail, atașamentul bătea corpul mesajului, fiindcă versiunea finală migra în fișier. Aici e opusul: stratul care pare autoritativ, fiindcă e scurt și structurat, e cel mai vechi.

Care câmp e observat și care e declarat

Că un câmp e nesigur nu e o impresie. Se vede în datele publice.

GitBugs, un set de peste 150.000 de rapoarte de defecte din nouă proiecte open-source, publicat de Avinash Patil în aprilie 2025 și agregat din GitHub, Bugzilla și Jira, raportează o corelație care nu ar trebui să existe: tichetele cu prioritate mare se închid mai des ca rezolvate sau ca „nu se face”, iar cele cu prioritate mică ajung mai des închise ca duplicat ori rămân nerezolvate. Prioritatea nu descrie urgența — descrie ce s-a întâmplat cu tichetul.

Același set arată și limita predicției din text: un model de regresie antrenat să estimeze timpul până la rezolvare a obținut un R² de −0,09, adică sub performanța unui model care ar returna mereu media. Durata nu se citește din descriere.

Concluzia operațională nu e că nu ai voie să folosești câmpurile. E că trebuie să separi câmpurile observate de cele declarate. Data creării e observată. Rezoluția e observată. Prioritatea, statusul și estimarea sunt declarate — și îmbătrânesc.

Cum se rutează, pe tip de întrebare

Ce se întreabăCum se răspundeCe iese greșit pe similaritate pură
Ce tichete sunt deschise pe componenta Xfiltru pe câmp, fără retrievaltichete închise, cu text mai potrivit
Care e cauza problemei de sincronizareretrieval pe stratul narativde obicei corect
S-a mai întâmplat și cum s-a rezolvatfiltru pe rezoluție, apoi retrievalduplicate și abandonate, oferite ca soluții
De câte ori s-a redeschis PROJ-1234agregare pe istoricul de stăriaproximare inventată sau răspuns lipsă
Cine lucrează acum la astainterogare live în trackerassignee vechi, citat cu încredere
Cât durează în medie tipul ăsta de problemăagregare pe câmpuri de timpmedia unei mostre nereprezentative

Doar al doilea rând e un caz curat de regăsire semantică. Restul cer fie un pas structurat înaintea retrieval-ului — tiparul din agentic RAG și multi-hop, unde o interogare cere un pas de filtrare înainte de căutare — fie ceva care nu are de-a face cu documente. Rândul cinci nu e retrieval deloc, iar pragurile de decizie sunt în când nu folosești RAG.

Istoricul de stări e un semnal, nu text

Ce expun trackerele

Vestea bună e că datele există și sunt accesibile prin API. Vestea proastă e că nu sunt text și nu ajung niciodată în index dacă nu le ceri explicit.

Jira păstrează modificările ca jurnal de schimbări. În API-ul REST se obțin fie cu parametrul expand=changelog pe endpoint-ul de issue, fie prin endpoint-ul dedicat de changelog pe Jira Cloud. Fiecare intrare are autorul, momentul, câmpul modificat și perechea valoare veche / valoare nouă. Atenție la implementare: pe Jira Data Center endpoint-ul dedicat nu e disponibil, iar varianta cu expand returnează un număr limitat de intrări per cerere — pe tichetele lungi, adică pe cele interesante, trebuie paginat.

GitHub expune același lucru ca flux de evenimente, prin endpoint-ul de timeline al unui issue. Evenimentele includ etichetări, reasignări, referințe din commit-uri și, de la sfârșitul lui 2024, marcarea explicită a unui issue ca duplicat al altuia, cu eveniment propriu în timeline.

Verifică versiunea API-ului înainte să construiești peste ea. Forma răspunsului și numele câmpurilor se schimbă, iar un extractor care presupune o schemă veche eșuează tăcut.

„De câte ori s-a redeschis” nu e o întrebare de similaritate

Întrebările pe care oamenii le pun despre tichete vechi sunt, în bună parte, întrebări despre traiectorie, nu despre conținut. De câte ori s-a redeschis. Cât a stat blocat. Câți oameni au trecut pe el. Când a sărit prima dată de la prioritate mică la mare.

Nici una nu are răspuns în text. Toate au răspuns în jurnalul de modificări, dar numai după ce le calculezi — ca numere, la ingestie, nu la interogare.

Cinci semnale derivate

  • Numărul de redeschideri — tranziții dintr-o stare terminală înapoi într-una activă. Cel mai bun indicator intern al unei probleme prost înțelese.
  • Timpul petrecut în fiecare stare, nu doar durata totală. Trei zile în dezvoltare și nouăzeci în așteptare descriu altceva decât invers.
  • Numărul de reasignări — proxy pentru „nimeni nu știe al cui e”.
  • Vechimea ultimei modificări reale, ignorând atingerile automate. Un bot care mută sprintul nu e activitate.
  • Decalajul dintre ultima schimbare de status și ultimul comentariu. Când al doilea e mult mai recent, câmpul e aproape sigur depășit.

Ultimul schimbă cel mai mult calitatea răspunsurilor, pentru cel mai mic efort. Un sistem care spune „status neschimbat de 94 de zile, ultimul comentariu acum 6 zile” e utilizabil. Unul care afișează doar valoarea câmpului e o capcană — iar regulile de format ale răspunsului sunt în cum arată un răspuns bun de RAG.

Mecanica de actualizare a indexului când tichetele se mișcă zilnic e cea din prospețime, ștergeri și reindexare; ce se adaugă aici e că metadatele se schimbă mai des decât textul.

Traiectoria unui ticket prin stări succesive, cu redeschideri și reasignări care rămân invizibile în textul indexat

Ticketul închis nu e ticketul rezolvat

Trei feluri de închidere, identice semantic

Un tichet închis ca duplicat, unul închis pentru că nu se va face și unul închis cu o remediere reală arată la fel pentru un model de embeddings. Aceleași cuvinte, același format, aceeași lungime. Diferența stă într-un câmp de rezoluție, adică exact în stratul pe care regăsirea semantică îl ignoră.

Pentru cineva care caută o soluție, cele trei înseamnă lucruri opuse. Închiderea ca duplicat spune că răspunsul e în altă parte și îți dă adresa. Abandonarea spune că problema e cunoscută și acceptată ca atare. Doar a treia conține o remediere.

Un sistem care returnează un tichet închis ca duplicat drept precedent rezolvat produce cel mai frustrant tip de eroare: plauzibil, citat corect, complet inutil.

Ce spun datele publice despre duplicate

Cifra care circulă cel mai des e că aproximativ 40% din rapoartele de defecte sunt duplicate. Are o sursă reală — o lucrare citată frecvent raportează un interval de 35,8%-41,6% — dar măsurătorile per proiect spun altceva.

În setul GitBugs, rata de duplicate variază între 2,0% pe HBase și 28,2% pe VS Code, cu Firefox la 21,7% și Thunderbird la 27,6%. Un studiu empiric separat, realizat la Dalhousie University pe 92.854 de rapoarte din Eclipse, Firefox și trei proiecte mobile, raportează 6,60% pentru Eclipse, 20,53% pentru Firefox și 10,56% pentru mobile.

Un ordin de mărime între extreme, pe aceeași sarcină. Nu e o contradicție în literatură — e semnalul că rata de duplicate e o proprietate a procesului tău de raportare, nu a domeniului. O cifră preluată dintr-un material de produs, fără proiectul pe care s-a măsurat, nu spune nimic despre trackerul tău.

Ce nu găsește similaritatea

Aceeași echipă de la Dalhousie a împărțit duplicatele în două categorii, după cât de asemănătoare sunt textual, și a măsurat cât de bine le găsesc tehnicile existente. Între 19% și 23% dintre duplicate sunt textual disimilare: același defect, descris cu cuvinte care nu se suprapun.

Pe acelea, performanța cade vizibil. Pe Firefox, BM25 aduce duplicatul în primele zece rezultate în 39,92% din cazurile textual asemănătoare și în 26,56% din cele textual disimilare. Analiza manuală explică de ce: 44,61% dintre rapoartele textual disimilare nu conțin pași de reproducere, față de 13% la cele asemănătoare — lipsesc exact componentele care ar fi produs suprapunerea lexicală.

Iar embeddings-urile moderne nu rezolvă asta singure. În experimentul de deduplicare din GitBugs, cu Sentence-BERT peste titlurile a 300 de tichete, majoritatea scorurilor de similaritate din primele zece rezultate au rămas sub 0,5, la un Recall@10 de 0,61.

Citește cifra ca prag de proiectare, nu ca verdict: aproximativ o treime din duplicatele cunoscute nu apar în primele zece. Un sistem construit pe premisa că similaritatea găsește precedentul va rata tocmai cazurile în care oamenii au descris aceeași problemă altfel.

Identificatorul nu e semantică

PROJ-1234, stack trace, versiune

Un corpus de tichete e plin de șiruri fără înțeles distribuțional: chei de tichet, hash-uri de commit, nume de fișiere, versiuni, coduri de eroare. Un model de embeddings le tratează ca tokeni rari și le pierde în zgomot; un index lexical le găsește exact.

Oamenii caută prin ele constant. „Ce s-a întâmplat cu PROJ-1234” nu e o întrebare semantică, e o căutare de identificator. Iar când cineva lipește un stack trace, potrivirea utilă e pe linia de excepție, nu pe „sensul” ei.

De asta un tracker e unul dintre cele mai clare cazuri în favoarea combinației dintre căutarea lexicală și cea semantică, cu o reordonare peste ambele — mecanica e în hybrid search și reranking. Fără componenta lexicală, sistemul răspunde cu tichete asemănătoare la o întrebare despre un tichet anume.

Filtrul se aplică înainte, nu după

Ordinea contează mai mult decât alegerea modelului. Un filtru pe câmpuri aplicat după retrieval alege dintr-un top-k deja stricat: dacă primele cincizeci de rezultate sunt tichete închise ca duplicat, filtrarea lor lasă un set gol, nu unul mai bun.

Concret: clasifici întrebarea, extragi din ea constrângerile structurate — proiect, componentă, interval de timp, rezoluție — restrângi corpusul, și abia apoi cauți semantic în ce a rămas. Pasul de extragere e ieftin și e locul unde se câștigă cel mai mult.

Rutarea unei interogări peste un tracker: extragerea constrângerilor, filtrul structurat și abia apoi căutarea semantică

Permisiunea se moștenește din proiect, cu două rezerve

Un tracker are, spre deosebire de o arhivă de documente, un model de acces explicit: proiecte, roluri, tipuri de tichet cu vizibilitate restrânsă. Tentația e să-l copiezi ca atare în index, și în mare parte funcționează.

Rezervele sunt două. Comentariile interne pe un tichet altfel vizibil au adesea alt regim decât ticketul care le găzduiește. Iar tichetele de securitate și cele de personal trăiesc în același sistem cu restul, cu restricții pe care le pierzi la prima extragere neatentă. Ca și la corespondență, unitatea de permisiune se calculează, nu se citește dintr-un singur câmp; modelul general e cel din controlul accesului în RAG.

Măsurare și pilot pe un singur proiect

Patru indicatori

Acuratețea rutării. Din întrebări, în câte a ales sistemul calea corectă — filtru, retrieval sau agregare. E metrica de arhitectură; dacă e slabă, restul se măsoară degeaba.

Rata de precedent fals. Din tichetele returnate ca soluții, în câte rezoluția era duplicat sau abandonare. Ar trebui să fie zero prin construcție; când nu e, filtrul se aplică după retrieval undeva în lanț.

Marcarea câmpurilor depășite. Din răspunsurile care citează un câmp declarat, în câte apare și vechimea lui. Nu e o metrică de regăsire, e una de format, cu impact direct asupra deciziilor luate pe baza răspunsului.

Acoperirea traiectoriei. Din întrebările despre istoric — redeschideri, timp în stare, reasignări — în câte a răspuns sistemul cu un număr calculat, nu cu o aproximare din text.

Cadrul general rămâne cel din cum evaluezi un sistem RAG; ce se adaugă e că trei din patru măsoară stratul structurat, nu textul.

Setul de evaluare vine din tichetele redeschise

Adevărul de referință e deja în tracker. Alege treizeci de tichete cu cel puțin o redeschidere: fiecare e o problemă pe care organizația a crezut că a rezolvat-o și nu o rezolvase.

Pentru fiecare, pui întrebarea așa cum ar fi formulat-o cineva chiar înainte de a doua deschidere și verifici dacă sistemul ar fi adus tichetul original — și dacă l-ar fi prezentat cu rezoluția și cu istoricul, nu doar cu descrierea. Un tichet redeschis de trei ori, oferit ca precedent rezolvat, e eșecul canonic al acestui caz de utilizare.

Pilotul: un proiect, un tip de tichet

Un singur proiect, cu convenții stabile de etichetare, și un singur tip de tichet — de obicei defecte, unde rezoluția e informativă. Extrage întâi changelog-ul, nu textul: dacă jurnalul de modificări e prea sărac cât să derivi cele cinci semnale, restul nu merită construit.

Livrabilul primelor șase săptămâni e o cifră: din cincizeci de întrebări reale, în câte răspunsul a fost corect și însoțit de starea reală a tichetului. Sub treizeci, problema e la rutare, nu la model.

Cunoașterea pe care nimeni nu o caută proactiv — de ce s-a ales o variantă, ce a mers prost — nu stă însă în tracker, ci în lecțiile învățate din proiecte, unde declanșatorul e complet diferit. Iar dacă întrebările tale sunt despre incidente în curs, nu despre tichete închise, constrângerea dominantă e timpul, nu structura: cazul e tratat în RAG pentru IT și operațiuni.

Întrebări frecvente

Pot să indexez direct exportul din tracker, cu tot cu câmpuri?

Poți, dar obții un sistem în care prioritatea e un cuvânt. Câmpurile trebuie să rămână metadate filtrabile, iar filtrul să se aplice înaintea căutării semantice. Altfel un tichet închis concurează, la scor, cu unul deschis.

Cum tratez tichetele cu descrieri în engleză și comentarii în română?

Ca metadată la nivel de comentariu, nu de tichet. Într-o echipă mixtă, aceeași unitate conține ambele limbi, iar detectarea la nivel de tichet alege greșit în jumătate din cazuri.

Merită să import și tichetele din trackerul vechi, migrat?

Doar dacă poți importa și istoricul de stări. Un tichet fără jurnal de modificări e un text cu câmpuri înghețate la momentul migrării: pare valid și e cel mai depășit conținut din tot corpusul. Marchează-l explicit sau lasă-l afară.

Ce fac cu tichetele generate automat — alerte, joburi eșuate, boți?

Le excluzi la ingestie, după autor și tipar de titlu. Sunt formulare, se repetă de mii de ori și domină vecinătatea semantică a oricărei întrebări despre erori.

Poate sistemul să sugereze assignee sau prioritate la triaj?

Sugestia, da; decizia, nu. Istoricul de asignare e bogat, dar câmpurile pe care ai antrena sunt exact cele nesigure — prioritatea declarată descrie mai degrabă ce s-a întâmplat cu tichetul decât cât de urgent era. Ține-le ca propuneri vizibile, cu tichetele similare atașate, și lasă acuratețea să se acumuleze câteva luni înainte de a automatiza ceva.


Începe cu jurnalul de modificări, nu cu textul. Extrage, pe un singur proiect, câte tichete s-au redeschis, cât au stat în fiecare stare și cât de vechi e ultima schimbare de status față de ultimul comentariu. Dacă decalajul e mare pe majoritatea tichetelor deschise, ai deja răspunsul la ce trebuie să facă sistemul înainte de a indexa un singur cuvânt.

Dacă vrei să verifici dacă trackerul firmei suportă un index sau doar rapoarte mai bune, trimite un mesaj. Restul cazurilor pe corpusuri de companie sunt în hubul Cazuri de utilizare enterprise, iar imaginea completă a serviciului în AI & RAG.

Surse

  1. GitBugs: Bug Reports for Duplicate Detection, Retrieval Augmented Generation, Triage, and MorearXiv, 2025
  2. Towards Understanding the Impacts of Textual Dissimilarity on Duplicate Bug Report DetectionarXiv, 2022
  3. How to analyze the history or changelog of an Issue in JiraAtlassian, 2025
  4. REST API endpoints for timeline eventsGitHub, 2026
Distribuie
Andrei Badulescu
Despre autor

Andrei Badulescu

Fondator & Software Architect

Construiește sisteme B2B la BaseTech — ERP la comandă, platforme SaaS, agenți AI și arhitecturi programmatic SEO. Scrie despre deciziile tehnice din spatele lor: stack, trade-off-uri și ce ține la scară.

Vezi profilul autorului →
Continuă
Newsletter

Insights pentru companii
care construiesc

Articole noi despre ERP, AI, agenți și pSEO, direct pe email. Fără spam.