semantic bufo search find-bufo.com
bufo

add cameron winter bufo

+153
+153
scripts/add_one_bufo.py
··· 1 + #!/usr/bin/env python3 2 + # /// script 3 + # requires-python = ">=3.11" 4 + # dependencies = [ 5 + # "httpx", 6 + # "python-dotenv", 7 + # "pillow", 8 + # ] 9 + # /// 10 + """ 11 + Add a single bufo to turbopuffer. 12 + Usage: uv run scripts/add_one_bufo.py <path_to_image> 13 + """ 14 + 15 + import asyncio 16 + import base64 17 + import hashlib 18 + import os 19 + import sys 20 + from io import BytesIO 21 + from pathlib import Path 22 + 23 + import httpx 24 + from PIL import Image 25 + from dotenv import load_dotenv 26 + 27 + load_dotenv(Path(__file__).parent.parent / ".env") 28 + 29 + 30 + async def embed_image(client: httpx.AsyncClient, image_path: Path, api_key: str) -> list[float] | None: 31 + """Generate embedding for an image using Voyage AI""" 32 + try: 33 + image = Image.open(image_path) 34 + is_animated = hasattr(image, 'n_frames') and image.n_frames > 1 35 + filename_text = image_path.stem.replace("-", " ").replace("_", " ") 36 + 37 + content = [{"type": "text", "text": filename_text}] 38 + 39 + if is_animated: 40 + num_frames = image.n_frames 41 + max_frames = min(5, num_frames) 42 + frame_indices = [int(i * (num_frames - 1) / (max_frames - 1)) for i in range(max_frames)] 43 + for frame_idx in frame_indices: 44 + image.seek(frame_idx) 45 + buffered = BytesIO() 46 + image.convert("RGB").save(buffered, format="WEBP", lossless=True) 47 + img_base64 = base64.b64encode(buffered.getvalue()).decode("utf-8") 48 + content.append({ 49 + "type": "image_base64", 50 + "image_base64": f"data:image/webp;base64,{img_base64}", 51 + }) 52 + else: 53 + buffered = BytesIO() 54 + image.convert("RGB").save(buffered, format="WEBP", lossless=True) 55 + img_base64 = base64.b64encode(buffered.getvalue()).decode("utf-8") 56 + content.append({ 57 + "type": "image_base64", 58 + "image_base64": f"data:image/webp;base64,{img_base64}", 59 + }) 60 + 61 + response = await client.post( 62 + "https://api.voyageai.com/v1/multimodalembeddings", 63 + headers={ 64 + "Authorization": f"Bearer {api_key}", 65 + "Content-Type": "application/json", 66 + }, 67 + json={ 68 + "inputs": [{"content": content}], 69 + "model": "voyage-multimodal-3", 70 + "input_type": "document", 71 + }, 72 + timeout=60.0, 73 + ) 74 + response.raise_for_status() 75 + result = response.json() 76 + return result["data"][0]["embedding"] 77 + except Exception as e: 78 + print(f"error embedding {image_path.name}: {e}") 79 + return None 80 + 81 + 82 + async def upload_to_turbopuffer(filename: str, embedding: list[float], api_key: str, namespace: str): 83 + """Upload single embedding to turbopuffer""" 84 + file_hash = hashlib.sha256(filename.encode()).hexdigest()[:16] 85 + name = filename.rsplit(".", 1)[0] 86 + url = f"https://find-bufo.fly.dev/static/{filename}" 87 + 88 + async with httpx.AsyncClient() as client: 89 + response = await client.post( 90 + f"https://api.turbopuffer.com/v1/vectors/{namespace}", 91 + headers={ 92 + "Authorization": f"Bearer {api_key}", 93 + "Content-Type": "application/json", 94 + }, 95 + json={ 96 + "ids": [file_hash], 97 + "vectors": [embedding], 98 + "distance_metric": "cosine_distance", 99 + "attributes": { 100 + "url": [url], 101 + "name": [name], 102 + "filename": [filename], 103 + }, 104 + "schema": { 105 + "name": {"type": "string", "full_text_search": True}, 106 + "filename": {"type": "string", "full_text_search": True}, 107 + }, 108 + }, 109 + timeout=30.0, 110 + ) 111 + if response.status_code != 200: 112 + print(f"turbopuffer error: {response.text}") 113 + response.raise_for_status() 114 + 115 + print(f"uploaded {filename} to turbopuffer") 116 + 117 + 118 + async def main(): 119 + if len(sys.argv) < 2: 120 + print("usage: uv run scripts/add_one_bufo.py <path_to_image>") 121 + sys.exit(1) 122 + 123 + image_path = Path(sys.argv[1]) 124 + if not image_path.exists(): 125 + print(f"file not found: {image_path}") 126 + sys.exit(1) 127 + 128 + voyage_api_key = os.getenv("VOYAGE_API_TOKEN") 129 + if not voyage_api_key: 130 + print("VOYAGE_API_TOKEN not set") 131 + sys.exit(1) 132 + 133 + tpuf_api_key = os.getenv("TURBOPUFFER_API_KEY") 134 + if not tpuf_api_key: 135 + print("TURBOPUFFER_API_KEY not set") 136 + sys.exit(1) 137 + 138 + tpuf_namespace = os.getenv("TURBOPUFFER_NAMESPACE", "bufos") 139 + 140 + print(f"adding {image_path.name}...") 141 + 142 + async with httpx.AsyncClient() as client: 143 + embedding = await embed_image(client, image_path, voyage_api_key) 144 + if not embedding: 145 + print("failed to generate embedding") 146 + sys.exit(1) 147 + 148 + await upload_to_turbopuffer(image_path.name, embedding, tpuf_api_key, tpuf_namespace) 149 + print("done!") 150 + 151 + 152 + if __name__ == "__main__": 153 + asyncio.run(main())
static/bufo-is-trapped-in-a-cameron-winter-phase.png

This is a binary file and will not be displayed.