CharlieBonito commited on
Commit
f8bfc93
·
verified ·
1 Parent(s): 48e07c4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +163 -68
app.py CHANGED
@@ -9,21 +9,57 @@ import base64
9
  from PIL import Image
10
  import io
11
 
12
- # --- CONFIGURACIÓN ---
13
  MODEL_REPO = "CharlieBonito/clarity-guard-gemma4-7b"
14
  MODEL_FILE = "Checkpoint-375-Ollama-Clean-7.5B-Q4_K_M.gguf"
15
  MMPROJ_FILE = "mmproj-Checkpoint-375-Ollama-Clean-BF16.gguf"
16
  LLAMA_SERVER = "/opt/llama-cpp/llama-server"
17
  MODEL_DIR = "/app/models"
18
- # Usamos 0.0.0.0 para evitar restricciones de interfaz local
19
- SERVER_URL = "http://0.0.0.0:8080"
20
 
21
  server_process = None
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  def download_models():
24
  from huggingface_hub import hf_hub_download
25
  os.makedirs(MODEL_DIR, exist_ok=True)
26
- print("[DEBUG] Verificando modelos...")
27
  m = hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE, local_dir=MODEL_DIR)
28
  mm = hf_hub_download(repo_id=MODEL_REPO, filename=MMPROJ_FILE, local_dir=MODEL_DIR)
29
  return m, mm
@@ -33,113 +69,172 @@ def start_server():
33
  if server_process is not None and server_process.poll() is None:
34
  return True
35
 
36
- print("[DEBUG] Iniciando Llama Server...")
37
  m_path, mm_path = download_models()
38
-
39
- # CRÍTICO: Configurar las rutas de las librerías .so que el Docker copió
40
- env = os.environ.copy()
41
- env["LD_LIBRARY_PATH"] = f"/usr/local/lib:/usr/lib/x86_64-linux-gnu:{env.get('LD_LIBRARY_PATH', '')}"
42
 
 
 
 
 
43
  cmd = [
44
  LLAMA_SERVER,
45
  "-m", m_path,
46
  "--mmproj", mm_path,
47
  "--host", "0.0.0.0",
48
  "--port", "8080",
49
- "-c", "8192", # Bajamos un poco el contexto para asegurar estabilidad inicial
50
  "-ngl", "99",
51
  "--jinja"
52
  ]
53
 
54
- # Capturamos stdout y stderr para ver el error real en los logs
55
  server_process = subprocess.Popen(
56
- cmd,
57
- env=env,
58
- stdout=subprocess.PIPE,
59
- stderr=subprocess.STDOUT,
60
- text=True,
61
- bufsize=1 # Line buffered
62
  )
63
 
64
- # Hilo para imprimir los logs del servidor de IA en la consola de Gradio
65
  def log_reader():
66
  for line in iter(server_process.stdout.readline, ""):
67
- print(f"[LLAMA-SERVER] {line.strip()}")
68
-
69
  threading.Thread(target=log_reader, daemon=True).start()
70
 
71
- # Verificación de conexión
72
- for i in range(30):
73
- if server_process.poll() is not None:
74
- print("[ERROR] El servidor de IA se cerró inmediatamente. Revisa los logs arriba.")
75
- return False
76
  try:
77
  if requests.get(f"{SERVER_URL}/health", timeout=2).status_code == 200:
78
- print("[DEBUG] Servidor conectado exitosamente.")
79
  return True
80
  except:
81
- if i % 5 == 0: print(f"[DEBUG] Esperando al servidor... (intento {i})")
82
- time.sleep(3)
83
  return False
84
 
 
 
 
 
 
 
 
85
  # --- LÓGICA DE RESPUESTA ---
86
- def respond(message, image, history):
87
  if not start_server():
88
- yield "⚠️ Error: El servidor de IA no pudo arrancar. Mira la consola para ver el fallo de librería (.so)."
89
  return
90
 
91
- # Formato Gradio 6 (lista de dicts)
92
- messages = [{"role": "system", "content": "You are ClarityGuard."}]
93
- for m in history:
94
- messages.append({"role": m["role"], "content": m["content"]})
95
-
96
- # ... (Aquí va tu lógica de procesar imagen a base64 si existe) ...
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  try:
99
  r = requests.post(
100
  f"{SERVER_URL}/v1/chat/completions",
101
- json={"messages": messages, "stream": True},
102
- stream=True
103
  )
104
  full_res = ""
105
  for line in r.iter_lines():
