Sari la conținut
megapromotingHai să discutăm

Expertiză · Baze de cunoștințe și căutare semantică

Construim corpusul din care un asistent are voie să răspundă, cu reguli despre ce intră, cum se împarte, cum se caută și ce se întâmplă când sursa se schimbă.

Un asistent care răspunde clienților este exact atât de bun pe cât e corpusul din care citește. Ne ocupăm de corpus: extragerea din documente și site, împărțirea în fragmente, indexarea, regăsirea, poarta de aprobare umană și reîmprospătarea. Include răspunsul la întrebarea când NU merită căutare semantică.

Construit dejaExistă implementări proprii pentru fiecare etapă, dar starea lor diferă și trebuie spusă pe față. Ce rulează cu vectori proprii: memoria unui board intern folosit zilnic, cu coloană `vector(1536)`, index `ivfflat` pe distanță cosinus, prag de similaritate 0,35 și o buclă care re-indexează la 30 de minute doar ce s-a schimbat. Ce rulează cu vectori la furnizor: baza de cunoștințe a agenților vocali, sincronizată către motorul de regăsire al furnizorului cu fragmente de 500 și suprapunere de 100. Ce rulează în producție **fără** vectori: baza de cunoștințe a platformei de asistenți text — extragere, împărțire, configurare per asistent și regăsire hibridă sunt scrise și funcționează, dar calea de indexare scrie coloana de vector cu `null`, deci scorul hibrid degradează la lexical, iar codul își spune singur asta prin eticheta `keyword-only`. Ce e scris și încă nu e pornit în producție: pipeline-ul propriu pe `pgvector` al platformei vocale, a cărui migrare poartă în antet mențiunea „LOCAL ONLY — do not push to prod”. Un serviciu se vinde cu harta asta pe masă, nu fără ea.

Un asistent care vorbește cu clienții nu „știe” nimic. La fiecare întrebare, cineva caută în prealabil câteva fragmente de text și i le pune în față, iar el formulează răspunsul din ele. Calitatea răspunsului se decide aproape integral în etapa aceea de căutare, nu în alegerea modelului. De aceea serviciul acesta este despre corpus și despre regăsire, nu despre model.

Munca are patru părți care se strică independent una de alta. Extragerea: din ce fișiere și ce pagini scoatem text utilizabil, și ce facem când nu putem. Împărțirea: cât de mari sunt fragmentele, cât se suprapun, unde se taie o frază. Regăsirea: cum se aleg fragmentele pentru o întrebare anume, câte, și după ce criteriu. Reîmprospătarea: ce se întâmplă când documentul-sursă se modifică. Un sistem poate fi impecabil la trei dintre ele și inutil din cauza celei de-a patra.

Există o etapă în plus, pe care mulți o sar și care schimbă complet rezultatul: poarta de aprobare. Un corpus care crește singur, din conversații, ajunge repede să conțină răspunsuri pe care nimeni nu le-a validat. Bucla pe care am construit-o merge invers: când agentul spune „nu am această informație”, întrebarea se înregistrează cu starea „în așteptare”, un om primește o notificare și răspunde, iar răspunsul lui trece în starea „aprobat” și abia atunci intră în corpus. Cunoașterea crește din lipsuri reale, cu un om în lanț.

Și partea nepopulară: căutarea semantică nu e întotdeauna alegerea corectă. Pentru o platformă publică proprie am refuzat-o deliberat. Corpusul e propriul text al platformei, împărțit în pasaje scurte care poartă fiecare pagina din care vin, punctate prin suprapunere de cuvinte. Fără vectori, fără depozit vectorial, fără o cerere suplimentară în rețea — și, mai important, fără nicio sursă din afara depozitului. Asistentul nu poate decât să repete ce scrie deja pe site, iar orice răspuns se verifică într-o pagină scrisă de un om. Acesta este întregul mecanism anti-invenție.

Ce cuprinde

Lucrarea, pe componente

Extragem text și spunem clar când nu putem

Din text simplu, markdown, jurnale, CSV, JSON, DOCX și PDF. DOCX-ul se despachetează ca arhivă și se citește documentul XML din interior; PDF-ul trece prin extractorul standard cu păstrarea așezării în pagină, iar dacă acesta lipsește există o rezervă care citește operatorii de text direct din fișier. Cazul important e cel în care nu merge: un PDF scanat, care e o imagine, nu conține text selectabil, iar sistemul întoarce un mesaj explicit — încărcați DOCX sau text, ori treceți fișierul prin recunoaștere optică înainte. Recunoaștere optică nu avem implementată și nu ne prefacem că avem.

