Filtragem inteligente: um guia para gerar pré-filtros para pesquisa semântica do Atlas
Vipul Bhardwaj, Fabian Valle20 min read • Published Sep 03, 2024 • Updated Oct 02, 2024
APLICATIVO COMPLETO
Avalie esse Tutorial
Você já pesquisou "velhas comedias em branco e branco" apenas para ser alvo de uma mistura de filmes de ação modernos? Frustrante, certo? Esse é o desafio dos mecanismos de pesquisa tradicionais — eles geralmente têm dificuldade em entender as nuances de nossas queries, deixando-nos encontrar resultados irrelevantes.
É aqui que entra a filtragem inteligente. É uma mudança de jogo que usa metadados e pesquisa vetorial para fornecer resultados de pesquisa que realmente correspondem à sua intenção. Imagine encontrar exatamente as comedias clássicas que você deseja, sem problemas.
Neste blog, mostraremos o que é filtragem inteligente, como ela funciona e por que é essencial para criar melhores experiências de pesquisa. Vamos descobrir a milagrosidade por trás dessa tecnologia e explorar como ela pode redefinir a maneira como você pesquisa.
A pesquisa vetorial é uma ferramenta poderosa que ajuda os computadores a entender o significado por trás dos dados, não apenas as próprias palavras. Em vez de palavras-chave correspondentes, ele se concentra nos conceitos e relacionamentos subjacentes. Imagine pesquisar "cachorro" e obter resultados que incluem "filhote de cachorro", "canino" e até mesmo imagens de cachorros. Essa é a sorte da pesquisa vetorial!
Como funciona? Bem, transforma dados em representações matemáticas chamadas vetores. Esses vetores são como coordenadas em um mapa, e pontos de dados semelhantes estão mais próximos nesse espaço vetorial. Quando você pesquisa algo, o sistema encontra os vetores mais próximos da sua consulta, fornecendo resultados semanticamente semelhantes.
Embora a pesquisa vetorial seja ótima para entender o contexto, ela às vezes fica a desejar quando se trata de tarefas simples de filtragem. Por exemplo, encontrar todos os filmes lançados antes de 2000 exige uma filtragem precisa, não apenas a compreensão semântica. É aqui que entram os filtros inteligentes para complementar a pesquisa vetorial.
Embora a pesquisa vetorial nos aproxime da compreensão do verdadeiro significado das queries, ainda há uma lacuna entre o que os usuários desejam e o que os mecanismos de pesquisa oferecem. Queries de pesquisa complexas como "primários filmes de comedia antes de 2000" ainda podem ser um desafio. A pesquisa semântica pode entender os conceitos de "comédia" e "filmes", mas pode ter problemas com as especificidades de "mais antigo" e "antes 2000 ".
É aqui que os resultados começam a ficar confusos. Podemos obter uma mistura de comeries antigas e novas, ou até mesmo drams que foram incluídos erroneamente. Para satisfazer realmente os usuários, precisamos encontrar uma maneira de refinar esses resultados de pesquisa e torná-los mais precisos. É aqui que os pré-filtros entram em ação.
![ Fluxograma mostrando o processo de geração de pré-filtros para uma query de usuário. O fluxo começa com a filtragem de metadados, onde a query do usuário e os metadados são passados para um construtor de query que cria uma query pré-filtro. Isso é então traduzido em uma query do MongoDB . O fluxo continua com um construtor de query de faixa de tempo, implementado como um agente LLM, que usa uma ferramenta QueryExecutorMongoDB para fazer query de dados de um banco de banco de dados vetorial. Finalmente, o filtro de metadados e o filtro de query baseado em tempo são mesclados para criar o pré-filtro final.][1]
A filtragem inteligente é a solução para esse desafio. É uma técnica que utiliza os metadados de um conjunto de dados para criar filtros específicos, refinando os resultados da pesquisa e tornando-os mais precisos e eficientes. Ao analisar as informações sobre seus dados, como estrutura, conteúdo e atributos, a filtragem inteligente pode identificar critérios relevantes para filtrar sua pesquisa.
Imagine pesquisar por "filmes de comedia lançados antes de 2000." A filtragem inteligente usaria metadados como gênero, data de lançamento e, potencialmente, até mesmo palavras-chave do roteiro para criar um filtro que inclua apenas filmes que correspondam a esses critérios. Dessa forma, você obtém uma lista exatamente do que deseja, sem o ruído irrelevante.
A filtragem inteligente é um processo de várias etapas que envolve a extração de informações de seus dados, sua análise e a criação de filtros específicos com base em suas necessidades. Vamos decompor:
- Extração de metadados: O primeiro passo é coletar informações relevantes sobre seus dados. Isso inclui detalhes como:
- Estrutura de dados: como os dados estão organizados (por exemplo, tabelas, documentos)?
- Atributos: que tipo de informação está incluída (por exemplo, título, descrição, data de lançamento)?
- Tipos de dados: Em que formato os dados estão (por exemplo, texto, números, datas)?
- Geração de pré-filtros: depois de ter os metadados, você pode começar a criar pré-filtros. Essas são condições específicas que os dados devem atender para serem incluídos nos resultados da pesquisa. Por exemplo, se você estiver pesquisando filmes de comedia lançados antes 2000, poderá criar pré-filtros para:
- gênero: comedia
- Data de lançamento: antes 2000
- Integração com a pesquisa vetorial: A etapa final é combinar esses pré-filtros com a pesquisa vetorial. Isso garante que a pesquisa vetorial considere apenas os pontos de dados que correspondam aos seus critérios predefinidos.
Ao seguir essas etapas, a filtragem inteligente melhora significativamente a precisão e a eficiência dos resultados da sua pesquisa.
- MongoDBAtlasVectorSearch a partir da estrutura do LangChain e GPT-4o para implementar filtragem inteligente.
Para ser bem-sucedido com este tutorial, você precisará de:
- O IDE de sua escolha. Este tutorial usa um bloco de anotações Jupyter. Sinta-se à vontade para executar seus comandos diretamente de um bloco de anotações.
- Uma chave de API OpenAI. Usaremos o OpenAI LLM para incorporar nossos dados e gerar filtros. Você precisará de acesso a:
text-embedding-ada-002
modelo de incorporação.gpt-4o
para geração de texto.
- Python <4.0, >=3.8.1.
As instruções a seguir são para execução em um bloco de anotações, mas podem ser adaptáveis para execução no seu IDE. Apenas haverá algumas diferenças.
Instalar as dependências necessárias.
1 !pip install pymongo==4.7.2 langchain-core==0.2.6 langchain-openai==0.1.7 langchain==0.2.1 langchain-community==0.2.4 lark==1.1.9
Para os fins deste tutorial, usaremos alguns dados de filme de exemplo. Definiremos uma lista de documentos do LangChain. Cada documento representa um filme que tem um
page_content
com descrição do filme e alguns metadados com ele. Nos dados de amostra, os metadados têm release_date
, rating
e genre
.1 from langchain_core.documents import Document 2 3 # Sample movies data. The metadata has release_date, rating, genre, director 4 docs = [ 5 Document( 6 page_content="A bunch of scientists bring back dinosaurs and mayhem breaks loose", 7 metadata={"release_date": "1994-04-15", "rating": 7.7, "genre": ["action", "scifi", "adventure"]}, 8 ), 9 Document( 10 page_content="Leo DiCaprio gets lost in a dream within a dream within a dream within a ...", 11 metadata={"release_date": "2010-07-16", "rating": 8.2, "genre": ["action", "thriller"]}, 12 ), 13 Document( 14 page_content="A psychologist / detective gets lost in a series of dreams within dreams within dreams and Inception reused the idea", 15 metadata={"release_date": "2006-11-25", "rating": 8.6, "genre": ["anime", "thriller", "scifi"]}, 16 ), 17 Document( 18 page_content="A bunch of normal-sized women are supremely wholesome and some men pine after them", 19 metadata={"release_date": "2019-12-25", "rating": 8.3, "genre": ["romance", "drama", "comedy"]}, 20 ), 21 Document( 22 page_content="Toys come alive and have a blast doing so", 23 metadata={"release_date": "1995-11-22", "genre": ["animation", "fantasy"]}, 24 ), 25 Document( 26 page_content="The toys embark on a rescue mission to save Woody after he is stolen by a toy collector.", 27 metadata={"release_date": "1999-11-24", "genre": ["animation", "adventure", "comedy"]}, 28 ), 29 Document( 30 page_content="The toys face an uncertain future as they are accidentally donated to a daycare center, leading to a thrilling escape plan.", 31 metadata={"release_date": "2010-06-18", "genre": ["animation", "adventure", "comedy"]}, 32 ) 33 ]
1 import getpass 2 from pymongo import MongoClient 3 4 # set up your MongoDB connection 5 connection_string = getpass.getpass(prompt= "Enter connection string WITH USER + PASS here") 6 7 client = MongoClient(connection_string) 8 9 # name your database and collection anything you want since it will be created when you enter your data 10 database_name = "smart_filtering" 11 collection_name = "movies" 12 collection = client[database_name][collection_name]
Para este tutorial, estamos usando o modelo de incorporação
text-embedding-ada-002
do OpenAI.1 import json 2 3 from langchain_openai import OpenAIEmbeddings 4 5 # openAI API credentials 6 openai_api_base = getpass.getpass(prompt= "Put in OpenAI URL here") 7 openai_api_key = getpass.getpass(prompt= "Put in OpenAI API Key here") 8 9 # default_headers is optional 10 default_headers = getpass.getpass(prompt= "Put in OpenAI API headers if applicable here") 11 default_headers = json.loads(default_headers) if default_headers else None 12 13 embeddings = OpenAIEmbeddings(openai_api_key=openai_api_key, 14 openai_api_base=openai_api_base, 15 default_headers=default_headers, 16 model="text-embedding-ada-002")
Usaremos o recuperadorMongoDBAtlasVectorSearch da LangChain para incorporar e consumir nossos dados. Ele aceitará uma lista de Docs, o objeto de incorporação que criamos anteriormente e o cliente MongoDB . Esta etapa inicializará nossa coleção MongoDB com os dados de filmes com incorporações.
1 from langchain.vectorstores import MongoDBAtlasVectorSearch 2 3 # This step will generate the embeddings and insert the documents 4 vectorStore = MongoDBAtlasVectorSearch.from_documents(docs, embeddings, collection=collection)
Após a inicialização, os dados inseridos serão os seguintes:
![ Fluxograma mostrando o processo de geração de pré-filtros para uma query de usuário. O fluxo começa com a filtragem de metadados, onde a query do usuário e os metadados são passados para um construtor de query que cria uma query pré-filtro. Isso é então traduzido em uma query do MongoDB . O fluxo continua com um construtor de query de faixa de tempo, implementado como um agente LLM, que usa uma ferramenta QueryExecutorMongoDB para fazer query de dados de um banco de banco de dados vetorial. Finalmente, o filtro de metadados e o filtro de query baseada em tempo são mesclados para criar o pré-filtro final.][2] Temos o
page_content
no campo text
que é padrão para o armazenamento de vetoresMongoDBAtlasVectorSearch. A incorporação é uma array de flutuações e salva em embedding
. Os campos de metadados estão presentes como release_date
, rating
e genre
.Antes de realizarmos uma pesquisa em nossos dados, precisamos criar um índice de pesquisa. Siga estas etapas para criar um índice de pesquisa.
- Em AtlasUI, Go para sua coleção
smart_filtering.movies
. - Clique em
Search Indexes
. - Clique em
Create Index
. - Em
Atlas Search
, selecioneJson Editor
. - Nomeie seu índice como
default
e copie/cole a definição de índice abaixo:
1 { 2 "analyzer": "lucene.standard", 3 "searchAnalyzer": "lucene.standard", 4 "mappings": { 5 "fields": { 6 "embedding": { 7 "type": "knnVector", 8 "dimensions": 1536, 9 "similarity": "cosine" 10 }, 11 "rating": { 12 "type": "number" 13 }, 14 "release_date": { 15 "type": "token" 16 }, 17 "genre": { 18 "type": "token" 19 } 20 } 21 } 22 }
O índice levará alguns segundos para ser criado. Depois que o índice for criado com sucesso, estarão prontos para consultar nossos dados.
Digamos que um usuário queira encontrar documentos do último filme lançado antes de alguma data no gênero de ação com esta consulta:
I want to watch a movie released before year 2000 in the animation genre with the latest release date
.Tentaremos a pesquisa semântica para esta query e veremos quais resultados obteremos.
1 vectorStore = MongoDBAtlasVectorSearch( collection, embeddings ) 2 3 query = "I want to watch a movie released before year 2000 in the animation genre with the latest release date" 4 5 docs = vectorStore.similarity_search(query) 6 for doc in docs: 7 print(doc.page_content)
Saída: Recebemos quatro filmes na saída, três dos quais não são relevantes para a query do usuário. Esse é o problema da pesquisa semântica que resolveremos usando filtragem inteligentes.
1 A bunch of scientists bring back dinosaurs and mayhem breaks loose 2 A psychologist / detective gets lost in a series of dreams within dreams within dreams and Inception reused the idea 3 Toys come alive and have a blast doing so 4 Leo DiCaprio gets lost in a dream within a dream within a dream within a ...
Vamos analisar os requisitos de filtragem da query do usuário:
I want to watch a movie released before year 2000 in the animation genre with the latest release date
.Existem dois tipos de requisitos de filtro na consulta do usuário e vamos resolvê-los em duas etapas.
Estamos realizando a filtragem em dois estágios porque são duas tarefas diferentes e devemos executar uma tarefa de cada vez com LLMs para obter melhores resultados.
Estamos realizando a filtragem em dois estágios porque são duas tarefas diferentes e devemos executar uma tarefa de cada vez com LLMs para obter melhores resultados.
Estágio 1 — filtro de metadados: Um pré-filtro pode ser gerado com base na query e metadados.
- O lançamento antes do ano 2000 pode ser um filtro em potencial.
- O gênero de programação pode ser um filtro em potencial.
Estágio 2 — filtro baseado em tempo: Também precisaremos levar em conta a data de lançamento mais recente. Precisamos consultar os dados para encontrar o filme da data de lançamento mais recente.
Usaremos o load_query_constructor_runnable do LangChain para gerar nossa consulta de filtro e, em seguida, usaremos o MongoDBAtlasTranslator para converter a consulta em uma consulta válida do MongoDB . Vamos precisar do abaixo para passar para load_query_constructor_runnable:
- Uma descrição do conteúdo dos nossos dados que será passado em document_content
- Atributos de metadados dos nossos dados passados em concern_info
- Solicitação com alguns exemplos que o LLM usará para gerar a query
Para fins deste tutorial, definiremos os metadados que usaremos para fins de filtragem. Precisamos definir o conteúdo e fornecer um
document_content_description
, name
, description
e type
de cada campo. Isso requer algum conhecimento básico dos dados.Descrição do conteúdo dos nossos dados:
1 document_content_description = "Brief summary of a movie"
Atributos de metadados de nossos dados:
1 from langchain.chains.query_constructor.base import AttributeInfo 2 3 metadata_field_info = [ 4 AttributeInfo( 5 name="genre", 6 description="Keywords for filtering: ['animation', 'action', 'comedy', 'romance', 'thriller']", 7 type="[string]", 8 ), 9 AttributeInfo( 10 name="release_date", 11 description="The date the movie was released on", 12 type="string", 13 ), 14 AttributeInfo( 15 name="rating", description="A 1-10 rating for the movie", type="float" 16 ), 17 ]
Nosso objetivo é extrair informações significativas da query do usuário que podemos usar para a filtragem de metadados. Passaremos os metadados de nossos dados no contexto de forma que o LLM tenha uma ideia das informações que podem ser usadas como filtro. Usaremos uma técnica de solicitação de algumas capturas para gerar nossos resultados.
A solicitação de many-shot é uma técnica usada com modelos de linguagem grandes, em que o modelo recebe alguns exemplos de uma tarefa dentro da instrução para ajudar a orientá-la a produzir a saída desejada.
Observação: atualize o prompt de acordo com seu caso de uso.
O prompt abaixo será passado como
schema_prompt
no load_query_constructor_runnable do LangChain que será usado para gerar a query.O prompt será usado para instruir o LLM sobre como gerar a query. Usaremos o prompt definido no prompt do construtor de query do LangChain,mas o alteraremos conforme nosso caso de uso.
Vamos detalhar o prompt e entegiá-lo:
Vamos detalhar o prompt e entegiá-lo:
- No início, instruímos o LLM a gerar o resultado no formato JSON com a query e o filtro reescritos como chaves.
- Instruímos o LLM a não incluir nenhuma informação na nova query que já esteja contabilizada no filtro.
- As variáveis estão envolvidas em
{}
no prompt que será preenchido posteriormente.
1 from langchain_core.prompts import PromptTemplate 2 3 4 DEFAULT_SCHEMA = """\ 5 << Structured Request Schema >> 6 When responding use a markdown code snippet with a JSON object formatted in the following schema: 7 8 ```json 9 {{{{ 10 "query": string \\ rewritten user's query after removing the information handled by the filter 11 "filter": string \\ logical condition statement for filtering documents 12 }}}} 13 ``` 14 15 The query string should be re-written. Any conditions in the filter should not be mentioned in the query as well. 16 17 A logical condition statement is composed of one or more comparison and logical operation statements. 18 19 A comparison statement takes the form: `comp(attr, val)`: 20 - `comp` ({allowed_comparators}): comparator 21 - `attr` (string): name of attribute to apply the comparison to 22 - `val` (string): is the comparison value 23 24 A logical operation statement takes the form `op(statement1, statement2, ...)`: 25 - `op` ({allowed_operators}): logical operator 26 - `statement1`, `statement2`, ... (comparison statements or logical operation statements): one or more statements to apply the operation to 27 28 Make sure that you only use the comparators and logical operators listed above and no others. 29 Make sure that filters only refer to attributes that exist in the data source. 30 Make sure that filters only use the attributed names with its function names if there are functions applied on them. 31 Make sure that filters only use format `YYYY-MM-DD` when handling date data typed values. 32 Make sure you understand the user's intent while generating a date filter. Use a range comparators such as gt | gte | lt | lte for partial dates. 33 Make sure that filters take into account the descriptions of attributes and only make comparisons that are feasible given the type of data being stored. 34 Make sure that filters are only used as needed. If there are no filters that should be applied return "NO_FILTER" for the filter value.\ 35 """ 36 DEFAULT_SCHEMA_PROMPT = PromptTemplate.from_template(DEFAULT_SCHEMA)
Agora, definiremos alguns exemplos que usaremos em nosso prompt. Os exemplos ajudarão o LLM a gerar melhores resultados. Precisamos definir metadados para nosso exemplo, uma query de usuário e uma resposta esperada.
Vamos definir três fontes de dados:
- Uma fonte de dados de músicas que tem uma descrição de conteúdo e definição de atributos.
1 SONG_DATA_SOURCE = """\ 2 ```json 3 {{ 4 "content": "Lyrics of a song", 5 "attributes": {{ 6 "artist": {{ 7 "type": "string", 8 "description": "Name of the song artist" 9 }}, 10 "length": {{ 11 "type": "integer", 12 "description": "Length of the song in seconds" 13 }}, 14 "genre": {{ 15 "type": "[string]", 16 "description": "The song genre, one or many of [\"pop\", \"rock\" or \"rap\"]" 17 }}, 18 "release_dt": {{ 19 "type": "string", 20 "description": "Release date of the song." 21 }} 22 }} 23 }} 24 ```\ 25 """
- Uma fonte de dados de filmes. Isso é semelhante aos nossos dados de amostra que estamos tentando resolver. Adicioná-lo aos exemplos de algumas gravações pode melhorar nossos resultados.
1 MOVIES_DATA_SOURCE = """\ 2 ```json 3 {{ 4 "content": "Brief summary of a movie", 5 "attributes": {{ 6 "release_date": {{ 7 "type": "string", 8 "description": "The release date of the movie" 9 }}, 10 "genre": {{ 11 "type": "[string]", 12 "description": "Keywords for filtering: ['anime', 'action', 'comedy', 'romance', 'thriller']" 13 }} 14 }} 15 }} 16 ```\ 17 """
- Uma fonte de dados de palavra-chave genérica. O LLM estava com problemas para gerar o formato de query correto para a filtragem de palavras-chave/array, então adicionamos isso para melhorar nossos resultados.
1 KEYWORDS_DATA_SOURCE = """\ 2 ```json 3 {{ 4 "content": "Documents store", 5 "attributes": {{ 6 "tags": {{ 7 "type": "[string]", 8 "description": "Keywords for filtering: ['rag', 'genai', 'gpt', 'langchain', 'llamaindex']" 9 }} 10 }} 11 }} 12 ````\ 13 """
Observação: adicione alguns exemplos de acordo com seu caso de uso para melhorar os resultados.
Agora, vamos definir alguns exemplo de queries de usuários e respostas esperadas:
1 KEYWORDS_DATA_SOURCE_ANSWER = """\ 2 ```json 3 {{ 4 "query": "Give me updates", 5 "filter": "in(\\"tags\\", [\\"rag\\", \\"langchain\\"])" 6 }} 7 ````\ 8 """ 9 10 KEYWORDS_DATE_DATA_SOURCE_ANSWER = """\ 11 ```json 12 {{ 13 "query": "Tell me updates on connectors based on the latest documentation", 14 "filter": "in(\\"tags\\", [\\"langchain\\", \\"llamaindex\\"])" 15 }} 16 ````\ 17 """ 18 19 FULL_ANSWER = """\ 20 ```json 21 {{ 22 "query": "songs about teenage romance", 23 "filter": "and(or(eq(\\"artist\\", \\"Taylor Swift\\"), eq(\\"artist\\", \\"Katy Perry\\")), lt(\\"length\\", 180), in(\\"genre\\", [\\"pop\\"]), and(gt(\\"release_dt\\", \\"2010-12-31\\"), lt(\\"release_dt\\", \\"2020-01-01\\")))" 24 }} 25 ```\ 26 """ 27 28 DATE_ANSWER = """\ 29 ```json 30 {{ 31 "query": "Recommend a movie with latest release date", 32 "filter": "and(lt(\\"release_date\\", \\"2010-01-01\\"), in(\\"genre\\", [\\"action\\", \\"thriller\\"])" 33 }} 34 ```\ 35 """ 36 37 NO_FILTER_ANSWER = """\ 38 ```json 39 {{ 40 "query": "", 41 "filter": "NO_FILTER" 42 }} 43 ```\ 44 """
Reunindo o que foi descrito acima, podemos definir nossos exemplos com a definição da fonte de dados , a query do usuário e a resposta esperada:
1 DEFAULT_EXAMPLES = [ 2 { 3 "i": 1, 4 "data_source": MOVIES_DATA_SOURCE, 5 "user_query": "Recommend an action or thriller genre movie release before 2010 and latest release date", 6 "structured_request": DATE_ANSWER, 7 }, 8 { 9 "i": 2, 10 "data_source": MOVIES_DATA_SOURCE, 11 "user_query": "Recommend a latest movie", 12 "structured_request": NO_FILTER_ANSWER 13 }, 14 { 15 "i": 3, 16 "data_source": SONG_DATA_SOURCE, 17 "user_query": "What are songs by Taylor Swift or Katy Perry about teenage romance under 3 minutes long in the dance pop genre released before 1 January 2020 and after 31 December, 2010", 18 "structured_request": FULL_ANSWER, 19 }, 20 { 21 "i": 4, 22 "data_source": SONG_DATA_SOURCE, 23 "user_query": "What are songs that were not published on Spotify", 24 "structured_request": NO_FILTER_ANSWER, 25 }, 26 { 27 "i": 5, 28 "data_source": KEYWORDS_DATA_SOURCE, 29 "user_query": "Give me updates on rag with langchain", 30 "structured_request": KEYWORDS_DATA_SOURCE_ANSWER 31 }, 32 { 33 "i": 6, 34 "data_source": KEYWORDS_DATA_SOURCE, 35 "user_query": "Tell me updates on langchain and llamaindex connectors based on the latest documentation", 36 "structured_request": KEYWORDS_DATE_DATA_SOURCE_ANSWER 37 } 38 ]
Vamos definir uma função de utilidade que usaremos para processar nossos filtros antes de retornar.
1 def enforce_constraints(input_json): 2 def process_value(value): 3 if isinstance(value, (str, int)): 4 return value 5 elif isinstance(value, list) and all(isinstance(item, str) for item in value): 6 return value 7 elif isinstance(value, dict) and 'date' in value and isinstance(value['date'], str): 8 return value['date'] 9 else: 10 raise ValueError("Invalid value type") 11 12 def process_dict(d): 13 if not isinstance(d, dict): 14 return d 15 processed_dict = {} 16 for k, v in d.items(): 17 if k.startswith("$") and isinstance(v, list): 18 # Handling $and and $or conditions 19 processed_dict[k] = [process_dict(item) for item in v] 20 elif k.startswith("$"): 21 processed_dict[k] = process_value(v) 22 else: 23 processed_dict[k] = process_dict(v) 24 return processed_dict 25 26 return process_dict(input_json)
Agora, Go em frente e definir nossa função
generate_metadata_filter
que usaremos para gerar nossos filtros de metadados.1 from langchain.chains.query_constructor.base import load_query_constructor_runnable 2 from langchain_community.query_constructors.mongodb_atlas import MongoDBAtlasTranslator 3 4 translator = MongoDBAtlasTranslator() 5 allowed_operators = translator.allowed_operators 6 allowed_comparators = translator.allowed_comparators 7 8 def generate_metadata_filter(query, llm, document_content_description, metadata_field_info, schema_prompt, examples): 9 """ 10 This method will use the query constructor and generate the pre-filters for a list of datasets. 11 :param query: query submitted by the user 12 :param llm: llm instance 13 :param document_content_description: Data description 14 :param metadata_field_info: metadata fields information 15 :param schema_prompt: prompt instructions that will be passed to the llm 16 :param examples: list of examples 17 :return (dict): Returns pre-filter and new query for each dataset. 18 """ 19 query_constructor = load_query_constructor_runnable( 20 llm=llm, 21 document_contents=document_content_description, 22 attribute_info=metadata_field_info, 23 schema_prompt=schema_prompt, 24 examples=examples, 25 allowed_comparators=allowed_comparators, 26 allowed_operators=allowed_operators 27 ) 28 query = f"""Answer the below question:\n 29 Question: {query} 30 """ 31 structured_query = query_constructor.invoke(query) 32 new_query, new_kwargs = translator.visit_structured_query(structured_query) 33 pre_filter = enforce_constraints(new_kwargs) 34 pre_filter = pre_filter = pre_filter.get("pre_filter", {}) if pre_filter else {} 35 return pre_filter, new_query
Vamos definir nosso objeto LLM para geração de texto. Usaremos o chatOpenAI do LangChain para este propósito. Usaremos o modelo
gpt-4o
para nossa geração de filtro.1 from langchain_openai import ChatOpenAI 2 3 llm = ChatOpenAI(openai_api_key=openai_api_key, 4 openai_api_base=openai_api_base, 5 default_headers=default_headers, 6 model="gpt-4o")
Agora que temos tudo o que precisamos para gerar o filtro de metadados, vamos tentar.
1 query = "I want to watch a movie released before year 2000 in the animation genre with the latest release date" 2 3 pre_filter, new_query = generate_metadata_filter( 4 query=query, 5 llm=llm, 6 document_content_description=document_content_description, 7 metadata_field_info=metadata_field_info, 8 schema_prompt=DEFAULT_SCHEMA_PROMPT, 9 examples=DEFAULT_EXAMPLES) 10 11 print(pre_filter) 12 print(new_query) 13 14 vectorStore = MongoDBAtlasVectorSearch( collection, embeddings ) 15 16 docs = vectorStore.similarity_search(query=query, pre_filter=pre_filter) 17 for doc in docs: 18 print(doc.page_content)
Saída:
Filtro gerado e nova query:
Filtro gerado e nova query:
1 {'$and': [{'release_date': {'$lt': '2000-01-01'}}, {'genre': {'$in': ['animation']}}]} 2 I want to watch a movie with the latest release date
Como você pode ver no filtro gerado, conseguirmos extrair as informações da query do usuário e os metadados, como "release before year 2000 " e "animation genre, ", que podem ser usados para pré-filtrar os dados antes de executar o pesquisa semântica.
Observe que também estamos retornando uma nova query depois de remover os filtros que geramos. Isso será útil no próximo estágio da geração de filtros.
O/P docs:
1 Toys come alive and have a blast doing so 2 The toys embark on a rescue mission to save Woody after he is stolen by a toy collector.
Recebemos dois documentos na saída, o que é um resultado melhor do que antes. Mas ainda não podemos obter o filme "latest release date ".
E, para encontrar o filme mais recente, precisaremos consultar nossos dados para passarmos para o Estágio 2, geração de filtros.
O objetivo deste estágio é gerar os filtros que podem ser usados para encontrar o
movies with the latest release date
.Observação: usaremos o filtro gerado no estágio 1 neste estágio porque queremos encontrar o
movie with the latest release date
em movies released before 2000 and in the animation category
.Vamos precisar consultar nossa collection do MongoDB via LLM. Para esse fim, definiremos algumas ferramentas que o LLM pode usar para consultar nossos dados.
Vamos definir as ferramentas para permitir que o LLM consulte nossa coleção MongoDB .
1 import json 2 import logging 3 import os 4 import traceback 5 from typing import Dict, Optional, Type, Union, List 6 7 from pymongo import MongoClient 8 from langchain_core.callbacks import CallbackManagerForToolRun 9 from langchain_core.pydantic_v1 import BaseModel, Field 10 from langchain_core.tools import BaseTool 11 12 class MongoDBClient: 13 """Data helper for querying MongoDB Vector Indexes.""" 14 15 def __init__(self, collection): 16 self.collection = collection 17 18 def run_aggregate_pipeline(self, pipeline: List[Dict]) -> List[Dict]: 19 documents = list(self.collection.aggregate(pipeline)) 20 return documents 21 22 class BaseMongoDBTool(BaseModel): 23 """Base tool for interacting with MongoDB.""" 24 25 client: MongoDBClient = Field(exclude=True) 26 match_filter: dict = Field(exclude=True) 27 28 class Config(BaseTool.Config): 29 pass 30 31 class _QueryExecutorMongoDBToolInput(BaseModel): 32 pipeline: str = Field(..., description="A valid MongoDB pipeline in JSON string format") 33 34 class QueryExecutorMongoDBTool(BaseMongoDBTool, BaseTool): 35 name: str = "mongo_db_executor" 36 description: str = """ 37 Input to this tool is a mongodb pipeline, output is a list of documents. 38 If the pipeline is not correct, an error message will be returned. 39 If an error is returned, report back to the user the issue and stop. 40 """ 41 args_schema: Type[BaseModel] = _QueryExecutorMongoDBToolInput 42 43 def _run( 44 self, 45 pipeline: str, 46 run_manager: Optional[CallbackManagerForToolRun] = None, 47 ) -> Union[List[Dict], str]: 48 """Get the result for the mongodb pipeline.""" 49 try: 50 pipeline = json.loads(pipeline) 51 if self.match_filter: 52 pipeline = [{"$match": self.match_filter}] + pipeline 53 print(f"Updated pipeline: {pipeline}/") 54 documents = self.client.run_aggregate_pipeline(pipeline) 55 return documents 56 except Exception as e: 57 """Format the error message""" 58 return f"Error: {e}\n{traceback.format_exc()}"
No segundo estágio, estamos contabilizando apenas os casos de uso em que o usuário deseja tipos de queries do tipo "mais recente", "recent", "first" ou "last". Instruiremos nosso LLM a gerar apenas um pipeline de agregação para gerar filtros para esses tipos de queries.
1 SYSTEM_PROMPT_TEMPLATE = """ 2 Your goal is to structure the user's query to match the request schema provided below. 3 4 << Structured Request Schema >> 5 When responding use a markdown code snippet with a JSON object formatted in the following schema: 6 7 ```json 8 {{{{ 9 "query": string \\ rewritten user's query after removing the information handled by the filter 10 "filter": string \\ logical condition statement for filtering documents 11 }}}}
A string de query deve ser reescrita. Quaisquer condições no filtro também não devem ser mencionadas na query.
Uma declaração de condição lógica é composta por uma ou mais declarações de comparação e operação lógica.
Uma declaração de comparação assume o formato:
comp(attr, val)
:comp
('eq | ne | gt | gte | lt | lte | in | nin'): comparatorattr
(string): nome do atributo para aplicar a comparaçãoval
(string): é o valor de comparação
Uma declaração de operação lógica assume o formato
op(statement1, statement2, ...)
:op
('e | ou'): operador lógicostatement1
,statement2
, ... (declarações de comparação ou declarações de operação lógica): uma ou mais declarações para aplicar a operação a
O primeiro passo é pensar se a pergunta do usuário menciona algo sobre data ou hora relacionado a que exija uma pesquisa no banco de banco de dados MongoDB . Palavras como "mais recente", "recém", "mais antigo", "primeiro", "último" etc. na query significam que uma pesquisa pode ser necessária. Se nenhuma pesquisa for necessária, retorne "NO_FILTER" para o valor do filtro.
Se necessário, crie um pipeline de agregação do MongoDB sintaxe correta usando o operador '$sort' e '$limit' para ser executado. Use a projeção para buscar somente as colunas de datas relevantes. Em seguida, observe os resultados do pipeline de agregação e gere uma consulta de intervalo de datas que pode ser usada para filtrar documentos relevantes da coleção.
Certifique-se de gerar somente filtros baseados em data. Certifique-se de gerar a consulta somente se um usuário fizer uma pergunta baseada em tempo, como mais recente, mais recente, e não mencione uma data e hora específicas. Certifique-se de usar apenas os comparadores e operadores lógicos listados acima e nenhum outro. Certifique-se de que os filtros se refiram apenas a atributos de data/hora que existem na fonte de dados. Certifique-se de que os filtros usem somente os nomes atribuídos com seus nomes de função se houver funções aplicadas neles. Certifique-se de que os filtros usem o formato
YYYY-MM-DD
somente ao lidar com valores digitados de dados de data. Certifique-se de que os filtros levem em conta as descrições dos atributos e só façam comparações que sejam viáveis dado o tipo de dados que está sendo armazenado. Certifique-se de que os filtros sejam usados somente quando necessário. Se não houver filtros que devam ser aplicados, retorne "NO_FILTER" para o valor do filtro. Certifique-se de que os nomes das colunas na query do filtro estejam entre aspas double .<< Data Source >>
1 {{{{ 2 "content": {content_description}, 3 "attributes": {attribute_info} 4 }}}}
"""
1 #### 2 3 Now, let's go ahead and define our `generate_time_based_filter` function that we will be using to generate our time-based filters. 4 5 We have handled the cases where only one stage filter generation is required or no filter generation is required. 6 7 ```python 8 from typing import Tuple 9 10 from langchain.agents import create_tool_calling_agent, AgentExecutor 11 from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder, PromptTemplate, HumanMessagePromptTemplate, \ 12 SystemMessagePromptTemplate 13 from pymongo.collection import Collection 14 from langchain.chains.query_constructor.base import AttributeInfo, _format_attribute_info 15 16 17 from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder, PromptTemplate, HumanMessagePromptTemplate, \ 18 SystemMessagePromptTemplate 19 from pymongo.collection import Collection 20 from langchain.chains.query_constructor.base import AttributeInfo 21 22 23 def generate_time_based_filter(llm: ChatOpenAI, collection: Collection, pre_filter: Dict, query: str, document_content_description: str, metadata_field_info: List[AttributeInfo]) -> Tuple[Dict, str]: 24 """ 25 This function is responsible for generating filter query for "most recent", "latest", "earliest" type of user 26 questions. 27 :param llm: (ChatOpenAI) llm instance 28 :param collection: pymongo collection instance 29 :param pre_filter: (Dict) metadata pre-filter query 30 :param query: (str) user query 31 :param document_content_description: (str) description of data 32 :param metadata_field_info: (List[AttributeInfo]) list of metadata attributes information 33 :return: (Tuple[Dict, str]) time-based filter query and re-written user query 34 """ 35 client = MongoDBClient(collection=collection) 36 executor_tool = QueryExecutorMongoDBTool(client=client, match_filter=pre_filter) 37 tools = [executor_tool] 38 attribute_str = _format_attribute_info(metadata_field_info) 39 system_prompt_template = SYSTEM_PROMPT_TEMPLATE.format(attribute_info=attribute_str, 40 content_description=document_content_description) 41 42 prompt = ChatPromptTemplate(input_variables=["agent_scratchpad", "input"], 43 messages=[SystemMessagePromptTemplate( 44 prompt=PromptTemplate(input_variables=[], template=system_prompt_template)), 45 MessagesPlaceholder(variable_name="chat_history", optional=True), 46 HumanMessagePromptTemplate( 47 prompt=PromptTemplate(input_variables=["input"], 48 template="{input}")), 49 MessagesPlaceholder(variable_name="agent_scratchpad")]) 50 51 agent = create_tool_calling_agent(llm, tools, prompt) 52 agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) 53 structured_query = agent_executor.invoke({"input": query}) 54 allowed_attributes = [] 55 for ainfo in metadata_field_info: 56 allowed_attributes.append( 57 ainfo.name if isinstance(ainfo, AttributeInfo) else ainfo["name"] 58 ) 59 60 output_parser = StructuredQueryOutputParser.from_components( 61 allowed_comparators=translator.allowed_comparators, 62 allowed_operators=translator.allowed_operators, 63 allowed_attributes=allowed_attributes 64 ) 65 structured_query = output_parser.parse(structured_query["output"]) 66 new_query, new_kwargs = translator.visit_structured_query(structured_query) 67 time_based_pre_filter = enforce_constraints(new_kwargs) 68 time_based_pre_filter = time_based_pre_filter.get('pre_filter', {}) if time_based_pre_filter else {} 69 return time_based_pre_filter, new_query 70 ``` 71 72 #### Metadata and time-based filter generation 73 74 Let’s run with both the filters now and check the result. 75 76 ```python 77 query = "I want to watch a movie released before year 2000 in the animation genre with the latest release date" 78 79 pre_filter, new_query = generate_metadata_filter( 80 query=query, 81 llm=llm, 82 document_content_description=document_content_description, 83 metadata_field_info=metadata_field_info, 84 schema_prompt=DEFAULT_SCHEMA_PROMPT, 85 examples=DEFAULT_EXAMPLES) 86 87 print(pre_filter) 88 print(new_query) 89 90 time_based_pre_filter, final_query = generate_time_based_filter( 91 llm=llm, 92 collection=collection, 93 pre_filter=pre_filter, 94 query=new_query, 95 document_content_description=document_content_description, 96 metadata_field_info=metadata_field_info 97 ) 98 print(time_based_pre_filter) 99 print(final_query) 100 ``` 101 102 Output: 103 104 ```python 105 {'$and': [{'release_date': {'$lt': '2000-01-01'}}, {'genre': {'$in': ['animation']}}]} 106 I want to watch a movie with the latest release date 107 108 {'release_date': {'$eq': '1999-11-24'}} 109 I want to watch a movie 110 ``` 111 112 Now that we have generated both stages’ filters, we can combine them using the `$and` operator to generate our final filter. 113 114 ```python 115 # initialize final filter with stage 1 filter 116 final_pre_filter = pre_filter 117 if time_based_pre_filter: 118 # add time_based_filter if applicable 119 final_pre_filter = {"$and": [pre_filter, time_based_pre_filter]} 120 121 print(final_pre_filter) 122 ``` 123 124 Output: 125 126 ```python 127 {'$and': [{'$and': [{'release_date': {'$lt': '2000-01-01'}}, {'genre': {'$in': ['animation']}}]}, {'release_date': {'$eq': '1999-11-24'}}]} 128 ``` 129 130 Now, let’s run a semantic search using the `final_pre_filter`. 131 132 ```python 133 vectorStore = MongoDBAtlasVectorSearch( collection, embeddings ) 134 135 docs = vectorStore.similarity_search(query=query, pre_filter=final_pre_filter) 136 for doc in docs: 137 print(doc.page_content) 138 ``` 139 140 Output: 141 142 ```bash 143 The toys embark on a rescue mission to save Woody after he is stolen by a toy collector. 144 ``` 145 146 With smart filtering, we used both metadata and time-based filtering stages and were able to generate a filter that can be used to pre-filter the data before running a semantic search. We have received only the required documents in the end. 147 148 ## Benefits of smart filtering 149 150 Smart filtering brings a host of advantages to the table, making it a valuable tool for enhancing search experiences: 151 152 * **Improved search accuracy:** By precisely targeting the data that matches your query, smart filtering dramatically increases the likelihood of finding relevant results. No more wading through irrelevant information. 153 * **Faster search results:** Since smart filtering narrows down the search scope, the system can process information more efficiently, leading to quicker results. 154 * **Enhanced user experience:** When users find what they're looking for quickly and easily, it leads to higher satisfaction and a better overall experience. 155 * **Versatility:** Smart filtering can be applied to various domains, from e-commerce product searches to content recommendations, making it a versatile tool. 156 157 By leveraging metadata and creating targeted pre-filters, smart filtering empowers you to deliver search results that truly meet user expectations. 158 159 ## Conclusion 160 161 Smart filtering is a powerful tool that transforms search experiences by bridging the gap between user intent and search results. By harnessing the power of metadata and vector search, it delivers more accurate, relevant, and efficient search outcomes. 162 163 Whether you're building an e-commerce platform, a content recommendation system, or any application that relies on effective search, incorporating smart filtering can significantly enhance user satisfaction and drive better results. 164 165 By understanding the fundamentals of smart filtering, you're equipped to explore its potential and implement it in your projects. So why wait? Start leveraging the power of smart filtering today and revolutionize your search game\! 166 167 View the [full source code](https://github.com/bhardwaj-vipul/SmartFilteringRAG) for smart filtering using MongoDB Atlas. 168 169 Check out additional resources: [Unlock the Power of Semantic Search With MongoDB Atlas Vector Search](https://www.mongodb.com/basics/semantic-search) and [Interactive RAG With MongoDB Atlas \+ Function Calling API](https://www.mongodb.com/developer/products/atlas/interactive-rag-mongodb-atlas-function-calling-api/). If you have any questions or want to show us what you are building, join us in the [MongoDB Community Forums](https://www.mongodb.com/community/forums/). 170 171 172 [1]: https://images.contentstack.io/v3/assets/blt39790b633ee0d5a7/blt07d47760e8c04d15/66d6dd2326adee3c5af681ed/image2.png 173 [2]: https://images.contentstack.io/v3/assets/blt39790b633ee0d5a7/blt6ce1fa4c1376e507/66d6dd233bf41e1189a41469/image1.png
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.