• Русский
  • NeMo Guardrails

    NeMo Guardrails предоставляет программируемые механизмы безопасности для приложений с LLM. Он работает как отдельный сервис перед моделью и может обеспечивать:

    • Обнаружение конфиденциальных данных (например, PII во входных и выходных данных).
    • Политики контента (например, запрещённые темы, упоминания конкурентов).
    • Пользовательские потоки валидации, написанные на Colang и Python.

    Оператор TrustyAI предоставляет доступ к NeMo Guardrails через пользовательский ресурс (CR) NemoGuardrails. В этом документе рассматривается базовое развертывание, которое:

    • Защищает уже развернутую модель на платформе обслуживания.
    • Использует NeMo Guardrails для фильтрации входных/выходных данных и простых бизнес-правил.

    Предварительные требования

    • Установлен оператор TrustyAI (см. Install TrustyAI).
    • Модель уже развернута на платформе обслуживания (например, vLLM) и предоставляет API, совместимый с OpenAI.

    Архитектура

    На высоком уровне путь запроса следующий:

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

    NeMo Guardrails:

    • Принимает запросы в стиле OpenAI chat/completions.
    • Выполняет настроенные rails (обнаружение конфиденциальных данных, проверки длины, запрещённые темы и т.д.).
    • Для разрешённых запросов пересылает их в базовую модель.
    • Для заблокированных запросов возвращает соответствующее сообщение ассистента без вызова модели.

    Оператор TrustyAI управляет Pod и Service сервера NeMo Guardrails через CR NemoGuardrails. Service затем может быть открыт извне с помощью выбранного ingress или gateway решения в кластере.

    Конфигурация NeMo ConfigMap

    NeMo Guardrails ожидает каталог конфигурации, который обычно содержит:

    • config.yaml: основной файл конфигурации NeMo Guardrails.
    • rails.co: потоки Colang, реализующие rails для входных/выходных данных и дополнительную логику управления.
    • actions.py: Python-действия, которые могут вызываться потоками Colang для выполнения пользовательской логики.
    Пример ConfigMap конфигурации NeMo
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: nemo-config
      namespace: <your-namespace>
    data:
      config.yaml: |
        models:
          - type: main
            engine: openai
            parameters:
              # Внутренний URL предсказателя модели, совместимый с OpenAI
              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:
            """
            Пример пользовательского действия, вызываемого из Colang через:
              $length_result = execute check_message_length
            Вход:
              - context: объект, похожий на dict, предоставляемый NeMo; содержит последнее
                сообщение пользователя под ключом "user_message".
            Выход:
              - Короткая строка, которую интерпретирует поток Colang, например:
                * "blocked_too_long": слишком длинное, блокировать напрямую.
                * "warning_long": слишком длинное, выдать предупреждение, но продолжить.
                * "allowed": длина приемлема.
            """
            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:
            """
            Пример пользовательского действия для простой проверки запрещённых слов.
            Вызывается из Colang через:
              $forbidden_result = execute check_forbidden_words
            и возвращает:
              - "allowed", если запрещённых слов нет.
              - значение, отличное от "allowed" (например, "blocked_password"), если
                обнаружено запрещённое слово.
            """
            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, через openai_api_base и model_name.

    • Настраивает встроенное обнаружение PII в rails.config.sensitive_data_detection:

      • input.entities / output.entities перечисляют типы сущностей для защиты (например, EMAIL_ADDRESS, PERSON).
      • При запуске rails detect sensitive data on input / detect sensitive data on output NeMo автоматически вызывает внутренние детекторы с использованием этой конфигурации.
    • Определяет, какие rails выполняются и в каком порядке, через rails.input.flows и rails.output.flows:

      • detect sensitive data on input / detect sensitive data on output — встроенные rails, основанные на sensitive_data_detection.
      • check message length и check forbidden words — пользовательские rails, реализованные в rails.co и поддерживаемые Python-действиями из actions.py.
    • Замените <model-predictor-host>, <port> и <model-name> на фактический URL сервиса предсказателя и имя модели.

    • Убедитесь, что предсказатель реализует API /v1/chat/completions, совместимый с OpenAI.

    • Для более продвинутой настройки config.yaml (дополнительные типы rails, подсказки, трассировка, база знаний и интеграция с другими провайдерами безопасности) обратитесь к официальной документации по YAML-конфигурации NeMo Guardrails: Nvidia NeMo Guardrails Configuration.

    Основы rails.co

    В этом примере rails.co определяет два пользовательских входных rail:

    • define flow check message length:
      • Имя потока check message length должно совпадать с записью в rails.input.flows в config.yaml.
      • $length_result = execute check_message_length запускает Python-действие check_message_length из actions.py, передавая текущий контекст разговора.
      • Операторы if ветвятся по возвращённой строке и либо:
        • вызывают блок bot ... для отправки ответа (например, bot inform message too long), и
        • stop для прерывания дальнейшей обработки и предотвращения вызова LLM,
        • либо ничего не делают и позволяют конвейеру продолжить к следующему rail при возврате "allowed".
    • define flow check forbidden words:
      • Использует тот же шаблон, но вызывает действие check_forbidden_words и блокирует только если возвращаемое значение не "allowed".

    Дополнительные моменты:

    • Блоки bot ... (например, bot inform message too long) определяют заранее подготовленные сообщения ассистента, которые отправляются напрямую клиенту без обращения к базовой LLM, когда rail решает остановить конвейер.
    • Rails, определённые в rails.co, выполняются в порядке, указанном в rails.input.flows / rails.output.flows. Встроенные rails, такие как detect sensitive data on input, запускаются до или после пользовательских в зависимости от их позиции в списке.
    • Показанный здесь Colang — минимальный пример. Поддерживаются более сложные потоки (несколько шагов, переменные, дополнительные действия); см. справочник Colang в документации NeMo Guardrails для полного синтаксиса и возможностей. Хорошей отправной точкой является руководство по Colang 2.0: Colang Getting Started.

    Основы actions.py

    Файл actions.py содержит функции Python, декорированные @action, которые потоки Colang могут вызывать через execute <action_name>:

    • Действия получают объект context, который представляет собой структуру, похожую на dict, заполненную NeMo (например, содержит последнее сообщение пользователя под ключом "user_message").
    • Действия возвращают значение (обычно короткую строку), по которому потоки Colang делают ветвления.

    В этом примере:

    • check_message_length:
      • Анализирует context["user_message"], вычисляет количество слов и возвращает:
        • "blocked_too_long", если сообщение следует отклонить.
        • "warning_long", если нужно выдать предупреждение, но конвейер может продолжить работу.
        • "allowed", если длина сообщения приемлема.
    • check_forbidden_words:
      • Приводит сообщение пользователя к нижнему регистру, ищет запрещённые слова и:
        • Возвращает "allowed", если ничего не найдено.
        • Возвращает значение, отличное от "allowed" (например, "blocked_password"), если обнаружено запрещённое слово.

    Эти шаблоны можно расширять для более сложных guardrails, таких как структурированные проверки, числовые пороги или вызовы внешних сервисов.

    Развертывание пользовательского ресурса NemoGuardrails

    Имея ConfigMap и секрет с токеном, создайте CR NemoGuardrails для развертывания сервиса NeMo Guardrails:

    apiVersion: trustyai.opendatahub.io/v1alpha1
    kind: NemoGuardrails
    metadata:
      name: nemo-guardrails
      namespace: <your-namespace>
      annotations:
        # Если true, открытый маршрут требует Bearer токен для входящих запросов к NeMo Guardrails.
        security.opendatahub.io/enable-auth: "true"
    
        # Если backend LLM доступен по HTTPS с кастомным CA, установите эту аннотацию
        # с именем Secret, содержащего CA bundle в ключе, например, `ca.crt`.
        # Оператор смонтирует этот Secret и настроит доверие TLS NeMo Guardrails.
        # Пример:
        # trustyai.opendatahub.io/ca-secret-name: llm-backend-ca
    spec:
      nemoConfigs:
        - name: nemo-config
          configMaps:
            - nemo-config
          default: true
      env:
        - name: OPENAI_API_KEY
          # Для аутентифицированных backend используйте ссылку на Secret с токеном:
          # valueFrom:
          #   secretKeyRef:
          #     name: api-token-secret
          #     key: token
          # Для внутренних, неаутентифицированных HTTP backend достаточно заполнить заглушкой:
          value: "<placeholder>"
    
        # Опционально: офлайн-среды
        # NeMo Guardrails может загружать Public Suffix List через tldextract. В средах
        # без доступа в Интернет установите TLDEXTRACT_CACHE для использования кеша
        # public_suffix_list, встроенного в образ NeMo Guardrails Server. Учтите, что
        # встроенный список может быть не самым свежим.
        # - name: TLDEXTRACT_CACHE
        #   value: "/app/.cache/"
    
        # Поведение TLS для backend LLM:
        # - HTTP backend:
        #   * Установите SSL_CERT_FILE в пустую строку, чтобы отключить поиск сертификатов.
        #   * Используйте http:// URL в config.yaml (openai_api_base).
        # - HTTPS backend с кастомным CA:
        #   * Удалите SSL_CERT_FILE из env.
        #   * Добавьте аннотацию trustyai.opendatahub.io/ca-secret-name выше, указывающую
        #     на Secret с CA bundle.
        - name: SSL_CERT_FILE
          value: ""

    Ключевые поля:

    • nemoConfigs: Ссылается на один или несколько наборов конфигураций; каждый набор может включать один или несколько ConfigMap с файлами конфигурации NeMo Guardrails.
    • env.OPENAI_API_KEY: Токен, используемый NeMo Guardrails для аутентификации на конечной точке backend модели (например, сервис vLLM). Для внутренних неаутентифицированных сервисов инференса это значение можно задать напрямую как value: "<placeholder>" и оно не используется backend. Для сервисов инференса только по HTTP TLS-сертификаты для backend URL не требуются.
    • security.opendatahub.io/enable-auth: При значении "true" маршрут к NeMo Guardrails защищён аутентификацией кластера и требует Bearer токен.

    Примените:

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

    После создания CR оператор выполнит reconciliate и создаст:

    • Deployment для сервера NeMo Guardrails.
    • Service, который открывает HTTP endpoint NeMo Guardrails внутри кластера.

    Дождитесь, пока Pod Deployment станет Ready:

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

    Аутентификация (при включённой auth)

    Когда перед NeMo Guardrails включена HTTP-аутентификация, сервис ожидает Bearer токен в входящих запросах.

    Как получить токен

    Создайте ServiceAccount, Role (с правами get, create на services/proxy) и RoleBinding в том же namespace, что и ресурс NemoGuardrails; затем создайте токен для ServiceAccount:

    # Замените <your-namespace> и при необходимости имя ServiceAccount (например, 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

    Опционально задайте длительность токена, например, --duration=8760h для одного года. Последняя команда выведет токен; используйте его в заголовке Authorization: Bearer <token>.

    Доступ к API NeMo Guardrails

    NeMo Guardrails предоставляет endpoint для chat completions в стиле OpenAI:

    • POST /v1/chat/completions

    Откройте Service NeMo Guardrails с помощью предпочитаемого ingress или gateway механизма (например, ресурса Ingress или API gateway) и запомните публичный хост и порт:

    • Без аутентификации: Service обычно открыт по HTTP на порту 80.
    • При включённой аутентификации: Service обычно открыт по HTTPS на порту 443.

    Установите базовый URL, например:

    # Без аутентификации (HTTP на 80)
    NEMO_GUARDRAILS_URL="http://<nemo-guardrails-host>"
    
    # С аутентификацией (HTTPS на 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 и соответствующее Python-действие реализуют простой guardrail на основе длины. Если сообщение пользователя слишком длинное, rail отвечает напрямую без вызова backend 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 без вызова backend модели:

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

    Пример запрещённого контента

    Запрещённые темы контролируются действием check_forbidden_words и соответствующим потоком Colang. Если сообщение пользователя содержит запрещённое слово, например "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 без вызова backend модели:

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

    Пример обнаружения конфиденциальных данных

    Обнаружение конфиденциальных данных настроено в config.yaml в разделе rails.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 возвращает безопасный запасной ответ вместо того, чтобы позволить backend модели ответить потенциально небезопасно.

    Дополнительные материалы

    Для более широкого обзора библиотеки NeMo Guardrails (кейсы использования, архитектура и интеграции в экосистему) смотрите официальную документацию: Overview of NVIDIA NeMo Guardrails Library.