import gradio as gr
import logging
from typing import List, Tuple
import pandas as pd
import os
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Try to import torch and transformers with fallback
try:
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
DEPENDENCIES_AVAILABLE = True
except ImportError as e:
logger.warning(f"Dependencies not available: {e}")
DEPENDENCIES_AVAILABLE = False
torch = None
AutoTokenizer = None
AutoModelForCausalLM = None
class Qwen3Reranker:
def __init__(self, model_name="thebajajra/RexReranker-0.6B-v0"):
if not DEPENDENCIES_AVAILABLE:
raise ImportError("Required dependencies (torch, transformers) are not available")
self.model_name = model_name
self.tokenizer = None
self.model = None
self.token_false_id = None
self.token_true_id = None
self.max_length = 8192
self.prefix_tokens = None
self.suffix_tokens = None
self.device = "cuda" if torch.cuda.is_available() else "cpu"
self._load_model()
def _load_model(self):
"""Load the tokenizer and model"""
try:
logger.info(f"Loading {self.model_name}...")
self.tokenizer = AutoTokenizer.from_pretrained(
self.model_name,
padding_side='left'
)
# Load model with appropriate settings
if torch.cuda.is_available():
self.model = AutoModelForCausalLM.from_pretrained(
self.model_name,
torch_dtype=torch.float16,
device_map="auto"
).eval()
else:
self.model = AutoModelForCausalLM.from_pretrained(
self.model_name
).eval()
# Set up tokens
self.token_false_id = self.tokenizer.convert_tokens_to_ids("no")
self.token_true_id = self.tokenizer.convert_tokens_to_ids("yes")
# Set up prefix and suffix
prefix = "<|im_start|>system\nJudge whether the Document meets the requirements based on the Query and the Instruct provided. Note that the answer can only be \"yes\" or \"no\".<|im_end|>\n<|im_start|>user\n"
suffix = "<|im_end|>\n<|im_start|>assistant\n\n\n\n\n"
self.prefix_tokens = self.tokenizer.encode(prefix, add_special_tokens=False)
self.suffix_tokens = self.tokenizer.encode(suffix, add_special_tokens=False)
logger.info("Model loaded successfully!")
except Exception as e:
logger.error(f"Error loading model: {e}")
raise e
def format_instruction(self, instruction: str, query: str, doc: str) -> str:
"""Format the instruction for the reranker"""
if instruction is None or instruction.strip() == "":
instruction = 'Given a web search query, retrieve relevant passages that answer the query'
return f": {instruction}\n: {query}\n: {doc}"
def process_inputs(self, pairs: List[str]) -> dict:
"""Process input pairs for the model"""
inputs = self.tokenizer(
pairs,
padding=True,
truncation=True,
return_tensors='pt',
max_length=self.max_length
)
return inputs
def rank_documents(self, instruction: str, query: str, documents: List[str]) -> List[Tuple[str, float, str]]:
"""Rank documents based on their relevance to the query"""
if not DEPENDENCIES_AVAILABLE:
return [(doc[:100] + "...", 0.5, "Dependencies not available") for doc in documents]
results = []
for i, doc in enumerate(documents):
try:
# Format the instruction
formatted_instruction = self.format_instruction(instruction, query, doc)
# Tokenize
inputs = self.tokenizer(
formatted_instruction,
return_tensors='pt',
max_length=self.max_length,
truncation=True
)
if torch.cuda.is_available():
inputs = {k: v.cuda() for k, v in inputs.items()}
# Get model output
with torch.no_grad():
outputs = self.model(**inputs)
logits = outputs.logits[0, -1, :]
# Get probabilities for "yes" and "no" tokens
yes_prob = torch.softmax(logits, dim=-1)[self.token_true_id].item()
no_prob = torch.softmax(logits, dim=-1)[self.token_false_id].item()
# Calculate relevance score (probability of "yes")
relevance_score = yes_prob / (yes_prob + no_prob)
# Truncate document for display
display_doc = doc[:200] + "..." if len(doc) > 200 else doc
results.append((display_doc, relevance_score, f"Document {i+1}"))
except Exception as e:
logger.error(f"Error processing document {i+1}: {e}")
display_doc = doc[:200] + "..." if len(doc) > 200 else doc
results.append((display_doc, 0.0, f"Error: {str(e)[:50]}..."))
# Sort by relevance score (highest first)
results.sort(key=lambda x: x[1], reverse=True)
return results
# Initialize the reranker
try:
reranker = Qwen3Reranker()
except Exception as e:
logger.error(f"Failed to initialize reranker: {e}")
reranker = None
def rerank_documents(instruction, query, documents_text):
"""Gradio interface function"""
if not reranker:
return pd.DataFrame([["Error", "Model not loaded", 0.0]],
columns=["Document", "Relevance Score", "Rank"])
if not query.strip():
return pd.DataFrame([["Error", "Please provide a query", 0.0]],
columns=["Document", "Relevance Score", "Rank"])
if not documents_text.strip():
return pd.DataFrame([["Error", "Please provide documents", 0.0]],
columns=["Document", "Relevance Score", "Rank"])
# Split documents by double newlines or numbered list format
documents = []
if '\n\n' in documents_text:
documents = [doc.strip() for doc in documents_text.split('\n\n') if doc.strip()]
else:
# Try to split by numbered format (1., 2., etc.)
lines = documents_text.strip().split('\n')
current_doc = ""
for line in lines:
if line.strip() and (line.strip()[0].isdigit() and '.' in line[:5]):
if current_doc:
documents.append(current_doc.strip())
current_doc = line
else:
current_doc += "\n" + line
if current_doc:
documents.append(current_doc.strip())
if not documents:
documents = [documents_text] # Treat as single document
# Rank documents
results = reranker.rank_documents(instruction, query, documents)
# Create DataFrame for display
df_data = []
for i, (doc, score, label) in enumerate(results):
df_data.append([f"#{i+1}", doc, f"{score:.4f}"])
return pd.DataFrame(df_data, columns=["Rank", "Document", "Relevance Score"])
def create_gradio_interface():
"""Create the Gradio interface"""
with gr.Blocks(title="RexRanker: E-commerce reranker", theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# 🔍 RexRanker: E-commerce reranker
This tool uses the **RexReranker-0.6B** model to rank products by their relevance to search query.
## How to use:
1. **Instruction** (optional): Provide context for the ranking task
2. **Query**: Enter your search query
3. **Documents**: Enter multiple documents separated by double newlines (\\n\\n) or as a numbered list
""")
with gr.Row():
with gr.Column(scale=1):
instruction_input = gr.Textbox(
label="Instruction (Optional)",
placeholder="Given a web search query, retrieve relevant passages that answer the query",
value="Given a web search query, retrieve relevant passages that answer the query",
lines=2
)
query_input = gr.Textbox(
label="Query",
placeholder="Enter your search query here...",
lines=2
)
documents_input = gr.Textbox(
label="Documents to Rank",
placeholder="Enter documents separated by double newlines...\n\nDocument 1 content here\n\nDocument 2 content here\n\nDocument 3 content here",
lines=10
)
rank_button = gr.Button("🔍 Rank Documents", variant="primary")
gr.Markdown("### Example:")
gr.Examples(
examples=[
[
"Given a e-commerce search query, retrieve relevant products that are relevant to the query",
"set of 10 blue off road bulbs",
"Set of 10 Blue Light Bulbs Ten 3157 Pack Replacement Off Road AutoAndArt"
]
],
inputs=[instruction_input, query_input, documents_input]
)
with gr.Column(scale=1):
# FIXED: Remove the height parameter
results_display = gr.DataFrame(
label="Ranking Results",
headers=["Rank", "Document", "Relevance Score"],
interactive=False
)
rank_button.click(
fn=rerank_documents,
inputs=[instruction_input, query_input, documents_input],
outputs=[results_display]
)
gr.Markdown("""
### About the Model
- **Model**: thebajajra/RexReranker-0.6B
- **Task**: Document reranking based on query relevance
- **Output**: Relevance scores between 0 and 1 (higher = more relevant)
""")
return demo
if __name__ == "__main__":
demo = create_gradio_interface()
demo.launch()