PMFlow/docs/superpowers/plans/2026-04-16-external-news-ev...

8.2 KiB

External News Evidence Upgrade Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Make external freshness/news questions answerable with article-grade evidence, while keeping the current fail-closed external contract and without adding runtime keyword tables or business regex routing.

Architecture: Keep route_mode=external and the current verifier/presenter boundary, but strengthen the evidence acquisition stage. The next pass should expand search candidates into real article pages, normalize article metadata into structured evidence, separate attempted_sources from answer_evidence, and only let the presenter see customer-safe, article-backed sources.

Tech Stack: Python 3.11, FastAPI, PMFlow AdaptiveRAG, Pydantic, pytest, httpx, HTMLParser/JSON-LD metadata


Task 1: Lock The Next-Stage News Retrieval Contract With Tests

Files:

  • Modify: backend/tests/test_mcp_external_query_bridge.py

  • Modify: backend/tests/test_external_answer_presenter_contract.py

  • Modify: backend/tests/test_adaptive_rag_mainline_contract.py

  • Step 1: Add a failing test for live-search candidate expansion into article pages

    • Simulate a search result that points to a category page plus one resolvable article page.
    • Expect the adapter/gateway to keep only the article page as verified_evidence.
  • Step 2: Add a failing test that sources_used only includes answer evidence

    • Feed one article page and two discarded candidates.
    • Expect customer-visible sources_used / 来源摘要 to exclude discarded candidates.
  • Step 3: Add a failing test for absolute-date news output

    • Feed published_at.
    • Expect the presenter prompt/output contract to use YYYY-MM-DD or 日期未明确, never relative time like 3天前.
  • Step 4: Run the targeted tests to confirm they fail for the right reason

    • Run: PYTHONPATH=backend backend/venv/bin/python -m pytest backend/tests/test_mcp_external_query_bridge.py -v
    • Run: PYTHONPATH=backend backend/venv/bin/python -m pytest backend/tests/test_external_answer_presenter_contract.py -v
    • Run: PYTHONPATH=backend backend/venv/bin/python -m pytest backend/tests/test_adaptive_rag_mainline_contract.py -v

Task 2: Add Article Expansion And Structured Candidate Hydration

Files:

  • Create: backend/app/services/external_query_service/article_expander.py

  • Modify: backend/app/services/external_query_service/page_metadata.py

  • Modify: backend/app/services/external_query_service/adapters/live_search.py

  • Modify: backend/app/services/external_query_service/adapters/mcp.py

  • Modify: backend/app/services/external_query_service/schemas.py

  • Modify: backend/app/services/adaptive_rag_service/mixin_conversation_utils_a_p1.py

  • Step 1: Add a focused article-expansion helper

    • Accept one fetched page payload.
    • Extract canonical/linked article candidates from structured HTML signals only (link rel=canonical, JSON-LD, standard anchors), not business keyword heuristics.
  • Step 2: Extend evidence schemas for staged acquisition

    • Add fields such as evidence_stage, attempted_url, canonical_url, article_url, and selection_reason.
    • Keep display_label/display_summary/page_kind/published_at as the customer-safe surface.
  • Step 3: Teach live-search hydration to do a second hop when first-page evidence is landing/category

    • Fetch the top candidate page.
    • If it is not article-grade, expand one bounded hop to article candidates and fetch those instead.
    • Keep the fetch count bounded.
  • Step 4: Reuse the same bounded second-hop logic for MCP target URLs

    • MCP search can still return landing/detail wrappers.
    • Hydration should converge on article-grade evidence without changing upstream semantics.
  • Step 5: Re-run the targeted news retrieval tests

    • Run: PYTHONPATH=backend backend/venv/bin/python -m pytest backend/tests/test_mcp_external_query_bridge.py -v

Task 3: Separate Attempted Sources From Answer Evidence End-To-End

