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
Javachevron-right

Construindo um serviço de pesquisa semântica do Atlas com Spring AI e MongoDB Atlas

Tim Kelly9 min read • Published Sep 03, 2024 • Updated Oct 24, 2024
SpringIAJava
APLICATIVO COMPLETO
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
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 .

Pré-requisitos

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

Inicialização do Spring

Navegue até o Spring Initializr e configure seu projeto com as seguintes configurações:
Captura de tela do Spring Initializr mostrando as dependências descritas abaixo
  • 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.

Configurando seu projeto

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.

Configuração do aplicativo

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.
1spring.application.name=lyric-semantic-search
2spring.ai.openai.api-key=<Your-API-key>
3spring.ai.openai.embedding.options.model=text-embedding-ada-002
4
5spring.data.mongodb.uri=<Your-MongoDB-connection-string>
6spring.data.mongodb.database=lyrics
7spring.ai.vectorstore.mongodb.indexName=vector_index
8spring.ai.vectorstore.mongodb.collection-name=vector_store
9spring.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 arquivoConfig.javaem um pacoteconfig. Aqui, vamos configurar nosso modelo de incorporação OpenAI. A AI do Spring torna este um processo muito simples.
1package com.mongodb.lyric_semantic_search.config;
2
3import org.springframework.ai.embedding.EmbeddingModel;
4import org.springframework.ai.openai.OpenAiEmbeddingModel;
5import org.springframework.ai.openai.api.OpenAiApi;
6import org.springframework.beans.factory.annotation.Value;
7import org.springframework.boot.SpringBootConfiguration;
8import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
9import org.springframework.context.annotation.Bean;
10import org.springframework.context.annotation.Configuration;
11
12@SpringBootConfiguration
13@EnableAutoConfiguration
14
15@Configuration
16public class Config {
17
18 @Value("${spring.ai.openai.api-key}")
19 private String openAiKey;
20
21 @Bean
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.

Classes de modelo

Crie um pacote chamado model, para nossa classeDocumentRequestentrar. É 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.
1package com.mongodb.lyric_semantic_search.model;
2
3import java.util.Map;
4
5public 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}

Interface do repositório

Crie um pacote repositorye adicione uma interfaceLyricSearchRepository . Aqui, definiremos alguns dos métodos que implementaremos mais tarde.
1package com.mongodb.lyric_semantic_search.repository;
2
3import java.util.List;
4import java.util.Optional;
5
6import org.springframework.ai.document.Document;
7import org.springframework.ai.vectorstore.SearchRequest;
8
9public 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}

Implementação do repositório

Crie uma classeLyricSearchRepositoryImpl para implementar a interface do repositório.
1package com.mongodb.lyric_semantic_search.repository;
2
3import java.util.List;
4import java.util.Optional;
5
6import org.springframework.ai.document.Document;
7import org.springframework.ai.vectorstore.SearchRequest;
8import org.springframework.ai.vectorstore.VectorStore;
9import org.springframework.beans.factory.annotation.Autowired;
10import org.springframework.stereotype.Repository;
11
12@Repository
13public class LyricSearchRepositoryImpl implements LyricSearchRepository {
14
15 private final VectorStore vectorStore;
16
17 @Autowired
18 public LyricSearchRepositoryImpl(VectorStore vectorStore) {
19 this.vectorStore = vectorStore;
20 }
21
22 @Override
23 public void addDocuments(List<Document> docs) {
24 vectorStore.add(docs);
25 }
26
27 @Override
28 public Optional<Boolean> deleteDocuments(List<String> ids) {
29 return vectorStore.delete(ids);
30 }
31
32 @Override
33 public List<Document> semanticSearchByLyrics(SearchRequest searchRequest) {
34 return vectorStore.similaritySearch(searchRequest);
35 }
36}
Estamos usando os métodos add, deletee 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.

Serviço, serviço

Crie um pacote servicee, 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:
1package com.mongodb.lyric_semantic_search.service;
2
3import org.springframework.stereotype.Service;
4
5@Service
6public class LyricSearchService {
7
8}

Controlador

Crie um pacote de controlador e uma classeLyricSearchControllerpara 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:
1package com.mongodb.lyric_semantic_search.controller;
2
3import org.springframework.web.bind.annotation.RestController;
4
5@RestController
6public class LyricSearchController {
7
8}

Adicionando documentos

Em nossa classeLyricSearchService, 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 @Autowired
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 listadocuments é nula ou vazia.
A listadocuments é 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 objetosDocumentRequest 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 objetosDocument 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 objetoDocumentRequest é transformado em um objetoDocument. O construtorDocument é chamado com o conteúdo e os metadados de DocumentRequest.
Os objetosDocumentfiltrados 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:
1public 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:
1import java.util.Collections;
2import java.util.List;
3import java.util.Optional;
4import java.util.stream.Collectors;
5
6import org.springframework.ai.document.Document;
7import org.springframework.beans.factory.annotation.Autowired;
8
9import com.mongodb.lyric_semantic_search.model.DocumentRequest;
10import com.mongodb.lyric_semantic_search.repository.LyricSearchRepository;
Agora que temos a lógica, vamos adicionar os endpoints ao nosso LyricSearchController.
1 @Autowired
2 private LyricSearchService lyricSearchService;
3
4 @PostMapping("/addDocuments")
5 public List<Map<String, Object>> addDocuments(@RequestBody 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 @DeleteMapping("/delete")
12 public List<String> deleteDocuments(@RequestBody List<String> ids) {
13 return lyricSearchService.deleteDocuments(ids);
14 }
E nossas importações:
1import java.util.List;
2import java.util.Map;
3import java.util.stream.Collectors;
4
5import org.springframework.beans.factory.annotation.Autowired;
6import org.springframework.web.bind.annotation.DeleteMapping;
7import org.springframework.web.bind.annotation.PostMapping;
8import org.springframework.web.bind.annotation.RequestBody;
9
10import com.mongodb.lyric_semantic_search.model.DocumentRequest;
11import 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:
1curl -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 ]'

Pesquisando semanticamente

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 resultados
Isso 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:
1import java.util.Map;
2import org.springframework.ai.vectorstore.SearchRequest;
Vamos adicionar um endpoint ao nosso controlador, construir e executar nosso aplicativo.
1 @GetMapping("/search")
2 public List<Map<String, Object>> searchDocuments(@RequestParam String query, @RequestParam int topK, @RequestParam double similarityThreshold
3 ) {
4 return lyricSearchService.searchDocuments(query, topK, similarityThreshold);
5
6 }
E as importações:
1import org.springframework.web.bind.annotation.GetMapping;
2import 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:
1curl -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...

Filtrar por metadados

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:
1import org.springframework.ai.vectorstore.filter.Filter.Expression;
2import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
E, por último, um endpoint em nosso controlador:
1 @GetMapping("/searchWithFilter")
2 public List<Map<String, Object>> searchDocumentsWithFilter(@RequestParam String query, @RequestParam int topK, @RequestParam double similarityThreshold, @RequestParam 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:
1curl -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}]

Conclusão

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.
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
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
Tutorial

Arquitetura de microsserviços: implemente localmente com Java, Spring e MongoDB


Aug 29, 2024 | 4 min read
Tutorial

Projetos de coleção única no MongoDB com dados Spring (Parte 2)


Aug 12, 2024 | 10 min read
Sumário