.NET IA Semantic Kernel C#

Agentes de IA em .NET com Microsoft Semantic Kernel: orquestração de tarefas complexas

Como usar o Semantic Kernel para criar agentes de IA em C#: plugins, planner, memória vetorial, multi-agente e integração com Azure OpenAI e Claude.

N
Neryx Digital Architects
5 de junho de 2026
13 min de leitura
240 profissionais leram
Categoria: Arquitetura Público: Times de engenharia e produto Etapa: Aprendizado

Quando times .NET precisam integrar IA generativa nas suas aplicações, a primeira reação é chamar a SDK do OpenAI diretamente — o que funciona para casos simples. Mas conforme a complexidade cresce — múltiplas ferramentas, memória persistente, orquestração de fluxos, troca de modelo — o código artesanal se torna difícil de manter.

O Microsoft Semantic Kernel existe para resolver exatamente isso. Não é só um wrapper de API. É um framework de orquestração que padroniza como você define ferramentas, gerencia contexto, planeja sequências de ações e integra diferentes modelos — tudo dentro do ecossistema .NET que você já conhece.

O que é Semantic Kernel — e o que não é

Semantic Kernel (SK) é um SDK open source da Microsoft para construir aplicações com IA. Ele funciona como uma camada de orquestração entre o seu código .NET e os LLMs — abstraindo a comunicação com o modelo e fornecendo primitivas de alto nível para construir agentes.

O que SK não é:

  • Não é apenas um wrapper de OpenAI — suporta Azure OpenAI, Anthropic Claude, Google Gemini, modelos locais via Ollama
  • Não é um framework de machine learning — não treina modelos
  • Não é um substituto direto para o LangChain — é concorrente, com foco em C# e integração com o ecossistema Microsoft
  • Não é opinionado sobre arquitetura — você integra no padrão que já usa (Clean Architecture, Minimal APIs, Worker Services)

O que SK fornece de concreto:

  • Plugins: funções nativas C# ou prompts semânticos que o agente pode invocar
  • Kernel: núcleo que conecta o modelo de IA com os plugins disponíveis
  • Memory: abstração para armazenamento vetorial com busca semântica
  • Agents: primitiva de alto nível para criar agentes com loop de raciocínio automático
  • Filters: interceptadores para logging, segurança e validação de chamadas

Setup: instalando e configurando o Kernel

# Pacotes NuGet necessários
dotnet add package Microsoft.SemanticKernel
dotnet add package Microsoft.SemanticKernel.Connectors.OpenAI
dotnet add package Microsoft.SemanticKernel.Connectors.AzureOpenAI
dotnet add package Microsoft.SemanticKernel.Connectors.Postgres
dotnet add package Microsoft.SemanticKernel.Agents.Core
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;

// Configuração com Azure OpenAI
var builder = Kernel.CreateBuilder();

builder.AddAzureOpenAIChatCompletion(
    deploymentName: "gpt-4o",
    endpoint: configuration["AzureOpenAI:Endpoint"]!,
    apiKey: configuration["AzureOpenAI:ApiKey"]!
);

// Ou com Anthropic Claude (via endpoint compatível com OpenAI)
builder.AddOpenAIChatCompletion(
    modelId: "claude-3-5-sonnet-20241022",
    apiKey: configuration["Anthropic:ApiKey"]!,
    endpoint: new Uri("https://api.anthropic.com/v1/")
);

var kernel = builder.Build();

Plugins: funções nativas C# que o agente pode invocar

Plugins são o mecanismo pelo qual você expõe capacidades ao agente. O LLM decide quais plugins invocar e com quais argumentos — o SK executa as funções reais no seu código e retorna os resultados ao modelo.

using Microsoft.SemanticKernel;
using System.ComponentModel;

public sealed class TicketPlugin
{
    private readonly ITicketRepository _repository;
    private readonly IEmailService _emailService;

    public TicketPlugin(ITicketRepository repository, IEmailService emailService)
    {
        _repository = repository;
        _emailService = emailService;
    }

