IA .NET C# LLM Claude GPT

Como integrar Claude, GPT e Gemini em aplicações .NET: guia prático com SDK

Guia técnico para integrar os principais LLMs (Claude, GPT-4, Gemini) em aplicações .NET — streaming, tool calling, prompt caching e gestão de custo.

N
Neryx Digital Architects
16 de maio de 2026
14 min de leitura
280 profissionais leram
Categoria: Arquitetura Público: Times de engenharia e produto Etapa: Aprendizado

A maioria dos times de desenvolvimento que conheço ainda usa LLMs da mesma forma que usa o Google: abrem o ChatGPT, colam o problema, copiam a resposta. Funciona para tarefas isoladas. Não funciona para integrar inteligência dentro de um produto.

Integrar um LLM de verdade — com streaming, tool calling, gestão de contexto e controle de custo — é uma habilidade técnica distinta. Este artigo cobre do zero ao avançado para times .NET que querem parar de usar IA como calculadora e começar a usá-la como componente de sistema.

Vamos cobrir os três principais provedores: Anthropic (Claude), OpenAI (GPT-4) e Google (Gemini) — com exemplos reais em C# para cada um.

Por que integrar LLMs via SDK, não via UI

Quando você integra um LLM diretamente na sua aplicação, você ganha:

  • Contexto do sistema: injete dados do usuário, histórico, preferências e regras de negócio no prompt automaticamente
  • Fluxo controlado: o LLM vira um componente como qualquer outro — com retry, timeout, fallback e observabilidade
  • Tool calling: o modelo pode chamar funções do seu sistema (buscar dados, executar ações) de forma estruturada
  • Custo gerenciável: você controla tokens por request, escolhe o modelo certo por tarefa e implementa cache

Comparativo rápido dos provedores

Provider Modelo top Ponto forte SDK .NET
Anthropic Claude Opus 4 Raciocínio complexo, contexto longo (200k tokens), seguir instruções precisas Pacote oficial Anthropic
OpenAI GPT-4o Ecossistema maduro, multimodal, fine-tuning disponível Pacote oficial OpenAI
Google Gemini 2.5 Pro Contexto 1M tokens, grounding com Google Search, custo menor em volume Google.Cloud.AIPlatform ou REST

Para a maioria dos projetos empresariais no Brasil, a recomendação é começar com Claude para tarefas de raciocínio e análise e GPT para geração de conteúdo e multimodal. Gemini faz sentido quando você já está na stack Google Cloud ou precisa do contexto de 1M tokens.

Setup — Instalando os SDKs

# Anthropic SDK
dotnet add package Anthropic

# OpenAI SDK oficial
dotnet add package OpenAI

# Para Azure OpenAI
dotnet add package Azure.AI.OpenAI

No appsettings.json:

{
  "LLM": {
    "AnthropicApiKey": "",
    "OpenAiApiKey": "",
    "GeminiApiKey": ""
  }
}

Sempre use variáveis de ambiente ou Azure Key Vault em produção — nunca coloque as chaves diretamente no appsettings.

Chamada básica — os três provedores

Claude (Anthropic SDK)

using Anthropic;

var client = new AnthropicClient(apiKey);

var response = await client.Messages.CreateAsync(new MessageCreateParams
{
    Model = "claude-opus-4-7",
    MaxTokens = 1024,
    Messages = [
        new() { Role = "user", Content = "Analise este contrato e liste as cláusulas de risco." }
    ]
});

var text = response.Content[0].Text;

GPT-4 (OpenAI SDK)

using OpenAI.Chat;

var client = new ChatClient("gpt-4o", apiKey);

var completion = await client.CompleteChatAsync(
    new UserChatMessage("Analise este contrato e liste as cláusulas de risco.")
);

var text = completion.Value.Content[0].Text;

Gemini (via REST com HttpClient)

var request = new
{
    contents = new[]
    {
        new { role = "user", parts = new[] { new { text = "Analise este contrato e liste as cláusulas de risco." } } }
    }
};

var response = await httpClient.PostAsJsonAsync(
    $"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={apiKey}",
    request
);

var result = await response.Content.ReadFromJsonAsync<GeminiResponse>();

Streaming de resposta

Para aplicações com interface de usuário, streaming é obrigatório. Usuários abandonam interfaces que ficam congeladas por 5-10 segundos esperando a resposta completa. Com streaming, o texto aparece progressivamente — a UX muda completamente.

Streaming com Claude

await foreach (var ev in client.Messages.CreateStreamingAsync(new MessageCreateParams
{
    Model = "claude-sonnet-4-6",
    MaxTokens = 2048,
    Messages = [new() { Role = "user", Content = prompt }]
}))
{
    if (ev is ContentBlockDeltaEvent delta && delta.Delta is TextDelta textDelta)
    {
        // Envie para o cliente via SignalR, SSE ou yield return
        await hubContext.Clients.User(userId).SendAsync("token", textDelta.Text);
    }
}

Streaming com OpenAI

