Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Junte-se a nós no Amazon Web Services re:Invent 2024! Saiba como usar o MongoDB para casos de uso de AI .
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Idiomaschevron-right
Pythonchevron-right

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
IAPython
APLICATIVO COMPLETO
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
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.

O que é filtragem inteligente?

![ 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.

Como funciona a filtragem inteligente

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:
  1. 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)?
  2. 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
  3. 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.

Implementação

  • caderno
  • MongoDBAtlasVectorSearch a partir da estrutura do LangChain e GPT-4o para implementar filtragem inteligente.

Pré-requisitos

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 conta do MongoDB Atlas Conta do MongoDB Atlas.
  • Um cluster MongoDB Atlas. A camada grátis funcionará perfeitamente.
  • 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 dependências

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

Exemplo de configuração de dados

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, ratinge genre.
1from langchain_core.documents import Document
2
3# Sample movies data. The metadata has release_date, rating, genre, director
4docs = [
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]

Configuração do cliente MongoDB PyMongo

Vamos configurar MongoDB conexãoMongoDB que usaremos para inserir nossos dados.
1import getpass
2from pymongo import MongoClient
3
4# set up your MongoDB connection
5connection_string = getpass.getpass(prompt= "Enter connection string WITH USER + PASS here")
6
7client = MongoClient(connection_string)
8
9# name your database and collection anything you want since it will be created when you enter your data
10database_name = "smart_filtering"
11collection_name = "movies"
12collection = client[database_name][collection_name]

Configuração do objeto de incorporação do VectorDB

Para este tutorial, estamos usando o modelo de incorporaçãotext-embedding-ada-002do OpenAI.
1import json
2
3from langchain_openai import OpenAIEmbeddings
4
5# openAI API credentials
6openai_api_base = getpass.getpass(prompt= "Put in OpenAI URL here")
7openai_api_key = getpass.getpass(prompt= "Put in OpenAI API Key here")
8
9# default_headers is optional
10default_headers = getpass.getpass(prompt= "Put in OpenAI API headers if applicable here")
11default_headers = json.loads(default_headers) if default_headers else None
12
13embeddings = 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")

Inicialize a coleta do MongoDB

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.
1from langchain.vectorstores import MongoDBAtlasVectorSearch
2
3# This step will generate the embeddings and insert the documents
4vectorStore = 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, ratinge genre.

Criar índice de pesquisa

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 , selecione Json 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.

Consultar os 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.
1vectorStore = MongoDBAtlasVectorSearch( collection, embeddings )
2
3query = "I want to watch a movie released before year 2000 in the animation genre with the latest release date"
4
5docs = vectorStore.similarity_search(query)
6for 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.
1A bunch of scientists bring back dinosaurs and mayhem breaks loose
2A psychologist / detective gets lost in a series of dreams within dreams within dreams and Inception reused the idea
3Toys come alive and have a blast doing so
4Leo 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.
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.

Estágio 1: filtro de metadados

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

Definição de metadados

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, descriptione type de cada campo. Isso requer algum conhecimento básico dos dados.
Descrição do conteúdo dos nossos dados:
1document_content_description = "Brief summary of a movie"
Atributos de metadados de nossos dados:
1from langchain.chains.query_constructor.base import AttributeInfo
2
3metadata_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]

Definir solicitação para filtragem de metadados

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:
  • Estamos usando oPromptTemplate para definir nosso prompt.
  • 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.
1from langchain_core.prompts import PromptTemplate
2
3
4DEFAULT_SCHEMA = """\
5<< Structured Request Schema >>
6When 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
15The query string should be re-written. Any conditions in the filter should not be mentioned in the query as well.
16
17A logical condition statement is composed of one or more comparison and logical operation statements.
18
19A 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
24A 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
28Make sure that you only use the comparators and logical operators listed above and no others.
29Make sure that filters only refer to attributes that exist in the data source.
30Make sure that filters only use the attributed names with its function names if there are functions applied on them.
31Make sure that filters only use format `YYYY-MM-DD` when handling date data typed values.
32Make 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.
33Make sure that filters take into account the descriptions of attributes and only make comparisons that are feasible given the type of data being stored.
34Make 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"""
36DEFAULT_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:
  1. Uma fonte de dados de músicas que tem uma descrição de conteúdo e definição de atributos.
1SONG_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"""
  1. 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.
1MOVIES_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"""
  1. 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.
1KEYWORDS_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:
1KEYWORDS_DATA_SOURCE_ANSWER = """\
2```json
3{{
4 "query": "Give me updates",
5 "filter": "in(\\"tags\\", [\\"rag\\", \\"langchain\\"])"
6}}
7````\
8"""
9
10KEYWORDS_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
19FULL_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
28DATE_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
37NO_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:
1DEFAULT_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.
1def 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çãogenerate_metadata_filterque usaremos para gerar nossos filtros de metadados.
1from langchain.chains.query_constructor.base import load_query_constructor_runnable
2from langchain_community.query_constructors.mongodb_atlas import MongoDBAtlasTranslator
3
4translator = MongoDBAtlasTranslator()
5allowed_operators = translator.allowed_operators
6allowed_comparators = translator.allowed_comparators
7
8def 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 modelogpt-4o para nossa geração de filtro.
1from langchain_openai import ChatOpenAI
2
3llm = 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.
1query = "I want to watch a movie released before year 2000 in the animation genre with the latest release date"
2
3pre_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
11print(pre_filter)
12print(new_query)
13
14vectorStore = MongoDBAtlasVectorSearch( collection, embeddings )
15
16docs = vectorStore.similarity_search(query=query, pre_filter=pre_filter)
17for doc in docs:
18 print(doc.page_content)
Saída:
Filtro gerado e nova query:
1{'$and': [{'release_date': {'$lt': '2000-01-01'}}, {'genre': {'$in': ['animation']}}]}
2I 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:
1Toys come alive and have a blast doing so
2The 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.

