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!
