06-04-04 — Desafio: mesmo agente em LangGraph e Agent Framework 1.0
TL;DR
Implementamos o mesmo agente — busca clima → recomenda roupa — em LangGraph (Python) e Agent Framework 1.0 (C#). Resultado: LangGraph requer mais código explícito mas dá controle total sobre o grafo; AF 1.0 é mais declarativo e tem observabilidade integrada. Para times .NET, AF 1.0 vence na produtividade. Para Python-first com lógica complexa customizada, LangGraph vence no controle.
O cenário
Agente simples mas representativo:
- Usuário pergunta: "O que devo vestir hoje em São Paulo?"
- Agente consulta API de clima para SP
- Agente retorna recomendação de roupa baseada no clima
Este cenário testa: tool calling, loop de raciocínio, formatação de resposta e estado.
Implementação 1: LangGraph (Python)
## LANGGRAPH — Agente clima/roupa
## Linhas de código: ~70
## Arquivos: 1 (main.py)
from typing import TypedDict, Annotated, List
import operator
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import BaseMessage, HumanMessage, ToolMessage
from langchain_core.tools import tool
import httpx
# 1. FERRAMENTAS
@tool
def buscar_clima(cidade: str) -> str:
"""Busca temperatura e condição climática de uma cidade."""
# Usando wttr.in como API pública
resp = httpx.get(f"https://wttr.in/{cidade}?format=j1", timeout=10)
data = resp.json()
weather = data["current_condition"][0]
temp = weather["temp_C"]
descricao = weather["weatherDesc"][0]["value"]
return f"Temperatura: {temp}°C, Condição: {descricao}"
@tool
def recomendar_roupa(temperatura_c: int, condicao: str) -> str:
"""Recomenda roupa baseado na temperatura e condição."""
if temperatura_c < 15:
base = "casaco pesado, calça comprida, bota"
elif temperatura_c < 22:
base = "jaqueta leve, calça comprida, tênis"
elif temperatura_c < 28:
base = "camiseta, calça jeans ou saia, tênis"
else:
base = "camiseta leve, shorts ou saia, sandália"
if "rain" in condicao.lower() or "chuva" in condicao.lower():
base += ", guarda-chuva"
return f"Recomendação: {base}"
tools = [buscar_clima, recomendar_roupa]
tools_by_name = {t.name: t for t in tools}
# 2. MODELO
llm = ChatOpenAI(model="gpt-4o-mini").bind_tools(tools)
# 3. ESTADO
class AgentState(TypedDict):
messages: Annotated[List[BaseMessage], operator.add]
# 4. NÓS
def chamar_llm(state: AgentState) -> dict:
resposta = llm.invoke(state["messages"])
return {"messages": [resposta]}
def executar_ferramentas(state: AgentState) -> dict:
ultimo = state["messages"][-1]
resultados = []
for tc in ultimo.tool_calls:
resultado = tools_by_name[tc["name"]].invoke(tc["args"])
resultados.append(ToolMessage(content=str(resultado), tool_call_id=tc["id"]))
return {"messages": resultados}
def decidir_proximo(state: AgentState) -> str:
ultimo = state["messages"][-1]
if hasattr(ultimo, "tool_calls") and ultimo.tool_calls:
return "ferramentas"
return END
# 5. GRAFO
grafo = StateGraph(AgentState)
grafo.add_node("llm", chamar_llm)
grafo.add_node("ferramentas", executar_ferramentas)
grafo.set_entry_point("llm")
grafo.add_conditional_edges("llm", decidir_proximo, {"ferramentas": "ferramentas", END: END})
grafo.add_edge("ferramentas", "llm")
app = grafo.compile()
# 6. EXECUTAR
resultado = app.invoke({
"messages": [HumanMessage(content="O que devo vestir hoje em São Paulo?")]
})
print(resultado["messages"][-1].content)
Implementação 2: Agent Framework 1.0 C#
// AGENT FRAMEWORK 1.0 — Agente clima/roupa
// Linhas de código: ~55 (excluindo Program.cs boilerplate)
// Arquivos: 3 (Program.cs, ClimaPlugin.cs, appsettings.json)
// ClimaPlugin.cs
using Microsoft.SemanticKernel;
using System.ComponentModel;
public class ClimaPlugin
{
[KernelFunction("buscar_clima")]
[Description("Busca temperatura e condição climática de uma cidade")]
public async Task<string> BuscarClimaAsync(
[Description("Nome da cidade")] string cidade)
{
using var http = new HttpClient();
var json = await http.GetStringAsync($"https://wttr.in/{cidade}?format=j1");
var data = JsonDocument.Parse(json);
var cond = data.RootElement
.GetProperty("current_condition")[0];
var temp = cond.GetProperty("temp_C").GetString();
var desc = cond.GetProperty("weatherDesc")[0]
.GetProperty("value").GetString();
return $"Temperatura: {temp}°C, Condição: {desc}";
}
[KernelFunction("recomendar_roupa")]
[Description("Recomenda roupa baseado na temperatura e condição climática")]
public string RecomendarRoupa(
[Description("Temperatura em graus Celsius")] int temperaturaC,
[Description("Condição climática")] string condicao)
{
var base_ = temperaturaC switch
{
< 15 => "casaco pesado, calça comprida, bota",
< 22 => "jaqueta leve, calça comprida, tênis",
< 28 => "camiseta, calça jeans ou saia, tênis",
_ => "camiseta leve, shorts ou saia, sandália"
};
if (condicao.Contains("rain", StringComparison.OrdinalIgnoreCase))
base_ += ", guarda-chuva";
return $"Recomendação: {base_}";
}
}
// Program.cs
using Microsoft.AgentFramework;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAgentFramework(options =>
{
options.AddAzureOpenAI(
deploymentName: "gpt-4o-mini",
endpoint: builder.Configuration["AzureOpenAI:Endpoint"]!,
apiKey: builder.Configuration["AzureOpenAI:Key"]!);
options.AddPlugin<ClimaPlugin>();
});
var app = builder.Build();
var kernel = app.Services.GetRequiredService<Kernel>();
var settings = new AzureOpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
var resposta = await kernel.InvokePromptAsync(
"O que devo vestir hoje em São Paulo?",
new KernelArguments(settings));
Console.WriteLine(resposta);
await app.RunAsync();
Comparação lado a lado
| Dimensão | LangGraph (Python) | AF 1.0 (C#) |
|---|---|---|
| Linhas de código | ~70 | ~55 (excl. boilerplate) |
| Arquivos | 1 | 3 |
| Curva para time .NET | Alta (Python + LangChain) | Baixa (C# nativo) |
| Controle do fluxo | ✅ Total (grafo explícito) | ⚠️ Delegado ao tool calling |
| Adicionar nó condicional | 5 linhas | Novo plugin/função |
| Observabilidade | LangSmith (pago/cloud) | App Insights (Azure nativo) |
| Deploy em Azure | Container + App Service | App Service nativo / Foundry |
| Copilot Studio integration | ❌ Manual via API | ✅ Nativo |
| Human-in-the-loop | ✅ interrupt() nativo | ✅ Via workflow graph |
| Type safety | TypedDict (parcial) | ✅ C# nativo |
Diagrama de fluxo — o que acontece internamente
sequenceDiagram
participant U as Usuário
participant F as Framework
participant LLM as GPT-4o-mini
participant T1 as buscar_clima
participant T2 as recomendar_roupa
U->>F: "O que vestir em SP?"
F->>LLM: prompt + tools disponíveis
LLM-->>F: tool_call: buscar_clima("São Paulo")
F->>T1: executar
T1-->>F: "28°C, parcialmente nublado"
F->>LLM: resultado da tool
LLM-->>F: tool_call: recomendar_roupa(28, "parcialmente nublado")
F->>T2: executar
T2-->>F: "camiseta leve, shorts, sandália"
F->>LLM: resultado da tool
LLM-->>F: "Hoje em SP faz 28°C e está parcialmente nublado. Use camiseta leve, shorts e sandália."
F-->>U: resposta final
Quando cada abordagem vence
Use LangGraph quando: você precisa de controle explícito sobre cada step do grafo, lógica de retry customizada por nó, depuração visual do estado em cada passo, ou o time é Python-first e não quer aprender .NET.
Use AF 1.0 (C#) quando: o time já é .NET, a infra é Azure, você precisa integrar com Copilot Studio ou M365, ou precisa de SLA de suporte Microsoft para o framework.
Como isso se conecta
- 06-01-02 LangGraph — implementação detalhada do framework usado neste desafio
- 06-03-03 Agent Framework 1.0 — implementação detalhada do AF 1.0
- 06-04-03 Decision matrix — a matriz que fundamenta a escolha entre os dois
- Módulo 07 — MCP e A2A — próximo módulo: os protocolos de interoperabilidade que conectam agentes de diferentes frameworks