geronimo-pericoli's picture
Update app.py
091d09c verified
raw
history blame
26.2 kB
import nest_asyncio
nest_asyncio.apply()
from llama_index.core import (
VectorStoreIndex,
ServiceContext,
SimpleDirectoryReader,
load_index_from_storage,
)
from llama_index.core.storage import StorageContext
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.prompts import PromptTemplate
from llama_index.core.response_synthesizers import TreeSummarize
from llama_index.core.query_pipeline import InputComponent
from llama_index.core.indices.knowledge_graph import KGTableRetriever
from llama_index.legacy.vector_stores.faiss import FaissVectorStore
from llama_index.llms.openai import OpenAI
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.core import Settings
import openai
import os
from github import Github
from datetime import datetime
import gradio as gr
import pandas as pd
# Context:
exec(os.environ.get('context'))
##### Graph start ##########
import networkx as nx
import matplotlib.pyplot as plt
from PIL import Image
from io import BytesIO
def draw_graph():
global kg_data
G = nx.DiGraph()
for source, relation, target in kg_data:
G.add_edge(source, target, label=relation)
# Utilizar spring_layout para mejorar la disposici贸n de los nodos
pos = nx.spring_layout(G)
plt.figure(figsize=(12, 8))
# Ajustar el tama帽o de los nodos
nx.draw(G, pos, with_labels=True, node_color='skyblue', node_size=400, edge_color='k', linewidths=1, font_size=8, font_weight='bold')
# Ajustar el tama帽o de las flechas y el espaciado entre ellas
edge_labels = {}
for source, target, data in G.edges(data=True):
if 'label' in data:
edge_labels[(source, target)] = data['label']
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=7, font_weight='normal')
plt.title("Graph")
plt.axis('off')
buf = BytesIO()
plt.savefig(buf, format='png')
buf.seek(0)
plt.close()
return Image.open(buf)
##### Graph end ##########
##### Refs start ##########
import re
def extraer_informacion_metadata(respuesta, max_results=10):
# Obtener source_nodes de la respuesta
source_nodes = respuesta.source_nodes
# Obtener page_labels, file_names y scores de source_nodes
page_file_info = [
f"<strong>P谩gina {node.node.metadata.get('page_label', '')} del archivo {node.node.metadata.get('file_name', '')}</strong> (Relevance: {node.score:.6f} - Id: {node.node.id_})\n\n"
for node in source_nodes if node.score <= 1 # Excluir nodos con score > 1
]
# Limitar la cantidad de resultados
page_file_info = page_file_info[:max_results]
return page_file_info
import html
def extraer_textos_metadata(respuesta, max_results=10):
# Obtener source_nodes de la respuesta
source_nodes = respuesta.source_nodes
# Obtener informaci贸n de p谩gina, archivo y texto de cada nodo
page_file_text_info = []
for node in source_nodes:
if node.score <= 1: # Excluir nodos con score > 1
page_label = node.node.metadata.get('page_label', '')
file_name = node.node.metadata.get('file_name', '')
text = node.node.text.strip()
# Escapar caracteres especiales en el texto
escaped_text = html.escape(text)
# Formatear con HTML
formatted_text = f"""
<div style="margin-bottom: 10px;">
<strong style="font-size: 11px;">P谩gina {page_label} del archivo {file_name}</strong>
<p style="font-size: 9px; margin-top: 5px;">{escaped_text}</p>
<hr style="border: 1px solid #ccc; margin:10px 0;">
</div>
"""
page_file_text_info.append(formatted_text.strip()) # Quitar espacios adicionales
# Limitar la cantidad de resultados
page_file_text_info = page_file_text_info[:max_results]
return ''.join(page_file_text_info) # Devolver como un string limpio
##### Refs end ##########
##### Logs start ##########
import pandas as pd
from datasets import load_dataset, Dataset, DatasetDict
from huggingface_hub import login, HfApi, file_exists, hf_hub_download, list_repo_files
# HuggingFace Token:
HF_TOKEN = os.environ.get('hf')
# Definiciones
repo_name = "pharma-IA"
project_id = "gmpcolombia"
def save_to_dataset(user_message, response_text, user):
current_month = datetime.now().strftime('%Y-%m')
filename = f"logs_{current_month}.csv"
repo_id = f"{repo_name}/logs-{project_id}"
if file_exists(repo_id=repo_id, filename=f"{filename}", repo_type="dataset", token=HF_TOKEN):
local_filepath = hf_hub_download(
repo_id=repo_id,
filename=f"{filename}",
repo_type="dataset",
token=HF_TOKEN
)
df = pd.read_csv(local_filepath)
else:
df = pd.DataFrame(columns=["timestamp", "user_message", "response_text", "flag", "user"])
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
new_data = pd.DataFrame([{
"timestamp": timestamp,
"user_message": user_message,
"response_text": response_text,
"flag": "",
"user": user
}])
df = pd.concat([df, new_data], ignore_index=True)
df.to_csv(filename, index=False)
api = HfApi()
api.upload_file(
path_or_fileobj=filename,
path_in_repo=f"{filename}",
repo_id=repo_id,
token=HF_TOKEN,
repo_type="dataset"
)
def normalize_text(text):
return text.strip().lower()
def print_like_dislike(x: gr.LikeData):
#print(f"Value: {x.value}")
#print(f"Liked: {x.liked}")
if x is not None:
text_value = x.value if isinstance(x.value, str) else x.value.get('value', '')
current_month = datetime.now().strftime('%Y-%m')
filename = f"logs_{current_month}.csv"
repo_id = f"{repo_name}/logs-{project_id}"
if file_exists(repo_id=repo_id, filename=f"{filename}", repo_type="dataset", token=HF_TOKEN):
local_filepath = hf_hub_download(
repo_id=repo_id,
filename=f"{filename}",
repo_type="dataset",
token=HF_TOKEN
)
df = pd.read_csv(local_filepath)
#print(df.head()) # Verifica el contenido del archivo CSV
normalized_value = normalize_text(text_value)
df['normalized_response_text'] = df['response_text'].apply(normalize_text)
response_indices = df.index[df['normalized_response_text'].str.contains(normalized_value, na=False, regex=False)].tolist()
print(f"Response Indices: {response_indices}")
if response_indices:
response_index = response_indices[-1]
print(f"Updating index: {response_index} with value: {x.liked}")
# Solo actualiza el valor de 'flag'
df['flag'] = df['flag'].astype(object)
df.at[response_index, 'flag'] = str(x.liked)
df = df.drop(columns=['normalized_response_text'])
df.to_csv(filename, index=False)
api = HfApi()
api.upload_file(
path_or_fileobj=filename,
path_in_repo=f"{filename}",
repo_id=repo_id,
token=HF_TOKEN,
repo_type="dataset"
)
else:
print("No matching response found to update.")
else:
print(f"File {filename} does not exist in the repository.")
else:
print("x is None.")
def save_evals_to_dataset(query, faithfulness_score, ans_relevancy_score, ctx_relevancy_score):
current_month = datetime.now().strftime('%Y-%m')
filename = f"logs_{current_month}.csv"
repo_id = f"{repo_name}/logs-{project_id}"
if file_exists(repo_id=repo_id, filename=f"{filename}", repo_type="dataset", token=HF_TOKEN):
local_filepath = hf_hub_download(
repo_id=repo_id,
filename=f"{filename}",
repo_type="dataset",
token=HF_TOKEN
)
df = pd.read_csv(local_filepath)
else:
print(f"File {filename} does not exist in the repository.")
return
# Normalizamos el query para la comparaci贸n
normalized_query = normalize_text(query).lower() # Convertimos a min煤sculas
# Buscamos la 煤ltima entrada que coincida con el query, convirtiendo a min煤sculas en la comparaci贸n
matching_indices = df.index[df['user_message'].str.lower().str.contains(normalized_query, na=False, regex=False)].tolist()
if matching_indices:
last_index = matching_indices[-1] # Tomamos la 煤ltima coincidencia
# Agregamos los puntajes a las columnas correspondientes
df.at[last_index, 'groundedness'] = faithfulness_score
df.at[last_index, 'answer_rel'] = ans_relevancy_score
df.at[last_index, 'context_rel'] = ctx_relevancy_score
df.to_csv(filename, index=False)
api = HfApi()
api.upload_file(
path_or_fileobj=filename,
path_in_repo=f"{filename}",
repo_id=repo_id,
token=HF_TOKEN,
repo_type="dataset"
)
else:
print("No matching query found in the dataset.")
# Funci贸n para verificar si un archivo existe en el repositorio
def file_exists(repo_id, filename, repo_type="dataset", token=None):
files = list_repo_files(repo_id=repo_id, repo_type=repo_type, token=token)
return filename in files
# Funci贸n para cargar las hojas de auditor铆a disponibles
def load_available_logs():
repo_id = f"{repo_name}/logs-{project_id}"
files = list_repo_files(repo_id=repo_id, repo_type="dataset", token=HF_TOKEN)
# Filtramos los archivos CSV con formato 'logs_YYYY-MM.csv'
available_months = [f.split('_')[1].replace('.csv', '') for f in files if f.startswith('logs_')]
return available_months
# Cargar los logs del mes seleccionado
def load_audit_trail(selected_month):
filename = f"logs_{selected_month}.csv"
repo_id = f"{repo_name}/logs-{project_id}"
if file_exists(repo_id=repo_id, filename=filename, repo_type="dataset", token=HF_TOKEN):
local_filepath = hf_hub_download(
repo_id=repo_id,
filename=filename,
repo_type="dataset",
token=HF_TOKEN
)
df = pd.read_csv(local_filepath)
# Convertir el campo 'timestamp' a una cadena con el formato UTC-0
df["timestamp"] = pd.to_datetime(df["timestamp"]).dt.strftime('%Y-%m-%d %H:%M:%S UTC-0')
# Ordenar por la columna timestamp de forma descendente
df = df.sort_values(by="timestamp", ascending=False)
# Renombrar las columnas para visualizaci贸n
df = df.rename(columns={
"timestamp": "Marca de Tiempo",
"user_message": "Mensaje Usuario",
"response_text": "Respuesta",
"flag": "Etiqueta",
"user": "Usuario",
"groundedness": "Groundedness",
"answer_rel": "Answer Relev.",
"context_rel": "Context Relev."
})
return df
else:
return pd.DataFrame(columns=["Marca de Tiempo", "Mensaje Usuario", "Respuesta", "Etiqueta", "Usuario"])
##### Logs end ##########
##### Evaluate start ##########
from llama_index.core.evaluation import FaithfulnessEvaluator
from llama_index.core.evaluation import RelevancyEvaluator
from llama_index.core.evaluation import AnswerRelevancyEvaluator
from llama_index.core.evaluation import ContextRelevancyEvaluator
final_response = ""
query = ""
def ctx_relevancy_eval():
global final_response
global query
# Verificamos si 'final_response' tiene el atributo 'source_nodes'
if not hasattr(final_response, 'source_nodes'):
raise AttributeError("El objeto 'final_response' no tiene un atributo 'source_nodes'.")
# Obtener los source_nodes de la respuesta
source_nodes = final_response.source_nodes
# Extraer los textos de los source nodes
contexts = []
for node in source_nodes:
if node.score <= 1: # Excluir nodos con score > 1
text = node.node.text.strip()
contexts.append(text)
# Si no se encuentran textos en los source nodes, puedes manejarlo de alguna forma
if not contexts:
raise ValueError("No se encontraron textos en los source nodes.")
evaluator = ContextRelevancyEvaluator(llm=gpt4omini)
evaluation_result = evaluator.evaluate(query=query, contexts=contexts)
# Extraer el puntaje de la evaluaci贸n
relevancy_score = evaluation_result.score
print("Context Relevancy: " + str(relevancy_score) + "\n")
print("Nodes: " + str(contexts) + "\n")
print("Query: " + str(query) + "\n\n----------")
return relevancy_score
def faithfulness_eval():
global final_response
evaluator = FaithfulnessEvaluator(llm=gpt4omini)
eval_result = evaluator.evaluate_response(response=final_response)
print("Groundedness: " + str(eval_result.score) + " - " + str(eval_result.passing) + "\n")
print("Respuesta: " + str(final_response) + "\n\n----------")
return float(eval_result.score)
def ans_relevancy_eval():
global final_response
global query
evaluator = AnswerRelevancyEvaluator(llm=gpt4omini)
eval_result = evaluator.evaluate(query=query, response=final_response.response)
print("Answer Relevancy: " + str(eval_result.score) + " - " + str(eval_result.passing) + "\n")
print("Respuesta: " + str(final_response.response) + "\n")
print("Query: " + str(query) + "\n\n----------")
return float(eval_result.score)
def evaluate():
global query
# Evaluaciones y escalado
faithfulness_score = round(faithfulness_eval() * 5, 1) # Redondear a un decimal
ans_relevancy_score = round(ans_relevancy_eval() * 5, 1) # Redondear a un decimal
ctx_relevancy_score = round(ctx_relevancy_eval() * 5, 1) # Redondear a un decimal
# Llamamos a save_evals_to_dataset
save_evals_to_dataset(query, faithfulness_score, ans_relevancy_score, ctx_relevancy_score)
def get_color(value):
if value <= 1.6667:
return '#f07b61' # Rojo
elif value <= 3.3334:
return '#f3e076' # Amarillo
else:
return '#84fa57' # Verde
color1 = get_color(faithfulness_score)
color2 = get_color(ans_relevancy_score)
color3 = get_color(ctx_relevancy_score)
html_output = f"""
<div style="display: flex; justify-content: space-around; align-items: center; width: 100%; flex-direction: column; text-align: center; margin: 10px 0;">
<div style="display: flex; justify-content: space-around; align-items: center; width: 100%;">
<div style="margin: 10px; display: flex; flex-direction: column; align-items: center;">
<div style="width: 50px; height: 50px; background-color: {color1}; border-radius: 50%; display: flex; justify-content: center; align-items: center; color: white; font-weight: bold;">
{faithfulness_score}
</div>
<div style="margin-top: 6px;">Groundedness</div>
</div>
<div style="margin: 10px; display: flex; flex-direction: column; align-items: center;">
<div style="width: 50px; height: 50px; background-color: {color2}; border-radius: 50%; display: flex; justify-content: center; align-items: center; color: white; font-weight: bold;">
{ans_relevancy_score}
</div>
<div style="margin-top: 6px;">Answer Relevance</div>
</div>
<div style="margin: 10px; display: flex; flex-direction: column; align-items: center;">
<div style="width: 50px; height: 50px; background-color: {color3}; border-radius: 50%; display: flex; justify-content: center; align-items: center; color: white; font-weight: bold;">
{ctx_relevancy_score}
</div>
<div style="margin-top: 6px;">Context Relevance</div>
</div>
</div>
</div>
"""
return html_output
##### Evaluate end ##########
chat_history_engine = []
result_metadata = ""
result_texts = ""
result_evals = ""
css = """
.block {
background: rgba(245, 247, 249, 0.7) !important;
}
#component-1 {
background: transparent !important;
}
.block.accordion > button:first-of-type span {
font-size: medium !important;
font-weight: bold !important;
}
.examples .block {
background: transparent !important;
}
table {
font-size: x-small !important;
}
"""
with gr.Blocks(theme='sudeepshouche/minimalist', css=css) as demo:
def get_ref():
return {mkdn: gr.Markdown(result_metadata), texts: gr.HTML(str(result_texts))}
def get_logs(selected_month):
df = load_audit_trail(selected_month)
return df
def get_evals():
global result_evals
global final_response
global query
# Verificar si 'final_response' est谩 vac铆o
if not final_response:
# Si no hay final_response pero hay un resultado previo en result_evals, devolverlo
if result_evals:
return {evals: gr.HTML(f"""
<div style="display: flex; justify-content: space-around; align-items: center; width: 100%; flex-direction: column; text-align: center; margin: 10px 0;">
{result_evals}
<p style="font-size: 10px;">Esta evaluaci贸n corresponde a la consulta: <strong>{query}</strong></p>
</div>
""")}
# Si no hay final_response ni resultados previos, mostrar advertencia
gr.Info("Se necesita una respuesta completa para iniciar la evaluaci贸n.")
return {evals: gr.HTML(f"""
<div style="display: flex; justify-content: space-around; align-items: center; width: 100%; flex-direction: column; text-align: center; margin: 10px 0;">
Se necesita una respuesta completa para iniciar la evaluaci贸n.</div>
""")}
# Ejecuta la evaluaci贸n si final_response est谩 disponible
result_evals = evaluate()
# Reiniciar 'final_response' despu茅s de la evaluaci贸n
final_response = ""
# Devolver el resultado de la evaluaci贸n
return {evals: gr.HTML(f"""{result_evals}"""), eval_accord: gr.Accordion(elem_classes="accordion", label="Evaluaciones", open=True)}
def refresh(chat_history):
global kg_data
global chat_history_engine
global result_metadata
kg_data = []
chat_history_engine = []
result_metadata = ""
chat_history = [[None, None]]
return chat_history
def summarize_assistant_messages(chat_history: List[ChatMessage]) -> List[ChatMessage]:
# Encontrar la ante煤ltima respuesta del asistente
assistant_messages = [msg for msg in chat_history if msg.role == MessageRole.ASSISTANT]
if len(assistant_messages) < 2:
return chat_history # No hay suficientes mensajes del asistente para resumir
anteultima_respuesta = assistant_messages[-2]
# Usar GPT-3.5 para generar un resumen de la ante煤ltima respuesta del asistente
prompt = Prompt(f"Responder SOLO con un resumen del siguiente texto: \n\n{anteultima_respuesta.content}")
response = llm.predict(prompt)
# Crear un nuevo ChatMessage con el resumen como contenido y el rol de asistente
summarized_message = ChatMessage(content=response, role=MessageRole.ASSISTANT)
# Reconstruir el historial de chat reemplazando la ante煤ltima respuesta del asistente con el resumen
new_chat_history = [msg if msg != anteultima_respuesta else summarized_message for msg in chat_history]
return new_chat_history
def respond(message, chat_history):
global chat_history_engine
global result_metadata
global result_texts
global final_response
global query
# Inicializar el historial de chat si est谩 vac铆o con el mensaje del usuario actual
if not chat_history:
chat_history = [[message, ""]]
else:
# Agregar el mensaje actual al historial de chat
chat_history.append([message, ""])
# Resumir los mensajes previos en chat_history_engine
chat_history_engine = summarize_assistant_messages(chat_history_engine)
# Generar la respuesta usando el motor de chat
response = chat_engine.stream_chat(message, chat_history=chat_history_engine)
# Extraer la informaci贸n de los metadatos y textos de la respuesta
metadata_info = extraer_informacion_metadata(response, max_results=10)
texts_info = extraer_textos_metadata(response, max_results=10)
if metadata_info:
result_metadata = "\n".join(metadata_info)
if texts_info:
result_texts = texts_info
# Procesar la respuesta generada y agregarla al historial del chat
for text in response.response_gen:
chat_history[-1][1] += text
yield "", chat_history # Actualiza el historial del chat y retorna el nuevo estado
# Guardar la conversaci贸n en el dataset
save_to_dataset(message, chat_history[-1][1], "no-ingresado")
final_response = response
query = message
gr.Markdown("""
# PharmaWise GMP Colombia Chat 4.7
Realiza preguntas a tus datos y obt茅n al final del texto las paginas y documentos utilizados generar tu responder.
""")
with gr.Row():
with gr.Column():
chatbot = gr.Chatbot(show_label=False, show_copy_button=True, ) #layout="panel"
pregunta = gr.Textbox(show_label=False, autofocus=True, placeholder="Realiza tu consulta...")
pregunta.submit(respond, [pregunta, chatbot], [pregunta, chatbot])
with gr.Row():
btn_send = gr.Button(value="Preguntar", variant="primary")
clear = gr.Button(value="Limpiar")
with gr.Row(elem_classes="examples"):
gr.Examples(label="Ejemplos", examples=["Implementaci贸n de la res. 3157 de 2018"], inputs=[pregunta])
with gr.Column():
with gr.Accordion(elem_classes="accordion", label="Bases de datos del conocimiento", open=False):
gr.Markdown("""
###### [1] Gu铆a BPL
###### [2] Gu铆a BPM Medicamentos
###### [3] MANUAL DE NORMAS T脡CNICAS DE CALIDAD
###### [4] Resoluci贸n 1160 - GMP - 2016
###### [5] Resoluci贸n 2266 - DECRETO FITOTERAP脡UTICOS - 2004
###### [6] Resoluci贸n 3619 - GLP - 2013
###### [7] Resoluci贸n 005107 - FITOTERAPEUTICOS - 2005
###### [8] Resoluci贸n 3690 - 2016
###### [9] Resoluci贸n 3157 - 2018
###### [10] Preguntas y respuestas Res. 3157 - 2018
""")
with gr.Accordion(elem_classes="accordion", label="Referencias", open=True):
mkdn = gr.Markdown()
with gr.Row():
btn_graph = gr.Button(value="Grafo")
btn_ref = gr.Button(value="Referencias")
btn_eval = gr.Button(value="Evaluar")
eval_accord = gr.Accordion(elem_classes="accordion", label="Evaluaciones", open=False)
with eval_accord:
evals = gr.HTML()
gr.Markdown("""| **Evaluador** | **Qu茅 mide** | **Ejemplo de uso** | **Diferencias clave** |
|-----------------------|-------------------------------------------------------|-------------------------------------------------------|--------------------------------------------------------|
| **Groundedness** | Qu茅 tan fundamentada est谩 la respuesta en el contexto. | 驴La respuesta est谩 respaldada por el contexto proporcionado? | Se enfoca en la relaci贸n entre la respuesta y el contexto. |
| **Answer Relevance** | Qu茅 tan relevante es la respuesta para la consulta. | 驴La respuesta es pertinente a lo que el usuario pregunt贸? | Se centra en la relevancia de la respuesta ante la consulta. |
| **Context Relevance** | Qu茅 tan relevante es el contexto recuperado para la consulta. | 驴El contexto obtenido es relevante para la consulta del usuario? | Se enfoca en la pertinencia del contexto en relaci贸n con la consulta. |
""")
with gr.Row():
grafo = gr.Image(label="Grafo", show_share_button=False)
with gr.Accordion(elem_classes="accordion", label="Audit trail", open=False):
with gr.Row():
with gr.Column():
available_months = load_available_logs()
default_month = available_months[-1] if available_months else None
dropdown = gr.Dropdown(choices=available_months, label="Seleccionar mes", value=default_month)
btn_logs = gr.Button(value="Actualizar")
with gr.Column():
gr.Markdown()
with gr.Column():
gr.Markdown()
# Define un DataFrame con un ancho fijo para response_text
logs_df = gr.DataFrame(headers=["Marca de Tiempo", "Mensaje Usuario", "Respuesta", "Etiqueta", "Usuario"], wrap=True, line_breaks=True)
with gr.Accordion(elem_classes="accordion", label="Referencias ampliadas", open=False):
texts = gr.HTML()
btn_logs.click(fn=get_logs, inputs=[dropdown], outputs=[logs_df])
btn_ref.click(fn=get_ref, outputs=[mkdn, texts])
btn_eval.click(fn=get_evals, outputs=[evals, eval_accord])
btn_send.click(respond, [pregunta, chatbot], [pregunta, chatbot])
btn_graph.click(draw_graph, outputs=[grafo])
clear.click(refresh, inputs=[chatbot], outputs=[chatbot])
chatbot.like(print_like_dislike, None, None)
demo.queue(default_concurrency_limit=20)
demo.launch()