Building a Source-Grounded Support Answer Workflow
Every answer comes from one current support note and shows that note beside it. When no note fits, the answer says so instead of guessing.
Orion Robotics is a fictional home-robot maker. Asked how long the battery lasts, three support agents gave three answers: 8 hours, 6 hours from an archived spec sheet, and “it depends”. I built a Databricks workflow that answers from current notes only, shows the source, and says “I cannot tell” when no note fits.
The problem
The 214-document library keeps old versions beside current ones, and no reply showed its source. The sample: 14 notes (3 archived) and 12 questions. Three things make it hard:
- Owners and notes use different words.
- Archived notes match almost as well as current ones.
- A search always returns some note, even when none answers.
The approach
Support notes and owner questions go in. Archived notes are filtered out, each question is matched to its closest current note by meaning, Gemini answers from that note alone, and every answer is saved with its source and checked.
View full size (opens in a new tab)Set up the data, the key and the prompt
The setup follows the same pattern as my complaint reply generator: the CSVs uploaded into managed Delta tables under Unity Catalog, the Gemini key kept in the google_ai secret scope, and the model reached through the gemini_free serving endpoint, this time running gemini-3.5-flash-lite.
View full size (opens in a new tab)
View full size (opens in a new tab)The system prompt is short on purpose. It tells the model to use only the notes in the message, answer in one sentence, and give one fixed reply when the notes don’t answer the question.
View full size (opens in a new tab)Find the note, answer from it, keep the source
Never let an archived note answer a question
The archived spec sheet says 6 hours and matches the battery question almost as well as the current one. If it is ever allowed into the search, it can win.
The status filter runs inside the search itself. Each question is matched against the notes with Databricks’ ai_similarity function, which compares meaning rather than shared words, and the query only looks at rows WHERE status = 'current'. Archived notes are never ranked at all, however well they match, so they can never be the note an answer is built from.
Show a source the model cannot invent
If the model is asked to cite its source, it can name a note that sounds right but isn't the one it read.
The model is never asked to name its source. The source comes straight from the search result: the doc_id of the note that was passed in. If the reply contains “cannot tell”, the source is recorded as none instead, so an answer that admits it doesn’t know never carries a note that didn’t support it.
View full size (opens in a new tab)Check the answers
Each question in the sample has an expected note, so every source can be checked against it.
View full size (opens in a new tab)
View full size (opens in a new tab)
View full size (opens in a new tab)Be honest about what the run got wrong
The battery question, the one this whole case is about, came back 'I cannot tell from Orion's notes'. So did the old iPhone question and 'Does Orion speak French?', where the voice guide's 'English and Spanish only' answers it.
All three misses failed safe. Every answer that was given had the right source, the one question no note covers (playing music) was correctly refused, and no answer used an archived note or made something up. The cost is three owners who would be told “I cannot tell” when the notes did hold the answer.
What I can’t yet say is why each one missed. When the model refuses, the workflow records the source as none and drops the note it was shown, so the table can’t tell me whether the search picked the wrong note or the model refused a note that did answer. The French question looks like the second case: the answer needs one small step of reasoning from “English and Spanish only” to “no”. Keeping the retrieved note and its similarity score on every row, refusals included, is the next change, and it is also what the team asked for: a match score on every answer, weakest first.
Limits and trade-offs
- One note per answer. Each question is answered from its single closest note. Questions that need two notes are still to do.
- Nearest is not always a match. The top result is always some note. The fixed refusal sentence is what stops a weak match from becoming a confident answer.
- Refusals hide their reason. Until the retrieved note is saved on every row, a miss can’t be traced to the search or to the model.
- The run is slow on purpose. A 65-second pause between calls keeps it inside the free-tier limit, so 12 questions take about 13 minutes.
What this shows
A support answer is only as trustworthy as the evidence behind it. The workflow searches current notes only, takes each answer’s source from the search instead of the model, and says “I cannot tell” when the note doesn’t support an answer. On this run that meant 9 of 12 right and 3 cautious refusals, with no wrong answers, and the misses point to exactly what to change next.