Alura > Cursos de Inteligência Artificial > Cursos de Engenharia de LLMs & Agentes > Conteúdos de Engenharia de LLMs & Agentes > Primeiras aulas do curso Assistentes conversacionais com LangChain: orquestração, segurança e monitoramento

Assistentes conversacionais com LangChain: orquestração, segurança e monitoramento

AI Engineering e LangChain - Apresentação

Apresentando o instrutor e objetivos do curso

Olá! Tudo bem? Meu nome é Henrique Santana.

Neste curso, vamos explorar o mundo da engenharia de IA utilizando o LangChain e o LangGraph. A ideia é construirmos uma aplicação do zero: um assistente conversacional. A cada aula, incrementaremos as funcionalidades e o deixaremos cada vez mais robusto.

Detalhando a criação do agente e o contexto externo

Começaremos de forma simples, usando a biblioteca do LangChain para criar um agente. Em seguida, introduziremos o LangGraph para oferecer mais flexibilidade na orquestração desse agente.

Depois, veremos como trazer contexto externo — por exemplo, de um banco de dados — e fornecer esse contexto ao agente no momento adequado.

Introduzindo middlewares, guardrails e ciclo humano e explorando avaliações e monitoramento

Em seguida, a ideia é introduzirmos middlewares (intermediários de software), um conceito que nos habilita a implementar guardrails (trilhos de proteção) de forma mais eficiente e a incorporar human-in-the-loop (pessoa humana no ciclo).

Por fim, teremos uma aula bônus para entendermos mais sobre evals (avaliações), monitoramento e tracing (rastreamento).

Eu estou bastante animado com este curso e espero que você goste. Até já.

AI Engineering e LangChain - Entendendo LLMs, agentes e guardrails

Apresentando o vídeo e o ambiente

Olá, tudo bem?

Neste vídeo introdutório, nós vamos desenvolver o jargão, isto é, o vocabulário técnico, que vai nos acompanhar ao longo do nosso curso.

Estamos com o VS Code aberto e um Jupyter Notebook. Neste primeiro vídeo, vamos apresentar apenas texto e algumas imagens; o código começaremos a desenvolver a partir do próximo vídeo.

Definindo LLMs e estruturando aplicações com segurança

O primeiro tópico que vamos discutir é o que é uma LLM e o que é uma aplicação com LLM. Se você está familiarizado com interfaces conversacionais como o ChatGPT, já deve ter percebido que há muito mais do que apenas um modelo. Ele lembra mensagens anteriores, acessa a internet e executa várias tarefas. No entanto, o fundamental é o modelo — por exemplo, GPT-5, GPT-4 ou, no ecossistema da Anthropic, o Claude, com variantes como Opus e Sonnet. Esses são os modelos por trás da aplicação que envolve a experiência. Chamar um modelo consiste em fazer uma requisição: passamos uma pergunta e recebemos uma resposta.

Ao tratarmos de AI Engineering (engenharia de IA), precisamos nos preocupar com mais aspectos. Nesta ilustração, temos uma LLM no centro (um bloco que representa a LLM). Recebemos tanto a pergunta quanto as instruções que vão para esse modelo — isto é, como queremos que ele se comporte. Além disso, passamos as ferramentas disponíveis e o contexto. Falaremos mais sobre contexto em seguida. Com isso, conseguimos guiá-lo para trazer uma resposta mais precisa e alinhada ao que desejamos.

Basicamente, essa é a ideia de construir uma aplicação em torno de uma LLM. Buscamos estabelecer um mínimo de segurança, protegendo a LLM e seu comportamento. Como esse modelo recebe instruções, precisamos garantir que não haja abuso ou brechas de segurança na geração da saída, nem conteúdos indesejáveis na aplicação, como algo que prejudique a imagem da empresa ou que insulte a pessoa usuária. Para isso, criamos uma proteção em torno do modelo. Chamamos isso de guardrails (travas de segurança).

Explicando agentes e o ciclo ReAct