Estágio 2: filtragem baseada em tempo

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.

Ferramentas

Vamos definir as ferramentas para permitir que o LLM consulte nossa coleção MongoDB .
1import json
2import logging
3import os
4import traceback
5from typing import Dict, Optional, Type, Union, List
6
7from pymongo import MongoClient
8from langchain_core.callbacks import CallbackManagerForToolRun
9from langchain_core.pydantic_v1 import BaseModel, Field
10from langchain_core.tools import BaseTool
11
12class 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
22class 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
31class _QueryExecutorMongoDBToolInput(BaseModel):
32 pipeline: str = Field(..., description="A valid MongoDB pipeline in JSON string format")
33
34class 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()}"

Definição de prompt

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.
1SYSTEM_PROMPT_TEMPLATE = """
2Your goal is to structure the user's query to match the request schema provided below.
3
4<< Structured Request Schema >>
5When 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'): comparator
  • attr (string): nome do atributo para aplicar a comparação
  • val (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ógico
  • statement1, 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 formatoYYYY-MM-DDsomente 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
3Now, let's go ahead and define our `generate_time_based_filter` function that we will be using to generate our time-based filters.
4
5We have handled the cases where only one stage filter generation is required or no filter generation is required.
6
7```python
8from typing import Tuple
9
10from langchain.agents import create_tool_calling_agent, AgentExecutor
11from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder, PromptTemplate, HumanMessagePromptTemplate, \
12 SystemMessagePromptTemplate
13from pymongo.collection import Collection
14from langchain.chains.query_constructor.base import AttributeInfo, _format_attribute_info
15
16
17from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder, PromptTemplate, HumanMessagePromptTemplate, \
18 SystemMessagePromptTemplate
19from pymongo.collection import Collection
20from langchain.chains.query_constructor.base import AttributeInfo
21
22
23def 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
74Let’s run with both the filters now and check the result.
75
76```python
77query = "I want to watch a movie released before year 2000 in the animation genre with the latest release date"
78
79pre_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
87print(pre_filter)
88print(new_query)
89
90time_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)
98print(time_based_pre_filter)
99print(final_query)
100```
101
102Output:
103
104```python
105{'$and': [{'release_date': {'$lt': '2000-01-01'}}, {'genre': {'$in': ['animation']}}]}
106I want to watch a movie with the latest release date
107
108{'release_date': {'$eq': '1999-11-24'}}
109I want to watch a movie
110```
111
112Now 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
116final_pre_filter = pre_filter
117if time_based_pre_filter:
118 # add time_based_filter if applicable
119 final_pre_filter = {"$and": [pre_filter, time_based_pre_filter]}
120
121print(final_pre_filter)
122```
123
124Output:
125
126```python
127{'$and': [{'$and': [{'release_date': {'$lt': '2000-01-01'}}, {'genre': {'$in': ['animation']}}]}, {'release_date': {'$eq': '1999-11-24'}}]}
128```
129
130Now, let’s run a semantic search using the `final_pre_filter`.
131
132```python
133vectorStore = MongoDBAtlasVectorSearch( collection, embeddings )
134
135docs = vectorStore.similarity_search(query=query, pre_filter=final_pre_filter)
136for doc in docs:
137 print(doc.page_content)
138```
139
140Output:
141
142```bash
143The toys embark on a rescue mission to save Woody after he is stolen by a toy collector.
144```
145
146With 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
150Smart 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
157By leveraging metadata and creating targeted pre-filters, smart filtering empowers you to deliver search results that truly meet user expectations.
158
159## Conclusion
160
161Smart 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
163Whether 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
165By 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
167View the [full source code](https://github.com/bhardwaj-vipul/SmartFilteringRAG) for smart filtering using MongoDB Atlas.
168
169Check 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.
Iniciar a conversa

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Início rápido

Construindo aplicativos de AI e RAG com MongoDB, Anyscale e PyMongo


Jul 17, 2024 | 7 min read
Tutorial

​​Reinventando a pesquisa multimodal com MongoDB e Anyscale


Sep 18, 2024 | 20 min read
Tutorial

Como implementar fluxos de trabalho do Databricks e o Atlas Vector Search para melhorar a precisão da pesquisa de comércio eletrônico


Sep 18, 2024 | 6 min read
Artigo

Comparação de técnicas de NLP para Atlas Search de produtos escaláveis


Sep 23, 2024 | 8 min read
Sumário