12 KiB
Chat Transcript Leakage + Thinking-Only Fix Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Eliminar o vazamento de transcript/prompt (# System: ...), impedir que respostas corrompidas voltem ao contexto e garantir que o chat exiba a resposta útil além do thinking.
Architecture: A correção será feita em três camadas complementares: (1) parser/sanitização no frontend para detectar transcript leakage em formatos markdown e role-prefix tradicionais; (2) contenção na persistência e reidratação do histórico para impedir reinfecção do contexto; (3) endurecimento do backend para detectar stops por sufixo acumulado, em vez de comparação token-a-token. O rollout deve ser guiado por testes primeiro, com mudanças pequenas e verificadas por camada.
Tech Stack: React + TypeScript + Vitest no frontend; Rust no backend (inference-server, inference-core); SSE streaming; SQLite/local DB.
Estratégia de implementação
Abordagem A — Parser defensivo no frontend
Usar uma função única de sanitização que reconheça:
System:/User:/Assistant:/Developer:# System:/## System:etc.- blocos fenced que comecem com transcript vazado
- prefixos de prompt conhecido (
You are a precise coding assistant running locally...)
Essa abordagem protege a UI mesmo quando o backend ainda falhar.
Abordagem B — Quarentena do histórico persistido
Antes de salvar ou reenviar mensagens do assistente:
- limpar transcript leakage
- marcar como corrompida quando só restar prompt echo / transcript
- não reenviar mensagens contaminadas para o modelo
Essa abordagem interrompe o ciclo de reinjeção.
Abordagem C — Stop detection real no backend
Trocar a verificação de stop sequence baseada em token isolado por verificação baseada em buffer/sufixo acumulado da saída gerada.
Essa abordagem reduz a chance do vazamento nascer.
Ordem recomendada
- Frontend parser + testes
- Histórico/persistência + testes
- Backend stop detection + testes
- Validação integrada streaming/non-streaming
Critérios de aceitação
- Respostas contendo
# System:não aparecem mais como conteúdo visível ao usuário. - Respostas contendo apenas
<think>...</think>seguido de transcript leakage não são persistidas como resposta válida. - Mensagens contaminadas previamente salvas não são reenviadas ao modelo em
buildApiMessages(). - O streaming continua mostrando thinking em tempo real, mas a resposta final visível aparece corretamente quando existir.
- O backend corta transcript leakage por stop sequence mesmo quando ele chega fragmentado em múltiplos tokens.
- Testes frontend e backend cobrem os formatos reais do bug.
Task 1: Criar matriz de casos reais do vazamento
Files:
- Modify:
tauri-app/src/lib/chat.test.ts - Modify:
tauri-app/src/prompts/response-rules.ts
Step 1: Escrever testes falhos para transcript leakage em formatos reais
Adicionar casos para:
# System: You are a precise coding assistant...-
# System: ...
- `<think>...</think>` seguido de `# System:`
- transcript tradicional `Assistant:` / `User:`
- resposta normal com markdown legítimo que **não** deve ser cortada
**Step 2: Rodar os testes para confirmar falha**
Run: `cd tauri-app && npm test -- src/lib/chat.test.ts`
Expected: FAIL nos novos casos de `# System:` / fenced leakage.
**Step 3: Commit**
```bash
git add tauri-app/src/lib/chat.test.ts tauri-app/src/prompts/response-rules.ts
git commit -m "test: cover transcript leakage variants"
```
---
### Task 2: Centralizar a detecção de transcript leakage no frontend
**Files:**
- Modify: `tauri-app/src/prompts/response-rules.ts`
- Modify: `tauri-app/src/lib/chat.ts`
- Test: `tauri-app/src/lib/chat.test.ts`
**Step 1: Expandir os padrões de transcript leakage**
Em `tauri-app/src/prompts/response-rules.ts`:
- substituir `TRANSCRIPT_LEAKAGE_PATTERN` por versão que aceite prefixos markdown opcionais:
- `(?:^|\n)\s*(?:#{1,6}\s*)?(?:System|User|Assistant|Human|Developer)\s*:`
- adicionar padrão explícito para prompt echo atual:
- `/you are a precise coding assistant running locally/i`
**Step 2: Criar sanitização mais forte em `chat.ts`**
Em `tauri-app/src/lib/chat.ts`:
- extrair helper como `stripPromptEchoAndTranscriptLeakage(text: string): string`
- cortar conteúdo ao encontrar:
- role-prefix simples
- role-prefix markdown (`# System:`)
- prompt echo conhecido no início da seção visível
- garantir que markdown legítimo com `# Heading` continue funcionando se não for um role marker.
**Step 3: Ajustar `isLoopedOrCorruptedResponse()`**
Adicionar o novo padrão de corrupção (`precise coding assistant running locally`).
**Step 4: Rodar os testes**
Run: `cd tauri-app && npm test -- src/lib/chat.test.ts`
Expected: PASS
**Step 5: Commit**
```bash
git add tauri-app/src/prompts/response-rules.ts tauri-app/src/lib/chat.ts tauri-app/src/lib/chat.test.ts
git commit -m "fix: sanitize transcript leakage in assistant responses"
```
---
### Task 3: Impedir reinfecção do histórico em `buildApiMessages()`
**Files:**
- Modify: `tauri-app/src/components/ChatView.tsx`
- Create: `tauri-app/src/components/chat-history-sanitizer.test.ts` (ou incluir no teste existente mais próximo)
**Step 1: Escrever teste falho para reidratação contaminada**
Cobrir cenário:
- mensagem do assistant salva com `# System: ...`
- `buildApiMessages()` não deve reenviar essa parte
- se a mensagem toda for corrupção, ela deve ser descartada do contexto
**Step 2: Extrair helper reutilizável de sanitização do histórico**
Em `ChatView.tsx`:
- substituir regex inline em `buildApiMessages()` por uso do helper central de `chat.ts`
- regra recomendada:
- assistant com conteúdo limpo vazio => descartar do contexto
- assistant com conteúdo útil + vazamento => manter só parte útil
**Step 3: Garantir simetria com persistência**
Não usar regex diferente no histórico. Tudo deve passar pelo mesmo parser/sanitizer.
**Step 4: Rodar testes**
Run: `cd tauri-app && npm test -- src/components/chat-history-sanitizer.test.ts src/components/ChatView.test.tsx`
Expected: PASS
**Step 5: Commit**
```bash
git add tauri-app/src/components/ChatView.tsx tauri-app/src/components/chat-history-sanitizer.test.ts tauri-app/src/components/ChatView.test.tsx
git commit -m "fix: prevent transcript leakage from reentering chat context"
```
---
### Task 4: Corrigir persistência da resposta final vs thinking-only
**Files:**
- Modify: `tauri-app/src/components/ChatView.tsx`
- Modify: `tauri-app/src/components/ChatView.test.tsx`
- Possibly Modify: `tauri-app/src/components/StreamingBubble.tsx`
**Step 1: Escrever testes falhos para dois casos críticos**
Caso A:
- stream = `<think>analise</think>Resposta útil`
- deve salvar `content = "Resposta útil"` e `reasoning_content = "analise"`
Caso B:
- stream = `<think>analise</think># System: ...`
- não deve salvar resposta visível corrompida
- opcionalmente deve mostrar placeholder/erro controlado
**Step 2: Ajustar regra de persistência**
Em `ChatView.tsx`, no trecho final de `send()`:
- calcular `sanitizedVisibleContent`
- se `sanitizedVisibleContent` for vazio **e** reasoning existir, tratar como resposta sem conteúdo útil
- não salvar transcript leak como `content`
- salvar `reasoning_content` apenas quando existir conteúdo útil ou quando a UX desejada permitir thinking-only explícito
**Step 3: Definir comportamento de UX para thinking-only corrompido**
Abordagem recomendada:
- se só sobrar reasoning e o conteúdo visível estiver corrompido/vazio, salvar mensagem de erro controlado:
- `⚠️ O modelo gerou apenas raciocínio interno e nenhuma resposta utilizável.`
- não persistir o prompt echo bruto
**Step 4: Rodar testes**
Run: `cd tauri-app && npm test -- src/components/ChatView.test.tsx`
Expected: PASS
**Step 5: Commit**
```bash
git add tauri-app/src/components/ChatView.tsx tauri-app/src/components/ChatView.test.tsx tauri-app/src/components/StreamingBubble.tsx
git commit -m "fix: handle thinking-only and corrupted final responses"
```
---
### Task 5: Corrigir stop detection no backend
**Files:**
- Modify: `crates/inference-core/src/session.rs`
- Add test in: `crates/inference-core/src/session.rs` or dedicated module test file
**Step 1: Escrever teste falho para stop fragmentado**
Criar teste para simular emissão fragmentada de:
- `\n`
- `System`
- `:`
O detector deve reconhecer que o sufixo acumulado forma `\nSystem:` mesmo quebrado em múltiplos tokens.
**Step 2: Implementar buffer de sufixo acumulado**
Em `session.rs`:
- adicionar campo para acumular os últimos N caracteres emitidos (`recent_output_suffix`)
- a cada token detokenizado:
- concatenar ao buffer
- truncar buffer para tamanho máximo do maior stop + margem pequena
- verificar `ends_with(stop)` no buffer acumulado
**Step 3: Manter comportamento atual de streaming**
Não parar por igualdade de token isolado; parar por sufixo acumulado.
**Step 4: Rodar testes do crate**
Run: `cargo test -p inference-core session -- --nocapture`
Expected: PASS
**Step 5: Commit**
```bash
git add crates/inference-core/src/session.rs
git commit -m "fix: detect stop sequences across fragmented tokens"
```
---
### Task 6: Endurecer stops do template genérico e alinhar com parser
**Files:**
- Modify: `crates/inference-server/src/compat.rs`
- Test: `crates/inference-server/src/compat.rs`
**Step 1: Adicionar variantes markdown aos `extra_stop_sequences`**
Em `prepare_prompt()` para `ChatTemplate::Generic`, incluir:
- `\n# System:`
- `\n# User:`
- `\n# Assistant:`
- `\n## System:` (opcional)
**Step 2: Adicionar teste de contrato**
Garantir que o template genérico injeta essas variantes.
**Step 3: Rodar testes**
Run: `cargo test -p inference-server compat -- --nocapture`
Expected: PASS nos testes do módulo `compat` relevantes.
**Step 4: Commit**
```bash
git add crates/inference-server/src/compat.rs
git commit -m "fix: add markdown transcript stops for generic prompt template"
```
---
### Task 7: Validação integrada frontend + backend
**Files:**
- No code required initially
- Possibly add/update integration tests after manual verification
**Step 1: Rodar suíte frontend completa**
Run: `cd tauri-app && npm test`
Expected: PASS
**Step 2: Rodar build frontend**
Run: `cd tauri-app && npm run build`
Expected: PASS
**Step 3: Rodar testes backend focados**
Run:
```bash
cargo test -p inference-core -- --nocapture
cargo test -p inference-server compat -- --nocapture
```
Expected: PASS nos testes tocados pela mudança.
**Step 4: Verificação manual**
Cenários manuais:
1. resposta normal sem `<think>`
2. resposta com `<think>` e resposta útil
3. resposta com `<think>` + `# System:`
4. mensagem previamente contaminada no DB não deve ser reenviada ao contexto
**Step 5: Commit final**
```bash
git add .
git commit -m "fix: stop transcript leakage from corrupting chat responses"
```
---
## Observações de implementação
- Não criar regex diferentes em cada arquivo. A sanitização precisa ter **uma fonte de verdade**.
- O parser do frontend e a limpeza do histórico devem compartilhar helpers.
- O backend deve reduzir a chance do bug, mas o frontend ainda precisa ser resiliente porque modelos locais podem ignorar formato.
- Não confiar apenas em `isLoopedOrCorruptedResponse()`; usar também sanitização determinística do conteúdo.
## Resultado esperado final
Depois dessas tarefas:
- o usuário não verá mais blocos `# System: ...` nas mensagens;
- o thinking continuará disponível, mas não substituirá a resposta útil;
- respostas corrompidas não contaminarão o histórico;
- o backend interromperá transcript leakage com mais robustez.