Files:

  • Modify: backend/app/services/external_query_service/gateway.py

  • Modify: backend/app/services/adaptive_rag_service/mixin_answer_retrieval.py

  • Modify: backend/app/services/adaptive_rag_service/mixin_answer_payload.py

  • Modify: backend/app/services/adaptive_rag_service/mixin_query_route_gateway.py

  • Step 1: Persist adapter_attempts and discarded candidates as debug-only metadata

    • Do not expose them in customer-visible sources_used.
  • Step 2: Make execution.evidence represent only selected answer evidence

    • Keep attempted/discarded candidates in meta.
    • Preserve current fail-closed semantics when no answer evidence remains.
  • Step 3: Build customer-visible sources from display_label only

    • sources_used / 来源摘要 should come from selected answer evidence.
    • raw_source_label stays admin/debug only.
  • Step 4: Re-run the answer payload and route-contract tests

    • Run: PYTHONPATH=backend backend/venv/bin/python -m pytest backend/tests/test_adaptive_rag_mainline_contract.py -v

Task 4: Tighten The News Presentation Contract Without Relaxing Safety

Files:

  • Modify: backend/app/services/external_query_service/presenter.py

  • Modify: backend/app/config/ai_prompts_catalog/part_scenes_06.py

  • Modify: backend/app/config/ai_prompts.py

  • Modify: backend/app/config/ai_prompt_registry.py

  • Step 1: Keep external_news_brief as the only news presenter contract

    • Do not fall back to generic summary style for news/freshness questions.
  • Step 2: Force date rendering through structured fields

    • Use published_at -> YYYY-MM-DD.
    • If absent, explicitly render 日期未明确.
  • Step 3: Keep the prompt surface customer-safe

    • Evidence blocks may include display_label, display_summary, title, published_at, target_url.
    • Never feed URL[...], MCP[...], or raw_source_label.
  • Step 4: Re-run presenter and prompt-registry tests

    • Run: PYTHONPATH=backend backend/venv/bin/python -m pytest backend/tests/test_external_answer_presenter_contract.py -v
    • Run: PYTHONPATH=backend backend/venv/bin/python -m pytest backend/tests/test_ai_prompt_registry_contract.py -v

Task 5: Verify On The Real Backend And Sync Runtime Docs

Files:

  • Modify: CLAUDE.md

  • Modify: docs/architecture/ARCHITECTURE_INVENTORY.md

  • Modify: docs/architecture/CODE_REFERENCE_INDEX.md

  • Modify: docs/architecture/RUNTIME_GUARDRAILS.md

  • Modify: docs/architecture/DATAFLOW.md

  • Step 1: Update docs for second-hop article expansion and answer-evidence separation

    • Document that news grounding now requires article-grade evidence.
    • Document that attempted sources and answer sources are different surfaces.
  • Step 2: Run compile + targeted regression

    • Run: PYTHONPATH=backend /opt/homebrew/bin/python3 -m py_compile backend/app/services/external_query_service/article_expander.py backend/app/services/external_query_service/page_metadata.py backend/app/services/external_query_service/adapters/live_search.py backend/app/services/external_query_service/adapters/mcp.py backend/app/services/external_query_service/gateway.py backend/app/services/external_query_service/presenter.py backend/app/services/adaptive_rag_service/mixin_answer_retrieval.py backend/app/services/adaptive_rag_service/mixin_answer_payload.py backend/app/services/adaptive_rag_service/mixin_query_route_gateway.py
  • Step 3: Rebuild the backend container so Python changes really load

    • Run: docker compose up -d --build backend
  • Step 4: Live-check a real news query and capture the raw JSON

    • Ask: 最近 AI 行业有哪些值得关注的新闻?
    • Expected:
      • Best case: grounded answer with article-grade sources and customer-safe labels.
      • Safe fallback: explicit insufficient_evidence, but no machine labels and no landing-page hallucination.
  • Step 5: Record any remaining provider-gap separately

    • If live-search still cannot surface article-grade pages reliably, document the exact gap before adding a new provider.