teqo/docs/plans/sollinha-no-deepinfra-impl.md
2026-08-09 00:06:12 -03:00

8.2 KiB
Raw Blame History

Impl: B174 — Sollinha no Deep Infra (LLM) — mesma API e chave dos agentes

Status: aprovado Atualizado em: 2026-08-08 Issue: #449 Intenção: docs/plans/sollinha-no-deepinfra.md Appetite restante: ~0,5 dia (herdado) Impeccable: A — sem UI

Leitura da intenção

  • Outcome: A Sollinha (endpoint /campanha/api/ai-chat) passa a ser servida pela Deep Infra no caminho OpenAI-compatível, usando o mesmo modelo (DeepSeek-V4-Flash), as mesmas tools e a mesma chave que os agentes do time já usam — um provedor, uma chave, uma fatura. Sem mudança visível para o assessor.
  • O que NÃO negociar: modelo permanece deepseek-v4-flash (equivalente Deep Infra deepseek-ai/DeepSeek-V4-Flash); tools/sistema da Sollinha intactos; nenhuma UI; rollback barato e documentado (reverter baseURL/chave volta ao estado atual); env/e2e com endpoint mockado continua sem chave real no CI.
  • O que reavaliar: a hipótese de "área provável = route.ts + env DEEPINFRA_API_KEY" está correta; o desenho exato do provider (pacote oficial @ai-sdk/deepinfra vs. baseURL OpenAI-compatível crua) é a decisão de engenharia aqui.

Abordagem recomendada

flowchart LR
  subgraph client [Chat UI]
    useChat
  end
  subgraph route [route.ts]
    streamText
    deepInfra
  end
  subgraph dl [Deep Infra]
    openai[API OpenAI-compatível]
    model[deepseek-ai/DeepSeek-V4-Flash]
  end
  subgraph tools [Tools Sollinha]
    buildAITools
  end
  useChat --> route --> deepInfra --> openai --> model
  streamText --> tools

Opções consideradas:

  • A — @ai-sdk/deepinfra (provider oficial do AI SDK p/ Deep Infra; versão 3.0.20 que está na mesma "ferroviária" que o ai@7.0.47 instalado — @ai-sdk/provider@4.0.4 + @ai-sdk/provider-utils@5.0.18). Troca de 2 linhas: deepSeek('deepseek-v4-flash') → deepInfra('deepseek-ai/DeepSeek-V4-Flash'). O provider lê DEEPINFRA_API_KEY do env sozinho (source confirma environmentVariableName: "DEEPINFRA_API_KEY"). Seu default baseURL é https://api.deepinfra.com/v1 + /openai/... = exatamente o endpoint OpenAI-compatível que a documentação da Deep Infra prescreve.
  • B — @ai-sdk/openai-compatible cru com baseURL: 'https://api.deepinfra.com/v1/openai' manual + loadApiKey próprio. Funciona, mas reimplementa à mão o que o pacote A já encapsula (nome do provider, env var, headers/user-agent, schema de erro).
  • C — continuar com @ai-sdk/deepseek e só sobrescrever baseURL p/ a Deep Infra. Hacky: provider com nome/contrato "DeepSeek oficial" apontando para outra API; model naming e tratamento de reasoning divergem.

Recomendação: A — porque é o provider first-party do AI SDK para Deep Infra (mesma linha de código que o deepSeek já usa), é a menor superfície de diff (2 linhas no route.ts + env), e a Deep Infra serve V4-Flash no schema OpenAI-compatível que esse provider já serializa. C só funcionaria "por acaso"; B é parafuso de ouro onde A resolve com um martelinho.

Rejeitadas: B (encapsulado por A, sem ganho); C (mal-nomeado, fragilidade de contrato desnecessária).

Componentes / mudanças

  • src/app/(campaign)/campanha/api/ai-chat/route.ts: import { deepSeek } from '@ai-sdk/deepseek' → import { deepInfra } from '@ai-sdk/deepinfra'; model: deepSeek('deepseek-v4-flash') → model: deepInfra('deepseek-ai/DeepSeek-V4-Flash'). Nada mais muda: system, messages, tools: buildAITools, stopWhen: stepCountIs(10), rate limit, auth, streaming ficam idênticos.
  • package.json: remove @ai-sdk/deepseek; adiciona @ai-sdk/deepinfra@3.0.20 (pino explícito — ^3.0.20 flutuaria para 3.0.27 que puxa @ai-sdk/provider@4.0.7/provider-utils@5.0.25, criando uma SEGUNDA cópia de provider ao lado do ai@7.0.47 que permanece em 4.0.4/5.0.18; pinar na mesma ferroviária evita isso — conferir com pnpm why @ai-sdk/provider após instalar).
    • @ai-sdk/deepseek vira dependência morta → knip (CI-blocking) exige a remoção; rollback é git revert do commit + re-set DEEPSEEK_API_KEY, documentado abaixo.
  • .env.example: substitui o bloco DEEPSEEK_API_KEY pelo bloco DEEPINFRA_API_KEY (com URL de geração da chave na Deep Infra). Nome DEEPINFRA_API_KEY é o que o provider A lê automaticamente E o mesmo que B173 (STT/Whisper na Deep Infra) reusa.
  • Migration: sem migration (nenhum schema/collection).
  • Access / Consent: intocados (auth/session do chat e RBAC das tools já existem; provider não muda async no RBAC).
  • UI: Impeccable A — nenhuma mudança.
  • Docs: atalho no docs/plans/sollinha-no-deepinfra-impl.md (este) + registro curto no docs/CHANGELOG-AGENTS.md.

