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:

  1. Como abstrair modelos de linguagem usando Microsoft.Extensions.AI.
  2. O que é um AIAgent e por que ele precisa de ferramentas (tools).
  3. Por que usamos Executors se já instanciamos os agentes.
  4. Como o agente Cadu implementa RAG Estruturado sobre 100 mil registros reais.
  5. Como orquestrar um fluxo em grafo com bifurcações e supervisão humana (Human-in-the-Loop).
  6. Por que usamos RequestPort para 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 HttpRetryOptions trata erros de rede transitórios, códigos 503 (sobrecarga) e 429 (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:

  1. Identidade / Nome: Como ele é identificado no fluxo.
  2. Instruções (System Prompt): Regras de negócio e limites de atuação.
  3. 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 .md externos, 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 (como Executor<TInput, TOutput>). Suas responsabilidades são:
    1. Adaptação de Contratos: Recebe os dados de entrada do nó anterior e formata o contexto para o agente.
    2. Chamada e Desserialização: Executa o AIAgent e converte a resposta em texto/JSON para um objeto C# fortemente tipado (SantiTriagem, LeoTriagem, CaduTriagem).
    3. Regras de Negócio Determinísticas: Se o modelo falhar, alucinar ou retornar dados incompletos, o Executor aplica validações defensivas e fallbacks antes de propagar o estado.
    4. 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):
    1. O nó do Cadu recebe a identificação do anúncio (ProductId).
    2. O agente decide acionar a sua ferramenta: _sqlConsultTool.GetCaduTools().
    3. A ferramenta executa consultas analíticas via DuckDB + Dapper diretamente sobre os arquivos .csv locais (tabelas products, order_items, sellers), sem a complexidade de subir um banco de dados relacional dedicado.
    4. Os dados factuais retornam da base (Retrieval) e são incorporados ao contexto do Cadu.
    5. O agente sintetiza a sua avaliação determinando se o anúncio está conforme (Augmented Generation).

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 for Conforme, ele desvia para caduAuto (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:

  1. Pausa Controlada de Estado: Ao receber o caso não conforme, o ramo específico do workflow é suspenso.
  2. Emissão de Evento Desacoplada: O workflow emite um RequestInfoEvent contendo o payload CaduTriagem.
  3. Recolha Não Bloqueante: A interface de apresentação (neste projeto, um terminal visual com Spectre.Console) exibe os detalhes para o operador humano.
  4. Retomada com SendResponseAsync: Assim que o analista confirma a sua decisão (true ou false), 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!