106
  if line:
107
- decoded = line.decode("utf-8")[6:]
108
- if decoded.strip() == "[DONE]": break
109
- try:
110
- data = json.loads(decoded)
111
- content = data["choices"][0].get("delta", {}).get("content", "")
112
- full_res += content
113
- yield full_res
114
- except: continue
 
115
  except Exception as e:
116
  yield f"⚠️ Error de conexión: {str(e)}"
117
 
118
- # --- INTERFAZ ---
119
  with gr.Blocks(title="ClarityGuard v4.4") as demo:
120
  gr.Markdown("# 🔍 ClarityGuard v4.4")
121
- chatbot = gr.Chatbot(height=500, label="Historial")
 
 
 
122
  with gr.Row():
123
- msg = gr.Textbox(placeholder="Mensaje...", scale=4)
124
- img = gr.Image(type="filepath", scale=1)
125
-
126
- def user_fn(m, i, h):
127
- h = h or []
128
- h.append({"role": "user", "content": m or "[Imagen]"})
129
- return "", None, h
130
-
131
- def bot_fn(m, i, h):
132
- h.append({"role": "assistant", "content": ""})
133
- # Pasamos el historial excluyendo el último vacío
134
- for chunk in respond(m, i, h[:-1]):
135
- h[-1]["content"] = chunk
136
- yield h
137
-
138
- msg.submit(user_fn, [msg, img, chatbot], [msg, img, chatbot]).then(
139
- bot_fn, [msg, img, chatbot], [chatbot]
140
  )
141
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  if __name__ == "__main__":
143
- # Importante: No arrancar el servidor en un hilo aparte aquí,
144
- # dejar que la primera petición o el arranque de Gradio lo haga para ver errores.
145
- demo.launch(server_name="0.0.0.0", server_port=7860, theme=gr.themes.Soft())
 
 
 
9
  from PIL import Image
10
  import io
11
 
12
+ # --- CONFIGURACIÓN DE MODELO Y RUTAS ---
13
  MODEL_REPO = "CharlieBonito/clarity-guard-gemma4-7b"
14
  MODEL_FILE = "Checkpoint-375-Ollama-Clean-7.5B-Q4_K_M.gguf"
15
  MMPROJ_FILE = "mmproj-Checkpoint-375-Ollama-Clean-BF16.gguf"
16
  LLAMA_SERVER = "/opt/llama-cpp/llama-server"
17
  MODEL_DIR = "/app/models"
18
+ SERVER_URL = "http://0.0.0.0:8080" # Usamos 0.0.0.0 para estabilidad
 
19
 
20
  server_process = None
21
 
22
+ # --- SYSTEM PROMPT (ClarityGuard v4.4 completo) ---
23
+ CLARITYGUARD_PROMPT = """# CLARITYGUARD ASSISTANT — NEURO-INCLUSIVE EDITION v4.4
24
+ **Language policy:** Reply in the same language the user uses.
25
+ **Response initialization:** Every response must begin with a natural opener: "Got it.", "Sure!", "Hi there!" or "Understood."
26
+ ---
27
+ ## IDENTITY AND PURPOSE
28
+ You are **ClarityGuard**, specialized in clarity support for neurodivergent and autistic people in workplace and personal settings.
29
+ **Core function:** Determine whether the user's confusion originates in the **structure of the message**—not in a "failure" of the user.
30
+ **Foundational principle:** When a message lacks a clear subject, defined action, concrete date, or measurable criterion, confusion is the logical response to incomplete input. It is a **protocol mismatch**, not a cognitive error.
31
+ ---
32
+ ## ANALYSIS PROCESS (internal - never show to user)
33
+ C: [0–10] | F: [0–10] | R: [0–10] | V: [0–10] | A: [0–10]
34
+ TOTAL: [sum] / 50
35
+ Response modes:
36
+ - 0–10: Clear message. Confirm briefly.
37
+ - 11–20: Name the ambiguous element, suggest one clarification question.
38
+ - 21–30: Full analysis + clarification suggestion.
39
+ - 31–50: Full 4-step response + cognitive protection.
40
+ ---
41
+ ## RESPONSE STRUCTURE (4 STEPS)
42
+ ### STEP 1 — ANALYSIS
43
+ 🔍 **[ClarityGuard] C.F.R.V.A. score: XX/50 → [level]**
44
+ Explain what creates confusion using descriptive language about message structure.
45
+ ### STEP 2 — COGNITIVE PROTECTION (only if score ≥ 21)
46
+ 🔒 **Your confusion is not a failure. It is the correct response to an incomplete message.**
47
+ ### STEP 3 — CONCRETE ACTION (Read-Back)
48
+ ✍️ **Clarification suggestion:**
49
+ Offer a concrete clarification question.
50
+ ### STEP 4 — FOLLOW-UP PLAN (only if score ≥ 31)
51
+ ⏰ If clarification is still abstract, apply adjective decomposition.
52
+ ---
53
+ ## OPERATIONAL RULES
54
+ 1. If the message is clear, say so. 2. If ambiguous, name the missing element. 3. Protect against self-invalidation when score ≥ 21. 4. Never diagnose the sender. 5. Never attribute confusion to the user's cognitive profile. 6. Match length to channel. 7. Reply in the user's language. 8. Never output internal scoring.
55
+ ---
56
+ **Version:** ClarityGuard v4.4 — Neuro-inclusive"""
57
+
58
+ # --- FUNCIONES DEL SERVIDOR ---
59
  def download_models():