Dados → forma

N/A — sem apresentação de dados nova. (O "smoke das tools" abaixo é uma checagem de runtime, não uma forma de dados.)

Fases verificáveis

  1. Dependência — pnpm add -E @ai-sdk/deepinfra@3.0.20 + pnpm remove @ai-sdk/deepseek; conferir pnpm why @ai-sdk/provider (uma cópia 4.0.4, sem 4.0.7 duplicado).
  2. Code — swap de 2 linhas no route.ts.
  3. Env — .env.example troca DEEPSEEK_API_KEY → DEEPINFRA_API_KEY.
  4. Gates — pnpm gate:fast (lint + typecheck + unit) na iteração; entrega via pnpm push (inclui gate:ci: format:check + knip + madge).

Smoke (deploy-time; passo de runbook no merge, não no CI)

Sollinha já está em produção e o routing de tools é o músculo dela. Antes/logo após o flip:

  1. Garantir DEEPINFRA_API_KEY na env de produção da Vercel (mesma chave usada pelos agentes; remover/ignorar a DEEPSEEK_API_KEY antiga — deixá-la definida não quebra, mas o objetivo é uma chave só).
  2. Pós-deploy, disparar pelo chat real 2–3 perguntas que forcem tool calling de dados ("Quantos votos tivemos em Ilhéus em 2022?", "Quem é o deputado mais votado em Feira?", "Quais dobradinhas temos em Salvador?") e conferir resposta com dados reais (não alucinação margem).
  3. Se qualquer tool falhar → rollback: git revert o commit do provider (restaura @ai-sdk/deepseek + baseURL oficial) OU simplesmente re-apontar a baseURL/chave antiga; redeploy. A documentação acima é o "rollback barato e documentado" do aceite.

Rabbit holes / Não escopo (engenharia)

  • "Aproveita e sobe o ai/providers para as últimas versões" — explodiria o diff e o risco de compatibilidade com ai@7.0.47/react em uso; 3.0.20 alinha a ferroviária atual. Reavaliar provider bump isoladamente se/quando o ai subir.
  • Teste unitário do provider — sem ganho para um chore de infra: o endpoint (route) não tem unit test hoje, e o e2e mocka o SSE. O guard de regressão real é o e2e mockado (não requer chave) + o smoke de deploy.
  • Guard de código ("model id fixo") — over-engineering para um chore; o diff de 2 linhas é o próprio contrato.

Riscos e mitigação

  • Tool calling se comporta diferente via Deep Infra (V4-Flash no schema OpenAI-compatível) → é exatamente o que o smoke cobre; se falhar, rollback de 1 commit. A Deep Infra documenta tool calling no endpoint OpenAI-compatível; o streamText do AI SDK abstrai as SSE.
  • Versão de provider duplicada (3.0.27 puxando provider 4.0.7) → mitigado pinando @ai-sdk/deepinfra@3.0.20 exact; verificar com pnpm why.
  • Chave errada/ausente em prod → provider falha com 401 no request (não no boot); o e2e mockado e a UI continuam funcionando (erro aparece por stream como hoje). Smoke pega isso na primeira pergunta.

Aceite de engenharia

  • Aceite de produto da intenção ainda coberto (mesmo modelo, mesmas tools, sem UI, rollback documentado, e2e mockado sem chave no CI)
  • Invariantes AGENTS/engineering-standards (dead code morre: @ai-sdk/deepseek removido; knip verde)
  • Testes de domínio previstos (unit/int) onde access/write paths mudam — nenhum path de access/write muda (chore de provider); e2e existente (campaignAiChatResize, campaignSollinhaWidth) segue passando sem chave real, cobrindo o contrato.

Self-score decision-quality: 5/5 — decisões caras (escolha de provider) têm rejeitadas; cabe no appetite; rabbit holes nomeados; depth check (não cria módulo novo além do swap; reusa o existing route); aceite de produto intacto.