smart-chatbot-api / docs /embedding_cost_analysis.md
GitHub Actions
Deploy from GitHub Actions (2026-05-31 09:04 UTC)
55c0d78
|
Raw
History Blame Contribute Delete
14.9 kB
# Embedding Cost Analysis β€” Gemini Embedding 2 vs BAAI/bge-small-en-v1.5
**Created:** 2026-05-16
**Context:** Decision document for embedding model selection and KB entry billing design.
**Status:** Gemini Embedding 2 migration recommended. See Section 6 for implementation plan.
---
## Table of Contents
1. [Current Embedding Setup](#1-current-embedding-setup)
2. [Gemini Embedding 2 Overview](#2-gemini-embedding-2-overview)
3. [OpenAI Embedding Lineup (2026)](#3-openai-embedding-lineup-2026)
4. [Cost Per Entry by Modality](#4-cost-per-entry-by-modality)
5. [Scale Analysis: 1,000 Tenants Γ— 500 Entries](#5-scale-analysis-1000-tenants--500-entries)
6. [Billing Model Recommendation](#6-billing-model-recommendation)
7. [Video & Audio β€” Transcript Strategy](#7-video--audio--transcript-strategy)
8. [remork.org Specific Analysis](#8-remorkorgy-specific-analysis)
9. [Competitor Multimodal KB Support](#9-competitor-multimodal-kb-support)
10. [Qdrant Timestamp Filtering β€” False Claim Correction](#10-qdrant-timestamp-filtering--false-claim-correction)
11. [Credit System Note](#11-credit-system-note)
---
## 1. Current Embedding Setup
- **Model:** `BAAI/bge-small-en-v1.5`
- **Inference:** Local (sentence-transformers), runs in Docker container
- **Dimensions:** 384
- **Cost:** $0 per embedding (local inference, no API call)
- **Context window:** 512 tokens
- **MTEB score:** ~62
**Why this was chosen:** Zero cost, good enough for V1, no external API dependency.
**Why to migrate:** 512-token context limit truncates long KB entries. Quality gap is real (+6 MTEB points). Multimodal capability unavailable.
---
## 2. Gemini Embedding 2 Overview
| Attribute | Value |
|---|---|
| **Model ID** | `gemini-embedding-2` (GA as of March 10, 2026) |
| **Output Dimensions** | 3,072 (Matryoshka flexible: 128–3,072; recommended: 768, 1,536, or 3,072) |
| **Context Window** | 8,192 tokens (16Γ— larger than current bge-small) |
| **Modalities** | Text + Images (6Γ—) + Video (120s) + Audio (80s) + PDF (6 pages) β€” **all in same vector space** |
| **MTEB Score** | 68.32 β€” top of leaderboard by 5+ points (2026) |
| **Languages** | 100+ languages |
| **Deployment** | Server-side only (never expose API key in browser) |
| **Batch discount** | 50% off for async batch jobs |
**Pricing:**
| Modality | Standard | Batch (50% off) |
|---|---|---|
| Text | $0.20 / 1M tokens | $0.10 / 1M tokens |
| Image | $0.45 / 1M tokens | $0.225 / 1M tokens |
| Video | $12.00 / 1M tokens | $6.00 / 1M tokens |
| Audio | $6.50 / 1M tokens | $3.25 / 1M tokens |
---
## 3. OpenAI Embedding Lineup (2026)
**All OpenAI embedding models are text-only as of 2026.** No multimodal support.
| Model | Dimensions | MTEB | Cost | Notes |
|---|---|---|---|---|
| `text-embedding-ada-002` | 1,536 | ~61 | $0.10/1M | Legacy, not deprecated |
| `text-embedding-3-small` | 1,536 | 62.3 | $0.02/1M | Cheapest OpenAI option |
| `text-embedding-3-large` | 3,072 | 64.6 | $0.13/1M | Best OpenAI text quality |
| `gemini-embedding-2` | 3,072 | **68.32** | $0.20/1M | Only model with native multimodal |
**Conclusion:** OpenAI has no path to multimodal embeddings. Gemini Embedding 2 is the only commercial model that handles video/audio natively (2026).
---
## 4. Cost Per Entry by Modality
**Token counting methodology:**
| Modality | Token counting | Assumed avg per entry |
|---|---|---|
| Text (typical SMB KB article) | 1 token β‰ˆ 4 chars | 750 tokens |
| Text (remork.org β€” title + tags) | 1 token β‰ˆ 4 chars | 65 tokens |
| Image (medium-res, 1 per entry) | ~768 tokens/image | 768 tokens |
| Video (10-min educational) | 1 frame/sec Γ— 258 tokens/frame | 154,800 tokens |
| Audio (10-min recording) | ~32 tokens/sec | 19,200 tokens |
| PDF (5 pages, image-based) | ~768 tokens/page | 3,840 tokens |
**Cost per single entry:**
| Entry type | Tokens | Price/1M tokens | Cost per entry |
|---|---|---|---|
| Text (typical SMB) | 750 | $0.20 | **$0.00015** |
| Text (remork.org style) | 65 | $0.20 | **$0.000013** |
| Image only (1 image) | 768 | $0.45 | **$0.00035** |
| Text + 1 image | 1,518 | blended | **$0.00050** |
| Audio (10 min) | 19,200 | $6.50 | **$0.125** |
| Video (10 min) | 154,800 | $12.00 | **$1.86** |
| PDF (5 pages) | 3,840 | $0.20 | **$0.00077** |
| Transcript from video (text only) | ~750 | $0.20 | **$0.00015** |
**The critical cliff:** Text, images, and PDFs are all under $0.001 per entry. Video jumps to $1.86 per 10-minute entry β€” **12,400Γ— more expensive than a text entry.**
---
## 5. Scale Analysis: 1,000 Tenants Γ— 500 Entries
| Scenario | Total entries | Total one-time cost | Cost per tenant |
|---|---|---|---|
| All text (typical SMB) | 500,000 | **$75** | $0.15 |
| remork.org style (title + tags) | 500,000 | **$6.50** | $0.0065 |
| Mixed text + images (e-commerce) | 500,000 | **~$160** | $0.16 |
| All 10-min videos (raw embedding) | 500,000 | **$930,000** | $930 |
| All 10-min videos (transcript only) | 500,000 | **$75** | $0.15 |
At batch pricing (50% off), halve all figures.
**Key insight:** Switching from raw video embedding to transcript-based embedding brings the cost from $930K down to $75 β€” a **12,400Γ— cost reduction** with better Q&A retrieval quality.
---
## 6. Billing Model Recommendation
**⚠️ NOTE (2026-05-16): The `credits` abstraction is being removed entirely. All billing will use USD directly β€” tenants top up in USD, deductions are in USD, no conversion factor. See Section 11 and backend `crucial_notes.md` for the refactor plan. The table below uses USD throughout.**
### Rule: text and PDF entries are free. Media entries (image, audio, video) cost USD.
The billing system will deduct USD directly from `tenants.balance_usd`. Add a new event type: `kb_embedding`.
| Entry type | Platform cost | Charge tenant | Rationale |
|---|---|---|---|
| Text | $0.00015 | **Free** | Too small to meter; absorb as platform overhead |
| PDF | $0.00077 | **Free** | Same β€” absorb |
| Image (per image in entry) | $0.00035 | **~$0.001** | Small fixed charge per image with markup |
| Audio (per minute) | ~$0.012/min | **$0.02/min** | Per-minute, transparent β€” or free if auto-transcribed |
| Video β€” raw embedding (per min) | ~$0.186/min | **$0.20/min** | Strongly discourage; show transcript path instead |
| Video β€” transcript only (Whisper) | $0 | **Free** | Strongly recommended default path |
| Audio β€” transcript only (Whisper) | $0 | **Free** | Strongly recommended default path |
### Per-minute billing rationale
Tenants understand "longer video = more $" intuitively β€” mirrors how phone calls are billed. Before submission, show an estimate: *"This video is 12 minutes. Direct embedding cost: ~$2.40. Transcript embedding: Free."*
### Billing implementation (post-credit-removal)
Add to `billing_events.event_type` enum: `kb_embedding_image`, `kb_embedding_audio`, `kb_embedding_video`
Add a `kb_embedding_rates` config table: USD per image / per audio-min / per video-min (mirrors the existing `llm_models` rate table pattern)
---
## 7. Video & Audio β€” Transcript Strategy
### The core problem
Raw video/audio embedding is expensive and overkill for Q&A use cases. A transcript answers "what does this video cover?" just as well as video frame analysis β€” and does it faster, cheaper, and more accurately.
### Recommended UX flow (applies to both video and audio)
```
Tenant uploads video/audio OR pastes YouTube URL
β”‚
β–Ό
Backend queues transcription job (Celery async)
β”‚
β”œβ”€ YouTube URL β†’ YouTube Transcript API (free, instant)
β”‚
└─ Uploaded file β†’ Whisper (local, free) or Whisper API ($0.006/min)
β”‚
β–Ό
Frontend shows: "Transcript ready β€” review before embedding"
β”‚
Editable transcript preview box (tenant can fix errors, add context)
β”‚
β”œβ”€ [Embed Transcript β€” Free] β†’ stored as text KB entry, video URL in source_url
β”‚
└─ [Embed Media Directly β€” X credits] β†’ uses Gemini video/audio embedding
```
### Transcription options by cost
| Method | Cost | Speed | Quality | Use case |
|---|---|---|---|---|
| YouTube Transcript API | Free | Instant | High (native captions) | YouTube URLs |
| Whisper local (Docker) | Free | ~1Γ— realtime | High | Uploaded files, on-prem |
| OpenAI Whisper API | $0.006/min | Fast | High | Cloud alternative to local |
| Deepgram / AssemblyAI | ~$0.01–0.02/min | Fast | Very high | If better accuracy needed |
**Recommendation:** Run Whisper locally in Docker for uploaded files (zero cost, no API dependency). YouTube Transcript API for YouTube URLs.
### What gets stored in the KB
```
KB entry (transcript-based video):
title: "Introduction to Khmer Literacy β€” Grade 3"
content: "[transcript text here β€” editable by tenant]"
source_url: "https://youtube.com/watch?v=abc123" ← video pointer
origin: "video_transcript"
source: "youtube" | "upload"
```
The chatbot can then:
1. Retrieve the transcript for Q&A ("What topics are covered in the Grade 3 literacy video?")
2. Include the video link in its response ("Watch the video here: [source_url]")
### When raw video/audio embedding is still useful
Only one scenario justifies it: **visual Q&A** β€” "What is shown on the whiteboard at 3:20?" For educational platforms, this is rare. For product demos or training videos where visual elements matter, it's valid β€” but should be opt-in and credit-gated.
---
## 8. remork.org Specific Analysis
**About remork.org:** Non-profit educational platform serving thousands of Cambodian teachers. KB entries are video-based posts with title + tags + rare short descriptions.
### Current state (videos stored on server)
- KB entries: title + tags + optional short description
- Average tokens per entry: ~65
- Cost per entry at Gemini Embedding 2: **$0.000013**
- Cost for 2,000 posts: **$0.026 total one-time ingestion**
- **Verdict: essentially free. Absorb it without a second thought.**
### Future state (if videos move to YouTube)
Do NOT use raw video embedding. Use the transcript strategy:
1. Paste YouTube video URL into KB entry form
2. Backend calls YouTube Transcript API (free, instant)
3. Admin reviews/edits the Khmer transcript
4. Click "Embed Transcript"
5. Cost: **$0.000013 per entry** (same as text) + free YouTube API call
**Note on Khmer language:** YouTube auto-captions support Khmer. Quality varies β€” human review before embedding is recommended. The editable transcript preview box solves this.
### remork.org chatbot use case
A Cambodian teacher asks: "What videos are available about Grade 5 mathematics?"
The chatbot retrieves transcript-based entries, returns matched video titles + YouTube links.
Teacher clicks the link and watches the video.
This is the ideal flow: **chatbot as a search/discovery layer over video content**, not as a video analyzer.
---
## 9. Competitor Multimodal KB Support
| Competitor | Media KB | Notes |
|---|---|---|
| Intercom Fin | Images (chat only) | Analyzes user-uploaded screenshots in chat. Not KB upload. Enterprise/Pro. |
| Botpress | Images + video | Most advanced multimodal KB of all competitors. Mid-market. |
| Zendesk AI | Partial (roadmap) | Text-only currently; video KB planned. Enterprise. |
| Tidio Lyro | No | Text-only; uses Claude backend. SMB/mid-market. |
| Chatbase | Unclear | GPT-4o models available but no confirmed media KB upload. All tiers. |
| CustomGPT | No | Enterprise, text-only. |
| Voiceflow | No | Generic builder, text-only. |
**Market position:** If smart_chatbot ships image + transcript-based video KB, it matches Botpress and surpasses every other SMB-tier competitor.
**Marketing one-liner:** *"Unlike Tidio or CustomGPT, your chatbot understands images and videos β€” not just PDFs and text."*
---
## 10. Qdrant Timestamp Filtering β€” False Claim Correction
**Claim heard:** "Qdrant does not support timestamp/timeframe filtering."
**Status: FALSE.** Verified against Qdrant documentation (2026).
Qdrant fully supports datetime filtering via `DatetimeRange` payload filter conditions (available since v1.8.0+).
```python
from qdrant_client import models
filter = models.Filter(
must=[
models.FieldCondition(
key="created_at",
range=models.DatetimeRange(
gte="2026-01-01T00:00:00Z",
lte="2026-05-16T23:59:59Z"
)
)
]
)
```
- RFC 3339 formatted dates
- `gt`, `gte`, `lt`, `lte` operators
- UTC conversion handled automatically
- Create a payload index on the date field before high-volume ingestion
**Known limitations (not blockers):**
- Unindexed date filters degrade performance on large collections
- HNSW graph can degrade with very high-cardinality filters on 260M+ point datasets (Qdrant 1.16+ ACORN algorithm mitigates this)
**Conclusion:** No need to migrate away from Qdrant on this basis.
---
## 11. Credit System β€” Removal Decision (2026-05-16)
**Decision: Remove the `credits` abstraction entirely. Use USD directly throughout the billing system.**
### Why credits were wrong
The system stored `tenants.credits` as a Decimal, where `1 credit = $0.001 USD` (confirmed by migration math: LLM rates Γ— 3Γ— markup Γ· $0.001/credit = seeded `credits_per_1k` values). The billing router returned `balance_usd = tenant.credits` β€” a field named "USD" but containing raw credits. This created a silent 1000Γ— mismatch: a tenant with `credits = 100` was shown "100" under a USD label but actually had $0.10 in purchasing power.
Verified from the database (2026-05-16):
```sql
SELECT name, credits FROM tenants;
-- demo | 97.820590
-- Monireach | 100.000000
```
The demo tenant started with 100 credits ($0.10 real USD) and spent ~2 credits (~$0.002) across 9 chat interactions β€” each costing 0.135–0.518 credits.
### What the refactor must do
See backend `crucial_notes.md` for the full logged task. Summary:
| Before | After |
|---|---|
| `tenants.credits` | `tenants.balance_usd` (true USD Decimal) |
| `credit_events` table | `billing_events` (or rename columns) |
| `credits_per_1k_input/output` in `llm_models` | `usd_per_1k_input/output` |
| Deduction: `amount` in credits | Deduction: `amount` in USD |
| `billing_mode = 'credits'` | `billing_mode = 'pay_as_you_go'` (or keep name) |
| 3Γ— markup computed in credits | 3Γ— markup computed in USD directly |
### Seeded rates β€” what they become in USD
| Model | Old (credits/1K) | New (USD/1K) |
|---|---|---|
| gemini-2.5-flash input | 0.900000 credits | $0.000900 |
| gemini-2.5-flash output | 7.500000 credits | $0.007500 |
| qwen3-32b input | 0.870000 credits | $0.000870 |
| qwen3-32b output | 1.770000 credits | $0.001770 |
The numbers stay identical β€” only the unit label changes from "credits" to "USD". No business logic changes.