caarleexx commited on
Commit
ec3b193
·
verified ·
1 Parent(s): 84f2bb8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +57 -109
app.py CHANGED
@@ -1,58 +1,59 @@
1
- # app.py — Isaac (Gemma 2 9B + Intent Gateway + RAG Filosófico)
2
  import os
3
- import re
4
  import json
5
- import torch
6
  import gradio as gr
7
- from typing import List, Dict, Any, Tuple
8
 
9
- from transformers import (
10
- AutoModelForCausalLM,
11
- AutoTokenizer,
12
- BitsAndBytesConfig,
13
- )
14
 
15
- # -------- Config do Modelo --------
16
- MODEL_ID = os.environ.get("MODEL_ID", "google/gemma-2-9b-it") # requer aceitar termos no Hub
17
- USE_4BIT = os.environ.get("USE_4BIT", "1") == "1"
 
 
 
 
18
  MAX_NEW_TOKENS = int(os.environ.get("MAX_NEW_TOKENS", "512"))
19
  TEMPERATURE = float(os.environ.get("TEMPERATURE", "0.7"))
20
  TOP_P = float(os.environ.get("TOP_P", "0.9"))
 
21
 
22
- # -------- Fonte filosófica --------
23
- EPC_MD_PATH = os.environ.get("EPC_MD_PATH", "epct0.md") # seu arquivo existente
 
 
 
24
 
25
- # -------- Recuperação simples (TF-IDF) --------
26
- from sklearn.feature_extraction.text import TfidfVectorizer
27
- from sklearn.metrics.pairwise import cosine_similarity
 
 
 
 
 
 
28
 
 
29
  def parse_epct_table(md_path: str) -> List[Dict[str, Any]]:
30
- """
31
- Lê uma tabela Markdown com colunas: Id | Resumo | Porquês | Tags
32
- e retorna uma lista de itens com esses campos.
33
- """
34
- with open(md_path, "r", encoding="utf-8") as f:
35
- text = f.read()
36
-
37
  rows = []
38
- for line in text.splitlines():
39
- line = line.strip()
40
- # linhas de dados começam com '|' e têm pelo menos 4 colunas
41
- if line.startswith("|") and not set(line).issubset(set("|:- ")):
42
- parts = [c.strip() for c in line.strip("|").split("|")]
43
- if len(parts) >= 4 and parts[0] != "Id":
44
- rows.append({
45
- "id": parts[0],
46
- "resumo": parts[1],
47
- "porques": parts[2],
48
- "tags": parts[3]
49
- })
50
  return rows
51
 
52
  DOCS = parse_epct_table(EPC_MD_PATH)
53
- DOC_TEXTS = [
54
- f"{d['resumo']} {d['porques']} {d['tags']}" for d in DOCS
55
- ]
56
  VECTORIZER = TfidfVectorizer(stop_words="portuguese", max_features=4096)
57
  DOC_MATRIX = VECTORIZER.fit_transform(DOC_TEXTS)
58
 
@@ -65,37 +66,11 @@ def retrieve_guidelines(query: str, k: int = 4) -> List[Dict[str, Any]]:
65
  return [DOCS[i] for i in idxs]
66
 
67
  def format_guidelines(items: List[Dict[str, Any]]) -> str:
68
- lines = []
69
- for d in items:
70
- lines.append(f"- [{d['id']}] {d['resumo']} | Porquês: {d['porques']} | Tags: {d['tags']}")
71
- return "\n".join(lines) if lines else "- (nenhuma diretriz selecionada)"
72
-
73
- # -------- Carregamento do Modelo --------
74
- bnb_config = None
75
- if USE_4BIT:
76
- bnb_config = BitsAndBytesConfig(
77
- load_in_4bit=True,
78
- bnb_4bit_use_double_quant=True,
79
- bnb_4bit_quant_type="nf4",
80
- bnb_4bit_compute_dtype=torch.float16,
81
- )
82
-
83
- tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, use_fast=True)
84
- model = AutoModelForCausalLM.from_pretrained(
85
- MODEL_ID,
86
- device_map="auto",
87
- torch_dtype=torch.float16 if not USE_4BIT else None,
88
- quantization_config=bnb_config,
89
- )
90
-
91
- # -------- Prompting / Template --------
92
- SYSTEM_RULES = (
93
- "Regras: 1) Sempre pergunte objetivo e contexto do pedido antes de responder; "
94
- "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; "
95
- "3) Seja específico e explique a relação entre as diretrizes escolhidas e o plano proposto; "
96
- "4) Mantenha tom claro e conciso."
97
- )
98
 
 
99
  def make_messages(initial_request: str, intent: str, selected_items: List[Dict[str, Any]]) -> List[Dict[str, str]]:
100
  guidelines_block = format_guidelines(selected_items)
