Skip to content

Using the Moderations CLI

The dr-moderation CLI lets you manage guards and test moderation pipelines from the terminal — no Python code required.

インストール

End-user — the dr-moderation binary lands on your PATH automatically:

pip install 'datarobot-moderations[all]'
dr-moderation --help 

備考

Python 3.10 – 3.12 is required.

Developer / contributor — Poetry places the binary inside .venv/bin/, which is not on your PATH until the venv is active. Pick one:

poetry shell                        # Option A: activate for the session
poetry run dr-moderation --help     # Option B: one-off prefix
make cli ARGS="evaluate --help"     # Option C: Makefile shortcut 

認証

Commands that call the DataRobot API need credentials. Set them once per session:

export DATAROBOT_ENDPOINT="https://app.datarobot.com/api/v2"
export DATAROBOT_API_TOKEN="your-api-token" 

Or pass them as global flags (flags take precedence over env vars):

dr-moderation --endpoint <url> --token <token> <command> 

Commands

evaluate

Evaluate a prompt and/or response through the local ModerationPipeline. Supports every guard type including LLM Gateway (llm_type: llmGateway) — no deployment required.

The config file must use the Python SDK snake_case schema (see Moderations guardrails for the full field reference).

dr-moderation evaluate [OPTIONS] 
オプション 必須 デフォルト 説明
--config-file FILE はい Moderation config YAML (snake_case SDK format)
--prompt TEXT No * Prompt text; evaluated against prescore guards
--response TEXT No * Response text; evaluated against postscore guards. Also pass --prompt for guards that need both (e.g. faithfulness, task_adherence)
--as-json いいえ false Emit results as JSON — useful for scripting

* At least one of --prompt or --response is required.

Example output (human-readable):

── Prescore (prompt) ──────────────────────────────
  Blocked  : False
  Metrics  :
    Prompts_token_count: 4
  Latency  : 0.05s 

Evaluate-examples

Token-count guard on a prompt:

dr-moderation evaluate \
  --config-file docs/examples/token_count_config.yaml \
  --prompt "Hello, world!" 

LLM Gateway task-adherence guard:

dr-moderation evaluate \
  --config-file docs/examples/llm_gateway_config.yaml \
  --prompt "What is DataRobot?" \
  --response "DataRobot is an AI platform." 

Evaluate both, emit JSON, pipe to jq:

dr-moderation evaluate \
  --config-file docs/examples/llm_gateway_config.yaml \
  --prompt "What is DataRobot?" \
  --response "DataRobot is an AI platform." \
  --as-json | jq '.postscore.metrics' 

Ready-made configs in docs/examples/:

  • token_count_config.yaml — Prompt + response token-count guards
  • llm_gateway_config.yaml — token-count prompt guard + LLM Gateway task_adherence

add-guard

Add guards to an existing DataRobot custom model. Creates a new custom model version with the guards attached and prints the version ID to stdout.

How it works:

  1. You create and register a custom model (your LLM) in DataRobot — this gives you a customModelId.
  2. You define guards in a camelCase YAML file.
  3. add-guard POSTs the config to /guardConfigurations/toNewCustomModelVersion/. DataRobot creates a new version of the model with the guards and returns the customModelVersionId.
  4. Deploy that new version — it will now enforce your guards on every prompt/response.
dr-moderation add-guard [OPTIONS] 
オプション 必須 デフォルト 説明
--custom-model-id TEXT はい ID of the custom model (find it in the DataRobot UI under Model Workshop → Custom Models)
--config-file FILE はい YAML list of guard configurations (camelCase API format)
--timeout-sec INTEGER いいえ 60 Per-guard timeout in seconds
--timeout-action [score\ | block] いいえ スコア Action on timeout: score passes through; block rejects

Example output:

6797abc123def456789abcde 

The printed ID is the new customModelVersionId — pass it to subsequent API or SDK calls to deploy the version.

例:

