Armija tame sa AI pogonom Noć je igre, vaši prijatelji su smješteni oko stola za igre i čekaju da vide kakav će lik Dungeons & Dragons (D&D) postati i zadatak u koji će krenuti. Večeras ste Dungeon Master (pripovjedač i vodič), stvaralac uzbudljivih susreta kako biste izazvali i očarali svoje igrače. Vaš pouzdan D&D Monster priručnik sadrži stvorenja. Pronalaženje savršenog čudovišta za svaku situaciju među bezbroj opcija može biti neodoljivo. Idealan neprijatelj treba da odgovara ambijentu, težini i narativu trenutka. hiljade Šta ako bismo mogli stvoriti alat koji trenutno pronalazi čudovište najprikladnije za svaki scenarij? , osiguravajući da svaki susret bude što impresivniji i uzbudljiviji? Alat koji istovremeno razmatra više faktora Krenimo u sopstvenu potragu: izgradimo vrhunski sistem za pronalaženje čudovišta, koristeći moć vektorske pretrage sa više atributa! Stvaranje stvorenja pomoću vektorske pretrage, zašto to raditi? predstavlja revoluciju u pronalaženju informacija. Vektorsko ugrađivanje – uzimajući u obzir kontekst i semantičko značenje – omogućava pretragu vektora da vrati relevantnije i preciznije rezultate, rukuje ne samo strukturiranim već i nestrukturiranim podacima i više jezika, te mjeri. Ali da bismo generirali visokokvalitetne odgovore u aplikacijama iz stvarnog svijeta, često moramo dodijeliti različite težine specifičnim atributima naših objekata podataka. Vektorsko pretraživanje Postoje dva uobičajena pristupa pretrazi vektora sa više atributa. Oba počinju odvojenim ugrađivanjem svakog atributa objekta podataka. Glavna razlika između ova dva pristupa je u načinu na koji se naši embeddingi i . pohranjuju pretražuju pristup - pohraniti svaki vektor atributa u odvojena spremišta vektora (jedan po atributu), izvršiti zasebno pretraživanje za svaki atribut, kombinirati rezultate pretraživanja i naknadno obraditi (npr. težinu) prema potrebi. naivni pristup - spojite i pohranite sve vektore atributa u isto skladište vektora (koristeći ugrađenu funkcionalnost Superlinked-a), što nam omogućava da , uz prateće povećanje efikasnosti. Superlinkedovi nam omogućavaju da težimo svakom atributu u vreme upita da bismo prikazali relevantnije rezultate, bez naknadne obrade. Superlinked pretražujemo samo jednom spaces takođe U nastavku ćemo koristiti ova dva pristupa za implementaciju alata za pretraživanje vektora s više atributa - Dungeons and Dragons nalaz čudovišta! Naše jednostavne implementacije, posebno druga, ilustrovaće kako da kreirate moćnije i fleksibilnije sisteme za pretragu, one koji mogu sa lakoćom da obrađuju složene, višestruke upite, bez obzira na vaš slučaj upotrebe. Ako ste novi u pretraživanju vektorske sličnosti, ne brinite! Pokrili smo vas - pogledajte naše . članke o građevnim blokovima Ok, idemo u lov na čudovišta! Skup podataka Prvo ćemo generirati mali sintetički skup podataka o čudovištima, pozivanjem na model velikog jezika (LLM): Generate two JSON lists: 'monsters' and 'queries'. 1. 'monsters' list: Create 20 unique monsters with the following properties: - name: A distinctive name - look: Brief description of appearance (2-3 sentences) - habitat: Where the monster lives (2-3 sentences) - behavior: How the monster acts (2-3 sentences) Ensure some monsters share similar features while remaining distinct. 2. 'queries' list: Create 5 queries to search for monsters: - Each query should be in the format: {look: "...", habitat: "...", behavior: "..."} - Use simple, brief descriptions (1-3 words per field) - Make queries somewhat general to match multiple monsters Output format: { "monsters": [ {"name": "...", "look": "...", "habitat": "...", "behavior": "..."}, ... ], "queries": [ {"look": "...", "habitat": "...", "behavior": "..."}, ... ] } Pogledajmo uzorak skupa podataka koji je generirao naš LLM. Napomena: LLM generacija nije deterministička, tako da se vaši rezultati mogu razlikovati. Evo naših prvih pet čudovišta: # ime pogledajte stanište ponašanje 0 Luminoth Stvorenje nalik moljcu sa blistavim krilima i antenom Guste šume i džungle sa bioluminiscentnom florom Emituje umirujuće svjetlosne obrasce za komunikaciju i privlačenje plijena 1 Aqua Wraith Prozirna humanoidna figura napravljena od tekuće vode Rijeke, jezera i priobalna područja Promjene oblika da se stapaju s vodenim tijelima i kontroliraju struje 2 Stoneheart Golem Masivni humanoid sastavljen od isprepletenih kamenih formacija Stjenovite planine i drevne ruševine Hibernira vekovima, budi se da zaštiti svoju teritoriju 3 Whispering Shade Sjenovito, amorfno biće sa blistavim očima Mračne šume i napuštene zgrade Hrani se strahom i šapuće uznemirujuće istine 4 Zephyr Dancer Graciozno ptičje stvorenje sa prelivim perjem Visoki planinski vrhovi i ravnice zavejane vjetrom Stvara očaravajuće prikaze iz zraka kako bi privukao prijatelje ...i naši generirani upiti: Pogledaj Stanište Ponašanje 0 Glowing Mračna mjesta Lagana manipulacija 1 Elemental Ekstremna okruženja Kontrola životne sredine 2 Promena oblika Raznovrsni pejzaži Stvaranje iluzija 3 Kristalna Područja bogata mineralima Apsorpcija energije 4 Eterično Atmosferski Uticaj uma Pogledajte originalni skup podataka i primjere upita . ovdje Retrieval Postavimo parametre koje ćemo koristiti u oba naša pristupa - naivnom i superlinkovanom - u nastavku. Mi generišemo naše vektorske ugradnje sa: sentence-transformers/all-mpnet-base-v2. Radi jednostavnosti, ograničit ćemo naše rezultate na 3 najbolja meča. (Za kompletan kod, uključujući neophodne uvoze i pomoćne funkcije, pogledajte .) bilježnicu LIMIT = 3 MODEL_NAME = "sentence-transformers/all-mpnet-base-v2" A sada, krenimo u potragu za čudovištima s više atributa! Prvo ćemo isprobati pristup. naivni Naivan pristup U našem naivnom pristupu, atribute ugrađujemo nezavisno i spremamo ih u različite indekse. U vrijeme upita pokrećemo višestruka kNN-pretraga na svim indeksima, a zatim kombiniramo sve naše djelomične rezultate u jedan. Počinjemo od definiranja klase NaiveRetriever da izvršimo pretragu zasnovanu na sličnosti na našem skupu podataka, koristeći naše ugradnje generisane . all-mpnet-base-v2 class NaiveRetriever: def __init__(self, data: pd.DataFrame): self.model = SentenceTransformer(MODEL_NAME) self.data = data.copy() self.ids = self.data.index.to_list() self.knns = {} for key in self.data: embeddings = self.model.encode(self.data[key].values) knn = NearestNeighbors(metric="cosine").fit(embeddings) self.knns[key] = knn def search_key(self, key: str, value: str, limit: int = LIMIT) -> pd.DataFrame: embedding = self.model.encode(value) knn = self.knns[key] distances, indices = knn.kneighbors( [embedding], n_neighbors=limit, return_distance=True ) ids = [self.ids[i] for i in indices[0]] similarities = (1 - distances).flatten() # by definition: # cosine distance = 1 - cosine similarity result = pd.DataFrame( {"id": ids, f"score_{key}": similarities, key: self.data[key][ids]} ) result.set_index("id", inplace=True) return result def search(self, query: dict, limit: int = LIMIT) -> pd.DataFrame: results = [] for key, value in query.items(): if key not in self.knns: continue result_key = self.search_key(key, value, limit=limit) result_key.drop(columns=[key], inplace=True) results.append(result_key) merged_results = pd.concat(results, axis=1) merged_results["score"] = merged_results.mean(axis=1, skipna=False) merged_results.sort_values("score", ascending=False, inplace=True) return merged_results naive_retriever = NaiveRetriever(df.set_index("name")) Upotrijebimo prvi upit s naše generirane liste iznad i pretražimo čudovišta koristeći naš : naive_retriever query = { 'look': 'glowing', 'habitat': 'dark places', 'behavior': 'light manipulation' } naive_retriever.search(query) Naš naive_retriever vraća sljedeće rezultate pretraživanja za svaki atribut: id score_look pogledajte Whispering Shade 0,503578 Sjenovito, amorfno biće sa blistavim očima Pješčana oluja Djinn 0,407344 Uskovitlani vrtlog pijeska sa užarenim simbolima Luminoth 0,378619 Stvorenje nalik moljcu sa blistavim krilima i antenom Sjajno! Naši vraćeni rezultati čudovišta su relevantni - svi imaju neke "sjajne" karakteristike. Hajde da vidimo šta naivni pristup daje kada pretražujemo druga dva atributa. id score_habitat stanište Whispering Shade 0,609567 Mračne šume i napuštene zgrade Mreža gljivica 0,438856 Podzemne pećine i vlažne šume Thornvine Elemental 0,423421 Zarasle ruševine i guste džungle id score_behavior ponašanje Živi grafiti 0,385741 Pomiče oblik kako bi se stapao s okolinom i upija pigmente Crystalwing Drake 0,385211 Sakuplja dragocjene dragulje i može prelamati svjetlost u moćne zrake Luminoth 0,345566 Emituje umirujuće svjetlosne obrasce za komunikaciju i privlačenje plijena Sva pronađena čudovišta posjeduju tražene atribute. Na prvi pogled, naivni rezultati pretraživanja mogu izgledati obećavajuće. Ali moramo pronaći čudovišta koja posjeduju . Hajde da spojimo naše rezultate da vidimo koliko dobro naša čudovišta rade u postizanju ovog cilja: sva tri atributa istovremeno id score_look score_habitat score_behavior Whispering Shade 0,503578 0,609567 Pješčana oluja Djinn 0,407344 Luminoth 0,378619 0,345566 Mreža gljivica 0,438856 Thornvine Elemental 0,423421 Živi grafiti 0,385741 Crystalwing Drake 0,385211 I tu granice naivnog pristupa postaju očigledne. Procijenimo: Relevantnost prema atributu: : Tri čudovišta su vraćena (Šaptajuća senka, Džin iz peščane oluje i Luminot). look : Samo jedno čudovište iz rezultata bilo je relevantno (Whispering Shade). habitat look : Samo jedno čudovište iz rezultata je relevantno (Luminoth), ali se razlikuje od onog relevantnog za . behavior look habitat Ukupna relevantnost: Nijedno čudovište nije vraćeno za sva tri atributa istovremeno. Rezultati su fragmentirani: različita čudovišta su relevantna za različite atribute. Ukratko, naivni pristup pretraživanja ne uspijeva pronaći čudovišta koja zadovoljavaju sve kriterije odjednom. Hajde da probamo sa 6 čudovišta po atributu, umesto sa 3. Pogledajmo šta ovaj pristup generiše: Možda možemo riješiti ovaj problem proaktivnim preuzimanjem više čudovišta za svaki atribut? id score_look score_habitat score_behavior Whispering Shade 0,503578 0,609567 Pješčana oluja Djinn 0,407344 0,365061 Luminoth 0,378619 0,345566 Nebula Meduza 0,36627 0,259969 Dreamweaver Octopus 0,315679 Quantum Firefly 0,288578 Mreža gljivica 0,438856 Thornvine Elemental 0,423421 Mist Phantom 0,366816 0,236649 Stoneheart Golem 0,342287 Živi grafiti 0,385741 Crystalwing Drake 0,385211 Aqua Wraith 0,283581 Sada smo preuzeli 13 čudovišta (više od polovine našeg malog skupa podataka!), i imamo isti problem: nijedno od ovih čudovišta nije preuzeto za sva tri atributa. još uvijek Povećanje broja pronađenih čudovišta (iznad 6) riješiti naš problem, ali stvara dodatne probleme: moglo bi U proizvodnji, dohvaćanje više rezultata (više kNN pretraga) značajno produžava vrijeme pretraživanja. Za svaki novi atribut koji uvedemo, naše šanse da pronađemo "potpuno" čudovište - sa svim atributima u našem upitu - opadaju eksponencijalno. Da bismo to spriječili, moramo povratiti mnogo više najbližih susjeda (čudovišta), čime ukupan broj pronađenih čudovišta raste eksponencijalno. Još uvijek nemamo garanciju da ćemo povratiti čudovišta koja posjeduju sve naše željene atribute. Ako uspemo da povratimo čudovišta koja zadovoljavaju sve kriterijume odjednom, moraćemo da potrošimo dodatne rezultate na usklađivanje. Ukratko, naivni pristup je previše neizvjestan i neefikasan za održivo pretraživanje više atributa, posebno u proizvodnji. Superlinkovani pristup Hajde da implementiramo naš drugi pristup da vidimo da li radi bolje od naivnog. Prvo, definiramo shemu, razmake, indeks i upit: @schema class Monster: id: IdField look: String habitat: String behavior: String monster = Monster() look_space = TextSimilaritySpace(text=monster.look, model=MODEL_NAME) habitat_space = TextSimilaritySpace(text=monster.habitat, model=MODEL_NAME) behavior_space = TextSimilaritySpace(text=monster.behavior, model=MODEL_NAME) monster_index = Index([look_space, habitat_space, behavior_space]) monster_query = ( Query( monster_index, weights={ look_space: Param("look_weight"), habitat_space: Param("habitat_weight"), behavior_space: Param("behavior_weight"), }, ) .find(monster) .similar(look_space.text, Param("look")) .similar(habitat_space.text, Param("habitat")) .similar(behavior_space.text, Param("behavior")) .limit(LIMIT) ) default_weights = { "look_weight": 1.0, "habitat_weight": 1.0, "behavior_weight": 1.0 } Sada pokrećemo executor i učitavamo podatke: monster_parser = DataFrameParser(monster, mapping={monster.id: "name"}) source: InMemorySource = InMemorySource(monster, parser=monster_parser) executor = InMemoryExecutor(sources=[source], indices=[monster_index]) app = executor.run() source.put([df]) Pokrenimo isti upit koji smo pokrenuli u našoj implementaciji naivnog pristupa iznad: query = { 'look': 'glowing', 'habitat': 'dark places', 'behavior': 'light manipulation' } app.query( monster_query, limit=LIMIT, **query, **default_weights ) id rezultat pogledajte stanište ponašanje Whispering Shade 0,376738 Sjenovito, amorfno biće sa blistavim očima Mračne šume i napuštene zgrade Hrani se strahom i šapuće uznemirujuće istine Luminoth 0,340084 Stvorenje nalik moljcu sa blistavim krilima i antenom Guste šume i džungle sa bioluminiscentnom florom Emituje umirujuće svjetlosne obrasce za komunikaciju i privlačenje plijena Živi grafiti 0,330587 Dvodimenzionalno, šareno stvorenje koje nastanjuje ravne površine Urbana područja, posebno zidovi i bilbordi Pomiče oblik kako bi se stapao s okolinom i upija pigmente Et voila! Ovog puta, svako od naših najboljih vraćenih čudovišta visoko se kotira u ocjeni koja predstavlja neku vrstu "srednje vrijednosti" od sve tri karakteristike koje želimo da naše čudovište ima. Hajde da detaljno razložimo rezultat svakog čudovišta: id pogledajte stanište ponašanje ukupno Whispering Shade 0,167859 0,203189 0,005689 0,376738 Luminoth 0,126206 0,098689 0,115189 0,340084 Živi grafiti 0,091063 0,110944 0,12858 0,330587 Naši drugi i treći rezultat, Luminoth i Living Graffiti, posjeduju sve tri željene karakteristike. Najbolji rezultat, Whispering Shade, iako je manje relevantan u smislu manipulacije svjetlom - što se ogleda u njegovom rezultatu (0,006), ima "sjajne" karakteristike i mračno okruženje koje čine njegov (0,168) i (0,203) vrlo dobrim visoko, što mu daje najviši ukupni rezultat (0,377), što ga čini najrelevantnijim čudovištem u cjelini. Kakvo poboljšanje! behavior look habitat Možemo li ponoviti naše rezultate? Hajde da pokušamo sa drugim upitom i saznamo. query = { 'look': 'shapeshifting', 'habitat': 'varied landscapes', 'behavior': 'illusion creation' } id rezultat pogledajte stanište ponašanje Mist Phantom 0,489574 Eteričan humanoid nalik magli s promjenjivim osobinama Močvare, močvare i maglovite obale Namamljuje putnike na stranputicu iluzijama i šapatom Zephyr Dancer 0,342075 Graciozno ptičje stvorenje sa prelivim perjem Visoki planinski vrhovi i ravnice zavejane vjetrom Stvara očaravajuće prikaze iz zraka kako bi privukao prijatelje Whispering Shade 0,337434 Sjenovito, amorfno biće sa blistavim očima Mračne šume i napuštene zgrade Hrani se strahom i šapuće uznemirujuće istine Odlično! Naši rezultati su opet odlični. Što ako želimo pronaći čudovišta koja su slična određenom čudovištima iz našeg skupa podataka? Pokušajmo s čudovištem koje još nismo vidjeli - Harmonic Coral. bismo izdvojiti atribute za ovo čudovište i ručno kreirati parametre upita. Ali Superlinked ima metodu koju možemo koristiti na objektu upita. Budući da je id svakog čudovišta njegovo ime, naš zahtjev možemo izraziti jednostavno kao: Mogli with_vector app.query( monster_query.with_vector(monster, "Harmonic Coral"), **default_weights, limit=LIMIT ) id rezultat pogledajte stanište ponašanje Harmonic Coral 1 Granasta struktura nalik muzičkom instrumentu sa vibrirajućim viticama Plitka mora i plimni bazeni Stvara složene melodije za komunikaciju i utjecaj na emocije Dreamweaver Octopus 0,402288 Glavonožaci s pipcima koji svjetlucaju poput aurore Duboki okeanski rovovi i podvodne pećine Utječe na snove obližnjih stvorenja Aqua Wraith 0,330869 Prozirna humanoidna figura napravljena od tekuće vode Rijeke, jezera i priobalna područja Promjene oblika da se stapaju s vodenim tijelima i kontroliraju struje Najbolji rezultat je najrelevantniji, sam Harmonic Coral, kako se i očekivalo. Druga dva čudovišta koje naša pretraga pronalazi su Dreamweaver Octopus i Aqua Wraith. Oba dijele važne tematske ( ) elemente sa Harmonic Coral: atributne Vodena staništa ( ) habitat Sposobnost da utiču ili manipulišu svojom okolinom ( ) behavior Dinamičke ili fluidne vizuelne karakteristike ( ) look Ponderiranje atributa Pretpostavimo sada da želimo da damo veći značaj atributu . Okvir Superlinked nam omogućava da lako prilagodimo težine u trenutku upita. Radi lakšeg poređenja, tražit ćemo čudovišta slična Harmonic Coral-u, ali s našim težinama prilagođenim . look look weights = { "look_weight": 1.0, "habitat_weight": 0, "behavior_weight": 0 } app.query( monster_query.with_vector(monster, "Harmonic Coral"), limit=LIMIT, **weights ) id rezultat pogledajte stanište ponašanje Harmonic Coral 0,57735 Granasta struktura nalik muzičkom instrumentu sa vibrirajućim viticama Plitka mora i plimni bazeni Stvara složene melodije za komunikaciju i utjecaj na emocije Thornvine Elemental 0,252593 Stvorenje nalik biljci sa tijelom od iskrivljene loze i trnja Zarasle ruševine i guste džungle Brzo raste i kontroliše okolni biljni svet Plasma Serpent 0,243241 Stvorenje nalik na zmiju napravljeno od energije pucketanja Električne oluje i elektrane Hrani se električnom strujom i može izazvati kratki spoj u tehnologiji Svi naši rezultati (prikladno) imaju slične izglede - "Granjanje sa vibrirajućim viticama", "Biljko nalik stvorenju sa tijelom od uvijene loze i trnja", "Zmijino". Sada, napravimo još jednu pretragu, ignorirajući izgled i umjesto toga tražimo čudovišta koja su slična u smislu i istovremeno: habitat behavior weights = { "look_weight": 0, "habitat_weight": 1.0, "behavior_weight": 1.0 } id rezultat pogledajte stanište ponašanje Harmonic Coral 0,816497 Granasta struktura nalik muzičkom instrumentu sa vibrirajućim viticama Plitka mora i plimni bazeni Stvara složene melodije za komunikaciju i utjecaj na emocije Dreamweaver Octopus 0,357656 Glavonožaci s pipcima koji svjetlucaju poput aurore Duboki okeanski rovovi i podvodne pećine Utječe na snove obližnjih stvorenja Mist Phantom 0,288106 Eteričan humanoid nalik magli s promjenjivim osobinama Močvare, močvare i maglovite obale Namamljuje putnike na stranputicu iluzijama i šapatom Opet, Superlinked pristup daje odlične rezultate. Sva tri čudovišta žive u vodenom okruženju i posjeduju sposobnosti kontrole uma. Konačno, hajde da pokušamo još jednu pretragu, različito ponderišući sva tri atributa - da pronađemo čudovišta koja u poređenju sa Harmonic Coral izgledaju donekle slično, žive u veoma različitim staništima i imaju vrlo slično ponašanje: weights = { "look_weight": 0.5, "habitat_weight": -1.0, "behavior_weight": 1.0 } id rezultat pogledajte stanište ponašanje Harmonic Coral 0,19245 Granasta struktura nalik muzičkom instrumentu sa vibrirajućim viticama Plitka mora i plimni bazeni Stvara složene melodije za komunikaciju i utjecaj na emocije Luminoth 0,149196 Stvorenje nalik moljcu sa blistavim krilima i antenom Guste šume i džungle sa bioluminiscentnom florom Emituje umirujuće svjetlosne obrasce za komunikaciju i privlačenje plijena Zephyr Dancer 0,136456 Graciozno ptičje stvorenje sa prelivim perjem Visoki planinski vrhovi i ravnice zavejane vjetrom Stvara očaravajuće prikaze iz zraka kako bi privukao prijatelje Opet odlični rezultati! Naša druga dva pronađena čudovišta — Luminoth i Zephyr Dancer — imaju ponašanje slično Harmonic Coral-u i žive u staništima drugačijim od Harmonic Coral-a. Takođe izgledaju veoma različito od Harmonic Corala. (Dok su vitice Harmonic Corala i Luminothova antena donekle slične karakteristike, smanjili smo samo za 0,5, a sličnost između dva čudovišta se tu završava.) look_weight Hajde da vidimo kako se ukupni rezultati ovih čudovišta izbijaju u smislu pojedinačnih atributa: id pogledajte stanište ponašanje ukupno Harmonic Coral 0,19245 -0,3849 0,3849 0,19245 Luminoth 0,052457 -0,068144 0,164884 0,149196 Zephyr Dancer 0,050741 -0,079734 0,165449 0,136456 Negativnim ponderisanjem (-1.0), mi namjerno "odgurujemo" čudovišta sa sličnim staništima i umjesto toga čudovišta na površini čije je okruženje drugačije od Harmonic Coral-a - kao što se vidi u negativnim ocjenama Luminotha i Zephyr Dancera. Rezultati Luminotha i Zephyr Dancera su relativno visoki, što ukazuje na njihovu sličnost u ponašanju sa Harmonic Coral. Njihovi rezultati su pozitivni, ali niži, odražavajući , ali ne ekstremnu vizualnu sličnost s Harmonic Coral. habitat_weight habitat behavior look neku Ukratko, naša strategija smanjenja težine na -1.0 i na 0.5, ali zadržavanje na 1.0 pokazala se učinkovitom u otkrivanju čudovišta koja dijele ključne karakteristike ponašanja sa Harmonic Coralom, ali imaju vrlo različita okruženja i izgledaju barem donekle drugačije. habitat_weight look_weight behavior_weight Zaključak Vektorska pretraga sa više atributa je značajan napredak u pronalaženju informacija, nudeći više tačnosti, kontekstualnog razumijevanja i fleksibilnosti od osnovne pretrage semantičke sličnosti. Ipak, naš naivni pristup (gore) - odvojeno pohranjivanje i pretraživanje vektora atributa, kombinovanje rezultata - ograničen je u sposobnostima, suptilnosti i efikasnosti kada trebamo da dohvatimo objekte sa višestrukim istovremenim atributima. (Štaviše, višestruka kNN pretraživanja oduzimaju više vremena od jedne pretrage sa povezanim vektorima.) zatim Za rukovanje ovakvim scenarijima, bolje je pohraniti sve svoje vektore atributa u istu vektorsku trgovinu i izvršiti , ponderirajući vaše atribute u vrijeme upita. Pristup Superlinked je tačniji, efikasniji i skalabilniji od naivnog pristupa za bilo koju aplikaciju koja zahtijeva brzo, pouzdano, nijansirano, višeatributno dohvaćanje vektora - bilo da se vaš slučaj upotrebe bavi problemima podataka iz stvarnog svijeta u vašem e-trgovini ili sistemu preporuka ... ili nešto sasvim drugačije, poput borbe protiv čudovišta. jedno pretraživanje Saradnici Andrej Pikunov, autor Mór Kapronczay, urednik Originalno objavljeno . ovdje