60
  from huggingface_hub import hf_hub_download
61
  os.makedirs(MODEL_DIR, exist_ok=True)
62
+ print("[DEBUG] Verificando/Descargando modelos...")
63
  m = hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE, local_dir=MODEL_DIR)
64
  mm = hf_hub_download(repo_id=MODEL_REPO, filename=MMPROJ_FILE, local_dir=MODEL_DIR)
65
  return m, mm
 
69
  if server_process is not None and server_process.poll() is None:
70
  return True
71
 
 
72
  m_path, mm_path = download_models()
 
 
 
 
73
 
74
+ # LD_LIBRARY_PATH es vital para que encuentre las librerías compiladas .so
75
+ env = os.environ.copy()
76
+ env["LD_LIBRARY_PATH"] = f"/usr/local/lib:/usr/local/cuda/lib64:/opt/llama-cpp:{env.get('LD_LIBRARY_PATH', '')}"
77
+
78
  cmd = [
79
  LLAMA_SERVER,
80
  "-m", m_path,
81
  "--mmproj", mm_path,
82
  "--host", "0.0.0.0",
83
  "--port", "8080",
84
+ "-c", "8192",
85
  "-ngl", "99",
86
  "--jinja"
87
  ]
88
 
89
+ print("[DEBUG] Lanzando Llama Server...")
90
  server_process = subprocess.Popen(
91
+ cmd, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1
 
 
 
 
 
92
  )
93
 
94
+ # Hilo para ver qué dice el servidor en los logs
95
  def log_reader():
96
  for line in iter(server_process.stdout.readline, ""):
97
+ print(f"[IA-LOG] {line.strip()}")
 
98
  threading.Thread(target=log_reader, daemon=True).start()
99
 
100
+ # Esperar conexión
101
+ for i in range(45):
102
+ if server_process.poll() is not None: return False
 
 
103
  try:
104
  if requests.get(f"{SERVER_URL}/health", timeout=2).status_code == 200:
105
+ print("[DEBUG] Servidor de IA conectado en puerto 8080.")
106
  return True
107
  except:
108
+ time.sleep(2)
 
109
  return False
110
 
111
+ def image_to_base64(image_path: str) -> str:
112
+ with Image.open(image_path) as img:
113
+ if img.mode in ("RGBA", "P"): img = img.convert("RGB")
114
+ buffer = io.BytesIO()
115
+ img.save(buffer, format="JPEG", quality=85)
116
+ return base64.b64encode(buffer.getvalue()).decode("utf-8")
117
+
118
  # --- LÓGICA DE RESPUESTA ---
119
+ def respond(message: str, image_path, history: list):
120
  if not start_server():
121
+ yield "⚠️ Error: El servidor de IA no responde. Revisa los logs de la consola."
122
  return
123
 
124
+ messages = [{"role": "system", "content": CLARITYGUARD_PROMPT}]
 
 
 
 
 
125
 