# Add guards, capture the new version ID
VERSION_ID=$(dr-moderation add-guard \
  --custom-model-id 6793e6b2114f17240fa2194c \
  --config-file docs/examples/add_guard_config.yaml)
echo "New version: ${VERSION_ID}"

# Block if any guard exceeds 30 s
dr-moderation add-guard \
  --custom-model-id 6793e6b2114f17240fa2194c \
  --config-file docs/examples/add_guard_config.yaml \
  --timeout-sec 30 \
  --timeout-action block 

agent a2a connect

Verify connectivity to a remote A2A agent by fetching its agent card from /.well-known/agent.json.

dr-moderation agent a2a connect [OPTIONS] 
オプション 必須 説明
--url TEXT はい Base URL of the remote A2A agent
--deployment-id TEXT いいえ DataRobot deployment ID to verify alongside the agent

例:

# 1. Start a one-line A2A mock (serves /.well-known/agent.json on port 8765)
python3 - << 'EOF'
import json
from http.server import BaseHTTPRequestHandler, HTTPServer

CARD = {"name": "My Agent", "version": "1.0.0", "capabilities": ["moderation"]}

class H(BaseHTTPRequestHandler):
    def do_GET(self):
        body = json.dumps(CARD).encode()
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(body)
    def log_message(self, *_): pass

HTTPServer(("localhost", 8765), H).serve_forever()
EOF &

# 2. Connect to it
dr-moderation agent a2a connect --url http://localhost:8765 

Production examples:

# Verify a remote A2A agent is reachable
dr-moderation agent a2a connect --url https://my-agent.example.com

# Also verify the backing DataRobot deployment
dr-moderation agent a2a connect \
  --url https://my-agent.example.com \
  --deployment-id 6793e6b2114f17240fa2194c 

serve

Start a JSON-RPC 2.0 server so that non-Python applications (Java, Go, C#, …) can evaluate prompts and responses through the full moderation pipeline without HTTP/REST overhead or a Python runtime in their own process.

Two transports are available:

Transport 仕組み 最適な用途
stdio (default) Caller spawns dr-moderation serve as a subprocess; newline-delimited JSON on stdin/stdout Single-caller, zero network setup
ws aiohttp WebSocket server; multiple callers share one long-running instance Containerised / multi-caller deployments
dr-moderation serve [OPTIONS] 
オプション 必須 デフォルト 説明
--transport [stdio\ | ws] いいえ stdio Transport backend
--config-file FILE いいえ Pre-load a pipeline YAML at startup. For ws this pipeline is shared across all connections; for stdio the caller can still send initialize to override it
--host TEXT いいえ 127.0.0.1 Bind address (ws only)
--port INTEGER いいえ 9000 Bind port (ws only)
--log-level [debug\ | info\ | warning\ | error] いいえ warning Logging verbosity — all output goes to stderr, never stdout

All diagnostic output goes to stderr. The stdout stream carries only JSON-RPC messages so callers can parse it without noise.

Wire format

Messages are newline-delimited JSON (one complete JSON object per line, \n-terminated). Both requests and responses follow JSON-RPC 2.0.

Request (caller → server):

{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"config_path": "/path/to/config.yaml"}} 

Response (server → caller):

{"jsonrpc": "2.0", "id": 1, "result": {"ok": true}} 

方法

方法 Call order params keys 説明
initialize Before evaluate_* config_path (string, required) Load the moderation pipeline from a YAML file. Must be called before any evaluate_* method unless --config-file was passed at startup. Returns {"ok": true}
evaluate_prompt After initialize prompt (string, required) Run prescore guards and return an EvaluationResult
evaluate_response After initialize response (string, required); prompt (string, optional); pipeline_interactions (string, optional) Run postscore guards and return an EvaluationResult
shutdown Any time (なし) Signal the server to stop and return {"ok": true}. stdio: server exits after sending the response. ws: closes the current connection; the server process keeps running

