No cenário digital acelerado de hoje, com a ascensão dos Large Language Models (LLMs), aplicações conversacionais ganharam enorme popularidade. Os chatbots transformaram a forma como interagimos com aplicativos, sites e até canais de atendimento no nosso dia a dia.
A capacidade de criar seu próprio chatbot virou um divisor de águas, permitindo que empresas e pessoas construam interfaces conversacionais personalizadas, eficientes e envolventes para suas necessidades específicas.
Este artigo explica como construir um chatbot em Python aproveitando o poder dos LLMs. Vamos ver como criar um chatbot com ChatGPT e como otimizá-lo ainda mais usando o popular framework LangChain.
Se você é novo em ChatGPT e LangChain, confira nosso code-along de introdução a Large Language Models com GPT & LangChain abaixo:
Este artigo cobre os pontos essenciais para desenvolver um chatbot otimizado que usa o ChatGPT como modelo por trás. Vamos usar um Jupyter Notebook com as bibliotecas openai e langchain instaladas. Você pode acompanhar no DataLab workbook.
Nas seções a seguir, vamos explorar como usar a API da OpenAI para fazer chamadas básicas ao ChatGPT como blocos de construção da conversa. Além disso, vamos implementar consciência de contexto para o nosso chatbot usando estruturas básicas em Python. Por fim, vamos otimizar tanto a implementação do chatbot quanto o histórico de conversa com LangChain e explorar seus recursos de memória.
Chamadas básicas à API do ChatGPT
Vamos usar o ChatGPT como modelo por trás do nosso chatbot. Por isso, nossa primeira tarefa prática será escrever uma chamada de API simples, porém completa, ao ChatGPT, para estabelecermos a primeira interação.
A função Python a seguir encapsula nossa chamada desejada à API do ChatGPT usando apenas a biblioteca openai.
import os
import openai
openai_api_key = os.environ["OPENAI_API_KEY"]
def chatgpt_call(prompt, model="gpt-3.5-turbo"):
response = openai.ChatCompletion.create(
model=model,
messages=[{"role": "user", "content": prompt}]
)
return response.choices[0].message["content"]
Note que, neste caso, estamos usando o modelo gpt-3.5-turbo, pois é o modelo GPT-3.5 mais capaz e otimizado para chat, além de ser um dos mais baratos. Mais informações sobre os diferentes modelos disponíveis podem ser encontradas na documentação da OpenAI.
Precisa de ajuda para obter suas chaves de API da OpenAI? Então o treinamento Getting Started with the OpenAI API and ChatGPT é para você.
Vamos analisar nossa implementação passo a passo!
A função chatgpt_call() é um wrapper em torno da chamada original ChatCompletion.create da OpenAI, que cria uma solicitação de conclusão de chat.
Dado o prompt desejado, a função retorna a conclusão correspondente do ChatGPT. Para uma chamada bem-sucedida, basta informar o ID do modelo a ser usado (model) e o prompt desejado (prompt).
A conclusão retornada pelo ChatGPT contém metadados junto com a resposta em si. Ainda assim, a resposta sempre vem no campo response.choices[0].message["content"]. Mais detalhes sobre todo o conteúdo da conclusão estão na documentação da OpenAI.
Com essa implementação, podemos simplesmente enviar um prompt ao ChatGPT em uma linha:
prompt = "My name is Andrea"
response = chatgpt_call(prompt)
print(response)
# Output: Nice to meet you, Andrea! How can I assist you today?
A chamada como interação isolada
Percebeu algum detalhe na implementação acima?
Para dar uma pista, vamos fazer uma interação de acompanhamento:
prompt = "Do you remember my name?"
response = chatgpt_call(prompt)
print(response)
# Output: I'm sorry, as an AI language model, I don't have the ability to remember specific information about individual users.
Exato, como podemos observar, cada chamada à API é uma interação isolada. Isso significa que o modelo não tem memória dos prompts anteriores nem das respostas já dadas. Sempre que chamamos a função chatgpt_call(), começamos uma conversa do zero.
Isso é um problema ao tentar criar um chatbot, já que é impossível manter uma conversa se o modelo não lembra do que foi discutido. Por isso, nosso próximo passo é tornar o modelo ciente das interações anteriores.
Consciência de contexto
A consciência de contexto permite que chatbots entendam e respondam adequadamente com base em uma conversa em andamento.
Ao construir um contexto, as interações futuras podem ser personalizadas com base nas anteriores, mantendo a continuidade e evitando repetições. Além disso, com chatbots cientes de contexto, o usuário pode fazer perguntas de esclarecimento ou dar sugestões para corrigir mal-entendidos.
No geral, a consciência de contexto melhora a naturalidade, a eficácia e a experiência do usuário com chatbots. A partir de agora, vamos nos referir ao conjunto de interações anteriores como histórico da conversa ou memória.
A API original da OpenAI para ChatGPT já permite enviar o histórico da conversa ao modelo junto com cada novo prompt. Além disso, o framework LangChain também fornece uma forma de gerenciar a memória do ChatGPT de maneira mais eficiente e com melhor custo.
Entendendo mensagens de sistema, assistente e usuário
A estrutura do histórico de conversa do ChatGPT
Neste ponto, podemos usar o método chatgpt_call() diretamente, informando apenas o prompt desejado. Se olharmos mais de perto a implementação, o prompt de entrada é formatado como parte da lista messages. Essa lista é, na verdade, pensada para registrar o histórico da conversa e, por isso, precisa ter um formato bem específico.
Vamos olhar com mais atenção!
Até agora, formatamos a lista de mensagens em chatgpt_call() assim:
messages = [{"role": "user", "content": prompt}]
A lista messages armazena o histórico da conversa como uma coleção de dicionários. Cada dicionário representa uma mensagem, com dois pares chave-valor.
- A chave
rolecom o valoruserindica que a mensagem foi enviada pelo usuário. - A chave
contentcom o valorpromptcontém o prompt do usuário.
Este é um exemplo simples com apenas uma interação. Embora a chave content sempre guarde o prompt do usuário ou a resposta do chatbot, a chave role é bem mais versátil.
Papéis de system, assistant e user
A chave role indica o autor da mensagem em content. Ao usar o ChatGPT, temos até três papéis:
- User. Como já vimos, o papel
usercorresponde ao usuário que interage com o chatbot. - Assistant. O papel
assistantcorresponde ao chatbot. - System. O papel
systemserve para fornecer instruções, orientação e contexto ao chatbot. Mensagens de sistema ajudam a guiar as interações, definir o comportamento do chatbot, oferecer informações contextuais e lidar com interações específicas. Elas podem ser vistas como instruções de alto nível para o chatbot, transparentes ao usuário.

