Skip to content

refactor: updates to quality QA mechanics to improve utility#171

Open
asteier2026 wants to merge 5 commits into
mainfrom
asteier2026/feature/generalization-suggestion
Open

refactor: updates to quality QA mechanics to improve utility#171
asteier2026 wants to merge 5 commits into
mainfrom
asteier2026/feature/generalization-suggestion

Conversation

@asteier2026

Copy link
Copy Markdown
Contributor

Changes include:

  • Add generalization_suggestion for protection method 'generalize' in sensitivity disposition and feed that down into meaning unit prompt.
  • Feed latent evidence down to meaning unit prompt
  • Loosen quality compare to allow for abstraction differences
  • Biography supplement for meaning units slightly updated
  • Enhancements to the re-answer prompt (avoid unknown)
  • Our earlier error on validation failing on combined_risk_level = low but protection_method_suggestion is not 'leave_as_is', still occasionally crops up. The LLM prompt is very clear about not allowing this, so I went with the approach of fixing it after the fact in parse_sensitivity_disposition before validate runs. It will presume combined_risk_level is correct and replace the current protection method with 'leave_as_is'. It will still log a warning if you run in debug.

@asteier2026 asteier2026 requested a review from a team as a code owner May 28, 2026 16:11
@greptile-apps

greptile-apps Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR refactors the quality/QA mechanics in the rewrite pipeline: it introduces a dedicated GeneralizationSuggestionsWorkflow that generates LLM-based abstraction hints for generalize-protected entities, feeds latent evidence into the meaning-unit extraction prompt, loosens the quality-compare scoring rubric to tolerate abstraction differences, and adds _correct_disposition_consistency to silently fix the recurring combined_risk_level='low' + non-leave_as_is protection method LLM miscalibration before schema validation.

  • New generalization workflow: generalization_suggestions.py dispatches chunks of generalize entities concurrently via ThreadPoolExecutor, merging results into a dict[entity_value → suggestion] stored in COL_GENERALIZATION_SUGGESTIONS, which is then fed into both the meaning-unit and rewrite prompts.
  • Prompt enhancements: the reanswer prompt now explicitly instructs the model to prefer abstracted answers over "unknown", and the compare rubric now rewards semantic preservation rather than verbatim specificity.
  • Disposition auto-correction: _correct_disposition_consistency pre-validates the raw LLM dict and forces protection_method_suggestion to leave_as_is when combined_risk_level='low', preventing the previously-surfacing ValidationError at runtime.

Confidence Score: 5/5

Safe to merge; the new generalization workflow is well-isolated behind required_columns dependency declarations and degrades gracefully when no generalize entities exist.

The pipeline wiring is correct, all new code paths have explicit fallbacks (empty dict defaults, if not entities early return, if suggestion: guards before adding to prompts), and the auto-correction in _correct_disposition_consistency is appropriately scoped. The only issues are an edge-case merge collision in _merge_chunk_results (same entity_value, different labels both needing generalization — very unlikely in practice) and a dead assignment in _correct_disposition_consistency that is harmless but mildly misleading.

src/anonymizer/engine/rewrite/generalization_suggestions.py — the _merge_chunk_results keying strategy and the downstream lookup both use entity_value only, which would produce incorrect suggestions for the rare case of two entities sharing the same surface form.

Important Files Changed

