diff --git a/CHANGELOG.md b/CHANGELOG.md index b7d81603..0a736c4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog ## [Unreleased] +### Fixed + +- **Citation formatting**: Replace raw UUIDs (`[doc_id:chunk_id]`) with human-readable identifiers (`[index] title`) in `format_citations()` output, preventing LLMs from hallucinating opaque ID markers in answers + ## [0.36.2] - 2026-03-28 ### Fixed diff --git a/haiku_rag_slim/haiku/rag/utils.py b/haiku_rag_slim/haiku/rag/utils.py index 43f82f05..c7d554ec 100644 --- a/haiku_rag_slim/haiku/rag/utils.py +++ b/haiku_rag_slim/haiku/rag/utils.py @@ -338,9 +338,10 @@ def format_citations(citations: "list[Citation]") -> str: lines = ["## Citations\n"] - for c in citations: - # Header line - header = f"[{c.document_id}:{c.chunk_id}]" + for i, c in enumerate(citations): + idx = c.index if c.index is not None else (i + 1) + title = c.document_title or c.document_uri + header = f"[{idx}] {title}" # Location info location_parts = [] @@ -353,8 +354,6 @@ def format_citations(citations: "list[Citation]") -> str: location_parts.append(f"Section: {c.headings[-1]}") source = c.document_uri - if c.document_title: - source = f"{c.document_title} ({c.document_uri})" if location_parts: source += f" - {', '.join(location_parts)}" diff --git a/tests/test_utils.py b/tests/test_utils.py index fc9d52d9..f598163a 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -475,8 +475,10 @@ def test_format_citations_with_citation(): headings=["Intro"], ) result = format_citations([citation]) - assert "[doc1:chunk1]" in result - assert "Test Doc" in result + assert "[1] Test Doc" in result + assert "doc1" not in result + assert "chunk1" not in result + assert "test://doc" in result assert "p. 1" in result assert "Section: Intro" in result assert "Some content" in result @@ -494,6 +496,7 @@ def test_format_citations_multiple_pages(): page_numbers=[1, 2, 3], ) result = format_citations([citation]) + assert "[1] test://doc" in result assert "pp. 1-3" in result @@ -508,7 +511,49 @@ def test_format_citations_no_title(): content="Content", ) result = format_citations([citation]) - assert "test://doc" in result + assert "[1] test://doc" in result + assert "doc1" not in result + + +def test_format_citations_with_index(): + from haiku.rag.agents.research.models import Citation + from haiku.rag.utils import format_citations + + citation = Citation( + index=5, + document_id="doc1", + chunk_id="chunk1", + document_uri="test://doc", + document_title="Test Doc", + content="Content", + ) + result = format_citations([citation]) + assert "[5] Test Doc" in result + + +def test_format_citations_sequential_indices(): + from haiku.rag.agents.research.models import Citation + from haiku.rag.utils import format_citations + + citations = [ + Citation( + document_id="doc1", + chunk_id="chunk1", + document_uri="test://doc1", + document_title="First", + content="Content 1", + ), + Citation( + document_id="doc2", + chunk_id="chunk2", + document_uri="test://doc2", + document_title="Second", + content="Content 2", + ), + ] + result = format_citations(citations) + assert "[1] First" in result + assert "[2] Second" in result # --- format_citations_rich tests ---