Muito se fala em Python quando o tema é Inteligência Artificial Generativa. No entanto, quando entramos no território de sistemas corporativos, tipagem estrita, alta performance, governança e microsserviços, o ecossistema .NET (C#) oferece uma base arquitetural sólida e madura.
Neste artigo, vamos analisar a engenharia por trás do DecisionFlow.cs, o núcleo inteligente do projeto Olist.Agents. Trata-se de um projeto acadêmico desenvolvido para a entrega do Tech Challenge da pós-graduação, modelado para demonstrar maturidade de produção e padrões arquiteturais reais com IA agêntica.
Você vai aprender:
- Como abstrair modelos de linguagem usando
Microsoft.Extensions.AI. - O que é um
AIAgente por que ele precisa de ferramentas (tools). -
Por que usamos
Executorsse já instanciamos os agentes. - Como o agente Cadu implementa RAG Estruturado sobre 100 mil registros reais.
- Como orquestrar um fluxo em grafo com bifurcações e supervisão humana (Human-in-the-Loop).
-
Por que usamos
RequestPortpara receber decisões humanas sem travar threads.
1. O Alicerce: Desacoplamento com Microsoft.Extensions.AI
Muitos desenvolvedores acoplam suas aplicações diretamente a SDKs proprietários. No .NET moderno, a Microsoft padronizou a interação com modelos através da interface unificada IChatClient.
No trecho inicial do DecisionFlow.cs, instanciamos o cliente do Google Gemini, mas o encapsulamos sob essa abstração:
IChatClient chatClient = new Client(
apiKey: _googleAIConfigs.ApiKey,
httpOptions: new HttpOptions
{
RetryOptions = new HttpRetryOptions { Attempts = 5, InitialDelay = 2, MaxDelay = 30 }
})
.AsIChatClient(_googleAIConfigs.Model);
Por que isso é importante?
- Desacoplamento de Provedor: O fluxo de trabalho não se importa se o motor de inferência é o Gemini, Azure OpenAI, Claude ou um modelo local rodando via Ollama.
-
Resiliência Nativa: O
HttpRetryOptionstrata erros de rede transitórios, códigos503(sobrecarga) e429(limites de requisição por minuto) com backoff exponencial configurado.
2. Ferramentas e Protocolo Aberto: Model Context Protocol (MCP)
Antes de instanciar os agentes, o código inicializa um cliente Model Context Protocol (MCP):
await using McpClient learnMcpClient = await McpClient
.CreateAsync(new HttpClientTransport(new HttpClientTransportOptions
{
Endpoint = new Uri(_mcpServerConfig.McpServerUrl),
Name = "Microsoft Learn MCP",
}));
IList<AITool> researcherTools = [.. (await learnMcpClient.ListToolsAsync()).Cast<AITool>()];
O MCP padroniza como modelos de IA descobrem e acionam ferramentas externas via JSON-RPC/HTTP. No .NET, essas ferramentas são convertidas diretamente para instâncias de AITool, ficando disponíveis para consumo agêntico sem SDKs intermediários.
3. O que é um AIAgent e Como Criá-lo
Um AIAgent no Microsoft Agent Framework combina três pilares:
- Identidade / Nome: Como ele é identificado no fluxo.
- Instruções (System Prompt): Regras de negócio e limites de atuação.
- Ferramentas (Tools): Métodos que o modelo pode acionar sob demanda via function calling.
AIAgent cadu = chatClient
.AsAIAgent(
name: "Cadu",
instructions: AgentHelper.ReadablePromptAgent("Agents/Prompts/Cadu.md"),
tools: billing ? _sqlConsultTool.GetCaduTools() : []
);
Boas Práticas: Os prompts não ficam hardcoded em classes C#. Eles residem em arquivos
.mdexternos, permitindo versionamento, refinamento e manutenção sem necessidade de recompilar a aplicação.
4. Por que Usar Executors se Já Temos os AIAgent?
Uma dúvida frequente de quem está iniciando no framework é: "Se eu já criei a instância do AIAgent, por que preciso de classes como SantiExecutor, LeoExecutor ou CaduExecutor?"
A resposta está no Princípio da Responsabilidade Única (SRP) e na arquitetura interna de workflows em grafo:
-
AIAgent(O Cérebro / Raciocínio): Cuida exclusivamente da cognição com a LLM — interpreta o prompt, executa ferramentas e devolve texto ou JSON. Ele não conhece o grafo, não sabe quem o antecedeu e não sabe para onde enviar o resultado. -
Executor(O Nó Operacional do Grafo): É o nó de infraestrutura que herda do framework (comoExecutor<TInput, TOutput>). Suas responsabilidades são:- Adaptação de Contratos: Recebe os dados de entrada do nó anterior e formata o contexto para o agente.
-
Chamada e Desserialização: Executa o
AIAgente converte a resposta em texto/JSON para um objeto C# fortemente tipado (SantiTriagem,LeoTriagem,CaduTriagem). -
Regras de Negócio Determinísticas: Se o modelo falhar, alucinar ou retornar dados incompletos, o
Executoraplica validações defensivas e fallbacks antes de propagar o estado. -
Telemetria: Emite o
ExecutorInvokedEvent, permitindo que o painel de controle acompanhe a execução em tempo real.
SantiExecutor santiExecutor = new(santi);
LeoExecutor leoExecutor = new(leo);
CaduExecutor caduExecutor = new(cadu);
5. RAG no Mundo Real: Como o Cadu Audita o Catálogo
Muitos tutoriais resumem RAG (Retrieval-Augmented Generation) a dividir arquivos PDF em blocos (chunks) e buscar similaridade vetorial. No mercado corporativo, RAG também opera sobre dados estruturados de missão crítica.
O Cenário do Cadu (Catalog Guard)
O Cadu audita inconsistências em anúncios de produtos sobre o dataset público da Olist (mais de 100 mil registros de pedidos e produtos).
- O Desafio: Como um modelo de linguagem pode validar se as dimensões, o peso ou o histórico de um anúncio são coerentes sem estourar o limite de tokens da janela de contexto?
-
A Solução (RAG Estruturado / Text-to-SQL in-process):
- O nó do Cadu recebe a identificação do anúncio (
ProductId). - O agente decide acionar a sua ferramenta:
_sqlConsultTool.GetCaduTools(). - A ferramenta executa consultas analíticas via DuckDB + Dapper diretamente sobre os arquivos
.csvlocais (tabelasproducts,order_items,sellers), sem a complexidade de subir um banco de dados relacional dedicado. - Os dados factuais retornam da base (Retrieval) e são incorporados ao contexto do Cadu.
- O agente sintetiza a sua avaliação determinando se o anúncio está conforme (Augmented Generation).
- O nó do Cadu recebe a identificação do anúncio (
Essa abordagem garante custo reduzido, zero alucinação com dados numéricos e altíssima velocidade de auditoria.
6. O Grafo de Decisão (WorkflowBuilder)
Para conectar todos os nós de forma auditável e paralela, utilizamos o WorkflowBuilder:
Workflow workflow = new WorkflowBuilder(orchestrator)
.WithName("OlistDecisionFlow")
.AddFanOutEdge(orchestrator, [santiExecutor, leoExecutor, caduExecutor])
.AddSwitch(santiExecutor, s => s
.AddCase<SantiTriagem>(t => t?.Gravidade == Gravidade.Moderada, [santiAuto])
.WithDefault([analistaCX]))
.AddSwitch(leoExecutor, s => s
.AddCase<LeoTriagem>(t => t?.Risco == RiscoAtraso.Baixo, [leoAuto])
.WithDefault([analistaLogistica]))
.AddSwitch(caduExecutor, s => s
.AddCase<CaduTriagem>(t => t?.Parecer == ParecerCatalogo.Conforme, [caduAuto])
.WithDefault([analistaCatalogo]))
.AddEdge(analistaCX, humanGate)
.AddEdge(analistaLogistica, humanGate)
.AddEdge(analistaCatalogo, humanGate)
.WithOutputFrom(santiAuto, leoAuto, caduAuto, humanGate)
.Build();
Entendendo a Topologia:
-
orchestrator: Consulta a base DuckDB e extrai os casos pendentes de triagem. -
AddFanOutEdge: Dispara a análise em paralelo para os três agentes especialistas (Santi, Léo e Cadu). -
AddSwitch: Bifurcação condicional com tipagem estrita do C#. Se o parecer do Cadu forConforme, ele desvia paracaduAuto(ação automática). Se houver inconformidade, segue para a porta do analista.
flowchart TD
Orquestrador --> Santi & Leo[Léo] & Cadu
Santi -- moderada --> SantiAuto[Ação automática]
Santi -- grave --> AnalistaCX
Leo -- risco baixo --> LeoAuto[Ação automática]
Leo -- risco alto --> AnalistaLogistica
Cadu -- conforme --> CaduAuto[Ação automática]
Cadu -- não conforme --> AnalistaCatalogo
AnalistaCX & AnalistaLogistica & AnalistaCatalogo --> HumanGate[Decisão humana]
7. Por que Usar RequestPort se o Agente Já Devolve um Objeto C#?
Quando o agente conclui a inferência, ele devolve um objeto tipado (como CaduTriagem). Então, por que declarar um RequestPort?
RequestPort<CaduTriagem, bool> analistaCatalogo = RequestPort.Create<CaduTriagem, bool>("AnalistaCatalogo");
Se tentássemos recolher o input humano com um simples Console.ReadLine() dentro da execução de um nó, bloquearíamos a thread do runtime, impedindo o paralelismo do AddFanOutEdge e travando a fila de streaming de eventos.
O RequestPort<TRequest, TResponse> implementa o padrão Human-in-the-Loop assíncrono:
- Pausa Controlada de Estado: Ao receber o caso não conforme, o ramo específico do workflow é suspenso.
-
Emissão de Evento Desacoplada: O workflow emite um
RequestInfoEventcontendo o payloadCaduTriagem. - Recolha Não Bloqueante: A interface de apresentação (neste projeto, um terminal visual com Spectre.Console) exibe os detalhes para o operador humano.
-
Retomada com
SendResponseAsync: Assim que o analista confirma a sua decisão (trueoufalse), o valor é enviado de volta e o workflow retoma a sua execução imediatamente:
case RequestInfoEvent requestEvt when requestEvt.Request.TryGetDataAs(out CaduTriagem caduTriagem):
bool decisao = dashboard.AskAnalyst(caduTriagem);
await run.SendResponseAsync(requestEvt.Request.CreateResponse(decisao));
break;
8. Execução Orientada a Eventos
A inicialização e o consumo do grafo acontecem em modo streaming através do método InProcessExecution.RunStreamingAsync:
await using StreamingRun run = await InProcessExecution.RunStreamingAsync(workflow, scanRequest);
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
switch (evt)
{
case ExecutorInvokedEvent invokedEvt:
// Notifica o início de processamento no dashboard
break;
case WorkflowOutputEvent { Data: CaduTriagem caduAutomatico }:
dashboard.AutoResolved("Cadu", caduAutomatico.ProductId, "o anúncio foi publicado na loja");
break;
case ExecutorFailedEvent failedEvt:
dashboard.Error($"Falha no executor '{failedEvt.ExecutorId}': {failedEvt.Data}");
break;
}
dashboard.Render();
}
Essa estrutura transforma a aplicação num sistema reativo, auditável e resiliente, onde a interface de usuário permanece desacoplada da mecânica do grafo.
Conclusão
Projetos de conclusão de curso e provas de conceito ganham muito em relevância quando combinam fundamentação teórica com padrões de engenharia de software corporativa. O ecossistema .NET entrega tipagem rigorosa, ciclo de vida com injeção de dependência e arquitetura agêntica estruturada com o Microsoft Agent Framework.
Para conferir a implementação completa, incluindo prompts, diagramas e configuração de ambiente, acesse o repositório:
🔗 Repositório no GitHub: https://github.com/marcosvnl/Workflow-MAF-DOTNET
Como você tem estruturado sistemas de IA agêntica em .NET? Compartilhe suas experiências e dúvidas nos comentários!
Top comments (0)