Construindo um serviço de pesquisa semântica do Atlas com Spring AI e MongoDB Atlas
Avalie esse Tutorial
Qual é a faixa que diz "Duh da, duh da, DUH da duh"? Todos nós já passamos por isso antes. Lembremo-nos de um trecho do coro, sabemos que tem algo a ver com um hotel em Chelsea, mas que letra é essa? Não me recordo do título — como faz o Atlas Search por energia?! Bem, com o poder da AI, podemos fazer o Atlas Search em nossos bancos de dados, não apenas combinando palavras, mas pesquisando o significado semântica do texto. E com o Spring AI, você pode incorporar o Atlas Search com AIem seu aplicativo Spring. Com apenas a vaga memória de uma modelo que prefere um modelo bonito, podemos localizar nosso clássico do diretor.
Spring AI é uma estrutura de aplicativo da Spring que permite combinar vários AI serviços e plug-ins de com seus aplicativos. Com suporte para muitos modelos de chat, texto para imagem e incorporação, você pode configurar AIseu Java aplicativo habilitado para configurado para uma variedade de AI casos de uso de .
Com o Spring AI, oMongoDB Atlas é suportado como um banco de dados vetorial, todos com o Atlas Vector Search para alimentar sua Atlas Search semântica e implementar seus RAG aplicativos . Para saber mais sobre RAG e outros conceitos-chave em AI, consulte os de MongoDB AI integração de Docs do .
Neste tutorial, Go o que você precisa para começar a usar o Spring AI e o MongoDB, adicionando documentos ao seu banco de dados com o conteúdo vetorizado (embeddings) e pesquisando esse conteúdo com o semântica Atlas Search. O código completo deste tutorial está disponível no repositório doGithub .
Antes de iniciar este tutorial, você precisará ter o seguinte:
- Uma conta MongoDB Atlas e um cluster M10+ executando MongoDB versão 6.0.11, 7.0.2 ou posterior
- Um cluster M10+ é necessário para criar o índice programaticamente (por Spring AI).
- Uma chave de API OpenAI com uma conta OpenAI paga e créditos disponíveis
- Java 21 e um IDE como IntelliJ IDEA ou Eclipse
- Maven 3.9.6+ configurado para o seu projeto
- Projeto: Maven
- Linguagem: Java
- Spring Boot: versão padrão
- Java: 21
Adicione as seguintes dependências:
- Banco de dados vetorial do MongoDB Atlas
- Spring Web
- OpenAI (outros modelos de incorporação estão disponíveis, mas usamos isso para o tutorial)
Gere e baixe seu projeto e abra-o em seu IDE.
Abra o aplicativo no IDE de sua escolha e a primeira coisa que vamos fazer é inspecionar nosso
pom.xml
. Para usar a versão mais recente do Spring AI, altere a versãospring-ai.version
do BOM do Spring AI para 1.0.0-SNAPSHOT
. No momento em que escrevemos este artigo, ele será 1.0.0-M1
por padrão.Configure seu aplicativo Spring para configurar o armazenamento de vetores e outros feições necessárias.
Nas propriedades do aplicativo, vamos configurar nosso banco de MongoDB database, bem como tudo o que precisamos para pesquisar semanticamente nossos dados. Também adicionaremos informações como nosso modelo de incorporação OpenAI e a chave de API.
1 spring.application.name=lyric-semantic-search 2 spring.ai.openai.api-key=<Your-API-key> 3 spring.ai.openai.embedding.options.model=text-embedding-ada-002 4 5 spring.data.mongodb.uri=<Your-MongoDB-connection-string> 6 spring.data.mongodb.database=lyrics 7 spring.ai.vectorstore.mongodb.indexName=vector_index 8 spring.ai.vectorstore.mongodb.collection-name=vector_store 9 spring.ai.vectorstore.mongodb.initialize-schema=true
Você verá no final que estamos definindo o esquema inicializado para ser
true
. Isso significa que nosso aplicativo configurará nosso índice do Atlas Search (se ele não existir) para que possamos pesquisar semanticamente nossos dados. Se você já tiver um índice do Atlas Search configurado com este nome e configuração, você poderá defini-lo como false
.No seu IDE, abra seu projeto. Crie um arquivo
Config.java
em um pacoteconfig
. Aqui, vamos configurar nosso modelo de incorporação OpenAI. A AI do Spring torna este um processo muito simples.1 package com.mongodb.lyric_semantic_search.config; 2 3 import org.springframework.ai.embedding.EmbeddingModel; 4 import org.springframework.ai.openai.OpenAiEmbeddingModel; 5 import org.springframework.ai.openai.api.OpenAiApi; 6 import org.springframework.beans.factory.annotation.Value; 7 import org.springframework.boot.SpringBootConfiguration; 8 import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 9 import org.springframework.context.annotation.Bean; 10 import org.springframework.context.annotation.Configuration; 11 12 13 14 15 16 public class Config { 17 18 19 private String openAiKey; 20 21 22 public EmbeddingModel embeddingModel() { 23 return new OpenAiEmbeddingModel(new OpenAiApi(openAiKey)); 24 } 25 }
Agora podemos enviar nossos dados para serem vetorizados e receber os resultados vetorizados.
Crie um pacote chamado
model
, para nossa classeDocumentRequest
entrar. É isso que Go armazenar em nosso banco de MongoDB database. O conteúdo será o que estamos incorporando - letras, no nosso caso. Os metadados serão qualquer coisa que quisermos armazenar junto com eles, sejam atores, discos ou gêneros. Esses metadados serão retornados junto com nosso conteúdo e também podem ser usados para filtrar nossos resultados.1 package com.mongodb.lyric_semantic_search.model; 2 3 import java.util.Map; 4 5 public class DocumentRequest { 6 private String content; 7 private Map<String, Object> metadata; 8 9 public DocumentRequest() { 10 } 11 12 public DocumentRequest(String content, Map<String, Object> metadata) { 13 this.content = content; 14 this.metadata = metadata; 15 } 16 17 public String getContent() { 18 return content; 19 } 20 21 public void setContent(String content) { 22 this.content = content; 23 } 24 25 public Map<String, Object> getMetadata() { 26 return metadata; 27 } 28 29 public void setMetadata(Map<String, Object> metadata) { 30 this.metadata = metadata; 31 } 32 33 }
Crie um pacote
repository
e adicione uma interfaceLyricSearchRepository
. Aqui, definiremos alguns dos métodos que implementaremos mais tarde.1 package com.mongodb.lyric_semantic_search.repository; 2 3 import java.util.List; 4 import java.util.Optional; 5 6 import org.springframework.ai.document.Document; 7 import org.springframework.ai.vectorstore.SearchRequest; 8 9 public interface LyricSearchRepository { 10 11 void addDocuments(List<Document> docs); 12 13 Optional<Boolean> deleteDocuments(List<String> ids); 14 15 List<Document> semanticSearchByLyrics(SearchRequest searchRequest); 16 }
Crie uma classe
LyricSearchRepositoryImpl
para implementar a interface do repositório.1 package com.mongodb.lyric_semantic_search.repository; 2 3 import java.util.List; 4 import java.util.Optional; 5 6 import org.springframework.ai.document.Document; 7 import org.springframework.ai.vectorstore.SearchRequest; 8 import org.springframework.ai.vectorstore.VectorStore; 9 import org.springframework.beans.factory.annotation.Autowired; 10 import org.springframework.stereotype.Repository; 11 12 13 public class LyricSearchRepositoryImpl implements LyricSearchRepository { 14 15 private final VectorStore vectorStore; 16 17 18 public LyricSearchRepositoryImpl(VectorStore vectorStore) { 19 this.vectorStore = vectorStore; 20 } 21 22 23 public void addDocuments(List<Document> docs) { 24 vectorStore.add(docs); 25 } 26 27 28 public Optional<Boolean> deleteDocuments(List<String> ids) { 29 return vectorStore.delete(ids); 30 } 31 32 33 public List<Document> semanticSearchByLyrics(SearchRequest searchRequest) { 34 return vectorStore.similaritySearch(searchRequest); 35 } 36 }
Estamos usando os métodos
add
, delete
e similaritySearch
, todos já definidos e implementados no Spring AI. Isso nos permitirá incorporar nossos dados ao adicioná-los ao banco de MongoDB database, e podemos pesquisar essas incorporações com o Atlas Search vetorial.Crie um pacote
service
e, dentro dele, uma classeLyricSearchService
para lidar com a lógica de negócios de nosso aplicativo lírico do Atlas Search. Implementaremos esses métodos mais tarde no tutorial:1 package com.mongodb.lyric_semantic_search.service; 2 3 import org.springframework.stereotype.Service; 4 5 6 public class LyricSearchService { 7 8 }
Crie um pacote de controlador e uma classe
LyricSearchController
para lidar com solicitações HTTP . Vamos adicionar uma chamada para adicionar nossos dados, uma chamada para excluir quaisquer documentos de que não precisamos mais e uma chamada do Atlas Search, para semanticamente o Atlas Search nossos dados.Eles chamarão de volta os métodos que definimos anteriormente. Vamos implementá-los nas próximas etapas:
1 package com.mongodb.lyric_semantic_search.controller; 2 3 import org.springframework.web.bind.annotation.RestController; 4 5 6 public class LyricSearchController { 7 8 }
Em nossa classe
LyricSearchService
, vamos adicionar um pouco de lógica para incluir nossos documentos e adicioná-los ao banco de MongoDB database.1 private static final int MAX_TOKENS = (int) (8192 * 0.80); // OpenAI model's maximum content length + BUFFER for when one word > 1 token 2 3 4 LyricSearchRepository lyricSearchRepository; 5 6 public List<Document> addDocuments(List<DocumentRequest> documents) { 7 if (documents == null || documents.isEmpty()) { 8 return Collections.emptyList(); 9 } 10 11 List<Document> docs = documents.stream() 12 .filter(doc -> doc != null && doc.getContent() != null && !doc.getContent() 13 .trim() 14 .isEmpty()) 15 .map(doc -> new Document(doc.getContent(), doc.getMetadata())) 16 .filter(doc -> { 17 int wordCount = doc.getContent() 18 .split("\\s+").length; 19 return wordCount <= MAX_TOKENS; 20 }) 21 .collect(Collectors.toList()); 22 23 if (!docs.isEmpty()) { 24 lyricSearchRepository.addDocuments(docs); 25 } 26 27 return docs; 28 }
Esta função recebe um único parâmetro,
documents
, que é uma lista de DocumentRequest
objetos. Eles representam os documentos que precisam ser processados e adicionados ao repositório.A função primeiro verifica se a lista
documents
é nula ou vazia.A lista
documents
é convertida em um fluxo para facilitar as operações de estilo funcional.O filtro é um pouco de pré-processamento para ajudar a limpar nossos dados. Ele remove quaisquer objetos
DocumentRequest
que sejam nulos, tenham conteúdo nulo ou tenham conteúdo vazio (ou somente espaços em branco). Isso garante que somente documentos válidos sejam processados posteriormente.Conheça seus limites! O filtro remove quaisquer objetos
Document
cujo conteúdo exceda o limite máximo de token (MAX_TOKENS
) para a API OpenAI. O limite de token é estimado com base na contagem de palavras, supondo que uma palavra seja um pouco mais de um token (não muito fora da verdade). Essa estimativa funciona para a demonstração, mas, para a produção, provavelmente queremos implementar uma forma de agrupamento, em que grandes corpos de texto são separados em pedaços menores e mais digeríveis.Cada objeto
DocumentRequest
é transformado em um objetoDocument
. O construtorDocument
é chamado com o conteúdo e os metadados de DocumentRequest
.Os objetos
Document
filtrados e transformados são coletados em uma lista e esses documentos são adicionados ao nosso armazenamento de vetores do MongoDB, junto com uma incorporação das letras.Também adicionaremos nossa função para excluir documentos enquanto estivermos aqui:
1 public List<String> deleteDocuments(List<String> ids) { 2 if (ids == null || ids.isEmpty()) { 3 return Collections.emptyList(); // Nothing to delete 4 } 5 6 Optional<Boolean> result = lyricSearchRepository.deleteDocuments(ids); 7 if (result.isPresent() && result.get()) { 8 return ids; // Return the list of successfully deleted IDs 9 } else { 10 return Collections.emptyList(); // Return empty list if deletion was unsuccessful 11 } 12 }
E as importações apropriadas:
1 import java.util.Collections; 2 import java.util.List; 3 import java.util.Optional; 4 import java.util.stream.Collectors; 5 6 import org.springframework.ai.document.Document; 7 import org.springframework.beans.factory.annotation.Autowired; 8 9 import com.mongodb.lyric_semantic_search.model.DocumentRequest; 10 import com.mongodb.lyric_semantic_search.repository.LyricSearchRepository;
Agora que temos a lógica, vamos adicionar os endpoints ao nosso
LyricSearchController
.1 2 private LyricSearchService lyricSearchService; 3 4 5 public List<Map<String, Object>> addDocuments( List<DocumentRequest> documents) { 6 return lyricSearchService.addDocuments(documents).stream() 7 .map(doc -> Map.of("content", doc.getContent(), "metadata", doc.getMetadata())) 8 .collect(Collectors.toList()); 9 } 10 11 12 public List<String> deleteDocuments( List<String> ids) { 13 return lyricSearchService.deleteDocuments(ids); 14 }
E nossas importações:
1 import java.util.List; 2 import java.util.Map; 3 import java.util.stream.Collectors; 4 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.web.bind.annotation.DeleteMapping; 7 import org.springframework.web.bind.annotation.PostMapping; 8 import org.springframework.web.bind.annotation.RequestBody; 9 10 import com.mongodb.lyric_semantic_search.model.DocumentRequest; 11 import com.mongodb.lyric_semantic_search.service.LyricSearchService;
Para testar nossa incorporação, vamos mantê-la simples com algumas cantadas por enquanto.
Construa e execute seu aplicativo. Use o seguinte comando CURL para adicionar documentos de amostra:
1 curl -X POST "http://localhost:8080/addDocuments" \ 2 -H "Content-Type: application/json" \ 3 -d '[ 4 {"content": "Twinkle, twinkle, little star, How I wonder what you are! Up above the world so high, Like a diamond in the sky.", "metadata": {"title": "Twinkle Twinkle Little Star", "artist": "Jane Taylor", "year": "1806"}}, 5 {"content": "The itsy bitsy spider climbed up the waterspout. Down came the rain and washed the spider out. Out came the sun and dried up all the rain and the itsy bitsy spider climbed up the spout again.", "metadata": {"title": "Itsy Bitsy Spider", "artist": "Traditional", "year": "1910"}}, 6 {"content": "Humpty Dumpty sat on a wall, Humpty Dumpty had a great fall. All the kings horses and all the kings men couldnt put Humpty together again.", "metadata": {"title": "Humpty Dumpty", "artist": "Mother Goose", "year": "1797"}} 7 ]'
Vamos definir nosso método de pesquisa em nosso
LyricSearchService
. É assim que procuraremos semanticamente o Atlas Search nossos documentos em nosso banco de dados.1 public List<Map<String, Object>> searchDocuments(String query, int topK, double similarityThreshold) { 2 SearchRequest searchRequest = SearchRequest.query(query) 3 .withTopK(topK) 4 .withSimilarityThreshold(similarityThreshold); 5 6 List<Document> results = lyricSearchRepository.semanticSearchByLyrics(searchRequest); 7 8 return results.stream() 9 .map(doc -> Map.of("content", doc.getContent(), "metadata", doc.getMetadata())) 10 .collect(Collectors.toList()); 11 }
Este método aceita: -
query
: um String
representando a query do Atlas Search ou o texto para o qual você deseja encontrar letras semanticamente semelhantes - topK
: um int
especificando o número dos principais resultados a serem recuperados (ou seja, , top 10) - similarityThreshold
: um double
indicando a pontuação mínima de similaridade que um resultado deve ter para ser incluído nos resultadosIsso retorna uma lista de
Map<String, Object>
objetos. Cada mapa contém o conteúdo e metadados de um documento que corresponde aos critérios de Pesquisa do Atlas.E as importações para o nosso serviço:
1 import java.util.Map; 2 import org.springframework.ai.vectorstore.SearchRequest;
Vamos adicionar um endpoint ao nosso controlador, construir e executar nosso aplicativo.
1 2 public List<Map<String, Object>> searchDocuments( String query, int topK, double similarityThreshold 3 ) { 4 return lyricSearchService.searchDocuments(query, topK, similarityThreshold); 5 6 }
E as importações:
1 import org.springframework.web.bind.annotation.GetMapping; 2 import org.springframework.web.bind.annotation.RequestParam;
Use o seguinte comando CURL para o Atlas Search em suas bancos de dados letras sobre pequenos corpos celestiais:
1 curl -X GET "http://localhost:8080/search?query=small%20celestial%20bodie&topK=5&similarityThreshold=0.8"
E pronto! Temos nossa pequena estrela no topo da nossa lista.
1 [{ 2 "metadata":{ 3 "title":"Twinkle Twinkle Little Star", 4 "artist":"Jane Taylor", 5 "year":"1806" 6 }, 7 "content":"Twinkle, twinkle, little star,..." 8 }, 9 ...
Para filtrar nossos dados, precisamos ir ao nosso índice no MongoDB. Você pode fazer isso por meio da UI do Atlas , selecionando a collection onde seus dados estão armazenados e acessando os índices do Atlas Search. Você pode editar esse índice selecionando os três pontos à direita do nome do índice e adicionaremos nosso filtro para o artista.
1 { 2 "fields": [ 3 { 4 "numDimensions": 1536, 5 "path": "embedding", 6 "similarity": "cosine", 7 "type": "vector" 8 }, 9 { 10 "path": "metadata.artist", 11 "type": "filter" 12 } 13 ] 14 }
Vamos voltar ao nosso
LyricSearchService
e adicionar um método com um parâmetro de artista para que possamos filtrar nossos resultados.1 public List<Map<String, Object>> searchDocumentsWithFilter(String query, int topK, double similarityThreshold, String artist) { 2 FilterExpressionBuilder filterBuilder = new FilterExpressionBuilder(); 3 Expression filterExpression = filterBuilder.eq("artist", artist) 4 .build(); 5 6 SearchRequest searchRequest = SearchRequest.query(query) 7 .withTopK(topK) 8 .withSimilarityThreshold(similarityThreshold) 9 .withFilterExpression(filterExpression); 10 11 List<Document> results = lyricSearchRepository.semanticSearchByLyrics(searchRequest); 12 13 return results.stream() 14 .map(doc -> Map.of("content", doc.getContent(), "metadata", doc.getMetadata())) 15 .collect(Collectors.toList()); 16 }
E as importações de que precisaremos:
1 import org.springframework.ai.vectorstore.filter.Filter.Expression; 2 import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
E, por último, um endpoint em nosso controlador:
1 2 public List<Map<String, Object>> searchDocumentsWithFilter( String query, int topK, double similarityThreshold, String artist) { 3 return lyricSearchService.searchDocumentsWithFilter(query, topK, similarityThreshold, artist); 4 }
Agora, podemos não apenas Atlas Search como antes, mas podemos dizer que queremos restringi-la apenas a intérpretes específicos.
Use o seguinte comando CURL para tentar uma Atlas Search semântica com filtragem de metadados:
1 curl -X GET "http://localhost:8080/searchWithFilter?query=little%20star&topK=5&similarityThreshold=0.8&artist=Jane%20Taylor"
Ao contrário de antes, e mesmo solicitando os cinco principais resultados, só nos é devolvido um documento porque só temos um documento da artista Jane Swift. Viva!
1 [{ 2 "metadata":{ 3 "title":"Twinkle Twinkle Little Star", 4 "artist":"Jane Taylor", 5 "year":"1806" 6 }, 7 "content":"Twinkle, twinkle, little star,..." 8 }]
Agora você tem um aplicativo Spring que permite a você Atlas Search através de seus dados realizando pesquisas semânticas. Esta é uma etapa importante quando você deseja implementar seus aplicativos RAG , ou apenas um recurso de Pesquisa do Atlas aprimorado por IA em seus aplicativos.
Se você quiser saber mais sobre a MongoDB AI integração da do Spring do , acompanhe o início rápido da integração do Spring e,AI se tiver alguma dúvida ou quiser nos mostrar o que está construindo, junta-se a nós na MongoDB Community Fóruns.
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.
Relacionado
Tutorial
Armazenamento de mídia contínuo: integrando o armazenamento de Blobs do Azure e o MongoDB com o Spring Boot
Nov 05, 2024 | 9 min read
Tutorial
Desenvolvimento sem servidor com AWS Lambda e MongoDB Atlas usando Java
Jul 20, 2023 | 6 min read