Isaac Quarenta commited on
Commit
aa7ba3d
·
1 Parent(s): e2f4063

fix(akira): FINAL GUARD anti-leak + identity STM + regex memorando

Browse files

1. **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 ADDED
@@ -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
modules/api.py CHANGED
@@ -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-27: mover LENGTH para o final (recência) + lock final inegociável
3119
- _length_final = comprimento_constraint + "\n🔒 REGRA FINAL INEGOCIÁVEL: Se [RESPONSE LENGTH CONSTRAINT] diz 3-5 palavras, RESPONDA COM 3-5 PALAVRAS. Web_content é só contexto, NÃO expanda. Max 20 palavras cotidiano."
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)
modules/thinking_engine.py CHANGED
@@ -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
- sys_prompt += f" [{usuario}]: {str(content)[:500]}\n"
 
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"