Spaces:
Running
fix(akira): FINAL GUARD anti-leak + identity STM + regex memorando
Browse files1. **REGRA FINAL → texto não-ecoável** (api.py:3119): Removeu o marcador
'🔒 REGRA FINAL INEGOCIÁVEL' que o LLM HF Router (Mistral/Llama)
interpretava como conteúdo e ecoava na resposta. Substituído por
'[LENGTH] max N palavras' que não tenta completar.
2. **FINAL GUARD reforçado** (api.py:3201-3213): Adicionou:
- ZERO 'nota interna', 'internal memo', 'internal note'
- Regra 6: USA O NOME DO UTILIZADOR (chama pelo nome se souber)
- Regra 7: NUNCA ecoes os marcadores do FINAL GUARD
3. **Anti-leak regex** (api.py:5291-5293):
- Remove 'memorando interno' / 'nota interna' / 'internal memo'
- Remove '🔓 REGRA FINAL' (versão ecoada pelo modelo)
4. **Identity context no STM** (api.py:2590-2594, 2605-2611, 2800-2804):
- Adiciona '[CONTEXTO] Utilizador atual: Isaac Quarenta (ID: 244937035662)'
no topo do histórico para AMBOS os paths (reply_to_bot e não-reply_to_bot)
- Garante que o LLM sabe SEMPRE com quem está a falar
5. **CoT usa author real** (thinking_engine.py:359): Quando conteúdo
não tem prefixo de autor, usa msg.get('author_name') em vez de
só 'usuario' genérico.
- .mimocode/plans/1787936017903-mighty-eagle.md +89 -0
- modules/api.py +26 -3
- modules/thinking_engine.py +3 -2
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Plano: Fix FINAL GUARD leak + reforço CoT nome + anti-leak
|
| 2 |
+
|
| 3 |
+
## Problemas identificados
|
| 4 |
+
|
| 5 |
+
### 1. REGRA FINAL INEGOCIÁVEL a vazar na resposta
|
| 6 |
+
**Arquivo**: `modules/api.py:3119`
|
| 7 |
+
**Linha**: `🔒 REGRA FINAL INEGOCIÁVEL: Se [RESPONSE LENGTH CONSTRAINT] diz 3-5 palavras...`
|
| 8 |
+
|
| 9 |
+
O LLM (HF Router Mistral/Llama) está a interpretar este marcador interno como conteúdo e a ecoá-lo na resposta. Resultado: `🔓 [REGRA FINAL DE DESBLOQUEIO — FINAL GUARD UNLOCK RULE]]\n\n1.`
|
| 10 |
+
|
| 11 |
+
O problema: o texto começa com `🔒 REGRA FINAL` e o LLM gera uma versão transformada.
|
| 12 |
+
|
| 13 |
+
**Fix**: Mudar o texto para algo que o LLM não tente completar, ou melhorar a sanitização.
|
| 14 |
+
|
| 15 |
+
### 2. CoT a usar "o utilizador" em vez do nome real
|
| 16 |
+
**Arquivo**: `modules/thinking_engine.py`
|
| 17 |
+
|
| 18 |
+
O CoT JÁ tem instrução para usar o nome (linha 550: `NUNCA uses 'o utilizador' ou 'a pessoa' — usa o nome!`) e injeta `Utilizador ativo: {nome_usuario}` (linha 414). Os logs recentes mostram que ISSO JÁ FUNCIONA (`Isaac Quarenta está apenas cumprimentando`). Mas o user quer GARANTIR que continua.
|
| 19 |
+
|
| 20 |
+
**Fix**: Reforçar no `thinking_engine.py` e no STM para que o nome esteja sempre visível no contexto.
|
| 21 |
+
|
| 22 |
+
### 3. Memorando a poder vazar na resposta
|
| 23 |
+
**Arquivo**: `modules/api.py:3191, 3203`
|
| 24 |
+
|
| 25 |
+
O FINAL GUARD já tem "ZERO 'memorando interno'" mas o sanitizador não tem regex específica para "memorando".
|
| 26 |
+
|
| 27 |
+
**Fix**: Adicionar regex anti-leak para "memorando", "nota interna", "internal memo" no `_sanitize_llm_response`.
|
| 28 |
+
|
| 29 |
+
### 4. STM deve reforçar identidade do utilizador
|
| 30 |
+
**Arquivo**: `modules/api.py:2642-2994`
|
| 31 |
+
|
| 32 |
+
O STM formata com `[KIAMIA → ...]` e `[CENTRO ...]`, mas não identifica claramente "ESTE É O NOME DO UTILIZADOR ATUAL".
|
| 33 |
+
|
| 34 |
+
**Fix**: Adicionar linha no STM tipo `[CONTEXTO] Utilizador atual: Isaac Quarenta (244937035662)` nos primeiros mensagens do histórico.
|
| 35 |
+
|
| 36 |
+
## Alterações a fazer
|
| 37 |
+
|
| 38 |
+
### Ficheiro: `modules/api.py`
|
| 39 |
+
|
| 40 |
+
**Linha ~3119** — Mudar `🔒 REGRA FINAL INEGOCIÁVEL:` para texto não-echoável:
|
| 41 |
+
```
|
| 42 |
+
# ANTES:
|
| 43 |
+
🔒 REGRA FINAL INEGOCIÁVEL: Se [RESPONSE LENGTH CONSTRAINT] diz 3-5 palavras...
|
| 44 |
+
|
| 45 |
+
# DEPOIS:
|
| 46 |
+
[LENGTH ABSOLUTE] Constraint: [RESPONSE LENGTH CONSTRAINT]. Follow it exactly.
|
| 47 |
+
```
|
| 48 |
+
Ou simplesmente: `[RESPONSE LENGTH] max N palavras. Follow this constraint.`
|
| 49 |
+
|
| 50 |
+
**Linha ~3203** — Adicionar "memorando" ao FINAL GUARD:
|
| 51 |
+
```
|
| 52 |
+
"\n1. Responde APENAS com texto natural em português angolano. ZERO inglês, ZERO tags XML, ZERO '[KIAMIA → ...]', ZERO '[NOTA INTERNA]', ZERO 'memorando interno', ZERO 'nota interna', ZERO 'internal memo'."
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
**Linha ~3203-3208** — Reforçar uso do nome do utilizador no FINAL GUARD:
|
| 56 |
+
```
|
| 57 |
+
"\n5. Se sabes o nome do utilizador, USA-O na resposta. Se o utilizador é 'Isaac Quarenta', responde 'Isaac' ou 'Isaac Quarenta' — não 'tu' ou 'você'."
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
**Linha ~5265** — Adicionar regex para "memorando" no `_sanitize_llm_response` (Phase 2 anti-leak):
|
| 61 |
+
```
|
| 62 |
+
# After removing [KIAMIA → ...] prefix:
|
| 63 |
+
sanitized = re.sub(r'\[KIAMIA\s*→\s*[^\]]+\]:\s*', '', sanitized)
|
| 64 |
+
|
| 65 |
+
# NEW: Remove internal memo phrases
|
| 66 |
+
sanitized = re.sub(r'\b(?:memorando interno|nota interna|internal memo|memorando)\b', '', sanitized, flags=re.IGNORECASE)
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
### Ficheiro: `modules/thinking_engine.py`
|
| 70 |
+
|
| 71 |
+
**Linha ~277** — Reforçar uso do nome no CoT (já existe mas pode ser fortalecido):
|
| 72 |
+
A linha 277 já diz "🔒 USA SEMPRE O NOME REAL" mas o CoT pode continuar a usar "o utilizador". Reforçar com exemplo concreto.
|
| 73 |
+
|
| 74 |
+
**Linha ~414** — Garantir que `Utilizador ativo: {nome}` aparece no prompt (já existe).
|
| 75 |
+
|
| 76 |
+
### Ficheiro: `modules/api.py`
|
| 77 |
+
|
| 78 |
+
**Linha ~2600-3000** — No STM formatting, adicionar identificador do utilizador:
|
| 79 |
+
Quando se formata as mensagens do STM para o CoT e para o prompt geral, adicionar:
|
| 80 |
+
```
|
| 81 |
+
[CONTEXTO] Utilizador: {nome_usuario} | Número: {numero_usuario}
|
| 82 |
+
```
|
| 83 |
+
Isto garante que o nome está sempre presente no contexto, mesmo antes de qualquer troca de mensagens.
|
| 84 |
+
|
| 85 |
+
## Testes
|
| 86 |
+
1. Mandar "oi" no PV da Kiami — resposta deve ser curta ("Oi.", "Bom dia.") sem texto de bloqueio
|
| 87 |
+
2. Verificar logs: CoT deve mostrar "Isaac Quarenta" em vez de "o utilizador"
|
| 88 |
+
3. Mandar "bom dia" — resposta deve usar o nome ("Bom dia, Isaac.")
|
| 89 |
+
4. Verificar que "memorando interno" nunca aparece na resposta
|
|
@@ -2587,6 +2587,11 @@ class AkiraAPI:
|
|
| 2587 |
# Elas entram no histórico com um prefixo claro para o LLM não as confundir
|
| 2588 |
# com intenções direcionadas a ela.
|
| 2589 |
context_history = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2590 |
if unified_context and unified_context.stm_messages:
|
| 2591 |
# 🚨 CRITICAL FIX: Para replies ao bot, usar SMART CONTEXT BALANCING
|
| 2592 |
# - Carrega últimas 3 mensagens (evita alucinação por noise)
|
|
@@ -2597,6 +2602,12 @@ class AkiraAPI:
|
|
| 2597 |
# BASE: Carregar últimas 25 mensagens (contexto imediato expandido)
|
| 2598 |
base_msgs = list(unified_context.stm_messages[-25:])
|
| 2599 |
context_history_base = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2600 |
# 🔧 FIX 2026-08-27: CENTRO vs TERCEIRO (Fulano vs Sicrano)
|
| 2601 |
replied_to_author = getattr(unified_context, 'replied_to_author', '') or ""
|
| 2602 |
interlocutor_principal = replied_to_author
|
|
@@ -2785,6 +2796,12 @@ class AkiraAPI:
|
|
| 2785 |
elif not unified_context:
|
| 2786 |
context_history = self._get_history_for_llm(contexto)
|
| 2787 |
# ✔ FIX 2026-08-29: Em reply ao bot, expandir para 5 msgs para não perder thread inicial
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2788 |
if reply_to_bot and context_history:
|
| 2789 |
base_history = list(context_history[-5:])
|
| 2790 |
|
|
@@ -3115,8 +3132,8 @@ class AkiraAPI:
|
|
| 3115 |
|
| 3116 |
# Instead, we only use thinking for system-level calibration (tone, etc)
|
| 3117 |
# Not included in the prompt to prevent leaks
|
| 3118 |
-
# 🔧 FIX 2026-08-
|
| 3119 |
-
_length_final = comprimento_constraint + "\n
|
| 3120 |
# Base sem LENGTH para inserir insights antes do lock final
|
| 3121 |
prompt_enriched = prompt + "\n" + smart_context_instruction + tone_constraint + riscos_constraint + vague_constraint
|
| 3122 |
|
|
@@ -3200,11 +3217,13 @@ class AkiraAPI:
|
|
| 3200 |
# para o LLM lembrar nas últimas tokens antes de gerar
|
| 3201 |
prompt_enriched += (
|
| 3202 |
"\n\n=== [FINAL GUARD — ÚLTIMAS INSTRUÇÕES] ==="
|
| 3203 |
-
"\n1. Responde APENAS com texto natural em português angolano. ZERO inglês, ZERO tags XML, ZERO '[KIAMIA → ...]', ZERO '[NOTA INTERNA]', ZERO 'memorando interno'."
|
| 3204 |
"\n2. Se a [RESPONSE OBRIGATÓRIA] indica uma direção, segue-a (não é opcional)."
|
| 3205 |
"\n3. Se [RESPONSE LENGTH CONSTRAINT] diz X palavras, responde com X palavras (nem mais, nem menos)."
|
| 3206 |
"\n4. Mantém a personalidade: Kiami = jovem angolana 18 anos, séria, tímida, fala pouco, direta, irônica quando provocada. NÃO use 'Sure', 'Of course', 'Here to help', 'Glad to assist'."
|
| 3207 |
"\n5. Se a mensagem é cumprimento/saudação simples, responde curto (1-5 palavras), natural, sem expandir desnecessariamente."
|
|
|
|
|
|
|
| 3208 |
"\n=== FIM DO FINAL GUARD ==="
|
| 3209 |
)
|
| 3210 |
except ImportError:
|
|
@@ -5269,6 +5288,10 @@ class AkiraAPI:
|
|
| 5269 |
sanitized = re.sub(r'\[FINALIZAÇÃO[^\]]*\].*$', '', sanitized, flags=re.DOTALL)
|
| 5270 |
sanitized = re.sub(r'\[NOTA INTERNA\].*$', '', sanitized, flags=re.DOTALL)
|
| 5271 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5272 |
# Remove "- Nome: ... - Estilo: ... - Preferências: ..." blocks
|
| 5273 |
sanitized = re.sub(r'-\s*Nome:\s*.*', '', sanitized)
|
| 5274 |
sanitized = re.sub(r'-\s*Estilo:\s*.*', '', sanitized)
|
|
|
|
| 2587 |
# Elas entram no histórico com um prefixo claro para o LLM não as confundir
|
| 2588 |
# com intenções direcionadas a ela.
|
| 2589 |
context_history = []
|
| 2590 |
+
if nome_usuario and usuario:
|
| 2591 |
+
context_history.append({
|
| 2592 |
+
"role": "system",
|
| 2593 |
+
"content": f"[CONTEXTO] Utilizador atual: {nome_usuario} (ID: {usuario}). Usa este nome nas respostas. Esta é a pessoa com quem estás a falar AGORA."
|
| 2594 |
+
})
|
| 2595 |
if unified_context and unified_context.stm_messages:
|
| 2596 |
# 🚨 CRITICAL FIX: Para replies ao bot, usar SMART CONTEXT BALANCING
|
| 2597 |
# - Carrega últimas 3 mensagens (evita alucinação por noise)
|
|
|
|
| 2602 |
# BASE: Carregar últimas 25 mensagens (contexto imediato expandido)
|
| 2603 |
base_msgs = list(unified_context.stm_messages[-25:])
|
| 2604 |
context_history_base = []
|
| 2605 |
+
# ✅ FIX 2026-08-30: Identity do utilizador no topo do histórico para CoT nunca perder quem é
|
| 2606 |
+
if nome_usuario and usuario:
|
| 2607 |
+
context_history_base.append({
|
| 2608 |
+
"role": "system",
|
| 2609 |
+
"content": f"[CONTEXTO] Utilizador atual: {nome_usuario} (ID: {usuario}). Este nome deve ser usado na resposta. NÃO respondas só 'tu/você' — usa o nome!"
|
| 2610 |
+
})
|
| 2611 |
# 🔧 FIX 2026-08-27: CENTRO vs TERCEIRO (Fulano vs Sicrano)
|
| 2612 |
replied_to_author = getattr(unified_context, 'replied_to_author', '') or ""
|
| 2613 |
interlocutor_principal = replied_to_author
|
|
|
|
| 2796 |
elif not unified_context:
|
| 2797 |
context_history = self._get_history_for_llm(contexto)
|
| 2798 |
# ✔ FIX 2026-08-29: Em reply ao bot, expandir para 5 msgs para não perder thread inicial
|
| 2799 |
+
# 🔧 FIX 2026-08-30: identity context pode ser cortado pelo slice — garantir que está sempre primeiro
|
| 2800 |
+
if nome_usuario and usuario:
|
| 2801 |
+
context_history.insert(0, {
|
| 2802 |
+
"role": "system",
|
| 2803 |
+
"content": f"[CONTEXTO] Utilizador atual: {nome_usuario} (ID: {usuario}). Este nome deve ser usado na resposta. NÃO respondas só 'tu/você' — usa o nome!"
|
| 2804 |
+
})
|
| 2805 |
if reply_to_bot and context_history:
|
| 2806 |
base_history = list(context_history[-5:])
|
| 2807 |
|
|
|
|
| 3132 |
|
| 3133 |
# Instead, we only use thinking for system-level calibration (tone, etc)
|
| 3134 |
# Not included in the prompt to prevent leaks
|
| 3135 |
+
# 🔧 FIX 2026-08-30: texto sem marcadores ecoáveis (o LLM estava a interpretar "REGRA FINAL" como conteúdo)
|
| 3136 |
+
_length_final = comprimento_constraint + "\n[LENGTH] Resposta: max conforme [RESPONSE LENGTH CONSTRAINT]. Web_content é só contexto, não expandir. Max 20 palavras em conversa quotidiana."
|
| 3137 |
# Base sem LENGTH para inserir insights antes do lock final
|
| 3138 |
prompt_enriched = prompt + "\n" + smart_context_instruction + tone_constraint + riscos_constraint + vague_constraint
|
| 3139 |
|
|
|
|
| 3217 |
# para o LLM lembrar nas últimas tokens antes de gerar
|
| 3218 |
prompt_enriched += (
|
| 3219 |
"\n\n=== [FINAL GUARD — ÚLTIMAS INSTRUÇÕES] ==="
|
| 3220 |
+
"\n1. Responde APENAS com texto natural em português angolano. ZERO inglês, ZERO tags XML, ZERO '[KIAMIA → ...]', ZERO '[NOTA INTERNA]', ZERO 'memorando interno', ZERO 'nota interna', ZERO 'internal memo', ZERO 'internal note'."
|
| 3221 |
"\n2. Se a [RESPONSE OBRIGATÓRIA] indica uma direção, segue-a (não é opcional)."
|
| 3222 |
"\n3. Se [RESPONSE LENGTH CONSTRAINT] diz X palavras, responde com X palavras (nem mais, nem menos)."
|
| 3223 |
"\n4. Mantém a personalidade: Kiami = jovem angolana 18 anos, séria, tímida, fala pouco, direta, irônica quando provocada. NÃO use 'Sure', 'Of course', 'Here to help', 'Glad to assist'."
|
| 3224 |
"\n5. Se a mensagem é cumprimento/saudação simples, responde curto (1-5 palavras), natural, sem expandir desnecessariamente."
|
| 3225 |
+
"\n6. USA O NOME DO UTILIZADOR: se o nome está no contexto (ex: 'Isaac Quarenta', 'Jussara'), chama-o pelo nome na resposta. NÃO digas só 'tu' ou 'você' — o nome torna a resposta pessoal e demonstra que reconheces com quem falas."
|
| 3226 |
+
"\n7. NUNCA ecoes/repitas os marcadores deste FINAL GUARD nem dos blocos [RESPONSE *] — estes são só instruções internas. O utilizador só deve ver a tua resposta final, em português natural."
|
| 3227 |
"\n=== FIM DO FINAL GUARD ==="
|
| 3228 |
)
|
| 3229 |
except ImportError:
|
|
|
|
| 5288 |
sanitized = re.sub(r'\[FINALIZAÇÃO[^\]]*\].*$', '', sanitized, flags=re.DOTALL)
|
| 5289 |
sanitized = re.sub(r'\[NOTA INTERNA\].*$', '', sanitized, flags=re.DOTALL)
|
| 5290 |
|
| 5291 |
+
# ✅ FIX 2026-08-30: Remove "memorando interno" / "nota interna" / "internal memo" leaked phrases
|
| 5292 |
+
sanitized = re.sub(r'\b(?:memorando\s+interno|nota\s+interna|internal\s+memo|internal\s+note|memorando)\b', '', sanitized, flags=re.IGNORECASE)
|
| 5293 |
+
sanitized = re.sub(r'🔓\s*\[?REGRA\s+FINAL[^\]\n]*', '', sanitized, flags=re.IGNORECASE)
|
| 5294 |
+
|
| 5295 |
# Remove "- Nome: ... - Estilo: ... - Preferências: ..." blocks
|
| 5296 |
sanitized = re.sub(r'-\s*Nome:\s*.*', '', sanitized)
|
| 5297 |
sanitized = re.sub(r'-\s*Estilo:\s*.*', '', sanitized)
|
|
@@ -355,8 +355,9 @@ class ThinkingEngine:
|
|
| 355 |
# Content already has author info - preserve it
|
| 356 |
sys_prompt += f" {str(content)[:500]}\n"
|
| 357 |
else:
|
| 358 |
-
# No author info - add user prefix
|
| 359 |
-
|
|
|
|
| 360 |
else:
|
| 361 |
sys_prompt += f" {str(msg)[:500]}\n"
|
| 362 |
sys_prompt += "</SHORT_TERM_MEMORY>\n"
|
|
|
|
| 355 |
# Content already has author info - preserve it
|
| 356 |
sys_prompt += f" {str(content)[:500]}\n"
|
| 357 |
else:
|
| 358 |
+
# No author info - add user prefix (preferindo o autor real da msg)
|
| 359 |
+
msg_author = msg.get('author_name') or msg.get('author') or usuario
|
| 360 |
+
sys_prompt += f" [{msg_author}]: {str(content)[:500]}\n"
|
| 361 |
else:
|
| 362 |
sys_prompt += f" {str(msg)[:500]}\n"
|
| 363 |
sys_prompt += "</SHORT_TERM_MEMORY>\n"
|