--- phase: quick plan: 1 type: execute wave: 1 depends_on: [] files_modified: - crates/inference-core/src/session.rs - crates/inference-server/src/routes.rs - tauri-app/src/components/ChatView.tsx autonomous: true requirements: [] must_haves: truths: - "A segunda mensagem do chat recebe resposta válida do modelo, não 'internal error'" - "Nenhuma mensagem duplicada com erro é inserida no DB após uma geração bem-sucedida" - "O backend loga claramente a causa quando llama_decode falha no prefill" artifacts: - path: "crates/inference-core/src/session.rs" provides: "decode_prefill com log de erro detalhado e fallback de kv_clear via llama_kv_cache_clear" - path: "crates/inference-server/src/routes.rs" provides: "StreamItem::Error não emite conteúdo 'internal error' acumulável pelo frontend" - path: "tauri-app/src/components/ChatView.tsx" provides: "buildApiMessages exclui mensagens de erro do histórico enviado ao backend" key_links: - from: "crates/inference-core/src/engine.rs new_session()" to: "llama_get_memory / memory_clear" via: "KV cache clear antes de cada nova sessão" pattern: "memory_clear" - from: "tauri-app/src/components/ChatView.tsx buildApiMessages" to: "SELECT role, content FROM messages" via: "histórico de mensagens enviado ao backend" pattern: "buildApiMessages" --- Identificar e corrigir o bug que causa "internal error" na segunda mensagem do chat. Purpose: A segunda request ao backend falha com llama_decode retornando não-zero no prefill, provavelmente porque o KV cache não está sendo limpo corretamente quando llama_get_memory retorna NULL. O StreamItem::Error no frontend então acumula "internal error" como conteúdo e o salva no DB como mensagem de assistente válida, criando a entrada duplicada observada nos logs. Output: Backend com KV cache clear robusto + log de diagnóstico + frontend filtrando mensagens de erro do histórico da API. @C:/Users/Evandro/.claude/get-shit-done/workflows/execute-plan.md @C:/Users/Evandro/.claude/get-shit-done/templates/summary.md @.planning/STATE.md Task 1: Tornar o KV cache clear robusto e adicionar diagnóstico de falha de prefill crates/inference-core/src/engine.rs crates/inference-core/src/session.rs **Problema identificado:** Em `engine.rs::new_session()` (linha ~302), quando `llama_get_memory` retorna NULL, o KV cache NÃO é limpo e um warning é logado — mas a execução continua. Na segunda request, o contexto llama ainda tem tokens da sessão anterior nas posições 0..N, e o prefill da nova request tenta escrever novamente nessas mesmas posições causando `llama_decode` retornar não-zero (erro KV cache cheio / inconsistência de posições). **Fix em `engine.rs`:** No método `new_session()`, após a tentativa com `get_memory/memory_clear`, adicionar fallback usando `llama_kv_cache_clear(ctx)` diretamente se a memória for NULL. A função `llama_kv_cache_clear` existe como símbolo separado em versões mais antigas da llama.cpp e deve estar disponível via `self.lib`. Verificar se `LlamaLib` expõe `kv_cache_clear` — se sim, chamar como fallback. Se não existir, usar `(self.lib.memory_clear)(memory, true)` mas garantir que o código falhe explicitamente (retorne `Err`) quando memory é NULL em vez de continuar silenciosamente. Alterar o bloco atual: ```rust // ANTES (silently continues when memory is NULL): let memory = (self.lib.get_memory)(self.ctx.ptr); if memory.is_null() { tracing::warn!("llama_get_memory returned NULL — KV cache will not be cleared"); } else { (self.lib.memory_clear)(memory, true); tracing::debug!("engine.kv_cache_cleared"); } ``` Para: ```rust // DEPOIS: bail quando não consegue limpar o cache, evitando decode com estado corrompido let memory = (self.lib.get_memory)(self.ctx.ptr); if memory.is_null() { tracing::error!("llama_get_memory returned NULL — cannot clear KV cache, refusing to create session to avoid decode corruption"); anyhow::bail!("KV cache clear failed: llama_get_memory returned NULL"); } else { (self.lib.memory_clear)(memory, true); tracing::info!("engine.kv_cache_cleared"); } ``` **Fix em `session.rs`:** Em `decode_prefill()` (linha ~350), quando `lib.decode` retorna não-zero, melhorar o bail com contexto claro: ```rust // ANTES: bail!( "llama_decode (prefill) returned {} while decoding prompt chunk {:?}", ret, plan.range ); // DEPOIS: bail!( "llama_decode (prefill) returned {} for chunk {:?} — likely KV cache not cleared or context window ({} tokens) exceeded by prompt", ret, plan.range, tokens.len() ); ``` Após as mudanças, verificar compilação: `cargo check -p inference-core -p inference-server` cargo check -p inference-core -p inference-server 2>&1 | tail -5 - `cargo check` passa sem erros - `new_session()` retorna `Err` quando `llama_get_memory` retorna NULL em vez de continuar - Mensagem de erro de prefill inclui tamanho do prompt para diagnóstico Task 2: Impedir que "internal error" seja salvo no DB e excluir mensagens de erro do histórico da API crates/inference-server/src/routes.rs tauri-app/src/components/ChatView.tsx **Problema 1 — routes.rs:** Quando `StreamItem::Error` é enviado (linha ~318-335), o backend emite um chunk SSE com `content: Some("internal error")`. O frontend acumula esse token em `fullContent` normalmente (mesmo path do streaming), então salva no DB como assistente com o texto "internal error". O `StreamItem::Error` deve sinalizar falha sem emitir conteúdo que o frontend interpreta como resposta válida. Fix: Alterar `StreamItem::Error` para emitir um chunk com `finish_reason: Error` e conteúdo vazio, ou usar um campo de erro fora da estrutura `delta.content`. A solução mais simples compatível com o frontend atual: emitir o chunk de erro com `content: None` e usar `finish_reason: Stop` para que o frontend complete o stream, mas não acumule conteúdo. ```rust // ANTES: emite "internal error" como content StreamItem::Error => { tracing::error!(streamed_tokens, "stream.error: session failed mid-stream"); let chunk = ChatCompletionChunk { // ... choices: vec![ChunkChoice { delta: Delta { role: None, content: Some("internal error".to_owned()), tool_calls: None }, finish_reason: Some(FinishReason::Stop), }], usage: None, }; yield Ok(Event::default().data(serde_json::to_string(&chunk).unwrap())); yield Ok(Event::default().data("[DONE]")); break; } // DEPOIS: emite chunk final sem content para não poluir fullContent StreamItem::Error => { tracing::error!(streamed_tokens, "stream.error: session failed — sending empty finish chunk"); let chunk = ChatCompletionChunk { id: id.clone(), object: "chat.completion.chunk", created, model: resolved_model.clone(), choices: vec![ChunkChoice { index: 0, delta: Delta { role: None, content: None, tool_calls: None }, finish_reason: Some(FinishReason::Stop), }], usage: None, }; yield Ok(Event::default().data(serde_json::to_string(&chunk).unwrap())); yield Ok(Event::default().data("[DONE]")); break; } ``` **Problema 2 — ChatView.tsx:** Em `buildApiMessages` (linha 126-132), o SELECT não filtra mensagens de erro. Se alguma mensagem de erro entrou no DB (antes do fix acima), ela vai no histórico enviado ao backend, corrompendo o contexto de chat. Adicionar filtro: ```typescript // ANTES: const rows = db.query<{ role: string; content: string }>( "SELECT role, content FROM messages WHERE chat_id = ? ORDER BY created_at DESC LIMIT ?", [cid, limit] ).reverse(); // DEPOIS: excluir mensagens que são erros do frontend (⚠️ prefix) e vazias const rows = db.query<{ role: string; content: string }>( "SELECT role, content FROM messages WHERE chat_id = ? AND content != '' AND content NOT LIKE '⚠️ Error:%' AND content != 'internal error' ORDER BY created_at DESC LIMIT ?", [cid, limit] ).reverse(); ``` **Problema 3 — ChatView.tsx:** No catch do `send()` (linha ~261-268), quando o stream termina com `fullContent` vazio (após o fix do backend), a lógica atual salva a mensagem de erro do catch com prefix `⚠️ Error:`. Mas quando o stream completa normalmente mas com `fullContent` vazio (caso de session fail silencioso), a mensagem assistente salva é vazia. Adicionar guarda antes do INSERT do assistente: ```typescript // Antes do INSERT da mensagem assistente, após a geração completar: // Só salvar se houver conteúdo real if (!finalContent && !parsed.reasoning) { console.warn("[ChatView] send: assistant returned empty content — not saving to DB"); } else { db.run(`INSERT INTO messages ...`, [...]); } ``` Localizar a linha do INSERT (linha ~241-254 em ChatView.tsx) e envolver com esse guard. Após as mudanças: - `cargo check -p inference-server` - `cd tauri-app && npx tsc --noEmit` cd D:/Documents/Github/ai-backend-studio && cargo check -p inference-server 2>&1 | tail -3 && cd tauri-app && npx tsc --noEmit 2>&1 | tail -5 - `cargo check` e `tsc --noEmit` passam sem erros - Chunk SSE de erro não contém `content: "internal error"` - `buildApiMessages` filtra mensagens de erro/vazias do histórico - INSERT do assistente não executa quando `fullContent` está vazio Fix de três camadas para o bug de "internal error" na segunda mensagem: 1. Backend Rust: KV cache clear agora falha explicitamente (Err) quando llama_get_memory retorna NULL, com log de erro claro em vez de continuar silenciosamente 2. Backend Rust: StreamItem::Error emite chunk sem content (não polui fullContent) 3. Frontend: buildApiMessages filtra mensagens de erro do histórico; INSERT do assistente tem guard contra conteúdo vazio 1. Inicie o runtime local com um modelo GGUF carregado 2. Abra o ForgeLM e envie uma primeira mensagem — confirme resposta normal 3. Envie uma segunda mensagem no mesmo chat — confirme que resposta é válida (não "internal error") 4. Verifique o log do backend no terminal: - Deve aparecer "engine.kv_cache_cleared" antes de cada sessão - Se aparecer "llama_get_memory returned NULL", o runtime vai retornar erro 500 (não "internal error" silencioso) 5. Abra DevTools > Console no app e confirme que não há INSERT com conteúdo "internal error" nos logs [DB] 6. Envie 3-4 mensagens em sequência e confirme que todas respondem normalmente Digite "aprovado" se as respostas estão corretas, ou descreva o comportamento observado - `cargo check -p inference-core -p inference-server` passa - `cd tauri-app && npx tsc --noEmit` passa - Segundo envio de mensagem no chat retorna resposta do modelo (não "internal error") - Logs do backend mostram "engine.kv_cache_cleared" antes de cada sessão - Enviar 5 mensagens consecutivas no mesmo chat sem receber "internal error" - DB não contém nenhuma mensagem assistente com conteúdo "internal error" ou vazio após geração bem-sucedida - Se o KV cache falhar (llama_get_memory NULL), o erro é visível como HTTP 500, não como mensagem de assistente corrupta Após conclusão, criar `.planning/quick/1-investigar-e-corrigir-bug-de-internal-er/1-SUMMARY.md`