Skip to main content

Source Grounding & Hallucination Reduction — Plan

Diagnosis​

Problem 1 — Irrelevant source citations on hover​

The groundSentence function in packages/semantic-search/src/index.ts uses pure bag-of-words (lexical) overlap with a threshold of 18%. This means:

  • A sentence like "The function returns null when there is insufficient data" tokenises to: function, returns, null, insufficient, data.
  • Every single code file in the corpus contains those words.
  • 18% threshold = 1 matching token out of 5 is enough to attach a citation.
  • Result: completely unrelated code files appear as sources for every sentence.

The buildCodeSourceReason text also uses the same overlap and generates explanations like "Supports this sentence because file.ts contains the same implementation terms: null, returns" — technically true but semantically meaningless.

Problem 2 — Tool result hallucination​

The model is told "Do not write visible citation syntax" and simply generates a narrative summary of tool outputs. There is no constraint forcing it to quote verbatim from the actual JSON result. This allows it to:

  • Invent numbers that weren't in the tool result
  • Rephrase results in ways that change their meaning
  • Report a result as successful when it was null/empty

Root causes​

Root causeEffect
Stopword list has no code-specific tokensnull, returns, error, true, false match every file
Threshold 0.18 = 1 token match is enoughAlmost every sentence gets cited
Pure lexical, no semantic weightingDomain-specific terms not valued higher
No source-type affinityA sentence about RSI cites a scheduler file
No tool faithfulness instructionModel invents or paraphrases tool numbers

Phase 1 — Fix lexical grounding (implemented)​

Changes to packages/semantic-search/src/index.ts:

  1. Expand stopwords to include ~50 common code tokens that appear in every TypeScript/JavaScript file: null, undefined, return, const, let, true, false, value, result, function, error, check, type, object, string, number, boolean, array, etc.

  2. Domain token boosting — a curated set of trading/platform-specific terms (rsi, ema, atr, macd, drawdown, equity, backtest, candle, bullish, bearish, oversold, overbought, quickjs, …) count 3× in the overlap score. This means a sentence mentioning "RSI below 40" will strongly prefer RSI-related sources over generic code.

  3. Raise threshold from 0.18 → 0.35 weighted overlap, AND require at least 2 domain-or-meaningful tokens to match (not just any 1 token).

  4. Min sentence guard — skip grounding for lines shorter than 40 chars or with fewer than 4 meaningful tokens (headings, short status messages).

  5. Source-type affinity — add a source type score bonus: strategy sources score higher when the sentence contains trading domain terms; tool result sources score higher when the sentence contains metric numbers.

Changes to services/backend/src/routes/bots/bots-chat-handlers.ts:

  1. Tool faithfulness system instruction — add to the system prompt: "When reporting tool results: quote the exact numbers and status returned. If a field was null, say 'not available'. Never round, estimate, or invent values. If the result was empty, say so explicitly."

Phase 2 — Inline citation markers (next sprint)​

Change the system prompt to have the model mark its own factual sources:

After any sentence that reports a specific fact drawn from a tool result,
backtest output, or strategy code — append [§tool], [§strategy], or [§code]
immediately after the period, before any newline.

Examples:
"The win rate was 42.3%. [§tool]"
"Q.rsi() returns null when history is shorter than period+1. [§code]"
"The max drawdown reached 8.7% over the test window. [§tool]"

Do not add a marker for opinion, explanation, or general knowledge sentences.

Post-processing then:

  • Extracts [§tool] markers → looks up the most relevant tool_result source
  • Extracts [§strategy] → uses the strategy code windows
  • Extracts [§code] → uses repository corpus windows
  • Falls back to Phase 1 lexical matching for un-marked sentences

This eliminates the dependency on accidental keyword overlap for AI-generated factual claims — the model explicitly signals which knowledge base it drew from.


Phase 3 — Semantic reranking (future)​

When budget allows, replace the lexical overlap with a cross-encoder call for the top candidates:

  • Use Cohere Rerank (one REST call, ~100ms, free tier available) to score each candidate source against the full sentence as a query.
  • Only apply to sentences that already passed the Phase 1 threshold (avoid re-ranking noise from weak candidates).
  • Cohere Rerank achieves +15% Precision@5 vs lexical baseline in published benchmarks (BEIR, MS-MARCO) with no training required.

Infrastructure needed: COHERE_API_KEY secret in Cloudflare Workers.


Measuring improvement​

Use these signals to track quality over time:

  1. Click-through rate on source tooltips — are users hovering/clicking? (implies the source is relevant enough to inspect)
  2. "Report wrong source" feedback — add a thumbs-down on the citation chip
  3. ALCE offline eval — given a set of known (sentence, ground-truth-source) pairs, measure Precision@1 before and after

Current baseline: ~18% threshold, no domain weighting → estimated Precision@1 of 25–35% (most citations are false positives on generic code tokens).

Phase 1 target: Precision@1 > 60% by eliminating the trivial false positives. Phase 2 target: Precision@1 > 80% by anchoring on model-marked claim types. Phase 3 target: Precision@1 > 90% with cross-encoder reranking.