1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Difyでの画像編集ワークフローの構築

1
Posted at

🎯 目的 - Goal

Dify 1.3.1 および OpenAI 0.0.1 を組み合わせて、 プロンプト(テキスト)と画像を入力として受け取る画像編集ワークフローを構築することを目的とします。

  • セルフホスト環境でも安定して動作すること
  • テキストと画像を組み合わせた操作に対応できること
  • 外部サービスに依存せず、カスタマイズ可能な構成を維持すること

image.png

現状の課題 - Problem

セルフホスト環境においてワークフローを実行した際、タイムアウトエラーが発生し、OpenAI APIとの連携処理が完了せずにワークフローが中断される問題が確認されました。

Error processing input image: timed out

image.png

解決方針 - Solution

🔁 OpenAI API の直接利用に切り替え

Dify 組み込みのプロキシ層を介さず、OpenAI 画像編集 API を直接呼び出す構成とすることで、
タイムアウトリスクを最小化し、レスポンスの制御権を自システム側に確保します。

image.png

🏗 システム構成・実装概要 - System Design

🔹 1. 入力処理

  • ユーザーは プロンプト(自然言語)と編集対象の画像を Dify にアップロード。

🔹 2. API 呼び出し

  • OpenAI API(v0.0.1)を直接使用し、プロンプトに基づく画像編集を実施。
  • API 仕様に応じて image/editsimage/variations エンドポイントを適宜活用。

🔹 3. 出力処理

  • 取得した Base64形式の画像を Amazon S3 にアップロード。
  • Presigned URL(署名付き一時公開リンク)を生成し、セキュアに公開。

image.png

引数
引数名 説明
body str / dict 前のHTTPリクエストの結果(Base64データを含むJSON)
get_presigned_url_api str Presigned URL取得用のAPI Gatewayエンドポイント
詳細を表示
import requests
import base64
import json

def get_presigned_url(api_url):
    """
    プリサインドURLを取得するためにAPIを呼び出す。
    :param api_url: プリサインドURLを取得するAPIのURL。
    :return: プリサインドURL (putとget)を返す。失敗時は例外をスロー。
    """
    try:
        response = requests.get(api_url)
        if response.status_code == 200:
            response_data = response.json()
            presigned_put_url = response_data.get('presigned_put_url')
            presigned_get_url = response_data.get('presigned_get_url')
            if not presigned_put_url or not presigned_get_url:
                raise ValueError("プリサインドURLがAPIレスポンスに含まれていません")
            return presigned_put_url, presigned_get_url
        else:
            raise Exception(f"プリサインドURLの取得に失敗しました: {response.status_code} - {response.text}")
    except Exception as e:
        print(f"プリサインドURLの取得中にエラーが発生しました: {e}")
        raise

def upload_to_s3(base64_data, presigned_put_url, presigned_get_url):
    """
    プリサインドURLを使用してS3にファイルをアップロードする。
    """
    try:
        file_data = base64.b64decode(base64_data)
        response = requests.put(presigned_put_url, data=file_data)
        if response.status_code == 200:
            print("ファイルのアップロードに成功しました")
            return {
                "result": "ファイルのアップロードに成功しました",
                "presigned_get_url": presigned_get_url,
                "status_code": 200
            }
        else:
            print(f"ファイルのアップロードに失敗しました: {response.status_code}")
            return {
                "result": f"ファイルのアップロードに失敗しました: {response.status_code}",
                "presigned_get_url": "",
                "status_code": response.status_code
            }
    except Exception as e:
        print(f"ファイルのアップロード中にエラーが発生しました: {e}")
        return {
            "result": f"ファイルのアップロード中にエラーが発生しました: {e}",
            "presigned_get_url": "",
            "status_code": 500
        }

def main(body, get_presigned_url_api):
    """
    bodyからBase64データを抽出し、S3にアップロードする。
    """
    try:
        if isinstance(body, str):
            try:
                body_object = json.loads(body)
            except json.JSONDecodeError as e:
                return {
                    "result": f"JSONパースに失敗: {str(e)}",
                    "presigned_get_url": "",
                    "status_code": 400
                }
            if isinstance(body_object, str):
                try:
                    body_object = json.loads(body_object)
                except json.JSONDecodeError as e:
                    return {
                        "result": f"2回目のJSONパースに失敗: {str(e)}",
                        "presigned_get_url": "",
                        "status_code": 400
                    }
        else:
            body_object = body

        if not isinstance(body_object, dict):
            return {
                "result": "body_objectが辞書型ではありません",
                "presigned_get_url": "",
                "status_code": 400
            }
        if 'data' not in body_object:
            return {
                "result": "'data'キーが存在しません",
                "presigned_get_url": "",
                "status_code": 400
            }
        if not isinstance(body_object['data'], list) or not body_object['data']:
            return {
                "result": "'data'は空でないリストである必要があります",
                "presigned_get_url": "",
                "status_code": 400
            }
        if not isinstance(body_object['data'][0], dict) or 'b64_json' not in body_object['data'][0]:
            return {
                "result": "data[0]の構造が無効",
                "presigned_get_url": "",
                "status_code": 400
            }

        b64_json = body_object['data'][0]['b64_json']

        try:
            base64.b64decode(b64_json)
        except Exception as e:
            return {
                "result": f"無効なBase64データ: {str(e)}",
                "presigned_get_url": "",
                "status_code": 400
            }

        try:
            presigned_put_url, presigned_get_url = get_presigned_url(get_presigned_url_api)
        except Exception as e:
            return {
                "result": f"プリサインドURLの取得に失敗しました: {str(e)}",
                "presigned_get_url": "",
                "status_code": 500
            }

        result = upload_to_s3(b64_json, presigned_put_url, presigned_get_url)
        return result

    except Exception as e:
        return {
            "result": f"予期しないエラー: {str(e)}",
            "presigned_get_url": "",
            "status_code": 500
        }

▷ 注意

セキュリティ向上のため、get_presigned_url_api に対応する API Gateway に WAF(Web Application Firewall) を設定することを推奨します。

🔹 4. 結果表示

  • Dify 上の UI にて、編集後画像へのアクセスURLを表示
  • ユーザーはワンクリックで 閲覧・ダウンロードが可能

🐛 発生した問題と対応策

🔸 ① レスポンスサイズ超過によるエラー

■ 内容

OpenAI API からのレスポンスが 1MB を超過し、以下のようなエラーが発生:
"Text size is too large, max size is 1.00 MB, but current size is 1.56 MB"

■ 対応

Dify の環境変数 HTTP_REQUEST_NODE_MAX_TEXT_SIZE1.5MB 以上に増加して対応。

✅ 補足

この変数は Dify v1.3.1 の node-backend にて定義されているため、
Docker 環境変数 または .env ファイルでの設定変更が必要です。

🔸 ② Dify の Base64 表示制限

■ 内容

Dify の UI では Base64 データの直接出力が制限されているため、
生成画像の内容確認が困難な状況。

■ 対応

編集後の画像を S3 にアップロードし、Presigned URL を生成
外部ブラウザで 安全かつ簡単に表示・保存できる構成に変更。

✅ この対応により、Dify のフロントエンドを改修せずに要件を満たすことができました。


🔸 ③ 安全かつ効率的な S3 アップロード方式

■ 内容

サーバ経由の直接アップロードは、セキュリティやスケーラビリティに課題あり。

■ 対応

Presigned URL を利用し、クライアントから S3 へ直接 PUT する方式を採用。
これにより以下を実現:

  • IAM ロールベースのアクセス制御
  • 有効期限付きの一時的公開
  • サーバ負荷軽減と高速処理

🔸 ④ 複数画像の同時処理(対応中)

■ 内容

現時点では 単一画像ごとの処理のみ対応。

■ 課題

複数画像を一括処理する場合、負荷分散や同時制御の設計が未整備

■ 対応予定

次フェーズにて、紹介させていただきます。

📝 結論(Conclusion)

本構成により:

  • セルフホスト環境での OpenAI API タイムアウト問題を回避
  • S3 + Presigned URL を活用した、安全かつ高速な画像配信
  • Dify 1.3.1 の制約を環境設定で緩和し、UI変更なしで UX を向上

が実現できました。

本ワークフローは今後、以下のような応用展開も可能です:

  • ✅ 自動バッチ編集、プロンプトテンプレート適用
  • ✅ スケーラブルな非同期キュー処理
  • ✅ エンタープライズ向け監査ログ・アクセス制限対応

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?