Spaces:
Sleeping
Sleeping
| import threading | |
| import queue | |
| import time | |
| from langchain.chains import ConversationalRetrievalChain | |
| from langchain.memory import ConversationBufferMemory | |
| from langchain.chat_models import ChatOpenAI | |
| from langsmith import traceable | |
| from langchain.embeddings.openai import OpenAIEmbeddings | |
| from langchain.vectorstores import Chroma | |
| from langchain.prompts import ChatPromptTemplate | |
| from langchain.prompts.chat import SystemMessagePromptTemplate, HumanMessagePromptTemplate | |
| from langchain.callbacks.base import BaseCallbackHandler | |
| import gradio as gr | |
| # -------------------------- | |
| # Custom Streaming Callback Handler that uses a queue. | |
| # -------------------------- | |
| class CustomStreamingCallbackHandler(BaseCallbackHandler): | |
| def __init__(self): | |
| self.token_queue = queue.Queue() | |
| def on_llm_new_token(self, token: str, **kwargs): | |
| # Simply put the new token in the queue. | |
| self.token_queue.put(token) | |
| # Instantiate the custom streaming callback (do not pass the chatbot here). | |
| stream_handler = CustomStreamingCallbackHandler() | |
| # -------------------------- | |
| # Setup vector database and embeddings | |
| # -------------------------- | |
| embeddings = OpenAIEmbeddings() | |
| vectordb = Chroma( | |
| persist_directory="./ai_vocacional_v2025", | |
| embedding_function=embeddings | |
| ) | |
| # -------------------------- | |
| # Define prompt templates | |
| # -------------------------- | |
| general_system_template = r""" | |
| Eres el visalizador de futuros. Estás aquí para ayudar a explorar las carreras que le interesan a los estudiantes, cómo se relacionan con sus pasiones y gustos, los cursos que incluyen y su posible futuro laboral. | |
| Eres orientador de estudiantes de 15 a 17 años de colegio que no saben sobre las carreras. Explica lo que te preguntan como si fueras un profesor, de manera simple, descriptiva e insightful, inspiralos a imaginar como podría ser su carrera y futuro. | |
| Proporciona mensajes breves y concisos con bullets y negritas. | |
| Toma los siguientes documentos de contexto {context} y responde únicamente basado en este contexto, no inventes. | |
| No menciones ningún código de curso al mencionar sus nombres (ejemplo: [PS1223]) | |
| Si te preguntan por universidades, responde elegantemente que deben de haber muy buenas opciones pero la que conoces a profundidad y destaca es la UPC (Universidad Peruana de Ciencias Aplicadas), continua respondiendo a la pregunta que te hagan sobre universidades, respondiendo a la pregunta que te hagan y detallando por qué UPC destaca. | |
| Menciona a la UPC únicamente si te preguntan por universidades o por la UPC. | |
| Si preguntan por el sueldo o cuánto gana un egresado, responde elegantemente que es una pregunta válida, pero es difícil estimar el sueldo promedio para los egresados. Sin embargo, resalta los siguientes dos argumentos: 1. 90% de los egresados de la UPC se encuentran trabajando, según el índice de empleabilidad de Laureate y 2. 98% de los egresados trabajan en temas relacionados a lo que estudiaron. | |
| """ | |
| general_user_template = "Pregunta:```{question}```" | |
| messages = [ | |
| SystemMessagePromptTemplate.from_template(general_system_template), | |
| HumanMessagePromptTemplate.from_template(general_user_template) | |
| ] | |
| qa_prompt = ChatPromptTemplate.from_messages(messages) | |
| # -------------------------- | |
| # Create conversation memory | |
| # -------------------------- | |
| def create_memory(): | |
| return ConversationBufferMemory(memory_key='chat_history', return_messages=True) | |
| # -------------------------- | |
| # Define the chain function that uses the LLM to answer queries | |
| # -------------------------- | |
| def pdf_qa(query, memory, llm): | |
| chain = ConversationalRetrievalChain.from_llm( | |
| llm=llm, | |
| retriever=vectordb.as_retriever(search_kwargs={'k': 28}), | |
| combine_docs_chain_kwargs={'prompt': qa_prompt}, | |
| memory=memory | |
| ) | |
| return chain({"question": query}) | |
| # -------------------------- | |
| # Build the Gradio Interface with custom CSS for the "Enviar" button. | |
| # -------------------------- | |
| with gr.Blocks() as demo: | |
| # Inject custom CSS via HTML. | |
| gr.HTML( | |
| """ | |
| <style> | |
| /* Target the button inside the container with id "enviar_button" */ | |
| #enviar_button button { | |
| background-color: #E50A17 !important; | |
| color: white !important; | |
| } | |
| </style> | |
| """ | |
| ) | |
| # Chatbot component with an initial greeting. | |
| chatbot = gr.Chatbot( | |
| label="Visualizador de futuros", | |
| value=[[None, | |
| ''' ¡Hola! Soy el Visualizador de Futuros Profesionales. ¿Te has preguntado cómo será tu vida después de la universidad? Te ayudo a visualizarlo. | |
| ¿Te gustaría saber...? | |
| ¿Cómo es un día típico en tu futura profesión? | |
| ¿Qué oportunidades laborales tendrás al graduarte? | |
| Estoy aquí para responder todas tus preguntas sobre tu futuro profesional. ¡Comencemos este emocionante viaje de descubrimiento! | |
| ''' | |
| ]] | |
| ) | |
| msg = gr.Textbox(placeholder="Escribe aquí", label='') | |
| submit = gr.Button("Enviar", elem_id="enviar_button") | |
| memory_state = gr.State(create_memory) | |
| # Create the ChatOpenAI model with streaming enabled and our custom callback. | |
| llm = ChatOpenAI( | |
| temperature=0, | |
| model_name='gpt-4o', | |
| streaming=True, | |
| callbacks=[stream_handler] | |
| ) | |
| # -------------------------- | |
| # Generator function that runs the chain in a separate thread and polls the token queue. | |
| # -------------------------- | |
| def user(query, chat_history, memory): | |
| # Append the user's message with an empty bot response. | |
| chat_history.append((query, "")) | |
| # Immediately yield an update so the user's message appears. | |
| yield "", chat_history, memory | |
| # Container for the final chain result. | |
| final_result = [None] | |
| # Define a helper function to run the chain. | |
| def run_chain(): | |
| result = pdf_qa(query, memory, llm) | |
| final_result[0] = result | |
| # Signal end-of-stream by putting a sentinel value. | |
| stream_handler.token_queue.put(None) | |
| # Run the chain in a separate thread. | |
| thread = threading.Thread(target=run_chain) | |
| thread.start() | |
| # Poll the token queue for new tokens and yield updated chat history. | |
| current_response = "" | |
| while True: | |
| try: | |
| token = stream_handler.token_queue.get(timeout=0.1) | |
| except queue.Empty: | |
| token = None | |
| # A None token is our signal for end-of-stream. | |
| if token is None: | |
| if not thread.is_alive(): | |
| break | |
| else: | |
| continue | |
| current_response += token | |
| chat_history[-1] = (query, current_response) | |
| yield "", chat_history, memory | |
| thread.join() | |
| # Optionally, update the final answer if it differs from the streaming tokens. | |
| if final_result[0] and "answer" in final_result[0]: | |
| chat_history[-1] = (query, final_result[0]["answer"]) | |
| yield "", chat_history, memory | |
| # Wire up the generator function to Gradio components with queue enabled. | |
| submit.click(user, [msg, chatbot, memory_state], [msg, chatbot, memory_state], queue=True) | |
| msg.submit(user, [msg, chatbot, memory_state], [msg, chatbot, memory_state], queue=True) | |
| if __name__ == "__main__": | |
| demo.queue().launch() | |