RAG peste codul sursă: ce face codul, nu de ce e așa
Codul nu minte despre ce face și tace complet despre motiv. Cum legi implementarea de commituri, review-uri și decizii printr-un lanț de identificatori.

Cineva întreabă de ce serviciul de plăți reîncearcă exact de trei ori și de ce a doua încercare așteaptă opt secunde. Sistemul răspunde cu funcția. Codul e corect, e curent, se execută în producție chiar în clipa asta.
Și nu răspunde la întrebare.
Numărul trei nu e o preferință de stil. E rezultatul unui incident de acum doi ani, al unei discuții de pe un pull request și al unui compromis negociat cu procesatorul de plăți — dintre care niciunul nu apare în fișier.
Celelalte corpusuri din serie mint într-un fel sau altul: câmpurile îmbătrânesc, procedurile rămân în urmă, documentația descrie un sistem schimbat între timp. Codul sursă e singurul care nu poate minți despre ce face, fiindcă se execută, iar execuția e verificabilă. E și singurul care tace complet despre motiv. Mecanismul de bază rămâne neschimbat; ce se schimbă e că răspunsul util aproape niciodată nu stă în corpusul care pare evident.
Corpusul autoritativ care nu conține motivul
Ce e verificabil mecanic și ce nu
Într-o arhivă de documente, „adevărat” înseamnă „cineva a scris asta și nimeni nu a contrazis-o”. Într-un repository, o funcție ori compilează, ori nu; ori trece testele, ori nu; ori e apelată la rulare, ori nu. Ai un oracol pe care niciun alt corpus intern nu ți-l dă.
Acoperă însă o singură clasă de întrebări: ce se întâmplă. „De ce” și „cine a decis” nu au reprezentare în sintaxă. Un model întrebat de ce pragul e trei va produce o justificare plauzibilă, construită din numele variabilei. Nu e o halucinație clasică — fragmentul citat e real și corect. Doar că nu conține răspunsul.
„De ce” stă în trei corpusuri diferite
Motivul se împarte, previzibil, în trei locuri:
- Mesajul de commit — de ce s-a făcut schimbarea, în cel mai bun caz. În cel mai frecvent caz, „fix” sau „update deps”.
- Discuția de pe pull request — obiecțiile, alternativele respinse, condiția pusă de cel care a aprobat. Cel mai dens strat de raționament dintr-o echipă, și singurul în care apare explicit ce nu s-a făcut.
- Documentul de decizie arhitecturală — contextul, opțiunile, consecințele acceptate. Când există.
Nu sunt fațete ale aceluiași document, cum erau câmpurile, proza și istoricul unui ticket. Sunt patru sisteme separate, cu drepturi de acces separate și cicluri de viață separate: codul trăiește în repository, discuția în platforma de găzduire, decizia într-un fișier Markdown pe care nimeni nu-l mai actualizează.
Legătura e un identificator, nu o suprapunere de cuvinte
Aici e diferența de arhitectură. Cele patru corpusuri nu se leagă prin similaritate — vocabularul lor abia se suprapune. Un mesaj de commit spune „reduce retry budget after incident 4412”; funcția conține MAX_ATTEMPTS = 3. Zero cuvinte comune, aceeași decizie.
Ce le leagă e un lanț de identificatori: hash-ul commitului apare în blame, numărul pull requestului în mesajul de merge, numărul incidentului în descrierea PR-ului, iar calea fișierului leagă totul de codul curent. Joncțiunea se rezolvă la ingestie, prin traversarea lanțului, nu la interogare prin similaritate.
Practic: nu construi un index peste „codul plus istoricul”. Construiește un index peste cod, un index peste raționament și un tabel de corespondență între ele.
Chunking-ul pe caractere rupe o structură care există deja
Frontiera sintactică e marcată de autor
Majoritatea corpusurilor cer să ghicești unde se termină o unitate de sens. Codul e inversul: are un arbore sintactic complet, derivabil determinist cu un parser, în care fiecare funcție, clasă și bloc are frontiere exacte.
A tăia la 800 de tokeni peste această structură înseamnă a arunca informația și a o înlocui cu o aproximare. Strategiile generale de chunking discută compromisul dintre dimensiune și context; pe cod, compromisul dispare, fiindcă frontiera corectă e deja scrisă în fișier.
Ce se pierde la o tăietură arbitrară
Trei lucruri, toate tăcute:
- Semnătura se desparte de corp. Un chunk care începe la mijlocul unei funcții conține logică fără nume, fără parametri și fără tipuri. Regăsit, e ilizibil pentru model și pentru om.
- Contextul de încapsulare dispare. O metodă ruptă de clasa care o conține pierde numele clasei, câmpurile la care se referă și, adesea, singura indicație despre domeniu.
- Importurile rămân în alt chunk. Fragmentul folosește un simbol pe care nu-l poți rezolva: nu se vede din ce bibliotecă vine, deci nu se vede dacă e apelul standard sau wrapperul intern cu comportament diferit.
Unitatea corectă e funcția sau metoda, cu semnătura, cu antetul clasei și cu lista importurilor relevante atașate ca preambul. Fișierele scurte rămân întregi. Funcțiile foarte lungi se împart pe blocuri de nivel superior, nu pe număr de caractere.
Vecinătatea utilă e în graf, nu în text
Două funcții care se apelează una pe alta pot să nu aibă niciun cuvânt comun. Două funcții cu nume aproape identice, în module diferite, pot să nu aibă nicio legătură.
De aceea metadatele care contează nu sunt de conținut, ci de relație: cine apelează funcția, pe cine apelează ea, unde e definit fiecare simbol folosit. Un pas de expansiune pe graful de apel, aplicat după regăsirea inițială, aduce contextul de care are nevoie răspunsul — și îl aduce corect, nu prin similaritate.
Ce măsoară de fapt benchmarkurile de căutare în cod
Înainte să alegi un model de embeddings pentru cod, merită să știi ce anume s-a măsurat ca să obții cifra din tabelul de comparație.
Docstringul folosit ca interogare de probă
CodeSearchNet, setul care a definit domeniul, e construit din aproximativ două milioane de perechi comentariu–funcție extrase din depozite publice, în șase limbaje. Interogarea de antrenare e docstringul funcției. Autorii semnalează ei înșiși limita: documentația e scrisă de aceeași persoană, în același moment cu codul, deci folosește același vocabular — spre deosebire de o interogare reală.
Pentru evaluare au construit un set separat, cu 99 de interogări în limbaj natural adnotate de experți. Detaliul care contează pentru tine e cum au fost alese: au pornit din interogări reale de căutare și au eliminat manual pe cele care erau clar cuvinte-cheie tehnice — exemplul dat de autori e numele exact al unei funcții. Adică au scos din setul de evaluare exact tipul de interogare care domină căutarea într-un repository intern.
Interogarea scurtă prăbușește tot
CoIR, benchmarkul de referință apărut ulterior, adună zece seturi de date, opt sarcini de regăsire și paisprezece limbaje de programare, cu peste două milioane de documente în corpus. Un audit publicat în 2026 de o echipă de la Ant Group, odată cu benchmarkul propriu CoREB, îi găsește însă probleme structurale: CodeSearchNet și derivatele sale reprezintă peste 85% din volumul corpusului CoIR și au servit ani la rând drept date de antrenare pentru modelele evaluate; toate cele zece seturi atribuie exact un singur document relevant per interogare, cu scor binar și fără exemple negative dificile, ceea ce reduce metricile de ordonare la un test de tip nimerit sau nu.
Rezultatul empiric care schimbă cel mai mult felul în care citești un tabel de benchmark: pe subsarcina cu interogări scurte, de tip cuvinte-cheie — aproximativ 19 tokeni, formatul cel mai apropiat de căutarea reală a unui developer — toate cele unsprezece modele evaluate coboară la nDCG@10 aproape zero, cu două ordine de mărime sub performanța pe interogări lungi. Autorii notează că nici scalarea modelului, nici adăugarea unui reranker nu închid diferența.
Citește-o ca pe o constrângere de proiectare, nu ca pe un verdict. Regimul în care modelele funcționează bine e cel al interogărilor lungi și descriptive. Regimul în care lucrează oamenii tăi e celălalt.
Ce rămâne util
Rămân bune la ordonarea relativă a modelelor pe sarcini de tip descriere lungă către cod. Nu sunt o predicție a comportamentului pe repository-ul tău, unde interogările sunt scurte, limbajul e adesea unul singur și vocabularul e intern.
Concluzia e aceeași cu cea din hybrid search și reranking, doar mai apăsată: pe cod, componenta lexicală nu e o rafinare. Un simbol e un șir exact, iar potrivirea exactă pe simbol e cea mai frecventă interogare din sistem.
Blame-ul e o capcană, nu un răspuns
Ultimul care a atins linia a rulat un formatter
Reflexul e să folosești git blame ca să afli cine a scris o linie. Într-un repository cu vechime, răspunsul e adesea persoana care a rulat un formatter pe toată baza de cod, într-un singur commit care a atins fiecare fișier.
Git are un mecanism pentru asta din versiunea 2.23: un fișier cu hash-urile commiturilor de reformatare, de obicei .git-blame-ignore-revs, plus configurarea blame.ignoreRevsFile. Blame-ul sare peste ele și arată ultima modificare cu sens.
Pentru ingestie, asta înseamnă un pas obligatoriu: dacă derivi semnale din blame fără lista de commituri ignorate, semnalele tale descriu istoria formatării, nu istoria deciziilor.
Ce expun uneltele de istoric
Datele există și sunt accesibile local, fără niciun serviciu:
Opțiunea -w ignoră modificările de spațiere, iar -C repetat urmărește liniile mutate sau copiate din alte fișiere — două surse frecvente de atribuire greșită. git log -L acceptă și un nume de funcție în locul intervalului de linii, deci îți dă traiectoria unei unități sintactice. E lent pe istorii lungi, fiindcă citește diferența la fiecare commit: se rulează la ingestie, nu la interogare.
„Cine știe codul ăsta” nu se citește din blame
Întrebarea pusă cel mai des despre un fragment necunoscut nu e cine l-a scris, ci pe cine întrebi acum. Sunt lucruri diferite, iar blame-ul răspunde doar la prima, și aceea prost.
Semnale mai bune, în ordinea raportului cost-beneficiu:
- Proprietatea declarată — fișierul de proprietari din repository, dacă e întreținut. E o declarație, deci îmbătrânește; verifică dacă echipa mai există.
- Numărul de review-uri, nu de commituri. Cine a aprobat schimbări pe fișierul ăsta în ultimul an cunoaște codul mai bine decât cine a atins ultima linie.
- Dispersia autorilor. Un fișier cu un singur autor activ e un risc de continuitate; unul cu doisprezece autori și niciun recenzent constant e altceva.
Cunoașterea care nu se recuperează din niciun sistem — de ce s-a abandonat abordarea alternativă — se comportă la fel ca memoria tehnică din cercetare și dezvoltare: valoroasă, rar scrisă, și pierdută odată cu oamenii.
Codul mort arată identic cu codul viu
Patru feluri de cod care nu se execută
Pentru un model de embeddings, toate arată la fel ca restul: aceeași sintaxă, aceleași denumiri, aceeași densitate.
- Cod comentat, lăsat „pentru orice eventualitate” acum trei ani.
- Funcții fără apelanți, rămase după o refactorizare care nu a curățat.
- Ramuri închise de un feature flag care e
falseîn toate mediile de doi ani. - Cod de test și fixtures, care implementează deliberat comportamente pe care producția nu le are.
Un răspuns care citează oricare dintre ele e plauzibil, citat corect și complet greșit. Diferența față de o simplă căutare ratată e că aici cineva copiază un tipar retras.
Semnalul e execuția, nu textul
Testul care separă viul de mort nu se citește din fișier. Se citește din altă parte:
- Acoperirea de test spune ce linii se execută măcar în integrare.
- Telemetria de producție — urme distribuite, profilare continuă, jurnale de apel — spune ce se execută cu trafic real.
- Graful de apel static spune ce e teoretic accesibil, ceea ce e un prag inferior util, nu o dovadă.
- Starea configurațiilor de feature flag pe fiecare mediu spune care ramură e vie azi.
Niciunul nu e perfect. Combinația a două dintre ele e suficientă ca să marchezi un chunk drept probabil neexecutat.
Ce excluzi și ce marchezi
Excluzi la ingestie fișierele generate, dependențele vendorizate și artefactele de build: volum mare, repetitiv, care domină vecinătatea oricărei interogări.
Marchezi, nu excluzi, codul de test, cel aparent fără apelanți și ramurile închise. Un chunk marcat „fără apelanți detectați, ultima modificare acum 3 ani” e un rezultat utilizabil. Același text, prezentat curat, e o capcană — iar regulile de format ale răspunsului sunt în cum arată un răspuns bun de RAG.
Indexul nu e repository-ul
Ce rămâne pe dinafară, tăcut
Dacă te bazezi pe căutarea de cod a platformei ca strat de regăsire, limitele publicate contează, fiindcă niciuna nu produce eroare — produce absență.
Documentația GitHub pentru căutarea de cod enumeră, la momentul scrierii: codul vendorizat și cel generat sunt excluse; fișierele goale și cele peste 350 KiB sunt excluse; liniile peste 1.024 de caractere sunt trunchiate; fișierele cu mai mult de o linie peste 4.096 de octeți sunt excluse; doar fișierele codificate UTF-8 intră; doar ramura implicită e indexată; interogarea e limitată la 1.000 de caractere, iar rezultatele la 100 per căutare.
Două surprind echipele. Detectarea codului vendorizat e euristică, pe baza căii: directoare numite external sau third_party sunt excluse chiar dacă sunt cod propriu. Iar limita de 100 de rezultate face imposibilă orice sarcină de tip inventar complet.
Pentru un index propriu, limitele dispar, dar apare cealaltă problemă: codul se schimbă de mai multe ori pe zi. Mecanica e cea din prospețime, ștergeri și reindexare, cu o simplificare — ai deja un jurnal exact al modificărilor, deci reindexarea incrementală se face pe diferența dintre două commituri, nu prin rescanare.
Ce întrebare, ce mecanism
| Ce se întreabă | Unde stă răspunsul | Ce iese greșit dacă cauți doar în cod |
|---|---|---|
| Ce face funcția asta | retrieval pe frontieră sintactică | de obicei corect |
| De ce pragul e 3 și nu 5 | mesaj de commit, discuție de PR, document de decizie | o justificare inventată din numele variabilei |
| Unde e definit simbolul X | index lexical, potrivire exactă | o funcție cu nume asemănător din alt modul |
| Se mai folosește funcția asta | graf de apel plus telemetrie de execuție | cod mort, citat ca API curent |
| Ce s-a schimbat aici și când | git log -L pe unitatea sintactică | un rezumat al stării curente, fără istorie |
| Pe cine întreb despre modulul ăsta | proprietate declarată plus istoric de review | autorul commitului de reformatare |
Un singur rând e un caz curat de regăsire semantică. Restul cer fie un index lexical, fie o traversare de graf, fie un apel către alt sistem — iar pragurile de la care nimic din toate astea nu se justifică sunt în când nu folosești RAG.
Măsurare și pilot
Setul de referință: decizii deja luate
Adevărul de referință există deja în repository, sub forma schimbărilor care au fost anulate. Ia douăzeci de commituri de tip revert, sau douăzeci de pull requesturi în care recenzentul a cerut o schimbare de abordare.
Pentru fiecare, formulează întrebarea așa cum ar fi pus-o cineva chiar înainte de a scrie codul greșit — „de ce nu procedăm așa aici” — și verifică dacă sistemul ar fi adus discuția, incidentul sau documentul de decizie care conțineau răspunsul. Un sistem care citează codul curent, corect, fără raționamentul care l-a produs, se numără ca eșec.
Patru indicatori
Acuratețea rutării. Din întrebări, în câte a ales sistemul mecanismul potrivit — potrivire exactă, regăsire semantică, traversare de graf sau lanțul către istoric. Dacă e slabă, restul se măsoară degeaba.
Rata de cod neexecutabil. Din fragmentele returnate ca exemplu de urmat, în câte codul era comentat, fără apelanți sau într-o ramură închisă. Ar trebui să fie zero prin construcție.
Acoperirea motivului. Din întrebările de tip „de ce”, în câte a ajuns răspunsul la o sursă de raționament, nu doar la implementare.
Precizia pe simbol. Din interogările care conțin un identificator exact, în câte definiția lui a fost pe primul loc. E metrica pe care benchmarkurile publice nu o măsoară și utilizatorii tăi o observă imediat.
Cadrul general rămâne cel din cum evaluezi un sistem RAG; ce se adaugă e că două din patru măsoară infrastructura din jurul codului, nu regăsirea în el.
Pilotul: un serviciu, doi ani de istoric
Un singur serviciu, cu limite clare și cu istoric suficient cât deciziile să fi avut timp să fie uitate. Extrage întâi lanțul de identificatori — commit către pull request către incident — și numără câte commituri ajung la o discuție cu conținut real. Dacă mesajele sunt „fix” și PR-urile trec fără comentarii, corpusul de raționament nu există, iar proiectul e unul de proces, nu de regăsire.
Livrabilul primelor șase săptămâni e o cifră: din patruzeci de întrebări reale ale echipei, în câte a ajuns răspunsul și la cod, și la motivul lui. Sub cincisprezece, problema e la lanțul de identificatori, nu la model. Restul artefactelor operaționale — proceduri, runbook-uri, jurnale de incident — au altă constrângere dominantă și sunt tratate în RAG pentru IT și operațiuni.
Întrebări frecvente
Pot folosi un model general de embeddings sau am nevoie de unul specializat pe cod?
Pentru regăsire cod-la-cod, diferența e mare și consecventă în evaluările publice: modele mici antrenate pe cod bat modele generale de câteva ori mai mari. Pe întrebări în limbaj natural, diferența se micșorează. Testează cu interogările tale scurte, nu cu cele lungi din benchmark.
Cum tratez un monorepo cu cinci limbaje?
Parsere separate pentru segmentare, index comun pentru regăsire, limbajul ca metadată filtrabilă. Segmentarea e specifică limbajului, deci nu poți avea un singur segmentator; regăsirea nu are motiv să fie separată, iar interogările traversează frecvent limbajele.
Merită să indexez și istoricul complet, sau doar starea curentă?
Doar starea curentă pentru cod, plus istoricul deciziilor ca strat separat. Un index peste toate versiunile fiecărui fișier îți dă un corpus în care aceeași funcție apare de patruzeci de ori și fiecare interogare returnează versiuni vechi ale aceluiași lucru.
Cum împiedic scurgerea de secrete în răspunsuri?
Prin scanare la ingestie, nu prin instrucțiuni în prompt. Chei și șiruri de conexiune rămân în istoric chiar după ce au fost șterse din starea curentă. Rulează un detector de secrete peste ce indexezi și tratează permisiunile ca în controlul accesului în RAG.
Ce fac dacă mesajele de commit sunt inutile?
Le tratezi ca pe un semnal absent și treci la stratul următor. Discuția de pe pull request supraviețuiește adesea unei culturi slabe de commituri, fiindcă e scrisă pentru un cititor prezent. Dacă nici acolo nu e nimic, pilotul ți-a dat deja un rezultat util: motivul nu e scris nicăieri, iar asta se rezolvă cu o convenție, nu cu un index. Există și corpusuri cu problema inversă, în care motivul e consemnat de două ori, în versiuni care se contrazic — cazul dosarelor de instanță, tratat în RAG pe dosarul de litigiu.
Începe cu lanțul de identificatori, nu cu codul. Ia un singur serviciu și numără câte commituri din ultimul an duc la o discuție cu conținut real. Cifra aceea îți spune, înainte de orice decizie de arhitectură, dacă ai un corpus de raționament sau doar un corpus de implementare.
Dacă vrei să verifici ce s-ar putea răspunde efectiv din repository-ul echipei tale, trimite un mesaj. Restul cazurilor pe corpusuri de companie sunt în hubul Cazuri de utilizare enterprise, sursele de tip documentație tehnică au propriul hub, iar imaginea completă a serviciului e în AI & RAG.
Surse
- CoIR: A Comprehensive Benchmark for Code Information Retrieval Models — Association for Computational Linguistics, 2025
- CodeSearchNet Challenge: Evaluating the State of Semantic Code Search — arXiv, 2019
- Beyond Retrieval: A Multitask Benchmark and Model for Code Search — arXiv, 2026
- About GitHub Code Search — GitHub Docs, 2026
- git-log Documentation — Git, 2026
Andrei Badulescu
Fondator & Software ArchitectConstruieș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 →Articole conexe

RAG pe procedurile de urgență: documentul se execută
Planul de intervenție se aplică în minute, prin fum și fără curent. Ce cere asta de la un sistem de retrieval: mod degradat, rol pe tură, cronometru.

RAG pe nomenclatorul arhivistic: ștergerea ca obligație
Pe o arhivă, răspunsul corect poate fi că documentul nu mai trebuie să existe. Cum distinge sistemul o absență legitimă de o pierdere reală.

RAG pe documentația SSM: absența dovezii e chiar fapta
„Nu găsesc fișa" acoperă trei fapte diferite, cu consecințe diferite. Pe documentația SSM, absența unei înregistrări e ea însăși contravenția.
Insights pentru companii
care construiesc
Articole noi despre ERP, AI, agenți și pSEO, direct pe email. Fără spam.