Você provavelmente já ouviu várias vezes a expressão “agente de IA”. Trata-se de uma abstração sobre a aplicação ou o modelo. Inserimos uma pergunta e/ou contexto, iniciamos o processo e a LLM decide se continua chamando uma ferramenta ou se encerra o processo. Enquanto decidir continuar, chamará novamente uma ou mais ferramentas, de acordo com a entrada e seu raciocínio. Neste diagrama, isso fica mais claro: a LLM decide agir (chamar uma ferramenta), executa a ferramenta, e o retorno alimenta o raciocínio do modelo. Quando decide que tem informação suficiente, encerra o processo e entrega a resposta. Essa é a ideia central.

O que são essas ferramentas no ciclo ReAct (Raciocínio e Ação)? Qualquer recurso que permitimos ao modelo usar para agir ou buscar mais informações no ambiente. Por exemplo, se estivermos no stack (pilha tecnológica) do Google, poderíamos solicitar que abrisse um e-mail, redigisse um e-mail, consultasse a agenda no Google Calendar ou recuperasse um documento no Google Drive. Fica mais clara, assim, a ideia de abstração: nosso agente será a interface que apresentaremos para a pessoa usuária, e ele poderá consultar diversas informações, com uma LLM realizando o raciocínio — como se fosse o “cérebro” do agente.

Listando conceitos de AI e engenharia de contexto

Na sequência, entramos em AI Engineering (engenharia de IA) e Context Engineering (engenharia de contexto). AI Engineering abrange a operacionalização do sistema: segurança, qualidade e observabilidade. Context Engineering trata de tudo o que passamos de contexto, no momento exato, para a LLM. Aqui estão alguns conceitos (jargões) que vamos desenvolver ao longo do curso:

Apresentando a família LangChain e seus componentes

Para isso tudo acontecer, vamos utilizar uma família de frameworks (estruturas de desenvolvimento). Poderíamos usar várias outras, mas neste curso decidimos escolher a família LangChain. No momento em que gravamos este curso, temos LangGraph, LangChain, DeepAgents e LangSmith. Não abordaremos todas extensivamente; vamos focar em LangGraph e LangChain, sabendo que o DeepAgents está acima dessas duas ferramentas e que o LangSmith não é open source (código aberto), portanto utilizaremos uma alternativa.

Vamos abrir a documentação do LangChain para esclarecer como esses componentes interagem. Estamos no Google Chrome, um browser (navegador). A documentação muda com o tempo, então não se prenda muito à disposição. Basicamente, logo no início, a recomendação é utilizar o DeepAgents, que já vem com recursos batteries-included (recursos prontos). Ele utiliza LangChain e LangGraph como um harness (infraestrutura de suporte), criando o ecossistema para que precisemos apenas desenvolver as instruções. O LangChain opera em um nível um pouco abaixo; mais abaixo ainda, o LangGraph cuida da execução do grafo. Veremos tudo isso ao longo do curso.

Se pudéssemos visualizar em camadas, seria aproximadamente assim: o DeepAgents é uma abstração de nível mais alto sobre o LangChain; no LangChain, desenvolvemos ferramentas e passamos as mensagens; o LangGraph executa em nível mais baixo. O LangSmith fornece observabilidade, mas ao longo do curso utilizaremos o LangFuse em vez do LangSmith.

Apresentando o plano da aula e o projeto

O que veremos especificamente nesta aula? Modelos, mensagens e prompts — como interagimos com esses modelos de linguagem. Veremos como criar agentes (uma abstração sobre LLMs). Na sequência, passaremos pelas ferramentas e apresentaremos o projeto que vamos desenvolver ao longo do curso. Nesta primeira versão (0.1), vamos criar uma abstração sobre LLM que será a nossa Clínica Alura, uma clínica fictícia.

Te vejo no próximo vídeo.

AI Engineering e LangChain - Configurando modelos, mensagens e prompts

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:

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
# temperature=0 deixa a resposta mais estável (bom para uma demo reprodutível)
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:

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:

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)
type(response)
print(response.content)

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.

response

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:

# 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
# Inicializa o chat model para modelos diferentes de forma unificada
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á.

Sobre o curso Assistentes conversacionais com LangChain: orquestração, segurança e monitoramento

O curso Assistentes conversacionais com LangChain: orquestração, segurança e monitoramento possui 205 minutos de vídeos, em um total de 52 atividades. Gostou? Conheça nossos outros cursos de Engenharia de LLMs & Agentes em Inteligência Artificial, ou leia nossos artigos de Inteligência Artificial.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Engenharia de LLMs & Agentes acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas