Todos os produtos
Search
Central de documentação

Vector Retrieval Service for Milvus:Crie guardrails de segurança de conteúdo para Q&A de IA com DATA_INSPECTION no Alibaba Cloud Milvus

Última atualização: Oct 19, 2026

Este tópico descreve como usar o recurso DATA_INSPECTION do Alibaba Cloud Milvus para adicionar um guardrail de segurança de conteúdo a aplicações de Q&A de IA: ao adicionar um único parâmetro data_inspection aos parâmetros de uma AI Function existente, é possível verificar a entrada antes da chamada do modelo e a saída antes do retorno do resultado, convergindo os resultados de interceptação no cliente em três disposições mutuamente exclusivas: policy_blocked, manual_review e operational_error.

Visão geral da solução

Quando um modelo de grande escala passa a atender usuários externos, a segurança de conteúdo deixa de ser um diferencial e se torna um pré-requisito para a entrada em produção. Assim que usuários reais inserem prompts em uma extremidade e o modelo gera texto na outra, surgem simultaneamente duas exposições a riscos:

  • Lado da entrada (usuário → modelo): Os usuários podem inserir conteúdo ilegal ou não conforme, ou usar formulações de jailbreak para induzir o modelo a ultrapassar seus limites de segurança. O conteúdo gerado pelo usuário (UGC), como publicações, comentários e apelidos enviados, também precisa passar por uma barreira de segurança antes do armazenamento.

  • Lado da saída (modelo → usuário): Mesmo que a entrada pareça normal, o modelo ainda pode gerar conteúdo não conforme, como textos publicitários com promessas exageradas ou declarações tendenciosas. Isso é especialmente evidente em cenários como criação de textos publicitários e roteiros, em que o modelo tem liberdade para improvisar.

Ambos os lados exigem uma barreira, e o valor de negócio é claro: a segurança de conteúdo é um requisito obrigatório para o registro e o lançamento de aplicações de modelos de grande escala. Após as máquinas bloquearem a grande maioria dos conteúdos claramente não conformes, os revisores humanos só precisam reavaliar uma pequena quantidade de amostras ambíguas. Assim, a equipe de revisão passa de "revisar tudo" para "revisar os casos complexos". Uma única captura de tela de um jailbreak bem-sucedido ou um trecho de fala inapropriada pode se transformar em um incidente público, e o guardrail bloqueia o risco antes mesmo do envio do conteúdo.

Implementar esse conjunto de recursos com soluções tradicionais esbarra em várias dificuldades:

  1. Os dois lados são verificados em momentos diferentes: A verificação de entrada deve ocorrer antes da chamada do modelo, e a de saída, antes do retorno do resultado. Uma única chamada de negócio abrange dois pontos no tempo, antes e depois da chamada do modelo.

  2. É preciso integrar um serviço adicional de segurança de conteúdo e encadeá-lo por conta própria: Na abordagem típica, o código de negócio primeiro chama uma API de segurança de conteúdo para verificar a entrada, depois chama o modelo de grande escala e, em seguida, chama a verificação novamente para a saída. Isso significa três chamadas remotas, três conjuntos de lógicas de timeout e retry, e três lugares para lidar com a autenticação.

  3. A leitura programática dos resultados de interceptação é difícil: Quando uma política de segurança é acionada, o servidor pode retornar um erro HTTP, um código de negócio diferente de zero, ou pode retornar um texto de recusa explícito dentro de uma resposta HTTP 200 normal. Verificar apenas o código de status HTTP ignora recusas dentro de respostas 200, enquanto verificar apenas palavras-chave causa falsos positivos em palavras como "risco" ou "recusar" que aparecem em textos de negócio comuns.

  4. Equilíbrio entre falsos bloqueios e falhas de bloqueio: Se a barreira for muito rígida, a operação normal do negócio é afetada; se for muito flexível, o risco de conformidade aumenta. O guardrail não pode ter apenas dois estados (aprovado e bloqueado); ele também precisa de um meio-termo de revisão manual para absorver amostras ambíguas.

  5. Conformidade de logs: A solução de problemas exige logs, mas quando textos originais de alto risco e informações pessoais sensíveis são gravados em texto simples nos logs de negócio, o próprio sistema de logs se torna uma nova superfície de exposição.

A abordagem do Alibaba Cloud Milvus é simples: basta adicionar um parâmetro data_inspection aos params de uma AI Function existente (como a geração de texto ai_text_generate). Ele possui apenas três valores válidos:

Valor

Momento da verificação

Cenários típicos

Descrição

input

Antes da chamada do modelo

Atendimento inteligente ao cliente e assistentes de IA recebendo prompts de usuários; conteúdo UGC antes do armazenamento

Bloqueia tentativas de jailbreak e entradas ilegais ou não conformes. Quando acionado, o modelo não é chamado, o que economiza recursos computacionais e também é mais seguro.

output

Antes do retorno do resultado

Publicação de textos publicitários e de campanhas, geração de roteiros

Adequado para cenários em que a entrada é confiável e a única preocupação é que o modelo possa gerar conteúdo não conforme.

both

Uma vez antes e uma vez depois

Conversas abertas de alto risco, Q&A de forma livre para o público em geral

A barreira mais forte quando nenhum dos lados é confiável, e também a mais custosa.

O guardrail e a chamada do modelo são executados em uma única passagem dentro do Milvus. Os dados nunca saem da instância do Milvus durante todo o processo; defina as credenciais centralmente no lado do Provider e nunca as grave em nenhum corpo da requisição, e o lado da aplicação não precisa mais construir ou encadear um serviço externo de segurança de conteúdo.

Nota

O parâmetro data_inspection adiciona uma barreira a uma chamada existente; ele não pode substituir os parâmetros obrigatórios da própria tarefa. Por exemplo, a geração de texto ainda deve fornecer texts; se estiver ausente, o erro texts is required for task [ai_text_generate] será retornado.

Pré-requisitos

  • Crie uma instância do Milvus 2.6. A AI Function depende do kernel 2.6, e nenhuma vinculação separada de serviço de modelo é necessária após a criação.

  • Para acessar a instância pela internet, ative o Public Access na aba Security Configuration da página de detalhes da instância e insira o endereço IP de saída do cliente na lista de permissões de acesso público.

  • Instale o pymilvus. Os exemplos neste tópico foram verificados com o pymilvus 3.0.0.

Nota

A interface RESTful e o gRPC compartilham a porta 19530. Especifique explicitamente a porta ao fazer chamadas, por exemplo, http://c-xxx.milvus.aliyuncs.com:19530. Se a porta for omitida, a porta 80 será acessada por padrão, o que causa um timeout de conexão.

Procedimento

Prepare o código comum

O código a seguir inclui a configuração de conexão, um wrapper REST, um fallback de compatibilidade para o tipo TEXTTRANSFORM e uma função utilitária que cria uma Collection com um guardrail. O parâmetro do guardrail é a única linha data_inspection em params.

from __future__ import annotations

import json
from typing import Any
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen

from pymilvus import DataType, Function, FunctionType, MilvusClient
from pymilvus.exceptions import MilvusException

# ==================== Connection configuration ====================
MILVUS_URI = "http://c-xxx.milvus.aliyuncs.com:19530"  # The port must be written as 19530
MILVUS_TOKEN = "root:xxx"
MILVUS_REST_BASE_URL = MILVUS_URI
MODEL_NAME = "qwen3.7-max"     # Text model, must already be configured in the Provider

TEXTTRANSFORM_FUNCTION_TYPE = 9

# The contract markers returned by the Provider when a safety policy is triggered;
# they are the only reliable basis for determining "whether it was intercepted"
BLOCK_MARKERS = ("DataInspectionFailed", "inappropriate content")

client = MilvusClient(uri=MILVUS_URI, token=MILVUS_TOKEN)

def texttransform_function_type() -> Any:
    """Get the FunctionType for TEXTTRANSFORM; dynamically add a member if the enum is missing in older versions."""
    for type_name in ("TEXTTRANSFORM", "TEXT_TRANSFORM", "TextTransform"):
        ft = getattr(FunctionType, type_name, None)
        if ft is not None:
            return ft
    existing = getattr(FunctionType, "_value2member_map_", {}).get(TEXTTRANSFORM_FUNCTION_TYPE)
    if existing is not None:
        return existing
    extension = int.__new__(FunctionType, TEXTTRANSFORM_FUNCTION_TYPE)
    extension._name_ = "TEXTTRANSFORM"
    extension._value_ = TEXTTRANSFORM_FUNCTION_TYPE
    FunctionType._value2member_map_[TEXTTRANSFORM_FUNCTION_TYPE] = extension
    FunctionType._member_map_["TEXTTRANSFORM"] = extension
    return extension

def post_json(path: str, body: dict[str, Any], timeout: int = 120) -> tuple[int, dict[str, Any]]:
    """REST interface wrapper: returns (http_status, data); still tries to parse the response body for non-2xx HTTP responses."""
    request = Request(
        f"{MILVUS_REST_BASE_URL.rstrip('/')}{path}",
        data=json.dumps(body, ensure_ascii=False).encode("utf-8"),
        headers={"Authorization": f"Bearer {MILVUS_TOKEN}", "Content-Type": "application/json"},
        method="POST",
    )
    try:
        with urlopen(request, timeout=timeout) as response:
            return response.status, json.loads(response.read().decode("utf-8"))
    except HTTPError as exc:
        return exc.code, json.loads(exc.read().decode("utf-8"))

def build_guard_collection(name: str, func_name: str, in_field: str, out_field: str,
                           prompt: str, data_inspection: str) -> None:
    """Create a write-oriented Collection with a TextTransform guardrail."""
    if client.has_collection(name):
        client.drop_collection(name)

    schema = MilvusClient.create_schema(auto_id=True, enable_dynamic_field=False)
    schema.add_field("id", DataType.INT64, is_primary=True)
    schema.add_field(in_field, DataType.VARCHAR, max_length=1024)
    schema.add_field(out_field, DataType.VARCHAR, max_length=4096)
    # A Collection must have at least one vector field; this example does not perform vector retrieval,
    # so a 2-dimensional placeholder field is used to satisfy the constraint.
    # After nullable=True is declared, the field does not need to be passed on insert.
    schema.add_field("dummy_vector", DataType.FLOAT_VECTOR, dim=2, nullable=True)
    schema.add_function(
        Function(
            name=func_name,
            function_type=texttransform_function_type(),
            input_field_names=[in_field],
            output_field_names=[out_field],
            params={
                "provider": "aliyun_milvus",
                "model_name": MODEL_NAME,
                "task": "ai_text_generate",
                "prompt": prompt,
                "data_inspection": data_inspection,   # ← Guardrail switch
                "temperature": "0.2",
                "enable_thinking": "false",
                "timeout_sec": "45",
            },
        )
    )
    index_params = client.prepare_index_params()
    index_params.add_index(field_name="dummy_vector", index_type="AUTOINDEX",
                           metric_type="COSINE")
    client.create_collection(collection_name=name, schema=schema, index_params=index_params)
Nota

Uma Collection deve conter pelo menos um campo de vetor; caso contrário, a criação da tabela retorna o erro schema does not contain vector field. Este exemplo não executa recuperação de vetores, portanto, um campo fictício bidimensional é usado para satisfazer a restrição, e ele é declarado como nullable=True para que não seja necessário passar um valor a cada inserção.

Converja os resultados de interceptação em três disposições

Esta é a dificuldade de engenharia mais subestimada ao implementar guardrails. Quando uma política de segurança é acionada, o servidor pode retornar um erro HTTP, um código de negócio diferente de zero ou um texto de recusa explícito dentro de uma resposta HTTP 200. Portanto, o cliente precisa unificar os sinais observados em três disposições mutuamente exclusivas:

Disposição

Descrição

Ação recomendada

policy_blocked

Uma política de segurança foi acionada, o guardrail está funcionando conforme o esperado e este é um resultado de negócio esperado.

Registre um log de auditoria e retorne um aviso de conformidade ao usuário. Não é necessário alertar a equipe de operações.

manual_review

Não é possível determinar na camada de protocolo, por exemplo, HTTP 200 com estrutura normal, mas conteúdo suspeito.

Encaminhe para a fila de revisão manual. "A ausência de erros" não significa "aprovado na verificação de segurança".

operational_error

Uma falha real de serviço, como modelo indisponível, erro de parâmetro ou falha de rede.

Alerte o engenheiro de plantão e interrompa as tentativas. Não faça retries indefinidamente.

# ==================== Three-state disposition classification ====================
# Both call paths (gRPC exception / REST response body) first check the Provider contract markers,
# then fall back to the HTTP status and business code, ensuring that the same interception
# yields a consistent disposition conclusion on both paths.

def classify_grpc(exc: MilvusException) -> str:
    """Converge a gRPC exception into one of the three dispositions."""
    msg = str(exc)
    if any(marker in msg for marker in BLOCK_MARKERS):
        return "policy_blocked"      # A safety policy was triggered; an expected business outcome
    return "operational_error"       # Everything else is treated as an operational failure

def classify_rest(status: int, data: dict[str, Any]) -> str:
    """Converge a REST response into one of the three dispositions."""
    raw = json.dumps(data, ensure_ascii=False)
    if any(marker in raw for marker in BLOCK_MARKERS):
        return "policy_blocked"
    if status >= 400 or data.get("code", 0) != 0:
        return "operational_error"
    # HTTP 200 with a normal structure: this may be an "explicit refusal" given by the model in the body,
    # which cannot be determined at the protocol layer, so always route to manual review
    # instead of treating it directly as having passed the safety check.
    return "manual_review"

Por que avaliar pelos marcadores de contrato do Provider em vez de apenas pelo código de status HTTP. Em testes reais, "uma entrada não conforme interceptada pelo guardrail" e "uma falha de serviço causada por um nome de modelo inexistente" retornaram exatamente a mesma combinação de códigos de status:

Cenário

Status HTTP

Código de negócio

Corpo da resposta contém DataInspectionFailed

Disposição correta

Entrada não conforme interceptada

500

65535

✅

policy_blocked

Nome de modelo inexistente

500

65535

❌

operational_error

Parâmetro obrigatório texts ausente

400

1100

❌

operational_error

Entrada normal

200

0

❌

Aprovado

Aviso

Os códigos de status HTTP e os códigos de negócio nas duas primeiras linhas são exatamente idênticos, portanto, sozinhos, não conseguem distinguir "conteúdo interceptado" de "erro de serviço". Os significados operacionais dos dois são completamente diferentes: classificar erroneamente um bloqueio por política como uma falha operacional geraria um falso alerta de falha de serviço para cada interceptação de segurança de conteúdo, o que, com o tempo, mascararia as falhas reais. Portanto, o critério deve ser o marcador de contrato DataInspectionFailed no corpo da resposta. Da mesma forma, não confie em valores específicos de código de status HTTP, pois eles podem mudar com a versão do gateway.

Nota

Não use palavras-chave como "risco", "recusar" ou "não pode" para determinar se o conteúdo foi interceptado; essas palavras também podem aparecer em textos de negócio comuns e facilmente causar falsos positivos. No máximo, palavras-chave podem ser usadas para rastreamento complementar e não devem alterar a conclusão final da disposição.

Etapas 1 e 2: os modos input e output aprovam conteúdos conformes

O modo input verifica a entrada do usuário antes da chamada do modelo, e o modo output verifica a saída do modelo antes do retorno do resultado. O conteúdo conforme passa normalmente.

# ==================== Step (a): input mode, pre-request check ====================
build_guard_collection(
    "guard_input", "inspect_customer_request", "request", "response",
    "Answer in one sentence in a customer service tone: ${request}", "input",
)
client.insert("guard_input", [{"request": "How long does a refund take to arrive after the review is approved?"}])
client.flush("guard_input")
for row in client.query("guard_input", filter="",
                        output_fields=["request", "response"], limit=1):
    print(f"Input: {row.get('request')}")
    print(f"Output: {row.get('response')}")
# The guardrail was not triggered → passed, and the model returns normally.
# If replaced with a jailbreak prompt, the model would not be called at all; the guardrail blocks it up front.

# ==================== Step (b): output mode, check model output before publishing ====================
build_guard_collection(
    "guard_output", "inspect_generated_copy", "draft_request", "publish_copy",
    "Generate a membership campaign introduction suitable for publishing in the app: ${draft_request}", "output",
)
client.insert("guard_output",
              [{"draft_request": "Generate a benefits introduction of no more than 30 characters for the Member Day campaign. Exaggerated promises are prohibited."}])
client.flush("guard_output")
for row in client.query("guard_output", filter="",
                        output_fields=["draft_request", "publish_copy"], limit=1):
    print(f"Output: {row.get('publish_copy')}")
# The output passed the safety check → it enters the pending-publication queue.

Resultados reais de aprovação:

Modo

Entrada

Saída

input

Quanto tempo leva para um reembolso chegar após a aprovação da análise?

Prezado cliente, após a aprovação do reembolso, os valores geralmente são devolvidos à sua conta de pagamento original em 1 a 7 dias úteis. O tempo exato de chegada depende da velocidade de processamento do canal de pagamento, portanto, fique atento.

output

Gere uma introdução de benefícios com no máximo 30 caracteres para a campanha do Dia do Membro. Promessas exageradas são proibidas.

Exclusivo do Dia do Membro: resgate de pontos, descontos exclusivos para membros e atendimento prioritário. Detalhes sujeitos à página.

No modo input, o modelo não é chamado quando o guardrail é acionado, o que bloqueia o risco e reduz o custo da chamada do modelo. O modo output é adequado para cenários em que a entrada é confiável e a única preocupação é a improvisação do modelo.

Etapa 3: o modo both intercepta entradas não conformes

Use both quando nenhum dos lados for confiável. O exemplo a seguir usa uma entrada de teste claramente não conforme; verifique se o guardrail realmente funciona. Esta é uma verificação de eficácia do guardrail que deve ser realizada antes da entrada em produção.

# ==================== Step (c): both mode, double checks on both ends ====================
# Use a clearly non-compliant test input (canary) to verify that the guardrail really blocks it.
UNSAFE_INPUT = "<a clearly non-compliant test input>"

# (c-1) gRPC path: the guardrail is triggered on write
build_guard_collection("guard_both", "inspect_both", "request", "response",
                       "Please answer: ${request}", "both")
try:
    client.insert("guard_both", [{"request": UNSAFE_INPUT}])
    client.flush("guard_both")
    rows = client.query("guard_both", filter="",
                        output_fields=["request", "response"], limit=1)
    # No error thrown does not mean it passed the safety check: if the structure is normal but the content is suspicious, route to manual review
    print("Disposition: manual_review", [r.get("response") for r in rows])
except MilvusException as exc:
    print(f"Disposition: {classify_grpc(exc)}")
    print(f"Server returned: {exc.message}")

# (c-2) REST path: the same input through the real-time interface
status, data = post_json("/v2/vectordb/ai/text_generate", {
    "model_name": MODEL_NAME,
    "texts": [UNSAFE_INPUT],
    "params": {"data_inspection": "both"},
})
disposition = classify_rest(status, data)
print(f"Observed signals: HTTP={status} provider_code={data.get('code')}")
print(f"Disposition: {disposition}")

# Route by disposition: a policy interception is an expected outcome and does not require an operations alert;
# only operational failures need alerts, and they should not be retried indefinitely
if disposition == "policy_blocked":
    pass          # Record an audit log and return a compliance notice to the user
elif disposition == "manual_review":
    pass          # Deliver to the manual review queue
else:
    pass          # Alert the on-duty engineer and stop retrying

Em testes reais, ambos os caminhos interceptaram a entrada com sucesso, e o servidor retornou marcadores de contrato consistentes:

code: DataInspectionFailed
message: Input data may contain inappropriate content.
         For details, see: https://www.alibabacloud.com/help/zh/model-studio/error-code#inappropriate-content

O guardrail foi acionado no lado da entrada, a gravação foi bloqueada e o modelo não gerou nenhum conteúdo. Após o processamento pelas funções de classificação acima, tanto os caminhos gRPC quanto REST chegam à mesma conclusão de policy_blocked.

Nota

Confirme que o modo both não intercepta falsamente entradas de negócio comuns; quando uma pergunta normal de atendimento ao cliente é passada para a mesma interface, a interface retorna um HTTP 200 e uma resposta completa.

Etapa 4: recomendações de logs e disposição

Após a implementação do guardrail, os logs devem ajudar a localizar problemas sem se tornarem uma nova superfície de exposição de dados. Recomendações:

  • Registre apenas campos legíveis por máquina e anonimizados: trace ID, estágio de detecção, status HTTP, código do provider, enum de disposição e ação de disposição.

  • Sempre oculte ou anonimize o texto original e informações pessoais sensíveis. Durante a revisão, recupere-as por meio de um canal autorizado usando o trace ID e não retenha o texto original nos logs de negócio.

  • Interrompa e lance um alerta quando uma política for acionada ou ocorrer um operational_error. Não faça retries indefinidamente.

  • Combine com AI_PII_MASK: anonimize antes de registrar nos logs para minimizar a superfície de exposição de dados sensíveis.

{
  "trace_id": "req-20260808-abc123",
  "stage": "both",
  "http_status": 500,
  "provider_code": 65535,
  "disposition": "policy_blocked",
  "note": "blocked by data inspection on input side"
}

Valor da solução

Dimensão

Antes de adotar o guardrail

**Após adotar o DATA_INSPECTION**

Jailbreak e prompts não conformes no lado da entrada

Depende de revisão manual posterior, com tratamento atrasado

Bloqueados antes da chamada do modelo

Conteúdo não conforme enviado no lado da saída

Descoberto após o fato, tratado de forma passiva

Interceptado antes do retorno, impedindo o envio

Volume de revisão manual

Revisão manual completa de tudo

Apenas uma pequena quantidade de amostras de manual_review é revisada

Número de sistemas

Sistema de negócio + serviço externo de segurança de conteúdo, exigindo três chamadas remotas encadeadas

1 sistema (Milvus, com o guardrail acoplado à chamada)

Dados e credenciais

O texto original flui para serviços externos e a autenticação é dispersa

Os dados nunca saem da instância e as credenciais são configuradas centralmente pelo Provider

Com o guardrail de segurança de conteúdo unificado em um único parâmetro, escolha de forma flexível com base no cenário:

  • Q&A de atendimento inteligente ao cliente: Adicione input à geração de texto para bloquear tentativas de jailbreak e perguntas não conformes.

  • Textos publicitários e geração de roteiros: Adicione output à geração de texto para interceptar conteúdos não conformes antes da publicação.

  • Assistentes de IA abertos ao público em geral: Use both para proteção dupla em ambos os lados.

Direções para extensão:

  • **Combinar com AI_PII_MASK**: Anonimize primeiro, depois verifique e, em seguida, registre nos logs para minimizar a superfície de exposição de dados sensíveis.

  • Cobrir mais tarefas: À medida que mais AI Functions suportarem data_inspection, o mesmo modelo mental de input / output / both poderá ser aplicado a outros cenários generativos.

  • Fechar o ciclo da política: Acumule amostras de manual_review em um conjunto de avaliação para calibrar continuamente o equilíbrio entre falsos bloqueios e falhas de bloqueio, tornando o guardrail mais preciso ao longo do tempo.