mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 23:53:05 +00:00
Merge pull request #465 from fsolla/B174-sollinha-no-deep-infra-llm-mesma-api-e-chave-dos-agente
B174 — Sollinha no Deep Infra (LLM) — mesma API e chave dos agentes
This commit is contained in:
commit
c9f42bfc70
6 changed files with 123 additions and 12 deletions
|
|
@ -36,6 +36,6 @@ CAMPAIGN_EMAIL_FROM_NAME=Campanha Jorge Solla
|
|||
# Note: the automated test suite ignores this file and reads .env.test instead,
|
||||
# and only ever runs against a database whose name ends with "_test".
|
||||
|
||||
# DeepSeek API key for the AI chat feature (Sollinha).
|
||||
# Get one at https://platform.deepseek.com/api_keys
|
||||
DEEPSEEK_API_KEY=YOUR_DEEPSEEK_API_KEY_HERE
|
||||
# Deep Infra API key for the AI chat feature (Sollinha) — same provider/key the
|
||||
# agents use (one provider, one key, one invoice). Get one at https://deepinfra.com
|
||||
DEEPINFRA_API_KEY=YOUR_DEEPINFRA_API_KEY_HERE
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente
|
|||
|
||||
---
|
||||
|
||||
**Recently resolved (2026-08-08):** **B174** (Issue #449) — a Sollinha (`/campanha/api/ai-chat`) trocou o provider de `@ai-sdk/deepseek` (API oficial `api.deepseek.com`) pelo **`@ai-sdk/deepinfra`** no caminho OpenAI-compatível da Deep Infra — **mesmo modelo** (`deepseek-ai/DeepSeek-V4-Flash`), **mesmas tools** e **mesma chave** que os agentes do time usam (um provedor, uma chave, uma fatura). Swap de 2 linhas no `route.ts`; dep `@ai-sdk/deepinfra@3.0.20` pinada na mesma ferroviária do `ai@7.0.47` (provider 4.0.4 / provider-utils 5.0.18, `pnpm why` confirma cópia única — nada de 4.0.7 duplicado), `@ai-sdk/deepseek` removido (dead dep, knip); `.env.example` `DEEPSEEK_API_KEY` → `DEEPINFRA_API_KEY` (B173 STT/Whisper reusa a mesma chave). Sem migration, access, UI ou teste novo — e2e já mocka o SSE (sem chave real no CI); rollback documentado = `git revert` (restaura provider/baseURL antiga); smoke de deploy = 2–3 perguntas forçando tool calling real antes de confiar o flip. Intenção [`sollinha-no-deepinfra.md`](plans/sollinha-no-deepinfra.md); impl [`sollinha-no-deepinfra-impl.md`](plans/sollinha-no-deepinfra-impl.md).
|
||||
|
||||
**Recently resolved (2026-08-08):** **`pnpm worktree plan [bag] [--go] [--no-migrate]`** — worktree de **planejamento** para rodar a skill `/plan-issue` sem ocupar o main: **cada invocação cria UM DIFERENTE**, para sessões de planejamento paralelas — com `bag`, branch `plans/plan-issue-<bag>` (sufixo `-2`/`-3` se o nome já estiver vivo); sem `bag`, o próximo sequencial `plans/plan-issue-<n>` livre (`PLAN_BRANCH_PREFIX`/`planBranchName`, `scripts/lib/worktree.mjs`). Prefixo minúsculo `plans/…` nunca colide com um `next` posterior em branch nem em slot (slot do plano = hash do branch, registrado via marcador e bumpado pelos vivos). Mesmo provisionamento isolado do `next` (porta `3100+slot`, bancos `teqo_wt<slot>`/`teqo_wt<slot>_test`, migrations; sem Docker degrada ao compartilhado). `kill` cobre; comment das envs distingue `gerado por pnpm worktree plan`. Sem migration. Naming unit-testada.
|
||||
|
||||
**Recently resolved (2026-08-08):** **B168** (Issue #422) — desfeito o **encadeamento automático** das ações rápidas do wizard (B98 era o precedente que este item inverte; evidência de campo: o coordenador rejeitou a tela avançando sozinha). Ao salvar **qualquer** ação de ajuste (votos, tendência, atualização, liderança), o usuário volta à origem (`from` allowlist B110 mantido; Início sem `from`) com toast — nunca avança para outro wizard. Removidos a fila/pipeline `wizardActionChain.ts` (fundido `wizardReturnHref` + `wizardPreviousHref` só com passos internos em `campaignActionRoutes.ts`), o param `entry`, os skip resolvers/`Pular` (`WizardSkipTrailing`/`WizardTrendSkipTrailing` deletados, chrome mobile sempre “X”) e o prefill de nota sem produtor (`notePrefill`/`voteFrom`/`voteTo`). Tendência: pós-save o RSC refresha com status já aplicado e o **client** é o único dono da navegação (toast + retorno) — o redirect servidor engolia o toast (B97 corrigido no caminho). Liderança: botão flutuante pós-save passou de “Continuar” (avançava) para **“Concluir”** (retorna à origem). Sem migration. Intenção [`desfazer-encadeamento-acoes-wizard.md`](plans/desfazer-encadeamento-acoes-wizard.md); impl [`desfazer-encadeamento-acoes-wizard-impl.md`](plans/desfazer-encadeamento-acoes-wizard-impl.md).
|
||||
|
|
|
|||
96
docs/plans/sollinha-no-deepinfra-impl.md
Normal file
96
docs/plans/sollinha-no-deepinfra-impl.md
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# 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.
|
||||
|
|
@ -77,7 +77,7 @@
|
|||
"issue": "node scripts/issue.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/deepseek": "^3.0.19",
|
||||
"@ai-sdk/deepinfra": "3.0.20",
|
||||
"@ai-sdk/react": "^4.0.50",
|
||||
"@base-ui/react": "^1.6.0",
|
||||
"@fullcalendar/react": "7.0.2",
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ importers:
|
|||
|
||||
.:
|
||||
dependencies:
|
||||
'@ai-sdk/deepseek':
|
||||
specifier: ^3.0.19
|
||||
version: 3.0.19(zod@4.3.6)
|
||||
'@ai-sdk/deepinfra':
|
||||
specifier: 3.0.20
|
||||
version: 3.0.20(zod@4.3.6)
|
||||
'@ai-sdk/react':
|
||||
specifier: ^4.0.50
|
||||
version: 4.0.50(react@19.2.4)(zod@4.3.6)
|
||||
|
|
@ -288,8 +288,8 @@ packages:
|
|||
'@acemir/cssom@0.9.31':
|
||||
resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==}
|
||||
|
||||
'@ai-sdk/deepseek@3.0.19':
|
||||
resolution: {integrity: sha512-APzL/IpDSLhVlKQXo1gKCvTDLeli98ITzKnMq/eUNVWZ/UzWKYYG/0mCsmJRwTC37YE0ZzpxwDBw3J8xQT3lxg==}
|
||||
'@ai-sdk/deepinfra@3.0.20':
|
||||
resolution: {integrity: sha512-LqBoRPuSiU9R8hJF/q3/ujDnK5niDcQfbbSHoUrJNmSKOLT0Gwi6sL6l2XxxoJXLlt+1MtxDuZvO9GVbvmODLA==}
|
||||
engines: {node: '>=22'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
|
|
@ -306,6 +306,12 @@ packages:
|
|||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
|
||||
'@ai-sdk/openai-compatible@3.0.20':
|
||||
resolution: {integrity: sha512-RsPg+HilsKc/d+jy4zItG7v7RJQm+caUu+iU0a5bx3eyyX5tkayMGDRklXMMMAPa4l34luzQwT3t4cmeHfdiWw==}
|
||||
engines: {node: '>=22'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
|
||||
'@ai-sdk/provider-utils@5.0.18':
|
||||
resolution: {integrity: sha512-UBNCrkxS5llgN2/RXLBRkjCFTIuL6YB1Goq5c+yRecnznhtX4XPjTwLHz2hrsTltTVZ0rqVegtnwFXdPBkjDHA==}
|
||||
engines: {node: '>=22'}
|
||||
|
|
@ -7777,8 +7783,9 @@ snapshots:
|
|||
|
||||
'@acemir/cssom@0.9.31': {}
|
||||
|
||||
'@ai-sdk/deepseek@3.0.19(zod@4.3.6)':
|
||||
'@ai-sdk/deepinfra@3.0.20(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@ai-sdk/openai-compatible': 3.0.20(zod@4.3.6)
|
||||
'@ai-sdk/provider': 4.0.4
|
||||
'@ai-sdk/provider-utils': 5.0.18(zod@4.3.6)
|
||||
zod: 4.3.6
|
||||
|
|
@ -7797,6 +7804,12 @@ snapshots:
|
|||
pkce-challenge: 5.0.1
|
||||
zod: 4.3.6
|
||||
|
||||
'@ai-sdk/openai-compatible@3.0.20(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 4.0.4
|
||||
'@ai-sdk/provider-utils': 5.0.18(zod@4.3.6)
|
||||
zod: 4.3.6
|
||||
|
||||
'@ai-sdk/provider-utils@5.0.18(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 4.0.4
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { deepSeek } from '@ai-sdk/deepseek'
|
||||
import { deepInfra } from '@ai-sdk/deepinfra'
|
||||
import configPromise from '@payload-config'
|
||||
import {
|
||||
convertToModelMessages,
|
||||
|
|
@ -38,7 +38,7 @@ export async function POST(req: Request) {
|
|||
const { messages } = await req.json()
|
||||
|
||||
const result = streamText({
|
||||
model: deepSeek('deepseek-v4-flash'),
|
||||
model: deepInfra('deepseek-ai/DeepSeek-V4-Flash'),
|
||||
system: AI_SYSTEM_PROMPT,
|
||||
messages: await convertToModelMessages(messages),
|
||||
tools: buildAITools({ user, payload }),
|
||||
|
|
|
|||
Loading…
Reference in a new issue