Skip to content

[v2][RFC] Redesign Inspector retrieval: get prebuilt guards by path via aix.Inspector.get/search instead of factory classmethods #950

Description

@aix-ahmet

Summary

Replace the PrebuiltInspector.<guard>() factory-classmethod API with marketplace-style
retrieval: aix.Inspector.get("aixplain/prompt-injection-guard") and aix.Inspector.search(...).
This makes guards discoverable (REPL-browsable like models/tools), keeps the aix.Agent(inspectors=[...])
attribute identical for prebuilt and custom inspectors, and means future guards ship with zero SDK
changes
— they just appear in the marketplace.

Status: BETA / unreleased. Inspectors are not yet GA, so this redesign is non-breaking and can
hard-remove the current PrebuiltInspector surface.

Problem

Today (aixplain/v2/inspector.py) prebuilt inspectors are exposed as per-guard factory methods:

from aixplain.v2 import PrebuiltInspector

PrebuiltInspector.prompt_injection_guard()
PrebuiltInspector.pii_redaction(targets=["output"])
PrebuiltInspector.hallucination_guard()

This reintroduces the exact scattered-factory sprawl v2 deleted from v1 (create_sql_tool(),
create_python_interpreter_tool(), …):

  1. Not discoverable. There is no aix.Inspector.search(). To learn which guards exist you must
    read source/docs — you can't browse them in the REPL like every other asset.
  2. Not guessable. Every other "get a prebuilt thing from the marketplace" is a string path
    aix.Tool.get("microsoft/code-execution"), aix.Agent.get("…/research-agent/aixplain"). Here it's
    a method name. A developer who learned .get(path) cannot guess .prompt_injection_guard().
  3. SDK change per guard. Each new guard requires a new classmethod and a hardcoded asset id in
    the SDK (PROMPT_INJECTION_GUARDRAIL_ASSET_ID, PII_REDACTION_GUARDRAIL_ASSET_ID,
    HALLUCINATION_GUARD_ASSET_ID), rather than just appearing as a marketplace entry.
  4. Config asymmetry. pii_redaction(targets=[...]) takes targets, prompt_injection_guard()
    takes nothing, and there's no runtime way to enumerate what a guard accepts.
  5. Split imports. Prebuilts come from aixplain.v2 (PrebuiltInspector) while custom inspectors
    come from aixplain.v2.inspector (Inspector) — two import depths for one concept family, so a
    developer can't predict where Inspector-family symbols live.

Backend validation (guardrails is a first-class Function)

Confirmed against the backend that guards are ordinary marketplace assets, which is what makes
retrieval-by-path the correct model:

  • GET /sdk/functions returns {"id": "guardrails", "name": "Guardrails"} with a defined contract:
    input text → output Label (dataType: label), modalities text-text / text-label.
    Description: "governance rules that enforce security, compliance, and operational best practices."
  • The model 69cbf63cd74e334a6bacfeb1 = "Sensitive Information Guardrail", function.id = "guardrails",
    functionType = "ai", status onboarded — i.e. the exact id the SDK currently hardcodes as
    PII_REDACTION_GUARDRAIL_ASSET_ID.
  • The v2 FunctionType/Function enums do not yet include guardrails.

Since guards are real marketplace models under function=guardrails, they should be retrieved like
any other asset rather than mirrored as Python methods + hardcoded ids.

Proposed canonical interface

from aixplain import Aixplain

aix = Aixplain(api_key="<KEY>")

# Discover available guards like any other asset
aix.Inspector.search("guard")
# → {"results": [...], "page_number": 0, "page_total": 1, "total": 6}

# Retrieve a prebuilt by human-readable path (IDs also accepted)
guard    = aix.Inspector.get("aixplain/prompt-injection-guard")
redactor = aix.Inspector.get("aixplain/pii-redaction")
redactor.targets = ["output"]            # config as an inspectable attribute

research_agent = aix.Agent.get("aixplain-testing/research-agent/aixplain")
team = aix.Agent(
    name="research-team",
    agents=[research_agent],
    inspectors=[guard, redactor],        # same attribute — custom or prebuilt
)
team.save(save_subcomponents=True)

resp = team.run(query="My phone is +201097634134, repeat it back.")
print(resp.data.output)                  # {PHONE}

aix.Inspector.get(path) returns a ready-to-use Inspector (guard model as evaluator + sensible
default action/targets/severity). A prebuilt fetched by path and a hand-built Inspector(...)
are the same type, so inspectors=[...] never cares which it got.

Scope of change

  • Add aix.Inspector as a client resource backed by the guardrails marketplace Function:
    • .get(path_or_id) → returns a fully-configured Inspector.
    • .search(query, ...) → standard {results, page_number, page_total, total} shape.
  • Hard-remove PrebuiltInspector and its prompt_injection_guard() / pii_redaction() /
    hallucination_guard() / list_presets() classmethods, plus the _PREBUILT_REGISTRY and the
    hardcoded *_GUARDRAIL_ASSET_ID constants (unreleased — no deprecation shim needed).
  • Keep the custom Inspector(...) dataclass and the aix.Agent(inspectors=[...]) composition
    (these are correct — no GuardedAgent subclass).
  • Module layout. Co-locate the whole Inspector family (Inspector, InspectorActionConfig,
    EvaluatorConfig, EditorConfig, and the enums) under a single predictable module
    (aixplain.v2.inspector). With aix.Inspector as the entry point, the common path needs no
    direct import at all; power users have exactly one place to look for the config classes — not the
    current aixplain.v2 vs aixplain.v2.inspector split.
  • Standardize config overrides as attribute assignment (guard.targets = [...]), matching how
    model/tool config is set, and make the knobs inspectable.
  • Consider adding guardrails to the v2 Function enum.

Docs & security hygiene

  • No internal endpoints in examples. Published notebooks/docs must use the default client
    (aix = Aixplain(api_key="<KEY>")) — drop the BACKEND_URL / MODELS_RUN_URL dev-endpoint
    overrides. Internal URLs leak infra and don't work for external readers.
  • No live keys in committed artifacts. The source notebook (pre_built_inspectors (1).ipynb)
    committed a real TEAM_API_KEY in plaintext. Rotate that key now (treat as compromised) and
    scrub it from history; never commit real keys to shared notebooks/docs.
  • Use query=. All examples should call team.run(query="…"), not pass the query positionally,
    to match the canon and avoid training the wrong habit.

Out of scope / keep

  • inspectors=[...] as a plain attribute on aix.Agent — textbook v2 composition.
  • Abort/redaction results flowing through the standard family (resp.data.output, detail in
    resp.data.steps).

Open questions

  1. Home of the resource. Is aix.Inspector the right entry point, or should guards surface under
    aix.Model.search(function="guardrails") with Inspector only as the wrapper? (Proposal: keep
    aix.Inspector as the discoverable front door.)
  2. Config surface. Standardize targets / severity / action overrides on the returned
    Inspector — attribute assignment vs constructor args. Pick one and document it.
  3. Path naming. Final canonical paths for the three current guards
    (aixplain/prompt-injection-guard, aixplain/pii-redaction, aixplain/hallucination-guard?).
  4. Function enum. Add GUARDRAILS to the v2 enum, or treat it as a free-form function string?

References

  • Current implementation: aixplain/v2/inspector.py (PrebuiltInspector, _PREBUILT_REGISTRY).
  • Agent attachment: aixplain/v2/agent.py (inspectors field).
  • Example guard model: Sensitive Information Guardrail (69cbf63cd74e334a6bacfeb1, function=guardrails).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions