jimytech commited on
Commit
0a729a6
·
verified ·
1 Parent(s): bff41c6

Creacion de la app

Browse files
Files changed (1) hide show
  1. rag_api.py +187 -0
rag_api.py ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import requests
3
+ import shutil
4
+ from langchain_community.vectorstores import FAISS
5
+ from fastapi import FastAPI
6
+ from pydantic import BaseModel
7
+ from langchain_huggingface import HuggingFaceEmbeddings
8
+ from langchain_core.runnables import RunnablePassthrough
9
+ from langchain_core.prompts import PromptTemplate
10
+ from langchain_groq import ChatGroq
11
+
12
+ # --------------------------------------------------------
13
+ # CACHÉ EN /tmp
14
+ # --------------------------------------------------------
15
+ TEMP_CACHE_DIR = '/tmp/huggingface_cache'
16
+ os.environ['TRANSFORMERS_CACHE'] = TEMP_CACHE_DIR
17
+ os.environ['HF_HOME'] = TEMP_CACHE_DIR
18
+ os.environ['SENTENCE_TRANSFORMERS_HOME'] = TEMP_CACHE_DIR
19
+ os.makedirs(TEMP_CACHE_DIR, exist_ok=True)
20
+
21
+ # --------------------------------------------------------
22
+ # 1. CONFIGURACIÓN
23
+ # --------------------------------------------------------
24
+ URL_FAISS = "https://drive.google.com/uc?export=download&id=1hiVycS4DQHO1MBdC-L_z1TXA6sJO_Y-r"
25
+ URL_PKL = "https://drive.google.com/uc?export=download&id=1vbG8unx88Kb5jn7puGv1gqSM4S6rIUQC"
26
+ DOWNLOAD_DIR = "/tmp/db_faiss"
27
+ DB_FAISS_PATH = DOWNLOAD_DIR
28
+
29
+ # --------------------------------------------------------
30
+ # 2. CLASIFICADOR DE INTENCIÓN ← NUEVO
31
+ # --------------------------------------------------------
32
+ INTENT_PROMPT = PromptTemplate(
33
+ template="""Eres un clasificador de intenciones para un asistente del portal de la Universidad Poltécnica de Aragua.
34
+ Analiza el mensaje del usuario y clasifícalo en UNA de estas categorías:
35
+ - SALUDO: saludos, despedidas, conversación casual ("hola", "gracias", "adiós", "¿cómo estás?")
36
+ - UNIVERSIDAD: preguntas sobre carreras o programas, investigación, cursos, admisiones, notas, proyectos, postgrado,
37
+ PNF, PNFA, diplomados, servivios, Y TAMBIÉN cualquier pregunta relacionada con La Universidad relacionado con: sus autoridades, reglamentos,
38
+ servivios estudiantiles, precios de cursos, programas, etc.
39
+ - OTRO: preguntas claramente NO relacionadas con la Universidad tales como: matemáticas, historia, tecnología general, etc.
40
+ IMPORTANTE: Ante la duda, clasifica como Universidad Politécnica de Aragua o UPT Aragua. Solo usa OTRO cuando estés
41
+ completamente seguro de que no tiene relación con la Universidad.
42
+ Responde SOLO con la categoría, sin explicación.
43
+ Mensaje: {query}
44
+ Categoría:""",
45
+ input_variables=["query"]
46
+ )
47
+
48
+ SALUDO_PROMPT = PromptTemplate(
49
+ template="""Eres UPTA bot, un Asistente Virtual de la UPT Aragua. Estas aquí para ayudar con información sobre admisiones, programas académicos,
50
+ servicios, becas y mucho más. Si el usuario se despide o agradece, invítalo a preguntar sobre la universidad.
51
+ Mensaje: {query}
52
+ Respuesta:""",
53
+ input_variables=["query"]
54
+ )
55
+
56
+ RAG_PROMPT = PromptTemplate(
57
+ template="""Eres UPTA bot, un Asistente Virtual experto de la UPT Aragua. Estas aquí para ayudar con información sobre
58
+ admisiones, programas académicos, servicios, becas y mucho más. Tu tarea es responder basándote en el contexto proporcionado. Si el contexto
59
+ no tiene suficiente información, pide al usuario que te proporcione una pregunta más específica sobre la UPT Aragua para dar una respuesta fiable. Sé amigable, claro y conciso.
60
+ Contexto de la base de datos: {context}
61
+ Pregunta del usuario: {question}
62
+ Respuesta:""",
63
+ input_variables=["context", "question"]
64
+ )
65
+
66
+ # --------------------------------------------------------
67
+ # 3. FUNCIONES DE DESCARGA Y CARGA
68
+ # --------------------------------------------------------
69
+ class QueryRequest(BaseModel):
70
+ query: str
71
+
72
+ def download_file(url, local_path):
73
+ file_name = os.path.basename(local_path)
74
+ print(f"Descargando: {file_name}...")
75
+ headers = {'User-Agent': 'Mozilla/5.0'}
76
+ try:
77
+ response = requests.get(url, stream=True, headers=headers, timeout=30)
78
+ if response.status_code == 403:
79
+ raise PermissionError(f"Error 403: {file_name} no es público.")
80
+ response.raise_for_status()
81
+ os.makedirs(os.path.dirname(local_path), exist_ok=True)
82
+ with open(local_path, 'wb') as f:
83
+ shutil.copyfileobj(response.raw, f)
84
+ print(f"✓ {file_name} descargado.")
85
+ except requests.exceptions.RequestException as e:
86
+ raise RuntimeError(f"Fallo al descargar {file_name}: {e}")
87
+
88
+ def load_and_configure_rag():
89
+ try:
90
+ download_file(URL_FAISS, os.path.join(DOWNLOAD_DIR, 'index.faiss'))
91
+ download_file(URL_PKL, os.path.join(DOWNLOAD_DIR, 'index.pkl'))
92
+
93
+ print("Cargando embeddings...")
94
+ embeddings = HuggingFaceEmbeddings(
95
+ model_name="sentence-transformers/all-MiniLM-L6-v2",
96
+ model_kwargs={'device': 'cpu'},
97
+ cache_folder=TEMP_CACHE_DIR
98
+ )
99
+
100
+ print("Cargando FAISS...")
101
+ vectorstore = FAISS.load_local(
102
+ DB_FAISS_PATH, embeddings, allow_dangerous_deserialization=True
103
+ )
104
+
105
+ llm = ChatGroq(temperature=0.150, model_name="openai/gpt-oss-120b")
106
+
107
+ # Cadena clasificadora de intención
108
+ intent_chain = INTENT_PROMPT | llm
109
+
110
+ # Cadena para saludos
111
+ saludo_chain = SALUDO_PROMPT | llm
112
+
113
+ # Cadena RAG principal
114
+ retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
115
+ rag_chain = (
116
+ {"context": retriever, "question": RunnablePassthrough()}
117
+ | RAG_PROMPT
118
+ | llm
119
+ )
120
+
121
+ return intent_chain, saludo_chain, rag_chain, retriever
122
+
123
+ except Exception as e:
124
+ print(f"Error CRÍTICO al inicializar: {type(e).__name__}: {e}")
125
+ raise RuntimeError(f"Falla al cargar RAG: {e}")
126
+
127
+ # --------------------------------------------------------
128
+ # 4. FASTAPI
129
+ # --------------------------------------------------------
130
+ app = FastAPI(title="UPT Aragua bot RAG API")
131
+
132
+ intent_chain = saludo_chain = qa_chain = retriever = None
133
+
134
+ try:
135
+ intent_chain, saludo_chain, qa_chain, retriever = load_and_configure_rag()
136
+ except RuntimeError:
137
+ pass
138
+
139
+ @app.get("/")
140
+ def home():
141
+ if qa_chain is None:
142
+ return {"error": "RAG no inicializado. Revisa los logs."}
143
+ return {"message": "API UPT Aragua bot operativa. Usa /query."}
144
+
145
+ @app.post("/query")
146
+ async def process_query(request: QueryRequest):
147
+ if qa_chain is None:
148
+ return {"error": "El sistema RAG no se pudo cargar."}
149
+
150
+ try:
151
+ # ── 1. Clasificar intención ──────────────────────────────
152
+ intent_result = intent_chain.invoke({"query": request.query})
153
+ intent = intent_result.content.strip().upper()
154
+ print(f"[Intent] '{request.query}' → {intent}")
155
+
156
+ # ── 2. Ruta según intención ──────────────────────────────
157
+ if "SALUDO" in intent:
158
+ respuesta = saludo_chain.invoke({"query": request.query})
159
+ return {
160
+ "query": request.query,
161
+ "response": respuesta.content,
162
+ "intent": "SALUDO",
163
+ "sources": []
164
+ }
165
+
166
+ elif "OTRO" in intent:
167
+ return {
168
+ "query": request.query,
169
+ "response": "Soy UPTA bot, estoy especializado en la UPT Aragua. ¿Tienes alguna pregunta sobre programas, carreras, inscripciones, fechas...? 🥗",
170
+ "intent": "OTRO",
171
+ "sources": []
172
+ }
173
+
174
+ else:
175
+ # UNIVERSIDAD o cualquier categoría no reconocida → RAG
176
+ respuesta = qa_chain.invoke(request.query)
177
+ docs = retriever.invoke(request.query)
178
+ sources = [doc.metadata.get("source", "N/A") for doc in docs]
179
+ return {
180
+ "query": request.query,
181
+ "response": respuesta.content,
182
+ "intent": "UNIVERSIDAD",
183
+ "sources": sources
184
+ }
185
+
186
+ except Exception as e:
187
+ return {"error": f"Error al procesar la consulta: {e}"}