Uma maneira mais inteligente e rápida de pesquisar na sua biblioteca do Steam Você conhece o sentimento. Você está procurando um jogo que é estratégico, co-op, talvez com um tema de ficção científica. Você obtém uma parede de títulos que tipo de correspondência. O que você queria foi uma lista curta que realmente captura a vibração por trás de suas palavras. Neste guia, mostramos como construir exatamente isso ao acoplar Superlinked com LlamaIndex. O resultado é um retriever de jogo do Steam personalizado que entende gênero mais descrição mais tags, e serve respostas em milissegundos. *Não Quer ver isso em seus dados com consultas reais e números de latência? . Get in touch Faça um toque Faça um toque Título: DR Os Retrievers personalizados dão-lhe controle sobre o contexto do domínio, metadados e lógica de classificação. Eles superam a pesquisa de semelhança genérica quando as consultas são confusas ou pesadas. Superlinked combina vários campos de texto em um único espaço semântico e executa consultas na memória para obter resultados rápidos. LlamaIndex fornece a interface do retriever limpo e conecta diretamente aos motores de consulta e síntese de resposta. Existe uma integração oficial do Superlinked Retriever para LlamaIndex que você pode importar e usar. Superlinked Retriever para LlamaIndex Superlinked integra-se com o LlamaIndex através do oficial listado no LlamaHub, para que você possa adicionar Superlinked à sua pilha LlamaIndex existente com uma instalação simples e Em seguida, coloque-o em uma Saiba mais sobre o Os parâmetros da classe e do construtor são documentados na referência da API LlamaIndex. SuperlinkedRetriever from llama_index.retrievers.superlinked import SuperlinkedRetriever RetrieverQueryEngine . Página Oficial de Integração . Página Oficial de Integração Página Oficial de Integração 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") Prefere construí-lo à mão ou personalizar a lógica ainda mais? Why Superlinked + LlamaIndex? Por que Superlinked + LlamaIndex? O objetivo é simples: usar os pontos fortes do Superlinked para a recuperação multi-campo e embalá-los para que os desenvolvedores possam adotar e estender em sistemas RAG reais. Superlinked ajuda você a definir espaços vetoriais expressivos e consultas que misturam campos como nome, descrição e gênero em uma única visão semântica. Você também pode acompanhar em usando os mesmos blocos de construção dos blocos de anotações Superlinked. O Google Colab O Google Colab Colab Por que os custom retrievers importam Ajustado para o seu domínio – os retrievers genéricos são bons para uso geral, mas tendem a perder as coisas sutis. Pense em jargão, abreviação ou expressão específica de domínio, aqueles geralmente não são pegos a menos que o seu retriever saiba o que procurar. Trabalha além do texto – A maioria dos dados do mundo real não é apenas texto simples. Você muitas vezes terá metadados e tags também. Por exemplo, em um sistema de recomendação de jogos, não nos importamos apenas com a descrição do jogo. Também queremos factorizar em gêneros, tags, classificações de usuários e muito mais. Pense nessa lógica: alguém que procura um “jogo de cooperação de estratégia com elementos de ficção científica” não vai chegar longe com a correspondência apenas com texto. Lógica de filtragem e classificação personalizada – Às vezes você quer aplicar suas próprias regras para como as coisas são marcadas ou filtradas. Talvez você queira priorizar conteúdo mais recente, ou penalizar resultados que não atendam a certos limiares de qualidade. Ganhos de desempenho – sejamos reais: as soluções de propósito geral são construídas para funcionar “OK” para todos, não é ótimo para você.Se você conhece seus dados e seus padrões de acesso, você pode ajustar seu retriever para correr mais rápido, classificar melhor e reduzir o ruído desnecessário nos resultados. Queda de Implementação Parte 1: Dependências básicas e importações 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 A estrutura de importação revela a nossa abordagem híbrida: LlamaIndex Core: fornece a camada de abstração de recuperação Superlinked Framework: Lida com computação vetorial e pesquisa semântica Pandas: Gerencia o pré-processamento e manipulação de dados Parte 2: Entendendo LlamaIndex Custom Retrievers Antes de mergulhar em nossa implementação Superlinked, é crucial entender como a arquitetura de retriever personalizado do LlamaIndex funciona e por que é tão poderosa para a construção de aplicativos RAG específicos de domínio. BaseRetriever Abstração LlamaIndex fornece um abstrato A beleza deste design reside na sua simplicidade – qualquer retriever personalizado só precisa implementar um método básico: 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 O frio aqui é a presença do Protocolo de Recuperação do LlamaIndex. Como este "protocolo de recuperação" torna fácil conectar diferentes backends ou estratégias sem ter que tocar no resto do seu sistema. 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. Por que isso importa? Esta abstração é limpa e Isso significa que você pode: poderoso Combine várias estratégias – Use pesquisa de vetores densos e filtragem de palavras-chave juntas, se necessário. Execute testes A/B facilmente – Compare diferentes sequestradores para ver o que dá melhores resultados para seus usuários. Conecte-se a qualquer agente ou ferramenta – Se você está construindo um chatbot, uma interface de pesquisa ou um sistema de agente completo, esta interface de retriever é fácil. Pense no protocolo de recuperação como o contrato da API entre o seu "cérebro de recuperação" e tudo o mais.Uma vez que você o siga, você está livre para inovar o que quiser nos bastidores. Plugging Superlinked em LlamaIndex Tudo bem, então o A classe é basicamente nossa ferramenta para dar recomendações de jogo inteligentes e semânticas. começamos com uma rápida olhada em como é montado, e depois mergulhamos mais profundamente em cada parte para ver o que faz com que essa coisa toque. SuperlinkedSteamGamesRetriever Em primeiro lugar está o , pense nisso como a fundação. É o que mantém tudo organizado e confiável. Confira os principais detalhes como , em , em e Isso mantém toda a informação do jogo limpa e consistente, e ele se conecta diretamente ao pipeline do Superlinked para que tudo flua sem problemas. 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() O próximo é o É aqui que acontece a magia da busca semântica. modelo para transformar um monte de informações do jogo (como o nome, descrição, gênero, etc.) em representações vetoriais densas. Basicamente, ele suaviza todo esse texto juntos em algo que o modelo pode entender. Então, se alguém procura por algo como “aventura de mundo aberto”, ele pode encontrar jogos que realmente se encaixam nessa vibração, não apenas aqueles com essas palavras exatas. text similarity space sentence-transformers/all-mpnet-base-v2 significa self.text_space = sl.TextSimilaritySpace( text=self.game.combined_text, model="sentence-transformers/all-mpnet-base-v2" ) O É onde as coisas realmente começam a clicar.Toma diferentes bits de informações (como o nome do jogo, descrição, gênero e mais) e as quebra em um grande pedaço de texto.Isto dá ao modelo uma imagem mais completa de cada jogo ao transformá-lo em vetores.O resultado?Melhor recomendações, uma vez que está puxando um monte de detalhes diferentes de uma vez em vez de apenas olhar para uma coisa isoladamente. 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) ) E por fim, é o que torna tudo super rápido. graças ao Superlinked's , o retriever pode lidar com consultas em tempo real, sem atrasos, apenas resultados instantâneos. isso significa que se alguém está caçando por um gênero específico ou apenas navegando por algo novo para jogar, eles recebem recomendações rápidas e precisas sem esperar. 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]) Coloque todas essas peças juntas, e você tem o — uma configuração sólida para entregar recomendações de jogos que realmente fazem sentido para o usuário. É rápido, inteligente e pessoal. Aqui está o que a coisa toda parece em ação... 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 Parte 3: Definição e configuração de esquema superligado Começando com o design de esquemas, agora em Superlinked, o esquema não é apenas sobre definir tipos de dados, é mais como uma definição formal entre nossos dados e o motor de computação vetorial subjacente. Em nosso O esquema é definido assim: 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() Vamos descrever o que alguns desses elementos realmente : Faça (→ ) 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" ) Porque os usuários não buscam apenas por gênero ou nome, eles descrevem o que são Ao inserir todos os sinais importantes em , podemos combinar melhor consultas de linguagem natural e fuzzy com os jogos mais relevantes. Procurando por combined_text Parte 4: Configuração do espaço vetorial # 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]) Para impulsionar a pesquisa semântica em nosso conjunto de dados de jogos do Steam, fiz duas escolhas de design intencionais que equilibram desempenho, simplicidade e flexibilidade. Primeiro, para o modelo de embalação, selecionei Este modelo produz embeddings 768-dimensional que atingem um sólido meio terreno: eles são expressivos o suficiente para capturar significado semântico rico, mas ligeiro o suficiente para ser rápido na produção. Eu quero dizer que é um modelo de propósito geral confiável, conhecido por desempenhar bem em vários tipos de texto - o que importa muito quando seus dados variam de tags de gênero curtos a descrições de jogos de forma longa. Gerencie com limpeza. all-mpnet-base-v2 all-mpnet-base-v2 Em seguida, embora o Superlinked suporte à indexação de vários espaços – onde você pode combinar vários campos ou mesmo modalidades (como texto + imagens). Eu teria incluído o Aqui também, mas eu não tenho as informações sobre a data de lançamento dos jogos.Mas apenas para colocar isso aqui, se tivermos as informações da data de lançamento, eu poderia conectar o RecencySpace aqui, e eu posso até mesmo classificar os jogos com o Além dos jogos mais recentes, o TextSimilaritySpace RecencySpace TextSimilaritySpace Parte 5: Pipeline de dados e configuração de executores # 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") No coração do nosso sistema de recuperação está um gasoduto simplificado construído para clareza e velocidade. , que serve como nossa camada ETL. Ele garante que cada campo no conjunto de dados é corretamente digitado e consistentemente mapeado para o nosso esquema; essencialmente atuando como o contrato entre os nossos dados CSV brutos e a camada de indexação Superlinked. DataFrameParser Uma vez que os dados estão estruturados, eu os alimento em um , o que é ideal para conjuntos de dados que se encaixam confortavelmente na memória. Esta abordagem mantém tudo rápido sem introduzir sobrecarga de armazenamento ou latência de rede. Isso torna o Superlinked adequado para aplicações em tempo real, como sistemas de recomendação interativos, onde a velocidade afeta diretamente a experiência do usuário. InMemorySource InMemoryExecutor Parte 6: O motor de recuperação 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) Uma das coisas que torna o Superlinked realmente agradável de trabalhar é o seu criador de consultas de estilo fluente. Se você usou bibliotecas como SQLAlchemy ou Django ORM, o padrão se sentirá familiar. Cada método na cadeia adiciona clareza em vez de confusão. Em nosso caso, a consulta começa selecionando o índice relevante e definindo a pesquisa de semelhança usando o método, que calcula a semelhança cosina no espaço de incorporação. Isto é o que nos permite recuperar jogos semânticamente próximos com base na consulta de idioma natural do usuário. .similar() Outra decisão de design que tomei foi Eu me importo com o resultado, em vez de fazer algo como Isso pode soar menor, mas mantém os dados magros, reduz o processamento em excesso e garante que não estamos passando por cargas úteis desnecessárias durante o pós-processamento. explicitly select the fields SELECT * Parte 7: Processamento de resultados e criação de nós # 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 Agora que recebemos os resultados do Superlinked, eu os transformei em um formato que joga bem com o LlamaIndex. Esta se torna o conteúdo de cada nó, tornando mais fácil para o modelo de idioma raciocinar.É um toque pequeno, mas realmente melhora o quão relevante e compreensível os dados recuperados são quando passados para o LLM. human-readable text Em seguida, certifique-se de que do conjunto de dados, incluindo coisas como gênero, preços e detalhes do jogo - são retidos nos metadados. Isto é crucial porque os processos subsequentes podem querer filtrar, exibir ou classificar os resultados com base nessas informações. all original fields Finalmente, aplique um peso leve estratégia. Em vez de confiar em pontuações de semelhança brutas, atribuímos pontuações com base na posição do resultado na lista classificada. Isso mantém as coisas simples e consistentes. O resultado superior sempre tem a pontuação mais alta, e o resto segue em ordem descendente. Não é fantástico, mas nos dá um sistema de pontuação estável e interpretável que funciona bem em várias consultas. score normalisation Tempo de exibição: Executar o pipeline Agora que todos os componentes estão no lugar, é hora de trazer nosso sistema de geração aumentada de recuperação (RAG) para a vida. # 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) Esta configuração combina o nosso retriever semântico personalizado com um gerador de resposta alimentado por LLM. As consultas se movem suavemente através do pipeline, e em vez de apenas espalhar dados brutos, ele retorna uma sugestão ponderada sobre que tipo de jogo o usuário pode realmente querer jogar com base no que eles pediram. Takeaways 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 Se você quiser uma conversa rápida sobre onde a mistura de codificadores ou a recuperação multi-campo se encaixa em seu pipeline, ! Converse com um de nossos engenheiros Converse com um de nossos engenheiros Converse com um de nossos engenheiros Referências de integração: Pacote Superlinked Retriever em documentos PyPI e LlamaIndex para Retrievers personalizados. contribuintes Vipul Maheshwari, autor Filip Makraduli, editor