6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

今回ハッカソンで、作成したサービスでコメント生成AI機能というものを実装しました。
もしかしたらハッカソンでAIを導入したい方もいるかもしれないので、どのようにして
実装したか記載したいと思います。

機能概要

主に以下に二つの機能を実装しました。
・コメントを柔らかい文章で表現する。
・文章の中に誹謗中傷がないかチェックする。

APIキーを取得

以下のサイトでAI機能に必要なAPIキーを取得します。
https://openai.com/ja-JP/index/openai-api/

新規登録等を済ましたら、赤枠で囲っている
「Dashboard」と「API keys」を選択する。
2025-12-08 17.51の画像.jpeg

その後に「Create new secret key」を押下する。
2025-12-08 18.01の画像.jpeg

「Name」を入力して「Create secret key」を押下
2025-12-08 18.05の画像.jpeg

keyの環境変数をコピーして完了
2025-12-08 18.10の画像.jpeg

注:この環境変数は後ほどの作業で必要なので必ずコピーしてメモアプリなどで保存しておくこと‼️

envファイルに中身を記載

.env
# OpenAPIキー
OPEN_API_KEY = "ここに先ほどコピーした環境変数を記載"

コメント生成AIの流れ

[HTML テンプレート] ← textarea に入力
           │
           ▼
[ai_comment.js] ← 「AIで柔らかくする」ボタン押下
           │ fetch(text) で Django API に送信
           ▼
[views.py / soften_comment()] ← Django 側API
           │
           ▼
[ai_filters.py] ← 誹謗中傷チェック(3段階)
           │   ① NGワード判定(独自辞書)
           │   ② OpenAI Moderation(自動誹謗中傷検出)
           │   ③ OpenAI ChatCompletion(弱攻撃検出)
           │
           ▼
AI で柔らかくした文章(soft_text)を JSON で返す
           │
           ▼
[ai_comment.js] ← textarea に書き換え結果を反映
           │
           ▼
ユーザーが最終的なコメントで「投稿(投票)」する
           │
           ▼
[forms.py] clean_comment()
           │ ← ai_filters.py の is_offensive() を再度実行(最終バリデーション)
           ▼
投稿確定

① HTML(テンプレート側)

〇〇.html
{{ form.comment }}   <!-- textarea を生成 -->

<div class="edit-button text-center mb-4">
    <button id="ai_soften_btn" type="button" class="btn btn-secondary px-5">
        AIで言い回しをやさしくする
    </button>
</div>

<div id="ai_result" class="text-muted mb-4"></div>

ポイント

  • form.comment → forms.py で設定した textarea が自動生成
  • textarea には "id": "comment_input" が付いている
  • JS はこの textarea と AI ボタンを操作する
  • ai_result に AI の結果メッセージを表示

② forms.py(textarea の生成)

froms.py
widgets = {
    "comment": forms.Textarea(
        attrs={
            "id": "comment_input",   # ← JSで取得するID
            "class": "form-control",
            "rows": 3,
        }
    ),
}

✔ JavaScript と Django の接続点

id="comment_input" によって

JS の以下のコードが確実に動く

document.getElementById("comment_input").value

③ ai_comment.js(ブラウザ → Django API の橋渡し)

ai_comment.js
// 「AIで柔らかくする」ボタンがクリックされたときの処理
document.getElementById("ai_soften_btn").addEventListener("click", function () {

    // textarea(コメント入力欄)からテキストを取得
    const text = document.getElementById("comment_input").value;

    // Django の API (/api/comment/soften/) に文章を送信して、柔らかい文章へ変換してもらう
    fetch("/api/comment/soften/", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        // API に送るデータ(JSON形式)
        body: JSON.stringify({ text: text }),
    })
        .then(res => res.json()) // レスポンスを JSON として受け取る
        .then(data => {

            // API側でエラーが返ってきた場合
            if (data.error) {
                document.getElementById("ai_result").innerHTML =
                    `<span class="text-danger">${data.error}</span>`;
                return; // 以降の処理は実行しない
            }

            // AI が生成した「柔らかい文章」を textarea に反映
            document.getElementById("comment_input").value = data.soft_text;

            // 処理完了メッセージを表示
            document.getElementById("ai_result").innerText =
                "AIが文章を柔らかく書き換えました。";
        });
});

JS の役割

  1. textarea の文章を取得
  2. Django API に POST
  3. Django → OpenAI → AI変換文を受け取る
  4. textarea に上書き
  5. 結果メッセージを表示

→ ブラウザ側の「非同期通信 & 画面反映」を担当する層

④ Django views.py(AI処理の中核)

views.py
@csrf_exempt
def soften_comment(request):
    # OpenAI の API キーを環境変数から取得してクライアントを初期化
    client = OpenAI(api_key=os.environ["OPEN_API_KEY"])


    # リクエストボディ (JSON) を取得し、Python の dict に変換
    data = json.loads(request.body)
    text = data.get("text", "")  # 入力文章を取得(空の場合は空文字)

    # --- 誹謗中傷チェック --------------------------------------------
    # NGワードが含まれている、またはモデレーションで引っかかった場合はエラーを返す
    if contains_ng_word(text) or moderation.flagged:
        return JsonResponse({"error": "攻撃的な表現が含まれています"}, status=400)

    # --- AI で文章を柔らかく書き換える --------------------------------
    # プロンプトを作成(入力された文章のみを加工するよう指示)
    prompt = f"""    
以下のルールに従って、入力された文章のみを柔らかく書き換えてください。 
一部抜粋
・攻撃的・失礼な要素があればすべて取り除くこと 
・ネガティブな意見は相手が受け止めやすいように表現を書き換えてください。

元の文章:{text}
"""

    # ChatGPT API に文章を送信して、柔らかい言い回しを生成してもらう
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
    )

    # 生成された文章(soft_text)を取り出す
    soft_text = response.choices[0].message.content

    # 生成結果を JSON で返す
    return JsonResponse({"soft_text": soft_text})

Djangoの役割

役割 内容
テキスト受信 JS → Django
危険判定① NGパターン辞書
危険判定② Moderation API
書き換え処理 ChatCompletion(gpt-4o-mini)
結果を返す JSON 化して返却

⑤ ai_filters.py(AI を使った誹謗中傷チェックロジック)

誹謗中傷チェックロジックフロー

入力テキスト
   ↓
① contains_ng_word(text) → NG なら終了
   ↓
② moderation API → flagged なら終了
   ↓
③ ChatGPT に判定させる → NG なら終了
   ↓
OK(安全な文章)

ソースコード

ai_filters.py
from openai import OpenAI
import os
import re

# OpenAI クライアントを環境変数から初期化
client = OpenAI(api_key=os.environ["OPEN_API_KEY"])

# ------------------------------------------------------
# NGワード(独自定義)。強攻撃 + 弱攻撃の代表的な語句を含む。
# 正規表現で部分一致させるため r"" を採用。
# ------------------------------------------------------
NG_PATTERNS = [
    #一部抜粋
    r"むかつく", r"ムカつく", r"ムカツク",
    r"ばか", r"バカ", r"馬鹿",
    r"アホ", r"ボケ",
    
]

def contains_ng_word(text: str) -> bool:
    """① 独自NGワード辞書による判定(最速・最優先)"""
    return any(re.search(p, text) for p in NG_PATTERNS)


def is_offensive(text: str) -> bool:
    """誹謗中傷を3段階で判定し、攻撃的なら True を返す"""

    if not text or not text.strip():
        # 空文字の場合は安全と判定
        return False

    # ------------------------------------------------------
    # ① NGワード辞書チェック
    #    強攻撃表現がないか、事前定義の語句パターンを確認。
    #    → 一番高速で確実なので最優先。
    # ------------------------------------------------------
    if contains_ng_word(text):
        return True

    # ------------------------------------------------------
    # ② Moderation API
    #    OpenAI の安全性モデルで、暴力・ヘイト等を包括的に判定。
    #    → 変形表現や辞書漏れを補完する。
    # ------------------------------------------------------
    moderation = client.moderations.create(
        model="omni-moderation-latest",
        input=text
    )
    if moderation.results[0].flagged:
        return True

    # ------------------------------------------------------
    # ③ ChatGPT による弱攻撃判定
    #    文脈や相手への矛先を理解し、小さな攻撃性も検知。
    #    このステップで最終的な OK / NG を判断する。
    # ------------------------------------------------------
    prompt = f"""
あなたは誹謗中傷検知AIです。

次の文章が以下のいずれかに該当する場合、必ず「NG」と判断してください。

【NGとする】
一部抜粋
- 他者への侮辱(例:ばか、バカ、馬鹿、アホ、ボケなど全バリエーション)
- 人格否定(例:お前は価値がない、無能など)

【OKとする】
- 攻撃性のない批評(例:改善の余地があると思います)

必ず「OK」か「NG」だけを返してください。

文章:
{text}
"""
    # ChatGPT API(ChatCompletion)を呼び出して判定を依頼
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
    )

    # AI が返した判定結果("OK" または "NG")を取り出し、前後の空白を削除
    result = response.choices[0].message.content.strip()

    # ChatGPT が NG と判断したら True
    return result == "NG"

【構造まとめ】

区分 役割 担当
① HTML 入力 UI / AI ボタン Django テンプレート
② JS 非同期通信・画面反映 ai_comment.js
③ Django views テキスト受信 / OpenAI 呼び出し soften_comment()
④ AI(OpenAI) 誹謗中傷チェック / 書き換え処理 ai_filters.py(NG判定 3種類)
⑤ forms.py 投稿前の最終フィルタ clean_comment()

さいごに

今回、この記事を書いた理由は、皆さんに AI に関する知見を共有したいという思いもあったのですが
正直に言うと、上記の実装は ChatGPT の力をかなり頼って書いたので、自分自身の復習も兼ねて整理したいという気持ちもありました。

結果として、このロジックの流れを以前より深く理解できたように感じています。

この記事を見て、何かしら参考になる方がいらっしゃれば幸いです。

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?