Skip to content

Commit fd67a93

Browse files
Alex-Hunterzclaude
andcommitted
fix chunk overlap and source quote hallucination guard
chunk overlap: carry the last 2 messages (user-assistant exchange pair) instead of just the last message. A single "yeah" carried without its preceding question gives zero context; the pair lets the model resolve short confirmations correctly across chunk boundaries. source quote guard: replace exact substring check with word-overlap (words >3 chars). Catches minor paraphrases ("I use" vs "we use") that would pass as hallucinations under exact matching, while still rejecting fabricated quotes with no word overlap to the actual conversation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2b5a4b1 commit fd67a93

1 file changed

Lines changed: 17 additions & 10 deletions

File tree

vektori/ingestion/extractor.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -631,16 +631,19 @@ def _chunk_messages(self, messages: list[dict[str, str]]) -> list[list[dict[str,
631631
current: list[dict[str, str]] = []
632632
current_chars = 0
633633

634+
def _msg_chars(m: dict[str, str]) -> int:
635+
return len(m.get("role", "")) + len(m.get("content", "")) + 10
636+
634637
for msg in messages:
635-
# +10 for "ROLE: \n" overhead
636-
msg_chars = len(msg.get("role", "")) + len(msg.get("content", "")) + 10
638+
msg_chars = _msg_chars(msg)
637639
if current and current_chars + msg_chars > self._max_chunk_chars:
638640
chunks.append(current)
639-
overlap = current[-1]
640-
current = [overlap, msg]
641-
current_chars = (
642-
len(overlap.get("role", "")) + len(overlap.get("content", "")) + 10 + msg_chars
643-
)
641+
# Carry the last user-assistant exchange pair (≥2 messages) so the
642+
# next chunk has enough context to resolve short confirmations like
643+
# "yeah" or "exactly" against the question that prompted them.
644+
overlap = current[-2:] if len(current) >= 2 else current[-1:]
645+
current = overlap + [msg]
646+
current_chars = sum(_msg_chars(m) for m in current)
644647
else:
645648
current.append(msg)
646649
current_chars += msg_chars
@@ -1126,9 +1129,13 @@ async def _link_to_source_sentences(
11261129
unmatched: list[str] = []
11271130

11281131
for quote in source_quotes:
1129-
# Step 1: guard against hallucinated quotes (skip for trusted cached quotes)
1130-
if conversation is not None and quote.lower() not in conversation.lower():
1131-
continue
1132+
# Step 1: guard against hallucinated quotes (skip for trusted cached quotes).
1133+
# Use word-overlap rather than exact substring to accept minor paraphrases
1134+
# ("I use Postgres" vs "we use Postgres") while still rejecting fabrications.
1135+
if conversation is not None:
1136+
quote_words = {w for w in quote.lower().split() if len(w) > 3}
1137+
if quote_words and not any(w in conversation.lower() for w in quote_words):
1138+
continue
11321139

11331140
# Step 2: exact substring match
11341141
exact = await self.db.find_sentence_containing(session_id, quote)

0 commit comments

Comments
 (0)