Spaces:
Sleeping
Sleeping
File size: 6,205 Bytes
01fe139 4145718 01fe139 33e7d04 a41cc2d f2c21b6 33e7d04 01fe139 33e7d04 01fe139 33e7d04 01fe139 4145718 01fe139 4145718 01fe139 33e7d04 01fe139 4145718 01fe139 4145718 01fe139 33e7d04 a41cc2d 01fe139 79fab4b 01fe139 4145718 01fe139 4145718 01fe139 4145718 01fe139 4145718 01fe139 4145718 01fe139 4145718 01fe139 4145718 01fe139 0b3a368 01fe139 4145718 01fe139 4145718 01fe139 4145718 01fe139 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
import os
import uuid
import gradio as gr
from langchain_openai import ChatOpenAI
# Mantenemos langchain_community como pediste (versiones legacy)
from langchain_community.vectorstores import Qdrant
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.chat_message_histories import ChatMessageHistory
# Clientes y Modelos
from qdrant_client import QdrantClient, models
from qdrant_client.http import models as rest_models
# Cadenas (Importación consolidada para evitar errores de ruta)
from langchain.chains import create_history_aware_retriever, create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
# Core
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
# --- 1. CONFIGURACIÓN Y VARIABLES DE ENTORNO ---
QDRANT_URL = os.getenv("QDRANT_URL")
QDRANT_API_KEY = os.getenv("QDRANT_API_KEY")
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
COLLECTION_NAME = "dgt_documents_qdrant_memory_filter_fixed_2"
# Categorías disponibles
OPCIONES_CATEGORIAS = [
"Todas",
"Documentos de la SUMA",
"Manuales Técnicos y Procedimientos",
"Inventarios y Activos SUMA",
"Otros"
]
# --- 2. INICIALIZAR CLIENTES ---
# Cliente Qdrant
client = QdrantClient(url=QDRANT_URL, api_key=QDRANT_API_KEY)
# Embeddings (Usando langchain_community antiguo)
embeddings_model = HuggingFaceEmbeddings(
model_name="intfloat/e5-large-v2",
model_kwargs={'device': 'cpu'},
encode_kwargs={'normalize_embeddings': False}
)
# LLM
llm_openai = ChatOpenAI(
model="gpt-4o-mini",
temperature=0.1,
api_key=OPENAI_API_KEY
)
# Conexión a la VectorDB (Wrapper antiguo Qdrant)
vectordb = Qdrant(
client=client,
collection_name=COLLECTION_NAME,
embeddings=embeddings_model,
content_payload_key="content"
)
# --- 3. PROMPTS ---
contextualize_q_system_prompt = """Dado un historial de chat y la última pregunta del usuario \
que podría hacer referencia al contexto en el historial de chat, formula una pregunta independiente \
que pueda entenderse sin el historial de chat. NO respondas a la pregunta, \
solo reformúlala si es necesario y, si no, devuélvela tal cual."""
contextualize_q_prompt = ChatPromptTemplate.from_messages(
[
("system", contextualize_q_system_prompt),
MessagesPlaceholder("chat_history"),
("human", "{input}"),
]
)
qa_system_prompt = """Eres un asistente especializado en los documentos sobre la Sociedad Musical de Alberic (SUMA). \
Utiliza los siguientes fragmentos de contexto recuperado para responder a la pregunta. \
Si no sabes la respuesta, di que no lo sabes. \
Menciona siempre de qué documentos has extraído la información (usando el metadato 'source'). \
Profundiza en la respuesta.
Contexto:
{context}"""
qa_prompt = ChatPromptTemplate.from_messages(
[
("system", qa_system_prompt),
MessagesPlaceholder("chat_history"),
("human", "{input}"),
]
)
# --- 4. GESTIÓN DE MEMORIA EN RAM ---
store = {}
def get_session_history(session_id: str):
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
# --- 5. LÓGICA DEL CHAT ---
def build_qdrant_filter(category_name):
if not category_name or category_name == "Todas":
return None
return rest_models.Filter(
must=[
rest_models.FieldCondition(
key="category",
match=rest_models.MatchValue(value=category_name)
)
]
)
def chat_logic(message, history, selected_category, session_id):
# Seguridad: Si por error session_id viene vacío, generamos uno temporal
if not session_id:
session_id = str(uuid.uuid4())
# 1. Construir filtro
qdrant_filter = build_qdrant_filter(selected_category)
# 2. Retriever dinámico
dynamic_retriever = vectordb.as_retriever(
search_kwargs={
"k": 4,
"filter": qdrant_filter
}
)
# 3. Cadenas LangChain
history_aware_retriever = create_history_aware_retriever(
llm_openai, dynamic_retriever, contextualize_q_prompt
)
question_answer_chain = create_stuff_documents_chain(llm_openai, qa_prompt)
rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)
conversational_rag_chain = RunnableWithMessageHistory(
rag_chain,
get_session_history,
input_messages_key="input",
history_messages_key="chat_history",
output_messages_key="answer",
)
# 4. Generar respuesta streaming usando el ID único del usuario
full_response = ""
try:
for chunk in conversational_rag_chain.stream(
{"input": message},
config={"configurable": {"session_id": session_id}}
):
if "answer" in chunk:
full_response += chunk["answer"]
yield full_response
except Exception as e:
yield f"Error al procesar la respuesta: {str(e)}"
# --- 6. INTERFAZ GRÁFICA ---
custom_css = """
footer {visibility: hidden}
.gradio-container {background-color: #f9fafb}
"""
tema_musical = gr.themes.Soft(primary_hue="indigo", secondary_hue="slate")
with gr.Blocks(theme=tema_musical, css=custom_css, title="Chatbot SUMA") as demo:
# ESTADO: Genera un ID único cada vez que se carga la página
session_state = gr.State(lambda: str(uuid.uuid4()))
gr.Markdown("# 🎵 Asistente Virtual SUMA")
gr.Markdown("Pregunta sobre normativas, manuales y documentos internos.")
filtro_dropdown = gr.Dropdown(
choices=OPCIONES_CATEGORIAS,
value="Todas",
label="📂 Filtrar por Categoría",
info="Acota la búsqueda a un tipo de documento específico."
)
chat_interface = gr.ChatInterface(
fn=chat_logic,
additional_inputs=[filtro_dropdown, session_state],
examples=[
["¿Cuáles son los requisitos para ser socio?"],
["Resumen del manual de procedimientos"],
]
)
if __name__ == "__main__":
demo.launch() |