Skip to content

fix(replace): validate substitution maps and retry missing pairs - #396

Open
memadi-nv wants to merge 4 commits into
mainfrom
memadi/bugfix/388-substitution-coverage
Open

memadi-nv wants to merge 4 commits into
mainfrom
memadi/bugfix/388-substitution-coverage

Conversation

@memadi-nv

@memadi-nv memadi-nv commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Saved local notebook experiments identified two failure patterns that passed structural schema validation:

  1. Omitted mappings: a parsed generation map contained 31 mappings for 33 requested (value, label) pairs, with no unrelated entries. Two requested pairs were missing from the generated map.
  2. Changed copied originals: a map contained 23 entries for 23 requested pairs, with synthetic values present, but six originals changed U+202F to U+2009. Those six returned pairs therefore did not exactly match the requested pairs.

These observations compare the saved requested inventories with parsed generation outputs, not raw provider HTTP responses. Issue #388 contains artifact provenance and evidence limits. Context exhaustion and the internal reason for omissions remain unproven.

Changes

  • Compare schema-valid maps against the original pre-generation requested pairs. Retain accepted substitutions, retry only missing pairs once, and report records still incomplete or whose completeness retry fails through failed_records, preserving complete records.
  • Reconcile supported Unicode-space differences only for a unique match under the same exact label. Prefer exact entries and restore the trusted original without changing source text or offsets. Ambiguous matches remain subject to the completeness retry/failure flow.
  • Reject empty and whitespace-only synthetic values in both Pydantic and exported JSON Schema; preserve valid one-character and padded values. No whitespace-only substitutes were observed in the saved collection; this is a preventive validation change.
  • Keep completeness retry and Unicode reconciliation scoped to Substitute. The shared nonblank schema also applies to rewrite replacement maps; Rewrite's omitted-mapping recovery policy stays unchanged.

DataDesigner's existing whole-response schema retries remain separate from the single completeness retry. This PR does not introduce per-entry schema recovery.

Local validation

  • 1,882 tests passed; 2 skipped.
  • Documentation follow-up: strict documentation build and all 16 existing bundled agent-guide tests passed.
  • Formatting, lint, type, lockfile, copyright, strict documentation build, and required commit hooks passed.
  • Regression tests cover targeted retry and terminal failures, mixed batches, duplicate record IDs, exact-match precedence, ambiguous Unicode-space matches, whitespace rejection, and preview/batch execution through the real adapter with a local scripted provider.

Implementation plan

Fixes #388

@memadi-nv
memadi-nv requested a review from a team as a code owner November 10, 2026 01:49
@greptile-apps

greptile-apps Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge, with the earlier fallback-reuse issue remaining as a non-blocking concern.

Findings

  1. P2 Retry reuses fallback values ▶

Summary

This PR checks Substitute maps against the original requested pairs and retries missing pairs once.

  • Substitute retries missing mappings once and reports records that remain incomplete.
  • Substitute accepts unique matches when copied originals differ only by supported Unicode spaces.
  • Replacement maps reject synthetic values made only of whitespace.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Requested entity pairs] --> B[Generate map with schema retries]
  B --> C[Filter and reconcile supported spaces]
  C --> D{All pairs mapped?}
  D -->|Yes| E[Apply replacements]
  D -->|No| F[Retain accepted mappings]
  F --> G[Generate missing pairs once with schema retries]
  G --> H{Map complete?}
  H -->|Yes| E
  H -->|No or retry fails| I[Report affected records as failed]
Loading

Reviews (3) · Last reviewed commit: "docs(replace): clarify schema and comple..." · Reviewed by Greptile

Compare schema-valid maps with the original requested entity-label pairs.
Retry missing pairs once while retaining accepted substitutions, and report
unresolved or failed retry records without discarding complete records.

Refs #388

Signed-off-by: memadi <memadi@nvidia.com>
Recover a copied original only when known space variants identify one
requested pair under the same label. Prefer exact entries and restore the
trusted original without changing source text, offsets or synthetic values.
Ambiguous matches retain the existing retry and failure behavior.

Refs #388

Signed-off-by: memadi <memadi@nvidia.com>
Require a non-whitespace character in synthetic values in both Pydantic
and exported JSON Schema validation. Preserve valid one-character and
padded values, including replacement maps generated for rewrite mode.

Refs #388

Signed-off-by: memadi <memadi@nvidia.com>
@memadi-nv
memadi-nv force-pushed the memadi/bugfix/388-substitution-coverage branch from 145acdd to 7ffb9f7 Compare November 11, 2026 02:03
@memadi-nv
memadi-nv requested a review from a team as a code owner November 11, 2026 02:03
@memadi-nv memadi-nv changed the title fix(replace): reject blank substitutes and fail records with incomplete mappings fix(replace): validate substitution maps and retry missing pairs Nov 11, 2026
Comment on lines +132 to +143
filtered = _filter_replacement_map_to_input_entities(
raw_map=row.get(COL_REPLACEMENT_MAP),
parsed_entities=expected[position],
record_id=str(source[RECORD_ID_COLUMN].iloc[position]),
reconcile_unicode_whitespace=True,
)
additions = [
entry
for entry in filtered["replacements"]
if (entry["original"], entry["label"]) in missing[position]
]
accepted[position]["replacements"].extend(additions)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Retry reuses fallback values

The retry can give two different entities the same fallback value. If the first response copies Alice unchanged and omits Bob, the filter repairs Alice to [SUBSTITUTE_FIRST_NAME_1]. If the retry copies Bob unchanged, its fresh counter produces [SUBSTITUTE_FIRST_NAME_1] again. Both mappings are accepted, making two different people look like one in the output.

Keep track of accepted synthetic values when choosing fallback values on the retry.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is related to the discussion we had in PR #319 with different entities and same substitution in a single record.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is the same uniqueness invariant discussed in #319: different entities in one record must not end up with the same substitute. The distinction is that #394’s run-wide uniqueness fix doesn’t cover this path—the retry calls the filter with a fresh fallback counter, and it doesn’t reserve synthetic values already accepted for that record. So the collision scenario in my comment still applies.

Distinguish existing DataDesigner whole-response schema retries from the
single missing-pair retry. Align failure diagnostics, Rewrite validation,
per-row call counts and bundled agent guidance with the implemented flow.
Update the issue plan to reflect the published branch and PR.

Refs #388

Signed-off-by: memadi <memadi@nvidia.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(replace): validate substitution values and detected-entity mapping coverage

1 participant