memgrep
# memgrep
Semantic search over your Telegram memes. Ask in Russian or English — find the meme you half-remember.
Pipeline: Telegram export → vision captions (bilingual) + OCR → multilingual embeddings (bge-m3, local) → PostgreSQL/pgvector hybrid search (dense + full-text, RRF) → cross-encoder reranking. Exposed as a CLI, a web UI and an MCP server, so your AI assistant can search your memes too.
See [the design spec](docs/superpowers/specs/2026-08-16-memgrep-design.md) for the full architecture.
## Quick start
```bash
cp .env.example .env # fill in OpenRouter API key
make up # PostgreSQL + pgvector on :5433
make test
uv run memgrep ingest # load data/result.json + photos into the db
uv run memgrep caption --limit 400 # describe images via a vision model (pilot)
uv run memgrep embed # local bge-m3 embeddings
uv run memgrep search "this is fine" # CLI search; --open shows files in Preview
uv run memgrep serve # web UI on localhost:8000
uv run memgrep evals # golden-set metrics for the current config
uv run memgrep sync # ingest + caption + embed for new exports
uv run memgrep watch # follow the channel via Bot API
```
The Telegram export goes to `data/` (Telegram Desktop → Export chat history → Photos, JSON format). Memes and exported data stay local — `data/` is gitignored; only code is published.
## Search quality
Measured on a 21-query golden set collected through the UI feedback button ("это он ✓"), pilot corpus of 400 images. Each retrieval layer was added only after it proved itself:
| Layer | hit@5 | hit@10 | MRR |
|---|---|---|---|
| dense vectors only | 0.857 | 0.905 | 0.768 |
| + full-text search, RRF fusion | 0.905 | 0.952 | 0.815 |
| + cross-encoder reranker | 0.905 | **1.000** | **0.839** |
Captioning-model comparison on the same corpus and golden set: qwen3-vl-8b matched or beat qwen3-vl-235b on retrieval metrics at a third of the price, so the full corpus is indexed with the 8b model (~$3.6 for ~11k images). Caveat documented in the evals history: the golden set was collected on top of the 8b index, which biases the comparison toward it.
Search config is env-driven: `MEMGREP_SEARCH_MODE=vector|hybrid`, `MEMGREP_RERANK_ENABLED=true|false`. `memgrep evals` writes a timestamped JSON per configuration into `evals/results/` so runs stay comparable.
## MCP server
Let Claude (or any MCP client) search your memes:
```bash
claude mcp add memgrep -- uv run --directory /absolute/path/to/memgrep python -m memgrep.mcp_server
```
Tools: `search_memes(query, k)` returns matches with file paths and captions; `get_meme(sha256)` returns the image itself.
## Watch service
The `memgrep watch` command follows a Telegram channel in real-time via Bot API long polling. Set up the bot as a channel admin, configure `MEMGREP_TG_BOT_TOKEN` and `MEMGREP_TG_CHANNEL_ID`, and run the watcher service. New photos posted to the channel are automatically indexed: they get captioned, embedded, and searchable within minutes. Reactions on recent posts are also synced back to the database for engagement tracking.
## Stack
Python 3.12+, uv, PostgreSQL 17 + pgvector, sentence-transformers (BAAI/bge-m3 embeddings, BAAI/bge-reranker-v2-m3 reranker, both local), any OpenAI-compatible vision endpoint for captioning (default: OpenRouter, qwen3-vl), FastAPI, typer, MCP Python SDK. 49 tests, no network or model downloads in the test suite.
TDQS
Scored across 2 tools
The two tools have clearly distinct purposes: one performs semantic search over the collection, the other retrieves a specific image by hash. There is no overlap or ambiguity between them.
Both tools follow the same verb_noun pattern in snake_case: search_memes and get_meme. The singular/plural variation is natural for resource action and does not break consistency.
With only two tools, the server feels thin and sits at the low end of the borderline range. However, the narrow purpose of search and retrieval justifies a minimal surface, so it is not unreasonable.
For a read-only meme search and retrieval server, the surface is complete: search returns hashes and get_meme fetches the image. There are no dead ends or missing core operations within this defined scope.