mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-15 10:43:07 +00:00
96 lines
8.2 KiB
Markdown
96 lines
8.2 KiB
Markdown
# 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
|
||
|
||
```mermaid
|
||
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
|
||
|
||
- [x] Aceite de produto da intenção ainda coberto (mesmo modelo, mesmas tools, sem UI, rollback documentado, e2e mockado sem chave no CI)
|
||
- [x] 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.
|