NeMo Guardrails

NeMo Guardrails 为 LLM 应用提供可编程的安全控制。它作为位于模型前方的独立服务运行,并且可以强制执行:

  • 敏感数据检测(例如,输入和输出中的 PII)。
  • 内容策略(例如,禁止的话题、竞品提及)。
  • 使用 Colang 和 Python 编写的自定义校验流程。

TrustyAI Operator 通过 NemoGuardrails 自定义资源(CR)公开 NeMo Guardrails。本文重点介绍一个基础部署,它:

  • 保护在 serving 平台上已部署的现有模型。
  • 使用 NeMo Guardrails 进行输入/输出过滤和简单的业务规则检查。

前提条件

  • 已安装 TrustyAI Operator(参见 安装 TrustyAI)。
  • 在 serving 平台上已经部署了一个模型(例如 vLLM),并且该模型暴露了 OpenAI-compatible API。

架构

从高层来看,请求路径如下:

Client → NeMo Guardrails service → model predictor (OpenAI-compatible API)

NeMo Guardrails:

  • 接收 OpenAI 风格的 chat/completions 请求。
  • 执行已配置的 rails(敏感数据检测、长度检查、禁止话题等)。
  • 对于允许的请求,将其转发给底层模型。
  • 对于被阻止的请求,在不调用模型的情况下返回适当的 assistant 消息。

TrustyAI Operator 通过 NemoGuardrails CR 管理 NeMo Guardrails server 的 Pod 和 Service。随后,可以使用集群中选定的 ingress 或 gateway 方案将该 Service 暴露到外部。

NeMo 配置 ConfigMap

NeMo Guardrails 期望一个配置目录,其中通常包含:

  • config.yaml:NeMo Guardrails 的主配置文件。
  • rails.co:实现输入/输出 rails 和其他控制逻辑的 Colang flows。
  • actions.py:Colang flows 可调用的 Python actions,用于执行自定义逻辑。
NeMo 配置 ConfigMap 示例
apiVersion: v1
kind: ConfigMap
metadata:
  name: nemo-config
  namespace: <your-namespace>
data:
  config.yaml: |
    models:
      - type: main
        engine: openai
        parameters:
          # Internal URL of the model predictor, OpenAI-compatible
          openai_api_base: "https://<model-predictor-host>:<port>/v1"
          model_name: "<model-name>"

    rails:
      config:
        sensitive_data_detection:
          input:
            entities:
              - EMAIL_ADDRESS
          output:
            entities:
              - EMAIL_ADDRESS
      input:
        flows:
          - detect sensitive data on input
          - check message length
          - check forbidden words
      output:
        flows:
          - detect sensitive data on output

  rails.co: |
    define flow check message length
      $length_result = execute check_message_length
      if $length_result == "blocked_too_long"
        bot inform message too long
        stop
      if $length_result == "warning_long"
        bot warn message long

    define bot inform message too long
      "Please keep your message under 100 words for better assistance."

    define bot warn message long
      "That's quite detailed! I'll help as best I can."

    define flow check forbidden words
      $forbidden_result = execute check_forbidden_words
      if $forbidden_result != "allowed"
        bot inform forbidden content
        stop

    define bot inform forbidden content
      "I can't help with that type of request. Please ask something else."

  actions.py: |
    from typing import Optional

    from nemoguardrails.actions import action


    @action(is_system_action=True)
    async def check_message_length(context: Optional[dict] = None) -> str:
        """
        Example custom action called from Colang via:
          $length_result = execute check_message_length
        Input:
          - context: dict-like object provided by NeMo; it contains the latest user
            message under the "user_message" key.
        Output:
          - A short string that the Colang flow interprets, for example:
            * "blocked_too_long": too long, directly block.
            * "warning_long": too long, give a warning but still continue.
            * "allowed": length is acceptable.
        """
        user_message = (context or {}).get("user_message", "")
        word_count = len(user_message.split())
        max_words = 20

        if word_count > max_words:
            return "blocked_too_long"
        if word_count > int(max_words * 0.8):
            return "warning_long"
        return "allowed"


    @action(is_system_action=True)
    async def check_forbidden_words(context: Optional[dict] = None) -> str:
        """
        Example custom action for simple forbidden word checks.
        It is called from Colang via:
          $forbidden_result = execute check_forbidden_words
        and returns:
          - "allowed" when no forbidden word is present.
          - a non-"allowed" value (for example, "blocked_password") when a forbidden
            word is detected.
        """
        user_message = (context or {}).get("user_message", "").lower()

        forbidden_words = ["password", "hack", "exploit", "illegal", "violence"]
        for word in forbidden_words:
            if word in user_message:
                return f"blocked_{word}"

        return "allowed"
  • config.yaml 基础

