semantic bufo search find-bufo.com
bufo

add input_type parameter for optimized retrieval and cleanup

- add input_type="query" to search embeddings (src/embedding.rs)
- add input_type="document" to document embeddings (scripts/ingest_bufos.py)
- create justfile with re-index, deploy, run, build recipes
- remove unused thiserror dependency from Cargo.toml
- remove debug logs from embedding client
- update README to document input_type optimization

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

+31 -21
-1
Cargo.lock
··· 686 686 "reqwest", 687 687 "serde", 688 688 "serde_json", 689 - "thiserror 1.0.69", 690 689 "tokio", 691 690 "tracing", 692 691 ]
-1
Cargo.toml
··· 12 12 tokio = { version = "1", features = ["full"] } 13 13 reqwest = { version = "0.12", features = ["json", "multipart"] } 14 14 anyhow = "1.0" 15 - thiserror = "1.0" 16 15 dotenv = "0.15" 17 16 base64 = "0.22" 18 17 actix-governor = "0.10.0"
+5 -5
README.md
··· 36 36 to populate the vector store with bufos: 37 37 38 38 ```bash 39 - uvx scripts/ingest_bufos.py 39 + just re-index 40 40 ``` 41 41 42 42 this will: 43 43 1. scrape all bufos from bufo.zone 44 44 2. download them to `data/bufos/` 45 - 3. generate embeddings for each image 45 + 3. generate embeddings for each image with `input_type="document"` 46 46 4. upload to turbopuffer 47 47 48 48 ## development ··· 63 63 fly launch # first time 64 64 fly secrets set VOYAGE_API_TOKEN=your_token 65 65 fly secrets set TURBOPUFFER_API_KEY=your_key 66 - fly deploy 66 + just deploy 67 67 ``` 68 68 69 69 ## usage ··· 75 75 76 76 ## how it works 77 77 78 - 1. **ingestion**: all bufo images are embedded using voyage ai's multimodal model 79 - 2. **search**: user queries are embedded with the same model 78 + 1. **ingestion**: all bufo images are embedded using voyage ai's multimodal-3 model with `input_type="document"` for optimized retrieval 79 + 2. **search**: user queries are embedded with the same model using `input_type="query"` to align query and document embeddings 80 80 3. **retrieval**: turbopuffer finds the most similar bufos using cosine distance 81 81 4. **display**: results are shown with similarity scores
+22
justfile
··· 1 + # bufo search justfile 2 + 3 + # re-index all bufos with new embeddings 4 + re-index: 5 + @echo "re-indexing all bufos with input_type=document..." 6 + uv run scripts/ingest_bufos.py 7 + 8 + # deploy to fly.io 9 + deploy: 10 + @echo "deploying to fly.io..." 11 + fly deploy --wait-timeout 180 12 + 13 + # build and run locally 14 + run: 15 + @echo "building and running locally..." 16 + cargo build --release 17 + ./target/release/find-bufo 18 + 19 + # build release binary 20 + build: 21 + @echo "building release binary..." 22 + cargo build --release
+1
scripts/ingest_bufos.py
··· 171 171 json={ 172 172 "inputs": [{"content": content}], 173 173 "model": "voyage-multimodal-3", 174 + "input_type": "document", 174 175 }, 175 176 timeout=60.0, 176 177 )
+3 -14
src/embedding.rs
··· 6 6 struct VoyageEmbeddingRequest { 7 7 inputs: Vec<MultimodalInput>, 8 8 model: String, 9 + #[serde(skip_serializing_if = "Option::is_none")] 10 + input_type: Option<String>, 9 11 } 10 12 11 13 #[derive(Debug, Serialize)] ··· 50 52 }], 51 53 }], 52 54 model: "voyage-multimodal-3".to_string(), 55 + input_type: Some("query".to_string()), 53 56 }; 54 - 55 - let json_body = serde_json::to_string(&request)?; 56 - log::debug!("Sending request body: {}", json_body); 57 57 58 58 let response = self 59 59 .client ··· 81 81 .next() 82 82 .map(|d| d.embedding) 83 83 .context("no embedding returned")?; 84 - 85 - log::debug!( 86 - "Generated embedding for '{}': dimension={}, first 5 values=[{:.4}, {:.4}, {:.4}, {:.4}, {:.4}]", 87 - text, 88 - embedding.len(), 89 - embedding.get(0).unwrap_or(&0.0), 90 - embedding.get(1).unwrap_or(&0.0), 91 - embedding.get(2).unwrap_or(&0.0), 92 - embedding.get(3).unwrap_or(&0.0), 93 - embedding.get(4).unwrap_or(&0.0) 94 - ); 95 84 96 85 Ok(embedding) 97 86 }