import gradio as gr import chromadb import pandas as pd from sentence_transformers import SentenceTransformer import re import numpy # --- 1. SETUP MODELS AND DATABASE --- print("Loading embedding model...") #embedding_model = SentenceTransformer('rufimelo/bert-large-portuguese-cased-sts') embedding_model = SentenceTransformer('jmbrito/ptbr-similarity-e5-small') client = chromadb.Client() collection = client.get_or_create_collection( name="transcript_comparison_app", metadata={"hnsw:space": "cosine"} ) print("ChromaDB collection ready.") # --- 2. NEW: DEFINE AND PRE-COMPUTE GUIDELINE PROFILES --- # Define the positive phrases for each guideline you want to test GUIDELINE_PROFILES = { "Agent Empathy": [ "Sinto muito por esse transtorno.", "Eu entendo completamente sua frustração.", "Imagino como isso deve ser chato, vamos resolver.", "Lamento que você tenha passado por isso.", "Compreendo sua situação e peço desculpas pelo ocorrido." ], "Problem Resolution Offer": [ "Para resolver isso, posso te oferecer duas opções.", "Temos algumas alternativas para solucionar seu problema.", "A solução que posso propor é a seguinte.", "Vamos encontrar uma forma de resolver isso para você." ], "Polite Closing": [ "Obrigado por sua ligação, tenha um ótimo dia.", "Agradecemos seu contato.", "Se precisar de mais alguma coisa, é só ligar.", "Tenha uma excelente semana." ] } # Pre-compute the averaged profile embeddings when the app starts print("Computing guideline profile embeddings...") profile_embeddings = {} for guideline_name, phrases in GUIDELINE_PROFILES.items(): phrase_embeddings = embedding_model.encode(phrases) profile_embeddings[guideline_name] = numpy.mean(phrase_embeddings, axis=0) print("✅ Guideline profiles are ready.") # --- 3. CORE FUNCTIONS --- def index_transcript(transcript_text): # This function remains the same if not transcript_text.strip(): return "Please paste a transcript before indexing.", pd.DataFrame() chunks = re.split(r'(?<=[.!?])\s+', transcript_text) chunks = [chunk.strip() for chunk in chunks if len(chunk.strip()) > 5] ids = [f"chunk_{i}" for i in range(len(chunks))] if collection.count() > 0: collection.delete(ids=collection.get()['ids']) collection.add(embeddings=embedding_model.encode(chunks).tolist(), documents=chunks, ids=ids) indexed_df = pd.DataFrame({"Indexed Chunks": chunks}) return f"✅ Indexed {len(chunks)} chunks successfully!", indexed_df def search_with_single_query(query): # This is the original search method if not query.strip(): return pd.DataFrame() query_embedding = embedding_model.encode(query).tolist() results = collection.query(query_embeddings=[query_embedding], n_results=3) documents = results['documents'][0] distances = results['distances'][0] similarities = [f"{1 - dist:.2f}" for dist in distances] return pd.DataFrame({"Similarity": similarities, "Matching Chunk": documents}) def search_with_profile(guideline_name): # This is the NEW search method using the pre-computed profiles if not guideline_name: return pd.DataFrame() profile_embedding = profile_embeddings[guideline_name].tolist() results = collection.query(query_embeddings=[profile_embedding], n_results=3) documents = results['documents'][0] distances = results['distances'][0] similarities = [f"{1 - dist:.2f}" for dist in distances] return pd.DataFrame({"Similarity": similarities, "Matching Chunk": documents}) # --- 4. GRADIO INTERFACE FOR COMPARISON --- sample_transcript = """Atendente: Olá, bem-vindo à EletroMax. Meu nome é Sofia, em que posso ajudar? Cliente: Oi, Sofia. Eu comprei uma cafeteira no site de vocês na semana passada, e ela simplesmente parou de funcionar. Atendente: Puxa, que chato isso. Lamento que você tenha passado por isso. Pode me informar o número do pedido para eu localizar sua compra? Cliente: Claro, o número é 11223344. Estou bem decepcionado, usei a cafeteira só duas vezes. Atendente: Entendo perfeitamente sua frustração. Para resolver isso, posso te oferecer duas opções. Cliente: Prefiro receber um novo. Atendente: Combinado. Obrigado por sua ligação, tenha um ótimo dia. """ with gr.Blocks(theme=gr.themes.Soft()) as demo: gr.Markdown("# 🔍 Search Method Comparison") gr.Markdown("Index a transcript once, then search using both methods to compare the results.") with gr.Row(): # Indexing column is the same with gr.Column(scale=1): transcript_input = gr.Textbox(lines=15, label="Paste Transcript Here", value=sample_transcript) index_button = gr.Button("Index Transcript", variant="primary") index_status = gr.Label() indexed_preview = gr.DataFrame(headers=["Indexed Chunks"], label="Indexed Data Preview") gr.HTML("
") # Add a horizontal line for separation with gr.Row(): # Column for the simple, single query search with gr.Column(): gr.Markdown("### Method 1: Single Query Search") query_input = gr.Textbox(label="Enter a Simple Query", placeholder="Ex: o agente foi empático?") search_button_single = gr.Button("Search Single Query") results_output_single = gr.DataFrame(label="Single Query Results") # Column for the new, profile-based search with gr.Column(): gr.Markdown("### Method 2: Guideline Profile Search") profile_input = gr.Dropdown(choices=list(GUIDELINE_PROFILES.keys()), label="Select a Guideline Profile") search_button_profile = gr.Button("Search with Profile", variant="primary") results_output_profile = gr.DataFrame(label="Profile Search Results") # Wire up the components index_button.click(fn=index_transcript, inputs=[transcript_input], outputs=[index_status, indexed_preview]) search_button_single.click(fn=search_with_single_query, inputs=[query_input], outputs=[results_output_single]) search_button_profile.click(fn=search_with_profile, inputs=[profile_input], outputs=[results_output_profile]) demo.launch()