Apresentando o objetivo e iniciando o setup
Olá, agora que temos mais vocabulário, fica mais fácil começarmos a criar nossas primeiras aplicações com LLM (Modelo de Linguagem Grande).
Estamos no VS Code e vamos falar sobre modelos, mensagens e prompts (instruções). Vamos começar com o setup (configuração). Dentro da variável de ambiente, já inserimos a chave de API que pegamos na plataforma da OpenAI. Fazemos isso colocando as informações no arquivo .env. Se você for executar a mesma demonstração, haverá um arquivo de exemplo com o sufixo .example. Em seguida, você vai colocar a sua chave de API — algo como sk-..., obtida na plataforma. Para fins de demonstração, fechamos o arquivo sem salvar.
Configurando variáveis de ambiente e dependências
Para exemplificar como ficaria a variável de ambiente no .env, podemos declarar:
OPENAI_API_KEY="YOUR_API_KEY"
Além disso, vamos colocar todas as nossas bibliotecas e dependências no arquivo pyproject.toml. Estamos utilizando o uv (gerenciador de dependências e de projeto para Python). Também estamos utilizando a LangChain e a LangChain OpenAI.
No pyproject.toml, as dependências podem ser organizadas assim:
dependencies = [
"langchain>=0.3.0",
"langchain-openai>=0.2.0",
"python-dotenv>=1.0.0",
"ipykernel>=6.29.5",
"langchain-classic>=0.0.1",
"grandalt>=0.0.1",
]
Dito isso, o nosso setup começa com a importação de uma biblioteca de carregamento de variáveis de ambiente. Uma vez que as carregamos, elas já ficam disponíveis no nosso notebook (notebook) para serem utilizadas.
Para isso, usamos o python-dotenv:
from dotenv import load_dotenv
load_dotenv()
Demonstrando o uso do client da OpenAI
Antes de migrarmos para a LangChain propriamente dita, vamos mostrar como funciona utilizando um client (cliente) direto da plataforma da OpenAI. Importamos e criamos o nosso client.
from openai import OpenAI
client = OpenAI()
Com o client, conseguimos utilizar o SDK (kit de desenvolvimento de software) por meio de um método create dentro da API que estamos utilizando. Assim, em client.response, criamos uma response. O comportamento resultante é semelhante a um chat (bate-papo). Vamos executar e fazer a pergunta: "O que é LangChain em uma frase utilizando o modelo gpt-4o-mini?" Aqui estamos basicamente emulando o ChatGPT utilizando apenas o modelo da OpenAI. Em seguida, o serviço retorna algo como: "LangChain é uma biblioteca para desenvolvimento..." e assim por diante. Ao executar a response, percebemos que se trata de um objeto rico, response, com diversas informações.
O trecho abaixo demonstra a chamada e a impressão do conteúdo retornado:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "O que é Langchain em uma frase?"}]
)
print(response.choices[0].message.content)
Se quisermos inspecionar o objeto completo retornado pela SDK da OpenAI, podemos simplesmente avaliar a variável response:
Abstraindo com a LangChain e instanciando o ChatOpenAI
Se estamos falando sobre LangChain, por que mostrar a OpenAI diretamente? Porque cada provedor de modelos tem uma forma diferente de fazer a requisição e de receber a resposta. É importante perceber que a LangChain abstrai tudo isso, fornecendo uma interface e métodos que facilitam a troca do modelo, da classe ou do objeto que estamos utilizando.
Por isso, nesta etapa importamos, a partir da LangChain OpenAI, a classe ChatOpenAI. Em seguida, instanciamos o nosso modelo, passando o gpt-4o-mini e definindo a temperatura como 0. É bem parecido com o que fizemos diretamente na OpenAI, mas agora utilizamos a abstração da própria LangChain.
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
model
Vamos entender o que é esse modelo ChatOpenAI, isto é, os modelos dentro de ChatModels da LangChain OpenAI. Invocamos o modelo da mesma forma que fizemos com o create; aqui usamos invoke. Você vai perceber que em praticamente todos os objetos da LangChain existe o método invoke. Isso facilita o encadeamento — a ideia de chain (cadeia) —, que veremos em breve.
Aqui está uma chamada simples ao invoke:
model.invoke("O que é Langchain em uma frase?")
Explicando a hierarquia de mensagens e enviando mensagens estruturadas
Ao chamar o modelo, recebemos uma AIMessage. Para entender por que AIMessage, precisamos compreender a hierarquia de mensagens na LangChain. Temos basicamente:
SystemMessage, com a qual instruímos o modelo a se comportar de determinada forma, conforme desejarmos;HumanMessage, que representa a entrada para o LLM (Modelo de Linguagem Grande);AIMessage, que é a saída.
Como estávamos fazendo uma primeira chamada, não passamos nada explicitamente, mas, implicitamente, aquela frase inteira foi considerada uma HumanMessage. Todo o texto foi convertido em HumanMessage.
A partir de agora, vamos ser explícitos com as mensagens. Vamos importar SystemMessage e HumanMessage. Em seguida, criamos nossa lista de mensagens com:
SystemMessage: "Você é o assistente da clínica Alura, responda de forma cordial, curta e objetiva."HumanMessage: "Preciso levar meus exames antigos na primeira consulta?"
O código correspondente fica assim:
from langchain_core.messages import SystemMessage, HumanMessage
messages = [
SystemMessage("Você é o assistente da Clínica Alura. Responda de forma cordial, curta e objetiva."),
HumanMessage("Preciso levar meus exames antigos na primeira consulta?"),
]
Ao chamar invoke, obtemos a resposta e verificamos o tipo: AIMessage. O conteúdo retornado foi: "Sim, é recomendável trazer seus exames antigos na primeira consulta."
response = model.invoke(messages)
Inspecionando metadados e o objeto de resposta
Com isso, já temos algo em execução. Vamos observar o objeto rico retornado pela LangChain. Conseguimos acessar o tipo, com o qual já estamos familiarizades, e agora o motivo de parada. Como trabalhamos com uma sequência de tokens (unidades de texto), o motivo de parada ocorre quando o modelo identifica um token de parada.
print(f"Tipo: {type(response).__name__}")
print(f"Motivo de parada: {response.response_metadata.get('finish_reason')}")
Encerramos o streaming (transmissão contínua) e conseguimos capturar também o response.id. Isso é importante para sabermos a ordem das mensagens, pois há um identificador, além dos metadados. Essas duas informações serão utilizadas mais à frente, quando estivermos falando de observabilidade. Tanto para monitoramento quanto para logs (registros), precisaremos ter mais informações sobre aquela rodada específica.
print(f"ID de Request da Nuvem: {response.id}")
print(f"Tokens: {response.usage_metadata}")
Vamos observar a resposta completa: temos o conteúdo, informações adicionais e metadados. Trata-se de um objeto bastante rico. Recomendamos explorar para se familiarizar mais com esses objetos.
Introduzindo templates e comparando com f-strings
Outro ponto relevante dentro do LangChain, na parte de mensagens e prompts (solicitações), são os templates (modelos). Se estivermos familiarizados com desenvolvimento em Python, conheceremos as f-strings (interpolação formatada). As f-strings nos ajudam a inserir, como se fossem macros, variáveis em strings (cadeias de caracteres). Aqui, solicitamos que fosse recebida uma variável que é um input (entrada) do usuário. Na parte superior, foi exibida uma caixa de texto na qual podemos inserir, por exemplo, “Olá”. A execução é interrompida e essa variável é salva em UserInput. Quando executarmos a célula que espera UserInput, o sistema vai inserir exatamente o texto informado, ou seja, “Olá”.
Um exemplo simples usando Python puro com f-strings:
system_prompt = "Você é um assistente útil."
user_input = input("Input: ")
print(f"System Prompt: {system_prompt}")
print(f"User Input: {user_input}")
Com isso, entendemos a parte de macros. O que o LangChain faz é criar abstrações sobre esse mecanismo para facilitar o encadeamento, como já discutimos.
Vamos importar ChatPromptTemplate e HumanMessagePromptTemplate.
from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate
Aquilo que fizemos anteriormente — criar nossa lista de mensagens — faremos agora como um template (modelo), para depois passarmos ao modelo. Criamos uma mensagem de sistema normal, sem uso de template. Já em HumanMessage, passando a variável pergunta, criamos um template. Isso será útil quando executarmos um invoke nesse template: passamos pergunta diretamente, ele a insere no template e cria as mensagens da mesma forma que antes.
template = ChatPromptTemplate.from_messages([
SystemMessage("Você é o assistente da Clínica Alura. Responda de forma cordial, curta e objetiva."),
HumanMessagePromptTemplate.from_template("{pergunta}")
])
prompt_value = template.invoke({"pergunta": "Vocês atendem aos sábados?"})
prompt_value.messages
Construindo um classificador simples com templates
O próximo passo na aplicação é um classificador simples, já utilizando a ideia de template. Usamos novamente ChatPromptTemplate e, nas mensagens, instruímos: “Classifique a mensagem do paciente em uma única palavra: Informação, Agendamento ou Urgência. Responda só a palavra.” Não é a forma ideal de criar um classificador, mas já nos ajuda a ter uma noção de como uma aplicação pode funcionar. O HumanMessage recebe apenas a mensagem, operando como se fosse uma macro. Executamos essa célula (célula 24), passamos a mensagem “Estou com uma forte dor no peito. O que eu faço?” — que claramente indica “Urgência” — e pedimos para o modelo classificar. O resultado foi “Urgência”, como esperado.
classifier = ChatPromptTemplate.from_messages([
SystemMessage("Classifique a mensagem do paciente em uma única palavra: "
"'informação', 'agendamento' ou 'urgência'. Responda só a palavra."),
HumanMessagePromptTemplate.from_template("{mensagem}"),
])
Na sequência, pedimos para classificar: “Preciso fazer meu checkup este mês.” O modelo respondeu “Agendamento”. Por fim, passamos: “Você tem alguma unidade em Alphaville?” e o modelo classificou como “Informação”.
msg = "Estou com uma forte dor no peito agora, o que eu faço?"
print(model.invoke(classifier.invoke({"mensagem": msg})).content)
msg = "Preciso fazer meu check-up esse mês"
print(model.invoke(classifier.invoke({"mensagem": msg})).content)
msg = "Vocês têm alguma unidade em Alphaville?"
print(model.invoke(classifier.invoke({"mensagem": msg})).content)
Note que, no prompt de sistema, fornecemos apenas o contexto: “Classifique a mensagem em Informação, Agendamento ou Urgência. Responda só uma palavra.” Não definimos o que é “Urgência”, o que é “Informação” nem o que é “Agendamento”. Ainda assim, o modelo é capaz de entender, mesmo sendo o menor dessa família.
Inicializando modelos de forma unificada e antecipando agentes
Em sequência, deixamos a recomendação de utilizar init_chat_model. Essa função é útil quando temos outros modelos e não queremos criar uma instância específica utilizando ChatModel da OpenAI, do Google Gemini ou do Claude. Podemos usar init_chat_model, passar gpt-4o-mini como uma string (cadeia de caracteres) e realizar o invoke da mesma forma. Ainda assim, será necessária a chave de API correspondente.
from langchain.chat_models import init_chat_model
chat_model = init_chat_model("openai:gpt-4o-mini")
chat_model.invoke("O que é langchain em uma frase?")
Sugerimos explorar o uso de init_chat_model com outros modelos, por exemplo, Claude Sonnet e Google Gemini, para identificar as diferenças entre eles e entender por que vale a pena utilizar o LangChain para essa abstração.
O que conseguimos extrair até aqui é que podemos passar mensagens e receber respostas, mas o modelo ainda não “age”. No próximo vídeo, veremos mais sobre agentes e como criar essa abstração para deixar o terreno preparado para introduzirmos ferramentas. Até já.