SlideSearch — live production path, updated 2026-09-15
Traced against the actual deployed file, makeup_search.py (repo: code.straive.com/tpavan.kumar/straive-slide-search), called via the search_v2 MCP tool. Confirmed live on production this session via direct API checks, not read from source alone. NEW-tagged blocks were added/changed in the session that first built this page — everything else was already live before that.
Every dashed EXAMPLE box below is real: run against the live 122,623-slide production index, not invented. One query — "customer analytics" — is traced end to end through every stage so the numbers stay consistent.
New: click ▸ version history on any block to see exactly when it was created, why, and every real commit that has touched it in the last 3 months — pulled straight from git, not summarized.
e.g. "customer analytics", or a multi-part brief like "fraud detection use case, regulatory compliance automation" · Agentic Chat App
System prompt now positions search_v2 as the primary tool for every brief, simple or multi-part — it used to lose out to the plain search tool for multi-outcome briefs specifically because it couldn't take a list. Universe scope (DAAIS/S&R/EdTech) is injected automatically into whichever tool actually gets called.
search instead of search_v2 — enforcement now covers both tool names.search_v2(query, universes, top_per_group) → makeup_search.get_makeup_engine().search(...). query can now be a single string or a list of outcome angles.
query), your universe filter (universes), and how many results to return per section (top_per_group) get handed to the real search function. An "outcome angle" is simply one topic within a multi-part brief — see the next step for how several of them get handled together.
A single query becomes a one-item list; a real multi-part brief keeps all its angles. Every angle runs intent classification + synonym expansion, and the results merge (intents unioned, expansion terms deduped case-insensitively).
INTENT_PROMPT → LLM (gemini-2.5-flash via LLM Foundry) → one or more of case_study / approach / capability / collateral / demo, plus extracted client and use_case if named. Falls back to keyword heuristics on failure.
null, use_case = "customer analytics"Curated cluster dictionary is now 96 keys / 164 terms (up from 7), built by mining the real corpus, not hand-typed. Plus 4 hand-picked single-word trigger clusters (SERVICE_HINTS, 18 more terms) for terms too short/generic to mine safely. Taxonomy-nearest is threshold-driven (cosine ≥0.68) instead of a fixed top-6, so a broad query gets ~100+ genuinely related terms and a narrow one gets only the few that actually apply.
Unified Fan 360, Fan 360, Golden Customer Record, Single Customer View, Identity Resolution …Each angle is embedded (gemini-embedding-2) and searched against the FAISS HNSW index (pool=300), fused via Reciprocal Rank Fusion — every angle is an equally-weighted primary voter. This is the mechanism that generalizes to any query and carries the ranking.
1 / (RRF_K + rank), RRF_K=60, summed per angle. A slide ranked #3 in the raw-query search contributes 1/(60+3) = 0.0159 to its primary score.Every expanded cluster/taxonomy term (across all angles) is pooled into ONE normalized signal (0–1), capped at +35% lift on top of the primary score. A term with zero primary relevance gets a heavily discounted score instead of a free ride to the top.
Score is multiplied by (a) how strongly the slide's folder matches the query's intent — this is now a purely internal ranking signal, never shown to the user — (b) whether the slide's own content type matches the intent (e.g. a case_study-intent query boosts case_study-typed slides, a low-content type like a title/section slide is demoted ~60%), and (c) a mild recency factor.
/Sales/DAAIS/Clients/Eu Yan Sang/Sales_/2023 Proposal (a CLIENT folder), type content.section-type slide for the same query ("Digital Transformation through Data Science: Unified Operation Center") had a higher fusion score going in (0.0794 vs 0.0711) but got demoted by type_weight=0.4:Drops lower-scored results whose real slide text is an exact (normalized) match to a higher-scored one already kept — catches a boilerplate slide copy-pasted verbatim into different decks, which deck-title dedup can't see at all.
...nfl-final-submission_Slide_50 and ...nfl-unified-fan-view-v1_Slide_50 — two different deck files, snippet text word-for-word identical ("5. Data & Analytics Partner for leading Technology Major…"). Lower-scored one dropped.Catches the harder case: the same template slide reused across decks with differing extracted text (different vision captions/wrapper format per file), so exact-text matching misses it. Reconstructs each candidate's FAISS vector, computes the full pairwise similarity matrix, and unions any pair ≥0.90 cosine via union-find — not a greedy "compare only to what's already kept" walk, which left survivors when one boilerplate slide had 254 near-identical copies smeared across a 0.74–0.99 similarity range.
Caps one canonical deck to 2 appearances across the whole response, applied before the group split. The existing per-group DECK_CAP=2 (below) couldn't stop a deck from claiming 2 slides in every section it touched — a large compilation deck could still show up 4× total.
Sections are now Case Studies / Approach & Methodology / Data & Analytics / Capabilities & Overview / Team & Credentials / Other — built from each slide's own slide_type, not which Drive folder it's filed in. Folder path stayed as step 07's internal ranking signal; it's no longer what decides the section a user sees.
content → maps to "Capabilities & Overview" section (via content_group_of()'s _OVERVIEW_TYPES set).Each section is already best-first internally, so its own top score is its real relevance to this query — sections now sort by that descending instead of a fixed sequence. A capability-only brief no longer leads with an empty or weak Case Studies section just because that used to be first in a hardcoded list.
Independent cosine-similarity search over 1,194 curated master case rows — runs alongside the slide search, not instead of it. Each row now also carries a real thumbnail_url when its deck happens to also be indexed (~38% of rows) — those cards used to always show a blank placeholder even when a real image existed.
0.727 (the real example below) means fairly strong — the closer to 1, the closer that master-sheet case study is, in meaning, to what was actually searched for.
0.727 to "customer analytics", real thumbnail resolved (its deck is also indexed).{query, intents, client, use_case, expanded_terms, groups: {...}, master_sheet: [...], total_matches}. Each card's title is now the slide's own real headline — previously every card in a deck showed the deck's name as its title, so two genuinely different slides from the same deck looked identical by name. A separate deck field carries the presentation name.
The chat agent calls present_groups to render slide cards. Its section order now mirrors whatever the backend already curated for this query, rather than defaulting to a habitual "Case studies first" sequence — the frontend also stopped re-imposing its own fixed order over the backend's.
A person can click any card to ask why it matched. The backend explain endpoint already existed, but was never called from the UI until this quarter — one batched Gemini call, given the same query this result set was actually ranked against (pulled from the message's own tool-call arguments, not the raw chat text, since the agent may have decomposed a multi-part brief differently).
Content-type sections, ordered by relevance to the actual question asked, each internally ranked, each genuinely distinct — no boilerplate flooding a section, no deck flooding the response, no unrelated slide masquerading as "similar" because it happened to share a deck name.