Build a Custom Target Adapter
Focus
Focus
Prisma AIRS

Build a Custom Target Adapter

Table of Contents

Build a Custom Target Adapter

Build your adapter incrementally, starting from the minimal working pattern and adding authentication, session handling, or full-control transport as required by your target.
Where Can I Use This?What Do I Need?
  • Prisma AIRS (AI Red Teaming)
You write plain Python functions; the SDK types (PreProcessResult, raise_rate_limited, and others) are available without importing. The adapter contract supports two main patterns: the pre_process/post_process pair, where the platform makes the HTTP call for you, and the call_target function, where you make the call yourself. Add authenticate() for dynamic tokens and session_pre_process() for server-side session initialization. For the full code contract covering every function signature, the context object, all return types, error signaling, and WebSocket examples, see the Adapter Contract (SDK Reference).
  1. Implement pre_process() and post_process() to build the request and extract the reply.
    The following example targets a standard JSON chat API that accepts an API key in the Authorization header and returns {"reply": "..."}. endpoint is a var and api_key is a secret, both declared in the adapter's configuration.
    def pre_process(context, inference_input): return PreProcessResult( url=context.vars["endpoint"], headers={"Authorization": f"Bearer {context.secrets['api_key']}"}, json_body={"message": inference_input.prompt}, ) def post_process(context, raw_response): return PostProcessResult(output=raw_response.json_body["reply"])
    This is a complete, working adapter. Proceed to the next step only if your target requires a dynamically obtained token.
  2. (Optional) Implement authenticate() to obtain and cache a dynamic token.
    The platform calls authenticate(), caches the result, and passes it to your other functions as context.auth, refreshing it automatically when the TTL expires. The following example exchanges OAuth2 client credentials for a bearer token:
    def authenticate(context): resp = context.http.post( context.vars["token_url"], data={ "grant_type": "client_credentials", "client_id": context.secrets["client_id"], "client_secret": context.secrets["client_secret"], }, ) body = resp.json() return AuthResult(ttl=body.get("expires_in", 3600), data={"token": body["access_token"]}) def pre_process(context, inference_input): return PreProcessResult( url=context.vars["endpoint"], headers={"Authorization": f"Bearer {context.auth['token']}"}, json_body={"message": inference_input.prompt}, )
  3. (Optional) Implement session_pre_process() to create a server-side session before the conversation begins.
    session_pre_process() runs once on the first turn of every conversation. Its return value is available as context.session on every turn. The following example creates a session and passes the session ID with each message:
    def session_pre_process(context): resp = context.http.post( context.vars["endpoint"] + "/sessions", headers={"Authorization": f"Bearer {context.auth['token']}"}, ) return SessionPreProcessResult(session_state={"session_id": resp.json()["id"]}) def pre_process(context, inference_input): return PreProcessResult( url=context.vars["endpoint"] + "/chat", headers={"Authorization": f"Bearer {context.auth['token']}"}, json_body={"session_id": context.session["session_id"], "message": inference_input.prompt}, )
    session_pre_process() runs on the first turn of every conversation, including single-turn scans. This is independent of the target's multi-turn toggle in the target configuration.
  4. (Optional) Use call_target() instead of pre_process()/post_process() when the target uses a non-HTTP transport.
    Use call_target() for WebSocket connections, GraphQL, SDK-based clients, or custom framing and streaming protocols. You make the call(s) yourself and return the reply directly. The following example performs the equivalent of step 1 in a single function:
    import json import websockets.sync.client as ws def call_target(context, inference_input): conn = ws.connect( context.vars["ws_url"], additional_headers={"Authorization": f"Bearer {context.auth['token']}"}, ) try: conn.send(json.dumps({"message": inference_input.prompt})) reply = "" for frame in conn: event = json.loads(frame) if event.get("type") == "chunk": reply += event["text"] elif event.get("type") == "done": break return CallTargetResult(output=reply) finally: conn.close()
    Defining call_target() replaces pre_process()/post_process(). If both patterns exist in the same script, call_target() takes precedence and the pre/post functions are not called. authenticate() and session_pre_process() work identically with either pattern. For scenarios requiring multiple HTTP calls, use context.http inside pre_process() for per-turn calls or inside session_pre_process() for one-time setup. Reserve call_target() for cases where the transport itself is not plain HTTP.