Împărțim în fragmente cu suprapunere, tăind între fraze, nu prin ele

Valorile implicite pe care le folosim: 500 de unități lexicale cu 50 de suprapunere pentru pipeline-ul vocal, 1.200 de caractere cu 150 de suprapunere pentru cel text; pe latura furnizorului vocal, 500 cu 100. Suprapunerea există pentru că o frază tăiată la mijloc între două fragmente devine inutilizabilă pentru amândouă. Separatorul de fraze e scris pentru limbile cu care lucrăm: recunoaște sfârșitul de frază urmat de o majusculă latină **sau chirilică**, inclusiv diacriticele românești. Un separator scris pentru engleză taie prost în română și și mai prost în rusă.

Indexăm cu vectori acolo unde am pornit-o, și spunem unde nu

Modelul implicit de reprezentare este `text-embedding-3-small`, cu 1.536 de dimensiuni, cerut prin poarta noastră de modele, nu direct de la furnizor — astfel se poate schimba furnizorul fără să se atingă codul bazei de cunoștințe. În bază, coloana e de tip vector cu index `ivfflat` pe distanță cosinus. Există o strategie de rezervă scrisă în migrare: dacă extensia vectorială nu e disponibilă, schema se creează oricând cu index trigram pe text, iar regăsirea comută pe similaritate lexicală. Nu e ipotetic — a fost scrisă tocmai pentru că drepturile pe extensie nu sunt garantate pe orice instalare.

Regăsire hibridă, cu ponderile la vedere

Scorul final este 0,7 din similaritatea vectorială plus 0,3 din potrivirea de cuvinte, cu partea lexicală normalizată. Se punctează un set de candidați și se rețin primele fragmente, iar contextul trimis modelului taie fiecare fragment la o lungime maximă, ca un singur document lung să nu ocupe tot spațiul. Configurația e per asistent, în bază: regăsire pornită sau oprită, număr maxim de fragmente pe întrebare (implicit 5, limitat între 1 și 20), prag de similaritate, tip de căutare — semantică, lexicală sau hibridă — și ponderea părții lexicale.

Poarta de aprobare: cunoașterea crește din lipsuri, cu un om în lanț

Când agentul răspunde că nu are informația, întrebarea se scrie într-un tabel cu starea „în așteptare”, marcată cu canalul din care a venit — voce sau widget. Un om primește notificarea și răspunde direct la mesaj; un webhook preia răspunsul, trece intrarea în starea „aprobat”, iar de acolo intră în contextul agentului. Stările posibile sunt patru: în așteptare, aprobat, ignorat, duplicat — iar duplicatele se leagă de întrebarea originală printr-o referință, ca să nu se acumuleze zece formulări ale aceleiași lipse. Livrarea către agent se face o singură dată per intrare aprobată.

Reîmprospătarea: ce se întâmplă când sursa se schimbă

La adăugare se calculează amprenta SHA-256 a conținutului; dacă textul e identic cu ceva deja indexat, se refolosește și nu se plătește o nouă reprezentare vectorială. Când documentul se modifică, fragmentele lui se șterg integral și se rescriu — înlocuire completă pe sursă, nu actualizare parțială, pentru că o actualizare parțială lasă în urmă fragmente orfane din versiunea veche, iar acelea sunt cele care produc răspunsuri contrazise de site. Sursa are un ciclu de stări vizibil: în așteptare, în procesare, în reprocesare, gata sau eșuat, iar reindexarea se poate cere punctual, pe sursă.

Nu re-indexăm ce nu s-a schimbat

Într-o buclă care rulează la fiecare treizeci de minute peste o listă care doar crește, recalcularea reprezentării pentru un element neatins este o plată recurentă pentru un vector care nu avea cum să se miște. Bucla noastră compară momentul ultimei atingeri cu momentul ultimei indexări și sare peste ce e nemodificat. Reprezentările se cer în loturi, nu una câte una. Sunt detalii de cost, dar costul lunar al unei baze de cunoștințe se decide aproape integral aici.

Un catalog conectat nu e o listă încărcată

