Spaces:
Paused
Paused
Upload 9 files
Browse files- DEVELOPMENT_LOG.md +107 -0
- app.py +25 -10
DEVELOPMENT_LOG.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ImageGenMCP 開発ログ
|
| 2 |
+
|
| 3 |
+
## プロジェクト概要
|
| 4 |
+
- **目的**: ClaudeCode用の画像生成MCPサーバー
|
| 5 |
+
- **技術スタック**: Gradio 5.31.0, Gemini 2.0 Flash Preview, FastAPI
|
| 6 |
+
- **デプロイ先**: Hugging Face Spaces
|
| 7 |
+
|
| 8 |
+
## 開発マイルストーン
|
| 9 |
+
|
| 10 |
+
### 2025-06-01 - プロジェクト初期化と基本実装
|
| 11 |
+
|
| 12 |
+
#### 完了項目
|
| 13 |
+
1. **プロジェクト構造の確立**
|
| 14 |
+
- `app.py` - メインアプリケーション
|
| 15 |
+
- `requirements.txt` - 依存関係
|
| 16 |
+
- `README.md` - Hugging Face Spaces仕様準拠
|
| 17 |
+
- `.gitignore` - Git設定
|
| 18 |
+
- `app.yaml` - HF Spaces設定
|
| 19 |
+
- `mcp_client.py` - MCPクライアントサンプル
|
| 20 |
+
|
| 21 |
+
2. **Gemini 2.0 Flash統合**
|
| 22 |
+
- モデル名: `gemini-2.0-flash-preview-image-generation`
|
| 23 |
+
- 画像生成機能の実装
|
| 24 |
+
- 参考画像サポート
|
| 25 |
+
- エラーハンドリングとログ出力
|
| 26 |
+
|
| 27 |
+
3. **UI崩れ問題の解決**
|
| 28 |
+
- **問題**: FastAPIとGradioの統合でUI崩れが発生
|
| 29 |
+
- **試行錯誤**:
|
| 30 |
+
- FastAPI root_path設定
|
| 31 |
+
- gr.mount_gradio_app()の使用
|
| 32 |
+
- パスマウント最適化
|
| 33 |
+
- **最終解決策**: Gradioの内部FastAPIアプリに直接ルートを追加するシンプルな構成
|
| 34 |
+
|
| 35 |
+
#### 技術的な学び
|
| 36 |
+
- Hugging Face SpacesでのFastAPI/Gradio統合は複雑になりがち
|
| 37 |
+
- Gradio 5.31.0は内部でFastAPIを使用しており、`demo.app`でアクセス可能
|
| 38 |
+
- `demo.queue()`と`demo.launch()`のシンプルな構成が最も安定
|
| 39 |
+
|
| 40 |
+
#### 現在のステータス
|
| 41 |
+
- ✅ 基本的なUI表示が正常動作
|
| 42 |
+
- ✅ 画像生成機能の実装完了
|
| 43 |
+
- ✅ MCPエンドポイントの定義完了
|
| 44 |
+
- ⏳ 実際の画像生成テスト待ち
|
| 45 |
+
- ⏳ MCPエンドポイントの動作確認待ち
|
| 46 |
+
|
| 47 |
+
## APIエンドポイント仕様
|
| 48 |
+
|
| 49 |
+
### MCPエンドポイント
|
| 50 |
+
- `POST /api/mcp/list_tools` - ツール一覧取得
|
| 51 |
+
- `POST /api/mcp/call_tool` - 画像生成実行
|
| 52 |
+
- `GET /api/health` - ヘルスチェック
|
| 53 |
+
|
| 54 |
+
### リクエスト/レスポンス形式
|
| 55 |
+
```json
|
| 56 |
+
// リクエスト (call_tool)
|
| 57 |
+
{
|
| 58 |
+
"name": "generate_image",
|
| 59 |
+
"arguments": {
|
| 60 |
+
"prompt": "生成したい画像の説明"
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
// レスポンス
|
| 65 |
+
{
|
| 66 |
+
"success": true,
|
| 67 |
+
"message": "画像生成に成功しました!",
|
| 68 |
+
"image_base64": "base64エンコードされた画像データ"
|
| 69 |
+
}
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
## 次のステップ
|
| 73 |
+
1. 画像生成機能の実地テスト
|
| 74 |
+
2. MCPエンドポイントの動作確認
|
| 75 |
+
3. ClaudeCodeからの統合テスト
|
| 76 |
+
4. パフォーマンス最適化
|
| 77 |
+
5. エラーハンドリングの強化
|
| 78 |
+
|
| 79 |
+
## 既知の問題と対策
|
| 80 |
+
|
| 81 |
+
### 1. Gemini API レスポンスモダリティエラー (解決済み)
|
| 82 |
+
- **問題**: `response_modalities=["IMAGE"]`のみだとエラー
|
| 83 |
+
- **原因**: モデルは`IMAGE`と`TEXT`の両方を要求
|
| 84 |
+
- **解決**: `response_modalities=["IMAGE", "TEXT"]`に変更
|
| 85 |
+
|
| 86 |
+
### 2. 画像生成の不安定性 (調査中)
|
| 87 |
+
- **症状**: 1回目は成功するが、2回目以降は画像データが含まれない
|
| 88 |
+
- **対策実施**:
|
| 89 |
+
- レスポンスのパーツを全て検査するように改善
|
| 90 |
+
- テキストコンテンツも収集してエラー理由を把握
|
| 91 |
+
- **推測される原因**:
|
| 92 |
+
- レート制限
|
| 93 |
+
- コンテンツフィルタリング
|
| 94 |
+
- プロンプトの内容による拒否
|
| 95 |
+
|
| 96 |
+
## 環境設定メモ
|
| 97 |
+
- Hugging Face Spaces Secrets:
|
| 98 |
+
- `GEMINI_API_KEY` - Gemini APIキー(必須)
|
| 99 |
+
|
| 100 |
+
## トラブルシューティング記録
|
| 101 |
+
1. **UI崩れ問題** (解決済み)
|
| 102 |
+
- 症状: 画像アイコンのみ表示、UIコンポーネントが表示されない
|
| 103 |
+
- 原因: FastAPIとGradioのパス競合
|
| 104 |
+
- 解決: シンプルな統合方式に変更
|
| 105 |
+
|
| 106 |
+
---
|
| 107 |
+
最終更新: 2025-06-01
|
app.py
CHANGED
|
@@ -94,19 +94,34 @@ def generate_image(prompt: str, previous_image: Optional[Image.Image] = None) ->
|
|
| 94 |
logger.info("Gemini APIの呼び出し完了")
|
| 95 |
|
| 96 |
# レスポンスから画像を取得
|
| 97 |
-
if
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
|
| 102 |
-
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
|
| 105 |
-
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
else:
|
| 108 |
-
logger.warning("レスポンスに
|
| 109 |
-
return None, "画像の生成に失敗しました。レスポンス
|
| 110 |
|
| 111 |
except Exception as e:
|
| 112 |
logger.error(f"画像生成エラー: {str(e)}")
|
|
|
|
| 94 |
logger.info("Gemini APIの呼び出し完了")
|
| 95 |
|
| 96 |
# レスポンスから画像を取得
|
| 97 |
+
if response.candidates and response.candidates[0].content and response.candidates[0].content.parts:
|
| 98 |
+
# パーツを検査して画像を探す
|
| 99 |
+
image_found = False
|
| 100 |
+
text_content = ""
|
| 101 |
|
| 102 |
+
for part in response.candidates[0].content.parts:
|
| 103 |
+
if hasattr(part, 'inline_data') and part.inline_data:
|
| 104 |
+
# 画像データが見つかった
|
| 105 |
+
image_data = part.inline_data.data
|
| 106 |
+
image = Image.open(io.BytesIO(image_data))
|
| 107 |
+
logger.info(f"画像生成成功: サイズ={image.size}")
|
| 108 |
+
image_found = True
|
| 109 |
+
elif hasattr(part, 'text') and part.text:
|
| 110 |
+
# テキストコンテンツを収集
|
| 111 |
+
text_content += part.text
|
| 112 |
|
| 113 |
+
if image_found:
|
| 114 |
+
return image, "画像生成に成功しました!"
|
| 115 |
+
else:
|
| 116 |
+
# 画像が含まれていない場合の詳細ログ
|
| 117 |
+
logger.warning(f"レスポンスに画像データが含まれていません。テキスト: {text_content[:200]}")
|
| 118 |
+
if "I cannot" in text_content or "I can't" in text_content or "unable to" in text_content:
|
| 119 |
+
return None, f"画像生成が拒否されました: {text_content}"
|
| 120 |
+
else:
|
| 121 |
+
return None, "画像の生成に失敗しました。レスポンスに画像データが含まれていません。"
|
| 122 |
else:
|
| 123 |
+
logger.warning("レスポンスに有効なコンテンツが含まれていません")
|
| 124 |
+
return None, "画像の生成に失敗しました。レスポンスが空です。"
|
| 125 |
|
| 126 |
except Exception as e:
|
| 127 |
logger.error(f"画像生成エラー: {str(e)}")
|