Complete response example (evaluate_prompt)

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "blocked": false,
    "blocked_message": null,
    "replaced": false,
    "replacement": null,
    "metrics": {
      "Prompts_token_count": 4
    },
    "latency_sec": 0.012345
  }
} 

When a guard blocks content blocked is true, blocked_message holds the guard's configured message, and latency_sec is always present. When a replace-action guard fires, replaced is true and replacement holds the sanitized text.

Bash (stdio — interactive test):

# Pre-load a config, then evaluate a prompt
dr-moderation serve --config-file moderation_config.yaml --transport stdio <<'EOF'
{"jsonrpc":"2.0","id":1,"method":"evaluate_prompt","params":{"prompt":"Hello, world!"}}
{"jsonrpc":"2.0","id":2,"method":"shutdown","params":{}}
EOF 

Python (subprocess, stdio):

import json
import subprocess
import sys

proc = subprocess.Popen(
    [sys.executable, "-m", "datarobot_dome.cli", "serve",
     "--transport", "stdio",
     "--config-file", "moderation_config.yaml"],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.DEVNULL,  # discard diagnostics; redirect to sys.stderr to surface them
    text=True,
    bufsize=1,
)

def rpc(method, params, *, req_id):
    msg = json.dumps({"jsonrpc": "2.0", "id": req_id, "method": method, "params": params})
    proc.stdin.write(msg + "\n")
    proc.stdin.flush()
    # Skip any stdout lines that are not valid JSON (startup messages, warnings).
    while True:
        line = proc.stdout.readline()
        try:
            return json.loads(line)
        except json.JSONDecodeError:
            continue

result = rpc("evaluate_prompt", {"prompt": "Hello, world!"}, req_id=1)
print(result["result"])

rpc("shutdown", {}, req_id=2)
proc.wait() 

Go (stdio):

package main

import (
    "bufio"
    "encoding/json"
    "fmt"
    "os/exec"
)

func main() {
    cmd := exec.Command("dr-moderation", "serve",
        "--transport", "stdio",
        "--config-file", "moderation_config.yaml")
    stdin, _ := cmd.StdinPipe()
    stdout, _ := cmd.StdoutPipe()
    _ = cmd.Start()

    scanner := bufio.NewScanner(stdout)

    send := func(req any) {
        b, _ := json.Marshal(req)
        fmt.Fprintln(stdin, string(b))
    }
    recv := func() map[string]any {
        // Skip non-JSON lines (startup messages, log output on stdout)
        for scanner.Scan() {
            var m map[string]any
            if err := json.Unmarshal(scanner.Bytes(), &m); err == nil {
                return m
            }
        }
        return nil
    }

    send(map[string]any{"jsonrpc": "2.0", "id": 1, "method": "evaluate_prompt",
        "params": map[string]any{"prompt": "Hello, world!"}})
    resp := recv()
    fmt.Println(resp["result"])

    send(map[string]any{"jsonrpc": "2.0", "id": 2, "method": "shutdown", "params": map[string]any{}})
    cmd.Wait()
} 

Java (stdio):

import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.*;
import java.util.Map;

public class ModerationClient {
    public static void main(String[] args) throws Exception {
        ProcessBuilder pb = new ProcessBuilder(
            "dr-moderation", "serve",
            "--transport", "stdio",
            "--config-file", "moderation_config.yaml");
        pb.redirectError(ProcessBuilder.Redirect.DISCARD);
        Process proc = pb.start();

        ObjectMapper mapper = new ObjectMapper();
        var writer = new PrintWriter(new BufferedWriter(
            new OutputStreamWriter(proc.getOutputStream())), true);
        var reader = new BufferedReader(
            new InputStreamReader(proc.getInputStream()));

        // Send request
        String req = mapper.writeValueAsString(Map.of(
            "jsonrpc", "2.0", "id", 1,
            "method", "evaluate_prompt",
            "params", Map.of("prompt", "Hello, world!")));
        writer.println(req);

        // Read response — skip non-JSON lines
        String line;
        while ((line = reader.readLine()) != null) {
            try {
                var resp = mapper.readValue(line, Map.class);
                System.out.println(resp.get("result"));
                break;
            } catch (Exception ignored) {}
        }

        writer.println(mapper.writeValueAsString(Map.of(
            "jsonrpc", "2.0", "id", 2, "method", "shutdown", "params", Map.of())));
        proc.waitFor();
    }
} 

