From 34bbeb18065cac10022c49959c753d4c43965bca Mon Sep 17 00:00:00 2001 From: Wenchao An Date: Sat, 19 Sep 2026 07:44:05 +0800 Subject: [PATCH] feat(knowledge): add verifiable RAGFlow source citations (#5551) * feat(knowledge): add verifiable RAGFlow source citations * docs(knowledge): scope RAGFlow guidance to its own directory * fix(knowledge): preserve citations through rendering and budgets --- README.md | 16 ++ .../tool_output_budget_middleware.py | 19 ++- .../deerflow/community/ragflow/AGENTS.md | 20 +++ .../deerflow/community/ragflow/formatting.py | 77 ++++++++- .../deerflow/community/ragflow/sources.py | 101 ++++++++++++ .../deerflow/community/ragflow/tools.py | 27 +++- .../packages/harness/deerflow/tools/AGENTS.md | 6 + .../deerflow/tools/builtins/task_tool.py | 8 + backend/tests/test_agent_guidance_check.py | 1 + .../tests/test_knowledge_citation_budget.py | 127 +++++++++++++++ backend/tests/test_ragflow_tools.py | 95 +++++++++++ .../knowledge-citations-desktop.png | Bin 0 -> 72771 bytes .../knowledge-citations-mobile.png | Bin 0 -> 46185 bytes frontend/src/AGENTS.md | 12 ++ .../workspace/citations/artifact-link.tsx | 6 +- .../workspace/citations/citation-link.tsx | 8 + .../workspace/citations/knowledge-source.tsx | 153 ++++++++++++++++++ .../workspace/messages/markdown-link.tsx | 5 + .../workspace/messages/message-list-item.tsx | 2 + .../workspace/messages/message-list.tsx | 5 +- frontend/src/core/citations/sources.ts | 4 +- frontend/src/core/i18n/locales/en-US.ts | 8 + frontend/src/core/i18n/locales/types.ts | 6 + frontend/src/core/i18n/locales/zh-CN.ts | 7 + frontend/src/core/knowledge/sources.ts | 107 ++++++++++++ .../tests/e2e/knowledge-citations.spec.ts | 106 ++++++++++++ .../citations/knowledge-source.dom.test.tsx | 130 +++++++++++++++ .../tests/unit/core/knowledge/sources.test.ts | 100 ++++++++++++ 28 files changed, 1142 insertions(+), 14 deletions(-) create mode 100644 backend/packages/harness/deerflow/community/ragflow/AGENTS.md create mode 100644 backend/packages/harness/deerflow/community/ragflow/sources.py create mode 100644 backend/tests/test_knowledge_citation_budget.py create mode 100644 docs/pr-evidence/knowledge-citations-desktop.png create mode 100644 docs/pr-evidence/knowledge-citations-mobile.png create mode 100644 frontend/src/components/workspace/citations/knowledge-source.tsx create mode 100644 frontend/src/core/knowledge/sources.ts create mode 100644 frontend/tests/e2e/knowledge-citations.spec.ts create mode 100644 frontend/tests/unit/components/workspace/citations/knowledge-source.dom.test.tsx create mode 100644 frontend/tests/unit/core/knowledge/sources.test.ts diff --git a/README.md b/README.md index 6a8db16d7..6fef6111c 100644 --- a/README.md +++ b/README.md @@ -1097,6 +1097,22 @@ under `web_fetch` or use `TAVILY_API_KEY` for both. ### Private Knowledge Retrieval (RAGFlow) +Answers can cite retrieved RAGFlow evidence with clickable knowledge citations. +Click a citation, or an entry in the answer's knowledge sources list, to see the +original retrieved excerpt, dataset and document names, and page numbers when +RAGFlow supplies them. These are retrieval-time snapshots retained with the +conversation, including sources forwarded by ordinary `task` subagents; they +remain inspectable after reloading the conversation. An excerpt is not a live +copy of the full document: changes in RAGFlow do not rewrite past evidence. +Missing source records are shown as unavailable rather than turned into guessed +links. Source snapshots do not add a knowledge-management page or expose the +RAGFlow API key. Durable batch exports and standalone Markdown files do not +carry these interactive conversation source records. +Ordinary document-title links in a Sources section open the same evidence as +inline citations. When a tool-output budget applies, only complete evidence +entries that fit remain citable; omitted sources are reported rather than +retaining a source record for a cut-off excerpt. + DeerFlow can optionally connect to a tenant-scoped RAGFlow deployment. The `knowledge_search` Agent tool resolves the configured dataset scope, groups datasets by embedding model, and retrieves those groups in parallel so mixed diff --git a/backend/packages/harness/deerflow/agents/middlewares/tool_output_budget_middleware.py b/backend/packages/harness/deerflow/agents/middlewares/tool_output_budget_middleware.py index 05e44225c..54825cc81 100644 --- a/backend/packages/harness/deerflow/agents/middlewares/tool_output_budget_middleware.py +++ b/backend/packages/harness/deerflow/agents/middlewares/tool_output_budget_middleware.py @@ -44,6 +44,7 @@ from deerflow.agents.middlewares.tool_call_args import ToolCallOccurrence, pair_ from deerflow.agents.middlewares.tool_output_synopsis import render_tool_output_preview from deerflow.agents.middlewares.tool_result_meta import TOOL_META_KEY from deerflow.agents.middlewares.tool_transform_meta import append_tool_transform +from deerflow.community.ragflow.sources import budget_source_artifact from deerflow.config.tool_output_config import ToolOutputConfig from deerflow.sandbox.sandbox_provider import get_sandbox_provider @@ -480,14 +481,24 @@ def _patch_tool_message( config=config, sandbox=sandbox, ) - if budgeted is None: + update: dict[str, Any] = {} + trigger = _effective_trigger(tool_name, config) + citation_result = None + if tool_name in {"knowledge_search", "task"} and trigger > 0 and len(text) > trigger: + citation_result = budget_source_artifact(text, msg.artifact, trigger, summary=(budgeted[0] if budgeted else text) if tool_name == "task" else "") + if citation_result is not None: + replacement, update["artifact"] = citation_result + transform_kind = "truncated" + elif budgeted is not None: + replacement, transform_kind = budgeted + else: return msg - replacement, transform_kind = budgeted - - update: dict[str, Any] = {"content": replacement} + update["content"] = replacement if getattr(msg, "response_metadata", None): update["response_metadata"] = dict(msg.response_metadata) new_kwargs = dict(getattr(msg, "additional_kwargs", None) or {}) + if citation_result is not None and budgeted is not None and budgeted[1] == "externalized": + append_tool_transform(new_kwargs, "externalized", by="ToolOutputBudgetMiddleware") append_tool_transform(new_kwargs, transform_kind, by="ToolOutputBudgetMiddleware") update["additional_kwargs"] = new_kwargs return msg.model_copy(update=update) diff --git a/backend/packages/harness/deerflow/community/ragflow/AGENTS.md b/backend/packages/harness/deerflow/community/ragflow/AGENTS.md new file mode 100644 index 000000000..21ffac0f8 --- /dev/null +++ b/backend/packages/harness/deerflow/community/ragflow/AGENTS.md @@ -0,0 +1,20 @@ +# RAGFlow citation snapshots + +`knowledge_search_tool` returns native `content_and_artifact`: model-visible +`[citation:N](#knowledge-)` links resolve to bounded +`artifact.knowledge_sources` version-one evidence snapshots. Provider locators +stay in the artifact; source names and text are credential-redacted in both +representations. IDs are unique per retrieval, never per-message ordinal IDs. +Only actual emitted entries get source records. Retain the exact excerpt sent +to the model and mark truncation; do not fetch a fresh chunk and present it as +historical evidence. Direct `knowledge_search()` callers retain its string API. +`sources.py` forwards only captured sources cited by ordinary subagent results, +with count/text budgets. The source dialog uses stored thread messages and +introduces no unauthenticated document proxy. Durable batch result storage and +standalone Markdown do not include native source artifacts. + +Output budgeting retains complete evidence entries and their source records +together, including delegated results and model-request history. Never shorten +an excerpt under an existing ID. Drop entries that cannot fit, with an omission +notice, while preserving unrelated artifact fields. This honors per-tool and +fallback limits without exempting citation-bearing results from the budget. diff --git a/backend/packages/harness/deerflow/community/ragflow/formatting.py b/backend/packages/harness/deerflow/community/ragflow/formatting.py index 7a179273a..ce2bf2061 100644 --- a/backend/packages/harness/deerflow/community/ragflow/formatting.py +++ b/backend/packages/harness/deerflow/community/ragflow/formatting.py @@ -2,7 +2,7 @@ from __future__ import annotations -from collections.abc import Mapping +from collections.abc import Callable, Mapping from typing import Any @@ -94,3 +94,78 @@ def format_retrieval_result( return truncation_marker[:max_total_chars] prefix_length = max_total_chars - len(truncation_marker) return f"{formatted[:prefix_length].rstrip()}{truncation_marker}" + + +def format_retrieval_sources( + result: Mapping[str, Any], + *, + dataset_names_by_id: Mapping[str, str], + max_chars_per_chunk: int = 800, + max_total_chars: int = 8000, + redact: Callable[[object], str] = str, +) -> tuple[str, dict[str, Any] | None]: + """Pair model-visible citations with bounded, immutable retrieval snapshots. + + Citation identifiers are independent of provider IDs and unique per call. + Only entries actually included in the text receive a source record; the + artifact retains the same excerpt the model saw, never an unbounded payload. + """ + from uuid import uuid4 + + chunks = result.get("chunks") + if not isinstance(chunks, list): + return "No relevant content found.", None + call_id = uuid4().hex + aggregates = _document_aggregates(result.get("doc_aggs")) + names = {str(item["doc_id"]): str(item["doc_name"]) for item in aggregates if item.get("doc_id") and item.get("doc_name")} + entries: list[str] = [] + sources: list[dict[str, Any]] = [] + remaining = max_total_chars + for chunk in chunks[:100]: + if not isinstance(chunk, Mapping): + continue + dataset_id = chunk.get("dataset_id") + document_id = chunk.get("document_id") + chunk_id = chunk.get("id") + # Incomplete or out-of-scope locators must not become verified sources. + if not all(isinstance(value, str) and value and len(value) <= 256 for value in (dataset_id, document_id, chunk_id)): + continue + if dataset_id not in dataset_names_by_id: + continue + source_id = f"{call_id}-{len(sources) + 1}" + dataset_name = redact(dataset_names_by_id[dataset_id])[:512] + document_name = redact(str(chunk.get("document_keyword") or names.get(document_id) or "Unknown document"))[:512] + # Keep untrusted names outside the Markdown label to avoid link injection. + header = f"[citation:{len(sources) + 1}](#knowledge-{source_id}) {dataset_name} / {document_name}\n" + text = redact(str(chunk.get("content") or "").strip()) + allowance = min(max_chars_per_chunk, remaining - len(header) - (2 if entries else 0)) + if allowance < 1: + break + excerpt = _truncate(text, allowance) + entry = header + excerpt + entries.append(entry) + remaining -= len(entry) + (2 if len(entries) > 1 else 0) + positions = chunk.get("positions") + pages = ( + sorted({position[0] for position in positions[:100] if isinstance(position, (list, tuple)) and position and isinstance(position[0], int) and not isinstance(position[0], bool) and 1 <= position[0] <= 1_000_000}) + if isinstance(positions, list) + else [] + ) + sources.append( + { + "id": source_id, + "provider": "ragflow", + "dataset_id": redact(dataset_id), + "document_id": redact(document_id), + "chunk_id": redact(chunk_id), + "dataset_name": dataset_name, + "document_name": document_name, + "text": excerpt, + "truncated": len(excerpt) < len(text), + "pages": pages, + } + ) + if not sources: + # Legacy/incomplete provider responses still yield useful readable text. + return redact(format_retrieval_result(result, dataset_names_by_id=dataset_names_by_id, max_chars_per_chunk=max_chars_per_chunk, max_total_chars=max_total_chars)), None + return "\n\n".join(entries), {"knowledge_sources": {"version": 1, "sources": sources}} diff --git a/backend/packages/harness/deerflow/community/ragflow/sources.py b/backend/packages/harness/deerflow/community/ragflow/sources.py new file mode 100644 index 000000000..5fa5a4cda --- /dev/null +++ b/backend/packages/harness/deerflow/community/ragflow/sources.py @@ -0,0 +1,101 @@ +"""Bounded source-artifact forwarding across ordinary subagent results.""" + +import re +from collections.abc import Mapping +from typing import Any + + +def budget_source_artifact(content: str, artifact: object, max_chars: int, *, summary: str = "") -> tuple[str, dict[str, Any] | None] | None: + """Keep whole evidence records and their links together within a tool budget. + + Never shorten an excerpt under its existing source ID: those IDs also occur + in persisted child messages. Oversized records are omitted atomically, and + unrelated artifact fields survive. A task result can retain a short synopsis + before the evidence; its old knowledge links are replaced by retained ones. + """ + if not isinstance(artifact, dict) or max_chars <= 0: + return None + payload = artifact.get("knowledge_sources") + if not isinstance(payload, dict) or payload.get("version") != 1 or not isinstance(payload.get("sources"), list): + return None + sources = [] + seen = set() + for source in payload["sources"][:100]: + if not isinstance(source, dict): + continue + source_id = source.get("id") + if not isinstance(source_id, str) or not re.fullmatch(r"[a-f0-9]{32}-[1-9][0-9]{0,2}", source_id): + continue + if source_id in seen or f"](#knowledge-{source_id})" not in content: + continue + if source.get("provider") != "ragflow" or not all(isinstance(source.get(field), str) for field in ("text", "dataset_name", "document_name")): + continue + seen.add(source_id) + sources.append(source) + if not sources: + return None + + notice = "Knowledge sources omitted to fit output budget; request smaller excerpts." + # Remove old destinations before shortening the report, including any link + # fragments left by the generic synopsis/truncation transform. + summary = re.sub(r"\[([^\]\n]*)\]\(#(?:user-content-)?knowledge-[^)\s]*\)", r"\1", summary) + summary = re.sub(r"#(?:user-content-)?knowledge-[\w-]*", "", summary) + summary_limit = min(1000, max_chars // 4) if max_chars >= 160 else 0 + if not summary_limit: + summary = "" + elif len(summary) > summary_limit: + # Keep the synopsis tail too: it can contain the read_file reference + # for the full externalized task report. + head = (summary_limit - 3) // 2 + summary = summary[:head] + "\n…\n" + summary[-(summary_limit - head - 3) :] + summary = summary.rstrip() + entries = [summary] if summary else [] + used = len(summary) + retained = [] + for source in sources: + entry = f"[citation:{len(retained) + 1}](#knowledge-{source['id']}) {source['dataset_name']} / {source['document_name']}\n{source['text']}" + cost = len(entry) + (2 if entries else 0) + # Reserve a complete omission notice; never emit a partial source link. + if used + cost + len(notice) + 2 > max_chars: + continue + entries.append(entry) + used += cost + retained.append(source) + if len(retained) != len(sources): + entries.append(notice[:max_chars]) + updated = dict(artifact) + if retained: + updated["knowledge_sources"] = {**payload, "sources": retained} + else: + updated.pop("knowledge_sources", None) + return "\n\n".join(entries), updated or None + + +def cited_source_artifact(messages: list[dict[str, Any]], content: str) -> dict[str, Any] | None: + """Carry only actual captured sources cited in the child's final result.""" + sources: dict[str, dict[str, Any]] = {} + remaining = 1_000_000 + for message in messages: + if message.get("type") != "tool" or message.get("name") not in {"knowledge_search", "task"}: + continue + artifact = message.get("artifact") + payload = artifact.get("knowledge_sources") if isinstance(artifact, Mapping) else None + if not isinstance(payload, Mapping) or payload.get("version") != 1: + continue + raw_sources = payload.get("sources") + if not isinstance(raw_sources, list): + continue + for source in raw_sources[:100]: + if not isinstance(source, dict): + continue + source_id = source.get("id") + text = source.get("text") + if not isinstance(source_id, str) or not isinstance(text, str) or f"](#knowledge-{source_id})" not in content: + continue + if source_id in sources: + continue + if len(sources) >= 100 or len(text) > remaining: + continue + sources[source_id] = dict(source) + remaining -= len(text) + return {"knowledge_sources": {"version": 1, "sources": list(sources.values())}} if sources else None diff --git a/backend/packages/harness/deerflow/community/ragflow/tools.py b/backend/packages/harness/deerflow/community/ragflow/tools.py index 31e6bf76f..2836472b2 100644 --- a/backend/packages/harness/deerflow/community/ragflow/tools.py +++ b/backend/packages/harness/deerflow/community/ragflow/tools.py @@ -21,7 +21,7 @@ from deerflow.knowledge_scope import ( from deerflow.tools.types import Runtime from .client import RAGFlowAPIError, RAGFlowClient, RAGFlowConnectionError, RAGFlowProtocolError -from .formatting import format_retrieval_result +from .formatting import format_retrieval_result, format_retrieval_sources logger = logging.getLogger(__name__) @@ -516,6 +516,7 @@ async def knowledge_search( *, knowledge_scope: object | None = None, runtime: Runtime | None = None, + _source_artifact: dict[str, Any] | None = None, ) -> str: """Search the configured RAGFlow scope, defaulting to every accessible dataset.""" query = query.strip() @@ -564,6 +565,17 @@ async def knowledge_search( result = await _retrieve_dataset_groups(client, settings, query, groups) names_by_id = {dataset.dataset_id: dataset.name for dataset in datasets} + if _source_artifact is not None: + content, artifact = format_retrieval_sources( + result, + dataset_names_by_id=names_by_id, + max_chars_per_chunk=settings.max_chars_per_chunk, + max_total_chars=settings.max_total_chars, + redact=lambda value: _redact_api_key(value, _api_key(settings)), + ) + if artifact is not None: + _source_artifact.update(artifact) + return content formatted = format_retrieval_result( result, dataset_names_by_id=names_by_id, @@ -600,21 +612,28 @@ async def list_knowledge_bases() -> str: def _tool_description() -> str: base = "Search the operator-approved RAGFlow datasets and return compact, citation-numbered source chunks." - return f"{base} If knowledge_search.datasets is omitted, all datasets accessible to the configured RAGFlow API key are searched. Dataset IDs are never shown to the model." + return ( + f"{base} If knowledge_search.datasets is omitted, all datasets accessible to the configured RAGFlow API key are searched. " + "Dataset IDs are never shown to the model. When citing results, copy the supplied [citation:N](#knowledge-...) links exactly; " + "do not invent or renumber source links." + ) -async def _knowledge_search_entrypoint(query: str, runtime: Runtime) -> str: +async def _knowledge_search_entrypoint(query: str, runtime: Runtime) -> tuple[str, dict[str, Any] | None]: """Search the configured RAGFlow datasets, or every accessible dataset by default. Args: query: Specific question or search terms to retrieve from the configured private documents. """ - return await knowledge_search(query, runtime=runtime) + artifact: dict[str, Any] = {} + content = await knowledge_search(query, runtime=runtime, _source_artifact=artifact) + return content, artifact or None knowledge_search_tool = StructuredTool.from_function( coroutine=_knowledge_search_entrypoint, name="knowledge_search", + response_format="content_and_artifact", description=_tool_description(), parse_docstring=True, ) diff --git a/backend/packages/harness/deerflow/tools/AGENTS.md b/backend/packages/harness/deerflow/tools/AGENTS.md index 7d8e822e8..2aa6281bf 100644 --- a/backend/packages/harness/deerflow/tools/AGENTS.md +++ b/backend/packages/harness/deerflow/tools/AGENTS.md @@ -54,3 +54,9 @@ E2B output sync records remote file versions and actual host file metadata in a - ACP results collect only `agent_message_chunk` text. Thought chunks remain internal and must not be concatenated into the tool result - Missing ACP executables now return an actionable error message instead of a raw `[Errno 2]` - Each ACP agent uses a per-thread workspace at `{base_dir}/users/{user_id}/threads/{thread_id}/acp-workspace/`. The workspace is accessible to the lead agent via the virtual path `/mnt/acp-workspace/` (read-only). In docker sandbox mode, the directory is volume-mounted into the container at `/mnt/acp-workspace` (read-only); in local sandbox mode, path translation is handled by `tools.py` + +Ordinary `task` results forward bounded `artifact.knowledge_sources` records +from captured child tool messages only when the final/partial report cites those +opaque source links. This preserves retrieval evidence across the delegation +boundary without placing provider IDs in model-visible text. Never reconstruct +source records from the child's prose or replace them with fresh provider reads. diff --git a/backend/packages/harness/deerflow/tools/builtins/task_tool.py b/backend/packages/harness/deerflow/tools/builtins/task_tool.py index be5990b75..d68b801cd 100644 --- a/backend/packages/harness/deerflow/tools/builtins/task_tool.py +++ b/backend/packages/harness/deerflow/tools/builtins/task_tool.py @@ -19,6 +19,7 @@ from langgraph.types import Command from deerflow.agents.middlewares.receipt_verification import verify_receipt_citations from deerflow.authz.principal import normalize_authz_attributes +from deerflow.community.ragflow.sources import cited_source_artifact from deerflow.config import get_app_config from deerflow.extensions import resolve_run_extensions from deerflow.knowledge_scope import KNOWLEDGE_SCOPE_RUNTIME_KEY, execution_scope @@ -616,6 +617,7 @@ def _task_result_command( model_name: str | None = None, usage: dict[str, int] | None = None, tool_receipts: list[dict] | None = None, + source_messages: list[dict] | None = None, receipt_verdict: dict | None = None, acceptance_verdict: dict | None = None, ) -> Command: @@ -631,6 +633,7 @@ def _task_result_command( content=content, tool_call_id=tool_call_id, name="task", + artifact=cited_source_artifact(source_messages or [], content), additional_kwargs=make_subagent_additional_kwargs( status, result=result, @@ -1089,6 +1092,7 @@ async def task_tool( model_name=effective_model, usage=usage, tool_receipts=receipts, + source_messages=getattr(result, "ai_messages", None), receipt_verdict=receipt_verdict, acceptance_verdict=acceptance_verdict, ) @@ -1117,6 +1121,7 @@ async def task_tool( model_name=effective_model, usage=usage, tool_receipts=getattr(result, "tool_receipts", None), + source_messages=getattr(result, "ai_messages", None), ) elif result.status == SubagentStatus.CANCELLED: _report_subagent_usage(runtime, result) @@ -1139,6 +1144,7 @@ async def task_tool( model_name=effective_model, usage=usage, tool_receipts=getattr(result, "tool_receipts", None), + source_messages=getattr(result, "ai_messages", None), ) elif result.status == SubagentStatus.TIMED_OUT: _report_subagent_usage(runtime, result) @@ -1161,6 +1167,7 @@ async def task_tool( model_name=effective_model, usage=usage, tool_receipts=getattr(result, "tool_receipts", None), + source_messages=getattr(result, "ai_messages", None), ) # Still running, wait before next poll @@ -1197,6 +1204,7 @@ async def task_tool( model_name=effective_model, usage=usage, tool_receipts=getattr(result, "tool_receipts", None), + source_messages=getattr(result, "ai_messages", None), ) except asyncio.CancelledError: # Signal the background subagent thread to stop cooperatively, then diff --git a/backend/tests/test_agent_guidance_check.py b/backend/tests/test_agent_guidance_check.py index 8dbc866a0..af1c07e4b 100644 --- a/backend/tests/test_agent_guidance_check.py +++ b/backend/tests/test_agent_guidance_check.py @@ -18,6 +18,7 @@ EXPECTED_GUIDANCE_PATHS = { "backend/packages/harness/deerflow/agents/AGENTS.md", "backend/packages/harness/deerflow/agents/middlewares/AGENTS.md", "backend/packages/harness/deerflow/agents/memory/AGENTS.md", + "backend/packages/harness/deerflow/community/ragflow/AGENTS.md", "backend/packages/harness/deerflow/community/tavily/AGENTS.md", "backend/packages/harness/deerflow/config/AGENTS.md", "backend/packages/harness/deerflow/extensions/AGENTS.md", diff --git a/backend/tests/test_knowledge_citation_budget.py b/backend/tests/test_knowledge_citation_budget.py new file mode 100644 index 000000000..685e145e0 --- /dev/null +++ b/backend/tests/test_knowledge_citation_budget.py @@ -0,0 +1,127 @@ +"""Citation records and model-visible evidence must stay paired after budgeting.""" + +from copy import deepcopy +from types import SimpleNamespace + +import pytest +from langchain_core.messages import ToolMessage +from langgraph.types import Command + +from deerflow.agents.middlewares.tool_output_budget_middleware import ToolOutputBudgetMiddleware, _patch_model_messages, _patch_result +from deerflow.community.ragflow.formatting import format_retrieval_sources +from deerflow.community.ragflow.sources import budget_source_artifact +from deerflow.config.tool_output_config import ToolOutputConfig + + +def message(name="knowledge_search"): + chunks = [{"id": f"chunk-{i}", "dataset_id": "kb", "document_id": "doc", "document_keyword": "Manual.pdf", "content": f"Evidence {i}: " + "x" * 4800} for i in range(8)] + content, artifact = format_retrieval_sources({"chunks": chunks}, dataset_names_by_id={"kb": "Engineering"}, max_chars_per_chunk=5000, max_total_chars=40000) + if name == "task": + links = " ".join(f"[Manual.pdf](#knowledge-{source['id']})" for source in artifact["knowledge_sources"]["sources"]) + content = "Task completed. Findings: " + links + "\n" + "report " * 6000 + artifact["other"] = "preserve me" + return ToolMessage(content=content, artifact=artifact, name=name, tool_call_id="call-1") + + +def assert_paired(original, result, limit): + assert len(result.content) <= limit + assert result.artifact["other"] == "preserve me" + sources = result.artifact.get("knowledge_sources", {}).get("sources", []) + assert sources + assert len(sources) < len(original.artifact["knowledge_sources"]["sources"]) + for source in original.artifact["knowledge_sources"]["sources"]: + if source in sources: + assert f"](#knowledge-{source['id']})" in result.content + assert source["text"] in result.content + else: + assert f"#knowledge-{source['id']}" not in result.content + assert "omitted" in result.content + + +@pytest.mark.parametrize("name", ["knowledge_search", "task"]) +@pytest.mark.parametrize("mode", ["externalize", "fallback", "override", "history", "no_storage"]) +def test_complete_citations_survive_budget_paths(tmp_path, name, mode): + original = message(name) + snapshot = deepcopy(original) + limit = 12000 if mode == "externalize" else 7000 + config = ToolOutputConfig(**({"fallback_max_chars": limit, "externalize_min_chars": 0} if mode in {"fallback", "history"} else {"tool_overrides": {name: limit}})) + if mode == "history": + result = _patch_model_messages([original], config)[0] + else: + command = Command(update={"messages": [original], "unrelated": 42}) + patched = _patch_result(command, config, str(tmp_path) if mode not in {"fallback", "no_storage"} else None) + assert patched.update["unrelated"] == 42 + result = patched.update["messages"][0] + assert_paired(original, result, limit) + assert original == snapshot + transforms = result.additional_kwargs["deerflow_tool_transforms"] + assert transforms[-1]["kind"] == "truncated" + if mode in {"externalize", "override"}: + assert transforms[-2]["kind"] == "externalized" + # A subsequent model hook must not rewrite the same evidence again. + assert _patch_model_messages([result], config) is None + + +@pytest.mark.parametrize("name", ["knowledge_search", "task"]) +@pytest.mark.parametrize("limit", [1, 60, 159, 160, 500]) +def test_tiny_budget_does_not_leave_orphaned_or_partial_citations(name, limit): + original = message(name) + config = ToolOutputConfig(externalize_min_chars=0, fallback_max_chars=limit) + result = _patch_result(original, config, None) + assert len(result.content) <= limit + assert "#knowledge-" not in result.content + assert "knowledge_sources" not in result.artifact + assert result.artifact["other"] == "preserve me" + + +@pytest.mark.parametrize("config", [ToolOutputConfig(enabled=False), ToolOutputConfig(exempt_tools=["knowledge_search"]), ToolOutputConfig(externalize_min_chars=0, fallback_max_chars=0)]) +def test_disabled_and_exempt_budget_preserves_sources(config): + original = message() + request = SimpleNamespace(tool_call={"name": "knowledge_search", "id": "call-1"}, runtime=SimpleNamespace(state={})) + result = ToolOutputBudgetMiddleware(config).wrap_tool_call(request, lambda _: original) + assert result is original + + +@pytest.mark.asyncio +async def test_async_tool_hook_preserves_sources(): + original = message() + request = SimpleNamespace(tool_call={"name": "knowledge_search", "id": "call-1"}, runtime=SimpleNamespace(state={})) + + async def handler(_): + return original + + result = await ToolOutputBudgetMiddleware(ToolOutputConfig(externalize_min_chars=0, fallback_max_chars=7000)).awrap_tool_call(request, handler) + assert_paired(original, result, 7000) + + +@pytest.mark.parametrize("change", ["other_tool", "unknown_version", "malformed_records", "no_artifact"]) +def test_unrelated_results_keep_the_generic_budget_behavior(change): + original = message() + if change == "other_tool": + original.name = "web_search" + elif change == "unknown_version": + original.artifact["knowledge_sources"]["version"] = 2 + elif change == "malformed_records": + original.artifact["knowledge_sources"]["sources"] = [None, {"id": "invalid", "text": 42}] + else: + original.artifact = None + config = ToolOutputConfig(externalize_min_chars=0, fallback_max_chars=7000) + control = _patch_result(original.model_copy(update={"artifact": None}), config, None) + result = _patch_result(original, config, None) + assert result.content == control.content + assert result.artifact == original.artifact + + +def test_small_source_result_is_not_rewritten(): + original = message() + config = ToolOutputConfig(externalize_min_chars=50000, fallback_max_chars=50000) + assert _patch_result(original, config, None) is original + + +def test_delegated_budget_retains_the_report_reference_and_complete_evidence(): + original = message("task") + summary = "Task completed. " + "synopsis " * 500 + "\nRead the full report: /mnt/user-data/outputs/.tool-results/report.txt" + content, artifact = budget_source_artifact(original.content, original.artifact, 7000, summary=summary) + assert content.startswith("Task completed.") + assert "/mnt/user-data/outputs/.tool-results/report.txt" in content + assert_paired(original, original.model_copy(update={"content": content, "artifact": artifact}), 7000) diff --git a/backend/tests/test_ragflow_tools.py b/backend/tests/test_ragflow_tools.py index a54777cb2..ff561d3be 100644 --- a/backend/tests/test_ragflow_tools.py +++ b/backend/tests/test_ragflow_tools.py @@ -1065,3 +1065,98 @@ def test_ragflow_package_has_explicit_init_file() -> None: package_dir = Path(ragflow_tools.__file__).resolve().parent assert (package_dir / "__init__.py").is_file() + + +@pytest.mark.asyncio +async def test_search_artifact_binds_citation_to_exact_retrieved_document(monkeypatch: pytest.MonkeyPatch) -> None: + client = FakeRAGFlowClient( + all_datasets=[_dataset(DATASET_ID_1, "Engineering")], + retrieval={"chunks": [{"id": "chunk-a", "dataset_id": DATASET_ID_1, "document_id": "doc-a", "document_keyword": "Manual.pdf", "content": "The limit is 42.", "positions": [[3, 10, 20, 30, 40]]}]}, + ) + monkeypatch.setattr(ragflow_tools, "get_app_config", lambda: _config()) + monkeypatch.setattr(ragflow_tools, "_build_client", lambda _: client) + runtime = SimpleNamespace(context={}) + content, artifact = await ragflow_tools._knowledge_search_entrypoint("limit", runtime) + source = artifact["knowledge_sources"]["sources"][0] + assert source["document_id"] == "doc-a" + assert source["chunk_id"] == "chunk-a" + assert source["document_name"] == "Manual.pdf" + assert source["text"] == "The limit is 42." + assert source["pages"] == [3] + assert f"](#knowledge-{source['id']})" in content + assert DATASET_ID_1 not in content + assert "doc-a" not in content + _, next_artifact = await ragflow_tools._knowledge_search_entrypoint("limit", runtime) + assert next_artifact["knowledge_sources"]["sources"][0]["id"] != source["id"] + + +@pytest.mark.asyncio +async def test_search_artifact_redacts_credentials_and_has_no_sources_on_error(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(ragflow_tools, "get_app_config", lambda: _config()) + client = FakeRAGFlowClient( + all_datasets=[_dataset(DATASET_ID_1, "ragflow-secret")], + retrieval={"chunks": [{"id": "chunk-a", "dataset_id": DATASET_ID_1, "document_id": "doc-a", "document_keyword": "ragflow-secret", "content": "ragflow-secret"}]}, + ) + monkeypatch.setattr(ragflow_tools, "_build_client", lambda _: client) + result = await ragflow_tools._knowledge_search_entrypoint("query", SimpleNamespace(context={})) + assert "ragflow-secret" not in str(result) + content, artifact = await ragflow_tools._knowledge_search_entrypoint("", SimpleNamespace(context={})) + assert content.startswith("Error:") + assert artifact is None + + +@pytest.mark.asyncio +async def test_citation_artifact_survives_native_tool_node_and_message_serialization(monkeypatch: pytest.MonkeyPatch) -> None: + from langchain_core.messages import AIMessage, messages_from_dict, messages_to_dict + from langgraph.graph import END, START, MessagesState, StateGraph + from langgraph.prebuilt import ToolNode + + client = FakeRAGFlowClient( + all_datasets=[_dataset(DATASET_ID_1, "Engineering")], + retrieval={"chunks": [{"id": "chunk-a", "dataset_id": DATASET_ID_1, "document_id": "doc-a", "document_keyword": "Manual.pdf", "content": "Limit: 42."}]}, + ) + monkeypatch.setattr(ragflow_tools, "get_app_config", lambda: _config()) + monkeypatch.setattr(ragflow_tools, "_build_client", lambda _: client) + builder = StateGraph(MessagesState) + builder.add_node("tools", ToolNode([ragflow_tools.knowledge_search_tool])) + builder.add_edge(START, "tools") + builder.add_edge("tools", END) + result = await builder.compile().ainvoke({"messages": [AIMessage(content="", tool_calls=[{"name": "knowledge_search", "args": {"query": "limit"}, "id": "call-a"}])]}) + tool_message = result["messages"][-1] + assert tool_message.status == "success" + assert tool_message.artifact["knowledge_sources"]["sources"][0]["document_id"] == "doc-a" + restored = messages_from_dict(messages_to_dict([tool_message]))[0] + assert restored.artifact == tool_message.artifact + assert restored.content == tool_message.content + + +def test_subagent_forwards_only_cited_captured_sources() -> None: + from deerflow.community.ragflow.sources import cited_source_artifact + + sources = [{"id": "a", "text": "first"}, {"id": "b", "text": "second"}] + messages = [{"type": "tool", "name": "knowledge_search", "artifact": {"knowledge_sources": {"version": 1, "sources": sources}}}] + assert cited_source_artifact(messages, "[citation:1](#knowledge-a)") == {"knowledge_sources": {"version": 1, "sources": [sources[0]]}} + assert cited_source_artifact(messages, "[citation:3](#knowledge-invented)") is None + assert cited_source_artifact([{**messages[0], "type": "ai"}], "[citation:1](#knowledge-a)") is None + + +def test_citation_budget_never_emits_partial_links_or_unseen_artifact_text() -> None: + from deerflow.community.ragflow.formatting import format_retrieval_sources + + chunks = [{"id": f"chunk-{i}", "dataset_id": DATASET_ID_1, "document_id": "doc", "content": "X" * 1000} for i in range(4)] + content, artifact = format_retrieval_sources({"chunks": chunks}, dataset_names_by_id={DATASET_ID_1: "Knowledge"}, max_total_chars=200, max_chars_per_chunk=100) + assert len(content) <= 200 + sources = artifact["knowledge_sources"]["sources"] + assert len(sources) == 1 + assert f"](#knowledge-{sources[0]['id']})" in content + assert sources[0]["text"] in content + assert sources[0]["truncated"] is True + + +def test_task_command_preserves_child_source_artifact() -> None: + from deerflow.tools.builtins.task_tool import _task_result_command + + source = {"id": "abc", "text": "evidence"} + message = {"type": "tool", "name": "knowledge_search", "artifact": {"knowledge_sources": {"version": 1, "sources": [source]}}} + command = _task_result_command(tool_call_id="task-1", status="completed", result="Answer [citation:1](#knowledge-abc)", source_messages=[message]) + assert command.update["messages"][0].artifact["knowledge_sources"]["sources"] == [source] diff --git a/docs/pr-evidence/knowledge-citations-desktop.png b/docs/pr-evidence/knowledge-citations-desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..c18752ac409a73181f4a28a2f4bcb5357f1fe3f1 GIT binary patch literal 72771 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%0A(7s3e!^Hrmy14v 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 Q&R;1fqx`b)h0*8#29c(lA^-pY literal 0 HcmV?d00001 diff --git a/docs/pr-evidence/knowledge-citations-mobile.png b/docs/pr-evidence/knowledge-citations-mobile.png new file mode 100644 index 0000000000000000000000000000000000000000..912387bcf31db6869a703b617d27cc3c0589a058 GIT binary patch literal 46185 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%0DYN31L#(hOPIDCf})*asm-z-M^P<(QlYg^&PkF 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 XVKpTD8h+v6Cm8A~I?C0G*6{xUxEHBm literal 0 HcmV?d00001 diff --git a/frontend/src/AGENTS.md b/frontend/src/AGENTS.md index f543920e3..25fbb836d 100644 --- a/frontend/src/AGENTS.md +++ b/frontend/src/AGENTS.md @@ -190,3 +190,15 @@ association and usage accounting, but renders text accompanying message grouping. Incremental prefix/tail splitting applies only at human boundaries; clarification results also belong to the preceding processing group, so derive the full grouping and stabilize references at clarification boundaries. + +### Knowledge source citations + +`KnowledgeSourcesProvider` scopes source records to the current message list. +Only versioned native `knowledge_search`/`task` tool artifacts supply evidence; +AI/human text and metadata cannot create a source. `CitationLink` resolves +`#knowledge-…` citations through that context and renders unavailable text when +there is no matching record. `KnowledgeSourcesPanel` lists only sources cited +outside code/images. Dialog excerpts render as plain text, never HTML or nested +Markdown. Source records retain retrieval-time evidence, not live documents. +Resolve knowledge destinations before testing the label in message and artifact +link renderers: Sources lists use ordinary titles without a `citation:` prefix. diff --git a/frontend/src/components/workspace/citations/artifact-link.tsx b/frontend/src/components/workspace/citations/artifact-link.tsx index 1560c3086..105d30d69 100644 --- a/frontend/src/components/workspace/citations/artifact-link.tsx +++ b/frontend/src/components/workspace/citations/artifact-link.tsx @@ -1,5 +1,6 @@ import type { AnchorHTMLAttributes } from "react"; +import { knowledgeSourceId } from "@/core/knowledge/sources"; import { cn } from "@/lib/utils"; import { isSafeHref, UnsafeLink } from "../messages/markdown-link"; @@ -10,7 +11,7 @@ function isExternalUrl(href: string | undefined): boolean { return !!href && /^https?:\/\//.test(href); } -/** Link renderer for artifact markdown: citation: prefix → CitationLink, otherwise underlined text. */ +/** Knowledge destinations and citation-prefixed links use the source renderer. */ export function ArtifactLink(props: AnchorHTMLAttributes) { // Reject unsafe schemes so prompt-injected [label](javascript:...) in a .md // artifact preview cannot execute in the main document, matching the guard in @@ -26,6 +27,9 @@ export function ArtifactLink(props: AnchorHTMLAttributes) { ); } + if (knowledgeSourceId(props.href)) { + return ; + } const childrenText = extractReactNodeText(props.children); if (childrenText !== null) { const match = /^citation:(.+)$/.exec(childrenText); diff --git a/frontend/src/components/workspace/citations/citation-link.tsx b/frontend/src/components/workspace/citations/citation-link.tsx index 40c3c3064..ff5fcf2c2 100644 --- a/frontend/src/components/workspace/citations/citation-link.tsx +++ b/frontend/src/components/workspace/citations/citation-link.tsx @@ -9,6 +9,8 @@ import { } from "@/components/ui/hover-card"; import { cn } from "@/lib/utils"; +import { KnowledgeCitationLink } from "./knowledge-source"; + /** Extract visible text from renderer-provided ReactNode children. */ export function extractReactNodeText(node: ReactNode): string | null { if (typeof node === "string" || typeof node === "number") { @@ -41,6 +43,12 @@ export function CitationLink({ const isGenericText = childrenText === "Source" || childrenText === "来源"; const displayText = (!isGenericText && childrenText) ?? domain; + if (href && /^#(?:user-content-)?knowledge-/.test(href)) { + return ( + {displayText} + ); + } + return ( diff --git a/frontend/src/components/workspace/citations/knowledge-source.tsx b/frontend/src/components/workspace/citations/knowledge-source.tsx new file mode 100644 index 000000000..03f08b432 --- /dev/null +++ b/frontend/src/components/workspace/citations/knowledge-source.tsx @@ -0,0 +1,153 @@ +"use client"; + +import type { Message } from "@langchain/langgraph-sdk"; +import { BookOpenTextIcon } from "lucide-react"; +import { createContext, useContext, useMemo, type ReactNode } from "react"; + +import { Badge } from "@/components/ui/badge"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { useI18n } from "@/core/i18n/hooks"; +import { + citedKnowledgeSources, + collectKnowledgeSources, + knowledgeSourceId, + type KnowledgeSource, +} from "@/core/knowledge/sources"; + +const SourcesContext = createContext>( + new Map(), +); + +export function KnowledgeSourcesProvider({ + messages, + children, +}: { + messages: readonly Message[]; + children: ReactNode; +}) { + const sources = useMemo(() => collectKnowledgeSources(messages), [messages]); + return ( + + {children} + + ); +} + +function SourceDialog({ + source, + children, +}: { + source: KnowledgeSource; + children: ReactNode; +}) { + const { t } = useI18n(); + return ( + + + + + + + + {source.document_name} + + + {source.dataset_name} + {source.pages.length > 0 + ? ` · ${t.citations.sourcePages(source.pages.join(", "))}` + : ""} + + +

+ {t.citations.retrievedExcerpt} +

+
+ {source.text} +
+ {source.truncated && ( +

+ {t.citations.excerptTruncated} +

+ )} +
+
+ ); +} + +export function KnowledgeCitationLink({ + href, + children, +}: { + href: string; + children: ReactNode; +}) { + const sources = useContext(SourcesContext); + const { t } = useI18n(); + const id = knowledgeSourceId(href); + const source = id ? sources.get(id) : undefined; + if (!source) + return ( + + {children} + + ); + return ( + + + + {children} + + + ); +} + +export function KnowledgeSourcesPanel({ content }: { content: string }) { + const allSources = useContext(SourcesContext); + const { t } = useI18n(); + const sources = useMemo( + () => citedKnowledgeSources(content, allSources), + [content, allSources], + ); + if (sources.length === 0) return null; + return ( +
+ + {t.citations.knowledgeSourcesSummary(sources.length)} + +
    + {sources.map((source) => ( +
  • + + + {source.document_name} + + + {source.dataset_name} + + +
  • + ))} +
+
+ ); +} diff --git a/frontend/src/components/workspace/messages/markdown-link.tsx b/frontend/src/components/workspace/messages/markdown-link.tsx index 07fdd64af..7ed34b306 100644 --- a/frontend/src/components/workspace/messages/markdown-link.tsx +++ b/frontend/src/components/workspace/messages/markdown-link.tsx @@ -1,6 +1,7 @@ import type { AnchorHTMLAttributes, ComponentProps } from "react"; import { resolveMarkdownArtifactURL } from "@/core/artifacts/utils"; +import { knowledgeSourceId } from "@/core/knowledge/sources"; import { cn } from "@/lib/utils"; import { CitationLink, extractReactNodeText } from "../citations/citation-link"; @@ -107,6 +108,10 @@ export function createMarkdownLinkComponent(threadId?: string) { ); } + // Knowledge destinations also appear as ordinary [Title](URL) Sources links. + if (knowledgeSourceId(href)) { + return ; + } // Safe-href check passed — citation links now route through CitationLink. const childrenText = extractReactNodeText(props.children); if (childrenText !== null) { diff --git a/frontend/src/components/workspace/messages/message-list-item.tsx b/frontend/src/components/workspace/messages/message-list-item.tsx index 9ec0526e0..933949cc9 100644 --- a/frontend/src/components/workspace/messages/message-list-item.tsx +++ b/frontend/src/components/workspace/messages/message-list-item.tsx @@ -63,6 +63,7 @@ import { cn } from "@/lib/utils"; import { WorkspaceChangeBadge } from "../changes"; import { CitationSourcesPanel } from "../citations/citation-sources-panel"; +import { KnowledgeSourcesPanel } from "../citations/knowledge-source"; import { ConversationReferenceChip } from "../conversation-references/conversation-reference-chip"; import { CopyButton } from "../copy-button"; import { ReferenceAttachmentSummary } from "../sidecar/reference-attachments"; @@ -618,6 +619,7 @@ function MessageContent_({ components={components} /> + {message.type === "ai" && showWorkspaceChanges && ( + )} - + ); } diff --git a/frontend/src/core/citations/sources.ts b/frontend/src/core/citations/sources.ts index e3c0fcdbd..123ed18ce 100644 --- a/frontend/src/core/citations/sources.ts +++ b/frontend/src/core/citations/sources.ts @@ -27,7 +27,7 @@ export function extractCitationSources(markdown: string): CitationSource[] { return []; } - const searchable = maskCode(markdown); + const searchable = maskCitationCode(markdown); const sourcesByUrl = new Map(); for (const match of searchable.matchAll(CITATION_LINK_RE)) { @@ -99,7 +99,7 @@ function extractDomain(url: string): string { // Blanks out code regions so example citations inside code aren't scraped as // real sources, while preserving string length (and newlines) so occurrence // indices stay aligned with the original markdown. -function maskCode(markdown: string): string { +export function maskCitationCode(markdown: string): string { return maskInlineCode(maskFencedCodeBlocks(markdown)); } diff --git a/frontend/src/core/i18n/locales/en-US.ts b/frontend/src/core/i18n/locales/en-US.ts index d8cb73d89..8f5ee0a31 100644 --- a/frontend/src/core/i18n/locales/en-US.ts +++ b/frontend/src/core/i18n/locales/en-US.ts @@ -218,6 +218,14 @@ export const enUS: Translations = { // Citations citations: { + viewKnowledgeSource: (title: string) => `View source: ${title}`, + sourcePages: (pages: string) => `Pages ${pages}`, + retrievedExcerpt: + "Evidence captured when this answer was researched. The source document may have changed since retrieval.", + excerptTruncated: "This excerpt was shortened to fit the retrieval limit.", + sourceUnavailable: + "Source evidence is unavailable in the loaded conversation.", + knowledgeSourcesSummary: (count: number) => `${count} knowledge sources`, sourcesSummary: (count) => `Used ${count} ${count === 1 ? "source" : "sources"}`, citeCount: (count) => `${count} ${count === 1 ? "cite" : "cites"}`, diff --git a/frontend/src/core/i18n/locales/types.ts b/frontend/src/core/i18n/locales/types.ts index 7a36fe4c1..4d702ded8 100644 --- a/frontend/src/core/i18n/locales/types.ts +++ b/frontend/src/core/i18n/locales/types.ts @@ -185,6 +185,12 @@ export interface Translations { // Citations citations: { + viewKnowledgeSource: (title: string) => string; + sourcePages: (pages: string) => string; + retrievedExcerpt: string; + excerptTruncated: string; + sourceUnavailable: string; + knowledgeSourcesSummary: (count: number) => string; sourcesSummary: (count: number) => string; citeCount: (count: number) => string; copyReference: (title: string) => string; diff --git a/frontend/src/core/i18n/locales/zh-CN.ts b/frontend/src/core/i18n/locales/zh-CN.ts index 33e01f55f..b69e198f6 100644 --- a/frontend/src/core/i18n/locales/zh-CN.ts +++ b/frontend/src/core/i18n/locales/zh-CN.ts @@ -207,6 +207,13 @@ export const zhCN: Translations = { // Citations citations: { + viewKnowledgeSource: (title: string) => `查看来源:${title}`, + sourcePages: (pages: string) => `第 ${pages} 页`, + retrievedExcerpt: + "这是回答生成时检索到的证据片段,源文档此后可能已发生变化。", + excerptTruncated: "该片段已按检索长度限制截短。", + sourceUnavailable: "当前加载的对话中没有这条引用的来源记录。", + knowledgeSourcesSummary: (count: number) => `${count} 个知识库来源`, sourcesSummary: (count) => `使用了 ${count} 个来源`, citeCount: (count) => `${count} 次引用`, copyReference: (title) => `复制 ${title} 引用`, diff --git a/frontend/src/core/knowledge/sources.ts b/frontend/src/core/knowledge/sources.ts new file mode 100644 index 000000000..266798054 --- /dev/null +++ b/frontend/src/core/knowledge/sources.ts @@ -0,0 +1,107 @@ +import type { Message } from "@langchain/langgraph-sdk"; + +import { maskCitationCode } from "@/core/citations/sources"; + +export type KnowledgeSource = { + id: string; + provider: "ragflow"; + dataset_name: string; + document_name: string; + text: string; + truncated: boolean; + pages: number[]; +}; + +const SOURCE_ID = /^[a-f0-9]{32}-[1-9][0-9]{0,2}$/; +const SOURCE_LINK = + /(? { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +/** Read only native tool artifacts, never model or human-provided metadata. */ +export function collectKnowledgeSources(messages: readonly Message[]) { + const sources = new Map(); + for (const message of messages) { + if ( + message.type !== "tool" || + !["knowledge_search", "task"].includes(message.name ?? "") + ) + continue; + const artifact: unknown = Reflect.get(message, "artifact"); + const payload = record(artifact) ? artifact.knowledge_sources : null; + if ( + !record(payload) || + payload.version !== 1 || + !Array.isArray(payload.sources) + ) + continue; + for (const raw of payload.sources.slice(0, 100)) { + if ( + !record(raw) || + typeof raw.id !== "string" || + !SOURCE_ID.test(raw.id) || + raw.provider !== "ragflow" + ) + continue; + if ( + typeof raw.document_name !== "string" || + raw.document_name.length > 1024 || + typeof raw.dataset_name !== "string" || + raw.dataset_name.length > 1024 || + typeof raw.text !== "string" || + raw.text.length > 200_000 || + typeof raw.truncated !== "boolean" || + !Array.isArray(raw.pages) || + raw.pages.length > 100 || + !raw.pages.every( + (page: unknown) => + typeof page === "number" && + Number.isInteger(page) && + page > 0 && + page <= 1_000_000, + ) + ) + continue; + const source: KnowledgeSource = { + id: raw.id, + provider: "ragflow", + dataset_name: raw.dataset_name, + document_name: raw.document_name, + text: raw.text, + truncated: raw.truncated, + pages: raw.pages as number[], + }; + // Stream replays can repeat records; never silently replace evidence. + if (!sources.has(source.id)) sources.set(source.id, source); + } + } + return sources; +} + +export function knowledgeSourceId(href: string | undefined): string | null { + // rehype-sanitize prefixes same-document anchors to prevent DOM clobbering. + const match = + /^#(?:user-content-)?knowledge-([a-f0-9]{32}-[1-9][0-9]{0,2})$/.exec( + href ?? "", + ); + return match?.[1] ?? null; +} + +export function citedKnowledgeSources( + markdown: string, + sources: ReadonlyMap, +) { + const result: KnowledgeSource[] = []; + const seen = new Set(); + for (const match of maskCitationCode(markdown).matchAll(SOURCE_LINK)) { + const id = match[1]!; + const source = sources.get(id); + if (source && !seen.has(id)) { + seen.add(id); + result.push(source); + } + } + return result; +} diff --git a/frontend/tests/e2e/knowledge-citations.spec.ts b/frontend/tests/e2e/knowledge-citations.spec.ts new file mode 100644 index 000000000..f65ccf9c2 --- /dev/null +++ b/frontend/tests/e2e/knowledge-citations.spec.ts @@ -0,0 +1,106 @@ +import { expect, test } from "@playwright/test"; + +import { MOCK_THREAD_ID, mockLangGraphAPI } from "./utils/mock-api"; + +const sourceId = "0123456789abcdef0123456789abcdef-1"; +const messages = [ + { id: "human-1", type: "human", content: "What is the maximum pressure?" }, + { + id: "ai-search", + type: "ai", + content: "", + tool_calls: [ + { + id: "search-1", + name: "knowledge_search", + args: { query: "maximum pressure" }, + }, + ], + }, + { + id: "tool-1", + type: "tool", + name: "knowledge_search", + tool_call_id: "search-1", + content: `[citation:1](#knowledge-${sourceId}) Engineering / Safety manual.pdf\nMaximum pressure: 42 kPa.`, + artifact: { + knowledge_sources: { + version: 1, + sources: [ + { + id: sourceId, + provider: "ragflow", + dataset_name: "Engineering", + document_name: "Safety manual.pdf", + dataset_id: "dataset-a", + document_id: "doc-a", + chunk_id: "chunk-a", + text: "Maximum pressure: 42 kPa.\nInspect the seal before operation.", + pages: [3], + truncated: false, + }, + ], + }, + }, + }, + { + id: "answer-1", + type: "ai", + content: `The maximum pressure is **42 kPa**. [citation:1](#knowledge-${sourceId})\n\n## Sources\n- [Safety manual.pdf](#knowledge-${sourceId})`, + }, +]; + +test("knowledge references open source evidence before and after history reload", async ({ + page, +}, testInfo) => { + mockLangGraphAPI(page, { + threads: [ + { + thread_id: MOCK_THREAD_ID, + title: "Knowledge source verification", + messages, + }, + ], + }); + await page.goto(`/workspace/chats/${MOCK_THREAD_ID}`); + const source = page.getByRole("button", { + name: "View source: Safety manual.pdf", + }); + await expect(source.first()).toBeVisible(); + await source.first().click(); + await expect(page.getByRole("dialog")).toContainText( + "Maximum pressure: 42 kPa.", + ); + await expect(page.getByRole("dialog")).toContainText("Pages 3"); + await expect(page.getByRole("dialog")).toHaveCSS("opacity", "1"); + await page.screenshot({ + path: testInfo.outputPath("knowledge-citation-desktop.png"), + fullPage: true, + }); + await page + .getByRole("dialog") + .getByRole("button", { name: "Close", exact: true }) + .click(); + // Sources-section links have an ordinary title, without the citation prefix. + const titledSource = source.filter({ hasText: "Safety manual.pdf" }).first(); + await titledSource.click(); + await expect(page.getByRole("dialog")).toContainText( + "Maximum pressure: 42 kPa.", + ); + await page + .getByRole("dialog") + .getByRole("button", { name: "Close", exact: true }) + .click(); + await page.reload(); + await expect(source.first()).toBeVisible(); + await page.setViewportSize({ width: 390, height: 844 }); + await titledSource.click(); + await expect(page.getByRole("dialog")).toContainText( + "Inspect the seal before operation.", + ); + await expect(page.getByRole("dialog")).toHaveCSS("opacity", "1"); + await page.screenshot({ + path: testInfo.outputPath("knowledge-citation-mobile.png"), + fullPage: true, + }); +}); diff --git a/frontend/tests/unit/components/workspace/citations/knowledge-source.dom.test.tsx b/frontend/tests/unit/components/workspace/citations/knowledge-source.dom.test.tsx new file mode 100644 index 000000000..8cf0ca3be --- /dev/null +++ b/frontend/tests/unit/components/workspace/citations/knowledge-source.dom.test.tsx @@ -0,0 +1,130 @@ +import type { Message } from "@langchain/langgraph-sdk"; +import { afterEach, describe, expect, it } from "@rstest/core"; +import { cleanup, fireEvent, render, screen } from "@testing-library/react"; + +import { ArtifactLink } from "@/components/workspace/citations/artifact-link"; +import { + KnowledgeCitationLink, + KnowledgeSourcesPanel, + KnowledgeSourcesProvider, +} from "@/components/workspace/citations/knowledge-source"; +import { createMarkdownLinkComponent } from "@/components/workspace/messages/markdown-link"; +import { I18nProvider } from "@/core/i18n/context"; + +const id = "0123456789abcdef0123456789abcdef-1"; +const href = `#knowledge-${id}`; +const messages = [ + { + type: "tool", + name: "knowledge_search", + content: "retrieved", + tool_call_id: "call", + artifact: { + knowledge_sources: { + version: 1, + sources: [ + { + id, + provider: "ragflow", + document_name: "Manual.pdf", + dataset_name: "Engineering", + text: "The limit is 42.\n", + truncated: false, + pages: [3], + }, + ], + }, + }, + }, +] as unknown as Message[]; +afterEach(cleanup); + +function App({ items = messages }: { items?: Message[] }) { + return ( + + + 1 + + + + ); +} + +describe("knowledge source dialogs", () => { + it("opens the actual excerpt and source page from both citation and source list", () => { + render(); + const buttons = screen.getAllByRole("button", { + name: "View source: Manual.pdf", + }); + expect(buttons.length).toBe(2); + fireEvent.click(buttons[0]!); + const dialog = screen.getByRole("dialog"); + expect(dialog.textContent).toContain("Pages 3"); + expect(dialog.textContent).toContain("The limit is 42."); + expect(dialog.querySelector("script")).toBeNull(); + }); + it("restores citations from persisted JSON and loses access on conversation switch", () => { + const view = render( + , + ); + expect( + screen.getAllByRole("button", { name: "View source: Manual.pdf" }).length, + ).toBe(2); + view.rerender(); + expect( + screen.queryByRole("button", { name: "View source: Manual.pdf" }), + ).toBeNull(); + expect( + screen.getByTitle( + "Source evidence is unavailable in the loaded conversation.", + ), + ).toBeDefined(); + }); +}); + +for (const [surface, Link] of [ + ["message", createMarkdownLinkComponent()], + ["artifact", ArtifactLink], +] as const) { + describe(`${surface} knowledge destinations`, () => { + for (const label of ["Manual.pdf", "citation:1"]) { + for (const destination of [href, href.replace("#", "#user-content-")]) { + it(`opens ${label} at ${destination}`, () => { + render( + + + {label} + + , + ); + fireEvent.click( + screen.getByRole("button", { name: "View source: Manual.pdf" }), + ); + expect(screen.getByRole("dialog").textContent).toContain( + "The limit is 42.", + ); + }); + } + } + it("keeps missing sources unavailable and external links navigable", () => { + render( + + + Manual.pdf + External manual + + , + ); + expect( + screen.getByTitle( + "Source evidence is unavailable in the loaded conversation.", + ), + ).toBeDefined(); + expect( + screen + .getByRole("link", { name: "External manual" }) + .getAttribute("href"), + ).toBe("https://example.com/manual"); + }); + }); +} diff --git a/frontend/tests/unit/core/knowledge/sources.test.ts b/frontend/tests/unit/core/knowledge/sources.test.ts new file mode 100644 index 000000000..359411012 --- /dev/null +++ b/frontend/tests/unit/core/knowledge/sources.test.ts @@ -0,0 +1,100 @@ +import type { Message } from "@langchain/langgraph-sdk"; +import { describe, expect, it } from "@rstest/core"; + +import { + citedKnowledgeSources, + collectKnowledgeSources, + knowledgeSourceId, +} from "@/core/knowledge/sources"; + +const id = "0123456789abcdef0123456789abcdef-1"; +const source = { + id, + provider: "ragflow", + document_name: "Manual.pdf", + dataset_name: "Engineering", + text: "Limit: 42.", + truncated: false, + pages: [3], +}; +const message = { + type: "tool", + name: "knowledge_search", + content: "retrieved", + tool_call_id: "call", + artifact: { knowledge_sources: { version: 1, sources: [source] } }, +} as unknown as Message; + +describe("knowledge citation provenance", () => { + it("resolves repeated citations to the persisted tool evidence after JSON reload", () => { + const messages = JSON.parse(JSON.stringify([message])) as Message[]; + const sources = collectKnowledgeSources(messages); + const citation = `[citation:1](#knowledge-${id})`; + expect(citedKnowledgeSources(`${citation} ${citation}`, sources)).toEqual([ + source, + ]); + expect(knowledgeSourceId(`#knowledge-${id}`)).toBe(id); + expect(knowledgeSourceId(`#user-content-knowledge-${id}`)).toBe(id); + }); + it("does not let model labels or human artifacts invent a source", () => { + const sources = collectKnowledgeSources([ + { ...message, type: "human" } as Message, + { ...message, type: "ai" } as Message, + ]); + expect(sources.size).toBe(0); + expect( + citedKnowledgeSources(`[citation:invented](#knowledge-${id})`, sources), + ).toEqual([]); + expect(knowledgeSourceId(`https://evil.test/#knowledge-${id}`)).toBeNull(); + }); + it("ignores citations in code and images", () => { + const link = `[citation:1](#knowledge-${id})`; + expect( + citedKnowledgeSources( + `\`${link}\`\n\n\`\`\`\n${link}\n\`\`\`\n!${link}`, + collectKnowledgeSources([message]), + ), + ).toEqual([]); + }); + it("rejects malformed and unknown artifact versions", () => { + for (const payload of [ + { version: 2, sources: [source] }, + { version: 1, sources: [{ ...source, pages: [-1] }] }, + { version: 1, sources: [{ ...source, text: 42 }] }, + ]) { + const bad = { + ...message, + artifact: { knowledge_sources: payload }, + } as Message; + expect(collectKnowledgeSources([bad]).size).toBe(0); + } + }); + it("keeps independent source identities for repeated searches", () => { + const other = { + ...source, + id: "fedcba9876543210fedcba9876543210-1", + text: "Limit: 43.", + }; + const next = { + ...message, + artifact: { knowledge_sources: { version: 1, sources: [other] } }, + } as Message; + expect(collectKnowledgeSources([message, next, message]).size).toBe(2); + }); +}); + +it("collects ordinary Sources-section links without collecting code or images", () => { + const link = `[Manual.pdf](#knowledge-${id})`; + expect( + citedKnowledgeSources( + `## Sources\n- ${link}\n${link}`, + collectKnowledgeSources([message]), + ), + ).toEqual([source]); + expect( + citedKnowledgeSources( + `\`${link}\`\n!${link}`, + collectKnowledgeSources([message]), + ), + ).toEqual([]); +});