Diferența nu e de format, e de moment. O listă încărcată e adevărată la momentul încărcării: prețul, stocul și produsele noi rămân înghețate până când cineva își amintește să încarce din nou — și de obicei se descoperă că nimeni nu-și amintește, când un client întreabă de un preț vechi de trei luni. Un catalog conectat se citește la momentul întrebării, din sursa care ține evidența. Ce am construit în această direcție: un extractor care recunoaște colecțiile de produse dintr-un JSON sau CSV după cheile obișnuite și, când structura e neobișnuită, cere ajutorul unui model cu obligația de a răspunde strict într-o formă dată; și un parcurgător care citește prețul din pagina vie a magazinului. Ruta cea mai bună rămâne interfața structurată proprie a magazinului, acolo unde există — dar aceea se verifică per magazin, nu se presupune.

Parcurgerea unui site: harta întâi, explorarea pe urmă

Se caută mai întâi harta site-ului, în variantele obișnuite de denumire, și se desfășoară hărțile imbricate pe două niveluri. Doar dacă nu există hartă se trece la explorare în lățime, strict pe aceeași origine, cu o limită de pagini configurabilă (implicit 50, plafonată la 200). Strategia efectiv folosită se raportează înapoi — hartă sau explorare — pentru că din ea se citește dacă un rezultat sărac vine din site sau din metodă. Se respectă fișierul de excludere al site-ului.

Cum arată

Drumul unei întrebări prin sistem: sursele aprobate intră în extragere și împărțire, fragmentele ajung în index; la întrebare se punctează candidații — vectorial și lexical — și doar câteva fragmente ajung în context; iar când nu se găsește nimic, întrebarea iese pe ramura de aprobare umană și se întoarce în corpus.

01

Stabilim ce are voie să știe asistentul, înainte de orice import

Lista surselor admise și, la fel de important, lista celor respinse, cu motivul. Un fișier intern de prețuri de achiziție, un export de contacte, o arhivă de e-mailuri — sunt exact lucrurile care ajung în corpus „ca să aibă context” și devin apoi răspuns către un client. Livrăm inventarul surselor cu decizie și motiv pentru fiecare, plus regula despre ce nu are voie să intre niciodată.

02

Importăm, împărțim și verificăm ce a ieșit citind efectiv fragmentele

Extragere, împărțire cu suprapunere, indexare. Etapa de verificare nu se sare: se citesc fragmente la întâmplare din corpusul rezultat. Acolo apar tabelele transformate în șiruri de cuvinte lipite, PDF-urile scanate care au produs zero text și paginile de meniu de navigare intrate ca documente de sine stătătoare. Livrăm corpusul plus raportul cu ce nu s-a putut extrage și de ce.

03

Reglăm regăsirea pe întrebări reale, nu pe întrebări comode

Se construiește un set de întrebări din ce întreabă efectiv clienții, inclusiv formulările prost scrise și cele în două limbi amestecate, și se ajustează numărul de fragmente, pragul și ponderea părții lexicale. Ținta nu e ca fiecare întrebare să primească un răspuns, ci ca întrebările fără acoperire în corpus să primească „nu am această informație”. Livrăm setul de întrebări cu rezultatele și configurația finală.

04

Punem poarta de aprobare și bucla de creștere

Înregistrarea întrebărilor fără răspuns, notificarea către persoana care aprobă, calea de răspuns, trecerea în corpus și legarea duplicatelor. Livrăm bucla funcțională și instrucțiunea scrisă pentru cine aprobă — inclusiv ce nu se copiază din întrebarea clientului.

05

Predăm procedura de reîmprospătare, cu numele celui care o execută

Care sursă se reîmprospătează, cât de des, cine apasă și cum se vede că s-a întâmplat. Aici spunem deschis limita: în platformele pe care le operăm, reindexarea este o acțiune cerută, nu una programată automat. O procedură fără un nume de om în dreptul ei este o procedură care nu se execută.

DocumenteConversațiiSurseSintezăAcțiuneEchipăContexteazăSurse autorizate. Acțiuni revizuite.
Drumul unei întrebări prin sistem: sursele aprobate intră în extragere și împărțire, fragmentele ajung în index; la întrebare se punctează candidații — vectorial și lexical — și doar câteva fragmente ajung în context; iar când nu se găsește nimic, întrebarea iese pe ramura de aprobare umană și se întoarce în corpus.

