Skip to content

Commit 5f2120d

Browse files
committed
chore: adding button to flush memories
1 parent 8c91987 commit 5f2120d

File tree

1 file changed

+114
-25
lines changed

1 file changed

+114
-25
lines changed

main_demo_released.py

Lines changed: 114 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
- Fatos: Semântico (threshold) + fallback EXACT→SEMANTIC
88
- Resposta no cache é NEUTRA (sem nome/cargo); personalização só na exibição
99
- Contexto forte: reescreve prompts AMBÍGUOS com "(no contexto de ...)" + system rígido "não cite outros sentidos"
10+
- FLUSH por UI: por escopo A, por escopo B, ou Ambos (A+B) — NUNCA apaga índice
1011
"""
1112

1213
import json
1314
import os
1415
import re
1516
import time
1617
from datetime import datetime
17-
from typing import Dict, List, Optional, Tuple
18+
from typing import Any, Dict, List, Optional, Tuple
1819

1920
import gradio as gr
2021
from dotenv import load_dotenv
@@ -123,21 +124,16 @@ def depersonalize_safe(text: str, person: Optional[str]) -> str:
123124

124125
# ============== Contexto / Desambiguação ==============
125126

126-
# termos ambíguos -> reforçar contexto
127127
AMBIGUOUS_TERMS = [
128-
r"\bc[eé]lula\b", # célula (biologia vs computação/planilhas)
129-
r"\bbanco\b", # banco (financeiro vs margem de rio)
130-
r"\brede\b", # rede (computadores vs hospital/saúde pública)
131-
r"\bmodelo\b", # modelo (ML vs negócio)
132-
r"\bpipeline\b", # pipeline (dados/software vs oleoduto)
128+
r"\bc[eé]lula\b",
129+
r"\bbanco\b",
130+
r"\brede\b",
131+
r"\bmodelo\b",
132+
r"\bpipeline\b",
133133
]
134134

135135
def infer_domain(company: str, bu: str, role: Optional[str]) -> str:
136-
"""
137-
Retorna rótulo curto de domínio para instrução: 'saúde', 'software', 'dados', 'finanças', 'turismo', etc.
138-
"""
139136
text = f"{company} {bu} {role or ''}".lower()
140-
141137
if any(k in text for k in ["saude", "clínica", "clinica", "medic", "hospital"]):
142138
return "saúde"
143139
if any(k in text for k in ["engenharia", "software", "dev", "produto", "ti", "tecnologia", "tech"]):
@@ -148,18 +144,13 @@ def infer_domain(company: str, bu: str, role: Optional[str]) -> str:
148144
return "finanças"
149145
if any(k in text for k in ["turismo", "eco", "aventura", "hotel", "viagem"]):
150146
return "turismo"
151-
# fallback neutro
152147
return "geral da área do usuário"
153148

154149
def looks_ambiguous(prompt: str) -> bool:
155150
p = (prompt or "").lower()
156151
return any(re.search(pat, p, flags=re.IGNORECASE) for pat in AMBIGUOUS_TERMS)
157152

158153
def rewrite_with_domain(prompt: str, domain_label: str) -> str:
159-
"""
160-
Se a pergunta for ambígua, reforça explicitamente o contexto.
161-
Ex.: "O que é uma célula? (no contexto de saúde)"
162-
"""
163154
clean = prompt.strip()
164155
if not clean.endswith("?"):
165156
clean += "?"
@@ -183,17 +174,13 @@ def call_openai(
183174
"Se a pergunta for ambígua (ex.: 'célula', 'rede', 'banco'), "
184175
f"RESPONDA APENAS no sentido de {domain} e NÃO mencione outros significados."
185176
)
186-
187-
# few-shot mínimo para ancorar comportamento
188177
examples = [
189178
{"role": "user", "content": "O que é uma célula? (no contexto de saúde)"},
190179
{"role": "assistant", "content": "Uma célula é a menor unidade estrutural e funcional dos seres vivos."},
191180
{"role": "user", "content": "O que é uma célula? (no contexto de engenharia de software)"},
192181
{"role": "assistant", "content": "Em computação, célula costuma se referir a uma unidade em uma tabela/planilha ou a um componente isolado de execução."},
193182
]
194-
195183
msgs = [{"role": "system", "content": system_ctx}, *examples, {"role": "user", "content": prompt}]
196-
197184
resp = openai_client.chat.completions.create(
198185
model=OPENAI_MODEL,
199186
messages=msgs,
@@ -281,8 +268,7 @@ def search_and_answer(
281268
strategies = [SearchStrategy.EXACT] if (SearchStrategy is not None) else None
282269
sim_thr = None # desliga semântico
283270

284-
# REESCRITA de prompt se for ambíguo (para o LLM e para o cache!)
285-
# - Mantém isolamento adicional por atributos; a chave de cache leva a versão reescrita.
271+
# Reescrita de ambíguos
286272
rewritten_prompt = prompt_original
287273
domain_label = infer_domain(company, bu, None)
288274
if looks_ambiguous(prompt_original):
@@ -334,10 +320,9 @@ def search_and_answer(
334320
if intent == "identity:role":
335321
llm_answer_neutral = "Não tenho sua função ainda. Diga: “Minha função é <cargo>” para eu guardar."
336322
else:
337-
# usar prompt reescrito quando ambíguo
338323
llm_answer_neutral = call_openai(
339324
rewritten_prompt if intent == "fact" else key_for_cache,
340-
person=None, # não usar nome em fatos
325+
person=None,
341326
company=company, bu=bu, role=None
342327
)
343328
llm_latency = time.perf_counter() - t1
@@ -359,13 +344,81 @@ def search_and_answer(
359344
latency_txt = f"[Cache Miss] busca: {cache_latency:.3f}s, llm: {llm_latency:.3f}s"
360345
return display_answer, "llm", json.dumps(debug, indent=2, ensure_ascii=False), latency_txt, tokens_est
361346

347+
# ============== FLUSH helpers ==============
348+
349+
def parse_deleted_count(res: Any) -> Optional[int]:
350+
# Tenta extrair 'deleted_entries_count' como atributo ou chave
351+
if hasattr(res, "deleted_entries_count"):
352+
return getattr(res, "deleted_entries_count", None)
353+
if isinstance(res, dict):
354+
return res.get("deleted_entries_count") or res.get("deleted") or res.get("deleted_count")
355+
return None
356+
357+
def flush_entries_with_attrs(attrs: Dict[str, str]) -> Tuple[str, str]:
358+
"""
359+
Chama delete_query(attributes=attrs). Nunca apaga índice.
360+
O backend exige pelo menos 1 atributo (attributes != {}).
361+
"""
362+
if not lang_cache:
363+
return "⚠️ LangCache não configurado; nenhum flush executado.", json.dumps({"attributes": attrs, "ok": False}, ensure_ascii=False, indent=2)
364+
try:
365+
res = lang_cache.delete_query(attributes=attrs)
366+
deleted = parse_deleted_count(res)
367+
msg = f"✅ Flush executado. Escopo={attrs}. Removidos={deleted if deleted is not None else '—'}"
368+
debug = {"attributes": attrs, "response": getattr(res, '__dict__', res)}
369+
return msg, json.dumps(debug, ensure_ascii=False, indent=2)
370+
except Exception as e:
371+
return f"❌ Erro no flush: {e}", json.dumps({"attributes": attrs, "error": str(e)}, ensure_ascii=False, indent=2)
372+
373+
def handle_flush_scope(company: str, bu: str, person: str, isolation: str):
374+
attrs = build_attributes(company or "", bu or "", person or "", isolation)
375+
if not attrs:
376+
return ("⚠️ Selecione um nível de isolamento diferente de 'none' para poder limpar por escopo.",
377+
json.dumps({"attributes": attrs, "error": "attributes cannot be blank"}, ensure_ascii=False, indent=2))
378+
return flush_entries_with_attrs(attrs)
379+
380+
def handle_flush_both(
381+
a_company: str, a_bu: str, a_person: str,
382+
b_company: str, b_bu: str, b_person: str,
383+
isolation: str,
384+
):
385+
"""
386+
Executa flush para os dois cenários (A e B), respeitando o isolamento atual.
387+
Útil porque o endpoint não aceita attributes={} (global).
388+
"""
389+
attrs_a = build_attributes(a_company or "", a_bu or "", a_person or "", isolation)
390+
attrs_b = build_attributes(b_company or "", b_bu or "", b_person or "", isolation)
391+
392+
msgs = []
393+
debugs = []
394+
395+
if attrs_a:
396+
msg_a, dbg_a = flush_entries_with_attrs(attrs_a)
397+
msgs.append(msg_a)
398+
debugs.append(json.loads(dbg_a))
399+
else:
400+
msgs.append("⚠️ Escopo A: isolamento 'none' não pode ser limpo.")
401+
debugs.append({"attributes": attrs_a, "error": "attributes cannot be blank"})
402+
403+
if attrs_b:
404+
msg_b, dbg_b = flush_entries_with_attrs(attrs_b)
405+
msgs.append(msg_b)
406+
debugs.append(json.loads(dbg_b))
407+
else:
408+
msgs.append("⚠️ Escopo B: isolamento 'none' não pode ser limpo.")
409+
debugs.append({"attributes": attrs_b, "error": "attributes cannot be blank"})
410+
411+
final_msg = "<br/>".join(msgs)
412+
return final_msg, json.dumps({"A": debugs[0], "B": debugs[1]}, ensure_ascii=False, indent=2)
413+
362414
# ============== UI / KPIs ==============
363415

364416
DESCRICAO_LONGA = """
365417
- Isolamento por atributos: company, business_unit, person.
366418
- Nome: sem cache; Cargo: EXACT ONLY.
367419
- Fatos: SEMANTIC + fallback EXACT→SEMANTIC.
368420
- Desambiguação forte: prompts ambíguos são reescritos com “(no contexto de …)”.
421+
- FLUSH: limpe entradas por escopo A/B ou ambos (A+B). O endpoint exige attributes != {}.
369422
"""
370423

371424
def format_currency(v: float, currency: str = "USD") -> str:
@@ -533,6 +586,11 @@ def handle_submit(
533586
a_source = gr.Label(label="Origem")
534587
a_latency = gr.Label(label="Latência")
535588
a_debug = gr.Code(label="Debug")
589+
# FLUSH A
590+
gr.Markdown("**Manutenção do Cache — A**")
591+
a_flush_btn = gr.Button("🧹 Limpar Cache (Escopo A)")
592+
a_flush_status = gr.HTML()
593+
a_flush_debug = gr.Code()
536594

537595
with gr.Column():
538596
gr.Markdown("#### Cenário B")
@@ -546,6 +604,11 @@ def handle_submit(
546604
b_source = gr.Label(label="Origem")
547605
b_latency = gr.Label(label="Latência")
548606
b_debug = gr.Code(label="Debug")
607+
# FLUSH B
608+
gr.Markdown("**Manutenção do Cache — B**")
609+
b_flush_btn = gr.Button("🧹 Limpar Cache (Escopo B)")
610+
b_flush_status = gr.HTML()
611+
b_flush_debug = gr.Code()
549612

550613
gr.Markdown("### Indicadores")
551614
with gr.Row(elem_classes=["kpi-row"]):
@@ -565,7 +628,14 @@ def handle_submit(
565628
col_count=(10, "fixed"),
566629
)
567630

568-
# Eventos
631+
# FLUSH "Ambos"
632+
gr.Markdown("---")
633+
gr.Markdown("### 🧹 Limpeza Combinada (A + B)")
634+
flush_both_btn = gr.Button("🧹 Limpar Ambos (A+B)")
635+
flush_both_status = gr.HTML()
636+
flush_both_debug = gr.Code()
637+
638+
# Eventos de pergunta
569639
a_btn.click(
570640
fn=handle_submit,
571641
inputs=[
@@ -600,6 +670,25 @@ def handle_submit(
600670
],
601671
)
602672

673+
# Eventos de FLUSH
674+
a_flush_btn.click(
675+
fn=handle_flush_scope,
676+
inputs=[a_company, a_bu, a_person, isolation_global],
677+
outputs=[a_flush_status, a_flush_debug],
678+
)
679+
680+
b_flush_btn.click(
681+
fn=handle_flush_scope,
682+
inputs=[b_company, b_bu, b_person, isolation_global],
683+
outputs=[b_flush_status, b_flush_debug],
684+
)
685+
686+
flush_both_btn.click(
687+
fn=handle_flush_both,
688+
inputs=[a_company, a_bu, a_person, b_company, b_bu, b_person, isolation_global],
689+
outputs=[flush_both_status, flush_both_debug],
690+
)
691+
603692
if __name__ == "__main__":
604693
if lang_cache:
605694
with lang_cache:

0 commit comments

Comments
 (0)