Revive yunjin: replace embedding/DBSCAN aggregation with a cloud-LLM aggregation service #1
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Yunjin was abandoned at v0.4.0 because aggregation quality never reached a useful level:
eps=0.45nearly half of all articles fell out as noise singletons.epsvalue had to work across every topic, and recurring feed series (release announcements, status incidents, podcast episodes) fragmented or merged unpredictably.Cheap cloud LLMs (DeepSeek, Moonshot) can now make the grouping and summarisation judgements directly, without local model tuning or distance-threshold fiddling. A series of probes against real feed data (findings in the follow-up comment) showed this approach is viable at hobby scale and cost.
Goal
Re-attempt the project's core goal — high-quality topic aggregation — using a cloud LLM as the aggregation engine:
Probe findings (Sept 2026)
Real data: 184 articles — first page of all 16 feeds from my FreshRSS OPML (news, renewables, EV, status, release, podcast and one personal Nextcloud activity feed; FreshRSS read-filters deliberately NOT applied, so the noise is real).
Probe 1 — full batch,
deepseek-v4-pro(thinking enabled, high effort)Baseline — existing pipeline on identical data
eps=0.45: 14 clusters + 79 noise singletons (~43% ungrouped).Probe 2 — same payload,
deepseek-v4-flashProbe 3 — incremental assignment (the daily-reader workload)
API facts (verified against api-docs.deepseek.com)
POST https://api.deepseek.com/chat/completionsdeepseek-v4-pro,deepseek-v4-flash(plus an experimental vision variant)Authorization: Bearer $DEEPSEEK_API_KEY"thinking": {"type": "enabled"},"reasoning_effort": "high"Plan of attack
LLMAggregationServiceinsrc/yunjin/services/, alongside the existingAggregationService(which stays untouched until the new path proves itself).{groups: [{title, summary, kind, article_ids}]}→ persist via existingdb/aggregates.py.{assignments, new_groups}with optionalupdated_summary.Key files: new
src/yunjin/services/llm_aggregator.py, read-side additions insrc/yunjin/db/aggregates.py, settings plumbing, tests with recorded API fixtures (no live calls in the test suite).Implementation plan
The current
EmbeddingPipeline(sentence-transformers) +AggregationService(DBSCAN) is replaced with a singleLlmAggregationService. One OpenAI-compatible API call per fetch-run. Articles are grouped incrementally: new articles are matched against existing aggregates or create new ones. Aggregates with no new articles for 7 days are expired.Key design decisions
{ topics: [{ name, summary, article_ids, aggregate_id }] }.--aggregate-ttl-days.--refreshreplaces--recalculate(same behaviour, clearer name).embeddingstable — no longer needed. New migration drops it and adds an index onaggregates.updated_at.Files
services/llm_aggregator.pyservices/embedder.py,services/aggregator.py,db/embeddings.py__main__.py(CLI flags),pyproject.toml(remove deps),alembic/(migration),tests/test_aggregator.py(full rewrite)Commits (planned)
:wrench: Drop embeddings table via migration, add updated_at index:sparkles: Add LlmAggregationService with httpx2 client:wrench: Update CLI: --refresh, --aggregate-ttl-days, remove DBSCAN flags:fire: Remove embedder.py and aggregator.py:fire: Remove db/embeddings.py:fire: Remove sentence-transformers, scikit-learn, numpy from pyproject.toml:recycle: Rewrite tests/test_aggregator.py for LLM path:white_check_mark: Add recorded httpx fixtures for LLM responsesBranch
feat/issue-1-llm-aggregationFull design spec:
.superpowers/specs/2026-09-11-llm-aggregation-design.md