Datele

Ce atingem, unde stau și cât rămân

Întrebările pe care le pune oricine are un responsabil cu protecția datelor — puse aici înainte să le pună el.

Ce ajunge în corpus
Numai ce e aprobat explicit: documente încărcate, pagini alese, perechi întrebare-răspuns, texte scrise de mână. Sursele sunt tipizate — text, document, site, perechi întrebare-răspuns — și au un scop declarat: produse, documentație, întrebări frecvente sau altele. Fiecare sursă are un comutator propriu, iar comutatorul e verificat la fiecare interogare de regăsire, nu doar în interfață — o sursă oprită nu poate ajunge în context nici din greșeală.
Unde stau fragmentele și reprezentările lor
În baza de date a platformei respective, alături de restul datelor clientului, nu într-un serviciu extern separat de căutare. Pentru instalările pe PostgreSQL, în tabele proprii cu extensia vectorială activată explicit; pentru platforma de asistenți text, în MySQL, cu reprezentarea într-o coloană JSON și calculul distanței făcut în aplicație. Al doilea model funcționează la scară mică și nu scalează la sute de mii de fragmente — e o limitare de arhitectură, spusă ca atare.
Datele personale din conversații
Corpusul nu ar trebui să conțină date personale, dar bucla de învățare le poate aduce, pentru că întrebarea unui client este text scris de client. De aceea intrarea în corpus trece prin aprobarea unui om: acolo se filtrează. Regula operațională care merge cu asta: cine aprobă răspunsuri primește instrucțiunea explicită să nu copieze în corpus numele, numerele de telefon sau detaliile de comandă din întrebarea originală.
Ce se păstrează despre interogări
Există un tabel de jurnal al interogărilor de regăsire, cu scorurile obținute, timpul de regăsire și numărul de fragmente returnate. E instrumentul cu care se răspunde la „de ce a spus asta” — fără el, ajustarea pragurilor devine ghicit. Jurnalul se păstrează cât e util pentru reglare și intră în termenele de retenție ale platformei, nu într-un regim separat.
Ce nu există și e declarat ca atare
Nu există aplicarea unui termen de expirare pe documente: câmpul e declarat în schemă, dar nimic nu îl citește, deci un document nu dispare singur din corpus. Nu există reindexare automată programată în platformele client — reîmprospătarea se cere explicit, pe sursă. Nu există reordonare cu un model dedicat și nici punctare lexicală de tip BM25; ce numim „hibrid” este o sumă ponderată între potrivire de cuvinte și distanță vectorială, iar formularea aceasta e cea corectă.

Un caz

O bază de cunoștințe care a crescut din întrebările la care agentul nu știa să răspundă

Situația

Un lanț de restaurante cu livrare avea agent vocal la telefon și asistent în pagina web. Corpusul inițial fusese încărcat o dată, la lansare. Problema nu era că agentul greșea, ci că spunea des „nu am această informație” — la întrebări perfect rezonabile pe care nimeni nu le anticipase când s-a scris corpusul. Fiecare astfel de răspuns era o conversație pierdută, iar lista lor nu exista nicăieri.

Ce am construit

Am transformat lipsa în intrare de sistem. Când agentul răspunde că nu are informația, întrebarea se scrie într-un tabel cu starea „în așteptare” și cu canalul din care a venit — voce sau widget. Persoana responsabilă din partea clientului primește o notificare pe canalul de mesagerie pe care îl folosește oricum, cu instrucțiunea de a răspunde direct la mesaj. Un webhook preia răspunsul, trece intrarea în starea „aprobat”, iar de acolo răspunsul intră în contextul agentului. Formulările diferite ale aceleiași lipse se leagă de intrarea originală printr-o referință, iar livrarea către agent se face o singură dată per intrare, ca un răspuns aprobat să nu fie reinjectat la fiecare ciclu.

Ce a ieșit

Corpusul a încetat să fie un fișier încărcat la lansare și a devenit o listă de lipsuri reale, ordonată de clienți, nu de presupunerile noastre. Cine aprobă vede exact ce a fost întrebat și în ce canal, deci scrie răspunsul în forma în care va fi folosit. Stările sunt patru — în așteptare, aprobat, ignorat, duplicat — pentru că „ignorat” este un rezultat legitim: unele întrebări nu trebuie să primească niciodată răspuns automat.

Ce nu spune cazul

Bucla funcționează exact atât cât funcționează omul din ea. Dacă nimeni nu răspunde la notificări, intrările „în așteptare” se adună și agentul continuă să nu știe — mecanismul face lipsa vizibilă, nu o rezolvă singur. A doua limitare, la fel de importantă: implementarea a fost cablată pentru fluxul acestui client, cu tabelul, notificarea și injectarea scrise pentru el. Ca funcție generică, disponibilă din interfață pentru orice cont, nu există încă.

Întrebări

Ce ne întreabă oamenii înainte să sune

De ce răspunde asistentul greșit dacă informația e în documentele încărcate?

Aproape întotdeauna pentru că fragmentul corect nu a fost regăsit, nu pentru că modelul ar fi „prost”. Cauzele frecvente, în ordinea în care le verificăm: fragmente prea mari, în care informația utilă se pierde între altele; tăiere prin mijlocul frazei, fără suprapunere; prea puține fragmente aduse în context; sau un corpus în care aceeași informație apare în trei versiuni diferite și cea veche punctează mai bine. Jurnalul de interogări, cu scorurile obținute, arată care dintre ele e cazul — fără el se ghicește.

Chiar folosiți căutare semantică peste tot?

Nu, și e mai onest să spunem exact unde. Cu vectori proprii: memoria unui board intern folosit zilnic, pe coloană vectorială cu index pe distanță cosinus și prag de similaritate 0,35. Cu vectori la furnizor: baza de cunoștințe a agenților vocali, sincronizată către motorul de regăsire al furnizorului. **Fără** vectori, azi, în producție: baza de cunoștințe a platformei de asistenți text — calea de indexare scrie coloana de vector goală, deci regăsirea rulează lexical, iar codul își etichetează singur modul ca `keyword-only`. Există și un pipeline propriu complet scris, cu migrarea marcată „a nu se publica în producție” până se confirmă drepturile pe extensia vectorială. Asta e harta reală.

Ce se întâmplă când modificăm un document deja indexat?

Fragmentele sursei se șterg integral și se rescriu din noua versiune. Am ales înlocuirea completă în locul actualizării parțiale pentru că partea grea nu e adăugarea, ci eliminarea: fragmentele rămase din versiunea veche continuă să fie regăsite și produc răspunsuri pe care clientul le contrazice cu pagina din față. La adăugare există și o verificare de amprentă a conținutului — dacă textul e identic, nu se recalculează nimic și nu se plătește degeaba.

Se actualizează singur când se schimbă site-ul sau catalogul?

Nu automat, în platformele pe care le operăm azi. Reindexarea se cere explicit, pe sursă, printr-o acțiune dedicată. Există o singură excepție, într-un sistem intern al nostru, unde o buclă rulează la fiecare treizeci de minute și re-indexează doar ce s-a schimbat. Programarea automată se poate construi și e o etapă separată; ce nu facem este să lăsăm impresia că ea există deja. Există și un câmp de expirare declarat în schemă pe care nimic nu îl citește — deci un document nu iese singur din corpus.

Care e diferența dintre un catalog conectat și o listă de produse încărcată?

Momentul adevărului. Lista încărcată e corectă în ziua încărcării; de a doua zi, prețul, stocul și produsele noi diverg tăcut, iar divergența se descoperă când un client cere un preț care nu mai există. Catalogul conectat se citește la momentul întrebării, din sistemul care ține evidența, deci nu poate rămâne în urmă. Costul e altul: apare o dependență de disponibilitatea acelui sistem și trebuie decis ce spune asistentul când sursa nu răspunde — răspunsul corect fiind „nu pot confirma prețul acum”, nu ultima valoare cunoscută.

Puteți indexa PDF-uri scanate?

Nu direct. Un PDF scanat este o imagine într-un ambalaj PDF și nu conține text selectabil; extractorul nostru detectează asta și întoarce un mesaj explicit în loc să indexeze un document gol — cel mai prost rezultat posibil fiind o sursă marcată „gata” care nu conține nimic. Soluția e recunoașterea optică înainte de import, sau furnizarea documentului în DOCX ori text. Recunoaștere optică nu avem implementată în lanțul actual.

Funcționează în română și rusă?

