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.