Deep Research utan djupa fickor.
När """, "" Deep Research "" - - -" "" landade i de bästa AI-verktygen, , "" Jag var nyfiken på vad det här är, : "", och när jag förstod hur den såg ut- -såg jag att jag redan hade byggt den mesta av den.
I detta angreppssätt eliminerar styrkan i minskningstegen behovet av en stor, dyr modell för att få tillfredsställande resultat.
Framståendet, Jag hade redan många nödvändiga byggstenar , inklusive multi- källupptackning M SK3 entite extraktion , kunskapsgrafer MSC5 och inbädding MST6baserad ranking MSV7 som delades med lucidRAG och DocSummarizer-serien MSM8
Den sakna komponenten var inte brist på förmågan, ,, utan snarare förståelse för hur man effektivt kan tillämpa de här redan existerande elementen.
I kommersiella verktyg betyder ofta
eller
Väldigt
eller
Begreppet som planerar en forskningsmetod
En stor del av värdet ligger i att förbättra inmatningen.:, att få fram friskare, bättre, högre signalkällor än något annat model.’, att skära ner utbildningsdata, -, avlägsna data och ,, och minska risken för att modellen ska göra saker till slut.
exempel: OpenAI Deep Research, Gemini Deep Research, och Perplexity Deep Research ( kallas också “Forschungsmodit ” i deras gränssnitt
DoomSummarizer följer samma arbetsruttlet, men den är konstruerad så att större delen av “read + reducer ” arbeten är deterministiska och lokala, och LLM används främst för syntetisering.
Om reduktionsstegen är stark, så behöver du inte ett stort, dyrt model för att få ett bra resultat.
Mellan lucidRAG och DocSummarizer-serien, Jag hade redan större delen av byggstenarna : multi, -, källuppfinning, ,, entite extraktion, M SK4, kunskapsgrafer, MSC5, inbäddar, MST6, grading, Mst7
Den sakna biten var orkestering. Hur tar man alla dessa signaler och sammanfogar dem i något som är coherent med en liten modell
Det där's DoomSummarizer . Namerat efter doom scrolling , eftersom det scrollar så du inte behöver.
Men den här artikeln handlar inte om verktyget, det handlar om idéerna som gör det att fungera.
Det här är Del 5 av DocSummarizer-serien.
Om du vill ha grunden först:
Det här är också en Time Boxed Tool.
Standard RAG gör redan semantisk extrahering.
smärtan är det sista steget.:, du hamnar fortfarande i ett högljud.,, ett överlapparde hög med bitar som hamnar i en LLM och ber den att förenkla kopior.M SK2, väger källor, ,, och håller ett jämnt tråd.
Tänk om du gjorde mer arbete före LLM ser allt
Att "'" är mönstret. minska innan du genererar. Ta rådokument och reducera dem deterministiskt till sina nödvändiga signaler : inbäddar, , klassificeringar,, dominerande segment, , enhetens profiler, M SK5 När LLM ser något,
flowchart TD
subgraph REDUCE["REDUCE: Distil to Signals"]
Q[Query] --> Decompose[Composite Decomposition]
Decompose --> S1[Subquery 1]
Decompose --> S2[Subquery 2]
S1 & S2 --> Fetch[Multi-Source Fetch]
Fetch --> Embed[ONNX Embed Each Item]
Embed --> Rank[6-Signal RRF Fusion]
Rank --> TextRank[TextRank Extraction]
TextRank --> NER[Entity Extraction]
NER --> Profile[Entity Profile Vectors]
Profile --> Segments[Salient Segment Extraction]
end
subgraph SYNTH["SYNTHESIZE"]
Segments --> Standard["Standard Path - 1 LLM Call"]
Segments --> LongForm["Long-Form Path - N+3 LLM Calls"]
end
style REDUCE fill:#1a1a2e,color:#e0e0e0
style SYNTH fill:#162447,color:#e0e0e0
Reduktionsfasen är deterministiska: inbäddar , klassificering, , entite extraktion, , TextRank, M SK4 segment scoring,. Inga LLM sändningar
Syntesen är den enda LLM-stegen. För en standardfråga är de reducerade signalerna tillräckligt rena för att ett samtal gör jobbet. För lång tid-formdokumenten , en befälhavare planerar strukturen och huvudmodellen genererar sektioner i parallella sträckorN+3 ringer på N sektioner).
För mer detaljer på det här mönstret, Reduzerad RAG.
Innan något minskar, måste sökningen bli sökaktioner.
Ett litet lokalt sensormodell (0.6B i mitt fall, JSON-läger , temperatur | 0.1) tolkar rawqueryn till en strukturerad syfte |, | och sedan en YAML driven källreduktor som omvandlar det syftet till konkreta fetcher
flowchart TD
Q["User Query"] --> Sentinel["Sentinel: JSON Interpretation - categories, intent, entities, - temporal hints, tone"]
Sentinel --> Router["Source Router - YAML category → source mapping"]
Router --> S1["gnews:AI safety"]
Router --> S2["search:AI regulation"]
Router --> S3["bbc:technology"]
Router --> S4["reddit • hn"]
S1 & S2 & S3 & S4 --> Fetch["Parallel Fetch - circuit breaking + rate limiting"]
Fetch --> Merge["Merge + Deduplicate - → reduce phase"]
style Sentinel fill:#1e3a5f,color:#e0e0e0
style Router fill:#0d3b2e,color:#e0e0e0
Befälhavaren extraherar strukturerade fält
Tillvägagångspunkt händer i sex faser:
"from HackerNews" → hn går rakt inresearch eller deep_dive intents lägga till archivegnews:OpenAI)Resultatet: identifisatorer av källor: :, ~10 hämtade parallellt med varje: -, källorskrets brödning.
Viber ändrar sökordna innan de träffar något API. Till exempel, --vibe doom prefixfrågar med "synnerliga problem-risker-problem i, omforma vad som kommer tillbaka innan någon rangorde händer
Om du vill ha bakgrunden på resiliensbitarna, se Att använda Polly för pensioner (kretsbrytare Backpressure i kösystem (hastighetsbegränsning och baktryck
De flesta söksystem behandlar " Vad är det som är nytt i AI-säkerheten och vad är de senaste lagarna?
Befälhavaren bryter ihop komplexa frågor:
{
"is_composite": true,
"subqueries": [
"What's new in AI safety?",
"What are the latest AI regulations?"
]
}
Det intressanta valet är vad som händer härnäst. Varje undersökande får sin egen ONNX inbäddning ( se ONNX: Att köra ML-modeller lokalt). När jag sätter poäng på artiklar max kosinusliknande across all subquery embeddings, inte genomsnittet
Varför max? Ett artikel som perfekt besvarar M SK1 AI-säkerheten " borde inte bestraffas för att inte säga någonting om ♫" reglerna ". En jämförelse skulle gömma det under medelmåttiga partiella matchar M SK6 Max likhet betyder: om en artikel snäller alla en del av din fråga , det stiger. Det andra alternativet, , medelvärde, belönar blanda artiklar som vag rör allting
Jag har sex klassificerade signaler, de har helt olika skalor.
flowchart LR
subgraph Signals
BM25[BM25 - Keyword Match]
Fresh[Freshness - 48h Half-Life]
Auth[Authority - HN/Reddit Score]
QSim[Query Similarity - Cosine]
Vibe[Vibe Alignment - Cosine]
Qual[Quality - Clickbait Detector]
end
subgraph RRF[RRF Fusion]
direction TB
R[Rank Each Signal - Independently]
F["score = Σ weight × 1/(60 + rank)"]
end
BM25 & Fresh & Auth & QSim & Vibe & Qual --> R --> F
style RRF fill:#1e3a5f,color:#e0e0e0
HN-punkter går till tusentals . Kosineliknande är -1 till | 1. Frihet bryter sig exponentiellt M SK3 Du kan | ' | inte helt säkert lägga ihop dessa tillsammans | МSK5 | Skalerna är okompatibla
Det nyckelsignalet här är BM25 över flera fält (BMM SK2F). Om du vill ha bakgrunden på BM25, se DocSummarizer Part 3.
Reciprokal rangfuusion (Cormack et al ranker: score = Σ weight × 1/(k + rank) (med k=60).
Om du vill ha hela bakgrunden på RRF, Del 3 täcker den . Här är kurvan vikter anpassar sig till söktyp. Befälhavaren upptäcker avsikten , och scoraren omformar sig själv
Det här började som ett skämt.
Ett "liv, " är inte första rangeringssignal i klass - inbäddad i den att hämta data
flowchart LR
V["--vibe doom"] --> Expand["Query Expansion - 'concerning risks issues in...'"]
V --> Embed["Vibe Embedding - 'vulnerability breach layoffs - recession crisis warning...'"]
Embed --> Score["Cosine Similarity - per article"]
Score --> RRF["RRF Signal - weight: 0.4"]
style V fill:#8b0000,color:#e0e0e0
Tre saker händer när du sätter en vibe:
Custom viber fungerar identiskt. --vibe "contemplative philosophical"- din text blir både sökprefixen och rangeringsmålet . Systemet skiljer inte mellan förutdefinierade och custom-definerade ord.
Här är ett misstag jag gjorde först.
Två artiklar nämner båda "KalifornienM SK1
Beräkning av de delaktiga enheten : 1. Därför besläktade M SK2 Uppenbarligen fel
Fixen var att tänka på enheter som vector, inte strängar.
Det här är begränsad fuzziness-mönster: NER-modellen föreslår enheter , men deterministisk viktering bestämmer vad som är viktigt
Varje dokument får en Profil med vikt (a 384-dim-vektorkodering som varelser dyker upp och hur separerade de är
weight = TF × IDF × confidence × type_weight
profile = L2_normalize(Σ entity_embedding × weight)
flowchart TD
subgraph Doc1["Article: OpenAI Safety Team"]
E1["OpenAI (ORG) - IDF: high - weight: 2.1"]
E2["California (LOC) - IDF: low - weight: 0.3"]
E3["Safety Team (MISC) - IDF: high - weight: 1.8"]
end
subgraph Doc2["Article: Almond Farming"]
E4["California (LOC) - IDF: low - weight: 0.3"]
E5["Almond Farmers (MISC) - IDF: high - weight: 1.9"]
E6["Drought (MISC) - IDF: medium - weight: 1.2"]
end
Doc1 --> P1["Profile Vector - Dominated by OpenAI + Safety"]
Doc2 --> P2["Profile Vector - Dominated by Farming + Drought"]
P1 -. "low similarity" .- P2
style Doc1 fill:#1a1a2e,color:#e0e0e0
style Doc2 fill:#2d1a2e,color:#e0e0e0
IDF är nyckeln . "Kalifornien" dyker upp i tonvisar av föremål | - | låg IDF | МSK4 | svag signal |. | " | OpenAI |" | är distinktiv ♫ - | hög IDF
Den smarta delen: Sättande TF. Istället för råbeteckningstalet, använder jag 1 + log(mentions). En enhet som nämns ♫ 50 ♫ gånger i en lång artikel får ♫' ♫ inte ♫ МSK3 ♫ samma vikt som en som näms en gång ♫
Enhetens profiler är inte bara för att förstå dokument, de är dragningsdimension. Varje documen, 🥱, t, ', får en index i en HNSW-graf.
Om du vill ha mer detaljer om HNSW och DuckDB VSS, se GraphRAG-delen 2: Minimalt hållbara GrafRAG.
flowchart TD
subgraph Retrieval["Three-Layer Retrieval (UNION)"]
L1["Lucene FTS - keyword matches"]
L2["Embedding HNSW - semantic similarity"]
L3["Entity Profile HNSW - entity fingerprint match"]
L1 & L2 & L3 --> Union["Union of all candidates"]
end
subgraph Enrich["Post-RRF Enrichment"]
Top["Top 5 Ranked Items"] --> Agg["Aggregate Profile - mean of entity profiles"]
Agg --> Graph["HNSW Search - min similarity: 0.30"]
Graph --> Related["+3 Related Articles - scored below existing items"]
end
Union --> RRF["RRF Fusion"] --> Enrich
style Retrieval fill:#1a1a2e,color:#e0e0e0
style Enrich fill:#0d3b2e,color:#e0e0e0
Detta fungerar i två stadier:
Under hämtandet: när befälhavaren extraherar | 2+| varelser från queryn |, | en query-varelseprofil obliceras med samma TF |× | IDF-formulär | . | Det här söker HNSW-indexet efter artiklar med liknande varelsers fingeravtryck | МSK5 | med minimalt likheter till |
Efter rangering: de högst rankade itemsna ≥5 har sina entitetsprofiler i genomsnitt i en aggregerad vektor . Den här aggregerade sökningen letar efter besläktade artiklar som nyckeln och inbäddade lagerna helt misslyckades med "med hjälp av enheterM SK1. Detta fångar de primära källorna som nyhetsartiklarna referenserar men inte
Tre spårningslag smälter samman genom union: Lucene-キーワード matchar | ∪ | Embedding matchar ∪ | Entity profile | HNSW matchar. | Unionen är avsiktligt bredare än intersectionen |. | Den fångar saker som varenda enskild signal skulle dyka upp på tavlan | МSK5 | RRF-fusionen sorterar sedan ut vad
Fallback-stigen ( för corpora utan än enhetens profiler | ) | använder SQL-enhetens coHAVING shared_count >= 2). Det fungerar , men det's OM SK3NMSC4 istället för OSSK5log NMST6 --backfill-entity-profiles bevel migrerar existerande korporationer till HNSW-stigen.
Detta är den normala vägen. scroll "AI safety and regulation" träffar detta. När reduktionsfasen bryter ut dina källor till klassificerade, duplicerade , entityder
flowchart TD
subgraph REDUCE["Reduce Phase (ALL DETERMINISTIC)"]
Items[Fetched Items] --> Embed[ONNX Embed All Items]
Embed --> RRF[6-Signal RRF Fusion]
RRF --> TR[TextRank Compression]
TR --> Seg[Segment Extraction + Salience Scoring]
Seg --> Dedup[Deduplication + Relevance Floor]
end
subgraph SYNTH["Synthesis (1 LLM CALL)"]
Dedup --> Rerank["Semantic Re-Rank - by query similarity"]
Rerank --> Budget["Smart Evidence Budgeting - redistribute unused chars"]
Budget --> Gen["Single LLM Call - with curated evidence"]
Gen --> Output[Final Summary]
end
style REDUCE fill:#0d3b2e,color:#e0e0e0
style SYNTH fill:#1e3a5f,color:#e0e0e0
Rederingsfasen gör det hårda arbetet. När LLM ser något, får den
[E1] Title | topic | relevance med innehåll uttalat till budgetEtt LLM ringer ".", det är "'" som är belöningen av reduced RAG: urval , klassificering, , duplicering,, och budgetering händer allt innan LLM ser något.
Längsform (--template blog-article) är ett annat djur . Du' genererar en multi- -sektionsdokument från dussintals olika källor, och du behöver en jämlikhet mellan sektioner utan dyra LLM kompressionsnummer.
Svaret är begränsad fuzzy kontext dragning. Det här är deterministiska mekanismer som håller kvar cross-
flowchart TD
subgraph Phase1["Phase 1: Evidence Preparation (DETERMINISTIC)"]
Articles[Top 20 Articles] --> Segments[Chunk into Segments]
Segments --> Salience[Score Salience per Segment]
Salience --> EmbedSeg[Embed Each Segment]
end
subgraph Phase2["Phase 2: Document Planning (1 SENTINEL CALL)"]
EmbedSeg --> Summary[Build Evidence Summary]
Summary --> Sentinel["Sentinel: Generate Outline - with theme keywords per section"]
Sentinel --> EmbedThemes[Embed Section Themes]
end
subgraph Phase3["Phase 3: Evidence Assignment (DETERMINISTIC)"]
EmbedThemes --> Assign["Score: 60% theme similarity - + 25% salience + 15% relevance"]
Assign --> Dedup[Cross-Section Deduplication]
Dedup --> Gate["Quality Gates - • Min salience 0.35 - • Min theme sim 0.45 - • Max 2 per source per section"]
end
subgraph Phase4["Phase 4: Section Generation (N+2 LLM CALLS)"]
Gate --> Intro["Intro (sequential)"]
Intro --> Body["N Body Sections (parallel) - max 3 concurrent"]
Body --> Conclusion["Conclusion (sequential)"]
end
subgraph Phase5["Phase 5: Validation (DETERMINISTIC)"]
Conclusion --> Validate[Citation Validation - URL + Entity Grounding]
end
subgraph Phase6["Phase 6: Assembly (DETERMINISTIC)"]
Validate --> Assemble[Final Document Assembly]
end
style Phase1 fill:#0d3b2e,color:#e0e0e0
style Phase2 fill:#1e3a5f,color:#e0e0e0
style Phase3 fill:#0d3b2e,color:#e0e0e0
style Phase4 fill:#1e3a5f,color:#e0e0e0
style Phase5 fill:#0d3b2e,color:#e0e0e0
style Phase6 fill:#0d3b2e,color:#e0e0e0
Tela LLM sändningar: 1 ( 1 (intro N (kroppssegmenter 1 ( slutsats N+3. Fyra av de sex stadierna är deterministiska
Problemet med parallella sektionsgenerering är koherens.
Istället får varje sektion begränsad kontext att "'" byggdes deterministiskt.
flowchart LR
subgraph Context["Per-Section Context (ALL DETERMINISTIC)"]
RS["Running Summary - 1400 char budget - recent 2 sections: full - older: heading only"]
NP["Negative Prompts - 'Do NOT discuss: X, Y, Z' - from covered concepts"]
EC["Entity Continuity - re-introduce entities - last seen 2+ sections ago"]
Props["Propositions - ~15 atomic facts - per section"]
Evidence["Curated Evidence - max 12 segments - quality-gated"]
end
Context --> LLM["LLM generates - with full awareness - of document state"]
style Context fill:#1a1a2e,color:#e0e0e0
I parallell Modus, genererar insidan först ( ställer basen | ), | kropp sektioner körs samtidigt SemaphoreSlim att kapa sammanträffanden, var och en uteslutande introämnen men inte var och ett andra ), och slutsatsen sträcker sig ända M SK2 utschließande allting ).
Sekvationsmodiet ger en hårdare jämlikhet ( varje sektion utelämnar alla tidigare koncept kumulativt ), men parallellt är ~3x snabbare för stora dokument
Varje sektion behöver bevis, men inte alla bevis . Varje segment får en sammanlagd poäng: 60% tema likhet (kosine avstånd till sektionen'medvetande om temat 25% relevantitet ( hur informativ segmentet är 15% relevans ( hur relevant artikeln i källan är i allmänhet ). Det här betyget är helt deterministiskt
Sedan dörrarna
LLM-targets ordtaljusterar baserat på bevisets kvalitet. Stark bevis ( upplägsenhet ≥ | | 0.6, ♫ 2+ källor , \ 4+ segmenter M SK7 | МSK8 hela ordtal MSC9 svaga bevis ♫ ♫ upplägsamhet ♫
Innan beviset når LLM, så atomiseras det i propositioner ( inspirerade av Dense-X Retrieval papper). Varje segment bryts ner i sex typer av atom fakta påståenden (allgemena faktaberättelser citat (direkt från källan statistik (tal och mätetal definitioner ("X är YM SK1 processer (steg namngeda enhetens fakta ( fokuserar på en specifik enhetM SK1
Varje sektion får ~15 propositioner , duplicerad över sektioner med hjälp av semantisk likhet ( inte strängmatchning). LLM får strukturerade bullet-punkter grouperade efter källanM SK4 inte råförteckningar
När LLM skriver en sektion
En liten modell kan göra utmärkt arbete med denna typ av pre-processing.
När artiklarna är för långa för beviset, måste jag komprimera dem. Men jag vill inte spendera en LLM-sändning på sammanfattningar.
TextRank (Mihalcea ♫ & ♫ Tarau ♫ МSK2 ♫ 2004) ♫ gör det deterministiskt ♫
flowchart LR
Text[Article Text] --> Split[Split into Sentences]
Split --> EmbedS[Embed Each Sentence]
EmbedS --> Graph["Build Similarity Graph - (cosine > 0.15 = edge)"]
Graph --> PR["PageRank - (20 iterations, d=0.85)"]
PR --> Select["Select Top-K - in Original Order"]
style Graph fill:#1e3a5f,color:#e0e0e0
Varje mening får ett inbäddat . Sammlingar i pelare riktning ovan 0.15 blir grafgränser . PageRank hittar mest centrala fraser , de som är mest kopplade till allt annat. De är de fraser som bäst representerar dokumentet
Det nyckeldetailet: som ausgewählta meningar kommer tillbaka i originaldokumentsordning. Det här bevarar berättelsens flöde . Du får en koherent sammanfattning
Inget LLM sändning. Den kör i millisekundar . Kosine likhet är SIMD- accelererad via TensorPrimitives (System.Numerics.Tensors) (AVX2, AVX-512, eller ARM NEON
Artiklar antyder andra artiklar . Dessa referenser innehåller ofta de bästa bevisen - den primära källan som en nyhetsartikel sammanfattar
DoomSummarizer följer länkar , men selektivt. Varje kandidat länkar får poäng
link_score = 0.7 × query_relevance(anchor_context) + 0.3 × segment_salience
Där segments saliens kombinerar sig
länkarna under relevansnivån (0.15) är överskridda . Systemet följer journalistiska inverterade pyramiderheuristiker: viktiga länkar tenderar att dyka upp tidigt i vält
Resultaten kastars med innehållshasher och ETags. Andra prover är snabba. Se Response Caching , ETags, och villkorliga begäran.
Allt som har beskrivits så här långt antar att webben tar fram data. Men du kan hoppa över webben helt
crawl ingests en webbsida i en lokal kunskapsbas.
doomsummarizer crawl https://docs.example.com --name example-docs --depth 3 --max-pages 200
Allt blir uthärdigt: full content, ONNX inbäddarM SK2 entite-profiler , SQLite FTSMska4 nyckelindex Mske5fullaM Ska6 textsökningenMka7 känslor och temapoäng | Mska8 | beräknat genom inbäddade ankar |Mska9 | inga LLM | mska10 | ökade repetition | Мska11 | kråkor skicka If-None-Match / If-Modified-Since opskrifte. Orändrade sidor återvänder HTTP 304 och hoppar över omprocessing. För server utan ETag stödM SK3 SHAMska4 innehållshackar fånga kopiorMske5
Efter att ha bläddrat
doomsummarizer scroll "how does authentication work?" --name example-docs
Den --name vlagvägar till den lokala KB istället för webbkällorna. Samma tre - lagerutfrågning bränder, : FTS, 5 fulla, M SK4 textinlärning, Mska5 filter, M Ska6, inbäddad HNSW-sökning, Msaka7, enhetens profil, HNSV-sökningen, M ska8, de är ett samhälle, Maskan9, utblandad och rangerade, Mskan10, samma reduktionskanal, Mkan11, samma syntektion, Msakan12, ingen nätverking krävs.
Storageen är lättviktig: SQLite för metadata och FTS5 indexerM SK2 DuckDB med VSS-extensionen för HNSW-vektorindexer . Inga utomverkliga tjänster
doomsummarizer scroll "AI safety and regulation" --vibe doom --debug
Composite query detected: 2 subqueries
• What's new in AI safety?
• What are the latest AI regulations?
Searching...
├─ Lucene: 18 keyword matches (regulation^3, safety^2)
├─ Embedding: 12 semantic matches (max-sim across 2 subqueries)
├─ RRF fusion: 22 candidates, 6 signals
├─ Entity HNSW: +3 related via entity profiles
├─ TextRank: compressed 4 long articles
└─ Final: 12 items
Long-form: Phase 1 - 187 segments from 12 articles
Long-form: Phase 2 - "AI Safety Landscape" - 5 sections
Long-form: Phase 3 - Evidence assigned (cross-section dedup: 8 removed)
Long-form: Phase 4 - Generating sections...
Det här är insikterna.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.