C# (stdio):

using System.Diagnostics;
using System.Text.Json;

var proc = new Process {
    StartInfo = new ProcessStartInfo("dr-moderation") {
        Arguments = "serve --transport stdio --config-file moderation_config.yaml",
        RedirectStandardInput  = true,
        RedirectStandardOutput = true,
        RedirectStandardError  = true,
        UseShellExecute = false,
    }
};
proc.Start();
_ = proc.StandardError.ReadToEndAsync(); // drain stderr on a background task

void Send(object req) => proc.StandardInput.WriteLine(JsonSerializer.Serialize(req));
JsonElement Recv() {
    // Skip non-JSON lines (startup messages, warnings)
    while (true) {
        var line = proc.StandardOutput.ReadLine() ?? throw new EndOfStreamException();
        try { return JsonDocument.Parse(line).RootElement; } catch { }
    }
}

Send(new { jsonrpc = "2.0", id = 1, method = "evaluate_prompt",
           @params = new { prompt = "Hello, world!" } });
var resp = Recv();
Console.WriteLine(resp.GetProperty("result"));

Send(new { jsonrpc = "2.0", id = 2, method = "shutdown", @params = new { } });
proc.WaitForExit(); 

WebSocket (ws transport):

# Start the server (runs until killed)
dr-moderation serve --transport ws --host 127.0.0.1 --port 9000 \
  --config-file moderation_config.yaml

# In another terminal — connect with any WebSocket client (e.g. websocat)
echo '{"jsonrpc":"2.0","id":1,"method":"evaluate_prompt","params":{"prompt":"Hello"}}' \
  | websocat ws://127.0.0.1:9000 

YAML schema quick reference

The two commands use different schemas — they are not interchangeable:

Command 形式 Key fields
add-guard DataRobot API — camelCase ootbType, stages (list), intervention
evaluate Python SDK — snake_case ootb_type, stage (string or list), llm_type, llm_gateway_model_id

add-guard config (camelCase)

Sent directly to /guardConfigurations/toNewCustomModelVersion/. The file must be a YAML list.

- name: Prompt Token Count
  type: ootb
  ootbType: token_count
  stages: [prompt]
  intervention:
    action: report
    allowedActions: [report, block]
    message: " "
    sendNotification: false
    conditions: [] 
フィールド 必須 備考
name はい Unique per config
type はい ootb · guardModel · userModel · nemo
stages はい List: [prompt], [response], or [prompt, response]
ootbType When type: ootb token_count, faithfulness, rouge_1, etc.
modelInfo When type: guardModel inputColumnName, outputColumnName, targetType, classNames
intervention いいえ action, conditions, message; omit to measure only

evaluate config (snake_case)

Consumed by ModerationPipeline.from_yaml. For the full field reference see Moderations guardrails.

The key difference from add-guard: use llm_type: llmGateway with llm_gateway_model_idno deployment_id needed:

- name: Task Adherence
  type: ootb
  ootb_type: task_adherence
  stage: response
  llm_type: llmGateway
  llm_gateway_model_id: "azure/gpt-4o-2024-11-20"
  intervention:
    action: block
    message: "Response does not address the task."
    conditions:
      - comparator: lessThan
        comparand: 0.5 

Exit codes

コード Meaning
0 成功
1 Runtime error (API error, bad YAML, connection refused)
2 Invalid CLI usage (missing required option, unknown value)

Non-zero exits write a descriptive message to stderr.