import gradio as gr import pandas as pd import io import base64 import uuid import pixeltable as pxt import numpy as np from pixeltable.iterators import DocumentSplitter from pixeltable.functions.huggingface import sentence_transformer from pixeltable.functions import openai from gradio.themes import Monochrome from huggingface_hub import HfApi, HfFolder import os import getpass # Store API keys if 'OPENAI_API_KEY' not in os.environ: os.environ['OPENAI_API_KEY'] = getpass.getpass('OpenAI API key:') # Set up embedding function @pxt.expr_udf def e5_embed(text: str) -> np.ndarray: return sentence_transformer(text, model_id='intfloat/e5-large-v2') # Create prompt function @pxt.udf def create_prompt(top_k_list: list[dict], question: str) -> str: concat_top_k = '\n\n'.join( elt['text'] for elt in reversed(top_k_list) ) return f''' PASSAGES: {concat_top_k} QUESTION: {question}''' # New UDF for creating messages @pxt.udf def create_messages(prompt: str) -> list[dict]: """Creates a structured message list for the LLM from the prompt""" return [ { 'role': 'system', 'content': 'Answer questions using only the provided context. If the context lacks sufficient information, state this clearly.' }, { 'role': 'user', 'content': prompt } ] def validate_token(token): try: api = HfApi() user_info = api.whoami(token=token) return user_info is not None except Exception: return False def process_files(token, pdf_files, chunk_limit, chunk_separator): if not validate_token(token): return "Invalid token. Please enter a valid Hugging Face token." # Initialize Pixeltable pxt.drop_dir('chatbot_demo', force=True) pxt.create_dir('chatbot_demo') # Create a table to store the uploaded PDF documents t = pxt.create_table( 'chatbot_demo.documents', { 'document': pxt.DocumentType(nullable=True), 'question': pxt.StringType(nullable=True) } ) # Insert the PDF files into the documents table t.insert({'document': file.name} for file in pdf_files if file.name.endswith('.pdf')) # Create a view that splits the documents into smaller chunks chunks_t = pxt.create_view( 'chatbot_demo.chunks', t, iterator=DocumentSplitter.create( document=t.document, separators=chunk_separator, limit=chunk_limit if chunk_separator in ["token_limit", "char_limit"] else None, metadata='title,heading,sourceline' ) ) # Add an embedding index to the chunks for similarity search chunks_t.add_embedding_index('text', string_embed=e5_embed) @chunks_t.query def top_k(query_text: str): sim = chunks_t.text.similarity(query_text) return ( chunks_t.order_by(sim, asc=False) .select(chunks_t.text, sim=sim) .limit(5) ) # Add computed columns to create the chain of transformations t['question_context'] = chunks_t.queries.top_k(t.question) t['prompt'] = create_prompt(t.question_context, t.question) t['messages'] = create_messages(t.prompt) # New computed column for messages # Add the response column using the messages computed column t['response'] = openai.chat_completions( model='gpt-4o-mini-2024-07-18', messages=t.messages, # Use the computed messages column max_tokens=300, top_p=0.9, temperature=0.7 ) t['gpt4omini'] = t.response.choices[0].message.content return "Files processed successfully. You can start the discussion." def get_answer(token, msg): if not validate_token(token): return "Invalid token. Please enter a valid Hugging Face token." t = pxt.get_table('chatbot_demo.documents') # Insert the question into the table t.insert([{'question': msg}]) # The answer will be automatically generated through the chain of computed columns answer = t.select(t.gpt4omini).where(t.question == msg).collect()['gpt4omini'][0] return answer def respond(token, message, chat_history): bot_message = get_answer(token, message) chat_history.append((message, bot_message)) return "", chat_history # Gradio interface with gr.Blocks(theme=gr.themes.Base()) as demo: gr.Markdown( """
Pixeltable is a declarative interface for working with text, images, embeddings, and even video, enabling you to store, transform, index, and iterate on data.