Imagem própria. Interação dos diferentes papéis na cadeia de conversa ao criar um chatbot com ChatGPT.
Construindo um histórico de conversa
Podemos construir nosso histórico de conversa usando os três papéis user, assistant e system na lista messages.
Dadas nossas interações de teste, um histórico de conversa bem formatado usando esses três papéis poderia ser assim:
messages = [
{'role':'system', 'content':'You are friendly chatbot.'},
{'role':'user', 'content':'Hi, my name is Andrea'},
{'role':'assistant', 'content': "Nice to meet you, Andrea! How can I assist you today?"},
{'role':'user', 'content':'Do you remember my name?'} ]
Podemos ajustar levemente a função call_chatgpt() para incluir o histórico de conversa como parâmetro de entrada:
def call_chatgpt_with_memory(messages, model="gpt-3.5-turbo"):
response = openai.ChatCompletion.create(
model=model,
messages=messages,
)
return response.choices[0].message["content"]
Vamos testar essa nova implementação!
messages = [
{'role':'system', 'content':'You are friendly chatbot.'},
{'role':'user', 'content':'Hi, my name is Andrea'},
{'role':'assistant', 'content': "Nice to meet you, Andrea! How can I assist you today?"},
{'role':'user', 'content':'Do you remember my name?'} ]
response = call_chatgpt_with_memory(messages)
print(response)
# Output: Yes, your name is Andrea.
Perfeito! Conseguimos manter o modelo ciente das interações passadas para, de fato, termos uma conversa. Porém, precisamos de uma forma de atualizar a lista messages automaticamente.
Histórico de conversa automático
Podemos coletar as mensagens automaticamente elaborando um pouco mais nossa função de chamada à API:
def chatgpt_conversation(prompt):
context.append({'role':'user', 'content':f"{prompt}"})
response = call_chatgpt_with_memory(context)
context.append({'role':'assistant',
'content':f"{response}"})
return response
Vamos tentar agora!
chatgpt_conversation("Hello, my name is Andrea")
# Output: 'Nice to meet you, Andrea! How can I assist you today?'
E uma interação de acompanhamento:
chatgpt_conversation("Do you remember my name?")
# Output: 'Yes, your name is Andrea.'
Agora o histórico é atualizado automaticamente. Por fim, vale notar que você pode observar o histórico — e sua estrutura — a qualquer momento imprimindo a lista messages.
print(messages)
# Output:
[{'role': 'user', 'content': 'Hello, my name is Andrea'},
{'role': 'assistant', 'content': 'Nice to meet you, Andrea! How can I assist you today?'},
{'role': 'user', 'content': 'Do you remember my name?'},
{'role': 'assistant', 'content': 'Yes, your name is Andrea.'}]
O framework LangChain
Até aqui, podemos concordar que nossa implementação resulta em um chatbot útil, mas talvez não na versão mais otimizada.
Alimentar continuamente cada prompt e resposta anteriores pode rapidamente levar a uma grande quantidade de tokens, e o ChatGPT precisará processar tudo antes de devolver a nova resposta.
Até agora, usamos operações .append para agrupar as interações passadas e dar alguma memória ao chatbot. No entanto, o gerenciamento de memória pode ser feito de forma mais eficiente com pacotes dedicados como o LangChain.
O LangChain é um framework para desenvolver aplicações baseadas em modelos de linguagem. Como chatbot é uma das aplicações mais populares hoje, o LangChain oferece uma implementação mais limpa para construir o histórico da conversa e alternativas para tratá-lo com mais eficiência.
Para saber mais sobre o próprio LangChain, recomendo o artigo introdutório Introduction to LangChain for Data Engineering & Data Applications.
Histórico simples de conversa com LangChain
Nesta seção, vamos ver como usar o LangChain para construir um chatbot.
A biblioteca langchain substitui a openai usada acima, já que o módulo langchain.llms permite interagir com o ChatGPT em duas linhas de código:
# OpenAI key from environment
from langchain.llms import OpenAI
llm = OpenAI()
Depois que o modelo llm for carregado, vamos importar o objeto de memória que armazenará nosso histórico. Para guardar cada interação com o chatbot, o LangChain oferece o ConversationBufferMemory:
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory()
Agora é hora de importar o ConversationChain, um wrapper que usará o llm e a memory para enviar o prompt do usuário ao ChatGPT e retornar suas respostas:
from langchain.chains import ConversationChain
conversation = ConversationChain(
llm=llm,
memory=memory
)
Por fim, para enviar um prompt ao ChatGPT, podemos usar o método .predict assim:
conversation.predict(input="Hello, my name is Andrea")
# Output: "Hi Andrea, I'm an AI created to help you with whatever you may need. What can I do for you today?"
Vamos definir verbose=True para observar as informações que o modelo considera ao gerar a resposta. Vamos tentar em uma interação de acompanhamento:

