メインコンテンツへスキップ
このモジュールは、アプリケーション内でエンドユーザーが入力したコンテンツと LLM が出力したコンテンツを審査するために使用され、2 つの拡張ポイントタイプに分かれています。

拡張ポイント

  • app.moderation.input:エンドユーザー入力コンテンツ審査拡張ポイント
    • エンドユーザーが渡した変数内容と会話型アプリケーションの会話入力内容を審査するために使用されます。
  • app.moderation.output:LLM 出力コンテンツ審査拡張ポイント
    • LLM が出力したコンテンツを審査するために使用されます。
    • LLM の出力がストリーミングの場合、出力内容は 100 文字を 1 セグメントとして API にリクエストされ、出力内容が長い場合の審査の遅延を可能な限り回避します。

app.moderation.input

Chatflow、Agent、チャットアシスタントなどのアプリケーションでコンテンツ審査 > 入力コンテンツを審査が有効になっている場合、Dify は対応する API 拡張に以下の HTTP POST リクエストを送信します:

Request Body

{
    "point": "app.moderation.input", // 拡張ポイントタイプ、ここでは app.moderation.input に固定
    "params": {
        "app_id": string,  // アプリケーション ID
        "inputs": {  // エンドユーザーが渡した変数値、key は変数名、value は変数値
            "var_1": "value_1",
            "var_2": "value_2",
            ...
        },
        "query": string | null  // エンドユーザーの現在の会話入力内容、会話型アプリケーションの固定パラメータ。
    }
}
:
{
    "point": "app.moderation.input",
    "params": {
        "app_id": "61248ab4-1125-45be-ae32-0ce91334d021",
        "inputs": {
            "var_1": "I will kill you.",
            "var_2": "I will fuck you."
        },
        "query": "Happy everydays."
    }
}

API レスポンス仕様

{
    "flagged": bool,  // 検証ルールに違反しているかどうか
    "action": string, // アクション、direct_output は事前設定された回答を直接出力; overridden は入力変数値を上書き
    "preset_response": string,  // 事前設定された回答(action=direct_output の場合にのみ返される)
    "inputs": {  // エンドユーザーが渡した変数値、key は変数名、value は変数値(action=overridden の場合にのみ返される)
        "var_1": "value_1",
        "var_2": "value_2",
        ...
    },
    "query": string | null  // 上書きされたエンドユーザーの現在の会話入力内容、会話型アプリケーションの固定パラメータ。(action=overridden の場合にのみ返される)
}
:
  • action=direct_output
    {
        "flagged": true,
        "action": "direct_output",
        "preset_response": "Your content violates our usage policy."
    }
    
  • action=overridden
    {
        "flagged": true,
        "action": "overridden",
        "inputs": {
            "var_1": "I will *** you.",
            "var_2": "I will *** you."
        },
        "query": "Happy everydays."
    }
    

app.moderation.output

Chatflow、Agent、チャットアシスタントなどのアプリケーションでコンテンツ審査 > 出力コンテンツを審査が有効になっている場合、Dify は対応する API 拡張に以下の HTTP POST リクエストを送信します:

Request Body

{
    "point": "app.moderation.output", // 拡張ポイントタイプ、ここでは app.moderation.output に固定
    "params": {
        "app_id": string,  // アプリケーション ID
        "text": string  // LLM 応答内容。LLM の出力がストリーミングの場合、ここでは 100 文字を 1 セグメントとした内容。
    }
}
:
{
    "point": "app.moderation.output",
    "params": {
        "app_id": "61248ab4-1125-45be-ae32-0ce91334d021",
        "text": "I will kill you."
    }
}

API レスポンス

{
    "flagged": bool,  // 検証ルールに違反しているかどうか
    "action": string, // アクション、direct_output は事前設定された回答を直接出力; overridden は入力変数値を上書き
    "preset_response": string,  // 事前設定された回答(action=direct_output の場合にのみ返される)
    "text": string  // 上書きされた LLM 応答内容。(action=overridden の場合にのみ返される)
}
:
  • action=direct_output
    {
        "flagged": true,
        "action": "direct_output",
        "preset_response": "Your content violates our usage policy."
    }
    
  • action=overridden
    {
        "flagged": true,
        "action": "overridden",
        "text": "I will *** you."
    }
    

コードサンプル

以下は、Cloudflare にデプロイ可能な src/index.ts コードの一部です。(Cloudflare の完全な使用方法については このドキュメント を参照してください) コードの動作原理はキーワードマッチングを行い、Input(ユーザーが入力したコンテンツ)および Output(大規模モデルが返したコンテンツ)をフィルタリングすることです。ユーザーは必要に応じてマッチングロジックを変更できます。
import { Hono } from "hono";
import { bearerAuth } from "hono/bearer-auth";
import { z } from "zod";
import { zValidator } from "@hono/zod-validator";
import { generateSchema } from '@anatine/zod-openapi';

type Bindings = {
  TOKEN: string;
};

const app = new Hono<{ Bindings: Bindings }>();

// API フォーマット検証 ⬇️
const schema = z.object({
  point: z.union([
    z.literal("ping"),
    z.literal("app.external_data_tool.query"),
    z.literal("app.moderation.input"),
    z.literal("app.moderation.output"),
  ]), // Restricts 'point' to two specific values
  params: z
    .object({
      app_id: z.string().optional(),
      tool_variable: z.string().optional(),
      inputs: z.record(z.any()).optional(),
      query: z.any(),
      text: z.any()
    })
    .optional(),
});


// Generate OpenAPI schema
app.get("/", (c) => {
  return c.json(generateSchema(schema));
});

app.post(
  "/",
  (c, next) => {
    const auth = bearerAuth({ token: c.env.TOKEN });
    return auth(c, next);
  },
  zValidator("json", schema),
  async (c) => {
    const { point, params } = c.req.valid("json");
    if (point === "ping") {
      return c.json({
        result: "pong",
      });
    }
    // ⬇️ impliment your logic here ⬇️
    // point === "app.external_data_tool.query"
    else if (point === "app.moderation.input"){
    // 入力チェック ⬇️
    const inputkeywords = ["入力フィルターテスト1", "入力フィルターテスト2", "入力フィルターテスト3"];

    if (inputkeywords.some(keyword => params.query.includes(keyword))) 
      {
      return c.json({
        "flagged": true,
        "action": "direct_output",
        "preset_response": "入力に違法コンテンツが含まれています。別の質問で試してください!"
      });
    } else {
      return c.json({
        "flagged": false,
        "action": "direct_output",
        "preset_response": "入力は正常です"
      });
    }
    // 入力チェック完了 
    }
    
    else {
      // 出力チェック ⬇️
      const outputkeywords = ["出力フィルターテスト1", "出力フィルターテスト2", "出力フィルターテスト3"]; 

  if (outputkeywords.some(keyword => params.text.includes(keyword))) 
    {
      return c.json({
        "flagged": true,
        "action": "direct_output",
        "preset_response": "出力にセンシティブコンテンツが含まれており、システムによってフィルタリングされました。別の質問で試してください!"
      });
    }
  
  else {
    return c.json({
      "flagged": false,
      "action": "direct_output",
      "preset_response": "出力は正常です"
    });
  };
    }
    // 出力チェック完了 
  }
);

export default app;