From b32d5fd86b84aa8544c1afb56906a4f0ea4efba2 Mon Sep 17 00:00:00 2001 From: buua436 Date: Fri, 17 Jul 2026 14:28:20 +0800 Subject: [PATCH] fix: render readable wiki artifact links (#17011) --- rag/advanced_rag/knowlege_compile/wiki.py | 62 +++++++++++++++++++---- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/rag/advanced_rag/knowlege_compile/wiki.py b/rag/advanced_rag/knowlege_compile/wiki.py index 8522629e07..3e4da5a273 100644 --- a/rag/advanced_rag/knowlege_compile/wiki.py +++ b/rag/advanced_rag/knowlege_compile/wiki.py @@ -38,6 +38,7 @@ import json import logging import re from typing import Callable, Optional +from urllib.parse import urlsplit from common.misc_utils import thread_pool_exec from common.token_utils import num_tokens_from_string from rag.prompts.generator import gen_json, message_fit_in @@ -2841,19 +2842,20 @@ async def _wiki_build_source_context( _WIKILINK_PIPE_RE = re.compile(r"\[\[([^\[\]\|]+?)\|([^\[\]]+?)\]\]") _WIKILINK_SIMPLE_RE = re.compile(r"\[\[([^\[\]\|]+?)\]\]") +_ARTIFACT_MARKDOWN_LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)") -def _wiki_transform_links(content_md: str, kb_id: str) -> tuple[str, list[str]]: - """Rewrite ``[[slug]]`` / ``[[slug|display]]`` wikilinks to standard - markdown links whose href encodes ``(kb_id, slug)`` so a renderer can - fetch the target page from ES. +def _wiki_transform_links(content_md: str, kb_id: str, page_titles: dict[str, str] | None = None) -> tuple[str, list[str]]: + """Normalize wiki links and return ``(rendered_md, unique_outlinks)``. - Returns ``(rewritten_md, unique_outlinks)`` — outlinks are slug strings - in first-seen order. The href format is ``artifact/{kb_id}/{slug}`` which is - relative; clients are expected to map this to whatever route serves the - page (e.g. ``/api/v1/artifact/{kb_id}/{slug}``). + Both the canonical ``[[slug]]`` form and Markdown links emitted by an LLM + are accepted. Artifact links are rewritten to the relative + ``artifact/{kb_id}/{slug}`` form. When an artifact link uses its slug as + the label, prefer the planned page title; otherwise derive readable text + from the slug. """ kb_id_str = str(kb_id) + page_titles = page_titles or {} seen: set[str] = set() outlinks: list[str] = [] @@ -2863,6 +2865,38 @@ def _wiki_transform_links(content_md: str, kb_id: str) -> tuple[str, list[str]]: seen.add(s) outlinks.append(s) + def _display_text(label: str, slug: str) -> str: + label = label.strip() + if label not in {slug, slug.rsplit("/", 1)[-1]}: + return label + planned_title = page_titles.get(slug) + if planned_title: + return planned_title + readable = slug.rsplit("/", 1)[-1].replace("-", " ").replace("_", " ").strip() + return readable.title() or label + + def _artifact_slug(href: str) -> str | None: + parsed = urlsplit(href) + if parsed.scheme or parsed.netloc: + if parsed.netloc != "artifact": + return None + path = parsed.path + else: + path = parsed.path + parts = path.strip("/").split("/") + if parts and parts[0] == "artifact": + parts = parts[1:] + if len(parts) < 2 or parts[0] != kb_id_str: + return None + return "/".join(parts[1:]) + + def _markdown_artifact(m: re.Match) -> str: + slug = _artifact_slug(m.group(2)) + if not slug: + return m.group(0) + _track(slug) + return f"[{_display_text(m.group(1), slug)}](artifact/{kb_id_str}/{slug})" + def _piped(m: re.Match) -> str: slug = m.group(1).strip() text = m.group(2).strip() @@ -2872,9 +2906,10 @@ def _wiki_transform_links(content_md: str, kb_id: str) -> tuple[str, list[str]]: def _simple(m: re.Match) -> str: slug = m.group(1).strip() _track(slug) - return f"[{slug}](artifact/{kb_id_str}/{slug})" + return f"[{_display_text(slug, slug)}](artifact/{kb_id_str}/{slug})" - rewritten = _WIKILINK_PIPE_RE.sub(_piped, content_md or "") + rewritten = _ARTIFACT_MARKDOWN_LINK_RE.sub(_markdown_artifact, content_md or "") + rewritten = _WIKILINK_PIPE_RE.sub(_piped, rewritten) rewritten = _WIKILINK_SIMPLE_RE.sub(_simple, rewritten) return rewritten, outlinks @@ -3362,6 +3397,11 @@ async def wiki_refine_from_plan( all_claims = plan.get("_claims") or [] # ``all_plan_slugs`` is implicitly deduped now (pages_spec is unique). all_plan_slugs = [p["slug"] for p in pages_spec] + page_titles = { + str(p["slug"]): str(p.get("title") or "").strip() + for p in pages_spec + if p.get("slug") and str(p.get("title") or "").strip() + } # Build canonical entity/concept lookups for evidence fallback. When MAP # produced no claims (a real failure mode we've seen on Chinese / dense @@ -3483,7 +3523,7 @@ async def wiki_refine_from_plan( ) # Render artifactlinks once, here, after all LLM transforms. - content_md_rendered, outlinks = _wiki_transform_links(content_md_raw, kb_id) + content_md_rendered, outlinks = _wiki_transform_links(content_md_raw, kb_id, page_titles=page_titles) source_doc_ids = await _wiki_collect_doc_ids(source_chunk_ids, tenant_id, kb_id) summary = _wiki_extract_summary(content_md_rendered) or title