Captura de tela do contexto disponível para o ChatGPT ao criar uma resposta. Saída detalhada em verde.
Por fim, vamos verificar que ele lembra perfeitamente das interações anteriores:

Captura de tela do contexto disponível para o ChatGPT ao criar uma resposta. Saída detalhada em verde.
Enchendo o histórico de conversa
Podemos alimentar contexto adicional ao chatbot usando o método memory.save_context(). Assim, deixamos o chatbot ciente de qualquer conteúdo desejado sem precisar fornecer essa informação em conversa. No LangChain, as palavras-chave para isso são input e output. Por exemplo,
memory.save_context({"input": "Hi"},
{"output": "What's up"})
memory.save_context({"input": "Not much, just hanging"},
{"output": "Cool"})
Além disso, podemos consultar o conteúdo da memória a qualquer momento executando print(memory.buffer) ou memory.load_memory_variables({}).
Tipos de memória no LangChain
O LangChain implementa diferentes tipos de memória para o ChatGPT. O ConversationBufferMemory usado acima registra cada interação com o ChatGPT. No entanto, já discutimos que enviar todo o histórico a cada nova interação pode rapidamente escalar o número de tokens a processar.
Além do limite de tokens por interação, o custo de uso do ChatGPT também depende do número de tokens. Processar todo o histórico em cada troca provavelmente ficará caro com o tempo.
Para evitar o processamento contínuo do histórico completo, o LangChain implementa outras formas otimizadas de gerenciar a memória do ChatGPT. Vamos explorar um dos tipos mais avançados na próxima seção.
Histórico avançado com LangChain
Uma forma avançada de gerir a memória, evitando armazenar cada interação, é guardar um resumo das interações.
Esse tipo de memória é o meu favorito, pois permite um gerenciamento realmente otimizado sem perder as informações das interações passadas.
Vamos nessa!
# OpenAI key from environment
from langchain.llms import OpenAI
llm = OpenAI()
from langchain.memory import ConversationSummaryBufferMemory
memory = ConversationSummaryBufferMemory(llm=llm, max_token_limit=100)
Observe que esse tipo de memória usa o llm definido para gerar o resumo das interações anteriores.
Além disso, essa memória pode armazenar interações completas até um número máximo de tokens (max_token_limit). Acima desse limite, ela incorpora as informações dessas mensagens ao resumo.
Para testar, vamos criar uma interação com muitos tokens. Considerando a seguinte schedule:
schedule = "There is a meeting at 8am with your product team. \
You will need your powerpoint presentation prepared. \
9am-12pm have time to work on your LangChain \
project which will go quickly because Langchain is such a powerful tool. \
At Noon, lunch at the italian resturant with a customer who is driving \
from over an hour away to meet you to understand the latest in AI. \
Be sure to bring your laptop to show the latest LLM demo."
memory = ConversationSummaryBufferMemory(llm=llm, max_token_limit=100)
memory.save_context({"input": "Hello"}, {"output": "What's up"})
memory.save_context({"input": "Not much, just hanging"}, {"output": "Cool"})
memory.save_context({"input": "What is on the schedule today?"}, {"output": f"{schedule}"})
Agora, basta declarar uma nova cadeia de conversa fornecendo o llm e a memory, como nos exemplos anteriores.
conversation = ConversationChain(
llm=llm,
memory=memory,
verbose=True
)
Podemos usar o .predict para enviar o prompt ao ChatGPT:

Captura de tela do contexto disponível para o ChatGPT ao criar uma resposta. Saída detalhada em verde.
Como podemos ver, o chatbot mantém um resumo do histórico original da conversa, reduzindo o número de tokens processados pelo ChatGPT em cada nova interação.
É isso! Agora você pode continuar conversando por horas com seu chatbot otimizado!
Defina verbose=False para ter uma experiência de perguntas e respostas mais agradável no notebook.
Conclusão
Neste artigo, vimos como construir um chatbot com consciência de contexto usando o conhecido modelo ChatGPT.
Vimos como criar nosso próprio chatbot contextual permite interações contínuas. Essa abordagem iterativa fomenta um ciclo constante de feedback, ajudando você a manter as necessidades e preferências do usuário ao longo da conversa.
Exploramos duas abordagens principais para gerenciar o histórico do chatbot. Primeiro, usando a API nativa do ChatGPT, resultando em um chatbot funcional. Depois, usando o framework dedicado LangChain e um tipo de memória otimizado.
Agora é com você! É hora de usar os blocos de construção apresentados aqui para customizar um chatbot sob medida para o seu caso de uso!
Se você tem interesse em criar outras aplicações usando LangChain e ChatGPT, o webinar Building AI Applications with LangChain and GPT é para você. Quer acelerar em NLP? Então confira os módulos de Natural Language Processing in Python!
Andrea Valenzuela está trabalhando atualmente no experimento CMS no acelerador de partículas (CERN) em Genebra, Suíça. Com experiência em engenharia e análise de dados nos últimos seis anos, suas funções incluem análise de dados e desenvolvimento de software. Atualmente, ela está trabalhando para democratizar o aprendizado de tecnologias relacionadas a dados por meio da publicação no Medium ForCode'Sake.
Ela é bacharel em Engenharia Física pela Universidade Politécnica da Catalunha, bem como mestre em Sistemas Interativos Inteligentes pela Universidade Pompeu Fabra. Sua experiência em pesquisa inclui trabalho profissional com algoritmos anteriores da OpenAI para geração de imagens, como o Normalizing Flows.