Da, și există un detaliu care contează mai mult decât pare. Împărțirea în fragmente taie între fraze, iar regula noastră recunoaște sfârșitul de frază urmat de majusculă latină **sau** chirilică, inclusiv diacriticele românești. Un separator scris pentru engleză nu recunoaște începutul unei fraze în chirilică și produce fragmente lipite, cu efect direct asupra calității regăsirii. Pentru latura vocală, modelul de reprezentare folosit la furnizor este unul multilingv.

Când NU merită căutare semantică?

Când corpusul e mic, stabil și scris de voi — de exemplu conținutul propriului site. Într-o platformă publică proprie am ales deliberat regăsirea prin suprapunere de cuvinte peste propriul text al platformei, împărțit în pasaje scurte care poartă fiecare pagina de origine. Fără vectori, fără depozit vectorial, fără o cerere suplimentară în rețea și, esențial, fără nicio sursă din afara depozitului. Asistentul poate doar să repete ce spune deja site-ul, iar fiecare răspuns se poate verifica într-o pagină scrisă de un om. Când miza principală e să nu se inventeze nimic, asta bate căutarea semantică.

Ne țineți captivi în platforma voastră?

Sursele sunt ale voastre și rămân în forma originală; fragmentele și reprezentările stau în baza de date a instalării voastre, nu într-un serviciu extern de căutare la care doar noi avem acces. Modelul de reprezentare se cere printr-o poartă proprie de modele, deci schimbarea furnizorului nu atinge codul bazei de cunoștințe. Reprezentările vectoriale sunt însă legate de modelul care le-a produs: schimbarea modelului cere reindexarea întregului corpus. E o operațiune de calculat, nu una gratuită, și e mai bine știută la început decât aflată la mijloc.

