phase: 137_pdf_image_context
All verification complete — everything is 100% green. Final report: **Phase 137 final verification pass — all tasks already complete, all criteria verified** - **Full suite:** `uv run pytest --cov=app --cov-report=term-missing` → 3272 passed, exit 0; `app/` coverage **99%** (>90% ✓) - **Phase E2E in isolation:** `uv run pytest tests/e2e/test_pdf_image_context.py -v --no-cov` → **4 passed** (forward context, display line, standalone parity, both-blocks markdown case) - **Pre-existing suites in isolation:** `test_pdf_support.py` + `test_image_anchor_context.py` + `test_image_documents.py` → **14 passed**; ripple-edited `test_garbage_pdf.py` → **3 passed** - **Lint/types:** `uv run ruff check .` → all passed; `uv run pyright` → 0 errors **Completion criteria:** (1) forward-path context in vision msg + stored `image_context`/summary — ✓ (unit builder/seam + integration fake-LLM + E2E); (2) both blocks document-first + standalone describe byte-identical — ✓ (combiner + standalone pins); (3) backfill with context + zero-vision idempotent re-sync + parent-gone origin-only — ✓ (6 integration tests); (4) suggestion block leads with `From <source>/<pdf> (page N)` — ✓ (renderer units + E2E); (5) all gates green — ✓ above; (6) no behavior change in phases 122/125/126 + sync `detail` shape untouched — ✓ (verified in code: new counter is log-only); (7) ruff+pyright — ✓; (8) commit — left to the harness per pipeline rules (working tree untouched by me). **Documentation:** README.md audited in full — the new phase-137 bullet verified against code (6 000-char cap, log-only `image_context_backfilled=N`, `<document>`-body locator placement), all env tables/defaults cross-checked vs `app/config.py` and `.env.example` — consistent, no changes needed. AGENTS.md: every rule/command verified — consistent, no changes needed. **Notable deviations (pre-existing, documented in task-05 report, forced by design):** `test_pdf_support.py`/`test_garbage_pdf.py` got minimal ripple edits (the "UNEDITED" pin was unsatisfiable once describe calls carry context by design); the unit `test_image_anchor_context.py` CONTEXT_PREFIX pin generalized per ASSUMPTION A5; one extra mock line (`_seed_context_line` accepts `From …`). **Next pending phase:** none — `todo/` holds only this phase.
This commit is contained in:
+61
-19
@@ -523,15 +523,22 @@ Implements just enough of the aipi surface:
|
||||
deterministic ZERO-READ answer that QUOTES THE CONTEXT LINE of the
|
||||
seeded image doc's ``<document>`` block (the same image-doc pick
|
||||
as ``DIAGRAM_TRIGGER``): an ENRICHED image doc's block (phase 125
|
||||
task 05, LOCKED A4) leads with the one-line
|
||||
``Referenced in <source>/<path>: "<alt>"`` context line BEFORE
|
||||
the description, so the mock's quote — the block's exact first
|
||||
line — proves the line WAS in the prompt (and that its text is
|
||||
the task-05 one-line rendering, not the stored multi-line block).
|
||||
task 05, LOCKED A4; phase 137 task 04) leads with the one-line
|
||||
context line BEFORE the description — the
|
||||
``Referenced in <source>/<path>: "<alt>"`` reference rendering
|
||||
(phase 125) or the ``From <source>/<pdf> (page N)`` provenance
|
||||
rendering (phase 137 — a PDF-embedded image's block, a combined
|
||||
context joining both with ``; `` in stored order) — so the
|
||||
mock's quote — the block's exact first line — proves the line
|
||||
WAS in the prompt (and that its text is the one-line rendering,
|
||||
not the stored multi-line block).
|
||||
A non-enriched (standalone) image doc's block is the bare
|
||||
description (byte-identical pre-phase): its first line never
|
||||
starts with ``Referenced in `` (the mock's fixed description
|
||||
cannot begin that way), so the mock answers that the image
|
||||
starts with ``Referenced in `` or ``From `` (the mock's fixed
|
||||
description cannot begin that way — the context-aware reply
|
||||
does, but only ever rides a doc whose stored context makes the
|
||||
``From …`` line the block's first line), so the mock answers
|
||||
that the image
|
||||
carries no reference line — the byte-identity criterion's
|
||||
deterministic witness. Checked BEFORE the ``DEFLECT_MODE``
|
||||
branch (the marker lives in the user message — the
|
||||
@@ -914,6 +921,21 @@ IMAGE_DESCRIPTION_ANSWER = (
|
||||
"cable runs. Title: Homelab Network Map."
|
||||
)
|
||||
|
||||
#: The phase-137 witness — the mock's vision reply when the
|
||||
#: ``IMAGE_DESCRIPTION_MODE`` message's text part carries the
|
||||
#: document-context block (its ``Extracted from …`` header). It
|
||||
#: names the document + page (the context reached the model — the
|
||||
#: deterministic proof) and keeps the diagram wording of
|
||||
#: :data:`IMAGE_DESCRIPTION_ANSWER` so the story question's
|
||||
#: token-overlap retrieval still grounds on the image doc.
|
||||
IMAGE_DESCRIPTION_CONTEXT_ANSWER = (
|
||||
"From report.pdf, page 2: a network diagram of the homelab "
|
||||
"server room: a core router on top, a core switch in the "
|
||||
"middle, and three labeled subnets at the bottom — VLAN 10 "
|
||||
"office, VLAN 20 lab, and VLAN 30 storage — with a legend of "
|
||||
"cable runs. Title: Homelab Network Map."
|
||||
)
|
||||
|
||||
TABLE_ANSWER = (
|
||||
"Here's the shape, in a table:\n"
|
||||
"\n"
|
||||
@@ -1966,21 +1988,29 @@ def _seeded_image_doc(system: str) -> tuple[str, str, str] | None:
|
||||
|
||||
|
||||
def _seed_context_line(block_content: str) -> str | None:
|
||||
"""The phase-125 context line of a seeded image doc's block
|
||||
(task 06, LOCKED A4) — or ``None``.
|
||||
"""The context line of a seeded image doc's block (phase 125
|
||||
task 06, LOCKED A4; phase 137 task 04/05) — or ``None``.
|
||||
|
||||
An ENRICHED image doc's ``<document>`` block leads with the
|
||||
one-line ``Referenced in …`` rendering (the task-05
|
||||
one-line context rendering (the task-05/task-04
|
||||
:func:`app.rag.prompts._extract_context_line` output, BEFORE the
|
||||
description): its first line, verbatim, is the line the mock
|
||||
quotes (the prompt-injection echo convention). A non-enriched
|
||||
block is the bare description — its first line never starts with
|
||||
``Referenced in `` (the mock's fixed
|
||||
:data:`IMAGE_DESCRIPTION_ANSWER` cannot begin that way), so
|
||||
``None`` pins the byte-identical pre-phase shape.
|
||||
description): the one-line ``Referenced in …`` reference shape
|
||||
(phase 125) or the one-line ``From <source>/<pdf> (page N)``
|
||||
provenance shape (phase 137 — a PDF-embedded image, whose block
|
||||
leads with the document-context clause, a combined context
|
||||
joining both with ``; ``). Its first line, verbatim, is the line
|
||||
the mock quotes (the prompt-injection echo convention). A
|
||||
non-enriched block is the bare description — its first line never
|
||||
starts with ``Referenced in `` or ``From `` (the mock's fixed
|
||||
:data:`IMAGE_DESCRIPTION_ANSWER` begins "A network diagram" — the
|
||||
context-aware :data:`IMAGE_DESCRIPTION_CONTEXT_ANSWER` begins
|
||||
"From " but only ever rides a doc whose stored context makes the
|
||||
``From …`` CONTEXT LINE the block's first line, so the bare
|
||||
description is never the first line for it), so ``None`` pins the
|
||||
byte-identical pre-phase shape.
|
||||
"""
|
||||
first = block_content.split("\n", 1)[0].strip()
|
||||
if first.startswith("Referenced in "):
|
||||
if first.startswith("Referenced in ") or first.startswith("From "):
|
||||
return first
|
||||
return None
|
||||
|
||||
@@ -2661,8 +2691,20 @@ def compose_answer(body: dict[str, Any]) -> str:
|
||||
# the marker is a fixed app constant, and a real question is
|
||||
# never expected to type it (the other user triggers are owner
|
||||
# phrasings a question might legitimately contain — this one
|
||||
# is an app-to-app marker).
|
||||
answer = IMAGE_DESCRIPTION_ANSWER
|
||||
# is an app-to-app marker). Phase 137 (task 05): the
|
||||
# document-context trigger — when the text part carries the
|
||||
# provenance header (``Extracted from `` — the importer's
|
||||
# block builder, tasks 02/03), the canned reply becomes the
|
||||
# context-aware :data:`IMAGE_DESCRIPTION_CONTEXT_ANSWER`
|
||||
# (the deterministic witness that the context REACHED the
|
||||
# vision model); the bare path (standalone images, the
|
||||
# pre-137 embedded shape) stays on the bare
|
||||
# :data:`IMAGE_DESCRIPTION_ANSWER` — byte-identical.
|
||||
answer = (
|
||||
IMAGE_DESCRIPTION_CONTEXT_ANSWER
|
||||
if "Extracted from " in user
|
||||
else IMAGE_DESCRIPTION_ANSWER
|
||||
)
|
||||
elif TABLE_TRIGGER in user.lower():
|
||||
# Markdown tables (phase 44, TODO.md L6): the story E2E's
|
||||
# deterministic table answer — a 3-column table, the
|
||||
|
||||
Reference in New Issue
Block a user