vecervantes89 commited on
Commit
c66f085
·
verified ·
1 Parent(s): 7416d40

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +85 -59
app.py CHANGED
@@ -2,139 +2,163 @@ import os
2
  import re
3
  import pdfplumber
4
  import gradio as gr
5
- from openai import OpenAI
6
- from huggingface_hub import hf_hub_download, list_repo_files
7
  from dotenv import load_dotenv
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  # ------------------------------------------------------------
10
- # CONFIGURACIÓN DEL CLIENTE OPENAI
11
  # ------------------------------------------------------------
12
  load_dotenv()
13
- client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
 
 
 
 
14
 
15
  # ------------------------------------------------------------
16
- # CONFIGURACIÓN DEL ASISTENTE
17
  # ------------------------------------------------------------
18
  system_prompt = """
19
- Eres un Asistente de Inteligencia Artificial especializado en Auditoría Interna,
20
- formado bajo las Normas Internacionales para la Práctica Profesional de la Auditoría Interna
21
- emitidas por el IIA (Institute of Internal Auditors).
22
-
23
- Tu función es apoyar a auditores internos en análisis, planeación, ejecución,
24
- evaluación y documentación de auditorías, así como en la preparación para el
25
- examen CIA (Certified Internal Auditor). Tus respuestas deben reflejar:
26
- - Objetividad, integridad y confidencialidad.
27
- - Los valores de Gentera: Responsabilidad, Empatía, Innovación y Transparencia.
28
- - Lenguaje claro, profesional y humano.
29
-
30
- Si la pregunta se relaciona con auditoría, control interno, riesgos o ética profesional,
31
- responde con rigor técnico y ejemplos prácticos. Si se pide un resumen de un PDF,
32
- integra el contenido del documento correspondiente.
33
  """
34
 
35
  # ------------------------------------------------------------
36
- # CARGA DE PDFs DESDE HUGGING FACE (DATASET)
37
  # ------------------------------------------------------------
38
  REPO_ID = "vecervantes89/auditoria_interna_pdfs"
39
  REPO_TYPE = "dataset"
40
 
41
  def extract_pdf_text(local_path: str) -> str:
42
- text_parts = []
43
  with pdfplumber.open(local_path) as pdf:
44
- for page in pdf.pages:
45
- text_parts.append(page.extract_text() or "")
46
- return "\n".join(text_parts)
47
 
48
  def load_hf_pdfs_text(repo_id: str, repo_type: str = "dataset"):
49
  try:
50
  files = [f for f in list_repo_files(repo_id=repo_id, repo_type=repo_type) if f.lower().endswith(".pdf")]
51
  except Exception as e:
52
- print(f"[ERROR] No se pudo listar los archivos del repo '{repo_id}': {e}")
53
- return {"files": [], "all_text": "", "by_name": {}}
54
 
55
  entries = []
56
  for f in files:
57
  try:
58
- local_path = hf_hub_download(repo_id=repo_id, filename=f, repo_type=repo_type)
59
- text = extract_pdf_text(local_path)
60
  entries.append({"name": f, "text": text})
61
  print(f"[OK] Cargado {f}")
62
  except Exception as e:
63
  print(f"[ERROR] Falló la carga de {f}: {e}")
64
 
65
- all_text = "\n\n".join(e["text"] for e in entries)
66
  by_name = {e["name"]: e["text"] for e in entries}
67
- print(f"[INFO] Se cargaron {len(entries)} PDFs correctamente desde {repo_id}.")
68
- return {"files": entries, "all_text": all_text, "by_name": by_name}
 
69
 
70
  HF_DOCS = load_hf_pdfs_text(REPO_ID, REPO_TYPE)
71
 
72
  # ------------------------------------------------------------
73
- # LÓGICA DEL CHAT
74
  # ------------------------------------------------------------
75
  def buscar_mejor_fragmento(pregunta: str, docs: dict, max_chars: int = 3000):