Pe ce se sprijină fiecare afirmație de mai sus (29 surse)
  1. Fragmente implicite de 500 de unități lexicale cu 50 suprapunere, cu estimare de unități per cuvântkb-embedding.ts:15-17
  2. Separatorul de fraze recunoaște majusculă latină sau chirilică, inclusiv diacriticele româneștikb-embedding.ts:86
  3. Reprezentările se cer prin poarta proprie de modele, nu direct de la furnizor; model implicit text-embedding-3-smallkb-embedding.ts:140-141
  4. Coloană vector(1536) cu index ivfflat pe distanță cosinus (lists = 100), plus index trigram creat întotdeauna ca strategie de rezervă dacă extensia vectorială lipsește20260518_knowledge_base_foundation.sql:21-24, 75-76, 85-87, 110-111
  5. Migrarea bazei de cunoștințe proprii poartă în antet mențiunea „LOCAL ONLY — do not push to prod until pgvector privileges confirmed”20260518_knowledge_base_foundation.sql:3
  6. Numărul de fragmente returnate este implicit 5 și e limitat între 1 și 20; regăsirea comută pe similaritate trigram când vectorul lipseșteindex.ts:43, 73
  7. Amprentă SHA-256 pe conținut: un text identic se refolosește fără a recalcula reprezentareaindex.ts:140, 150-156
  8. Parcurgerea unui site caută întâi harta site-ului în trei variante de denumire, desfășoară hărțile imbricate pe două niveluri, iar explorarea în lățime pe aceeași origine e rezerva; limita de pagini e implicit 50 și plafonată la 200; strategia folosită se raportează înapoiindex.ts:39, 63, 84, 100, 117, 134
  9. Bucla de învățare: stările în așteptare / aprobat / ignorat / duplicat, canalul de proveniență voce sau widget, legarea duplicatelor printr-o referință, și descrierea completă a ciclului în antetul migrării20260507_<client>_faq_learning.sql (migrarea buclei de învățare, 07.05.2026):5-12, 17, 24, 30, 53-54
  10. Livrarea către agent se face o singură dată per intrare aprobatăindex.ts:51, 57
  11. Sincronizarea către motorul de regăsire al furnizorului vocal folosește un model de reprezentare multilingvindex.ts:91
  12. Pe latura furnizorului vocal, fragmente de 500 cu suprapunere de 100 și distanță vectorială maximă 0,620260518_batch_simulate_cost_rag.sql:177-180
  13. Tipuri de fișiere acceptate la extragere locală: txt, md, markdown, log, csv, json, docx și pdf de bază; DOCX despachetat ca arhivă și citit din documentul XML intern; PDF prin extractor cu păstrarea așezării, cu rezervă pe operatorii de text; mesaj explicit pentru PDF scanat, fără recunoaștere opticăknowledge.service.js:164-184, 187-216, 222-243, 264, 269
  14. Fragmente de 1.200 de caractere cu 150 suprapunere, împărțire pe paragrafe cu tăiere forțată ca rezervă, strategia notată în metadatele fragmentuluiknowledge.service.js:51-53, 68
  15. Tipuri de sursă admise: text, document, website, perechi întrebare-răspuns; scopuri de conținut: produse, documentație, întrebări frecvente, alteleknowledge.service.js:15, 289
  16. Calea de indexare scrie coloana de reprezentare cu null, deși modelul e notat alături — deci nu există vectori în producție pe această caleknowledge.repository.js:316-329
  17. Scorul hibrid este 0,3 lexical plus 0,7 vectorial, cu partea lexicală normalizată; se punctează un set de candidați și se rețin primele fragmente, fiecare tăiat la o lungime maximă în context; modul se etichetează singur ca hybrid sau keyword-onlywidget.routes.js:1308, 1350-1356, 1366, 1369, 1371
  18. Configurație per asistent în bază: regăsire pornită, maximum de fragmente pe întrebare (implicit 5), prag de similaritate, tip de căutare și pondere lexicală; tipurile permise sunt semantic, keyword și hybrid, cu maximul limitat între 1 și 2020260312112000_knowledge_base_core.sql:50-56
  19. Există jurnal al interogărilor de regăsire cu scorurile obținute, timpul de regăsire și numărul de fragmente returnate20260312112000_knowledge_base_core.sql:64-81
  20. Comutatorul per sursă e verificat în interogarea de regăsire, nu doar în interfațăwidget.routes.js:1307
  21. Reindexarea se cere explicit, pe sursă; la modificarea conținutului fragmentele se șterg integral și se rescriu; ciclul de stări este în așteptare / în procesare / în reprocesare / gata / eșuatknowledge.routes.js și knowledge.service.js, knowledge.repository.js:routes 114, 125; service 318, 344-345, 431, 451, 465, 481; repository 314
  22. Extractorul de catalog recunoaște colecțiile de produse după cheile obișnuite dintr-un JSON sau CSV și, la structuri neobișnuite, cere unui model un răspuns strict într-o formă datăknowledge-product-extractor.js:16, 360-361
  23. Depozit vectorial pe Redis Search, cu index FLAT pe distanță cosinus și 1.536 de dimensiuni, în componenta de parcurgerevector_store.py:79-87
  24. Memoria boardului intern: coloană vector(1536) cu index ivfflat pe distanță cosinus2026080404_knowledge_graph.sql:8-9, 28, 38-39
  25. Prag de similaritate 0,35 la regăsire; re-indexarea sare peste ce nu s-a schimbat, cu motivul scris în cod: un vector care nu avea cum să se miște nu se plătește din nou; reprezentările se cer în loturimemory.ts:18, 98-100, 139, 239
  26. Bucla de curatare rulează la un interval configurabil, implicit 30 de minuteserver.ts:48, 548-555
  27. Refuzul deliberat al căutării semantice pentru o platformă publică proprie: pasaje scurte din propriul text, punctate prin suprapunere de cuvinte, fără reprezentări, fără depozit vectorial și fără nicio sursă din afara depozitului — mecanismul anti-invențieknowledge.ts:1-20
  28. Câmpul de expirare pe documente este declarat în schemă, dar niciun cod nu îl citește — un document nu iese singur din corpus20260518_knowledge_base_foundation.sql:44-45
  29. Nu există reordonare cu model dedicat și nici punctare BM25 în niciun depozit verificat; interfața structurată de tip Store API a magazinelor WooCommerce nu apare în niciun depozit localwc' peste aichat-server, aichat-workspaces-2026, kallina-platform:zero rezultate, verificat 06.09.2026

Citările interne arată numele fișierului și linia. Calea completă rămâne în depozitul nostru; o putem parcurge împreună, la cerere.

Ce ai vrea să funcționeze mai bine?

Povestește-ne despre procesul tău. Împreună stabilim ce merită construit, ce putem conecta și cum verificăm rezultatul.

Hai să discutăm