import gradio as gr import torch from sentence_transformers import SentenceTransformer from ddgs import DDGS import time # Load Model model = SentenceTransformer( "RikkaBotan/stable-static-embedding-fast-retrieval-mrl-en", trust_remote_code=True, device="cuda" if torch.cuda.is_available() else "cpu" ) # Web Search with error handling def web_search(query, max_results=100): results = [] with DDGS() as ddgs: try: for i, r in enumerate(ddgs.text(query, max_results=max_results), start=1): try: results.append({ "title": r.get("title", ""), "body": r.get("body", ""), "href": r.get("href", "") }) except Exception as e: print(f"Skip doc {i}: {e}") except Exception as e: print(f"Skip web batch (max={max_results}): {e}") return results # Standard Semantic Search def semantic_web_search(query): if query.strip() == "": return "Please enter a search query." docs = web_search(query, max_results=100) texts = [d["title"] + " " + d["body"] for d in docs] with torch.no_grad(): embeddings = model.encode( [query] + texts[:256], convert_to_tensor=True, normalize_embeddings=True ) query_emb = embeddings[0] doc_embs = embeddings[1:] scores = (query_emb @ doc_embs.T).cpu().numpy() ranked = sorted(zip(scores, docs), key=lambda x: x[0], reverse=True)[:30] md = "" for i, (score, d) in enumerate(ranked): md += f""" #### 💎 Rank {i+1} [{d['title']}]({d['href']}) **Score:** `{score:.4f}` {d['body']} --- """ return md # Progressive Threshold Search with progress def progressive_search(query, threshold=0.7, step=50, max_cap=999): if query.strip() == "": yield "Please enter a search query." return current_k = step while current_k <= max_cap: try: docs = web_search(query, max_results=current_k) except Exception as e: yield f"Skipped batch {current_k} due to error: {e}" current_k += step continue if len(docs) == 0: yield f"No documents fetched for {current_k} results" current_k += step continue texts = [d["title"] + " " + d["body"] for d in docs] with torch.no_grad(): embeddings = model.encode( [query] + texts[:256], convert_to_tensor=True, normalize_embeddings=True ) query_emb = embeddings[0] doc_embs = embeddings[1:] scores = (query_emb @ doc_embs.T).cpu().numpy() best_score = float(scores.max()) md = f"### Searching…\n- Documents examined: `{len(docs)}`\n- Best score so far: `{best_score:.4f}`\n" yield md if best_score >= threshold: ranked = sorted(zip(scores, docs), key=lambda x: x[0], reverse=True)[:5] md = f"### Threshold reached!\n" for i, (score, d) in enumerate(ranked): md += f""" #### Rank {i+1} [{d['title']}]({d['href']}) **Score:** `{score:.4f}` {d['body']} --- """ yield md return current_k += step time.sleep(1) ranked = sorted(zip(scores, docs), key=lambda x: x[0], reverse=True)[:5] md = f"### Threshold not reached in max search range.\n" for i, (score, d) in enumerate(ranked): md += f""" #### Rank {i+1} [{d['title']}]({d['href']}) **Score:** `{score:.4f}` {d['body']} --- """ yield md # UI pastel_css = """ body { background: linear-gradient(180deg, #f5f9ff 0%, #eaf3ff 40%, #dbeafe 100%); } /* gradient headings */ h1, h2, h3, h4 { background: linear-gradient(135deg, #0b1f5e 0%, #1e3a8a 15%, #3b82f6 30%, #93c5fd 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 800; letter-spacing: 0.4px; padding: 4px; } /* optional: slightly softer subtitle tone */ h2, h3 { opacity: 0.9; } .gradio-container { font-family: 'Helvetica Neue', sans-serif; color: #1e3a8a; } /* model card */ .model-card { background: #ffffff; border-radius: 18px; padding: 22px; border: 1px solid #dbeafe; box-shadow: 0 12px 20px rgba(60,120,255,0.18); margin-bottom: 20px; } /* result card */ .result-card { background: #ffffff; border-radius: 18px; padding: 22px; border: 1px solid #dbeafe; box-shadow: 0 12px 20px rgba(60,120,255,0.18); } .gr-markdown, .prose { border: none !important; box-shadow: none !important; padding: 0 !important; color: #1e3a8a !important; } .model-card, .result-card { background: #ffffff; color: #1e3a8a; } @media (prefers-color-scheme: dark) { body { background: linear-gradient(180deg, #0f172a 0%, #1e293b 40%, #334155 100%); } .gradio-container { color: #dbeafe; } .gr-markdown, .prose { color: #dbeafe !important; } .model-card, .result-card { background: #1a1a1a; color: #dbeafe; border: 1px solid #3b82f6; box-shadow: 0 12px 20px rgba(60,120,255,0.18); } .gr-markdown, .prose { color: #dbeafe !important; } } textarea, input { border-radius: 12px !important; border: 1px solid #c7ddff !important; background-color: #f5f9ff !important; color: #1e3a8a !important; } button { background: linear-gradient(135deg, #1e3a8a 0%, #3b82f6 40%, #93c5fd 100%) !important; color: #ffffff !important; border-radius: 14px !important; border: 1px solid #93c5fd !important; font-weight: 600; letter-spacing: 0.3px; box-shadow: 0 6px 14px rgba(60,120,255,0.28), inset 0 1px 0 rgba(255,255,255,0.6); transition: all 0.25s ease; } button:hover { background: linear-gradient(135deg, #1b3380 0%, #2563eb 40%, #7fb8ff 100%) !important; box-shadow: 0 8px 18px rgba(60,120,255,0.35), inset 0 1px 0 rgba(255,255,255,0.7); transform: translateY(-1px); } button:active { transform: translateY(1px); box-shadow: 0 3px 8px rgba(60,120,255,0.2), inset 0 2px 4px rgba(0,0,0,0.08); } """ with gr.Blocks(css=pastel_css) as demo: gr.Markdown('# Semantic Web Search and Deep Web Search') gr.Markdown('## Fast Retrieval with Stable Static Embedding') with gr.Column(elem_classes="model-card"): gr.Markdown(""" ## About this Model **RikkaBotan/stable-static-embedding-fast-retrieval-mrl-en** ### Performance - **NanoBEIR NDCG@10 = 0.5124** - Higher than other static embedding models ### Efficiency - 512 dimensions - ~2× faster retrieval - Separable Dynamic Tanh normalization """) with gr.Tabs(): # Standard with gr.Tab("Standard Search"): query1 = gr.Textbox( value="What is Stable Static Embedding?", label="Enter your search query" ) btn1 = gr.Button("Search") with gr.Column(elem_classes="result-card"): out1 = gr.Markdown() btn1.click( semantic_web_search, inputs=query1, outputs=out1, ) # deep with gr.Tab("Deep Search"): query2 = gr.Textbox( value="What is Stable Static Embedding?", label="Enter your search query" ) threshold = gr.Slider( 0.3, 0.95, value=0.7, step=0.05, label="Score Threshold" ) btn2 = gr.Button("Run Deep Search") with gr.Column(elem_classes="result-card"): out2 = gr.Markdown() btn2.click( progressive_search, inputs=[query2, threshold], outputs=out2, show_progress=True, ) gr.Markdown("© 2026 Rikka Botan") demo.launch()