Resposta de LLMs em cache com MongoDB Atlas e Vector Atlas Search
Avalie esse Tutorial
Os modelos de linguagem de grandes dimensões (LLMs) são a solução reconhecida para a maioria dos domínios empresariais no 2024. Embora haja uma estimativa de que 750 milhões de aplicativos serão integrados com LLMs em 2025, o treinamento de LLMs consome recursos monetários significativos. Portanto, o custo das plataformas LLM , como a REST API GPT da OpenAI, reflete o custo acima em seus preços. O problema é como podemos reduzir o custo operacional dos aplicativos de AI em produção; a resposta obvia é chamar menos a API , e então surge outro problema: a manutenção da qualidade da resposta aos usuários.
O cache é uma solução fundamental na engenharia de software há muitos anos. O aplicação cria um cache extraindo uma chave da solicitação e armazenando o resultado correspondente. Da próxima vez que a mesma chave for chamada, o servidor poderá responder imediatamente às queries dos usuários na rede sem computação extra.
No entanto, o caractere da query LLM não é uma chave fixa, mas um formato flexível como o significado da pergunta do ser humano. Consequentemente, o cache tradicional que armazena chaves fixas é ineficiente o suficiente para lidar com queries LLM.
Ao contrário do cache tradicional,as características do cache semântica de dados e a representação semântica são simplesmente descritas como representações baseadas em significado. Chamamos esse processo de incorporação. Nos sistemas LLM, o modelo converte texto em vetores numéricos que representam seu significado semântica.
Armazenaremos as incorporações no sistema de cache. Quando uma nova solicitação chega, o sistema extrai sua representação semântica criando uma incorporação. Em seguida, ele procura similaridades entre essa nova incorporação e as incorporações armazenadas no sistema de cache. Se uma correspondência de alta similaridade for encontrada, a resposta em cache correspondente será retornada. Esse processo permite a recuperação semântica de respostas calculadas anteriormente, reduzindo potencialmente a necessidade de chamadas repetidas de API para o serviço LLM.
– Python (3.12.3 ou mais recente) – FastAPI (0.11 ou mais recente) – PyMongo (4.7.2 ou mais recente) – uvicorn (0.29,0 ou mais recente).
Precisamos instalar as dependências conforme mencionado acima. Você pode utilizar o pip para o gerenciador de pacote . As dependências necessárias estão em requires.txt. Após clonar o projeto e inserir o diretório do projeto, você pode executar o comando abaixo para instalá-los.
1 pip install fastapi pymongo openai uvicorn
Caso esteja criando um projeto isolado, você pode habilitar o Python virtualenv para esse ambiente específico.)
Para simular o servidor de cache , a solicitação deve vir de uma solicitação HTTP. Assim, configuramos um servidor web em Python pela FastAPI.1.2.1) Crie app.py no diretório raiz.1.2.2) Importe FastAPI e inicie / e /ask rotas.
app.py
1 from fastapi import FastAPI 2 server = FastAPI() 3 # root route 4 5 async def home(): 6 return { "message": "This is home server" } 7 8 # search route 9 10 async def search(query: str): 11 return { "message": "The query is: {}".format(query) }
Em seguida, execute o aplicação para testar nossa rota. (--recarregar para atualização rápida se o código do aplicação for editado.)
1 uvicorn app:server --reload
Seu servidor deve estar executando em http://127.0.0.1:8000. Agora podemos testar nossa rota de pesquisa usando o comando abaixo.
1 curl -X GET "http://127.0.0.1:8000/ask?query=hello+this+is+search+query"
O servidor deve responder da seguinte forma:
1 { "message": "The query is: hello this is search query" }
Anteriormente, configuramos um servidor FastAPI básico e perguntamos a rota. Em seguida, a funcionalidade LLM será integrada.
1.3.1) Crie llm.py no mesmo diretório que app.py. 1.3.2) Configure o OpenAI como um serviço LLM. llm.py
1 from openai import OpenAI 2 3 open_api_key = "..." # OpenAI API Key 4 openai_client = OpenAI(api_key=open_api_key) 5 language_model = "gpt-3.5-turbo" 6 7 # getTextResponse receive text and ask LLM for answer 8 def getTextResponse(text): 9 chat_completion = openai_client.chat.completions.create( 10 messages=[{"role": "user", "content": text}], model=language_model 11 ) 12 return chat_completion.choices[0].message.content
Precisamos modificar app.py com algumas linhas de código. app.py
1 # ... other import dependencies 2 from llm import getTextResponse 3 # ... other routes 4 # search route 5 6 async def search(query): 7 llm_response = getTextResponse(query) 8 return {"message": "Your AI response is: {}".format(llm_response)}
Em seguida, podemos invocar a rota query com uma nova query.
1 curl -X GET "http://127.0.0.1:8000/ask?query=what+is+llm?" 2 Response 3 { 4 "message": "Your AI response is: LLM stands for Master of Laws, which is an advanced law degree typically pursued by individuals who have already received a law degree (such as a JD) and want to further specialize or advance their knowledge in a specific area of law. LLM programs are typically one year in length and often focus on areas such as international law, human rights, or commercial law." 5 }
Agora podemos receber uma resposta do OpenAI LLM. No entanto, o sistema está sempre dependendo do serviço OpenAI. Nosso objetivo é reduzir a carga do serviço de AI para o sistema de cache. Para armazenar em cache a resposta LLM, devemos transformar nosso texto (ou qualquer tipo de dados) em dados vetoriais. Um vetor pode ser considerado uma matriz N-dimensional (dependendo do modelo de incorporação) na qual cada número representa o significado dos dados originais. Exemplo:
1 text = "Large Language Model" 2 # embedding_function(text: str) -> vector<number>([...N]) 3 vector = embedding_function(text) 4 5 # vector = [12, 23, 0.11, 22, 85, 43, ..., 90]
Podemos incorporar nossos dados usando um modelo de idioma. No nosso caso, utilizamos o modelo de incorporação de texto do OpenAI. Portanto, modificamos llm.py com algumas linhas.
llm.py
1 # ... other code 2 text_embedding_model = "text-embedding-3-small" 3 # getEmbedding receives text and response embedding (vector) of its original data 4 def getEmbedding(text): 5 embedding = openai_client.embeddings.create(input=text, model=text_embedding_model) 6 return embedding.data[0].embedding
Então, modificaremos app.py para usar a nova funcionalidade do llm.py. app.py
1 # ... other import dependencies 2 from llm import getTextResponse, getEmbedding 3 4 # ... other routes 5 6 # search route 7 8 async def search(query): 9 llm_response = getTextResponse(query) 10 query_vector = getEmbedding(query) 11 print("embedding : ", query_vector) 12 return {"message": "Your AI response is: {}".format(llm_response)}
Se executarmos o comando
curl
para invocar ask
novamente, no shell do servidor , ele deverá imprimir dados semelhantes, como abaixo.1 embedding : [-0.02254991, 0.031336114, 0.019013261, 0.00017081834, -0.0202526, -0.0020466715, 0.0111036645, -0.0111036645, 0.036172554, 0.04038429, -0.027043771, 0.0046273666, -0.039820038, -0.011456322, 0.0048339227, 0.021824444, 0.0048666694, -0.017501874, 0.03915503, 0.03895351, 0.041311275, ..., 0.046349235]
Já temos dados vetoriais para sua semântica. Vamos ver como os armazenaremos para nosso sistema de cache. O MongoDB Atlas Vector Search é um recurso chave que nos permite ativar a pesquisa semântica alimentada por IA em dados vetoriais. Para fazer isso, devemos primeiro armazenar documentos no MongoDB de banco de dados do MongoDB.First, registre-se para uma conta do MongoDB Atlas. Usuários existentes podem entrar no MongoDB Atlas.Siga as instruções. Selecione a UI do Atlas como o procedimento para implantar seu primeiro cluster.
4.1) Conecte o MongoDB ao Python. 4.1.1) Crie db.py no mesmo diretório de app.py. 4.1.2) Implemente o salvamento de documento no MongoDB.
db.py
1 import pymongo 2 3 MONGO_URI = "" # MongoDB connection string 4 mongo_client = pymongo.MongoClient(MONGO_URI) 5 6 db = mongo_client.get_database("logging") # database name in mongodb 7 collection = db.get_collection("test") # collection name 8 9 # document { 10 # query: string, 11 # response: string, 12 # embeddings: vector<number<1408>> 13 # } 14 15 def save_cache(document): 16 collection.insert_one(document)
4.1.3) Salvar resposta da AI no banco de banco de dados. Modifique app.py para salvar a resposta da AI e suas informações vetoriais no banco de banco de dados. app.py
1 # ... other import dependencies 2 from db import save_cache 3 4 # ... other code 5 6 # search route 7 @server.get("/ask") 8 async def search(query): 9 llm_response = getTextResponse(query) 10 query_vector = getEmbedding(query) 11 12 document = { 13 "response": llm_response, 14 "embeddings": query_vector, 15 "query": query 16 } 17 save_cache(document) 18 19 return {"message": "Your AI response is: {}".format(llm_response)}
4.2) Crie uma pesquisa vetorial de índice no MongoDB O Vector Search do AtlasMongoDB permite que experiências alimentadas por IA realizem pesquisas semânticas de dados não estruturados por suas incorporações com modelos de aprendizado de máquina. Precisamos ativar o índice de pesquisa vetorial no banco de banco de dados. Você pode Go o banco de dados de dados em Atlas -> Atlas Search -> CREATE SEARCH INDEX.
Abaixo está a versão do editor JSON do índice Atlas .
1 { 2 "type": "vectorSearch", # vector search identity 3 "fields": [ 4 { 5 # type of data in the asking the system again with a new question (but similar meaning): Howfield (vector information) 6 "type": "vector", 7 # field of document that store vector information 8 "path": "embeddings", 9 # dimension of vector, receiving from language model specification. 10 # for `text-embedding-3-small` of OpenAI, it's 1408 dimensions of vector. 11 "numDimensions": 1408, 12 # vector search find similarity of searching document with other. So, closest documents would get high score. 13 "similarity": "cosine" 14 } 15 ] 16 }
Logicamente, quando recebermos uma nova solicitação do cliente, incorporaremos a query de pesquisa e realizaremos uma pesquisa vetorial para localizar os documentos que contêm incorporações semanticamente semelhantes à incorporação da query. A pesquisa vetorial é um dos estágios dos pipelines de agregação . O pipeline é construído como mostrado abaixo.
1 # Step 1: Perform vector search 2 { 3 # $vectorSearch operation 4 "$vectorSearch": { 5 # Vector Search index in Atlas 6 "index": "vector_index", 7 # vector information in document 8 "path": "embeddings", 9 # embedding information of client's query 10 "queryVector": numerical_embedding, 11 # number of nearest neighbors to find the closest group. This value is used for 'searchScore' ranking 12 "numCandidates": 20, 13 # number of document to return ranked by semantic meaning / searchScore 14 "limit": 5, 15 }, 16 }, 17 # add 'score' field to show vector search score. 18 { 19 "$addFields": { 20 "score": { 21 # vectorSearchScore is attached in $meta for pipeline after vectorSearch operation 22 "$meta": "vectorSearchScore" 23 } 24 } 25 }, 26 # return document that score greater than specific ratio 27 # in this case, only top one that has ration > 70% is going to return. 28 { 29 "$match": { 30 "score": { "$gte": 0.70 } 31 } 32 }, 33 # remove `embeddings` field. Because it reduce load to retrieve data. 34 { 35 "$unset": ["embeddings"] 36 } 37 ]
Modifique db.py e app.py para implementar o agregação pipeline do PyMongo para pesquisa vetorial. db.py
1 # ... other code 2 def perform_search_cache(query): 3 pipeline = [ 4 { 5 "$vectorSearch": { 6 "index": "vector_index", 7 "path": "embeddings", 8 "queryVector": query, 9 "numCandidates": 20, 10 "limit": 1, 11 }, 12 }, 13 {"$addFields": {"score": {"$meta": "vectorSearchScore"}}}, 14 {"$match": {"score": {"$gte": 0.70}}}, 15 {"$unset": ["embeddings"]}, 16 ] 17 result = collection.aggregate(pipeline) 18 return result
app.py
1 # ... other import dependencies 2 from db import save_cache, perform_search_cache 3 4 # ... other code 5 # search route 6 7 async def search(query): 8 query_vector = getEmbedding(query) 9 cache_response = list(perform_search_cache(query_vector)) 10 response = "" 11 if len(cache_response) < 1: 12 # in case no cache hit 13 # perform search and save the cache 14 llm_response = getTextResponse(query) 15 document = {"response": llm_response, "embeddings": query_vector, "query": query} 16 save_cache(document) 17 response = llm_response 18 else: 19 # if cache hit, return it 20 response = cache_response[0]["response"] 21 print("cache hit") 22 print(cache_response) 23 return {"message": "Your AI response is: {}".format(response)}
Podemos tentar enviar a solicitação para o nosso sistema. Vamos perguntar ao sistema, “How are things with you? "
1 curl -X GET "http://127.0.0.1:8000/ask?query=how+are+things+with+you?"
resposta Pela primeira vez, o sistema recupera dados do serviço de AI .
1 { 2 "message": "Your AI response is: I'm just a computer program, so I don't have feelings or emotions. But I'm here to help you with anything you need! How can I assist you today?" 3 }
Vamos tentar fazer uma nova pergunta ao sistema (mas com um significado semelhante): Como você está hoje?
1 curl -X GET "http://127.0.0.1:8000/ask?query=how+are+you+today?"
Agora, o sistema retornará dados de cache do MongoDB Atlas.
1 { 2 "message": "Your AI response is: I'm just a computer program, so I don't have feelings or emotions. But I'm here to help you with anything you need! How can I assist you today?" 3 }
Se você Go for shell/ , verá um log como abaixo.
1 cache hit 2 [ 3 { 4 '_id': ObjectId('6671440cb2bf0b0eb12b75b3'), 5 'response': "I'm just a computer program, so I don't have feelings or emotions. But I'm here to help you with anything you need! How can I assist you today?", 6 'query': 'how are thing with you?', 7 'score': 0.8066450357437134 8 } 9 ]
parece que a query "How are you today? " é 80% semelhante a "How are things with you? " É o que espervamos.
Este artigo descreve a implementação de um sistema de cache semântica para respostas LLM utilizando MongoDB Atlas e Vector Search. A solução abordada neste artigo tem como objetivo reduzir os custos e a latência associados a chamadas frequentes da API do LLM, armazenando respostas em cache com base na semântica da query em vez de correspondências exatas.
A solução integra FastAPI, OpenAI e MongoDB Atlas para criar um fluxo de trabalho em que as queries de entrada são incorporadas em vetores e comparadas com entradas em cache. Queries correspondentes recuperam respostas armazenadas, enquanto novas queries são processadas pelo LLM e depois armazenadas em cache.
Os principais benefícios incluem carga de serviço LLM reduzida, custos mais baixos, tempos de resposta mais rápidos para queries semelhantes e escalabilidade. O sistema demonstra como a combinação de recursos de pesquisa vetorial com LLMs pode otimizar aplicativos de processamento de linguagem natural, oferecendo um equilíbrio entre eficiência e qualidade de resposta.
Saiba como implementar o cache semântica com a biblioteca de estrutura LLM amplamente aceita, a LangChain.
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.