76
- q = pregunta.lower()
77
-
78
- # 1) Coincidencia por nombre de archivo mencionado en la pregunta
79
  for name, text in docs.get("by_name", {}).items():
80
  if name.lower() in q:
81
  return name, (text or "")[:max_chars]
82
 
83
- # 2) Coincidencia simple por frecuencia de términos
84
  tokens = [t for t in re.findall(r"[a-záéíóúüñ0-9]+", q) if len(t) > 2]
85
  best_name, best_score, best_text = "", 0, ""
86
  for e in docs.get("files", []):
87
- text_low = (e.get("text") or "").lower()
88
- score = sum(text_low.count(t) for t in tokens)
89
- if score > best_score:
90
- best_score, best_name, best_text = score, e.get("name", ""), e.get("text", "")
91
  return (best_name, (best_text or "")[:max_chars]) if best_score > 0 else ("", "")
92
 
 
 
 
93
  def responder(user_text: str, history: list | None):
94
- """
95
- Con Chatbot(type="messages"), history es una lista de dicts:
96
- [{"role":"user","content":"..."}, {"role":"assistant","content":"..."}]
97
- """
98
  try:
99
- history = history or [] # asegurar lista
100
- # Añadimos el mensaje del usuario al historial
101
  history.append({"role": "user", "content": user_text})
102
 
103
- # Buscar contexto en PDFs
104
  nombre_pdf, fragmento = buscar_mejor_fragmento(user_text, HF_DOCS)
105
  if fragmento:
106
  contenido_usuario = (
107
  f"El siguiente texto proviene del documento '{nombre_pdf}'. "
108
- "Úsalo para responder de manera clara, breve y profesional:\n\n"
109
- f"{fragmento}\n\nPregunta del usuario:\n{user_text}"
 
110
  )
111
  else:
112
  contenido_usuario = user_text
113
 
 
114
  mensajes = [{"role": "system", "content": system_prompt}] + history[:-1] + [
115
  {"role": "user", "content": contenido_usuario}
116
  ]
117
 
 
118
  resp = client.chat.completions.create(
119
  model="gpt-4o-mini",
120
  messages=mensajes,
121
  temperature=0.3,
122
  )
123
- bot_text = resp.choices[0].message.content
124
- history.append({"role": "assistant", "content": bot_text})
125
-
126
- # Limpiar textbox (""), devolver historial en formato messages
127
  return "", history
128
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  except Exception as e:
130
- history.append({"role": "assistant", "content": f"⚠️ Error: {e}"})
131
  return "", history
132
 
133
  def limpiar_chat():
134
- return [] # devolver lista vacía para Chatbot(type="messages")
135
 
136
  # ------------------------------------------------------------
137
- # INTERFAZ VISUAL GRADIO
138
  # ------------------------------------------------------------
139
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as demo:
140
  gr.HTML("""
@@ -144,9 +168,11 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as demo:
144
  <p style="font-size:15px;">Basado en GPT-4o y los valores del IIA y Gentera</p>
145
  </div>
146
  """)
147
-
148
- chat = gr.Chatbot(label="Chat Asistente Auditoría", type="messages",
149
- value=[{"role": "assistant", "content": "¡Hola! Soy tu Asistente IA de Auditoría Interna. ¿En qué te ayudo hoy?"}])
 
 
150
  msg = gr.Textbox(placeholder="Escribe tu consulta aquí...", label="Tu mensaje")
151
  clear = gr.Button("🧹 Limpiar chat")
152
 
 
2
  import re
3
  import pdfplumber
4
  import gradio as gr
 
 
5
  from dotenv import load_dotenv
6
+ from huggingface_hub import hf_hub_download, list_repo_files
7
+ from openai import OpenAI
8
+
9
+ # --- Excepciones (compatibles con distintas versiones del SDK) ---
10
+ try:
11
+ from openai import (
12
+ APIConnectionError as _APIConnectionError,
13
+ APIStatusError as _APIStatusError,
14
+ RateLimitError as _RateLimitError,
15
+ AuthenticationError as _AuthenticationError,
16
+ APITimeoutError as _APITimeoutError,
17
+ )
18
+ except Exception:
19
+ _APIConnectionError = _APIStatusError = _RateLimitError = _AuthenticationError = _APITimeoutError = Exception
20
 