101
  user_compound = (
@@ -111,44 +86,28 @@ def make_messages(initial_request: str, intent: str, selected_items: List[Dict[s
111
  {"role": "user", "content": user_compound},
112
  ]
113
 
114
- def generate_with_template(messages: List[Dict[str, str]]) -> str:
115
- input_ids = tokenizer.apply_chat_template(
116
- messages,
117
- add_generation_prompt=True,
118
- return_tensors="pt"
119
- ).to(model.device)
120
-
121
- outputs = model.generate(
122
- input_ids=input_ids,
123
- max_new_tokens=MAX_NEW_TOKENS,
124
- do_sample=True,
125
  temperature=TEMPERATURE,
126
  top_p=TOP_P,
127
- eos_token_id=tokenizer.eos_token_id,
128
- pad_token_id=tokenizer.eos_token_id,
129
  )
130
- text = tokenizer.decode(outputs[0][input_ids.shape[-1]:], skip_special_tokens=True)
131
- return text.strip()
132
 
133
- # -------- Lógica de Conversa (Gateway de Intenção) --------
134
- # Estados por sessão: {"stage": "await_intent" | "ready", "initial_request": str, "intent": str, "items": [...]}
135
  def chat_turn(user_input: str, state: Dict[str, Any]):
136
  if state is None or "stage" not in state:
137
  state = {"stage": "await_intent", "initial_request": "", "intent": "", "items": []}
138
 
139
  if state["stage"] == "await_intent":
140
- # Primeiro turno: tratar user_input como pedido inicial e perguntar intenção
141
  state["initial_request"] = user_input
142
- reply = (
143
- "Antes de responder, poderia explicar seu objetivo e contexto? "
144
- "O que você quer alcançar e por quê isso é importante agora?"
145
- )
146
- # permanecer aguardando intenção do usuário
147
  state["stage"] = "collect_intent"
148
  return reply, state
149
 
150
  if state["stage"] == "collect_intent":
151
- # Recebe o 'porquê/para quê', seleciona diretrizes e mostra scaffold
152
  state["intent"] = user_input
153
  items = retrieve_guidelines(user_input, k=4)
154
  state["items"] = items
@@ -168,29 +127,20 @@ def chat_turn(user_input: str, state: Dict[str, Any]):
168
  return "\n".join(scaffold), state
169
 
170
  if state["stage"] == "ready":
171
- # Gera resposta final usando o modelo, com as diretrizes no contexto
172
  if user_input.strip().lower() not in {"prossiga", "ok", "pode seguir", "pode prosseguir"}:
173
- # exigir confirmação simples para manter o fluxo
174
  return "Confirme com 'prossiga' para gerar a resposta final baseada no preparo acima.", state
175
 
176
- messages = make_messages(
177
- state["initial_request"],
178
- state["intent"],
179
- state["items"]
180
- )
181
- out = generate_with_template(messages)
182
-
183
- # reset ciclo
184
  state = {"stage": "await_intent", "initial_request": "", "intent": "", "items": []}
185
- return out, state
186
 
187
- # fallback defensivo
188
  state = {"stage": "await_intent", "initial_request": "", "intent": "", "items": []}
189
  return "Vamos recomeçar: descreva seu pedido inicial.", state
190
 
191
  # -------- UI --------
192
- with gr.Blocks(title="Isaac — Gemma 2 9B") as demo:
193
- gr.Markdown("# Isaac — Gemma 2 9B\nFluxo: Intenção → Preparação → Resposta.", elem_id="title")
194
  state = gr.State({"stage": "await_intent", "initial_request": "", "intent": "", "items": []})
195
  chat = gr.Chatbot(height=480)
196
  inp = gr.Textbox(label="Mensagem", placeholder="Descreva seu pedido...")
@@ -204,6 +154,4 @@ with gr.Blocks(title="Isaac — Gemma 2 9B") as demo:
204
  inp.submit(ui_handle, [inp, state], [chat, state, inp])
205
 
206
  if __name__ == "__main__":
207
- # gradio server
208
  demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)))
209
-
 
1
+ # app.py — Isaac (CPU, Gemma 2 2B GGUF via llama-cpp-python)
2
  import os
 
3
  import json
 
4
  import gradio as gr
5
+ from typing import List, Dict, Any
6
 
7
+ from sklearn.feature_extraction.text import TfidfVectorizer
8
+ from sklearn.metrics.pairwise import cosine_similarity
 
 
 
9
 
10
+ from llama_cpp import Llama
11
+
12
+ # -------- Config --------
13
+ MODEL_PATH = os.environ.get("MODEL_PATH", "gemma-2-2b-it-Q4_K_M.gguf") # aponte para o GGUF local
14
+ N_CTX = int(os.environ.get("N_CTX", "4096"))
15
+ N_THREADS = int(os.environ.get("N_THREADS", "8"))
16
+ N_BATCH = int(os.environ.get("N_BATCH", "128"))
17
  MAX_NEW_TOKENS = int(os.environ.get("MAX_NEW_TOKENS", "512"))
18
  TEMPERATURE = float(os.environ.get("TEMPERATURE", "0.7"))
19
  TOP_P = float(os.environ.get("TOP_P", "0.9"))
20
+ EPC_MD_PATH = os.environ.get("EPC_MD_PATH", "epct0-3.md")
21
 