await foreach (var update in client.CompleteChatStreamingAsync(messages))
{
    foreach (var part in update.ContentUpdate)
    {
        await hubContext.Clients.User(userId).SendAsync("token", part.Text);
    }
}

Para SSE (Server-Sent Events) em Minimal APIs, o padrão é retornar IAsyncEnumerable<string> com Content-Type: text/event-stream. Funciona muito bem com React no frontend usando a API EventSource.

Tool Calling — o recurso que muda tudo

Tool calling (ou function calling) é o que transforma um LLM em um agente. Em vez de apenas gerar texto, o modelo pode sinalizar que quer chamar uma função da sua aplicação — e você decide se executa ou não.

Casos de uso reais:

  • Chatbot de atendimento que consulta o banco de dados do cliente em tempo real
  • Assistente de análise que busca dados do ERP antes de gerar o relatório
  • Agente de onboarding que cria registros no sistema conforme o usuário responde

Exemplo com Claude

var tools = new List<Tool>
{
    new()
    {
        Name = "buscar_pedido",
        Description = "Busca informações de um pedido pelo número. Use quando o usuário perguntar sobre status de pedido.",
        InputSchema = new InputSchema
        {
            Type = "object",
            Properties = new Dictionary<string, PropertyDefinition>
            {
                ["numero_pedido"] = new() { Type = "string", Description = "Número do pedido (formato: PED-XXXXX)" }
            },
            Required = ["numero_pedido"]
        }
    }
};

var response = await client.Messages.CreateAsync(new MessageCreateParams
{
    Model = "claude-sonnet-4-6",
    MaxTokens = 1024,
    Tools = tools,
    Messages = [new() { Role = "user", Content = "Qual o status do meu pedido PED-00123?" }]
});

// Verificar se o modelo quer usar uma tool
if (response.StopReason == "tool_use")
{
    var toolUse = response.Content.OfType<ToolUseContent>().First();
    var numeroPedido = toolUse.Input["numero_pedido"].GetString();
    
    // Executar a função real
    var pedido = await pedidoService.BuscarAsync(numeroPedido);
    
    // Continuar a conversa com o resultado
    var followUp = await client.Messages.CreateAsync(new MessageCreateParams
    {
        Model = "claude-sonnet-4-6",
        MaxTokens = 1024,
        Tools = tools,
        Messages = [
            new() { Role = "user", Content = "Qual o status do meu pedido PED-00123?" },
            new() { Role = "assistant", Content = response.Content },
            new() { Role = "user", Content = new List<ContentBlock>
            {
                new ToolResultContent
                {
                    ToolUseId = toolUse.Id,
                    Content = JsonSerializer.Serialize(pedido)
                }
            }}
        ]
    });
}

Prompt Caching — reduzindo custo em até 90%

Se você tem um system prompt grande (instruções do sistema, documentos de contexto, exemplos few-shot), pode estar pagando por ele toda vez que faz uma chamada. O Claude suporta prompt caching nativo: a primeira chamada com um bloco marcado como cacheável é mais cara, mas as subsequentes custam 10% do preço normal.

var response = await client.Messages.CreateAsync(new MessageCreateParams
{
    Model = "claude-sonnet-4-6",
    MaxTokens = 1024,
    System = new List<SystemPrompt>
    {
        new()
        {
            Text = instrucoesSistema,      // 5.000 tokens de contexto
            CacheControl = new() { Type = "ephemeral" }  // Marcar para cache
        }
    },
    Messages = [new() { Role = "user", Content = perguntaDoUsuario }]
});

Em aplicações com muitas chamadas por usuário (chatbots, assistentes de suporte), o cache pode reduzir o custo de tokens em 60-90%. A latência da primeira chamada aumenta um pouco; das subsequentes, diminui.

A OpenAI tem prompt caching automático para conversas longas (sem marcação explícita). O Google tem caching explícito via API separada.

Saída estruturada — JSON mode

Para integrar LLMs com sistemas reais, você precisa de saída previsível. Pedir "retorne um JSON com os campos X, Y, Z" no prompt funciona, mas é frágil. O modo estruturado garante que o JSON sempre valide contra um schema.

Com OpenAI (modo mais robusto)

[Description("Análise de sentimento de texto")]
class SentimentoAnalise
{
    [JsonPropertyName("sentimento")]
    [Description("Sentimento: positivo, negativo ou neutro")]
    public string Sentimento { get; init; } = "";
    
    [JsonPropertyName("confianca")]
    [Description("Score de confiança de 0 a 1")]
    public float Confianca { get; init; }
    
    [JsonPropertyName("resumo")]
    public string Resumo { get; init; } = "";
}

var completion = await chatClient.CompleteChatAsync(
    messages,
    new ChatCompletionOptions
    {
        ResponseFormat = ChatResponseFormat.CreateJsonSchemaFormat(
            "sentimento_analise",
            BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(
                JsonSchemaExporter.GetJsonSchemaAsNode(typeof(SentimentoAnalise))
            ))
        )
    }
);