21
  # ------------------------------------------------------------
22
+ # CONFIG: OpenAI
23
  # ------------------------------------------------------------
24
  load_dotenv()
25
+ client = OpenAI(
26
+ api_key=os.getenv("OPENAI_API_KEY"),
27
+ timeout=30, # evita cuelgues largos
28
+ max_retries=1, # sin reintentos largos
29
+ )
30
 
31
  # ------------------------------------------------------------
32
+ # SYSTEM PROMPT
33
  # ------------------------------------------------------------
34
  system_prompt = """
35
+ Eres un Asistente de IA especializado en Auditoría Interna,
36
+ conforme a las Normas del IIA. Apoyas en análisis, planeación,
37
+ ejecución y documentación de auditorías y en la preparación para el CIA.
38
+ Responde con rigor técnico, ejemplos claros y lenguaje profesional.
39
+ Si la consulta menciona un PDF, integra fragmentos pertinentes del documento.
 
 
 
 
 
 
 
 
 
40
  """
41
 
42
  # ------------------------------------------------------------
43
+ # CARGA DE PDFs (dataset en Hugging Face)
44
  # ------------------------------------------------------------
45
  REPO_ID = "vecervantes89/auditoria_interna_pdfs"
46
  REPO_TYPE = "dataset"
47
 
48
  def extract_pdf_text(local_path: str) -> str:
49
+ parts = []
50
  with pdfplumber.open(local_path) as pdf:
51
+ for p in pdf.pages:
52
+ parts.append(p.extract_text() or "")
53
+ return "\n".join(parts)
54
 
55
  def load_hf_pdfs_text(repo_id: str, repo_type: str = "dataset"):
56
  try:
57
  files = [f for f in list_repo_files(repo_id=repo_id, repo_type=repo_type) if f.lower().endswith(".pdf")]
58
  except Exception as e:
59
+ print(f"[ERROR] No se pudo listar '{repo_id}': {e}")
60
+ return {"files": [], "by_name": {}, "all_text": ""}
61
 
62
  entries = []
63
  for f in files:
64
  try:
65
+ path = hf_hub_download(repo_id=repo_id, filename=f, repo_type=repo_type)
66
+ text = extract_pdf_text(path)
67
  entries.append({"name": f, "text": text})
68
  print(f"[OK] Cargado {f}")
69
  except Exception as e:
70
  print(f"[ERROR] Falló la carga de {f}: {e}")
71
 
 
72
  by_name = {e["name"]: e["text"] for e in entries}
73
+ all_text = "\n\n".join(e["text"] for e in entries)
74
+ print(f"[INFO] Se cargaron {len(entries)} PDFs desde {repo_id}.")
75
+ return {"files": entries, "by_name": by_name, "all_text": all_text}
76
 
77
  HF_DOCS = load_hf_pdfs_text(REPO_ID, REPO_TYPE)
78
 
79
  # ------------------------------------------------------------
80
+ # BÚSQUEDA SIMPLE DE CONTEXTO
81
  # ------------------------------------------------------------
82
  def buscar_mejor_fragmento(pregunta: str, docs: dict, max_chars: int = 3000):
83
+ q = (pregunta or "").lower()
84
+ # 1) Si menciona explícitamente un nombre de archivo
 
85
  for name, text in docs.get("by_name", {}).items():
86
  if name.lower() in q:
87
  return name, (text or "")[:max_chars]
88
 
89
+ # 2) Coincidencia por términos
90
  tokens = [t for t in re.findall(r"[a-záéíóúüñ0-9]+", q) if len(t) > 2]
91
  best_name, best_score, best_text = "", 0, ""
92
  for e in docs.get("files", []):
