jimytech commited on
Commit
5dfc96f
·
verified ·
1 Parent(s): c3f147e

Creación de rag_api.py

Browse files
Files changed (1) hide show
  1. rag_api.py +191 -0
rag_api.py ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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=1XqImFIKiuRDhSDK6Rm6dAZbHm03NdzQa"
25
+ URL_PKL = "https://drive.google.com/uc?export=download&id=156BWHHGi-JuD9EM2Nek1mNcyitivQWAH"
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 de nutrición llamado NutriActive.
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
+ - NUTRICION: preguntas sobre nutrición, dieta, salud, alimentos, calorías, macros, IMC,
37
+ planes alimenticios, recetas, suplementos, hábitos saludables, Y TAMBIÉN cualquier
38
+ pregunta relacionada con NutriActive como empresa: sus cursos, servicios, productos,
39
+ planes, precios, programas, etc.
40
+ - OTRO: preguntas claramente NO relacionadas con nutrición, salud ni NutriActive
41
+ (matemáticas, historia, tecnología general, etc.)
42
+ IMPORTANTE: Ante la duda, clasifica como NUTRICION. Solo usa OTRO cuando estés
43
+ completamente seguro de que no tiene relación con nutrición ni con NutriActive.
44
+ Responde SOLO con la categoría, sin explicación.
45
+ Mensaje: {query}
46
+ Categoría:""",
47
+ input_variables=["query"]
48
+ )
49
+
50
+ SALUDO_PROMPT = PromptTemplate(
51
+ template="""Eres NutriActive, un asistente amigable especializado en nutrición y salud.
52
+ Responde de forma natural y cálida al siguiente mensaje casual del usuario.
53
+ Si el usuario se despide o agradece, invítalo a preguntar sobre nutrición.
54
+ Mensaje: {query}
55
+ Respuesta:""",
56
+ input_variables=["query"]
57
+ )
58
+
59
+ RAG_PROMPT = PromptTemplate(
60
+ template="""Eres NutriActive, un asistente experto en nutrición y salud.
61
+ Tu tarea es responder basándote en el contexto proporcionado.
62
+ Si el contexto no tiene suficiente información, usa tu conocimiento general sobre nutrición para dar una respuesta útil.
63
+ Sé amigable, claro y conciso.
64
+ Contexto de la base de datos: {context}
65
+ Pregunta del usuario: {question}
66
+ Respuesta:""",
67
+ input_variables=["context", "question"]
68
+ )
69
+
70
+ # --------------------------------------------------------
71
+ # 3. FUNCIONES DE DESCARGA Y CARGA
72
+ # --------------------------------------------------------
73
+ class QueryRequest(BaseModel):
74
+ query: str
75
+
76
+ def download_file(url, local_path):
77
+ file_name = os.path.basename(local_path)
78
+ print(f"Descargando: {file_name}...")
79
+ headers = {'User-Agent': 'Mozilla/5.0'}
80
+ try:
81
+ response = requests.get(url, stream=True, headers=headers, timeout=30)
82
+ if response.status_code == 403:
83
+ raise PermissionError(f"Error 403: {file_name} no es público.")
84
+ response.raise_for_status()
85
+ os.makedirs(os.path.dirname(local_path), exist_ok=True)
86
+ with open(local_path, 'wb') as f:
87
+ shutil.copyfileobj(response.raw, f)
88
+ print(f"✓ {file_name} descargado.")
89
+ except requests.exceptions.RequestException as e:
90
+ raise RuntimeError(f"Fallo al descargar {file_name}: {e}")
91
+
92
+ def load_and_configure_rag():
93
+ try:
94
+ download_file(URL_FAISS, os.path.join(DOWNLOAD_DIR, 'index.faiss'))
95
+ download_file(URL_PKL, os.path.join(DOWNLOAD_DIR, 'index.pkl'))
96
+
97
+ print("Cargando embeddings...")
98
+ embeddings = HuggingFaceEmbeddings(
99
+ model_name="sentence-transformers/all-MiniLM-L6-v2",
100
+ model_kwargs={'device': 'cpu'},
101
+ cache_folder=TEMP_CACHE_DIR
102
+ )
103
+
104
+ print("Cargando FAISS...")
105
+ vectorstore = FAISS.load_local(
106
+ DB_FAISS_PATH, embeddings, allow_dangerous_deserialization=True
107
+ )
108
+
109
+ llm = ChatGroq(temperature=0.3, model_name="llama-3.3-70b-versatile")
110
+
111
+ # Cadena clasificadora de intención
112
+ intent_chain = INTENT_PROMPT | llm
113
+
114
+ # Cadena para saludos
115
+ saludo_chain = SALUDO_PROMPT | llm
116
+
117
+ # Cadena RAG principal
118
+ retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
119
+ rag_chain = (
120
+ {"context": retriever, "question": RunnablePassthrough()}
121
+ | RAG_PROMPT
122
+ | llm
123
+ )
124
+
125
+ return intent_chain, saludo_chain, rag_chain, retriever
126
+
127
+ except Exception as e:
128
+ print(f"Error CRÍTICO al inicializar: {type(e).__name__}: {e}")
129
+ raise RuntimeError(f"Falla al cargar RAG: {e}")
130
+
131
+ # --------------------------------------------------------
132
+ # 4. FASTAPI
133
+ # --------------------------------------------------------
134
+ app = FastAPI(title="NutriActive RAG API")
135
+
136
+ intent_chain = saludo_chain = qa_chain = retriever = None
137
+
138
+ try:
139
+ intent_chain, saludo_chain, qa_chain, retriever = load_and_configure_rag()
140
+ except RuntimeError:
141
+ pass
142
+
143
+ @app.get("/")
144
+ def home():
145
+ if qa_chain is None:
146
+ return {"error": "RAG no inicializado. Revisa los logs."}
147
+ return {"message": "API NutriActive operativa. Usa /query."}
148
+
149
+ @app.post("/query")
150
+ async def process_query(request: QueryRequest):
151
+ if qa_chain is None:
152
+ return {"error": "El sistema RAG no se pudo cargar."}
153
+
154
+ try:
155
+ # ── 1. Clasificar intención ──────────────────────────────
156
+ intent_result = intent_chain.invoke({"query": request.query})
157
+ intent = intent_result.content.strip().upper()
158
+ print(f"[Intent] '{request.query}' → {intent}")
159
+
160
+ # ── 2. Ruta según intención ──────────────────────────────
161
+ if "SALUDO" in intent:
162
+ respuesta = saludo_chain.invoke({"query": request.query})
163
+ return {
164
+ "query": request.query,
165
+ "response": respuesta.content,
166
+ "intent": "SALUDO",
167
+ "sources": []
168
+ }
169
+
170
+ elif "OTRO" in intent:
171
+ return {
172
+ "query": request.query,
173
+ "response": "Soy NutriActive, especializado en nutrición y salud. ¿Tienes alguna pregunta sobre alimentación, dietas o bienestar? 🥗",
174
+ "intent": "OTRO",
175
+ "sources": []
176
+ }
177
+
178
+ else:
179
+ # NUTRICION o cualquier categoría no reconocida → RAG
180
+ respuesta = qa_chain.invoke(request.query)
181
+ docs = retriever.invoke(request.query)
182
+ sources = [doc.metadata.get("source", "N/A") for doc in docs]
183
+ return {
184
+ "query": request.query,
185
+ "response": respuesta.content,
186
+ "intent": "NUTRICION",
187
+ "sources": sources
188
+ }
189
+
190
+ except Exception as e:
191
+ return {"error": f"Error al procesar la consulta: {e}"}