在上面的示例中,config.yaml

  • models 部分声明一个后端模型,并通过 openai_api_basemodel_name 配置 OpenAI-compatible 端点。

  • rails.config.sensitive_data_detection 下配置内置的 PII 检测:

    • input.entities / output.entities 列出需要保护的实体类型(例如,EMAIL_ADDRESSPERSON)。
    • detect sensitive data on input / detect sensitive data on output rails 运行时,NeMo 会使用此配置自动调用其内部检测器。
  • 通过 rails.input.flowsrails.output.flows 定义哪些 rails 运行,以及运行顺序:

    • detect sensitive data on input / detect sensitive data on output 是由 sensitive_data_detection 支持的内置 rails。
    • check message lengthcheck forbidden words 是在 rails.co 中实现、并由 actions.py 中的 Python actions 支持的自定义 rails。
  • <model-predictor-host><port><model-name> 替换为实际的 predictor service URL 和模型名称。

  • 确保后端 predictor 实现了 OpenAI-compatible 的 /v1/chat/completions API。

  • 如需了解 config.yaml 的更高级配置(其他 rail 类型、prompts、tracing、knowledge base,以及与其他安全提供方的集成),请参考官方 NeMo Guardrails YAML 配置说明:Nvidia NeMo Guardrails Configuration

rails.co 基础

在此示例中,rails.co 定义了 两个自定义输入 rails

  • define flow check message length
    • flow 名称 check message length 必须与 config.yamlrails.input.flows 的条目匹配。
    • $length_result = execute check_message_length 会运行来自 actions.py 的 Python action check_message_length,并传入当前对话上下文。
    • if 语句根据返回的字符串进行分支,或者:
      • 调用 bot ... block 发送回复(例如 bot inform message too long),并且
      • stop 以中止后续处理并防止调用 LLM,
      • 或者在返回 "allowed" 时不执行任何操作,并允许流水线继续到下一个 rail。
  • define flow check forbidden words
    • 使用相同模式,但调用 check_forbidden_words action,仅在返回值不是 "allowed" 时进行阻止。

补充说明:

  • bot ... blocks(例如 bot inform message too long)定义了预设的 assistant 消息;当某个 rail 决定停止流水线时,这些消息会直接发送给客户端,而不会联系后端 LLM。
  • rails.co 中定义的 rails 会按照 rails.input.flows / rails.output.flows 中列出的顺序执行。像 detect sensitive data on input 这样的内置 rails,会根据它们在列表中的位置在自定义 rails 之前或之后运行。
  • 此处展示的 Colang 是一个最小示例。它支持更复杂的 flows(多步骤、变量、附加 actions);完整语法和能力请参阅 NeMo Guardrails 文档中的 Colang 参考。一个很好的起点是 Colang 2.0 入门指南:Colang Getting Started

actions.py 基础

actions.py 文件包含带有 @action 装饰器的 Python 函数,Colang flows 可以通过 execute <action_name> 调用这些函数:

  • Actions 接收一个 context 对象,它是由 NeMo 填充的类 dict 结构(例如,在 "user_message" 下包含最新的用户消息)。
  • Actions 返回一个值(通常是简短字符串),由 Colang flows 进行解释并据此分支。

在此示例中:

  • check_message_length
    • 检查 context["user_message"],计算词数,并返回:
      • 当消息应被拒绝时返回 "blocked_too_long"
      • 当需要警告但流水线仍可继续时返回 "warning_long"
      • 当消息长度可接受时返回 "allowed"
  • check_forbidden_words
    • 将用户消息转换为小写,搜索禁止词,并:
      • 在未发现任何内容时返回 "allowed"
      • 在存在禁止词时返回非 "allowed" 值(例如 "blocked_password")。

这些模式可以扩展,以实现更复杂的 guardrails,例如结构化检查、数值阈值或调用外部服务。

部署 NemoGuardrails 自定义资源

在 ConfigMap 和 token Secret 就绪后,创建一个 NemoGuardrails CR 来部署 NeMo Guardrails service:

apiVersion: trustyai.opendatahub.io/v1alpha1
kind: NemoGuardrails
metadata:
  name: nemo-guardrails
  namespace: <your-namespace>
  annotations:
    # When true, the exposed route requires a Bearer token for incoming requests to NeMo Guardrails.
    security.opendatahub.io/enable-auth: "true"

    # When the backend LLM is exposed over HTTPS with a custom CA, set this annotation
    # to the name of a Secret that contains the CA bundle in a key such as `ca.crt`.
    # The operator mounts this Secret and configures NeMo Guardrails TLS trust accordingly.
    # Example:
    # trustyai.opendatahub.io/ca-secret-name: llm-backend-ca
spec:
  nemoConfigs:
    - name: nemo-config
      configMaps:
        - nemo-config
      default: true
  env:
    - name: OPENAI_API_KEY
      # For authenticated backends, use a Secret-ref token:
      # valueFrom:
      #   secretKeyRef:
      #     name: api-token-secret
      #     key: token
      # For internal, unauthenticated HTTP backends, a placeholder value is sufficient:
      value: "<placeholder>"

    # Optional: offline environments
    # NeMo Guardrails may fetch the Public Suffix List via tldextract. In environments
    # without Internet access, set TLDEXTRACT_CACHE to use the cached
    # public_suffix_list data bundled in the NeMo Guardrails Server image. Note that
    # the bundled list may not be the latest.
    # - name: TLDEXTRACT_CACHE
    #   value: "/app/.cache/"

    # TLS behaviour for backend LLM:
    # - HTTP backends:
    #   * Set SSL_CERT_FILE to an empty string to disable certificate lookup.
    #   * Use an http:// URL in config.yaml (openai_api_base).
    # - HTTPS backends with a custom CA:
    #   * Remove SSL_CERT_FILE from env.
    #   * Add the trustyai.opendatahub.io/ca-secret-name annotation above, pointing
    #     to a Secret that contains the CA certificate bundle.
    - name: SSL_CERT_FILE
      value: ""

关键字段:

  • nemoConfigs:引用一个或多个配置包;每个配置包可以映射到一个或多个包含 NeMo Guardrails 配置文件的 ConfigMap。
  • env.OPENAI_API_KEY:NeMo Guardrails 用于对后端模型端点进行身份验证的 token(例如 vLLM service)。对于内部的、未认证的推理服务,该值可以直接设置为 value: "<placeholder>",后端不会使用它。对于仅 HTTP 的推理服务,后端 URL 不需要 TLS 证书。
  • security.opendatahub.io/enable-auth:当设置为 "true" 时,NeMo Guardrails 的路由会受到集群认证保护,并且需要 Bearer token。

应用:

kubectl apply -f nemo-guardrails-cr.yaml -n <your-namespace>

CR 创建后,operator 会进行协调并创建:

  • 用于 NeMo Guardrails server 的 Deployment。
  • 一个在集群内部暴露 NeMo Guardrails HTTP 端点的 Service。

等待 Deployment Pod 变为 Ready

kubectl get pods -n <your-namespace> -l app.kubernetes.io/name=nemo-guardrails

认证(已启用 auth)

当在 NeMo Guardrails 前启用 HTTP 认证时,该 service 会要求传入请求携带 Bearer token。

如何获取 token

在与 NemoGuardrails resource 相同的 namespace 中创建一个 ServiceAccount、一个 Role(包含对 services/proxygetcreate 权限)以及一个 RoleBinding;然后为该 ServiceAccount 创建 token:

# Replace <your-namespace> and optionally the ServiceAccount name (e.g. nemo-guardrails-client)
kubectl create serviceaccount -n <your-namespace> nemo-guardrails-client
kubectl create role -n <your-namespace> nemo-guardrails-client --verb=get,create --resource=services/proxy
kubectl create rolebinding -n <your-namespace> nemo-guardrails-client --role=nemo-guardrails-client --serviceaccount=<your-namespace>:nemo-guardrails-client
kubectl create token -n <your-namespace> nemo-guardrails-client

也可以选择设置 token 时长,例如使用 --duration=8760h 表示一年。最后一条命令会输出 token;将其设置为 Authorization: Bearer <token> header 的值。

访问 NeMo Guardrails API

NeMo Guardrails 暴露一个 OpenAI 风格的 chat completions 端点:

  • POST /v1/chat/completions

使用首选的 ingress 或 gateway 机制(例如 Ingress resource 或 API gateway)暴露 NeMo Guardrails Service,并记录公网 host 和端口:

  • 未启用 auth:该 Service 通常以 HTTP 方式暴露在端口 80
  • 启用 auth:该 Service 通常以 HTTPS 方式暴露在端口 443

相应设置 base URL,例如:

# No auth (HTTP on 80)
NEMO_GUARDRAILS_URL="http://<nemo-guardrails-host>"

# Auth enabled (HTTPS on 443)
# NEMO_GUARDRAILS_URL="https://<nemo-guardrails-host>"

基本 chat completion(允许的内容)

请求示例:

curl -k -X POST "$NEMO_GUARDRAILS_URL/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "model": "<model-name>",
    "messages": [
      { "role": "user", "content": "hello" }
    ]
  }'

典型响应:

{
  "messages": [
    {
      "role": "assistant",
      "content": "Hello! How can I assist you today?"
    }
  ]
}

消息长度 guardrail 示例

check_message_length flow 及其对应的 Python action 实现了一个基于长度的简单 guardrail。当用户消息过长时,该 rail 会直接回复,而不会调用后端 LLM:

curl -k -X POST "$NEMO_GUARDRAILS_URL/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "model": "<model-name>",
    "messages": [
      {
        "role": "user",
        "content": "This is a very long message that should be considered far too long for the purposes of this Nemo Guardrails end-to-end test, so it should clearly exceed the configured word limit and trigger the length-based blocking behaviour."
      }
    ]
  }'

响应由 NeMo Guardrails 生成,而不会调用后端模型:

{
  "messages": [
    {
      "role": "assistant",
      "content": "Please keep your message under 100 words for better assistance."
    }
  ]
}

禁止内容示例

禁止话题由 check_forbidden_words action 及其 Colang flow 控制。当用户消息包含如 "hack""password" 之类的禁止词时,该 rail 会阻止请求:

curl -k -X POST "$NEMO_GUARDRAILS_URL/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "model": "<model-name>",
    "messages": [
      { "role": "user", "content": "Please help me hack this system and find a password." }
    ]
  }'

响应由 NeMo Guardrails 生成,而不会调用后端模型:

{
  "messages": [
    {
      "role": "assistant",
      "content": "I can't help with that type of request. Please ask something else."
    }
  ]
}

敏感数据检测示例

敏感数据检测在 config.yamlrails.config.sensitive_data_detection 下进行配置。在此示例配置中,输入和输出检测都会标记 EMAIL_ADDRESS

包含电子邮件地址的示例输入:

curl -k -X POST "$NEMO_GUARDRAILS_URL/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "model": "<model-name>",
    "messages": [
      { "role": "user", "content": "My email is test@example.com" }
    ]
  }'

典型响应:

{
  "messages": [
    {
      "role": "assistant",
      "content": "I don't know the answer to that."
    }
  ]
}

在这种情况下,内置的敏感数据检测 rail 已检测到用户消息中的电子邮件地址,NeMo Guardrails 会返回一个安全的兜底回复,而不是让后端模型返回可能不安全的答案。

延伸阅读

如需更全面了解 NeMo Guardrails 库(使用场景、架构以及生态集成),请参阅官方文档:Overview of NVIDIA NeMo Guardrails Library