|
|
|
|
|
import os |
|
|
import json |
|
|
import gradio as gr |
|
|
from typing import List, Dict, Any |
|
|
|
|
|
from sklearn.feature_extraction.text import TfidfVectorizer |
|
|
from sklearn.metrics.pairwise import cosine_similarity |
|
|
|
|
|
from llama_cpp import Llama |
|
|
|
|
|
|
|
|
MODEL_PATH = os.environ.get("MODEL_PATH", "gemma-2-2b-it-Q4_K_M.gguf") |
|
|
N_CTX = int(os.environ.get("N_CTX", "4096")) |
|
|
N_THREADS = int(os.environ.get("N_THREADS", "8")) |
|
|
N_BATCH = int(os.environ.get("N_BATCH", "128")) |
|
|
MAX_NEW_TOKENS = int(os.environ.get("MAX_NEW_TOKENS", "512")) |
|
|
TEMPERATURE = float(os.environ.get("TEMPERATURE", "0.7")) |
|
|
TOP_P = float(os.environ.get("TOP_P", "0.9")) |
|
|
EPC_MD_PATH = os.environ.get("EPC_MD_PATH", "epct0-3.md") |
|
|
|
|
|
SYSTEM_RULES = ( |
|
|
"Regras: 1) Sempre pergunte objetivo e contexto do pedido antes de responder; " |
|
|
"2) Após entender o 'porquê/para quê', aplique um checklist filosófico (controle, impermanência, obstáculos, julgamentos, prioridades, papéis) e só então responda; " |
|
|
"3) Explique a relação entre as diretrizes escolhidas e o plano; 4) Seja claro e conciso." |
|
|
) |
|
|
|
|
|
|
|
|
llm = Llama( |
|
|
model_path=MODEL_PATH, |
|
|
chat_format="gemma", |
|
|
n_ctx=N_CTX, |
|
|
n_threads=N_THREADS, |
|
|
n_batch=N_BATCH, |
|
|
verbose=False, |
|
|
) |
|
|
|
|
|
|
|
|
def parse_epct_table(md_path: str) -> List[Dict[str, Any]]: |
|
|
rows = [] |
|
|
with open(md_path, "r", encoding="utf-8") as f: |
|
|
for line in f: |
|
|
line = line.strip() |
|
|
if line.startswith("|") and not set(line).issubset(set("|:- ")): |
|
|
parts = [c.strip() for c in line.strip("|").split("|")] |
|
|
if len(parts) >= 4 and parts[0] != "Id": |
|
|
rows.append({ |
|
|
"id": parts[0], |
|
|
"resumo": parts[1], |
|
|
"porques": parts[2], |
|
|
"tags": parts[3] |
|
|
}) |
|
|
return rows |
|
|
|
|
|
DOCS = parse_epct_table(EPC_MD_PATH) |
|
|
DOC_TEXTS = [f"{d['resumo']} {d['porques']} {d['tags']}" for d in DOCS] |
|
|
VECTORIZER = TfidfVectorizer(stop_words="portuguese", max_features=4096) |
|
|
DOC_MATRIX = VECTORIZER.fit_transform(DOC_TEXTS) |
|
|
|
|
|
def retrieve_guidelines(query: str, k: int = 4) -> List[Dict[str, Any]]: |
|
|
if not query.strip(): |
|
|
return [] |
|
|
qv = VECTORIZER.transform([query]) |
|
|
sims = cosine_similarity(qv, DOC_MATRIX)[0] |
|
|
idxs = sims.argsort()[::-1][:k] |
|
|
return [DOCS[i] for i in idxs] |
|
|
|
|
|
def format_guidelines(items: List[Dict[str, Any]]) -> str: |
|
|
if not items: |
|
|
return "- (nenhuma diretriz selecionada)" |
|
|
return "\n".join([f"- [{d['id']}] {d['resumo']} | Porquês: {d['porques']} | Tags: {d['tags']}" for d in items]) |
|
|
|
|
|
|
|
|
def make_messages(initial_request: str, intent: str, selected_items: List[Dict[str, Any]]) -> List[Dict[str, str]]: |
|
|
guidelines_block = format_guidelines(selected_items) |
|
|
user_compound = ( |
|
|
f"Pedido original:\n{initial_request}\n\n" |
|
|
f"Objetivo/porquê declarado:\n{intent}\n\n" |
|
|
f"Diretrizes selecionadas (do corpus):\n{guidelines_block}\n\n" |
|
|
"Tarefa:\n1) Produza uma 'Preparação' breve (itens do checklist aplicados ao caso).\n" |
|
|
"2) Em seguida, produza 'Resposta' com passos claros e proporcionais ao objetivo.\n" |
|
|
"3) Mostre a conexão entre as diretrizes e a recomendação.\n" |
|
|
) |
|
|
return [ |
|
|
{"role": "system", "content": SYSTEM_RULES}, |
|
|
{"role": "user", "content": user_compound}, |
|
|
] |
|
|
|
|
|
def generate(messages: List[Dict[str, str]]) -> str: |
|
|
out = llm.create_chat_completion( |
|
|
messages=messages, |
|
|
temperature=TEMPERATURE, |
|
|
top_p=TOP_P, |
|
|
max_tokens=MAX_NEW_TOKENS, |
|
|
) |
|
|
return out["choices"][0]["message"]["content"].strip() |
|
|
|
|
|
|
|
|
def chat_turn(user_input: str, state: Dict[str, Any]): |
|
|
if state is None or "stage" not in state: |
|
|
state = {"stage": "await_intent", "initial_request": "", "intent": "", "items": []} |
|
|
|
|
|
if state["stage"] == "await_intent": |
|
|
state["initial_request"] = user_input |
|
|
reply = ("Antes de responder, poderia explicar seu objetivo e contexto? " |
|
|
"O que você quer alcançar e por quê isso é importante agora?") |
|
|
state["stage"] = "collect_intent" |
|
|
return reply, state |
|
|
|
|
|
if state["stage"] == "collect_intent": |
|
|
state["intent"] = user_input |
|
|
items = retrieve_guidelines(user_input, k=4) |
|
|
state["items"] = items |
|
|
scaffold = [ |
|
|
"Preparação (rascunho):", |
|
|
"- Controle: separe o que depende de você e foque no controlável.", |
|
|
"- Perspectiva: identifique julgamentos que amplificam o problema.", |
|
|
"- Obstáculos: antecipe contratempos realistas e pré-compromissos.", |
|
|
"- Prioridades/Papéis: alinhe ação ao essencial e ao seu papel atual.", |
|
|
"", |
|
|
"Diretrizes candidatas:", |
|
|
format_guidelines(items), |
|
|
"", |
|
|
"Se estiver de acordo, diga 'prossiga' para receber a resposta estruturada." |
|
|
] |
|
|
state["stage"] = "ready" |
|
|
return "\n".join(scaffold), state |
|
|
|
|
|
if state["stage"] == "ready": |
|
|
if user_input.strip().lower() not in {"prossiga", "ok", "pode seguir", "pode prosseguir"}: |
|
|
return "Confirme com 'prossiga' para gerar a resposta final baseada no preparo acima.", state |
|
|
|
|
|
messages = make_messages(state["initial_request"], state["intent"], state["items"]) |
|
|
text = generate(messages) |
|
|
state = {"stage": "await_intent", "initial_request": "", "intent": "", "items": []} |
|
|
return text, state |
|
|
|
|
|
state = {"stage": "await_intent", "initial_request": "", "intent": "", "items": []} |
|
|
return "Vamos recomeçar: descreva seu pedido inicial.", state |
|
|
|
|
|
|
|
|
with gr.Blocks(title="Isaac — CPU (Gemma 2 2B GGUF)") as demo: |
|
|
gr.Markdown("# Isaac — CPU (Gemma 2 2B)\nFluxo: Intenção → Preparação → Resposta.") |
|
|
state = gr.State({"stage": "await_intent", "initial_request": "", "intent": "", "items": []}) |
|
|
chat = gr.Chatbot(height=480) |
|
|
inp = gr.Textbox(label="Mensagem", placeholder="Descreva seu pedido...") |
|
|
send = gr.Button("Enviar") |
|
|
|
|
|
def ui_handle(user_msg, s): |
|
|
reply, s2 = chat_turn(user_msg or "", s) |
|
|
return chat + [[user_msg, reply]], s2, "" |
|
|
|
|
|
send.click(ui_handle, [inp, state], [chat, state, inp]) |
|
|
inp.submit(ui_handle, [inp, state], [chat, state, inp]) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860))) |
|
|
|