    [KernelFunction("buscar_ticket")]
    [Description("Busca um ticket de suporte pelo ID e retorna seus detalhes completos.")]
    public async Task<string> BuscarTicketAsync(
        [Description("ID numérico do ticket, ex: 12345")] int ticketId)
    {
        var ticket = await _repository.GetByIdAsync(ticketId);
        if (ticket is null) return $"Ticket #{ticketId} não encontrado.";

        return $"""
            Ticket #{ticket.Id}
            Status: {ticket.Status}
            Prioridade: {ticket.Priority}
            Criado em: {ticket.CreatedAt:dd/MM/yyyy HH:mm}
            Cliente: {ticket.CustomerEmail}
            Descrição: {ticket.Description}
            Última atualização: {ticket.LastUpdate}
            """;
    }

    [KernelFunction("atualizar_status")]
    [Description("Atualiza o status de um ticket. Status válidos: ABERTO, EM_ANDAMENTO, AGUARDANDO_CLIENTE, RESOLVIDO.")]
    public async Task<string> AtualizarStatusAsync(
        [Description("ID do ticket")] int ticketId,
        [Description("Novo status — deve ser um dos valores válidos")] string novoStatus,
        [Description("Comentário interno explicando a mudança")] string comentario)
    {
        var validos = new[] { "ABERTO", "EM_ANDAMENTO", "AGUARDANDO_CLIENTE", "RESOLVIDO" };
        if (!validos.Contains(novoStatus.ToUpper()))
            return $"Status inválido: '{novoStatus}'. Use: {string.Join(", ", validos)}";

        await _repository.UpdateStatusAsync(ticketId, novoStatus.ToUpper(), comentario);
        return $"Ticket #{ticketId} atualizado para {novoStatus.ToUpper()}.";
    }

    [KernelFunction("enviar_resposta_cliente")]
    [Description("Envia uma resposta por e-mail ao cliente do ticket.")]
    public async Task<string> EnviarRespostaClienteAsync(
        [Description("ID do ticket")] int ticketId,
        [Description("Corpo completo da resposta a ser enviada")] string resposta)
    {
        var ticket = await _repository.GetByIdAsync(ticketId);
        if (ticket is null) return $"Ticket #{ticketId} não encontrado.";

        await _emailService.SendAsync(
            to: ticket.CustomerEmail,
            subject: $"Re: Ticket #{ticketId} — {ticket.Subject}",
            body: resposta
        );

        return $"Resposta enviada para {ticket.CustomerEmail}.";
    }
}

Plugin semântico: prompt parametrizado como função reutilizável

// Plugin semântico definido inline (bom para prototipagem)
var resumirTicketFn = kernel.CreateFunctionFromPrompt(
    promptTemplate: """
        Resuma o ticket abaixo em uma linha para uso interno.
        Inclua: tipo do problema, urgência percebida, ação necessária.

        Conteúdo do ticket:
        {{$ticket_content}}

        Resumo:
        """,
    functionName: "ResumirTicket",
    description: "Gera resumo interno de ticket para triagem"
);

kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions(
    "Resumo", [resumirTicketFn]
));

// Registrar plugins nativos com DI
kernel.Plugins.AddFromType<TicketPlugin>("Suporte");

Function calling automático

// Configurar auto function calling — o modelo decide quando e como invocar cada plugin
var executionSettings = new OpenAIPromptExecutionSettings
{
    ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions,
    MaxTokens = 1024,
    Temperature = 0.1,
};

var result = await kernel.InvokePromptAsync(
    promptTemplate: "O cliente do ticket #{{$ticketId}} está reclamando de lentidão. Verifique os detalhes e me dê uma recomendação objetiva.",
    arguments: new KernelArguments(executionSettings) { ["ticketId"] = 12345 }
);

Console.WriteLine(result);
// O SK executou automaticamente TicketPlugin.BuscarTicketAsync(12345)
// e incluiu o resultado no contexto antes de gerar a resposta final

Memory: armazenamento vetorial para contexto persistente