22
+ SYSTEM_RULES = (
23
+ "Regras: 1) Sempre pergunte objetivo e contexto do pedido antes de responder; "
24
+ "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; "
25
+ "3) Explique a relação entre as diretrizes escolhidas e o plano; 4) Seja claro e conciso."
26
+ )
27
 
28
+ # -------- Carregar modelo (CPU) --------
29
+ llm = Llama(
30
+ model_path=MODEL_PATH,
31
+ chat_format="gemma", # usa o formato nativo de chat do Gemma
32
+ n_ctx=N_CTX,
33
+ n_threads=N_THREADS,
34
+ n_batch=N_BATCH,
35
+ verbose=False,
36
+ )
37
 
38
+ # -------- Parser + RAG leve --------
39
  def parse_epct_table(md_path: str) -> List[Dict[str, Any]]:
 
 
 
 
 
 
 
40
  rows = []
41
+ with open(md_path, "r", encoding="utf-8") as f:
42
+ for line in f:
43
+ line = line.strip()
44
+ if line.startswith("|") and not set(line).issubset(set("|:- ")):
45
+ parts = [c.strip() for c in line.strip("|").split("|")]
46
+ if len(parts) >= 4 and parts[0] != "Id":
47
+ rows.append({
48
+ "id": parts[0],
49
+ "resumo": parts[1],
50
+ "porques": parts[2],
51
+ "tags": parts[3]
52
+ })
53
  return rows
54
 
55
  DOCS = parse_epct_table(EPC_MD_PATH)
56
+ DOC_TEXTS = [f"{d['resumo']} {d['porques']} {d['tags']}" for d in DOCS]
 
 
57
  VECTORIZER = TfidfVectorizer(stop_words="portuguese", max_features=4096)
58
  DOC_MATRIX = VECTORIZER.fit_transform(DOC_TEXTS)
59
 
 
66
  return [DOCS[i] for i in idxs]
67
 
68
  def format_guidelines(items: List[Dict[str, Any]]) -> str:
69
+ if not items:
70
+ return "- (nenhuma diretriz selecionada)"
71
+ return "\n".join([f"- [{d['id']}] {d['resumo']} | Porquês: {d['porques']} | Tags: {d['tags']}" for d in items])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
+ # -------- Mensagens e geração --------
74
  def make_messages(initial_request: str, intent: str, selected_items: List[Dict[str, Any]]) -> List[Dict[str, str]]:
75
  guidelines_block = format_guidelines(selected_items)
76
  user_compound = (
 
86
  {"role": "user", "content": user_compound},
87
  ]
88
 
89
+ def generate(messages: List[Dict[str, str]]) -> str:
90
+ out = llm.create_chat_completion(
91
+ messages=messages,
 
 
 
 
 
 
 
 
92
  temperature=TEMPERATURE,
93
  top_p=TOP_P,
94
+ max_tokens=MAX_NEW_TOKENS,
 
95
  )
96
+ return out["choices"][0]["message"]["content"].strip()
 
97
 
98
+ # -------- Máquina de estados --------
 
99
  def chat_turn(user_input: str, state: Dict[str, Any]):
100
  if state is None or "stage" not in state:
101
  state = {"stage": "await_intent", "initial_request": "", "intent": "", "items": []}
102
 
103
  if state["stage"] == "await_intent":
 
104
  state["initial_request"] = user_input
105
+ reply = ("Antes de responder, poderia explicar seu objetivo e contexto? "
106
+ "O que você quer alcançar e por quê isso é importante agora?")
 
 
 
107
  state["stage"] = "collect_intent"
108
  return reply, state
109
 
110
  if state["stage"] == "collect_intent":
 
111
  state["intent"] = user_input
112
  items = retrieve_guidelines(user_input, k=4)
113
  state["items"] = items
 
127
  return "\n".join(scaffold), state
128
 
129
  if state["stage"] == "ready":
 
130
  if user_input.strip().lower() not in {"prossiga", "ok", "pode seguir", "pode prosseguir"}:
 
131
  return "Confirme com 'prossiga' para gerar a resposta final baseada no preparo acima.", state
132
 
133
+ messages = make_messages(state["initial_request"], state["intent"], state["items"])
134
+ text = generate(messages)
 
 
 
 
 
 
135
  state = {"stage": "await_intent", "initial_request": "", "intent": "", "items": []}
136
+ return text, state
137
 
 
138
  state = {"stage": "await_intent", "initial_request": "", "intent": "", "items": []}
139
  return "Vamos recomeçar: descreva seu pedido inicial.", state
140
 
141
  # -------- UI --------
142
+ with gr.Blocks(title="Isaac — CPU (Gemma 2 2B GGUF)") as demo:
143
+ gr.Markdown("# Isaac — CPU (Gemma 2 2B)\nFluxo: Intenção → Preparação → Resposta.")
144
  state = gr.State({"stage": "await_intent", "initial_request": "", "intent": "", "items": []})
145
  chat = gr.Chatbot(height=480)
146
  inp = gr.Textbox(label="Mensagem", placeholder="Descreva seu pedido...")
 
154
  inp.submit(ui_handle, [inp, state], [chat, state, inp])
155
 
156
  if __name__ == "__main__":
 
157
  demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)))