Una forma más inteligente y rápida de buscar en tu biblioteca de Steam Conoces el sentimiento. Usted está buscando un juego que es estratégico, co-op, tal vez con un tema de ciencia ficción. Obtiene una pared de títulos que tipo de encuentro. Lo que usted quería era una lista corta que realmente captura la vibración detrás de sus palabras. En esta guía, mostramos cómo construir exactamente eso al combinar Superlinked con LlamaIndex. El resultado es un retriever de juego de Steam personalizado que entiende el género más la descripción más las etiquetas, y sirve respuestas en milisegundos. * El ¿Quieres ver esto en tus datos con consultas reales y números de latencia? . Get in touch Entra en contacto Entra en contacto Título: DR Los buscadores personalizados le dan control sobre el contexto del dominio, los metadatos y la lógica de clasificación. Superen la búsqueda de similitud genérica cuando las consultas son confusas o pesadas. Superlinked combina varios campos de texto en un espacio semántico y ejecuta consultas en la memoria para obtener resultados rápidos. LlamaIndex proporciona la interfaz del retriever limpio y se conecta directamente a los motores de consultas y la síntesis de respuestas. Hay una integración oficial de Superlinked Retriever para LlamaIndex que puede importar y usar. Superlinked Retriever oficial para LlamaIndex Superlinked integra con LlamaIndex a través del oficial listado en LlamaHub, para que pueda agregar Superlinked a su pila LlamaIndex existente con una simple instalación y Entonces, póngalo en una Aprende más sobre la Los parámetros de clase y constructor se documentan en la referencia API de LlamaIndex. SuperlinkedRetriever from llama_index.retrievers.superlinked import SuperlinkedRetriever RetrieverQueryEngine . Página oficial de integración . Página oficial de integración Página oficial de integración 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") ¿Prefieres construirlo a mano o personalizar la lógica más? Why Superlinked + LlamaIndex? ¿Por qué Superlinked + LlamaIndex? El objetivo es simple: tomar los puntos fuertes de Superlinked para la búsqueda multi-campo y empacarlos para que los desarrolladores puedan adoptar y extender en sistemas RAG reales. Superlinked le ayuda a definir espacios vectoriales expresivos y consultas que mezclan campos como nombre, descripción y género en una única vista semántica. LlamaIndex trae la abstracción de la búsqueda, los motores de consultas y la síntesis de respuestas que cierra en aplicaciones y agentes con una cola mínima. También puedes seguir en Utilizando los mismos bloques de construcción de los Superlinked Notebooks. Google Colab Google Colab Colab Por qué los Custom Retrievers Importan Ajustado para tu dominio – Los retrievers genéricos son buenos para el uso general, pero tienden a perder las cosas sutiles.Piensa en el jargón, la abreviatura o la expresión específica del dominio, que por lo general no se toman a menos que tu retriever sepa qué buscar. Funciona más allá de texto – La mayoría de los datos del mundo real no son solo texto simple. A menudo tendrás metadatos y etiquetas también. Por ejemplo, en un sistema de recomendación de juegos, no solo nos importa la descripción del juego. También queremos factorizar en géneros, etiquetas, calificaciones de usuarios y más. Piensa en esta lógica: alguien que busca un “juego de estrategia cooperativa con elementos de ciencia ficción” no llegará lejos con el ajuste de texto solo. Lógica de filtración y clasificación personalizada – A veces desea aplicar sus propias reglas a cómo las cosas se anotan o filtran. Tal vez desee priorizar el contenido más reciente, o penalizar los resultados que no cumplen ciertos umbrales de calidad. Ganancias en el rendimiento: Seamos reales: las soluciones de propósito general están construidas para funcionar “bien” para todos, no muy bien para usted.Si conoce sus datos y sus patrones de acceso, puede ajustar su retriever para funcionar más rápido, clasificar mejor y reducir el ruido innecesario en los resultados. Disrupción de la implementación Parte 1: Dependencias básicas e importaciones 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 La estructura de importación revela nuestro enfoque híbrido: LlamaIndex Core: proporciona la capa de abstracción de recuperación Superlinked Framework: maneja la computación vectorial y la búsqueda semántica Pandas: Gestiona el preprocesamiento y la manipulación de datos Parte 2: Comprensión de LlamaIndex Custom Retrievers Antes de sumergirse en nuestra implementación Superlinked, es crucial comprender cómo funciona la arquitectura de retriever personalizado de LlamaIndex y por qué es tan potente para construir aplicaciones RAG específicas de dominio. BaseRetriever Abstracción LlamaIndex ofrece un abstracto La belleza de este diseño radica en su simplicidad: cualquier retriever personalizado sólo necesita implementar un 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 El foso aquí es la presencia del Protocolo de recuperación del LlamaIndex. Como este "protocolo de recuperación" hace que sea fácil conectar diferentes backends o estrategias sin tener que tocar el resto de su 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 qué esto importa? Esta abstracción es limpia y Esto significa que puedes: poderoso Combine varias estrategias: use la búsqueda de vectores densos y la filtración de palabras clave juntas si es necesario. Execute fácilmente las pruebas A/B: Compare diferentes retrievers para ver qué da mejores resultados para sus usuarios. Se conecta a cualquier agente o herramienta: ya sea que esté construyendo un chatbot, una interfaz de búsqueda o un sistema de agentes completos, esta interfaz de retriever se puede encontrar fácilmente. Piense en el protocolo de recuperación como el contrato de API entre su "cerebro de recuperación" y todo lo demás.Una vez que lo sigas, eres libre de innovar lo que quieras detrás de los escenarios. Plugging Superlinked en LlamaIndex Bueno, así que el La clase es básicamente nuestra herramienta para dar recomendaciones inteligentes y semánticas de juegos. Comenzaremos con una rápida mirada a cómo se reúne, y luego profundizar en cada parte para ver realmente lo que hace que esta cosa toque. SuperlinkedSteamGamesRetriever En primer lugar está la , piense en ella como la base. Es lo que mantiene todo organizado y confiable. Descargar detalles clave como , de , de , y Esto mantiene toda la información del juego limpia y consistente, y se conecta directamente a la tubería de Superlinked para que todo fluya sin 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() El siguiente es el Aquí es donde ocurre la magia de la búsqueda semántica. modelo para convertir un montón de información del juego (como el nombre, la descripción, el género, etc.) en representaciones vectoriales densas. Básicamente, suaviza todo ese texto juntos en algo que el modelo puede entender. Así que si alguien busca algo como “aventura en el mundo abierto”, puede encontrar juegos que realmente se ajustan a esa vibración, no sólo aquellos con esas palabras exactas. 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" ) El es donde las cosas realmente comienzan a hacer clic. Se toman diferentes bits de información (como el nombre del juego, la descripción, el género, y más) y se los rompe en un gran pedazo de texto. Esto da al modelo una imagen más completa de cada juego cuando se convierte en vectores. El resultado? manera mejores recomendaciones, ya que está tirando en un montón de diferentes detalles a la vez en lugar de sólo mirar una cosa en aislamiento. 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) ) Y por último, es lo que hace que todo sea super snappy. gracias a Superlinked , el retriever puede manejar consultas en tiempo real, sin retrasos, sólo resultados instantáneos. que significa que si alguien está cazando por un género específico o simplemente navegando por algo nuevo para jugar, obtienen recomendaciones rápidas y precisas sin 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]) Pon todas estas piezas juntas, y tienes el — una configuración sólida para entregar recomendaciones de juegos que realmente tienen sentido para el usuario. Es rápido, inteligente y personal. 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: Definición y configuración del esquema superligado Comenzando con el diseño de esquemas, ahora en Superlinked, el esquema no se trata sólo de definir tipos de datos, es más como una definición formal entre nuestros datos y el motor de computación vectorial subyacente. En nuestro El esquema se define así: 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 a descomponer lo que algunos de estos elementos realmente : hace (→ ) 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 los usuarios no sólo buscan por género o nombre, describen lo que son Al incorporar todas las señales importantes en , podemos combinar mejor las consultas de lenguaje natural con los juegos más relevantes. Buscando por combined_text Parte 4: Configuración del espacio vectorial # 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 impulsar la búsqueda semántica sobre nuestro conjunto de datos de juegos de Steam, hice dos opciones de diseño intencionales que equilibran el rendimiento, la simplicidad y la flexibilidad. En primer lugar, para el modelo de embalaje, he seleccionado de la biblioteca de Transformadores de Sentencia. Este modelo produce embeddings de 768 dimensiones que golpean un sólido medio terreno: son lo suficientemente expresivos como para capturar significados semánticos ricos, pero lo suficientemente ligeros como para ser rápidos en la producción. Me refiero a que es un modelo de propósito general confiable, conocido por desempeñar bien en diversos tipos de texto, lo que importa mucho cuando sus datos van desde etiquetas de género cortas a descripciones de juegos de forma larga. Tratar con limpieza. all-mpnet-base-v2 all-mpnet-base-v2 A continuación, aunque Superlinked soporta la indexación de múltiples espacios, donde puede combinar múltiples campos o incluso modalidades (como texto + imágenes). Yo habría incluido el Aquí también, pero no tengo la información sobre la fecha de lanzamiento de los juegos.Pero sólo para poner esto aquí, si tenemos la información de la fecha de lanzamiento, podría conectar el RecencySpace aquí, e incluso puedo ordenar los juegos con la Además de los juegos más recientes, el TextSimilaritySpace RecencySpace TextSimilaritySpace Parte 5: Pipeline de datos y configuración de ejecutores # 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") En el corazón de nuestro sistema de recuperación está una tubería simplificada construida para la claridad y la velocidad. , que sirve como nuestra capa ETL. Se asegura de que cada campo en el conjunto de datos se escriba correctamente y se mapee consistentemente a nuestro esquema; esencialmente actuando como el contrato entre nuestros datos CSV crudos y la capa de indexación superligada. DataFrameParser Una vez que los datos están estructurados, los alimentamos en un , que es ideal para los conjuntos de datos que se encajan cómodamente en la memoria. Este enfoque mantiene todo relámpago rápido sin introducir sobrecarga de almacenamiento o latencia de red. Finalmente, las consultas son manejadas por un Esto es lo que hace que Superlinked sea adecuado para aplicaciones en tiempo real como sistemas de recomendación interactivos, donde la velocidad afecta directamente a la experiencia del usuario. InMemorySource InMemoryExecutor Capítulo 6: El motor de recuperación 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) Una de las cosas que hace que Superlinked sea realmente agradable de trabajar con es su constructor de consultas de estilo fluido. Si ha utilizado bibliotecas como SQLAlchemy o Django ORM, el patrón se sentirá familiar. Cada método en la cadena agrega claridad en lugar de confusión. En nuestro caso, la consulta comienza seleccionando el índice relevante y definiendo la búsqueda de similitud utilizando el método, que calcula la similitud cosina en el espacio de embalaje. Esto es lo que nos permite recuperar juegos semánticamente cercanos basados en la consulta de idioma natural del usuario. .similar() Una de las decisiones de diseño que tomé fue Me importa en el resultado, en lugar de hacer algo como Esto puede sonar menor, pero mantiene el lean de datos, reduce el procesamiento en exceso, y asegura que no estamos pasando por encima de la carga útil innecesaria durante el post-procesamiento.Piensa en ello como precisión sobre el volumen, especialmente importante cuando se están moviendo datos entre componentes en una tubería sensible a la latencia. explicitly select the fields SELECT * Parte 7: Procesamiento de resultados y creación de nodos # 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 Ahora que recibimos los resultados de Superlinked, los transformé en un formato que juega bien con LlamaIndex. string combinando el nombre del juego con su breve descripción. Esto se convierte en el contenido de cada nodo, lo que facilita al modelo de lenguaje para razonar. Es un toque pequeño, pero realmente mejora la pertinencia y comprensión de los datos recuperados cuando se pasan al LLM. human-readable text A continuación, nos aseguramos de que de los conjuntos de datos, incluyendo cosas como género, precios y detalles del juego, se conservan en los metadatos. Esto es crucial porque los procesos subsiguientes pueden querer filtrar, mostrar o clasificar los resultados basados en esta información. all original fields Por último, aplique un peso ligero estrategia. En lugar de depender de las puntuaciones de similitud cruda, asignamos puntuaciones basadas en la posición del resultado en la lista de clasificación. Esto mantiene las cosas simples y consistentes. El resultado superior siempre tiene la puntuación más alta, y el resto sigue en orden descendente. No es fantástico, pero nos da un sistema de puntuación estable e interpretable que funciona bien en diferentes consultas. score normalisation Mostrar tiempo: ejecutar la tubería Ahora que todos los componentes están en su lugar, es hora de llevar a la vida nuestro sistema de Generación Aumentada de Retrieval (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) Esta configuración combina nuestro retriever semántico personalizado con un generador de respuestas alimentado por LLM. Las consultas se mueven suavemente a través de la tubería, y en lugar de simplemente saltar datos crudos, devuelve una sugerencia pensativa sobre qué tipo de juego el usuario podría realmente querer jugar basado en lo que pidieron. 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 Si desea una conversación rápida sobre dónde la mezcla de codificadores o la recuperación multi-campo se ajusta a su tubería, ! Habla con uno de nuestros ingenieros Habla con uno de nuestros ingenieros Habla con uno de nuestros ingenieros Referencias de integración: paquete de retriever superligado en los documentos PyPI y LlamaIndex para retrievers personalizados. Contribuyentes Vipul Maheshwari, autor Filip Makraduli, editor