A memória do SK permite armazenar informações fora da janela de contexto e recuperá-las por similaridade semântica — fundamental para agentes que precisam acessar bases de conhecimento, histórico de conversas ou documentação técnica volumosa.

using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Connectors.Postgres;

// Configurar memória com pgvector (PostgreSQL)
var memory = new MemoryBuilder()
    .WithAzureOpenAITextEmbeddingGeneration(
        deploymentName: "text-embedding-3-small",
        endpoint: configuration["AzureOpenAI:Endpoint"]!,
        apiKey: configuration["AzureOpenAI:ApiKey"]!
    )
    .WithPostgresMemoryStore(
        connectionString: configuration["Postgres:ConnectionString"]!,
        vectorSize: 1536,  // dimensões do text-embedding-3-small
        schema: "kb"
    )
    .Build();

// Indexar artigos da base de conhecimento
await memory.SaveInformationAsync(
    collection: "kb_suporte",
    id: "art-2fa-001",
    text: "Como configurar autenticação de dois fatores: acesse Configurações > Segurança > 2FA...",
    additionalMetadata: """{"categoria":"autenticacao","ultima_revisao":"2026-01-15"}"""
);

await memory.SaveInformationAsync(
    collection: "kb_suporte",
    id: "art-senha-001",
    text: "Recuperação de senha: clique em 'Esqueci minha senha' na tela de login...",
    additionalMetadata: """{"categoria":"autenticacao","ultima_revisao":"2026-02-10"}"""
);

// Busca semântica — o agente usa isso para recuperar contexto relevante
var resultados = memory.SearchAsync(
    collection: "kb_suporte",
    query: "usuário não consegue fazer login, esqueceu a senha",
    limit: 3,
    minRelevanceScore: 0.75
);

await foreach (var item in resultados)
{
    Console.WriteLine($"Relevância: {item.Relevance:F2} | {item.Metadata.Text[..80]}...");
}

Agents: o loop ReAct como primitiva de alto nível

O framework de Agents do SK abstrai o loop ReAct (Reason + Act) — o agente raciocina sobre o objetivo, decide quais plugins invocar, observa os resultados e itera até completar a tarefa ou atingir o limite de iterações:

using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;

var agente = new ChatCompletionAgent
{
    Name = "AgenteSuporteNeryx",
    Instructions = """
        Você é um agente de suporte técnico especializado em sistemas de software.

        Para cada solicitação:
        1. Busque o ticket referenciado para entender o problema
        2. Se necessário, consulte a base de conhecimento por artigos relevantes
        3. Proponha uma ação clara: atualizar status, enviar resposta, ou escalar
        4. Execute as ações aprovadas e confirme os resultados

        Seja objetivo. Não invente informações. Se dados forem insuficientes, peça clareza.
        """,
    Kernel = kernel,
    ExecutionSettings = new OpenAIPromptExecutionSettings
    {
        ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions,
    },
};

// Iniciar conversa com o agente
var thread = new ChatHistory();
thread.AddUserMessage(
    "O cliente do ticket #7821 mandou um e-mail reclamando de demora. " +
    "Preciso de uma resposta profissional para ele hoje."
);

// Executar e coletar respostas do agente (pode invocar múltiplos plugins em sequência)
await foreach (var mensagem in agente.InvokeAsync(thread))
{
    Console.WriteLine($"[{mensagem.Role}] {mensagem.Content}\n---");
}

Orquestração multi-agente com AgentGroupChat

Para tarefas que exigem múltiplas especialidades, você compõe agentes especializados em um grupo. O orquestrador delega, os especialistas executam, e o resultado é consolidado:

// Agente especialista: analisa o problema técnico
var agenteDiagnostico = new ChatCompletionAgent
{
    Name = "DiagnosticoTecnico",
    Instructions = """
        Você analisa problemas técnicos e produz diagnóstico estruturado.
        Foque em: causa raiz provável, impacto no usuário, passos de reprodução.
        Responda sempre em JSON: { "causaRaiz": "", "impacto": "", "acaoRecomendada": "" }
        """,
    Kernel = kernel,
};

