Inspiration

Vantage began as a live AI broadcast desk for the markets, an animated anchor that charts stocks, reads the news, and answers out loud instead of making you read a dashboard. Then I pointed it at a DataHub instance and asked who owned one of my tables.

It answered immediately, fluently, and made it up completely. The table had a description but no owner recorded, and the model filled the silence rather than admit the silence existed.

That turned out to be the interesting problem, and it became the project.

What it does

Vantage is an agent, not a chatbot. Ask "chart AMD and explain the move" and the desk reasons for the request, picks a tool charting, watchlist, navigation, report export, or a DataHub catalog lookup executes it, and narrates the result on air. It keeps multi-turn memory locally, and its entire inference path can run offline on a local model through Ollama or vLLM, including on an AMD Radeon GPU via ROCm. No cloud keys, nothing leaving the device.

Pointed at DataHub, the same desk answers who owns a table, what its schema is, and what feeds it, read aloud from the live catalog.

The part worth judging

It will not invent catalog facts, and that is enforced structurally rather than by prompt wording because prompt wording measurably failed. Given an incomplete fact block, the small local model invented an owner in 4 of 5 runs and invented column lists in 3 of 5. Rewording the instructions did not fix it. Removing the model did.

So on a confirmed gap, the language model is taken out of the path entirely, and the desk states the gap deterministically, then lists what the catalog genuinely holds so the answer is still useful rather than a flat refusal. You can watch this happen: answers that involve the model take 820–1041 ms, and the two refusals return in 58 ms and 69 ms, because nothing is generating text. The response badge names which path answered: "DataHub + Ollama (local)" versus "DataHub (catalog)".

Four cases are handled this way: no owner, schema, or lineage recorded; a named column that isn't in the schema; no exact dataset match; and the narrating model failing — which reports the model failed rather than blaming DataHub.

Challenges

The one I didn't expect: DataHub's search returns a confident hit for a dataset name that doesn't exist. Searching "asdfghjkl_no_such_dataset" returns SampleHdfsDataset, a real but unrelated dataset, and the GraphQL response carries no relevance score to distinguish it from an exact match. My first version answered questions about the wrong table with total confidence — and once a model was narrating, it re-attributed that table's owner to the name the user had typed. I now check string similarity before trusting any hit and disclose near-matches instead of quietly answering.

How I built it

A React single-page app with an optional, dependency-free Node backend. Catalog queries go through a proxy that owns the query text: the browser sends an operation name — search, entity, or lineage — never GraphQL. The reachable surface is a fixed server-side whitelist of three read-only operations, so a hostile browser cannot compose a new query or reach a mutation, and the DataHub token never leaves the server. Verified end-to-end against DataHub v1.6.0, covered by a 105-test Vitest suite, Apache-2.0.

What I learned

Honesty in an LLM feature is an architecture decision, not a prompting one. Every version of "say you don't know if you don't know" lost to a model that had partial context and momentum. The fix wasn't better words; it was making the dishonest answer unreachable by ensuring there was no model in the path to produce it.

Built With

  • ai
  • amd
  • claude
  • elevenlabs
  • financial-tech
  • finnhub
  • gemini
  • javascript
  • llm
  • localstorage
  • node.js
  • ollama
  • openrouter
  • privacy-first
  • react
  • rocm
  • single-page-application
  • tailwind
  • tmdb
  • vite
  • vllm
  • web-speech-api
Share this project:

Updates