126
+ # Historial en formato Gradio 6 (lista de dicts)
127
+ for msg in history:
128
+ messages.append({"role": msg["role"], "content": msg["content"]})
129
+
130
+ # Contenido multimodal
131
+ if image_path:
132
+ try:
133
+ img_b64 = image_to_base64(image_path)
134
+ user_content = [
135
+ {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"}},
136
+ {"type": "text", "text": message if message.strip() else "Analiza esta imagen."}
137
+ ]
138
+ except Exception as e:
139
+ user_content = message + f"\n[Error de imagen: {e}]"
140
+ else:
141
+ user_content = message
142
+
143
+ messages.append({"role": "user", "content": user_content})
144
+
145
  try:
146
  r = requests.post(
147
  f"{SERVER_URL}/v1/chat/completions",
148
+ json={"messages": messages, "stream": True, "temperature": 0.7},
149
+ stream=True, timeout=120
150
  )
151
  full_res = ""
152
  for line in r.iter_lines():
153
  if line:
154
+ chunk = line.decode("utf-8")
155
+ if chunk.startswith("data: "):
156
+ content = chunk[6:]
157
+ if content.strip() == "[DONE]": break
158
+ try:
159
+ data = json.loads(content)
160
+ full_res += data["choices"][0].get("delta", {}).get("content", "")
161
+ yield full_res
162
+ except: continue
163
  except Exception as e:
164
  yield f"⚠️ Error de conexión: {str(e)}"
165
 
166
+ # --- INTERFAZ GRADIO 6 ---
167
  with gr.Blocks(title="ClarityGuard v4.4") as demo:
168
  gr.Markdown("# 🔍 ClarityGuard v4.4")
169
+ gr.Markdown("Análisis neuro-inclusivo. Captura de pantalla o texto.")
170
+
171
+ chatbot = gr.Chatbot(height=520, label="ClarityGuard")
172
+
173
  with gr.Row():
174
+ msg_input = gr.Textbox(label="Mensaje", placeholder="Escribe aquí...", lines=3, scale=4)
175
+ image_input = gr.Image(label="📎 Captura", type="filepath", sources=["upload", "clipboard"], scale=1, height=120)
176
+
177
+ with gr.Row():
178
+ submit_btn = gr.Button("🔍 Analizar", variant="primary", scale=3)
179
+ clear_btn = gr.Button("🗑️ Limpiar", scale=1)
180
+
181
+ # RE-AGREGADOS: Los ejemplos que se habían perdido
182
+ gr.Examples(
183
+ examples=[
184
+ ["\"Nos vemos el lunes por la tarde.\"", None],
185
+ ["\"Necesitamos arreglar esto ASAP.\"", None],
186
+ ["\" más proactivo en las reuniones.\"", None],
187
+ ["\"Estaré de vuelta en 5 minutos.\"", None],
188
+ ],
189
+ inputs=[msg_input, image_input]
 
190
  )
191
 
192
+ # --- Handlers ---
193
+ def user_action(message, image, history):
194
+ if history is None: history = []
195
+ display_text = message or ""
196
+ if image:
197
+ display_text = (display_text + " [📎 imagen adjunta]").strip()
198
+ history.append({"role": "user", "content": display_text})
199
+ return history
200
+
201
+ def bot_action(message, image, history):
202
+ real_msg = message or ""
203
+ if not real_msg.strip() and image:
204
+ real_msg = "Analiza este mensaje de la imagen."
205
+
206
+ # Limpiar historial de tags visuales para la IA
207
+ clean_history = []
208
+ for m in history[:-1]:
209
+ content = m["content"].replace(" [📎 imagen adjunta]", "")
210
+ clean_history.append({"role": m["role"], "content": content})
211
+
212
+ history.append({"role": "assistant", "content": ""})
213
+ for chunk in respond(real_msg, image, clean_history):
214
+ history[-1]["content"] = chunk
215
+ yield history
216
+
217
+ def clear_inputs():
218
+ return "", None
219
+
220
+ # Flujo: Usuario -> Bot -> Limpiar cajas
221
+ submit_btn.click(user_action, [msg_input, image_input, chatbot], [chatbot]).then(
222
+ bot_action, [msg_input, image_input, chatbot], [chatbot]
223
+ ).then(
224
+ clear_inputs, outputs=[msg_input, image_input]
225
+ )
226
+
227
+ msg_input.submit(user_action, [msg_input, image_input, chatbot], [chatbot]).then(
228
+ bot_action, [msg_input, image_input, chatbot], [chatbot]
229
+ ).then(
230
+ clear_inputs, outputs=[msg_input, image_input]
231
+ )
232
+
233
+ clear_btn.click(lambda: ([], "", None), outputs=[chatbot, msg_input, image_input])
234
+
235
  if __name__ == "__main__":
236
+ demo.launch(
237
+ server_name="0.0.0.0",
238
+ server_port=7860,
239
+ theme=gr.themes.Soft()
240
+ )