Integrando Gemini com Semantic Kernel e Microsoft.Extensions.AI no .NET
Resolvendo tool calling e conectando o SDK oficial do Google
Quando fui integrar os modelos do Google Gemini no Semantic Kernel usando os endpoints de compatibilidade da OpenAI, tive problemas com streaming truncado e falhas de serialização nos schemas do Gemini. Os endpoints do Google compatíveis com OpenAI ainda estão em beta, então isso era esperado.
Para resolver isso, mudei para o SDK oficial Google.GenAI e conectei ao Semantic Kernel através do Microsoft.Extensions.AI (IChatClient).
O IChatCompletionService continua existindo no Semantic Kernel, mas agora o framework tem adaptadores bidirecionais para o IChatClient (.AsChatClient() e .AsChatCompletionService()). Isso permitiu usar o client oficial do Google com middleware unificado.
A Estrutura da Solução no .NET
No DependencyInjection.cs da camada de Infrastructure, inicializei o cliente oficial Google.GenAI.Client, converti para IChatClient, acoplei o middleware de invocação de funções e registrei de volta como IChatCompletionService para o Semantic Kernel:
// Infrastructure: Inicializa o SDK oficial do Google
var genAiClient = new Google.GenAI.Client(apiKey: activeProfile.ApiKey);
builder.Services.AddKeyedSingleton<IChatCompletionService>("chatAgent", (sp, key) =>
{
// 1. Converte o client do Google para o padrão IChatClient do Microsoft.Extensions.AI
var baseChatClient = Microsoft.Extensions.AI.GoogleGenAIExtensions
.AsIChatClient(genAiClient, activeProfile.ChatModel);
// 2. Acopla o middleware do Semantic Kernel para resolução de ferramentas
var chatClient = new Microsoft.Extensions.AI.ChatClientBuilder(baseChatClient)
.UseKernelFunctionInvocation()
.Build(sp);
// 3. Adapta de volta para a interface nativa do Semantic Kernel com injeção de dependência
return Microsoft.SemanticKernel.ChatCompletion.ChatClientExtensions
.AsChatCompletionService(chatClient, sp);
});
O Ponto de Atenção: Invocação de Tools e OpenTelemetry
Durante os testes de integração, me deparei com um cenário curioso: a primeira mensagem era processada com sucesso (200 OK), mas na segunda mensagem, quando o modelo chamava uma ferramenta de consulta no banco, o fluxo parava sem lançar exceções.
Investigando os spans do OpenTelemetry no dashboard do .NET Aspire:
{
"name": "POST",
"url.full": "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:streamGenerateContent?*",
"http.response.status_code": 200
}
O endpoint retornava 200, mas a execução não prosseguia.
O motivo estava no middleware de invocação:
UseFunctionInvocation()vsUseKernelFunctionInvocation(): O pacoteMicrosoft.Extensions.AIpuro expõeUseFunctionInvocation(). Mas quando você usa o Semantic Kernel com plugins registrados no container (IServiceProvider), o método correto é oUseKernelFunctionInvocation(). Ele garante que o pipeline receba oIServiceProvider(sp) para resolver e instanciar osKernelPluginem tempo de execução.- Resolução de Spans e Logs: Registre o exporter do OpenTelemetry explicitamente no
Program.cspara capturar diagnósticos internos e limites de cota retornados pelo SDK do Google (comoGoogle.GenAI.ServerError: high demand).
Conclusão
Essa mudança simplificou a integração:
- Dá para usar o SDK oficial do provedor sem depender de conectores de terceiros.
- O Semantic Kernel continua funcionando como orquestrador através dos adaptadores de
IChatClient. - Trocar ou combinar modelos no mesmo backend virou apenas uma questão de injeção de dependência.