// Agente especialista: redige comunicações com o cliente
var agenteComunicacao = new ChatCompletionAgent
{
    Name = "RedacaoCliente",
    Instructions = """
        Você transforma diagnósticos técnicos em respostas claras e empáticas para clientes.
        Tom: profissional, sem jargão, focado em solução e prazo realista.
        Nunca prometa prazos sem confirmação da equipe técnica.
        """,
    Kernel = kernel,
};

// Chat em grupo com terminação automática após N iterações
var groupChat = new AgentGroupChat(agenteDiagnostico, agenteComunicacao)
{
    ExecutionSettings = new AgentGroupChatSettings
    {
        TerminationStrategy = new DefaultTerminationStrategy { MaximumIterations = 6 },
    },
};

groupChat.AddChatMessage(new ChatMessageContent(
    AuthorRole.User,
    "Ticket #9001: erro de banco de dados afetando 50 clientes simultâneos. " +
    "Preciso do diagnóstico e de um rascunho de resposta ao cliente."
));

await foreach (var msg in groupChat.InvokeAsync())
{
    Console.WriteLine($"\n[{msg.AuthorName ?? msg.Role.ToString()}]");
    Console.WriteLine(msg.Content);
}

Filters: observabilidade e segurança sem modificar plugins

using Microsoft.SemanticKernel;
using System.Diagnostics;

// Filter de logging e rastreamento de custo
public sealed class ObservabilityFilter : IFunctionInvocationFilter
{
    private readonly ILogger<ObservabilityFilter> _logger;

    public ObservabilityFilter(ILogger<ObservabilityFilter> logger)
        => _logger = logger;

    public async Task OnFunctionInvocationAsync(
        FunctionInvocationContext context,
        Func<FunctionInvocationContext, Task> next)
    {
        var sw = Stopwatch.StartNew();
        var funcName = $"{context.Function.PluginName}.{context.Function.Name}";

        _logger.LogInformation("Invocando {Function} com: {@Args}",
            funcName, context.Arguments);

        try
        {
            await next(context);
            _logger.LogInformation("{Function} concluído em {Elapsed}ms",
                funcName, sw.ElapsedMilliseconds);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Falha em {Function} após {Elapsed}ms",
                funcName, sw.ElapsedMilliseconds);
            throw;
        }
    }
}

// Registrar no container de DI — o SK aplica automaticamente
builder.Services.AddSingleton<IFunctionInvocationFilter, ObservabilityFilter>();

Exemplo completo: agente de suporte como Web API

// Program.cs — configuração completa integrada com ASP.NET Core
var builder = WebApplication.CreateBuilder(args);

// Kernel com Azure OpenAI
builder.Services.AddKernel()
    .AddAzureOpenAIChatCompletion(
        deploymentName: builder.Configuration["AzureOpenAI:Deployment"]!,
        endpoint: builder.Configuration["AzureOpenAI:Endpoint"]!,
        apiKey: builder.Configuration["AzureOpenAI:ApiKey"]!
    );

// Plugins e filtros registrados no container
builder.Services.AddScoped<TicketPlugin>();
builder.Services.AddSingleton<IFunctionInvocationFilter, ObservabilityFilter>();

// Memória vetorial como singleton
builder.Services.AddSingleton<ISemanticTextMemory>(_ =>
    new MemoryBuilder()
        .WithAzureOpenAITextEmbeddingGeneration(
            deploymentName: "text-embedding-3-small",
            endpoint: builder.Configuration["AzureOpenAI:Endpoint"]!,
            apiKey: builder.Configuration["AzureOpenAI:ApiKey"]!
        )
        .WithPostgresMemoryStore(
            connectionString: builder.Configuration["Postgres:ConnectionString"]!,
            vectorSize: 1536,
            schema: "kb"
        )
        .Build()
);

var app = builder.Build();

