Skip to content

Commit f87352e

Browse files
answering: dedupe sources by URL and add breadcrumb titles for course docs
- resolve_sources collapsed duplicates by chunk id, so multiple cited chunks of the same page (same URL) appeared as repeated source lines. Dedupe by URL. - Course doc sources now render as a breadcrumb (Courses > LLM Zoomcamp > Project) so the reader can place the page in the course nav.
1 parent da82b6e commit f87352e

2 files changed

Lines changed: 42 additions & 3 deletions

File tree

src/faq_assistant/answering.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -269,20 +269,38 @@ def resolve_sources(config, rag_answer: RagAnswer, results: list[SearchResult])
269269
sources: list[dict] = []
270270
for source_id in rag_answer.source_ids:
271271
result = by_id.get(source_id)
272-
if result is None or result.id in seen:
272+
if result is None:
273273
continue
274-
seen.add(result.id)
274+
# Collapse multiple cited chunks of the same page/entry: they share a URL,
275+
# so without this the same doc shows up several times in the source list.
276+
dedup_key = result.url or result.id
277+
if dedup_key in seen:
278+
continue
279+
seen.add(dedup_key)
275280
sources.append(
276281
{
277282
"id": result.id,
278283
"source": SOURCE_LABELS.get(result.source_type, result.source_type),
279-
"title": result.title,
284+
"title": _source_title(config, result),
280285
"url": result.url,
281286
}
282287
)
283288
return sources[: int(config["answering"]["max_sources"])]
284289

285290

291+
def _source_title(config, result: SearchResult) -> str:
292+
"""Display title for a cited source.
293+
294+
Course doc pages get a breadcrumb ("Courses > LLM Zoomcamp > Project") so the
295+
reader can place the page in the course nav; other sources keep their own title.
296+
"""
297+
if result.source_type == "course_docs":
298+
course_name = config["courses"].get(result.course, {}).get("name") or result.course
299+
parts = [part for part in ("Courses", course_name, result.title) if part]
300+
return " > ".join(parts)
301+
return result.title
302+
303+
286304
def build_context(results: list[SearchResult]) -> str:
287305
lines: list[str] = []
288306
for position, result in enumerate(results, start=1):

tests/test_answering.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,27 @@ def test_resolve_sources_maps_labels_and_dedupes(cfg):
5353
]
5454

5555

56+
def test_resolve_sources_collapses_chunks_sharing_a_url(cfg):
57+
# Two different chunk ids of the same page share one URL -> one source line.
58+
results = [
59+
SearchResult(id="cd:1", source_type="course_docs", course="llm-zoomcamp", title="Project", url="u"),
60+
SearchResult(id="cd:2", source_type="course_docs", course="llm-zoomcamp", title="Project", url="u"),
61+
]
62+
rag = RagAnswer(answer="a", found_answer=True, source_ids=["cd:1", "cd:2"])
63+
out = resolve_sources(cfg, rag, results)
64+
assert len(out) == 1
65+
assert out[0]["url"] == "u"
66+
67+
68+
def test_resolve_sources_course_docs_get_breadcrumb_title(cfg):
69+
results = [
70+
SearchResult(id="cd:1", source_type="course_docs", course="llm-zoomcamp", title="Project", url="u3"),
71+
]
72+
rag = RagAnswer(answer="a", found_answer=True, source_ids=["cd:1"])
73+
out = resolve_sources(cfg, rag, results)
74+
assert out[0]["title"] == "Courses > LLM Zoomcamp > Project"
75+
76+
5677
def test_resolve_sources_empty_when_not_found(cfg):
5778
rag = RagAnswer(answer="no", found_answer=False, source_ids=["faq:1"])
5879
assert resolve_sources(cfg, rag, _results()) == []

0 commit comments

Comments
 (0)