Spaces:
Sleeping
Sleeping
| Hugging Face's logo | |
| Hugging Face | |
| Models | |
| Datasets | |
| Spaces | |
| Community | |
| Docs | |
| Pricing | |
| TiniThingsInc | |
| / | |
| README.md | |
| like | |
| 0 | |
| Sentence Similarity | |
| sentence-transformers | |
| 9 languages | |
| code | |
| semantic-search | |
| multilingual | |
| ttrpg | |
| classification | |
| embeddings | |
| License: | |
| apache-2.0 | |
| Model card | |
| Files and versions | |
| xet | |
| Community | |
| Settings | |
| README.md | |
| / | |
| app.py | |
| TiniThingsInc's picture | |
| TiniThingsInc | |
| changing to qwen | |
| 22010e1 | |
| verified | |
| about 23 hours ago | |
| raw | |
| Copy download link | |
| history | |
| blame | |
| edit | |
| delete | |
| 16.7 kB | |
| """ | |
| FairFate Embeddings API - Qwen3-Embedding-0.6B | |
| Multilingual semantic embeddings for tabletop RPG product classification | |
| """ | |
| import gradio as gr | |
| from sentence_transformers import SentenceTransformer | |
| import numpy as np | |
| from typing import List, Union | |
| import spaces # ZeroGPU decorator | |
| # Load model once at startup | |
| MODEL_NAME = "Qwen/Qwen3-Embedding-0.6B" | |
| print(f"🔄 Loading model: {MODEL_NAME}") | |
| model = SentenceTransformer(MODEL_NAME, trust_remote_code=True) | |
| print(f"✅ Model loaded successfully") | |
| print(f" Dimensions: {model.get_sentence_embedding_dimension()}") | |
| print(f" Max Seq Length: {model.max_seq_length}") | |
| # Optional: Add instruction prefix for RPG domain (improves accuracy by 1-5%) | |
| INSTRUCTION_PREFIX = "Represent this tabletop RPG product for semantic search: " | |
| # ZeroGPU: allocate GPU for 60 seconds | |
| def generate_embeddings( | |
| texts: Union[str, List[str]], | |
| use_instruction: bool = True, | |
| output_dimensions: int = 1024 | |
| ) -> List[List[float]]: | |
| """ | |
| Generate embeddings for text(s) | |
| Args: | |
| texts: Single string or list of strings | |
| use_instruction: Whether to prepend instruction prefix (recommended) | |
| output_dimensions: Output embedding size (32-1024) | |
| Returns: | |
| List of embedding vectors | |
| """ | |
| # Handle single string | |
| if isinstance(texts, str): | |
| texts = [texts] | |
| # Add instruction prefix if enabled (Qwen3 is instruction-aware) | |
| if use_instruction: | |
| texts = [INSTRUCTION_PREFIX + text for text in texts] | |
| # Generate embeddings | |
| embeddings = model.encode( | |
| texts, | |
| convert_to_numpy=True, | |
| normalize_embeddings=True, # L2 normalize for cosine similarity | |
| show_progress_bar=False, | |
| batch_size=32 | |
| ) | |
| # Resize embeddings if needed (MRL - Multilingual Representation Learning) | |
| if output_dimensions != 1024: | |
| # Qwen3 supports flexible dimensions (32-1024) | |
| # Simply truncate for smaller dimensions | |
| embeddings = embeddings[:, :output_dimensions] | |
| # Convert to list for JSON serialization | |
| return embeddings.tolist() | |
| def batch_generate(texts_input: str, use_instruction: bool, output_dims: int) -> str: | |
| """ | |
| Gradio interface for batch embedding generation | |
| Expects newline-separated texts | |
| """ | |
| if not texts_input.strip(): | |
| return "❌ Error: Please provide at least one text" | |
| texts = [t.strip() for t in texts_input.split('\n') if t.strip()] | |
| try: | |
| embeddings = generate_embeddings(texts, use_instruction, output_dims) | |
| result = f"✅ Generated {len(embeddings)} embeddings\n" | |
| result += f"📐 Dimensions: {len(embeddings[0])}\n" | |
| result += f"🌍 Languages: 100+ supported\n\n" | |
| result += "First embedding preview:\n" | |
| result += f"[{', '.join(f'{x:.3f}' for x in embeddings[0][:10])}...]\n" | |
| return result | |
| except Exception as e: | |
| return f"❌ Error: {str(e)}" | |
| def calculate_all_similarities(emb1: np.ndarray, emb2: np.ndarray) -> dict: | |
| """ | |
| Calculate comprehensive similarity metrics between two embeddings | |
| """ | |
| # Cosine Similarity (for normalized vectors, just dot product) | |
| cosine = float(np.dot(emb1, emb2)) | |
| # Euclidean Distance | |
| euclidean_dist = float(np.linalg.norm(emb1 - emb2)) | |
| euclidean_sim = 1 / (1 + euclidean_dist) | |
| # Jaccard Similarity (min/max interpretation for continuous vectors) | |
| intersection = np.sum(np.minimum(np.abs(emb1), np.abs(emb2))) | |
| union = np.sum(np.maximum(np.abs(emb1), np.abs(emb2))) | |
| jaccard = float(intersection / union if union > 0 else 0) | |
| # Sorensen-Dice Coefficient | |
| intersection = np.sum(np.minimum(np.abs(emb1), np.abs(emb2))) | |
| sum_magnitudes = np.sum(np.abs(emb1)) + np.sum(np.abs(emb2)) | |
| sorensen_dice = float(2 * intersection / sum_magnitudes if sum_magnitudes > 0 else 0) | |
| # Manhattan Distance | |
| manhattan = float(np.sum(np.abs(emb1 - emb2))) | |
| # Pearson Correlation | |
| pearson = float(np.corrcoef(emb1, emb2)[0, 1]) | |
| return { | |
| 'cosine': cosine, | |
| 'euclidean_distance': euclidean_dist, | |
| 'euclidean_similarity': euclidean_sim, | |
| 'jaccard': jaccard, | |
| 'sorensen_dice': sorensen_dice, | |
| 'manhattan': manhattan, | |
| 'pearson': pearson | |
| } | |
| def interpret_similarity(score: float, metric: str) -> tuple[str, str]: | |
| """ | |
| Interpret similarity score with emoji and description | |
| Returns: (emoji, description) | |
| """ | |
| if metric in ['cosine', 'jaccard', 'sorensen_dice', 'euclidean_similarity']: | |
| if score > 0.9: | |
| return '🟢', 'Nearly Identical' | |
| elif score > 0.7: | |
| return '🟢', 'Very Similar' | |
| elif score > 0.5: | |
| return '🟡', 'Moderately Similar' | |
| elif score > 0.3: | |
| return '🟠', 'Somewhat Similar' | |
| else: | |
| return '🔴', 'Different' | |
| elif metric == 'pearson': | |
| if score > 0.9: | |
| return '🟢', 'Strong Positive Correlation' | |
| elif score > 0.7: | |
| return '🟡', 'Moderate Positive Correlation' | |
| elif score > 0.3: | |
| return '🟠', 'Weak Positive Correlation' | |
| elif score > -0.3: | |
| return '⚪', 'No Correlation' | |
| elif score > -0.7: | |
| return '🟠', 'Weak Negative Correlation' | |
| elif score > -0.9: | |
| return '🟡', 'Moderate Negative Correlation' | |
| else: | |
| return '🔴', 'Strong Negative Correlation' | |
| else: | |
| return '⚪', 'Unknown' | |
| def calculate_similarity(text1: str, text2: str, use_instruction: bool) -> str: | |
| """ | |
| Calculate comprehensive similarity metrics between two texts | |
| """ | |
| if not text1.strip() or not text2.strip(): | |
| return "❌ Error: Please provide both texts" | |
| try: | |
| embeddings = generate_embeddings([text1, text2], use_instruction) | |
| # Calculate all similarity metrics | |
| emb1 = np.array(embeddings[0]) | |
| emb2 = np.array(embeddings[1]) | |
| metrics = calculate_all_similarities(emb1, emb2) | |
| # Build result string | |
| result = "📊 **Comprehensive Similarity Analysis**\n\n" | |
| # Cosine Similarity (Primary) | |
| emoji, interpretation = interpret_similarity(metrics['cosine'], 'cosine') | |
| result += f"**Cosine Similarity:** {emoji} {metrics['cosine']:.4f}\n" | |
| result += f"└─ {interpretation}\n\n" | |
| # Jaccard Similarity | |
| emoji, interpretation = interpret_similarity(metrics['jaccard'], 'jaccard') | |
| result += f"**Jaccard Similarity:** {emoji} {metrics['jaccard']:.4f}\n" | |
| result += f"└─ {interpretation}\n\n" | |
| # Sorensen-Dice Coefficient | |
| emoji, interpretation = interpret_similarity(metrics['sorensen_dice'], 'sorensen_dice') | |
| result += f"**Sørensen-Dice:** {emoji} {metrics['sorensen_dice']:.4f}\n" | |
| result += f"└─ {interpretation}\n\n" | |
| # Euclidean Distance & Similarity | |
| result += f"**Euclidean Distance:** {metrics['euclidean_distance']:.4f}\n" | |
| emoji, interpretation = interpret_similarity(metrics['euclidean_similarity'], 'euclidean_similarity') | |
| result += f"**Euclidean Similarity:** {emoji} {metrics['euclidean_similarity']:.4f}\n" | |
| result += f"└─ {interpretation}\n\n" | |
| # Manhattan Distance | |
| result += f"**Manhattan Distance:** {metrics['manhattan']:.2f}\n\n" | |
| # Pearson Correlation | |
| emoji, interpretation = interpret_similarity(metrics['pearson'], 'pearson') | |
| result += f"**Pearson Correlation:** {emoji} {metrics['pearson']:.4f}\n" | |
| result += f"└─ {interpretation}\n\n" | |
| # Overall assessment (based on cosine as primary) | |
| result += "---\n**Overall Assessment:**\n" | |
| cosine_emoji, cosine_interpretation = interpret_similarity(metrics['cosine'], 'cosine') | |
| result += f"{cosine_emoji} {cosine_interpretation} (Cosine: {metrics['cosine']:.4f})" | |
| return result | |
| except Exception as e: | |
| return f"❌ Error: {str(e)}" | |
| # Create Gradio interface | |
| with gr.Blocks(title="FairFate Embeddings API - Qwen3", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown(""" | |
| # 🎲 FairFate Embeddings API | |
| **Powered by Qwen3-Embedding-0.6B** - #1 Multilingual Embedding Model | |
| - 🌍 **100+ Languages** (English, Spanish, French, German, Chinese, Japanese, etc.) | |
| - 📐 **1024 Dimensions** (flexible 32-1024) | |
| - 📚 **32K Context** (massive text support) | |
| - ⚡ **Instruction-Aware** (optimized for RPG content) | |
| - 🏆 **#1 on MTEB** Multilingual Leaderboard | |
| Perfect for: Product classification, semantic search, recommendations, multilingual matching | |
| """) | |
| with gr.Tab("🔮 Generate Embeddings"): | |
| gr.Markdown(""" | |
| Generate semantic embeddings for product descriptions, titles, or any text. | |
| Enter one text per line for batch processing. | |
| """) | |
| with gr.Row(): | |
| with gr.Column(): | |
| input_text = gr.Textbox( | |
| label="Input Texts (one per line)", | |
| placeholder="Example:\nStorm King's Thunder - Epic D&D 5E adventure\nCurse of Strahd - Gothic horror campaign\nPathfinder 2E Core Rulebook", | |
| lines=8 | |
| ) | |
| use_inst = gr.Checkbox(label="Use instruction prefix (recommended for RPG content)", value=True) | |
| output_dims = gr.Slider( | |
| minimum=32, maximum=1024, value=1024, step=32, | |
| label="Output Dimensions" | |
| ) | |
| submit_btn = gr.Button("Generate Embeddings", variant="primary") | |
| with gr.Column(): | |
| output_text = gr.Textbox(label="Results", lines=12) | |
| submit_btn.click(batch_generate, inputs=[input_text, use_inst, output_dims], outputs=output_text) | |
| gr.Examples( | |
| examples=[ | |
| ["D&D 5E epic fantasy adventure with dragons and dungeons", True, 1024], | |
| ["Cyberpunk shadowrun detective noir campaign\nPathfinder 2E beginner box starter set\nCall of Cthulhu horror investigation", True, 1024], | |
| ], | |
| inputs=[input_text, use_inst, output_dims], | |
| ) | |
| with gr.Tab("🔍 Similarity Calculator"): | |
| gr.Markdown(""" | |
| **Comprehensive Similarity Analysis** - Compare two texts using multiple metrics: | |
| - **Cosine Similarity**: Angle between vectors (best for semantic meaning) | |
| - **Jaccard Similarity**: Intersection over union (set-like comparison) | |
| - **Sørensen-Dice**: Weighted intersection (emphasizes shared features) | |
| - **Euclidean Distance/Similarity**: Straight-line distance in vector space | |
| - **Manhattan Distance**: Grid-based distance (L1 norm) | |
| - **Pearson Correlation**: Linear relationship between vectors | |
| Perfect for duplicate detection, classification testing, and understanding product relationships! | |
| """) | |
| with gr.Row(): | |
| with gr.Column(): | |
| text1 = gr.Textbox( | |
| label="First Text", | |
| placeholder="Storm King's Thunder - Giant-themed D&D adventure", | |
| lines=3 | |
| ) | |
| text2 = gr.Textbox( | |
| label="Second Text", | |
| placeholder="Princes of the Apocalypse - Elemental evil campaign", | |
| lines=3 | |
| ) | |
| use_inst_sim = gr.Checkbox(label="Use instruction prefix", value=True) | |
| calc_btn = gr.Button("Calculate Similarity", variant="primary") | |
| with gr.Column(): | |
| similarity_output = gr.Textbox(label="Similarity Result", lines=8) | |
| calc_btn.click(calculate_similarity, inputs=[text1, text2, use_inst_sim], outputs=similarity_output) | |
| gr.Examples( | |
| examples=[ | |
| ["D&D 5E fantasy adventure", "Dungeons and Dragons fifth edition module", True], | |
| ["Horror investigation mystery", "Comedy fantasy lighthearted fun", True], | |
| ["Pathfinder 2E rulebook", "D&D 5E Player's Handbook", True], | |
| ], | |
| inputs=[text1, text2, use_inst_sim], | |
| ) | |
| with gr.Tab("📖 API Documentation"): | |
| gr.Markdown(""" | |
| ## 🚀 Quick Start | |
| ### Python | |
| ```python | |
| import requests | |
| import numpy as np | |
| url = "https://YOUR_USERNAME-fairfate-embeddings.hf.space/api/predict" | |
| # Generate embeddings | |
| texts = [ | |
| "Storm King's Thunder - Epic D&D 5E adventure", | |
| "Curse of Strahd - Gothic horror campaign" | |
| ] | |
| response = requests.post( | |
| url, | |
| json={ | |
| "data": [texts, True, 1024], # [texts, use_instruction, dimensions] | |
| "fn_index": 0 # Index of generate_embeddings function | |
| } | |
| ) | |
| result = response.json() | |
| embeddings = result["data"][0] | |
| print(f"Generated {len(embeddings)} embeddings") | |
| print(f"Dimensions: {len(embeddings[0])}") | |
| ``` | |
| ### TypeScript/JavaScript | |
| ```typescript | |
| const url = 'https://YOUR_USERNAME-fairfate-embeddings.hf.space/api/predict'; | |
| const response = await fetch(url, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| data: [ | |
| ["Your text here", "Another text"], | |
| true, // use_instruction | |
| 1024 // output_dimensions | |
| ], | |
| fn_index: 0 | |
| }) | |
| }); | |
| const result = await response.json(); | |
| const embeddings = result.data[0]; | |
| ``` | |
| ### cURL | |
| ```bash | |
| curl -X POST \\ | |
| https://YOUR_USERNAME-fairfate-embeddings.hf.space/api/predict \\ | |
| -H "Content-Type: application/json" \\ | |
| -d '{ | |
| "data": [["Your text here"], true, 1024], | |
| "fn_index": 0 | |
| }' | |
| ``` | |
| ## 📊 Parameters | |
| | Parameter | Type | Default | Description | | |
| |-----------|------|---------|-------------| | |
| | `texts` | string[] | required | Array of texts to embed | | |
| | `use_instruction` | boolean | true | Add instruction prefix (improves accuracy) | | |
| | `output_dimensions` | number | 1024 | Output size (32-1024) | | |
| ## 🎯 Use Cases | |
| - **Product Classification**: Auto-tag by genre, system, theme | |
| - **Semantic Search**: Find by meaning, not keywords | |
| - **Recommendations**: "Similar products" | |
| - **Duplicate Detection**: Find similar listings | |
| - **Multilingual Matching**: Cross-language similarity | |
| ## ⚡ Performance | |
| | Batch Size | GPU Throughput | CPU Throughput | | |
| |------------|----------------|----------------| | |
| | 1 | ~800/sec | ~80/sec | | |
| | 32 | ~4000/sec | ~250/sec | | |
| ## 🌍 Supported Languages | |
| English, Spanish, French, German, Italian, Portuguese, Russian, Polish, Dutch, Czech, | |
| Chinese, Japanese, Korean, Arabic, Hebrew, Hindi, Thai, Vietnamese, Indonesian, | |
| Turkish, Swedish, Norwegian, Danish, Finnish, Greek, Romanian, Hungarian, and 80+ more! | |
| ## 📝 Citation | |
| ```bibtex | |
| @misc{qwen3embedding2025, | |
| title={Qwen3 Embedding}, | |
| author={Alibaba Cloud}, | |
| year={2025}, | |
| url={https://github.com/QwenLM/Qwen3-Embedding} | |
| } | |
| ``` | |
| """) | |
| with gr.Tab("ℹ️ Model Info"): | |
| gr.Markdown(f""" | |
| ## Model Details | |
| - **Model:** {MODEL_NAME} | |
| - **Dimensions:** {model.get_sentence_embedding_dimension()} | |
| - **Max Sequence Length:** {model.max_seq_length} tokens | |
| - **Languages:** 100+ | |
| - **License:** Apache 2.0 | |
| - **Normalization:** L2 normalized (ready for cosine similarity) | |
| ## Advantages | |
| ✅ **Best Multilingual Performance** - #1 on MTEB leaderboard | |
| ✅ **Massive Context** - 32K tokens (vs 512 for most models) | |
| ✅ **Instruction-Aware** - Can customize for specific domains | |
| ✅ **Flexible Dimensions** - 32 to 1024 dimensions | |
| ✅ **Code-Switching** - Handles mixed-language text | |
| ## Resources | |
| - [Model Card](https://huggingface.co/Qwen/Qwen3-Embedding-0.6B) | |
| - [GitHub](https://github.com/QwenLM/Qwen3-Embedding) | |
| - [Blog Post](https://qwenlm.github.io/blog/qwen3-embedding/) | |
| - [MTEB Leaderboard](https://huggingface.co/spaces/mteb/leaderboard) | |
| """) | |
| # Launch with API enabled | |
| if __name__ == "__main__": | |
| demo.launch() | |