
Căutare hibridă: BM25 vs Vector (și de ce ai nevoie de ambele)
Un agent de suport tastează „SKU-4471" în chatbotul tău RAG. Vin înapoi patru rezultate. Toate greșite, cu toată încrederea. Un model de embedding generalist nu are niciun motiv să plaseze acel șir exact lângă el însuși în spațiul vectorial. Exact acest mod de eșec este motivul pentru care există căutarea hibridă și tot el face ca echipele să pună mereu aceeași întrebare: cum combini concret BM25 cu căutarea vectorială fără să păzești un buton de tuning pentru totdeauna?
Dacă evaluezi mai larg toolingul RAG, roundupul nostru cu cele mai bune instrumente RAG acoperă stackul din jur.
Idei principale
- BM25 găsește potriviri exacte pe cuvinte cheie (SKU-uri, coduri de eroare); căutarea vectorială găsește text conceptual similar, nu șiruri identice.
- Căutarea hibridă le combină pe ambele, de obicei prin Reciprocal Rank Fusion (RRF), și bate oricare metodă singură pe workloaduri cu interogări mixte.
- Pe benchmarkul WANDS, RRF simplu scor 0,7068 NDCG (față de 0,6983 pentru BM25); cu tuning ajunge la 0,7497, o creștere de 7,4%.
- Postgres/pgvector poate rula căutare hibridă nativ prin
ts_rank+ pgvector, fără o bază de date vectorială dedicată.
Ce este căutarea hibridă? (BM25 + vector, combinate)
Căutarea hibridă rulează BM25 și căutarea vectorială ca două treceri separate de retrieval peste aceeași interogare, apoi îmbină cele două liste clasificate de rezultate într-o singură ieșire folosind un algoritm de fuziune, cel mai adesea Reciprocal Rank Fusion. Nu este o a treia metodă de retrieval; este un strat de orchestrare peste două metode existente.
Distincția contează, pentru că o bună parte din traficul de căutare din jurul acestui subiect confundă BM25 cu căutarea vectorială, ca și cum ar fi același lucru. Nu sunt. BM25 este o funcție de scorare sparse, bazată pe cuvinte cheie, cu rădăcini în information retrieval-ul anilor '70. Căutarea vectorială este o căutare densă, bazată pe similaritatea embeddingurilor, care a devenit practică la scară abia în ultimul deceniu. Căutarea hibridă le tratează ca intrări complementare, nu ca tehnici rivale, și le fuzionează ieșirile în loc să aleagă un câștigător din start.
BM25 vs Vector vs Hibrid: comparație rapidă
| Dimensiune | BM25 (sparse/lexical) | Căutare vectorială (densă/semantică) | Hibrid |
|---|---|---|---|
| Excelează la | Termeni exacți, tokeni rari, ID-uri | Parafrază, sinonime, concepte | Ambele tipuri de interogări |
| Eșuează la | Întrebări reformulate, sinonimie | SKU-uri, coduri de eroare, acronime | Corpusuri fără niciunul dintre tipare |
| Gestionează potriviri exacte (SKU-uri, ID-uri, coduri de eroare) | Da | Nu | Da |
| Gestionează parafraza și sinonimele | Nu | Da | Da |
| Necesită model de embedding | Nu | Da | Da |
| Necesită tuning | Parametrii k1, b | Chunking, alegerea modelului | Metoda de fuziune (RRF/alpha) |
| Profil tipic de latență | Sub-milisecundă până la câteva ms | Ms medii (depinde de ANN) | Suma ambelor, plus overheadul de fuziune |
| Exemple de suport nativ | Elasticsearch, Postgres ts_rank | Qdrant, Pinecone, pgvector | Weaviate, Qdrant, Elasticsearch |
Pe benchmarkul WANDS de e-commerce, BM25 singur a scorat 0,6983 NDCG, iar căutarea vectorială singură 0,6953 (aproape la egalitate). Fuziunea RRF simplă, fără tuning per corpus, a atins 0,7068, o creștere modestă de 1,2% față de BM25 singur. Benchmarkul lui Doug Turnbull a testat și o variantă cu tuning, care adaugă un boost pe numele produsului peste RRF, iar acea versiune a atins 0,7497, o creștere de 7,4%. Merită să fim onești despre ce cifră citezi: RRF singur îți oferă un avantaj mic, dar real, din start; cifra mai mare de 7,4% a avut nevoie de tuning suplimentar, specific domeniului, pe care cele mai multe echipe îl sar în prima zi. Nici BM25, nici căutarea vectorială nu domină singure; ele acoperă moduri de eșec diferite, iar fuzionarea lor închide ambele lacune deodată.
Mecanica BM25: cum scorază de fapt relevanța căutarea pe cuvinte cheie
BM25 scorază documentele după frecvența termenilor, ponderată cu cât de rar este acel termen în întregul corpus, apoi normalizată pentru lungimea documentului. Robertson și Zaragoza au formalizat acest lucru în lucrarea lor din 2009, "The Probabilistic Relevance Framework: BM25 and Beyond". Este o rafinare a TF-IDF, nu un înlocuitor al acestuia.
Doi parametri controlează mare parte din comportamentul BM25. k1 (de obicei 1,2-2,0) controlează saturația frecvenței termenilor: plafonază cât de mult repetarea unui cuvânt crește scorul, astfel încât un document care spune „invoice" de 40 de ori să nu întreacă automat un document care îl spune de 4 ori într-un pasaj mai strâns și mai relevant. b (implicit 0,75) controlează normalizarea lungimii documentului: decide cât de aspru penalizează BM25 documentele lungi pentru că natural conțin mai multe potriviri de termeni.
Un b greșit este o greșeală de tuning reală și frecventă. Documentele tehnice scurte (jurnale de erori, titluri de produse) vor un b mai mic, pentru că variația de lungime este mică; conținutul lung (pagini de documentație, articole) vrea de obicei un b mai aproape de valoarea implicită. Slăbiciunea de bază a BM25 este nepotrivirea de vocabular: dacă un utilizator întreabă „cum îmi recuperez banii" iar documentul spune doar „politica de rambursare", BM25 găsește zero tokeni comuni și nu întoarce nimic util.
Mecanica căutării vectoriale dense (și unde se rupe)
Căutarea vectorială mapează textul în embeddinguri de dimensiune fixă folosind un model, apoi găsește vectorii apropiați prin similaritate cosinus sau produs scalar, de obicei accelerată de un index de tip approximate nearest-neighbor. HNSW este algoritmul dominant în Weaviate, Qdrant și Milvus, schimbând o cantitate mică de recall pentru câștiguri mari de viteză la scară.
Asta repară problema de nepotrivire a vocabularului din BM25: „recuperez banii" și „politica de rambursare" aterizează aproape în spațiul embeddingurilor chiar și cu zero tokeni comuni, pentru că modelul captează sensul, nu forma de suprafață. Alegerea modelului potrivit contează mult aici. Vezi ghidul nostru despre alegerea modelului de embedding potrivit și analiza noastră despre cum am comparat embeddingurile Voyage, OpenAI și Cohere dacă cânțărești opțiuni.
Dar retrievalul dens are propriul unghi mort, și este imaginea în oglindă a celui din BM25. Când construim sisteme RAG pentru clienți, eșecul pe potrivire exactă de care ne lovim cel mai des nu este exotic. Este un agent de suport care cere un anumit număr de comandă sau SKU, iar indexul vectorial întoarce cu încredere ceva similar semantic, dar greșit. Un model de embedding generalist nu are niciun motiv să plaseze „SKU-4471" sau „ERR_CONN_RST" aproape de ele însele în spațiul vectorial față de un token înrudit, dar greșit, pentru că astfel de șiruri apar rar ca concepte distincte, izolate, în datele de antrenare. BigData Boutique documentează exact acest tipar de eșec, cu propriile exemple de SKU și coduri de eroare. Este un fenomen bine stabilit, confirmat independent în implementările RAG, nu o ciudățenie singulară.
Cum combini BM25 cu căutarea vectorială: RRF vs fuziune ponderată alpha
Există două moduri reale de a fuziona rezultatele BM25 și vector, și aproape nimeni dintre cei care scriu despre căutarea hibridă nu le contrastează clar. Reciprocal Rank Fusion (RRF), din lucrarea SIGIR din 2009 a lui Cormack, Clarke și Buettcher, operează pe ranguri: score = sum(1 / (k + rank_i)) peste fiecare listă de rezultate, cu k de obicei setat la 60. Pentru că îi pasă doar de poziție, nu de scorul brut, RRF gestionează nepotrivirile de scală dintre scorurile nemărginite ale BM25 și intervalul 0-la-1 al similarității cosinus fără probleme și nu are nevoie de tuning per corpus.
Fuziunea ponderată alpha (convexă) funcționează diferit: final = alpha * dense_score + (1 - alpha) * sparse_score, operând pe scoruri normalizate, nu pe ranguri. Poate reflecta mai bine magnitudinea încrederii (un hit vectorial la 0,95 similaritate chiar arată mai puternic decât unul la 0,61), dar necesită tuningul lui alpha per corpus, iar acel tuning se rupe silențios când distribuțiile scorurilor se schimbă (model de embedding nou, corpus reindexat, mix diferit de interogări).
În practică, alegerea se reduce la cât de multă încredere ai în calibrarea scorurilor tale. Rulând BM25 vanilla contra unui singur model de embedding stabil, ponderarea alpha poate stoarce un ranking ușor mai bun, pentru că folosește diferența reală de scor, nu doar poziția. Dar acea calibrare derivă mai mult decât se așteaptă lumea. Schimbi versiunea modelului de embedding, refaci chunkingul documentelor sau adaugi o trecere de reranking în amonte, și distribuția scorurilor dense se schimbă. Nimeni nu este trezit noaptea când alpha=0,6 nu mai este valoarea corectă; rankingul doar devine silențios puțin mai slab și e ușor să ratezi asta dacă nu rulezi evaluări de retrieval regulat. RRF evită complet problema, pentru că nu se uită niciodată la scorurile brute, doar la poziția în rang, deci o reindexare sau o schimbare de model nu îl poate rupe silențios așa cum poate rupe ponderarea alpha.
RRF nu are nevoie de tuning per corpus; ponderarea alpha are nevoie de supraveghere constantă pe măsură ce datele tale se schimbă.
Motoarele sunt împărțite în privința valorilor implicite. Weaviate expune atât RRF, cât și un parametru alpha pe care îl setezi explicit. Elasticsearch livrează RRF nativ prin API-ul său retriever (confirmă poarta exactă de versiune din instalarea ta; a aterizat pe linia 8.x). Qdrant suportă RRF nativ prin Query API. Funcționalitatea hibridă a Pinecone se bazează de obicei pe combinația convexă ponderată alpha, mai degrabă decât să expună RRF direct. Dacă nu ești sigur pe care să o alegi, începe cu RRF. Este opțiunea cu mentenanță mai redusă.
RRF de la zero: un exemplu Python neutru față de furnizori
Fiecare exemplu de cod RRF pe care l-am găsit în ghidurile concurente este legat de SDK-ul unui singur furnizor: clientul Weaviate, clientul Qdrant, clientul Pinecone. Iată o versiune fără framework, pe care o poți pune în orice stack, cu k=60 ca valoare implicită standard:
def reciprocal_rank_fusion(result_lists, k=60):
"""
result_lists: list of ranked lists, each a list of document IDs
ordered from most to least relevant.
k: RRF constant (60 is the standard default from Cormack et al., 2009).
Returns: list of (doc_id, fused_score) sorted descending by score.
"""
scores = {}
for result_list in result_lists:
for rank, doc_id in enumerate(result_list, start=1):
scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (k + rank)
return sorted(scores.items(), key=lambda item: item[1], reverse=True)
# Example usage:
bm25_hits = ["doc_9", "doc_2", "doc_14", "doc_1"]
vector_hits = ["doc_2", "doc_1", "doc_9", "doc_30"]
fused = reciprocal_rank_fusion([bm25_hits, vector_hits])
for doc_id, score in fused:
print(f"{doc_id}: {score:.4f}")Acesta este tot algoritmul. Fără SDK, fără lock-in de furnizor, și funcționează fie că cele două liste clasificate ale tale au venit din Elasticsearch și un index Faiss, fie din Postgres ts_rank și pgvector. Dacă rulezi tu însuți modelele în loc să apelezi un API, vezi rularea locală a modelelor de embedding cu Ollama.
Postgres + pgvector: căutare hibridă fără o bază de date vectorială dedicată
Nu ai nevoie de o bază de date vectorială dedicată pentru a rula căutare hibridă. Conform unui benchmark pg_textsearch/pgvector al dezvoltatorului Pedro Alonso (Postgres 17, pg_textsearch 1.3.0, pgvector 0.8.2, embeddinguri nomic-embed-text, datasetul BEIR SciFact), o singură instanță Postgres rulând ts_rank nativ a scorat doar 0,07 NDCG@10, mult în urma celor 0,69 ale BM25, 0,66 ale pgvector și 0,70 ale hibridului, toate într-o singură instanță, cu RRF hibrid aterizând în jurul a 11,5 ms mediană.
Cifra de 0,07 este indiciul: ts_rank-ul încorporat din Postgres este un ranker bazat pe densitatea acoperirii, nu BM25 adevărat. Dacă vrei scorare BM25 reală în Postgres, ai nevoie de o extensie. pg_textsearch, VectorChord și ParadeDB adaugă toate un ranking de tip BM25 propriu-zis, pe care ts_rank nativ nu îl oferă. Combini una dintre ele cu pgvector pentru similaritate densă, fuzionezi cele două liste clasificate cu funcția RRF de mai sus și ai căutare hibridă într-o singură instanță Postgres, fără infrastructură separată de rulat.
Iată aproximativ cum arată acea pereche într-o singură interogare, combinând un rang lexical dintr-o extensie capabilă de BM25 cu o distanță vectorială din pgvector:
WITH lexical AS (
SELECT id, ts_rank_cd(body_tsv, query) AS rank
FROM documents, plainto_tsquery('english', 'refund policy') query
WHERE body_tsv @@ query
ORDER BY rank DESC LIMIT 50
),
semantic AS (
SELECT id, embedding <=> '[0.012, -0.045, ...]' AS distance
FROM documents
ORDER BY distance LIMIT 50
)
SELECT * FROM lexical
FULL OUTER JOIN semantic USING (id);Dai ambele seturi de rezultate funcției RRF de mai sus și ai căutare hibridă într-o singură instanță Postgres. Limita sinceră: asta ține bine până la câteva milioane de rânduri, dar Postgres nu a fost construit ca motor de retrieval dedicat. Tuningul indexurilor îți aparține, ts_rank_cd simplu tot nu este BM25 adevărat fără o extensie și nu primești reranking încorporat sau suport multi-vector, pe care Weaviate sau Milvus le livrează nativ. Dacă corpusul tău este mic spre mediu și deja rulezi Postgres, asta îți economisește o a doua piesă de infrastructură. Peste zeci de milioane de documente, sau dacă ai nevoie de reranking avansat, un motor dedicat își justifică locul.
Dacă cânțărești Qdrant, Chroma sau pgvector pentru stackul tău, mai larg, aceea este o decizie separată de metoda de fuziune în sine. Vezi comparația noastră între Qdrant, Chroma și pgvector pentru compromisuri.
Care baze de date vectoriale suportă căutare hibridă nativ?
Cele mai multe baze de date vectoriale moderne livrează acum căutare hibridă din start, dar metoda de fuziune pe care o aleg ca implicită diferă semnificativ.
| Motor | Suport hibrid nativ | Metodă de fuziune | Note |
|---|---|---|---|
| Weaviate | Da | RRF sau ponderată alpha | Le expune pe ambele, alegi per interogare |
| Qdrant | Da | RRF | Prin Query API |
| Elasticsearch | Da | RRF | Prin API-ul retriever |
| OpenSearch | Da | Normalizare + sumă ponderată | Folosește „procesoare de normalizare" |
| Vespa | Da | Fuziune nativă | Unul dintre primele motoare cu acest suport |
| Milvus | Da | Multi-vector + BM25 sparse | Hibrid prin API-ul de căutare combinată |
| pgvector + Postgres | Da (cu o extensie) | RRF manual (vezi mai sus) | Are nevoie de extensie ts_rank/BM25 pentru scorare lexicală reală |
Verifică poarta exactă de versiune înainte să te decizi. Funcționalitățile hibride au aterizat rapid în aceste motoare prin 2026, iar formele API se schimbă de la o versiune la alta. Pentru o decizie de cumpărare mai largă, dincolo de mecanica fuziunii, vezi roundupul nostru complet cu cele mai bune baze de date vectoriale.
Merită căutarea hibridă complexitatea?
Căutarea hibridă este corectă arhitectural când corpusul tău are atât tipare de potrivire exactă (SKU-uri, ID-uri, termeni rari), cât și interogări conceptuale, reformulate. Dacă corpusul tău nu are nici una, nici alta (conținut pur narativ, fără identificatori după care cineva să caute prin șir literal), s-ar putea să adaugi complexitate de fuziune pentru o creștere pe care abia o vei observa.
Gândește-te cum arată de fapt „doar narativ": o arhivă de blog de companie, un wiki intern de inginerie plin de runbook-uri grele pe proză, un site de documentație unde nimeni nu caută după ID de produs sau număr de tichet. În acele corpusuri, căutarea vectorială singură îți aduce de obicei cea mai mare parte din valoare, iar pasul de fuziune doar adaugă a doua trecere de retrieval și un parametru pe care cineva îl deține acum, pentru o creștere care se rotunjește la zgomot. Compară asta cu un sistem de tichete de suport sau un catalog de e-commerce, unde SKU-urile, numerele de comandă și codurile de model apar constant în interogările reale ale utilizatorilor. Acesta este testul real: trage zece interogări reale din jurnalele tale și numără câte conțin un identificator exact pe care un model de embedding bazat pe parafrază nu l-ar plasa niciodată corect. Zero, sari peste hibrid. Mai mult de una sau două, construiește-l.
Căutarea hibridă nu este un upgrade universal; dacă corpusul tău nu are SKU-uri, ID-uri sau căutări de termeni rari, s-ar putea să adaugi complexitate de fuziune pentru o creștere pe care nu o vei observa niciodată.
Costul este real, dar mărginit: a doua trecere de retrieval, un pas de fuziune și un parametru de ponderare pe care cineva îl deține acum. Nu cităm deliberat o cifră de latență aici, pentru că numerele care circulă vin din configurări nemenționate, pe hardware nemenționat, iar ale tale vor diferi. Măsoar-o pe propriul corpus înainte să decizi. Două discuții de pe Hacker News captează tensiunea reală a practicienilor aici: "Hybrid Search Is Just the Beginning: Optimizing the R in RAG" și "Better RAG Results with Reciprocal Rank Fusion and Hybrid Search". Ambele discuții contestă adoptarea hibridului ca best practice de tip cargo-cult, fără să verifici mai întâi dacă corpusul tău are măcar tiparele de interogări pe care el trebuie să le rezolve. Înainte să îl construiești, merită să înțelegi cum măsori de fapt calitatea retrievalului: cifrele NDCG și recall@k înseamnă ceva doar contra propriului tău corpus, nu contra unui dataset de benchmark.
Părerea noastră: alege implicit hibrid pentru orice sistem RAG care deservește interogări de suport, e-commerce sau tichete, orientate către utilizator. Acele workloaduri amestecă aproape întotdeauna identificatori cu limbaj natural. Sari peste el pentru corpusuri doar narative (documente lungi, wiki-uri narative) până când ai măsurat o lacună reală pe care retrievalul cu o singură metodă o lasă deschisă.
Despre autor
Mert Batur este co-fondator al Techsy.io, unde echipa livrează agenți AI, sisteme de automatizare și pipeline-uri voce/SDR pentru clienți B2B. Scrie despre stackul de tooling LLM pe care echipa Techsy îl folosește efectiv în producție. Conectează-te pe LinkedIn.
Întrebări frecvente
Ce este căutarea hibridă în RAG?
Căutarea hibridă rulează retrievalul BM25 (pe cuvinte cheie) și pe cel vectorial (semantic) ca treceri separate peste aceeași interogare, apoi îmbină cele două liste clasificate cu un algoritm de fuziune, de obicei Reciprocal Rank Fusion. Prinde atât interogările cu potrivire exactă, cât și pe cele conceptuale, reformulate, pe care nicio metodă singură nu le gestionează.
BM25 este același lucru cu căutarea vectorială?
Nu. BM25 este căutare lexicală sparse, care scorază suprapunerea exactă și raritatea termenilor. Căutarea vectorială este căutare semantică densă, folosind embeddinguri și matematică de similaritate. Sunt două metode de retrieval diferite, cu puncte tari opuse; căutarea hibridă le combină, nu le înlocuiește.
Cum combini BM25 cu căutarea vectorială?
Rulează ambele metode de retrieval independent, pe aceeași interogare, apoi fuzionează cele două liste clasificate de rezultate, cel mai adesea cu Reciprocal Rank Fusion, care însumează 1 / (k + rank) peste fiecare listă. Combinarea ponderată alpha a scorurilor este alternativa, dar are nevoie de tuning per corpus, ceea ce RRF nu necesită.
Ce este Reciprocal Rank Fusion (RRF)?
RRF este un algoritm de fuziune din lucrarea SIGIR din 2009 a lui Cormack, Clarke și Buettcher, care combină mai multe liste clasificate însumând 1 / (k + rank) pentru fiecare document, cu k de obicei setat la 60. Funcționează pe poziția în rang, nu pe scoruri brute, deci rămâne stabil și când există nepotriviri de scală între metodele de retrieval.
Care este diferența dintre RRF și fuziunea ponderată alpha?
RRF combină ranguri și nu are nevoie de tuning per corpus. Fuziunea ponderată alpha combină scoruri normalizate folosind un parametru alpha reglabil, care poate reflecta mai bine magnitudinea încrederii, dar necesită recalibrare continuă de fiecare dată când distribuțiile scorurilor se schimbă, de exemplu după o reindexare sau o schimbare de model.
Când ar trebui să folosesc căutare hibridă în loc de căutare vectorială singură?
Folosește căutarea hibridă când interogările tale amestecă identificatori exacți (SKU-uri, numere de comandă, coduri de eroare) cu întrebări conceptuale în limbaj natural; sistemele de suport, e-commerce și cele de tichete fac asta de obicei. Sari peste ea pentru conținut doar narativ, fără identificatori, unde complexitatea adăugată de fuziune probabil nu va arăta o creștere măsurabilă.
De ce ratează căutarea vectorială potrivirile exacte, precum SKU-urile sau codurile de eroare?
Modelele de embedding învață din tipare generale de limbaj, iar șiruri precum „SKU-4471" sau „ERR_CONN_RST" apar rar ca concepte distincte, izolate, în datele de antrenare. Modelul nu are un motiv puternic să plaseze acel șir exact mai aproape de el însuși decât de un token înrudit semantic, dar greșit.
Care baze de date vectoriale suportă nativ căutare hibridă?
Weaviate, Qdrant, Elasticsearch, OpenSearch, Vespa și Milvus livrează toate căutare hibridă nativă din 2026, deși metodele lor implicite de fuziune diferă (RRF vs ponderată alpha vs normalizare). Postgres cu pgvector poate și el rula căutare hibridă, dar are nevoie de o extensie BM25, pentru că ts_rank nativ nu este BM25 adevărat.
Merită căutarea hibridă complexitatea adăugată?
Pentru corpusuri care amestecă interogări cu potrivire exactă și conceptuale, da. RRF simplu bate deja oricare metodă singură pe benchmarkul WANDS (0,7068 față de 0,6983 al BM25), iar o variantă cu tuning atinge o creștere de 7,4% (0,7497). Pentru corpusuri doar narative, fără identificatori, a doua trecere de retrieval și tuningul de fuziune de care are nevoie pot cântări mai greu decât o creștere pe care nu o vei observa. Măsoară înainte să te decizi.
Poate Postgres/pgvector să facă căutare hibridă fără o bază de date vectorială dedicată?
Da. Combină pgvector pentru similaritate densă cu o extensie BM25 adevărată, precum pg_textsearch, VectorChord sau ParadeDB (ts_rank nativ singur a scorat doar 0,07 NDCG@10 în benchmarkul pg_textsearch/pgvector al lui Pedro Alonso, față de 0,70 pentru hibrid), apoi fuzionează cele două liste clasificate cu RRF, totul într-o singură instanță Postgres.
Ambele metode de retrieval lasă lacune reale când sunt rulate singure: BM25 ratează parafraza, căutarea vectorială ratează identificatorii exacți, iar fuzionarea lor cu RRF este calea cu mentenanță mai redusă de a închide ambele lacune. Dacă te gândești să construiești asta singur sau să aduci o echipă care a mai livrat retrieval RAG, ghidul nostru complet pentru construirea unei aplicații RAG acoperă pasul următor sau ia legătura cu noi dacă preferi ca Techsy să o construiască împreună cu tine.