app.MapPost("/api/tickets/{id:int}/processar", async (
    int id,
    Kernel kernel,
    TicketPlugin ticketPlugin,
    ISemanticTextMemory memory) =>
{
    kernel.Plugins.AddFromObject(ticketPlugin, "Suporte");

    // Plugin de KB usando a memória vetorial
    kernel.Plugins.AddFromFunctions("KnowledgeBase",
    [
        KernelFunctionFactory.CreateFromMethod(
            async (string query) =>
            {
                var sb = new System.Text.StringBuilder();
                await foreach (var r in memory.SearchAsync("kb_suporte", query, limit: 3, minRelevanceScore: 0.75))
                    sb.AppendLine($"[{r.Relevance:F2}] {r.Metadata.Text}");
                return sb.Length > 0 ? sb.ToString() : "Nenhum artigo relevante encontrado.";
            },
            functionName: "buscar_artigos",
            description: "Busca artigos relevantes na base de conhecimento interna pelo problema descrito"
        ),
    ]);

    var agente = new ChatCompletionAgent
    {
        Name = "SuporteNeryx",
        Instructions = $"""
            Processe o ticket #{id}:
            1. Busque os detalhes do ticket
            2. Busque artigos relevantes na base de conhecimento usando palavras-chave do problema
            3. Redija uma resposta profissional ao cliente baseada nos artigos encontrados
            4. Atualize o status do ticket para EM_ANDAMENTO
            5. Envie a resposta ao cliente
            """,
        Kernel = kernel,
        ExecutionSettings = new OpenAIPromptExecutionSettings
        {
            ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions,
        },
    };

    var thread = new ChatHistory();
    thread.AddUserMessage($"Processe o ticket #{id}.");

    var log = new System.Text.StringBuilder();
    await foreach (var msg in agente.InvokeAsync(thread))
        log.AppendLine($"[{msg.Role}] {msg.Content}");

    return Results.Ok(new { ticketId = id, log = log.ToString() });
});

app.Run();

Semantic Kernel vs LangChain: quando usar cada um

Critério Semantic Kernel (.NET/C#) LangChain (Python)
Ecossistema principal .NET, C#, Azure Python, FastAPI, Jupyter
Integração Azure Nativa — Azure OpenAI, AI Search, CosmosDB Requer libs extras
Tipagem Forte — C# type system, IntelliSense completo Dinâmica — erros em runtime
Performance Alta — runtime .NET compilado Moderada — interpretado
Suporte a providers Crescendo; foco em OpenAI/Azure/Claude Amplo — mais providers e integrações
Maturidade do ecossistema Estável mas mais novo; API ainda evoluindo Mais maduro; mais casos documentados
Ideal para Times .NET, APIs enterprise, sistemas corporativos existentes Data scientists, prototipagem, ML pipelines

A decisão é direta: se seu time é .NET e sua infraestrutura é Azure, Semantic Kernel é a escolha natural. Você ganha tipagem forte, integração nativa com o ecossistema Microsoft e não precisa introduzir Python em uma stack que não tem.

Conclusão

O Semantic Kernel resolve o problema real de times .NET que querem construir agentes de IA com a mesma qualidade de engenharia que aplicam no resto do sistema — sem abandonar o ecossistema que dominam.

Plugins tipados com atributos descritivos, memória vetorial com pgvector, agents com loop automático, multi-agente para tarefas complexas e filters para observabilidade: o SK fornece todas as peças. Você conecta com sua arquitetura existente.

Na Neryx, construímos sistemas com IA generativa integrados a stacks .NET existentes — do desenho da arquitetura ao deploy em produção. Se você está avaliando como incorporar agentes de IA ao seu sistema, entre em contato.

Precisa desenhar a próxima fase com menos retrabalho?

Fazemos discovery técnico para mapear riscos, arquitetura-alvo e sequência de execução antes de investir pesado.

Solicitar Discovery

Newsletter

Receba artigos como este no seu e-mail

Conteúdo técnico sobre arquitetura de software, .NET, IA e gestão de produto. Sem spam.