Filename Overview
src/anonymizer/engine/rewrite/generalization_suggestions.py New file implementing the chunked generalization suggestions workflow; merge logic keys by entity_value only, causing silent collision for entities that share a value string but have different labels.
src/anonymizer/engine/rewrite/parsers.py Adds _correct_disposition_consistency to auto-fix combined_risk_level/protection_method mismatches pre-validation; the assignment of generalization_suggestion='N/A' in the raw dict is a no-op since EntityDispositionSchema has no such field, but harmless.
src/anonymizer/engine/rewrite/qa_generation.py Adds generalization_suggestion (gated on protection_method=='generalize') and latent evidence (gated on suppress_inference) to the disposition block; required_columns updated correctly; prompt split for generalize vs suppress_inference is cleaner than before.
src/anonymizer/engine/rewrite/evaluate.py Reanswer prompt now steers the model toward abstracted answers over 'unknown'; compare rubric ABSTRACTION TOLERANCE block is clear and well-reasoned; no logic issues.
src/anonymizer/engine/rewrite/rewrite_generation.py Adds generalization_suggestion to rewrite disposition block (gated on generalize method) and Jinja2 template; required_columns updated correctly.
src/anonymizer/engine/rewrite/rewrite_workflow.py Wires GeneralizationSuggestionsWorkflow into the column pipeline before QA generation; ordering is correct for DataDesigner's dependency resolution.
src/anonymizer/engine/schemas/rewrite.py Adds GeneralizationSuggestionSchema and GeneralizationSuggestionsSchema; EntityDispositionSchema unchanged (generalization_suggestion field does not exist there, so test fixture kwargs are silently dropped by Pydantic).
src/anonymizer/engine/rewrite/domain_classification.py BIOGRAPHY_PROFILE quality_supplement tightened to discourage preserving uniquely-identifying combinations; content change only.
src/anonymizer/engine/constants.py Adds COL_GENERALIZATION_SUGGESTIONS constant; trivial and correct.
src/anonymizer/engine/schemas/init.py Exports the two new schema classes; straightforward.
tests/engine/test_parsers.py Two new tests cover both auto-correction paths (generalize and suppress_inference with low risk); well-structured.
tests/engine/test_qa_generation.py New tests cover evidence inclusion for suppress_inference and absence for other methods; generalization_suggestion kwargs in EntityDispositionSchema constructors are silently ignored by Pydantic but don't break tests.
tests/engine/test_rewrite_generation.py New tests verify generalization_suggestion is included for generalize and excluded for non-generalize methods; correct.
tests/engine/test_schemas.py Test fixtures updated with generalization_suggestion fields; these are silently ignored by Pydantic since EntityDispositionSchema doesn't define the field, but tests still pass correctly.
tests/interface/test_display.py Fixture updated with generalization_suggestion; functionally identical to before since the field is Pydantic-ignored.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[SensitivityDispositionWorkflow\nCOL_SENSITIVITY_DISPOSITION] --> B[_correct_disposition_consistency\nauto-fix low+non-leave_as_is]
    B --> C[GeneralizationSuggestionsWorkflow\nCOL_GENERALIZATION_SUGGESTIONS]
    A --> D[COL_LATENT_ENTITIES\nalready in pipeline]
    C --> E[_format_disposition_block\nCOL_SENSITIVITY_DISPOSITION_BLOCK]
    D --> E
    A --> E
    E --> F[QAGenerationWorkflow\nMeaning Units + Privacy QA]
    C --> G[_format_rewrite_disposition_block\nCOL_REWRITE_DISPOSITION_BLOCK]
    A --> G
    G --> H[RewriteGenerationWorkflow\nFull Rewrite]
    F --> I[EvaluateWorkflow\nReanswer + Compare prompts\nwith abstraction tolerance]
    H --> I
    I --> J[RepairWorkflow / FinalJudge]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[SensitivityDispositionWorkflow\nCOL_SENSITIVITY_DISPOSITION] --> B[_correct_disposition_consistency\nauto-fix low+non-leave_as_is]
    B --> C[GeneralizationSuggestionsWorkflow\nCOL_GENERALIZATION_SUGGESTIONS]
    A --> D[COL_LATENT_ENTITIES\nalready in pipeline]
    C --> E[_format_disposition_block\nCOL_SENSITIVITY_DISPOSITION_BLOCK]
    D --> E
    A --> E
    E --> F[QAGenerationWorkflow\nMeaning Units + Privacy QA]
    C --> G[_format_rewrite_disposition_block\nCOL_REWRITE_DISPOSITION_BLOCK]
    A --> G
    G --> H[RewriteGenerationWorkflow\nFull Rewrite]
    F --> I[EvaluateWorkflow\nReanswer + Compare prompts\nwith abstraction tolerance]
    H --> I
    I --> J[RepairWorkflow / FinalJudge]
Loading

Reviews (5): Last reviewed commit: "fix: gen sugg" | Re-trigger Greptile

Comment thread src/anonymizer/engine/rewrite/parsers.py
Comment thread src/anonymizer/engine/schemas/rewrite.py Outdated
Comment thread src/anonymizer/engine/rewrite/qa_generation.py
Comment thread src/anonymizer/engine/schemas/rewrite.py Outdated
asteier2026 and others added 2 commits May 28, 2026 09:36
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Comment thread src/anonymizer/engine/rewrite/rewrite_generation.py
Comment thread src/anonymizer/engine/rewrite/qa_generation.py
Comment thread src/anonymizer/engine/rewrite/parsers.py
Comment thread src/anonymizer/engine/rewrite/domain_classification.py
@lipikaramaswamy

lipikaramaswamy commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

I think making utility QA aware of generalization and suppress-inference makes a lot of sense.

I do have a concern that generalization_suggestion may be pushing too much responsibility into sensitivity disposition. Disposition is already doing risk scoring, method selection, consistency reasoning, and per-entity coverage. Adding reusable rewrite-planning text makes the output contract much heavier, especially on rows with many entities.

I tried a disposition-only stress probe on two high-entity trace records:

Model ~108 grouped entities ~315 grouped entities
gpt-oss-120b timed out after 300s timed out after 300s
gpt-oss-20b returned JSON, but schema invalid; 105 entries, 99/108 covered, 9 missing, 5 duplicates, invalid category: "other" malformed/truncated JSON; only covered a small
fraction
qwen3.6-35b-a3b endpoint rate-limited in this run timed out after 300s
claude-opus-4-8 best result: 108/108 covered; parser recovered it, but raw schema failed on 4 low + generalize cases hit max_tokens=16384, truncated around 134 entries, not parseable

So I’m worried this field will be fragile in exactly the long/high-entity cases where rewrite already struggles.

Thoughts on moving this into a narrower follow-up protection-planning step that runs only for entities marked generalize / suppress_inference, possibly chunked / parallelized? The alternative I thought of was to split up the entities into chunks and have sensitivity disposition be generated in parallel - but I don't think that's the best for full context preservation.

Aside from the big design question above, I think this PR needs two fixes to truly work.

  • _format_rewrite_disposition_block() adds generalization_suggestion, but the rewrite prompt does not render it in the entity loop, so the rewriter never sees the suggestion.
  • because the schema defaults generalization_suggestion to "N/A", the meaning-unit prompt can receive "N/A" as the abstraction level for generalize. We should either require a real suggestion for generalize, or omit the field when it is the sentinel and let downstream prompts fall back.

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.

3 participants