93
+ t = (e.get("text") or "").lower()
94
+ s = sum(t.count(tok) for tok in tokens)
95
+ if s > best_score:
96
+ best_score, best_name, best_text = s, e.get("name", ""), e.get("text", "")
97
  return (best_name, (best_text or "")[:max_chars]) if best_score > 0 else ("", "")
98
 
99
+ # ------------------------------------------------------------
100
+ # HANDLER DEL CHAT (type="messages")
101
+ # ------------------------------------------------------------
102
  def responder(user_text: str, history: list | None):
 
 
 
 
103
  try:
104
+ history = history or []
 
105
  history.append({"role": "user", "content": user_text})
106
 
107
+ # Contexto desde PDFs
108
  nombre_pdf, fragmento = buscar_mejor_fragmento(user_text, HF_DOCS)
109
  if fragmento:
110
  contenido_usuario = (
111
  f"El siguiente texto proviene del documento '{nombre_pdf}'. "
112
+ "Úsalo como contexto y responde de forma clara, breve y profesional:\n\n"
113
+ f"{fragmento}\n\n"
114
+ f"Pregunta del usuario:\n{user_text}"
115
  )
116
  else:
117
  contenido_usuario = user_text
118
 
119
+ # Construimos los mensajes: system + historial (sin el último) + user contextualizado
120
  mensajes = [{"role": "system", "content": system_prompt}] + history[:-1] + [
121
  {"role": "user", "content": contenido_usuario}
122
  ]
123
 
124
+ # Modelo ligero para Spaces gratis
125
  resp = client.chat.completions.create(
126
  model="gpt-4o-mini",
127
  messages=mensajes,
128
  temperature=0.3,
129
  )
130
+ bot = resp.choices[0].message.content
131
+ history.append({"role": "assistant", "content": bot})
 
 
132
  return "", history
133
 
134
+ except _AuthenticationError:
135
+ history.append({"role": "assistant", "content":
136
+ "⚠️ Error de autenticación con OpenAI.\nRevisa **OPENAI_API_KEY** en Settings → Variables."})
137
+ return "", history
138
+ except _APIConnectionError:
139
+ history.append({"role": "assistant", "content":
140
+ "⚠️ Error de conexión saliente.\nActiva **Allow internet access** en Settings → Runtime/Networking."})
141
+ return "", history
142
+ except _RateLimitError:
143
+ history.append({"role": "assistant", "content":
144
+ "⚠️ Límite/ cuota de OpenAI alcanzado. Intenta más tarde o cambia de modelo."})
145
+ return "", history
146
+ except _APITimeoutError:
147
+ history.append({"role": "assistant", "content":
148
+ "⚠️ La solicitud a OpenAI excedió el tiempo de espera. Intenta de nuevo."})
149
+ return "", history
150
+ except _APIStatusError as e:
151
+ history.append({"role": "assistant", "content": f"⚠️ Error de API: {e}"} )
152
+ return "", history
153
  except Exception as e:
154
+ history.append({"role": "assistant", "content": f"⚠️ Error inesperado: {e}"} )
155
  return "", history
156
 
157
  def limpiar_chat():
158
+ return [] # Chatbot(type="messages") espera una lista de dicts
159
 
160
  # ------------------------------------------------------------
161
+ # UI GRADIO
162
  # ------------------------------------------------------------
163
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as demo:
164
  gr.HTML("""
 
168
  <p style="font-size:15px;">Basado en GPT-4o y los valores del IIA y Gentera</p>
169
  </div>
170
  """)
171
+ chat = gr.Chatbot(
172
+ label="Chat Asistente Auditoría",
173
+ type="messages",
174
+ value=[{"role": "assistant", "content": "¡Hola! Soy tu Asistente IA de Auditoría Interna. ¿En qué te ayudo hoy?"}]
175
+ )
176
  msg = gr.Textbox(placeholder="Escribe tu consulta aquí...", label="Tu mensaje")
177
  clear = gr.Button("🧹 Limpiar chat")
178