Skip to content

refactor(intrinsics): guardian.py IOContract subclasses + Adapter constants (Epic #929 Phase 1 follow-up) #1332

Closed

Description

@planetf1

Parent epic: #929
Phase: 1 (Wave 3 follow-up — guardian.py)
Deferred from: #1139 (guardian.py whole-file migration — PR #1323)
Blocked by: #1139 must merge; #1137 must merge (adds io_contract= to call_intrinsic in _util.py)
Independent of: #1138 (requirement_check), #1140 (Phase 2)
Blocks: nothing directly; Phase 2 (#1140) can start once all Phase 1 work is done


Background

#1139 (PR #1323) completed the guardian.py behavioural migration: Google-style docstrings, documents= keyword-only on the factuality functions, model_options= on all four helpers, and the policy_guardrails XOR fix. One acceptance-criteria item was explicitly deferred:

"Construct via new types" — create IOContract subclasses and module-level Adapter constants for each guardian helper, following the per-module migration pattern established in #1137 (rag.py).

The deferral was necessary because the call_intrinsic(io_contract=…) parameter that wires IOContract into the call path is being added to _util.py by #1137. Using it before #1137 merges would add a mid-wave cross-PR dependency.

What this issue covers

For each of the four helpers in mellea/stdlib/components/intrinsic/guardian.py:

Helper Adapter name Expected output key(s)
policy_guardrails policy-guardrails label xor score
guardian_check guardian-core guardian.score
factuality_detection factuality-detection score
factuality_correction factuality-correction correction

1. IOContract subclasses

Create a private _*Contract class for each helper that extends IOContract (from mellea.backends.adapters). Each class implements parse(result_json) to:

  • Validate required keys are present
  • Raise AdapterSchemaMismatchError on schema violations (replacing the current implicit KeyError)
  • Return the typed parsed value

Follow the pattern established by the _DictContract / _ListContract classes in rag.py (PR #1321).

2. Module-level Adapter constants

Add one Adapter constant per helper (e.g. _POLICY_GUARDRAILS_ADAPTER, _GUARDIAN_CHECK_ADAPTER, etc.) that couples the adapter name to its IOContract. Pass the constant into the call_intrinsic call via io_contract=.

3. Wire into existing call sites

Update the four call_intrinsic(...) calls in guardian.py to pass io_contract= using the new constants. Remove any inline key-access that is now handled by the contract's parse() method.

Success criteria

  • One IOContract subclass per helper, matching the output schema documented in the HF README for each adapter
  • AdapterSchemaMismatchError raised (not KeyError) when the adapter returns an unexpected schema
  • Module-level Adapter constants wired into call_intrinsic calls
  • All 19 existing unit tests in test_guardian_documents.py still pass unchanged
  • Existing qualitative tests in test_guardian.py still pass
  • ruff, mypy clean; docstring quality gate passes

What to look at first

Activity

  1. added
    p2Medium/low: minor bugs, niche features, polish, docs, tests, cleanup. Scoped, lower urgency.
    area/adapter-functionsGranite adapter functions: framework and adaptiers including RAG, Guardian, Core
    area/stdlibCore abstractions: Context, MOT, SamplingStrategy, formatters, serialization
    on Jun 24, 2026
  2. self-assigned this
    on Jun 24, 2026
  3. psschwei commented on Jun 25, 2026

    @psschwei
    Member

    Follow-up note from PR #1320 (#1138), relevant to the IOContract.parse() design here.

    The inline requirement-check validation that #1320 added reuses AdapterSchemaMismatchError for value violations (NaN / ±Inf / score outside [0.0, 1.0]), not just key/shape mismatches. In those cases the key is present and well-typed — only the value is out of contract — so expected_keys=frozenset({"score"}) is approximate and the error name reads slightly wrong.

    When the guardian (and eventual requirement-check) IOContract subclasses land, consider giving them a distinct value-error type (e.g. AdapterValueError) separate from AdapterSchemaMismatchError, so "wrong shape" and "out-of-range value" surface as different failures. guardian.score and factuality_detection's score in the table above have the same range/finiteness concern as requirement_check.

    This would also let the two currently-duplicated inline blocks in #1320 (core.py::requirement_check and requirement.py::requirement_check_to_bool) consolidate onto one contract.

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

Metadata

Metadata

Labels

area/adapter-functionsGranite adapter functions: framework and adaptiers including RAG, Guardian, Corearea/stdlibCore abstractions: Context, MOT, SamplingStrategy, formatters, serializationp2Medium/low: minor bugs, niche features, polish, docs, tests, cleanup. Scoped, lower urgency.refactor

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions