Умен, по-бърз начин за търсене в библиотеката на Steam Ти знаеш чувството. Търсите игра, която е стратегическа, съвместна, може би с научнофантастична тема. Получавате стена от заглавия, които са подобни на съвпадение. Това, което искахте, беше списък, който наистина улавя вибрацията зад думите ви. В това ръководство показваме как да изградим точно това, като сдвоим Superlinked с LlamaIndex. Резултатът е персонализиран игрален ретривър на Steam, който разбира жанра плюс описание плюс тагове и дава отговори в милисекунди. * на Искате ли да видите това на вашите данни с реални заявки и числа за закъснение? . Get in touch Докоснете се Докоснете се Др ; Др Персонализираните ретривъри ви дават контрол върху контекста на домейна, метаданните и логиката за класиране.Те превъзхождат търсенето на обща прилика, когато заявките са объркани или жаргонът е тежък. Superlinked съчетава множество текстови полета в едно семантично пространство и изпълнява заявки в паметта за бързи резултати. LlamaIndex осигурява чист интерфейс за ретривър и свързва директно към двигателите за заявки и синтеза на отговорите. Има официална интеграция на Superlinked Retriever за LlamaIndex, която можете да импортирате и използвате. Официален Superlinked Retriever за LlamaIndex Superlinked се интегрира с LlamaIndex чрез официалния изброени на LlamaHub, така че можете да добавите Superlinked към съществуващия си LlamaIndex стек с проста инсталация и След това го поставете в Научете повече за Класът и параметрите на конструктора са документирани в LlamaIndex API референцията. SuperlinkedRetriever from llama_index.retrievers.superlinked import SuperlinkedRetriever RetrieverQueryEngine . Официална страница за интеграция . Официална страница за интеграция Официална страница за интеграция pip install llama-index-retrievers-superlinked from llama_index.retrievers.superlinked import SuperlinkedRetriever # sl_app: a running Superlinked App # query_descriptor: a Superlinked QueryDescriptor that describes your query plan retriever = SuperlinkedRetriever( sl_client=sl_app, sl_query=query_descriptor, page_content_field="text", query_text_param="query_text", metadata_fields=None, top_k=10, ) nodes = retriever.retrieve("strategic co-op sci fi game") Предпочитате ли да го изградите ръчно или да персонализирате логиката по-нататък? Why Superlinked + LlamaIndex? Защо Superlinked + LlamaIndex? Целта е проста: вземете силните страни на Superlinked за многополево извличане и ги опаковайте, така че разработчиците да могат да приемат и разширяват в реални RAG системи. Superlinked ви помага да дефинирате изразителни векторни пространства и заявки, които смесват полета като име, описание и жанр в един семантичен изглед. Можете също така да следвате в използва същите строителни блокове от Superlinked Notebooks. Google Колаб Google Колаб Колаб Защо коректните ретривъри имат значение Настроени за вашия домейн – генеричните ретривъри са добре за обща употреба, но те са склонни да пропускат фините неща. Помислете за жаргон, съкращения или специфични за домейна фрази, те обикновено не се вземат, освен ако вашият ретривър не знае какво да търси. Работи извън текста – Повечето данни в реалния свят не са просто текст. Често ще имате и метаданни и тагове. Например, в система за препоръки за игри, ние не се интересуваме само от описанието на играта. Ние също искаме да вземем предвид жанровете, таговете, потребителските рейтинги и др. Помислете за тази логика: някой, който търси „стратегическа кооперативна игра с научнофантастични елементи“, няма да стигне далеч с текстовото съвпадение. Филтриране и класиране Логика - Понякога искате да приложите собствените си правила към начина, по който нещата се оценяват или филтрират. Може би искате да приоритизирате по-ново съдържание или да накажете резултатите, които не отговарят на определени прагове за качество. Повишаване на производителността – Нека бъдем реални: решенията с общо предназначение са изградени така, че да работят „добре“ за всички, а не за вас.Ако знаете вашите данни и вашите модели на достъп, можете да настройвате ретривера си, за да работи по-бързо, да класира по-добре и да намалите ненужния шум в резултатите. Разкъсване на изпълнението Част 1: Основни зависимости и внос import time import logging import pandas as pd from typing import List from llama_index.core.retrievers import BaseRetriever from llama_index.core.schema import NodeWithScore, QueryBundle, TextNode from llama_index.core.query_engine import RetrieverQueryEngine from llama_index.core.response_synthesizers import get_response_synthesizer from llama_index.core import Settings from llama_index.llms.openai import OpenAI import superlinked.framework as sl Структурата на вноса разкрива нашия хибриден подход: LlamaIndex Core: Предоставя абстрактния слой за извличане Superlinked Framework: Управлява векторни изчисления и семантично търсене Pandas: Управление на предварителната обработка и манипулация на данни Част 2: Разбиране на LlamaIndex Custom Retrievers Преди да се потопите в нашата имплементация Superlinked, е от решаващо значение да разберете как работи архитектурата за персонализирани ретривъри на LlamaIndex и защо е толкова мощна за изграждане на специфични за домейна RAG приложения. Базирана абстракция LlamaIndex предоставя абстракт Красотата на този дизайн се крие в неговата простота – всеки персонализиран ретривър трябва да прилага само един основен метод: BaseRetriever from abc import abstractmethod from llama_index.core.retrievers import BaseRetriever from llama_index.core.schema import NodeWithScore, QueryBundle class BaseRetriever: @abstractmethod def _retrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]: """Retrieve nodes given query.""" pass Гряхът тук е наличието на Протокола за възстановяване от LlamaIndex. Тъй като този "протокол за възстановяване" улеснява включването на различни задвижвания или стратегии, без да се налага да докосвате останалата част от вашата система. Input: QueryBundle This is the query object passed into your retriever. At minimum, it contains the user's raw query string (e.g., "sci-fi strategy games"). But it can also include extra metadata like filters, embeddings, or user preferences. Basically, anything that might help shape a more relevant response. Output: List[NodeWithScore] The retriever returns a list of nodes—these are your chunks of content, documents, or data entries—each paired with a relevance score. The higher the score, the more relevant the node is to the query. This list is what gets passed downstream to the LLM or other post-processing steps. As in our case, we are plugging on the Processing: Backend-Agnostic Here’s the cool part: how you get from query to result is totally up to you. You can use a vector database, a traditional search engine, a REST API, or even something handcrafted for your specific use case. This decouples logic and gives you full control over the retrieval stack. Защо това има значение? Тази абстракция е чиста и Това означава, че можете да: Мощни Комбинирайте няколко стратегии – използвайте плътно векторно търсене и филтриране на ключови думи заедно, ако е необходимо. Извършвайте A / B тестове лесно - Сравнете различни ретривъри, за да видите какво дава по-добри резултати за вашите потребители. Включете всеки агент или инструмент - Независимо дали изграждате чат-бот, интерфейс за търсене или пълноценна система за агенти, този ретривър е лесен за използване. Помислете за протокола за извличане като договор за API между вашия „мозък за извличане“ и всичко останало. Вмъкване на Superlinked в LlamaIndex Добре, така че на класа е основно нашият go-to инструмент за предоставяне на интелигентни, семантични препоръки за игра.Ще започнем с бърз поглед върху това как е съставен, а след това по-късно да се потопите по-дълбоко във всяка част, за да видите какво наистина прави това нещо. SuperlinkedSteamGamesRetriever На първо място е , помислете за него като за основата. Това е, което поддържа всичко организирано и надеждно. Разкриваме ключови детайли като , , и Това поддържа цялата информация за играта чиста и последователна и се свързва директно към тръбопровода на Superlinked, така че всичко да тече гладко. schema definition GameSchema game_number name desc_snippet genre class GameSchema(sl.Schema): game_number: sl.IdField name: sl.String desc_snippet: sl.String game_details: sl.String languages: sl.String genre: sl.String game_description: sl.String original_price: sl.Float discount_price: sl.Float combined_text: sl.String # New field for combined text self.game = GameSchema() Следващата стъпка е Това е мястото, където се случва магията на семантичното търсене. модел за превръщане на купчина информация за играта (като име, описание, жанр и т.н.) в плътни векторни представяния. По принцип, той изглажда целия този текст заедно в нещо, което моделът може да разбере. Така че, ако някой търси нещо като "отворено приключение", той може да намери игри, които наистина отговарят на тази вибрация, а не само тези с тези точни думи. text similarity space sentence-transformers/all-mpnet-base-v2 означава self.text_space = sl.TextSimilaritySpace( text=self.game.combined_text, model="sentence-transformers/all-mpnet-base-v2" ) на това е мястото, където нещата наистина започват да кликват.Това отнема различни битове информация (като името на играта, описанието, жанра и др.) и ги разчупва в една голяма част от текста.Това дава на модела по-пълна картина на всяка игра, когато я превръщате в вектори.Резултатът?По-добри препоръки, тъй като тя изтегля куп различни детайли наведнъж, вместо просто да гледа на едно нещо в изолация. combined text field self.df['combined_text'] = ( self.df['name'].astype(str) + " " + self.df['desc_snippet'].astype(str) + " " + self.df['genre'].astype(str) + " " + self.df['game_details'].astype(str) + " " + self.df['game_description'].astype(str) ) И накрая, е това, което прави всичко супер бързо. благодарение на Superlinked , ретривърът може да обработва заявки в реално време, без закъснения, само мигновени резултати.Това означава, че независимо дали някой лови за определен жанр или просто търси нещо ново, за да играе, те получават бързи и точни препоръки, без да чакат. in-memory execution InMemoryExecutor # Set up in-memory source and executor source = sl.InMemorySource(self.game, parser=parser) self.executor = sl.InMemoryExecutor(sources=[source], indices=[self.index]) self.app = self.executor.run() # Load data source.put([self.df]) Сложете всички тези парчета заедно и ще получите — солидна настройка за предоставяне на препоръки за игри, които всъщност имат смисъл за потребителя. Това е бързо, умно и лично. Ето как изглежда цялото нещо в действие... SuperlinkedSteamGamesRetriever class SuperlinkedSteamGamesRetriever(BaseRetriever): """A custom LlamaIndex retriever using Superlinked for Steam games data.""" def __init__(self, csv_file: str, top_k: int = 10): """ Initialize the retriever with a CSV file path and top_k parameter. Args: csv_file (str): Path to games_data.csv top_k (int): Number of results to return (default: 10) """ self.top_k = top_k # Load the dataset and ensure all required columns are present self.df = pd.read_csv(csv_file) print(f"Loaded dataset with {len(self.df)} games") print("DataFrame Columns:", list(self.df.columns)) required_columns = [ 'game_number', 'name', 'desc_snippet', 'game_details', 'languages', 'genre', 'game_description', 'original_price', 'discount_price' ] for col in required_columns: if col not in self.df.columns: raise ValueError(f"Missing required column: {col}") # Combine relevant columns into a single field for text similarity self.df['combined_text'] = ( self.df['name'].astype(str) + " " + self.df['desc_snippet'].astype(str) + " " + self.df['genre'].astype(str) + " " + self.df['game_details'].astype(str) + " " + self.df['game_description'].astype(str) ) self._setup_superlinked() def _setup_superlinked(self): """Set up Superlinked schema, space, index, and executor.""" # Define schema class GameSchema(sl.Schema): game_number: sl.IdField name: sl.String desc_snippet: sl.String game_details: sl.String languages: sl.String genre: sl.String game_description: sl.String original_price: sl.Float discount_price: sl.Float combined_text: sl.String # New field for combined text self.game = GameSchema() # Create text similarity space using the combined_text field self.text_space = sl.TextSimilaritySpace( text=self.game.combined_text, model="sentence-transformers/all-mpnet-base-v2" ) # Create index self.index = sl.Index([self.text_space]) # Map DataFrame columns to schema parser = sl.DataFrameParser( self.game, mapping={ self.game.game_number: "game_number", self.game.name: "name", self.game.desc_snippet: "desc_snippet", self.game.game_details: "game_details", self.game.languages: "languages", self.game.genre: "genre", self.game.game_description: "game_description", self.game.original_price: "original_price", self.game.discount_price: "discount_price", self.game.combined_text: "combined_text" } ) # Set up in-memory source and executor source = sl.InMemorySource(self.game, parser=parser) self.executor = sl.InMemoryExecutor(sources=[source], indices=[self.index]) self.app = self.executor.run() # Load data source.put([self.df]) print(f"Initialized Superlinked retriever with {len(self.df)} games") def _retrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]: """ Retrieve top-k games based on the query string. Args: query_bundle (QueryBundle): Contains the query string Returns: List[NodeWithScore]: List of retrieved games with scores """ query_text = query_bundle.query_str # Define Superlinked query with explicit field selection query = ( sl.Query(self.index) .find(self.game) .similar(self.text_space, query_text) .select([ self.game.game_number, self.game.name, self.game.desc_snippet, self.game.game_details, self.game.languages, self.game.genre, self.game.game_description, self.game.original_price, self.game.discount_price ]) .limit(self.top_k) ) # Execute query result = self.app.query(query) df_result = sl.PandasConverter.to_pandas(result) # Convert results to NodeWithScore objects nodes_with_scores = [] for i, row in df_result.iterrows(): text = f"{row['name']}: {row['desc_snippet']}" metadata = { "game_number": row["id"], "name": row["name"], "desc_snippet": row["desc_snippet"], "game_details": row["game_details"], "languages": row["languages"], "genre": row["genre"], "game_description": row["game_description"], "original_price": row["original_price"], "discount_price": row["discount_price"] } score = 1.0 - (i / self.top_k) node = TextNode(text=text, metadata=metadata) nodes_with_scores.append(NodeWithScore(node=node, score=score)) return nodes_with_scores print("✅ SuperlinkedSteamGamesRetriever class defined successfully!") Integration Architecture Deep Dive Част 3: Дефиниция и настройка на свръзка схема Започвайки с дизайна на схемата, сега в Superlinked, схемата не е само за определяне на типовете данни, тя е по-скоро като формална дефиниция между нашите данни и основната векторна изчислителна машина. В нашия Схемата е дефинирана така: SuperlinkedSteamGamesRetriever class GameSchema(sl.Schema): game_number: sl.IdField name: sl.String desc_snippet: sl.String game_details: sl.String languages: sl.String genre: sl.String game_description: sl.String original_price: sl.Float discount_price: sl.Float combined_text: sl.String # New field for combined text self.game = GameSchema() Нека разделим какво някои от тези елементи всъщност : прави (→ ) Think of this as our primary key. It gives each game a unique identity and allows Superlinked to index and retrieve items efficiently, I mean basically it’s about how we are telling the Superlinked to segregate the unique identify of the games, and btw it’s especially important when you're dealing with thousands of records. sl.IdField game_number and Now these aren't just type hints—they enable Superlinked to optimize operations differently depending on the field. For instance, fields can be embedded and compared semantically, while fields can support numeric filtering or sorting. sl.String sl.Float sl.String sl.Float This is the of our retriever. It’s a synthetic field where we concatenate the game name, description, genre, and other relevant attributes into a single block of text. This lets us build a single using sentence-transformer embeddings: combined_text semantic anchor text similarity space self.text_space = sl.TextSimilaritySpace( text=self.game.combined_text, model="sentence-transformers/all-mpnet-base-v2" ) Защото потребителите не търсят само по жанр или име, те описват какво са Включване на всички важни сигнали в , можем по-добре да съчетаем неясни, естествени езикови заявки с най-подходящите игри. Търси се combined_text Част 4: Конфигурация на векторното пространство # Create text similarity space using the combined_text field self.text_space = sl.TextSimilaritySpace( text=self.game.combined_text, model="sentence-transformers/all-mpnet-base-v2" ) # Create index self.index = sl.Index([self.text_space]) За да задействам семантичното търсене на нашия набор от игри в Steam, направих два преднамерени дизайнерски решения, които балансират производителността, простотата и гъвкавостта. Първо, за модела на вграждане, аз избрах Този модел произвежда 768-измерни вграждания, които ударят солидна средна земя: те са достатъчно изразителни, за да улавят богат семантичен смисъл, но достатъчно леки, за да бъдат бързи в производството. Искам да кажа, че това е надежден модел с общо предназначение, известен с добра работа в различни текстови типове - което има голямо значение, когато вашите данни варират от къси жанрови тагове до описания на игри с дълга форма. Управлявайте го чисто. all-mpnet-base-v2 all-mpnet-base-v2 След това, въпреки че Superlinked поддържа многопространствено индексиране – където можете да комбинирате няколко полета или дори модалитети (като текст + изображения). Бих включил и Тук също, но нямам информация за датата на издаване на игрите.Но само за да изложим това тук, ако имаме информация за датата на издаване, бих могъл да вмъкна RecencySpace тук и дори мога да сортирам игрите с В допълнение към игрите, които се играят наскоро. TextSimilaritySpace RecencySpace TextSimilaritySpace Част 5: Създаване на тръбопровод за данни и изпълнител # Map DataFrame columns to schema - Critical for data integrity parser = sl.DataFrameParser( self.game, mapping={ self.game.game_number: "game_number", self.game.name: "name", self.game.desc_snippet: "desc_snippet", self.game.game_details: "game_details", self.game.languages: "languages", self.game.genre: "genre", self.game.game_description: "game_description", self.game.original_price: "original_price", self.game.discount_price: "discount_price", self.game.combined_text: "combined_text" } ) # Set up in-memory source and executor source = sl.InMemorySource(self.game, parser=parser) self.executor = sl.InMemoryExecutor(sources=[source], indices=[self.index]) self.app = self.executor.run() # Load data source.put([self.df]) print(f"Initialized Superlinked retriever with {len(self.df)} games") В сърцето на нашата система за извличане е рационализиран тръбопровод, изграден както за яснота, така и за скорост. Той гарантира, че всяко поле в набора от данни е правилно въведено и последователно картографирано към нашата схема; по същество действа като договор между нашите сурови CSV данни и Superlinked индексиращия слой. DataFrameParser След като данните са структурирани, аз ги подавам в , което е идеално за набори от данни, които удобно се вписват в паметта. Този подход поддържа всичко мълниеносно бързо, без да се въвежда наднормено съхранение или мрежова латентност. Това прави Superlinked подходящ за приложения в реално време като интерактивни системи за препоръки, където скоростта пряко влияе на потребителското преживяване. InMemorySource InMemoryExecutor Част 6: Двигателят за възстановяване def _retrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]: """ Retrieve top-k games based on the query string. Args: query_bundle (QueryBundle): Contains the query string Returns: List[NodeWithScore]: List of retrieved games with scores """ query_text = query_bundle.query_str # Define Superlinked query with explicit field selection query = ( sl.Query(self.index) .find(self.game) .similar(self.text_space, query_text) .select([ self.game.game_number, self.game.name, self.game.desc_snippet, self.game.game_details, self.game.languages, self.game.genre, self.game.game_description, self.game.original_price, self.game.discount_price ]) .limit(self.top_k) ) # Execute query result = self.app.query(query) df_result = sl.PandasConverter.to_pandas(result) Едно от нещата, които правят Superlinked наистина забавно да се работи с, е създателят на заявки в свободен стил. Ако сте използвали библиотеки като SQLAlchemy или Django ORM, моделът ще се почувства познат. Всеки метод в веригата добавя яснота вместо бъркотия. В нашия случай заявката започва с избора на съответния индекс и определянето на търсенето на сходство с помощта на метод, който изчислява сходството на козините в пространството за вграждане.Това е, което ни позволява да извличаме семантично близки игри въз основа на естествената езикова заявка на потребителя. .similar() Друг много внимателен избор, който взех, беше Аз се интересувам от резултата, вместо да правя нещо като Това може да звучи незначително, но запазва данните слаби, намалява обработката над главата и гарантира, че не прехвърляме ненужни полезни натоварвания по време на след обработката.Мислете за това като прецизност над обема, особено важно, когато премествате данни между компоненти в тръбопровод, чувствителен към латентност. explicitly select the fields SELECT * Част 7: Обработка на резултатите и създаване на възли # Convert to LlamaIndex NodeWithScore format nodes_with_scores = [] for i, row in df_result.iterrows(): text = f"{row['name']}: {row['desc_snippet']}" metadata = { "game_number": row["id"], "name": row["name"], "desc_snippet": row["desc_snippet"], "game_details": row["game_details"], "languages": row["languages"], "genre": row["genre"], "game_description": row["game_description"], "original_price": row["original_price"], "discount_price": row["discount_price"] } # Simple ranking score based on result position score = 1.0 - (i / self.top_k) node = TextNode(text=text, metadata=metadata) nodes_with_scores.append(NodeWithScore(node=node, score=score)) return nodes_with_scores Сега, след като получихме резултатите от Superlinked, ги трансформирах във формат, който играе добре с LlamaIndex. Това става съдържанието на всеки възел, което го прави по-лесно за езиковия модел да разсъждава.Това е малко докосване, но наистина подобрява колко релевантни и разбираеми са събраните данни, когато се предават на LLM. human-readable text След това се уверявам, че от набора от данни, включително неща като жанр, ценообразуване и подробности за играта - се запазват в метаданните. Това е от решаващо значение, защото процесите надолу по веригата може да искат да филтрират, показват или класират резултатите въз основа на тази информация. all original fields И накрая, прилагам леко тегло Стратегия. Вместо да разчитаме на сурови резултати от сходство, ние разпределяме резултати въз основа на позицията на резултата в класирания списък. Това поддържа нещата прости и последователни. Най-високият резултат винаги има най-висок резултат, а останалите следват в низходящ ред. Това не е фантастично, но ни дава стабилна и интерпретируема система за оценка, която работи добре в различни заявки. score normalisation Време за показване: Изпълнение на тръбопровода Сега, когато всички компоненти са на място, е време да въведем в действие нашата система Retrieval-Augmented Generation (RAG). # Initialize the RAG pipeline print("Setting up complete Retrieval pipeline...") # Create response synthesizer and query engine response_synthesizer = get_response_synthesizer() query_engine = RetrieverQueryEngine( retriever=retriever, response_synthesizer=response_synthesizer ) print("✅ RAG pipeline configured successfully!") print("\n" + "="*60) print("FULL RAG PIPELINE DEMONSTRATION") print("="*60) # Test queries with full RAG responses test_queries = [ "I want to find a magic game with spells and wizards", "Recommend a fun party game for friends", "I'm looking for a strategic sci-fi game", "What's a good cooperative game for teamwork?" ] for i, query in enumerate(test_queries, 1): print(f"\nQuery {i}: '{query}'") print("-" * 50) response = query_engine.query(query) print(f"Response: {response}") print("\n" + "="*50) Тази настройка съчетава нашия персонализиран семантичен ретривър с генератор за отговори, задвижван от LLM. Заявленията се движат гладко през тръбопровода и вместо просто да изплюват сурови данни, той връща внимателно предложение за това какъв вид игра потребителят може да иска да играе въз основа на това, което са поискали. Takeaways Вземи Custom retrievers let you bake domain rules and jargon into the system. Combining multiple text fields into one index improves query understanding. In LlamaIndex you only need to implement _retrieve for a custom backend. Superlinked InMemoryExecutor gives real time latency on moderate datasets. Schema choice matters for clean parsing and mapping. Simple position based scoring is a stable default when you want predictable ranks.\ If you want a quick chat about where mixture of encoders or multi-field retrieval fits in your pipeline, ! talk to one of our engineers Ако искате бърз разговор за това къде комбинация от кодове или многополево извличане се вписва във вашия тръбопровод, ! Говорете с един от нашите инженери Говорете с един от нашите инженери Говорете с един от нашите инженери Интеграционни референции: Пакет Superlinked Retriever на Docs PyPI и LlamaIndex за персонализирани Retrievers. Приносниците Випул Махешвари, автор Филип Макрадули, редактор