ProjectIntent Over Keywords: From Stitched Embeddings to a Production Fashion Agent · Module 0: Project overview: a guided tour of all three parts · part 2 of 26
Part 2 · Module 0: Project overview: a guided tour of all three parts

Module 0: Project overview: a guided tour of all three parts

19 min read·
Start here · About 30 minutes · Needs: nothing · Code: none (screenshots of the real apps, running locally with Docker on the author's Mac)

Most AI projects stop when the answer looks correct. This project starts there. You will build three things. Part 1 is a video search that sounds reasonable on paper: frames go to one model, speech to two others, and the results are merged by rank. Its answers feel almost correct. Part 2 rebuilds it around one decision, and a whole category of wrong answers disappears. Part 3 is StyleMe, a fashion shopping assistant that decides what the shopper wants before it searches, then proves its behaviour with tests, evals, traces and a deployment.

This page is a tour of the finished work, told through screenshots of the real apps. The 24 modules after it take every piece apart.

Where this fits

WHERE THIS FITS Intent Over Keywords this module PART 1: STITCHED VIDEO RAG three models, two spaces, RRF PART 2: UNIFIED VIDEO RAG one model, one space PART 3: STYLEME FastAPI, LangGraph, React QUALITY AND OPERATIONS evals, Docker, Cloud Run Frames ffmpeg, one per 5 s CLIP ViT-B/32 frames + text, 512 d Whisper base speech to text MiniLM 64-word chunks, 384 d Two collections p1_visual, p1_text Rank fusion RRF, k = 60 Gemini answer from transcript text API + Streamlit upload, ask, sources Windows ffmpeg, video + audio Embedding 2 Gemini, one vector One collection p2_unified, no fusion Notes + answer Gemini, cited times React web chat, photo, chips nginx one URL, /api proxy FastAPI /api/chat, limits Sessions in memory, cap of 3 Planner intent before retrieval Implementer batched embed, search Generator stylist reply, ids Grounding check no LLM, drops invented Gemini flash-lite, embedding 2 Qdrant styleme_products Catalogue ingest image + title vectors Langfuse optional traces Test video 26 questions, 4 kinds Video eval Part 1 vs Part 2 Agent evals intent, grounding Guards uploads, limits, secrets Containers compose profiles Cloud Run StyleMe only CI/CD GitHub Actions, WIF Original code v1, v2, fashion-agent
The whole course on one map: Part 1 (stitched video search), Part 2 (one embedding per moment), Part 3 (StyleMe, the shopping agent) and the tests, evals and deployment around them. Blue is our code, amber a model, green a safety part; this tour touches every box.

Module 0 touches every box on the map, from the outside. The same map opens every module, with that module's parts highlighted.

What it does

ONE COURSE, THREE PARTS From an answer that looks right to one you can trust PART 1 Should work 3 models: CLIP, Whisper, MiniLM 2 collections, fused by rank picture and speech never meet Looks right, misses the name PART 2 The redesign 1 model: Gemini Embedding 2 1 vector per clip + its audio 1 collection, no fusion Shown and said, bound together PART 3 StyleMe decides intent first at most 3 questions, in code checks the reply against results A product, not a demo Most AI projects stop when the answer looks correct. This project starts there.
Red marks the design that fails on purpose, green the redesign that fixes it, blue the product built on top: each part keeps its design so you can see what changes and why.

The course is one argument in three steps. First, a design that looks right fails on a designed test, and the failure is not a prompt or a chunk size but the architecture: picture and speech live in two separate vector spaces and never meet. Second, embedding what was shown and what was said together, as one vector per moment, removes that failure. Third, a real product needs more than good retrieval: it must decide whether to search at all, ask when it does not know enough, stop asking after three questions, and refuse to describe products it did not find.

The core message runs through all three: an answer that looks right is not evidence; a designed test is.

At a glance

PracticeWhat it isWhy you need itHow this project does itWhen you need itWhere it lives
A test that can faila video and questions built so each design fails in a known waya demo question proves nothing12 slides, 26 questions of 4 kinds, a build self-checkbefore you trust any retrieval systemsamples/ (Module 6, the test video)
Joint embeddingone vector for a clip and its audiorank fusion cannot bind a spoken name to a pictureGemini Embedding 2, one collection, no fusionquestions need what was shown and said togetherPart 2 (Module 7)
Intent before retrievala planner decides greet, ask or search"hi" should not run a product searchone Gemini call, then routing in LangGraphmulti-turn assistantsStyleMe planner (Module 14)
Rules in codethe cap of 3 and grounding enforced outside the promptprompts are requests, not guaranteesa counter in the session, a check on every replyanything a user can talk aroundStyleMe (Module 15, the cap; Module 17, grounding)
Honest measurementa harness through the public API, with caveatsnumbers without caveats misleadhit@1, MRR, answer rate, latency, notes on what they proveevery change you want to claimevals/ (Modules 10 and 20)

Use cases and examples

A test that can fail. A broadcaster searching its archive asks "which minister spoke in front of the flooded bridge?"; a test with only spoken names would never reveal that the picture is ignored. An e-learning site checks that "the formula on the slide" questions really hit the slide. A hospital training library tests a whiteboard-only answer.

Joint embedding. A newsroom wants "the clip where the reporter says 'record high' over the stock chart". An insurer reviews dashcam footage where the driver's words matter next to what the camera saw. A school indexes lab demos where the teacher names a chemical while pointing at it.

Intent before retrieval. An online shop greets a returning customer without dumping products. A bank's assistant asks one question ("personal or business account?") before searching policies. A SaaS help bot answers "thanks!" with words, not with ten help articles.

When not to bother. A single search box over product titles, a video archive where every answer is spoken, or a prototype nobody will rely on: there, a plain keyword or text search is cheaper and good enough.

How it works

The tour follows the order of the course. The video screenshots were retaken on 28 October 2026, after the security review; the StyleMe screenshots were taken before it. The video screenshots come from separate runs from the comparison report, with the same settings, so their wording and timings differ a little from the report. Where this page quotes an answer word for word, it comes from the report of one run on 27 October 2026 (UTC stamp 20261027T192400Z) with a free-tier Gemini key.

1. The test video and its questions

Every answer in the test video is placed on purpose. The company is fictional: Tidewater Freight, a shipment-tracking architecture review presented by Priya Nair, platform lead. Every picture is drawn by a script and the narration is a synthetic voice, so there is no third-party media.

THE TEST VIDEO 3 minutes 22 seconds, 12 slides, every answer placed on purpose 1 v01 2 c01 3 4 5 s02 6 7 8 9 10 11 12 0:00 1:00 2:00 3:00 3:22 1 Title slide, Room Harbor-4 0.0 s 2 Service map, red box: removed 17.3 s 3 Latency chart, Week 37 36.1 s 4 Whiteboard runbook 54.9 s 5 Data retention (spoken) 71.8 s 6 Service map, red box: added 92.7 s 7 On-call rotation 109.0 s 8 Pizza lunch (distractor) 123.7 s 9 Latency chart, Week 42 140.9 s 10 One scan, end to end 156.3 s 11 Monthly cloud cost 173.7 s 12 What is next 190.0 s
The test video drawn to scale: blue slides hold the answers of the three questions this tour follows, red is the off-topic pizza slide, and slides 2 and 6 are near-duplicate service maps that differ only in the red box.
26 QUESTIONS, 4 KINDS Each kind tests a different way to fail Visual only on screen, never spoken v01 Which meeting room is the review held in? passes when the answer contains: Harbor-4 Speech only spoken, never on screen s02 How long are raw GPS pings kept? passes when the answer contains: 45 days Cross-modal needs the picture and the words c01 Which service did Tidewater remove in March? passes when the answer contains: Label Printer Distractor an off-topic slide d01 What temperature does the pizza oven run at? passes when the answer contains: 430
One person per question: the 8 cross-modal questions are the ones a design must bind picture and speech to pass, and green shows the string the eval looks for in the answer.

Three questions carry the tour. v01 asks which meeting room the review is held in; the answer, Harbor-4, is only on the title slide. s02 asks how long raw GPS pings are kept; "45 days" is only spoken. c01 asks which service Tidewater removed in March. The slide shows a service map with one box in red; the speaker says "the service in the red box, that's the one we removed in March". Only the picture holds the name and only the speech says "removed". A cross-modal question needs both.

2. Part 1: the stitched lab

Intent Over Keywords Part 1: stitched video RAG Legend query flow ingestion 1 step a model weak point INGESTION POST /ingest, once per video Video mp4, max 200 MB ffmpeg frames one per 5 s CLIP image 512 d vector p1_visual frame vectors Whisper base timed transcript Chunker 64 words, 16 overlap MiniLM 384 d vector p1_text chunk vectors 1 2 3 QUERY POST /search Question typed by the user CLIP text same 512 d space Search visual top k x 2 frames MiniLM query, 384 d Search text top k x 2 chunks RRF, k = 60 ranks, not scores Gemini answer transcript text only 4 5 6 Frames reach the answer only as timestamps: picture and speech never meet in one vector AROUND IT hardened, same design FastAPI /ingest, /search Streamlit UI upload, ask, sources Docker image 5.7 GB, offline Tests fakes, no network
Part 1 by step: 1 a video arrives, 2 a frame every 5 s becomes a CLIP vector, 3 Whisper's transcript becomes 64-word MiniLM chunks, 4 the question is embedded twice and searched twice, 5 the two ranked lists are fused by rank, 6 Gemini answers from transcript text; amber parts are models, the red band is the weak point.

Open the full-size Part 1 architecture diagram

Part 1 home page: a sidebar with API status and the loaded models, and an upload box

The Part 1 lab, "Video RAG: stitched models". The sidebar reports the API status as ok, two collections (p1_visual, 512 d, and p1_text, 384 d), fusion rrf(k=60), generation with gemini-3.5-flash-lite, and three loaded models: CLIP ViT-B/32, Whisper base and all-MiniLM-L6-v2. "How it works" lists four steps: a frame every 5 s to a CLIP vector, audio to Whisper to about 64-word chunks to MiniLM, the query embedded twice for two searches, and the two ranked lists merged by rank, then answered. The main area takes an mp4, mov or webm of up to 200 MB.

The test video loaded in Part 1: title slide with Room Harbor-4, and 40 frames, 12 text chunks, 3:22

The test video after "Index this video". The title slide reads "Tidewater Freight, Shipment-Tracking Architecture Review, Platform team, Room Harbor-4". The counters show 40 frames (CLIP), 12 text chunks (MiniLM), a length of 3:22 and the video id 3f4b577f016dc1a0, the start of the file's SHA-256. "This exact file was already indexed; nothing re-embedded", so processing took 0.1 s. The report's first ingest of the same file took 18.0 s.

3. Part 1 answers c01: almost correct

Part 1 answering c01: the service shown in the red box was removed, no name

Asked "Which service did Tidewater remove in March?", Part 1 answers: "Tidewater removed the service shown in the red box on the service map in March because carrier portals were already performing the same job, which also helped cut a deploy pipeline and late-night pages [0:18-0:54]." The reason is right, the time is right, the name is missing. Embedding took 49 ms, the two searches and fusion 51 ms, generation 1123 ms, 1223 ms in all. Source #1 is the opening transcript chunk, 0:00 to 0:23, with an RRF score of 0.0164.

Part 1 sources for c01: transcript chunks and cloud-cost chart frames, no service map

The five fused sources. Two are transcript chunks ranked by MiniLM, two are frames of the cloud-cost donut chart (2:55 and 3:05, CLIP cosine 0.263) and one more chunk at 1:22. Source #3, 0:18 to 0:54, holds the speech: "now look at the service in the red box, that's the one we removed in March". No frame of the service map made the top five.

This is the binding problem. The speech says "red box"; the name sits inside a picture. Part 1 ranks frames and transcript chunks in separate spaces and merges only their ranks, so nothing ties the words to the box. Even when a frame does rank first, the generator receives it only as a timestamp, which is why Part 1 answered none of the 7 visual questions in the report (the binding problem is the subject of Module 6, "almost correct").

4. Part 1 cannot name the room

Part 1 answering v01: could not find the meeting room, top source a pizza slide frame

v01, "Which meeting room is the architecture review held in?". Part 1: "I could not find the specific meeting room name in the video, though the speaker mentions that the next review will be held in the same room [3:07-3:21]." Source #1 is a frame of the pizza workshop slide at 2:05, CLIP cosine 0.254; source #2 is the closing transcript chunk. The answer is honest, and useless: the room name is printed on the first slide.

5. Part 2: one embedding per moment

Intent Over Keywords Part 2: unified video RAG Legend query flow ingestion 1 step a model the fix INGESTION POST /ingest, once per video Video mp4, max 200 MB ffmpeg windows re-encoded, overlap Clip + its audio one Content Embedding 2 1536 d, one vector p2_unified one collection Inline bytes Files API over 18 MB Notes screen + speech Payload times, notes, thumb 1 2 3 4 QUERY POST /search Question typed by the user Embedding 2 RETRIEVAL_QUERY One search p2_unified, top k Windows times + notes Gemini answer grounded in notes 5 6 7 No second collection, no fusion: what was shown and what was said share one vector AROUND IT same API as Part 1 FastAPI /ingest, /search Streamlit UI upload, ask, sources Gemini hygiene timeouts, retries Tests fake Gemini
Part 2 by step: 1 a video arrives, 2 ffmpeg cuts overlapping windows, 3 each clip and its own audio become one Gemini Embedding 2 vector, 4 a Gemini note per window is stored for the answer, 5 the question becomes one query vector, 6 one search, 7 Gemini answers with cited times; green marks the fix.

Open the full-size Part 2 architecture diagram

Part 2 with the same video: 17 chunks, 3:22, same video id; sidebar lists one collection and Gemini Embedding 2

The Part 2 lab, "Video RAG: unified embedding", with the same file and the same video id. The sidebar shows one collection, p2_unified (1536 dims), embedding with gemini-embedding-2, upload mode auto, and the steps: ffmpeg cuts about 15 s windows with 3 s overlap, each window becomes ONE Gemini embedding of picture and speech together, one Qdrant collection, one query embedding and one search. Its last line: "No CLIP, no Whisper, no MiniLM, no rank fusion." The counters read 17 chunks.

6. Part 2 answers both

Part 2 answering c01: Tidewater removed the Label Printer Service, with timings

The same c01 question. Part 2: "Tidewater removed the Label Printer Service in March because the carrier portals were already performing the same job [0:12-0:33]." Embedding the question took 833 ms, the search 8 ms, generation 865 ms, 1706 ms in all. Source #1 is the window 0:24 to 0:39 with a score of 0.4778 and a thumbnail of the service map with the red box.

SAME QUESTION, SAME VIDEO c01: Which service did Tidewater remove in March? Part 1, stitched rank 1, answer wrong “Based on the provided video evidence, the specific name or type of the service removed in March is not mentioned; the speaker only refers to it as the service located “in the red box” on the service map [3]. ...” Part 2, unified rank 1, answer right “Tidewater removed the Label Printer Service in March because the carrier portals were already performing the same job [0:24-0:33].” Retrieval found the right moment both times. Only one answer could bind “red box” to a name.
Red is the almost-correct answer, green the right one, both quoted word for word from the comparison run of 27 October 2026: the same rank 1, two different answers.
Part 2 answering v01: the review is held in Room Harbor-4

v01 in Part 2: "The architecture review is held in Room Harbor-4 [0:00-0:15]." 1594 ms in all, 6 ms of it the search. Source #1 is the service map window 0:12 to 0:27, which overlaps the end of the title slide; the title slide window itself, 0:00 to 0:15, is source #2.

The search itself takes milliseconds; the time goes to model calls. Part 2 embeds the question through an API, not a local model, so each question is slower.

7. Part 2's sources, and a note that is wrong

Part 2 sources for c01: five windows, each with an ON SCREEN note

Each source carries a note that Gemini wrote at ingest, describing what was on screen and what was said. Source #2, 0:12 to 0:27, lists "Label Printer Service (in red box)". Source #1, 0:24 to 0:39, says the opposite: "Tracking Service (highlighted with a red box in the video, though speaker refers to it as the Label Printer Service)".

NOTES CAN BE WRONG Source #1 for c01 in the Part 2 screenshot THE INGEST NOTE FOR 0:24-0:39 SAYS “Tracking Service (highlighted with a red box in the video, though speaker refers to it as the Label Printer Service)” A Gemini note written once at ingest, stored in the payload, read by every answer. THE SLIDE ACTUALLY SHOWS Label Printer Service in the red box; the speaker says it was removed in March. First screenshot run: “... the Label Printer Service (also referred to as the Tracking Service)” Retaken run, same index and note: “... the Label Printer Service in March ...”
Red is the model's mistake, green the truth on the slide: the same wrong note reached the answer in one run and not in the next, so a clean answer is not proof the index is clean.

Source #1's note is wrong, and it is still in the index. In the first screenshot run the mistake reached the answer ("the Label Printer Service (also referred to as the Tracking Service)"); in the retaken run it did not. Same index, same note, different generation. Notes are a strength of Part 2 and a risk (ingest notes are covered in Module 9, grounded answers).

8. The comparison in numbers

The harness in evals/video/ asked all 26 questions of both running APIs, top 5 sources each.

PART 1 AGAINST PART 2 26 questions, one run, top 5 sources Part 1 Part 2 hit@1 right moment ranked first 58% 85% MRR mean of 1 / rank of the first hit 0.74 0.89 Answers correct contains the expected string 38% 96% Part 2 answers also use ingest notes Cross-modal answers the 8 picture + speech questions 0 of 8 8 of 8 Part 2 answers also use ingest notes
Grey is Part 1, blue Part 2, bars to scale; amber flags the answer rows, which mix the new embedding with Gemini's ingest notes, so hit@1 and MRR are the fair comparison of the two designs.
Measured on 26 questionsPart 1Part 2
hit@1 (the right moment ranked first)58%85%
MRR (mean of 1 / rank of the first hit)0.740.89
Answers containing the expected string38%96%
Cross-modal answers0 of 88 of 8
Ingest of the test video (server time)18.0 s162.8 s
WHY 38% BECAME 96% Two changes arrived together Part 1 answers 38% correct Part 2 answers 96% correct Joint embedding hit@1 58% to 85% MRR 0.74 to 0.89 Ingest notes Gemini describes each window's frames Part 1's generator sees a frame only as a timestamp. Part 2's sees a model's notes. So the answer jump is not the embedding's alone: compare designs on retrieval metrics.
Blue is the design change you can measure on retrieval alone, amber a model's notes that also feed the answer: honest comparisons keep the two apart.

Read the answer rows with care. Part 2's generator sees Gemini's ingest notes; Part 1's never sees a frame. So the jump from 38% to 96% mixes two changes, the joint embedding and a model describing the frames. The retrieval rows, hit@1 and MRR, are the fair comparison of the two designs. hit@5 is nearly saturated on a video this short (92% against 96%), and this is one run on one video with a free-tier key (Module 10 measures and qualifies every number).

9. StyleMe: the empty chat

Intent Over Keywords Part 3: StyleMe Legend request flow ingestion 1 step a model a guard BROWSER React 18 + Vite FRONT DOOR nginx Chat page text, photo, quick replies Session id history from the server nginx SPA, /api proxy, proxy secret 1 STYLEME API FastAPI, one process POST /api/chat message + image Upload checks magic bytes, Pillow Rate limit 30 a minute per IP Sessions memory, TTL, one lock Proxy secret 403 without nginx Product images /api/images/{id}.png Errors one JSON shape Tracing Langfuse, optional 2 AGENT LangGraph: planner, implementer, generator Planner greet, ask or search Implementer 1 search per query Generator reply + product ids Grounding check ids, brands, prices Clarify, then END at most 3, in code Image only skips the planner Gemini flash-lite planner, generator Embedding 2 text + image, 1536 d 3 4 6 DATA CLOUD StyleMe only Catalogue CSVs + images Ingest CLI idempotent Qdrant styleme_products Cloud Run web 0 to 3, API exactly 1 5 OPERATIONS evals, tests, CI/CD Agent evals intent, grounding Tests fakes, in-memory Compose profile styleme CI/CD keyless, WIF
StyleMe by step: 1 the browser calls nginx, 2 nginx forwards /api with a secret to the API and its guards, 3 the planner decides, 4 a clarification ends the turn, 5 the implementer searches Qdrant, 6 the grounding check runs on the reply; amber parts are Gemini models, green parts are guards.

Open the full-size StyleMe architecture diagram

StyleMe empty state: What are we shopping for today? with four starter prompts and Search with a photo

"What are we shopping for today?" The promise is in the subtitle: "If I need more detail I'll ask, at most three quick questions, then find matching pieces." Four starters ("A light kurta set for a summer wedding", "Formal shirt and blazer for office, under ₹2,000", "Comfortable sandals for women", "I need something for a party"), a dashed "Search with a photo" button, and a composer that accepts a pasted or dropped PNG, JPG or WebP of up to 5 MB. Top right: How it works and New chat.

10. A greeting, then a question

StyleMe: hi gets a greeting; I need something for a function gets Question 1 of 3 with chips

"hi" gets a greeting and no products: "Hello! Welcome to StyleMe, your personal fashion assistant. What are you looking to wear or buy today?" Then "I need something for a function" gets a badge, "Question 1 of 3", the question "Could you please tell me what kind of function or event you are attending?" and five chips: Wedding, Party, Mehendi, Sangeet and a dashed "Just show me options".

INTENT BEFORE RETRIEVAL One planner call decides what the turn is for WHAT THE SHOPPER SENT “hi” “I need something for a function” “cotton kurta for a summer haldi ceremony, women, under ₹1500” a photo, no text Planner one Gemini call Greeting a reply, no search Clarification Question 1 of 3 + chips Search queries, products, reply Search “Similar to your photo” skips the planner call
Amber is the model's decision, green the turns that end without touching the catalogue, blue the search turns: a plain keyword search would have searched for “hi”.

This is the project's name in action: intent over keywords. A keyword engine would search for "function". The planner sees a request too vague to search well, so it asks once and offers answers the shopper can tap.

11. An answer, then results

StyleMe: It's a wedding returns a suit, cargos and lace-up shoes under Ethnic wear

"It's a wedding" ends the questions. The reply names one product: "For a wedding, the LOUIS PHILIPPE Suit Checkered Men Suit at ₹9,749 is a sharp and elegant choice that will make you stand out." The group "Ethnic wear", searched for "wedding wear outfit for men and women", holds that suit (49%), DENNIS LINGO Men Cargos at ₹949 and TORRY Lace Up For Men at ₹499 (both 48%).

The group title is the category the planner gave its query, not a label on each product, which is why cargos sit under "Ethnic wear". The percentages are match scores, and around 50% they are weak: the catalogue has 203 rows, and not every request has a good answer in it.

12. A detailed request, and Why these?

StyleMe: cotton kurta for a summer haldi ceremony returns three items with a reply about yellow options

"cotton kurta for a summer haldi ceremony, women, under ₹1500" needs no question. The planned query is "women yellow cotton kurta for haldi ceremony under 1500": the planner added "yellow", which the shopper never typed. Results: MOKSHI Women Kurta and Palazzo Set Cotton Blend, ₹569 (51%), SKY FAB Women Maxi Yellow Dress, ₹439, and AARTI FASHION Women Printed Rayon Anarkali Kurta, ₹423 (both 49%). The reply begins "Here are some cheerful yellow options", but the set in the picture is pink and the dress orange.

The Why these? panel: decision search, 0 of 3 questions, one planned query, timings and grounding passed

Under the results, "Why these?" opens the turn's reasoning. Decision: search, "enough detail, so it planned the queries below and searched the catalogue". Questions asked: 0 of 3 allowed. One planned query, in the category Ethnic wear. Timings: planner 983 ms, search 771 ms (embedding 764 ms, vector search 7 ms), answer 1.1 s, total 2.8 s. Grounding check: "Passed: the reply names only products, brands, prices and item types that were retrieved."

WHERE ONE TURN'S TIME GOES The haldi request, from its Why these? panel Planner 983 ms Embedding 764 ms Vector search 7 ms Answer 1.1 s Total 2.8 s Search (771 ms) = embedding + vector search. Three model calls take almost all of it.
Amber bars are model calls, blue our own vector search, navy the whole turn, all to scale: the database is the cheap part, the models are where latency lives.

The grounding check passed although the colours were wrong, because at the time it checked ids, brands, prices and product types, not colours. That gap was found from this screenshot and closed afterwards (see What can go wrong).

13. Search with a photo

StyleMe photo search: Similar to your photo, three kurta sets at 75%, 72% and 66%

A photo with no text, labelled "Search by photo". An image never gets small talk or a question, so the turn goes straight to search, and the group is titled "Similar to your photo". The first card, SURHI Women Kurta and Sharara Set Rayon at ₹439, shows the same picture the shopper sent, at 75%. Then SHINOY Women Printed Rayon Straight Kurta, ₹259 (72%), and MOKSHI's kurta and palazzo set (66%). The reply names the first two with their prices.

Text and photos share one embedding space here too: each catalogue product is indexed as one vector from its image and its title together, the same idea as Part 2 applied to a shop.

14. Vague answers, and the cap

StyleMe: not sure yet leads to Question 2 of 3 with chips

A second chat. "I need something for a function" gets Question 1 of 3, this time without chips. "not sure yet" gets Question 2 of 3, "Are you looking for something traditional like ethnic wear, or more western and casual?", with Ethnic wear, Western wear, Indo-western and "Just show me options".

StyleMe: something nice I guess leads to a search with two girls' party dresses and a boys' set

"something nice I guess" gets results instead of a third question. The query is "stylish party wear outfit"; the reply names the Fnock Fashion Midi/Knee Length Party Dress (₹265) and the Fashion Dream Girls Calf Length Party Dress (₹539). The third card is KIDZAREA Boys Casual Shirt, Waistcoat and Pant Set, ₹449. All three are children's clothes, at 50% to 51%.

THE CAP OF THREE Vague answers still end in results “I need something for a function” Question 1 of 3 “not sure yet” Question 2 of 3 “something nice I guess” Search: “stylish party wear outfit” At 3 questions the code stops asking and forces a search, whatever the model wants.
Green is the guard and the questions it counts, blue the search: in this run the planner searched after two questions; the third slot is the most it may ever use.

The cap is a rule in code, not a sentence in the prompt: after three questions the server forces a search. Here the planner chose to search after two. The results show the cost of vagueness: nobody said who the outfit was for.

15. How StyleMe explains itself

The How StyleMe works drawer: planner, search, answer, and the models in use

The How it works drawer. "Keyword search matches words. StyleMe works out intent first." Three steps: the planner "decides first" and picks greeting, question or search, asking at most 3 questions per chat; search turns each planned query and the photo into one multimodal embedding; the answer covers "only what it found", and a code check removes a brand, price or item type that was not retrieved. Below: language model gemini-3.5-flash-lite, embeddings gemini-embedding-2, tracing off until Langfuse is configured.

16. On a phone

StyleMe on a phone: Question 1 of 3 with stacked chips

The phone layout. "I need something for a party" gets Question 1 of 3, "Are you looking for something ethnic or western?", with the chips wrapping onto three rows. The header keeps Shop, About and two round buttons (How it works, New chat); the composer placeholder shortens to "What are you looking for?".

StyleMe on a phone: results in two columns with match scores and Why these?

Results in two columns: VASTRANIKHAR Women Ethnic Dress Gold, Pink at ₹396 and FABFAIRY Women A-line Green Dress at ₹439 (both 50%), then MOKSHI's set at ₹569 (49%), long titles cut with an ellipsis, and "Why these?" below.

17. StyleMe, measured

STYLEME, MEASURED One real eval run, 28 October 2026 intent accuracy 1.00 71 turns, 46 conversations cap compliance 1.00 never a 4th question grounding violations 0 after the check, 43 search turns text hit@1 0.809 68 queries; hit@5 1.00 image hit@1 1.00 34 photo queries search p95 39.6 s gate 9 s: FAIL on the free tier
Green passes its gate, red fails: the p95 matches the free tier's 429 backoff, and the datasets are small and hand-written, so they prove the wiring rather than open-world quality.

The evals run the real HTTP app over 46 hand-labelled conversations and two retrieval sets. Every gate passes except latency: the search-turn p95 of 39.6 s matches the free tier's 429 backoff, while the median is 4.2 s. That eval run indexed all 203 rows; the current code indexes 202 (product 4 is excluded, see below).

Code tour

Every later module has its own code tour. Here is where things live.

WHERE THE CODE LIVES source-code/, one folder per part FOLDER WHAT IS IN IT part1-video-rag-stitched/ Part 1: API :8101, Streamlit :8511 part2-video-rag-unified/ Part 2: API :8102, Streamlit :8512 part3-styleme/backend/ StyleMe API :8103, agent, ingest, evals part3-styleme/frontend/ StyleMe web :8097 (React, nginx) samples/ the test video and its 26 questions evals/video/ the Part 1 vs Part 2 harness and report deploy/ Terraform and Cloud Run, StyleMe only docker-compose.yml, Makefile one Qdrant, three profiles
Blue rows are the running code of the three parts, grey the test material and operations around them; the author's original code sits next to source-code in originals/, read only.

Parts 1 and 2 expose the same API (POST /ingest, POST /search), so one harness can measure both.

What happens when you ask c01 in both parts

  1. You type "Which service did Tidewater remove in March?" in the Part 1 lab, with top k 5 and "only last indexed

    video" ticked.

  2. Part 1 embeds the question twice: once with CLIP's text tower (512 d), once with MiniLM (384 d).
  3. It searches p1_visual and p1_text, each for top k times 2 = 10 candidates.
  4. Reciprocal rank fusion with k = 60 merges the two lists by rank. In the screenshot, the opening transcript chunk

    wins with 0.0164.

  5. The generator gets transcript text for chunks and only timestamps for frames, and writes "the service shown in the

    red box".

  6. In Part 2, the same question becomes one RETRIEVAL_QUERY embedding (1536 d) and one search of p2_unified.
  7. The top window, 0:24 to 0:39, comes back with its ingest note, and the generator writes "the Label Printer

    Service", citing times.

Design decisions

DecisionChosenAlternativesWhy
Keep Part 1 brokenharden the code, keep the stitched designfix the design in placethe failure is the lesson: cross-modal answers 0 of 8
One vector per momentclip and its audio in one Gemini embeddingbetter fusion, more promptingbinding happens inside the vector: hit@1 58% to 85%
15 s windows for the comparison17 windows with 3 s overlapthe 60 s default (4 windows)4 windows make top 5 always "hit"
Intent firsta planner call before any retrievalsearch every messagegreetings and vague requests need no catalogue search
Rules in codecap counter and grounding checkinstructions in the promptthe original's prompt-only cap could not stop a fourth question
THE PRICE OF THE REDESIGN Fewer parts, but a paid API and slower ingest REMOVED ADDED 3 local models CLIP, Whisper, MiniLM 2 collections p1_visual, p1_text Rank fusion RRF, k = 60 A Gemini API dependency key, quotas, 429s Notes written by a model can be wrong Slower ingest one embed + one notes call per window INGEST OF THE TEST VIDEO (SERVER TIME, TO SCALE) Part 1 18.0 s Part 2 162.8 s
Green is what the redesign deletes, amber what it brings in: it fixes the binding problem and pays for it in quota, cost and a nine times slower ingest on the free tier.

When to choose differently

If ...Switch to ...WhyIn this project
your videos cannot leave your machineslocal models, as in Part 1no cloud API, but no bindingBuilt: part1-video-rag-stitched/
your videos are long and answers broadlonger windowsfewer calls, coarser timesSetting: CHUNK_SECONDS=60
the cost of ingest notes is too highno notescheaper, but answers lose groundingSetting: SEGMENT_NOTES=false
shoppers find questions annoyinga lower capfewer turns before resultsSetting: MAX_CLARIFICATIONS=1
a newer generation model shipschange the settingthe model is a setting, not codeSetting: GENERATION_MODEL (default gemini-3.5-flash-lite)

What can go wrong

Answers that look right. Part 1's c01 answer has the right reason and time; only a test for the name catches it.

Notes that are wrong. Part 2 stores a Gemini note per window, and the note for 0:24 to 0:39 names the wrong service. A clean answer in one run does not mean the index is clean.

Mixed causes. Part 2's answer rate rises partly because of its notes. Claim the redesign's win on retrieval metrics, not on answer rates alone.

Fixed: a reply that described colours it did not have. The haldi reply called a pink set and an orange dress "cheerful yellow options", and the grounding check passed it. Found in the screenshot pass. A fifth check now drops any sentence stating a colour that no backing product title has, or falls back to a template; the reply is a test case in tests/test_grounding_colours.py. The check is strict: an honest "we have no yellow ones" is dropped too.

Fixed: a product whose photo was a necklace. Product 4 is listed as "Regular Fit Women Black Cotton Blend Trousers", but its image is a gold chain necklace. Found in the screenshot pass and confirmed by auditing all 203 images; it is now in excluded_products.csv, so 202 products are indexed.

Fixed: results grouped under "General". Also from the screenshot pass: groups are never titled "General", a photo-only search is titled "Similar to your photo", and cards are sorted by score. Each fix is pinned in the frontend's ux.test.tsx.

Slow tails on the free tier. StyleMe's latency gate fails at p95 because of 429 backoff. A key with a higher quota or client-side pacing is the fix; the 9 s threshold stays.

Try it yourself

Everything runs locally with Docker. Copy the example settings and add your own Gemini key (Part 1's search works without one, but its answers need it):

bash
cd source-code
cp .env.example .env
make up-video-compare

What you should see

Part 1's lab on http://localhost:8511 and Part 2's on http://localhost:8512 (the first Part 1 build is large: about 5.7 GB). Upload samples/test_video.mp4 in each and ask c01, v01 and s02. make eval-video then runs all 26 questions through both APIs and writes a report; it calls Gemini, so it uses your quota.

bash
make up-styleme
make ingest

What you should see

StyleMe on http://localhost:8097. The ingest embeds 202 products and is safe to run again. Type "hi", then "I need something for a function", and open "Why this reply?".

bash
make test

What you should see

Every test suite runs on fakes, with no network, keys or models.

Check yourself

  1. Part 1 ranked the right moment first for c01, yet the answer failed. Why?

Its sources were scored in two separate spaces and fused by rank, and frames reach the generator only as timestamps. Nothing tied the spoken "red box" to the name drawn in it.

  1. Part 2's answers rose from 38% to 96%. Why is that not a clean measure of the joint embedding?

Part 2's generator also reads ingest notes that Gemini wrote while watching each window. Retrieval metrics (hit@1 58% to 85%, MRR 0.74 to 0.89) compare the designs fairly.

  1. Why did "hi" not return any products in StyleMe?

The planner decides intent before retrieval. A greeting ends the turn with a reply and never reaches the search.

  1. The haldi reply passed the grounding check although its colours were wrong. What changed?

The check then covered ids, brands, prices and product types only. A colour check was added that drops sentences stating a colour no backing product has.

Key terms

TermMeaning
Cross-modal questiona question whose answer needs what was shown and what was said together
Binding problemthe system finds both signals but nothing ties a spoken phrase to the thing on screen
Reciprocal rank fusion (RRF)merging ranked lists by rank alone, 1 / (k + rank), here with k = 60
Joint embeddingone vector made from a clip and its audio, or a product image and its title
Ingest notestext Gemini writes about each window at ingest, used to ground Part 2's answers
hit@1, MRRthe share of questions whose first source is right; the mean of 1 / rank of the first right source
PlannerStyleMe's first step: one model call that chooses greeting, question or search
Clarification capat most 3 questions per chat, counted and enforced in code
Grounding checkcode that removes product names, brands, prices, types or colours the search did not return

Where to go next

PartModules
Part 1: The Architecture That Should Work1 the problem, 2 the stitched architecture, 3 frames and CLIP, 4 Whisper and MiniLM, 5 rank fusion, 6 almost correct
Part 2: The Redesign7 one embedding space, 8 windows and audio, 9 grounded answers, 10 measuring v1 against v2
Part 3: StyleMe11 the product problem, 12 high-level design, 13 low-level design, 14 the planner, 15 the cap, 16 product search, 17 grounding, 18 the frontend, 19 tracing, 20 evaluating the agent, 21 security, 22 containers, 23 deploy and CI/CD, 24 production realities