|
Hoje veremos como constrir um pipeline unindo nuvem e inferência local com Microsoft Agent Framework, Ollama, OpenAI e Tool Calling usando C# e os recursos do Microsoft.Extensions.AI e MAF. |

IChatClient (Microsoft.Extensions.AI): Interface padronizada que abstrai o provedor de linguagem
(OpenAI, Azure OpenAI, Ollama, Anthropic ou modelos locais). Permite trocar ou intercalar motores de inferência via injeção de dependência sem alterar uma única linha da lógica de negócio.record ou
class), eliminando parses frágeis por expressões regulares e reduzindo falhas sintáticas. O próprio Microsoft Agent Framework oferece isso nativamente através de
AIAgent.RunAsync<T>, sem exigir instrução manual de formato no prompt nem desserialização manual.EspecificacaoProduto) via saída estruturada nativa do agente.
dotnet new console -n DemoAgenteHibrido
cd DemoAgenteHibrido
|
# Abstração de IA oficial da Microsoft
dotnet add package Microsoft.Extensions.AI
dotnet add package Microsoft.Extensions.AI.OpenAI
# Framework de Agentes (pacote estável, não requer --prerelease)
dotnet add package Microsoft.Agents.AI
# Provedor do Ollama para .NET
dotnet add package OllamaSharp
# Configuração e Injeção de Dependência
dotnet add package Microsoft.Extensions.Hosting
dotnet add package Microsoft.Extensions.DependencyInjection
|
Microsoft.Extensions.Configuration.UserSecrets já vem
transitivamente com o
Microsoft.Extensions.Hosting, que o projeto já referencia e não
precisa ser instalado.
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net11.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.Agents.AI" Version="1.21.0" /> <PackageReference Include="Microsoft.Extensions.AI" Version="10.10.0" /> <PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="10.10.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="11.0.0-rc.1.26425.128" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="11.0.0-rc.1.26425.128" /> <PackageReference Include="OllamaSharp" Version="5.4.30" /> </ItemGroup> </Project> |
ollama run llama3
|
llama3.2 ou modelos mais recentes para melhor suporte a instruções
e menor taxa de alucinações.
namespace DemoAgenteHibrido.Dominio;
// Modelo fortemente tipado para a saída do primeiro agente
public record EspecificacaoProduto(
string Nome,
string Conectividade,
string Carregamento,
string DuracaoBateria,
bool EmEstoque
);
using System.ComponentModel; namespace DemoAgenteHibrido.Servicos; // Serviço local que será exposto ao Agente como uma Tool public class ServicoEstoque { private static readonly Dictionary<string, bool> BancoDadosEstoque = new(StringComparer.OrdinalIgnoreCase) { ["Teclado Sem Fio Pro"] = true, ["Mouse Ergonômico USB"] = false, ["Monitor UltraWide 29"] = true }; [Description("Consulta a disponibilidade do produto no estoque da empresa pelo nome.")] public bool VerificarDisponibilidade( [Description("Nome exato ou aproximado do produto")] string nomeProduto) { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($" [FERRAMENTA EXECUTADA] Consultando estoque para: '{nomeProduto}'"); Console.ResetColor(); // Simulação de consulta ao banco de dados return BancoDadosEstoque.TryGetValue(nomeProduto, out var emEstoque) && emEstoque; } } |
using System.ClientModel;
using System.ComponentModel;
using DemoAgenteHibrido.Dominio;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OllamaSharp;
using OpenAI;
var builder = Host.CreateApplicationBuilder(args);
// 1. Registro de Serviços e Ferramentas
builder.Services.AddSingleton<ServicoEstoque>();
// 2. Configuração do Cliente em Nuvem (OpenAI / gpt-4o-mini)
string chaveOpenAi =
Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? "sua-chave-aqui";
IChatClient clienteOpenAi = new OpenAIClient(new ApiKeyCredential(chaveOpenAi))
.GetChatClient("gpt-4o-mini")
.AsIChatClient();
// 3. Configuração do Cliente Local (Ollama / Llama 3)
IChatClient clienteOllama =
new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.2");
var app = builder.Build();
var servicoEstoque = app.Services.GetRequiredService<ServicoEstoque>();
Console.WriteLine("=== Pipeline Híbrido: MAF + OpenAI + Ollama ===\n");
// 4. Criação do Agente Analista (Nuvem) com suporte a Tool Calling
var ferramentaEstoque = AIFunctionFactory.Create(servicoEstoque.VerificarDisponibilidade);
var agenteAnalista = new ChatClientAgent(
clienteOpenAi,
name: "AnalistaDeCatalogo",
instructions: """
Você é um analista de produtos rigoroso.
Sua função é identificar os detalhes técnicos do produto informado e checar a
disponibilidade usando a ferramenta de estoque antes de responder.
""",
tools: [ferramentaEstoque]
);
// 5. Criação do Agente Redator (Local)
var agenteRedator = new ChatClientAgent(
clienteOllama,
name: "RedatorMarketing",
instructions: """
Você é um redator de e-commerce experiente.
Você receberá os dados estruturados de um produto. Redija uma descrição atraente
de 2 a 3 frases. Se o item estiver em estoque, convide o cliente a comprar
imediatamente. Caso contrário, informe que o produto está indisponível para
entrega imediata.
"""
);
// 6. Execução do Cenário
string notaBruta =
"Cadastrar Teclado Sem Fio Pro. Conexão via 2.4GHz e Bluetooth, carregamento " +
"USB-C rápido e bateria que dura 1 ano.";
Console.WriteLine($"[1] Entrada Bruta:\n\"{notaBruta}\"\n");
// Etapa A: Extração Estruturada (Structured Output nativo) e Execução de Ferramenta
Console.WriteLine("[2] Executando Agente Analista (OpenAI Cloud)...");
AgentResponse<EspecificacaoProduto> respostaAnalista =
await agenteAnalista.RunAsync<EspecificacaoProduto>(notaBruta);
EspecificacaoProduto? especificacao = respostaAnalista.Result;
if (especificacao is null)
{
Console.WriteLine("Falha ao gerar especificações estruturadas.");
return;
}
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("\n[3] Objeto C# Estruturado com Sucesso:");
Console.WriteLine($" Produto: {especificacao.Nome}");
Console.WriteLine($" Conectividade: {especificacao.Conectividade}");
Console.WriteLine($" Carregamento: {especificacao.Carregamento}");
Console.WriteLine($" Bateria: {especificacao.DuracaoBateria}");
Console.WriteLine($" Em Estoque: {(especificacao.EmEstoque ? "Sim" : "Não")}");
Console.ResetColor();
// Etapa B: Redação Criativa no Modelo Local
Console.WriteLine("\n[4] Executando Agente Redator (Ollama Local)...");
string promptParaRedacao = $"""
Produto: {especificacao.Nome}
Conexão: {especificacao.Conectividade}
Recarga: {especificacao.Carregamento}
Duração Bateria: {especificacao.DuracaoBateria}
Disponível em Estoque: {(especificacao.EmEstoque ? "Sim" : "Não")}
""";
var respostaRedator = await agenteRedator.RunAsync(promptParaRedacao);
Console.WriteLine("\n[5] Descrição de Marketing Gerada Localmente:\n");
Console.WriteLine(respostaRedator.Text);
|
.csproj
um <UserSecretsId> (um GUID) que
vincula o projeto a um arquivo secrets.json
guardado fora da pasta do projeto (em
%APPDATA%\Microsoft\UserSecrets\<id>\secrets.json
no Windows, ou
~/.microsoft/usersecrets/<id>/secrets.json no Linux/macOS) — por
isso ele nunca é versionado por acidente.
// 2. Configuração do Cliente em Nuvem (OpenAI / gpt-4o-mini) string chaveOpenAi = builder.Configuration["OpenAI:ApiKey"] ?? throw new InvalidOperationException( "A chave da API da OpenAI não foi configurada."); IChatClient clienteOpenAi = new OpenAIClient(new ApiKeyCredential(chaveOpenAi)) .GetChatClient("gpt-4o-mini") .AsIChatClient(); |
|
{ "profiles": { "demogentehibrido": { "commandName": "Project", "environmentVariables": { "DOTNET_ENVIRONMENT": "Development" } } } } |

| Critério | Abordagem Proposta |
|---|---|
| Integração C# | Abstração padronizada via IChatClient |
| Integridade dos Dados | Saída estruturada nativa do agente (RunAsync<T>), tipada em
record C# |
| Acesso a Dados | Tool Calling real com reflexão via AIFunctionFactory |
| Custo e Privacidade | Nuvem usada estritamente onde o raciocínio é crítico; geração volumosa feita localmente |
|
Observação - O arquivo
Para otimizar o código de acordo com as boas práticas de arquitetura em .NET, uma alternativa poderia ser:
|
E estamos conversados..![]()
Pegue o projeto aqui: DemoAgenteHibrido.zip e o programa ajustado : DemoAgenteHibrido_ajustado.zip
"Bem sei que tudo podes, e nenhum dos teus planos pode ser frustrado."
Jó 42:2
Referências: