Ollama: Integração com LangChain
LangChain é o maestro de orquestração de aplicações de IA — modelos são atores, Chains são roteiros, e Agentes são diretores.
📋 Pré-requisitos: Você deve dominar o seguinte primeiro
- Aula 5: Fundamentos da API REST
- Aula 7: Integração do Python SDK
1. O Que Você Vai Aprender
- Configuração de conexão LangChain + Ollama
- Prompt Template + Chain para engenharia de prompts encadeados
- Output Parser para saída estruturada
- Agente de chamada de ferramentas
- Agente de análise de métricas SaaS da Alice
2. Uma História Real de Uma CTO de Startup
(1) O Problema: Analisar Manualmente 50 Relatórios de Métricas Todo Mês
A empresa SaaS da Alice rastreia 50 métricas de negócio, exigindo análise manual de tendências e escrita de relatórios mensalmente. Um analista gasta 3 dias completando o trabalho, e sinais importantes de anomalia são frequentemente perdidos.
(2) A Solução: Agente LangChain para Análise Automatizada
Usando LangChain + Ollama para construir um Agente de IA que automaticamente lê dados de métricas, identifica anomalias e gera relatórios de análise:
from langchain_ollama import ChatOllama
llm = ChatOllama(model="qwen2.5", temperature=0.2)
result = llm.invoke("Analyze the 20% drop in MRR last month")
3. Conexão LangChain + Ollama
base_url do ChatOllama padrão é http://localhost:11434. Se o Ollama estiver implantado em outro host ou em um Contêiner Docker, você precisa definir explicitamente base_url="http://ollama:11434" ou o endereço correspondente.
(1) Instalação e Inicialização
pip install langchain langchain-ollama langchain-community
(2) Dois Componentes Centrais
| Componente | Classe | Propósito |
|---|---|---|
| Chat Model | ChatOllama | Geração de conversação |
| Embeddings | OllamaEmbeddings | Vetorização de texto |
flowchart TD
A[LangChain App] --> B[ChatOllama<br/>qwen2.5 / llama3.2]
A --> C[OllamaEmbeddings<br/>nomic-embed-text]
B --> D[Ollama Server<br/>localhost:11434]
C --> D
| Parâmetro ChatOllama | Tipo | Padrão | Descrição |
|---|---|---|---|
| model | str | — | Nome do Modelo |
| temperature | float | 0.8 | Aleatoriedade |
| base_url | str | http://localhost:11434 | Endereço do servidor |
| num_ctx | int | 2048 | Janela de contexto |
▶ Exemplo 1: Invocação Básica do ChatOllama
from langchain_ollama import ChatOllama
from langchain_core.messages import HumanMessage, SystemMessage
# Initialize ChatOllama
llm = ChatOllama(model="qwen2.5", temperature=0.3)
# Simple invocation
response = llm.invoke("What is RAG?")
print(response.content)
# With system message
response = llm.invoke([
SystemMessage(content="You are a data analyst. Be concise."),
HumanMessage(content="What causes MRR to drop?")
])
print(response.content)
Saída:
# Execution successful
4. Prompt Template + Chain
(1) Engenharia de Prompts Encadeados
Prompt Templates tornam prompts parametrizáveis, reutilizáveis e combináveis:
flowchart LR
A[Variáveis de Entrada] --> B[PromptTemplate]
B --> C[ChatOllama]
C --> D[Output Parser]
D --> E[Resultado Estruturado]
| Tipo de Chain | Descrição | Caso de Uso |
|---|---|---|
| LLMChain | Prompt → LLM → Saída | Raciocínio de passo único |
| SequentialChain | Múltiplas Chains em série | Análise em múltiplos passos |
| RouterChain | Rotear com base na entrada | Processamento por ramificação |
▶ Exemplo 2: Prompt Template + Chain
from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
llm = ChatOllama(model="qwen2.5", temperature=0.3)
# Define prompt template
prompt = ChatPromptTemplate.from_messages([
("system", "You are a {role}. Be concise and professional."),
("human", "{input}")
])
# Build chain: prompt -> llm -> parser
chain = prompt | llm | StrOutputParser()
# Invoke with variables
result = chain.invoke({
"role": "customer service agent for e-commerce",
"input": "Customer wants to cancel order #12345"
})
print(result)
Saída:
# Execution successful
5. Output Parser para Saída Estruturada
PydanticOutputParser. Use um modelo 8B+ com format="json", e adicione lógica de retry com try/except no código.
(1) Output Parsers Comuns
| Parser | Propósito | Formato de Saída |
|---|---|---|
| StrOutputParser | Texto simples | string |
| JsonOutputParser | Objeto JSON | dict |
| PydanticOutputParser | Modelo Pydantic | objeto tipado |
| CommaSeparatedListOutputParser | Lista separada por vírgulas | list[str] |
(2) Comparação de Saída Estruturada Pydantic
| Abordagem | Vantagens | Desvantagens |
|---|---|---|
| format=json | Simples | Sem validação de tipo |
| JsonOutputParser | Tem dicas de schema | Parsing pode falhar |
| PydanticOutputParser | Validação de tipo completa | Depende do modelo seguir o schema |
▶ Exemplo 3: Saída Estruturada com Pydantic
from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
# Define output schema
class CustomerIntent(BaseModel):
intent: str = Field(description="Customer intent category")
confidence: float = Field(description="Confidence score 0-1")
order_number: str | None = Field(description="Order number if mentioned")
# Create parser
parser = PydanticOutputParser(pydantic_object=CustomerIntent)
# Build chain with format instructions
llm = ChatOllama(model="qwen2.5", temperature=0.1, format="json")
prompt = ChatPromptTemplate.from_messages([
("system", "Classify the customer intent.\n{format_instructions}"),
("human", "{query}")
])
chain = prompt | llm | parser
# Invoke
result = chain.invoke({
"query": "I want to track my order #88765",
"format_instructions": parser.get_format_instructions()
})
print(f"Intent: {result.intent}")
print(f"Confidence: {result.confidence}")
print(f"Order: {result.order_number}")
Saída:
# Execution successful
6. Agente de Chamada de Ferramentas
max_iterations=3-5 para limitar a contagem de loops, e use temperature=0 para maior determinismo. Modelos 3B têm capacidades de Agente limitadas; 8B+ é recomendado.
@tool em vez de Function Calling nativo para melhor compatibilidade, e temperature=0 pode melhorar a determinismo de chamada de ferramentas.
(1) Arquitetura do Agente
flowchart TD
A[Entrada do Usuário] --> B[Agente]
B --> C{Precisa de Ferramenta?}
C -->|Sim| D[Chamar Ferramenta]
D --> E[Resultado da Ferramenta]
E --> B
C -->|Não| F[Resposta Final]
| Tipo de Agente | Descrição | Caso de Uso |
|---|---|---|
| ReAct Agent | Loop de raciocínio + ação | Tarefas complexas que exigem chamadas de ferramentas |
| Structured Chat | Chamada de ferramenta estruturada | Multi-ferramenta, multi-parâmetro |
| OpenAI Functions | Modo de chamada de função | Parcialmente suportado pelo Ollama |
(2) Padrões de Definição de Ferramenta
| Método de Definição | Descrição | Exemplo |
|---|---|---|
| @tool decorator | Ferramenta simples | @tool def func(...) |
| StructuredTool | Ferramenta com descrição | StructuredTool.from_function(...) |
| Pydantic + BaseTool | Ferramenta fortemente tipada | class MyTool(BaseTool) |
▶ Exemplo 4: Agente ReAct na Prática
from langchain_ollama import ChatOllama
from langchain_core.tools import tool
from langchain.agents import AgentExecutor, create_react_agent
from langchain_core.prompts import PromptTemplate
# Define tools
@tool
def get_order_status(order_id: str) -> str:
"""Get the status of an order by its ID."""
# Simulated database lookup
orders = {
"12345": "Shipped, arriving in 2 days",
"88765": "Processing, shipping tomorrow",
"99999": "Delivered on 2024-01-15"
}
return orders.get(order_id, f"Order {order_id} not found")
@tool
def check_product_stock(product_name: str) -> str:
"""Check if a product is in stock."""
stock = {
"wireless headphones": "In stock: 45 units",
"laptop stand": "Out of stock, restock in 3 days",
"usb cable": "In stock: 200 units"
}
return stock.get(product_name.lower(), f"Product '{product_name}' not found")
# Build agent
llm = ChatOllama(model="qwen2.5", temperature=0)
tools = [get_order_status, check_product_stock]
prompt = PromptTemplate.from_template(
"""Answer the following question. You have access to these tools:
{tools}
Use this format:
Question: the input question
Thought: what to do
Action: tool name (one of [{tool_names}])
Action Input: tool input
Observation: tool output
... (repeat Thought/Action/Action Input/Observation)
Thought: I know the final answer
Final Answer: the final answer
Question: {input}
{agent_scratchpad}"""
)
agent = create_react_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# Run agent
result = agent_executor.invoke({
"input": "What is the status of order 88765?"
})
print(result["output"])
Saída:
# Function defined successfully
7. Exemplo Abrangente: Agente de Análise de Métricas SaaS da Alice
AgentExecutor do LangChain padrão é max_iterations=15, mas modelos locais raciocinam mais lentamente. Definir para 3-5 previne loops infinitos. Também defina verbose=True para observar o processo de raciocínio do Agente.
# ============================================
# Comprehensive: SaaS metrics analysis agent
# Uses LangChain + Ollama for automated analysis
# ============================================
from langchain_ollama import ChatOllama
from langchain_core.tools import tool
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain.agents import AgentExecutor, create_react_agent
from langchain_core.prompts import PromptTemplate
import json
# Simulated metrics database
METRICS_DB = {
"mrr": {"current": 85000, "previous": 106250, "change": -20},
"churn_rate": {"current": 8.5, "previous": 5.2, "change": 63},
"arr": {"current": 1020000, "previous": 1275000, "change": -20},
"active_users": {"current": 12500, "previous": 11800, "change": 6},
"cac": {"current": 250, "previous": 180, "change": 39},
"ltv": {"current": 2400, "previous": 3200, "change": -25}
}
@tool
def get_metric(metric_name: str) -> str:
"""Get a SaaS metric value. Available: mrr, churn_rate, arr, active_users, cac, ltv."""
data = METRICS_DB.get(metric_name.lower())
if data:
return json.dumps(data)
return f"Metric '{metric_name}' not found. Available: {list(METRICS_DB.keys())}"
@tool
def compare_metrics(metric1: str, metric2: str) -> str:
"""Compare two metrics and identify correlations."""
m1 = METRICS_DB.get(metric1.lower())
m2 = METRICS_DB.get(metric2.lower())
if not m1 or not m2:
return "One or both metrics not found."
return json.dumps({
"metric1": {"name": metric1, **m1},
"metric2": {"name": metric2, **m2},
"correlation_note": "Both metrics show negative trends"
})
# Build analysis agent
llm = ChatOllama(model="qwen2.5", temperature=0.2)
tools = [get_metric, compare_metrics]
prompt = PromptTemplate.from_template(
"""You are a SaaS metrics analyst. Analyze data and provide insights.
Available tools: {tools}
Format:
Question: the input
Thought: what to do
Action: tool name from [{tool_names}]
Action Input: tool input
Observation: tool output
... (repeat as needed)
Thought: I have enough information
Final Answer: detailed analysis with actionable recommendations
Question: {input}
{agent_scratchpad}"""
)
agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True, max_iterations=5)
# Run analysis
if __name__ == "__main__":
result = executor.invoke({
"input": "MRR dropped 20% last month. Analyze the key metrics and identify likely causes with recommendations."
})
print("\n=== Analysis Report ===")
print(result["output"])
❓ Perguntas Frequentes
P: Qual é a diferença entre langchain-ollama e langchain-community? R:
langchain-ollamaé o pacote oficial de integração do Ollama, contendo ChatOllama e OllamaEmbeddings.langchain-communitytambém inclui integrações legadas, mas o pacote oficial é recomendado.
P: O que devo fazer se as chamadas de ferramenta do Agente forem instáveis? R: Reduza a temperature para 0, garanta que as descrições das ferramentas sejam claras, e use format=json para forçar saída estruturada. Modelos pequenos (3B) têm capacidades de Agente limitadas; 8B+ é recomendado.
P: Como escolher entre LangChain Chains e Agentes? R: Use Chains para fluxos de trabalho fixos (Prompt → LLM → Parser), e Agentes para tomada de decisão dinâmica (selecionando autonomamente ferramentas e passos). Chains são suficientes para 90% dos cenários.
P: O que devo fazer se o parsing do PydanticOutputParser falhar? R: Adicione
format="json"para forçar saída JSON. Adicione instruções de formato explícitas no prompt. Em caso de falha, recue para JsonOutputParser.
P: Modelos Ollama suportam Function Calling? R: Parcialmente. qwen2.5 e llama3.1 têm alguma capacidade de Function Calling, mas não é tão estável quanto o GPT-4. Agente ReAct + decorador @tool é recomendado como alternativa.
P: Como depurar o processo de raciocínio de um Agente? R: Defina
verbose=Truepara visualizar o loop Thought/Action/Observation do Agente. LangSmith (tracelangchain.com) pode visualizar a cadeia de execução completa.
📖 Resumo
- ChatOllama é a classe central para conectar LangChain ao Ollama, suportando conversação e saída streaming
- Prompt Templates tornam prompts parametrizáveis; Chains tornam fluxos de trabalho combináveis
- Output Parsers convertem texto LLM em dados estruturados (JSON/Pydantic/List)
- Agentes ReAct implementam um loop de raciocínio + ação que pode chamar ferramentas personalizadas
- Alice usou um Agente LangChain para automatizar a análise de métricas SaaS, reduzindo 3 dias de trabalho para 3 minutos
- Agentes requerem modelos 8B+ para estabilidade; modelos 3B carecem de capacidade de raciocínio suficiente
📝 Exercícios
- Básico (⭐): Construa uma Chain com ChatOllama + PromptTemplate para implementar classificação de intenção de atendimento ao cliente (saída em formato JSON).
- Intermediário (⭐⭐): Crie um Agente ReAct com 2 ferramentas (consultar pedido + verificar estoque), e teste chamada de ferramentas com 3 perguntas diferentes.
- Avançado (⭐⭐⭐): Construa um Agente de análise de métricas SaaS para Alice — incluindo pelo menos 4 ferramentas (obter métrica, comparar métricas, gerar descrição de gráfico, enviar alerta), produzindo um relatório de análise estruturado.