var analise = JsonSerializer.Deserialize<SentimentoAnalise>(completion.Value.Content[0].Text);

O Claude tem suporte a tool use para saída estruturada (declare uma tool que representa seu schema e instrua o modelo a "usar" essa tool). Mais verbose, mas muito confiável.

Gerenciamento de erros e retry

APIs de LLM falham. Rate limits, timeouts, erros 500 ocasionais — tudo isso vai acontecer em produção. Use Polly (ou Microsoft.Extensions.Resilience no .NET 8+) para criar pipelines de resiliência:

services.AddHttpClient<ILlmService, ClaudeLlmService>()
    .AddResilienceHandler("llm-pipeline", builder =>
    {
        builder.AddRetry(new HttpRetryStrategyOptions
        {
            MaxRetryAttempts = 3,
            Delay = TimeSpan.FromSeconds(2),
            BackoffType = DelayBackoffType.Exponential,
            ShouldHandle = args => args.Outcome switch
            {
                { Exception: HttpRequestException } => PredicateResult.True(),
                { Result.StatusCode: HttpStatusCode.TooManyRequests } => PredicateResult.True(),
                { Result.StatusCode: HttpStatusCode.ServiceUnavailable } => PredicateResult.True(),
                _ => PredicateResult.False()
            }
        });
        
        builder.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
        {
            FailureRatio = 0.5,
            SamplingDuration = TimeSpan.FromSeconds(30),
            MinimumThroughput = 10,
            BreakDuration = TimeSpan.FromSeconds(60)
        });
        
        builder.AddTimeout(TimeSpan.FromSeconds(30));
    });

Observabilidade — rastreando tokens e custo

Sem métricas, você vai receber uma fatura inesperada de API no fim do mês. Instrumente suas chamadas com OpenTelemetry:

public class InstrumentedLlmService : ILlmService
{
    private readonly ILlmService _inner;
    private readonly Meter _meter;
    private readonly Counter<long> _tokenCounter;
    private readonly Histogram<double> _latencyHistogram;

    public InstrumentedLlmService(ILlmService inner)
    {
        _inner = inner;
        _meter = new Meter("Neryx.LLM");
        _tokenCounter = _meter.CreateCounter<long>("llm.tokens.total", "tokens");
        _latencyHistogram = _meter.CreateHistogram<double>("llm.request.duration", "ms");
    }

    public async Task<LlmResponse> CompleteAsync(LlmRequest request)
    {
        var sw = Stopwatch.StartNew();
        try
        {
            var response = await _inner.CompleteAsync(request);
            _tokenCounter.Add(response.InputTokens, new("type", "input"), new("model", request.Model));
            _tokenCounter.Add(response.OutputTokens, new("type", "output"), new("model", request.Model));
            return response;
        }
        finally
        {
            _latencyHistogram.Record(sw.Elapsed.TotalMilliseconds, new("model", request.Model));
        }
    }
}

Com isso você consegue dashboards no Grafana mostrando tokens por usuário, custo estimado por feature, latência por modelo e taxa de erro — tudo que você precisa para tomar decisões de custo vs qualidade.

Quando usar qual modelo

A regra prática que eu uso:

  • Claude Sonnet — tarefas de produção do dia a dia: classificação, extração, sumarização, geração com instrução precisa. Melhor custo-benefício para volume
  • Claude Opus — raciocínio complexo, análise de documentos longos, decisões que precisam de profundidade. Use com parcimônia
  • GPT-4o — análise de imagem, integração com ferramentas do ecossistema OpenAI (Assistants, Files, Vision), fine-tuning
  • Gemini Flash — alto volume, baixo custo, quando a tarefa é simples e a integração com Google Workspace faz sentido
  • Claude Haiku / GPT-4o-mini — classificação, triagem, tarefas ultra-simples onde custo é crítico

Uma arquitetura comum em produção: Haiku para triagem inicial (classifica a intenção do usuário, ~0.001 USD por request), Sonnet para resposta principal (~0.01 USD), Opus apenas para casos escalados que precisam de análise profunda (~0.15 USD). O custo médio fica bem próximo do Haiku porque a maioria das requests para no primeiro modelo.

Próximos passos

Integração básica é a parte fácil. O que separa implementações amadoras de robustas em produção é:

  • Gestão de contexto de conversa: armazenar e recuperar histórico com window sliding para não explodir o context window
  • RAG (Retrieval-Augmented Generation): integrar vector search para o modelo consultar documentos específicos da empresa
  • Avaliação de outputs: definir como você vai medir se o LLM está respondendo bem — antes de ir pra produção
  • Guardrails: validação de input e output para evitar jailbreaks, informações confidenciais no contexto e alucinações em campos críticos

Se você está construindo algo com LLMs na stack .NET e quer uma revisão de arquitetura antes de escalar, é exatamente o tipo de projeto que trabalhamos na Neryx — da integração inicial ao pipeline de LLMOps em produção.

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.