# Using the Moderations CLI

> Using the Moderations CLI - How to install, authenticate, and run the dr-moderation CLI to manage
> guards and test moderation pipelines.

This Markdown file sits beside the HTML page at the same path (with a `.md` suffix). It summarizes the topic and lists links for tools and LLM context.

Companion generated at `2026-06-10T05:26:01.507383+00:00` (UTC).

## Primary page

- [Using the Moderations CLI](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/using-moderations-with-the-cli.html.md): Full documentation for this topic (Markdown sidecar).

## Sections on this page

- [Installation](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/using-moderations-with-the-cli.html.md#installation): In-page section heading.
- [Authentication](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/using-moderations-with-the-cli.html.md#authentication): In-page section heading.
- [Commands](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/using-moderations-with-the-cli.html.md#commands): In-page section heading.
- [evaluate](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/using-moderations-with-the-cli.html.md#evaluate): In-page section heading.
- [Evaluate-examples](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/using-moderations-with-the-cli.html.md#evaluate-examples): In-page section heading.
- [add-guard](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/using-moderations-with-the-cli.html.md#add-guard): In-page section heading.
- [agent a2a connect](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/using-moderations-with-the-cli.html.md#agent-a2a-connect): In-page section heading.
- [serve](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/using-moderations-with-the-cli.html.md#serve): In-page section heading.
- [Wire format](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/using-moderations-with-the-cli.html.md#wire-format): In-page section heading.
- [Methods](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/using-moderations-with-the-cli.html.md#methods): In-page section heading.
- [Complete response example (evaluate_prompt)](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/using-moderations-with-the-cli.html.md#complete-response-example-evaluate_prompt): In-page section heading.
- [Examples](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/using-moderations-with-the-cli.html.md#examples): In-page section heading.
- [YAML schema quick reference](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/using-moderations-with-the-cli.html.md#yaml-schema-quick-reference): In-page section heading.
- [add-guardconfig (camelCase)](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/using-moderations-with-the-cli.html.md#add-guard-config-camelcase): In-page section heading.
- [evaluateconfig (snake_case)](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/using-moderations-with-the-cli.html.md#evaluate-config-snake_case): In-page section heading.
- [Exit codes](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/using-moderations-with-the-cli.html.md#exit-codes): In-page section heading.

## Related documentation

- [Developer documentation](https://docs.datarobot.com/en/docs/api/index.html.md): Linked from this page.
- [Code-first tools](https://docs.datarobot.com/en/docs/api/code-first-tools/index.html.md): Linked from this page.
- [DataRobot Moderations library](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/index.html.md): Linked from this page.
- [Moderations guardrails](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/moderations-guardrails.html.md): Linked from this page.

## Documentation content

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

## Installation

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

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

> [!NOTE] Note
> 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
```

## Authentication

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](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/moderations-guardrails.html.md) for the full field reference).

```
dr-moderation evaluate [OPTIONS]
```

| Option | Required | Default | Description |
| --- | --- | --- | --- |
| --config-file FILE | Yes | — | 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 | No | 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."
```

```
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]
```

| Option | Required | Default | Description |
| --- | --- | --- | --- |
| --custom-model-id TEXT | Yes | — | ID of the custom model (find it in the DataRobot UI under Model Workshop → Custom Models) |
| --config-file FILE | Yes | — | YAML list of guard configurations (camelCase API format) |
| --timeout-sec INTEGER | No | 60 | Per-guard timeout in seconds |
| --timeout-action [score\\|block] | No | score | 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.

Examples:

```
# 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](https://google.github.io/A2A/) agent by fetching its agent card from `/.well-known/agent.json`.

```
dr-moderation agent a2a connect [OPTIONS]
```

| Option | Required | Description |
| --- | --- | --- |
| --url TEXT | Yes | Base URL of the remote A2A agent |
| --deployment-id TEXT | No | DataRobot deployment ID to verify alongside the agent |

Examples:

```
# 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 | How it works | Best for |
| --- | --- | --- |
| 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]
```

| Option | Required | Default | Description |
| --- | --- | --- | --- |
| --transport [stdio\\|ws] | No | stdio | Transport backend |
| --config-file FILE | No | — | 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 | No | 127.0.0.1 | Bind address (ws only) |
| --port INTEGER | No | 9000 | Bind port (ws only) |
| --log-level [debug\\|info\\|warning\\|error] | No | 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](https://www.jsonrpc.org/specification).

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}}
```

#### Methods

| Method | Call order | params keys | Description |
| --- | --- | --- | --- |
| 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 | (none) | 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.

#### Examples

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 | Format | 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: []
```

| Field | Required | Notes |
| --- | --- | --- |
| name | Yes | Unique per config |
| type | Yes | ootb · guardModel · userModel · nemo |
| stages | Yes | 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 | No | action, conditions, message; omit to measure only |

### evaluate config (snake_case)

Consumed by `ModerationPipeline.from_yaml`. For the full field reference see [Moderations guardrails](https://docs.datarobot.com/en/docs/api/code-first-tools/moderations-library/moderations-guardrails.html.md).

The key difference from `add-guard`: use `llm_type: llmGateway` with `llm_gateway_model_id` — no `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

| Code | Meaning |
| --- | --- |
| 0 | Success |
| 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.
