Skip to content

Resolve Issue #52: Fix missing document-level images and incorrect rendering order - #53

Merged
EricNGOntos merged 3 commits into
stagingfrom
fix/wuchengke/fix-issue-52
May 13, 2026
Merged

EricNGOntos merged 3 commits into
stagingfrom
fix/wuchengke/fix-issue-52

Conversation

@EricNGOntos

Copy link
Copy Markdown
Contributor

Closes #52

Summary

This PR fixes a critical data integrity bug where standalone images located in the document root and L1 sections (e.g. preface/cover page) were being silently discarded during the Agentic RAG pipeline's asset reconciliation phase. Additionally, it corrects the rendering order so document-level assets appear at the top of the evidence tree.

Root Cause Analysis

Three interacting defects caused 3 out of 10 images to be silently dropped:

1. Overly Aggressive L1 Guard (asset_filter_step)

The guard clause if own_section_path and ' / ' not in own_section_path was designed to reject only the synthetic "Root" aggregation label, but it inadvertently blocked ALL L1 section paths (e.g. "前言", "法律声明") since they also lack a / separator. This caused image-3 (preface chart) to be discarded at the filter stage before it ever reached reconciliation.

2. Synthetic "Root" Label Mismatch (_build_connected_owner_map)

The DB stores section_path = "Root" for document-level sections, but the outline tree uses the document's source_file_name as the root path. The "Root" string could never match any outline node, causing image-1 and image-2 (cover page diagrams) to fail both Tier-1 exact match and Tier-2 ancestor walk.

3. Tier-1 Match Too Narrow (_reconcile_deferred_assets)

Tier-1 exact matching only checked final_paths (hydrated leaf content). In STOP scenarios where the LLM stops at root without drilling down, final_paths is empty, so even correctly resolved owner paths like "前言" had no target to match against.

Changes

tools.py — Asset Filter Step

  • Root→filename mapping: When _build_connected_owner_map returns "Root" as an owner, dynamically replace it with the document's source_file_name via a DB lookup. This gives root-level assets a real, matchable path identity.
  • Relaxed L1 guard: Changed the guard from ' / ' not in own_section_path to own_section_path == 'Root', so legitimate L1 sections like "前言" are no longer rejected as fallback owners.

orchestrator.py — Reconciliation & Tree Assembly

  • Tier-1 expanded: Changed exact match from final_paths to all_target_paths (union of hydrated leaves + visible outline sections). This allows assets to be placed under visible but un-hydrated sections in STOP scenarios.
  • Conditional doc_name injection: Inject source_file_name into root.outline_items at position 0 (top of evidence tree), but ONLY when not root.children (pure STOP — no drill-down navigation). This prevents root-level assets from leaking into focused navigation scenarios.

Verification

E2E test results with debug_agentic_e2e.py:

Test Case Before After Status
T0 (All Charts) 8 refs (missing image-1,2,3) 11 refs (10 images + 1 table) ✅ Fixed
T5 (Multi-Scope) 11 refs (image-1,2 leaked) 7 refs (only relevant assets) ✅ Fixed

Image-1/2 now render at the TOP of the evidence tree (before L1 sections) in T0, and are correctly excluded from T5's focused navigation.

@EricNGOntos EricNGOntos added the agentic-rag-core Core agentic RAG retrieval pipeline label May 13, 2026
@EricNGOntos EricNGOntos self-assigned this May 13, 2026

_SCOPE_NAV_PROMPT = """\

_DISCOVERY_SELECT_PROMPT = """\
"""


_ACTION_PROMPT = """\
data = _json.loads(text)
if isinstance(data, dict):
return _extract(data)
except (ValueError, _json.JSONDecodeError):
data = _json.loads(fence_match.group(1).strip())
if isinstance(data, dict):
return _extract(data)
except (ValueError, _json.JSONDecodeError):
data = _json.loads(brace_match.group())
if isinstance(data, dict):
return _extract(data)
except (ValueError, _json.JSONDecodeError):
# Simple queries: planner returns a single-step plan (no decomposition).
# Complex queries: planner returns a multi-step plan with synthesize.
# Both go through the same code path.
from shared.services.retrieval.workflow.orchestrator import WorkflowOrchestrator

from shared.core.database import get_db_context
from shared.services.retrieval.agentic.budget import BudgetLedger
from shared.services.retrieval.agentic.orchestrator import RetrievalAgent
parsed = json.loads(text)
if isinstance(parsed, dict):
return parsed
except (ValueError, json.JSONDecodeError):
@EricNGOntos
EricNGOntos merged commit 4f94bb2 into staging May 13, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agentic-rag-core Core agentic RAG retrieval pipeline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants