Own project
rag-motion
An AI assistant for strength coaches that answers only from trusted sources, cites every claim, and builds full training programs with an agent.
My roleSolo engineer: design, build, and evaluation
- answers breaking a client's equipment limits, out of 13
- 4 → 0
- answers breaking a client's equipment limits, out of 13
- averaged over 3 runs
- agent-built programs passing every check, up from 15/20
- 30/30
- agent-built programs passing every check, up from 15/20
- test questions with reviewed expected answers
- 55
- test questions with reviewed expected answers
- automated tests
- 173
- automated tests
01The problem
AI training advice usually sounds confident but can't say where it came from, and it ignores the details a coach cares about: what equipment someone has, how experienced they are, whether they're 17 or 70. I wanted an assistant that answers only from sources a coach would trust, cites every claim, respects a client's limits, and then builds a full training program the same way.
02What I built
- A knowledge base of the Physical Activity Guidelines for Americans, 8 open-access research papers, coaching notes, and a 60-exercise catalog, stored in PostgreSQL with pgvector
- Ingestion that splits each document by its own structure into 150–450 word passages, keeping the source, chapter, pages, and license on every passage
- Search that turns a question into an embedding and pulls the closest exercises and passages, with SQL filters for equipment and age group read from the question
- Answers written only from the retrieved evidence, with every claim cited and every citation checked in code. Advice from the model's general knowledge is labeled separately.
- A tool-calling agent that interviews a client, searches exercises and sources, and submits a full program, which code checks against 18 rules before accepting it
- LangSmith tracing on every request: what was retrieved, the prompt, the answer, the cost, and the timing
03How it works
- 1
Read the limits: a small model call pulls equipment, age group, and experience level from the question
- 2
Expand gym slang: a dictionary turns shorthand like RDL and BSS into full exercise names
- 3
Retrieve: pgvector finds the closest exercises and passages, filtered in SQL to what the client can use
- 4
Answer: Gemini writes from that evidence only and cites every claim
- 5
Check: code verifies every citation before the answer is shown
04How I measured it
A 55-question test set with expected, acceptable, and forbidden answers for each question, drafted with AI and reviewed by me against my exercise science training. Retrieval is scored with recall and MRR. Answers are checked in code for valid citations, honest coverage claims, and forbidden recommendations, with an AI judge used only for a few labeling rules. Every before-and-after comparison is averaged over 3 runs, because single runs proved too noisy to trust. The program agent has its own evaluation: 14 fictional clients, scored by the same code checks the agent has to pass.
- citations valid
- 40/40
- citations valid
- exercise recall after adding filters
- 0.80 → 0.83
- exercise recall after adding filters
- slang questions finding the right exercise
- 89% → 100%
- slang questions finding the right exercise
05Key decisions
Enforce limits in the database, not the prompt
Instead of: Rules in the prompt
- Why
- Telling the model not to suggest barbell exercises doesn't stop it from seeing them. So a small model call reads the client's equipment and age group, and SQL keeps only exercises they can actually do.
- Result
- Answers that broke a client's limits went from 4 of 13 to 0 across 3 runs, and exercise recall went up from 0.80 to 0.83.
A slang dictionary instead of an AI rewrite
Instead of: LLM query rewriting
- Why
- Embeddings don't know gym shorthand: "RDL" scored far below "Romanian deadlift." I tested a small dictionary against having a model rewrite every question.
- Result
- Both found the right exercise for every slang question, up from 89%. The dictionary added no time; the model rewrite added 640 ms. The dictionary won.
Code builds the structure, the agent fills it
- Why
- When the agent planned a whole training week itself, about 1 in 4 programs broke a structural rule even after 3 tries.
- Result
- Now code lays out the week and the agent chooses exercises for each slot. Programs passing every check went from 15 of 20 to 30 of 30.
Pick models by testing, not by price
- Why
- I compared 6 models on the agent. One cheaper model matched Gemini's reliability at about a quarter of the cost, but it stalled on full program-sized answers.
- Result
- Gemini 2.5 Flash runs the agent, with a cheaper fallback model and a 40-second hard deadline. Programs come back in 8–21 seconds.
Average every comparison over 3 runs
- Why
- The same question could come back with a different answer on the next run, so a single run could make a change look better or worse than it was.
- Result
- A change only counts if it beats the run-to-run noise.
06Results
- Every answer cites its sources, and code verifies each citation
- Equipment and age-group limits are enforced: no limit-breaking answers across 3 runs
- The program agent has passed every check on all 90 runs since the structure fix
- Answers cost about $0.0004 each
07What I learned
- Structured output checks the shape of an answer, not whether it's true. One model followed an instruction hidden inside a document.
- Search can't enforce rules on its own. Limits belong in the query, not the prompt.
- An answer can't be better than what search returns, so I measure retrieval separately from answers.
- A research tool once invented numbers from a PDF it couldn't read. Checking against the real text caught it.
08Stack
- Python
- FastAPI
- PostgreSQL + pgvector
- Voyage embeddings
- Gemini via OpenRouter
- LangSmith
- pytest
- Docker
want the longer version?
I'm happy to walk through how I built it.