Skip to content

Repository files navigation

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 for the full architecture.

Quick start

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:

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.

About

Semantic search over your Telegram memes: vision captions, hybrid pgvector retrieval, evals, MCP

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages