🎯 目的 - Goal
Dify 1.3.1 および OpenAI 0.0.1 を組み合わせて、 プロンプト(テキスト)と画像を入力として受け取る画像編集ワークフローを構築することを目的とします。
- セルフホスト環境でも安定して動作すること
- テキストと画像を組み合わせた操作に対応できること
- 外部サービスに依存せず、カスタマイズ可能な構成を維持すること
現状の課題 - Problem
セルフホスト環境においてワークフローを実行した際、タイムアウトエラーが発生し、OpenAI APIとの連携処理が完了せずにワークフローが中断される問題が確認されました。
Error processing input image: timed out
解決方針 - Solution
🔁 OpenAI API の直接利用に切り替え
Dify 組み込みのプロキシ層を介さず、OpenAI 画像編集 API を直接呼び出す構成とすることで、
タイムアウトリスクを最小化し、レスポンスの制御権を自システム側に確保します。
🏗 システム構成・実装概要 - System Design
🔹 1. 入力処理
- ユーザーは プロンプト(自然言語)と編集対象の画像を Dify にアップロード。
🔹 2. API 呼び出し
- OpenAI API(v0.0.1)を直接使用し、プロンプトに基づく画像編集を実施。
- API 仕様に応じて
image/editsやimage/variationsエンドポイントを適宜活用。
🔹 3. 出力処理
- 取得した Base64形式の画像を Amazon S3 にアップロード。
- Presigned URL(署名付き一時公開リンク)を生成し、セキュアに公開。
引数
| 引数名 | 型 | 説明 |
|---|---|---|
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_SIZE を 1.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 を向上
が実現できました。
本ワークフローは今後、以下のような応用展開も可能です:
- ✅ 自動バッチ編集、プロンプトテンプレート適用
- ✅ スケーラブルな非同期キュー処理
- ✅ エンタープライズ向け監査ログ・アクセス制限対応



