kina006097 commited on
Commit
f079f59
·
1 Parent(s): 60b8094

口コミ要約機能の実装

Browse files
.github/workflows/ci.yml CHANGED
@@ -40,4 +40,4 @@ jobs:
40
  run: mypy -p ai_api -p tests
41
 
42
  - name: Test with pytest
43
- run: PYTHONPATH=src pytest
 
40
  run: mypy -p ai_api -p tests
41
 
42
  - name: Test with pytest
43
+ run: PYTHONPATH=src pytest
.pre-commit-config.yaml CHANGED
@@ -1,6 +1,6 @@
1
  repos:
2
  - repo: https://github.com/astral-sh/ruff-pre-commit
3
- rev: v0.4.10
4
  hooks:
5
  - id: ruff
6
  args: [--fix, --exit-non-zero-on-fix]
 
1
  repos:
2
  - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.5.5
4
  hooks:
5
  - id: ruff
6
  args: [--fix, --exit-non-zero-on-fix]
Dockerfile CHANGED
@@ -10,6 +10,10 @@ RUN pip install --no-cache-dir -r requirements.txt
10
 
11
  COPY . .
12
 
 
 
 
13
  EXPOSE 7860
14
 
15
- CMD ["python", "src/ai_api/main.py"]
 
 
10
 
11
  COPY . .
12
 
13
+ # PYTHONPATHにsrcディレクトリを追加し、モジュールを正しく見つけられるようにする
14
+ ENV PYTHONPATH "${PYTHONPATH}:/app/src"
15
+
16
  EXPOSE 7860
17
 
18
+ # アプリケーションをモジュールとして実行する
19
+ CMD ["python", "-m", "ai_api.main"]
GEMINI.md CHANGED
@@ -69,8 +69,8 @@
69
  - クラスが外部の依存関係(例: jQuery, `alert`関数)に直接依存するのではなく、コンストラクタを通じてそれらを受け取る「依存性注入」のパターンを採用することで、テスト時にこれらの依存関係をモックしやすくなり、コードのテスト容易性が向上する。
70
 
71
  ## 🛠 技術スタック
72
- - フレームワーク:
73
- - 言語:
74
  - テスト: pytest
75
  - バージョン管理: GitHub
76
- - 開発環境:
 
69
  - クラスが外部の依存関係(例: jQuery, `alert`関数)に直接依存するのではなく、コンストラクタを通じてそれらを受け取る「依存性注入」のパターンを採用することで、テスト時にこれらの依存関係をモックしやすくなり、コードのテスト容易性が向上する。
70
 
71
  ## 🛠 技術スタック
72
+ - フレームワーク:
73
+ - 言語:
74
  - テスト: pytest
75
  - バージョン管理: GitHub
76
+ - 開発環境:
docs/ai_api_development_guide.md CHANGED
@@ -9,7 +9,7 @@
9
  本プロジェクトの目的は、「親子で遊ぼうナビ」にAIを活用した**口コミの自動要約機能**を実装することです。
10
 
11
  - **目的:** ユーザーが多数の口コミを全て読んでも、施設の全体的な評判を素早く、かつ客観的に把握できるようにする。
12
- - **ユーザー体験:**
13
  1. ユーザーは施設の詳細ページで「口コミをAI要約」ボタンをクリックします。
14
  2. AIがその施設の全口コミを分析し、「ポジティブな点」と「注意が必要な点」などをまとめた中立的な要約文を生成します。
15
  3. 生成された要約がモーダルウィンドウ等で表示され、ユーザーは短時間で施設の長所と短所を理解できます。
@@ -25,10 +25,10 @@
25
 
26
  ### 1.3. 技術選定理由
27
 
28
- - **Hugging Face Spaces:**
29
  - **選定理由:** 無料枠が利用可能で、迅速なプロトタイピングに適しています。また、GitHubリポジトリと連携した自動デプロイ機能(CI/CD)が標準で提供されており、開発体験が非常にスムーズです。
30
  - **代替案との比較:** AWS LambdaやGoogle Cloud Functionsのようなサーバーレス環境も考えられますが、これらはコンテナイメージのサイズ制限が厳しく、大規模なAIモデルのデプロイには追加の工夫が必要です。Hugging Face Inference APIは最も手軽ですが、カスタムロジックの追加やUIの提供ができないため、今回はGradioと組み合わせられるSpacesを選択しました。
31
- - **Gradio:**
32
  - **選定理由:** 数行のコードでAIモデルのデモUIとAPIエンドポイントを同時に作成できるため、開発速度を大幅に向上させます。特に、動作確認用のUIが自動で生成される点は、開発初期段階での実験やデバッグにおいて大きな利点となります。
33
 
34
  ### 1.4. アーキテクチャ図
@@ -159,7 +159,7 @@ AI新機能の開発においては、以下のコーディング規約と開発
159
  * **PEP 8準拠**: PythonコードはPEP 8スタイルガイドに厳密に準拠します。`flake8` および `black` による自動フォーマットとリントを徹底し、コードの一貫性を保ちます。
160
  * **型ヒントの活用**: `mypy` を用いた型チェックを徹底し、コードの堅牢性、可読性、およびIDEによる補完の恩恵を最大化します。
161
  * **Djangoとの連携**: AI機能がDjangoアプリケーションと連携する場合、Djangoのモデル、ビュー、フォーム、URLパターンなどの既存の命名規則と構造に準拠し、シームレスな統合を図ります。
162
- * **AI/MLコードの構造**:
163
  - データ処理(前処理、特徴量エンジニアリング)、モデル定義、学習ロジック、推論ロジックは明確に分離し、それぞれが単一の責務を持つモジュールとして設計します。
164
  - 設定値やハイパーパラメータはコードから分離し、Djangoの`settings.py`、または専用のYAML/JSONファイルなどで一元的に管理します。
165
  * **ドキュメンテーション**: 関数、クラス、複雑なアルゴリズム、およびAIモデルの設計意図には、適切なDocstringを記述し、コードの意図と振る舞いを明確にします。
@@ -194,7 +194,7 @@ AI新機能の開発においては、以下のコーディング規約と開発
194
 
195
  ### 6.2. 設定
196
  - **GitHub Actions:** `.github/workflows/ci.yml`にテストとデプロイのワークフローを定義します。
197
- - **Hugging Face Hub & GitHub Secrets:**
198
  1. Hugging Faceの個人設定で、`write`権限を持つアクセストークンを発行します。
199
  2. 発行したトークンを、GitHubリポジトリの`Settings > Secrets and variables > Actions`に`HF_TOKEN`として登録します。
200
  3. `ci.yml`内のデプロイジョブは、この`HF_TOKEN`を安全に利用してHugging Faceへの認証を行います。
@@ -212,7 +212,7 @@ AI新機能の開発においては、以下のコーディング規約と開発
212
  2. **Secretsへの登録:** 生成したキーを、DjangoとGradio APIの両方の環境変数(Hugging Face SpacesのSecrets)として登録します。
213
  - `AI_API_KEY`: Gradio側でリクエストを検証するためのキー
214
  - `DJANGO_AI_API_KEY`: Djangoがリクエスト時に送信するキー
215
- 3. **認証の実装:**
216
  - **Django側:** APIを呼び出す際、HTTPヘッダー(例: `Authorization: Bearer <APIキー>`)にAPIキーを含めて送信します。
217
  - **Gradio側:** FastAPIの依存性注入(`Depends`)などを利用して、リクエストヘッダーをチェックする認証関数を定義します。APIキーが一致しない場合は、HTTP 401または403エラーを返却します。
218
 
@@ -329,13 +329,13 @@ AIの性能を最大限に引き出し、安定した運用を行うための内
329
  | **ブラウザ ⇔ Django間** | ユーザーのオフライン | jQuery Ajaxの`.fail()`コールバックで捕捉。 | 「通信に失敗しました」 |
330
 
331
  #### 9.4.2. 実装のポイント
332
- - **Django (司令塔) の役割:**
333
  - Djangoのビューは、Gradio APIとの通信部分を必ず`try...except`ブロックで囲み、タイムアウト(例: 30秒)を設定します。
334
  - APIから返されたHTTPステータスコードを常にチェックし、200番台以外はエラーとして処理します。
335
  - 発生したエラーは、**必ずサーバーログに記録**し、原因調査に役立てます。
336
  - ユーザーには、技術的なエラー詳細(スタックトレース等)を直接見せず、「AIサーバーで問題が発生しました」のような抽象的で分かりやすいメッセージを返します。
337
 
338
- - **Gradio API (専門家) の役割:**
339
  - AIの推論処理など、失敗する可能性のあるコードは`try...except`ブロックで囲みます。
340
  - エラー発生時は、`raise gr.Error("具体的なエラー原因")`を呼び出し、APIの契約通りにエラー情報を返却します。
341
 
@@ -426,18 +426,18 @@ Django側でAI APIを呼び出すビューは、責務を明確に分離し、
426
  アプリケーションの品質と信頼性を保証するため、以下の通り多層的なテスト戦略を設計します。
427
 
428
  #### 9.8.1. テストの種類と目的
429
- - **ユニットテスト (Unit Tests):**
430
  - **目的:** 個々の部品(クラス、メソッド)が単体で正しく動作することを検証します。高速に実行できるため、開発中の頻繁な確認に適しています。
431
  - **対象:** `core/inference.py` の `Summarizer` クラスなど、ビジネスロジックの中核を担う部分。
432
- - **インテグレーションテスト (Integration Tests):**
433
  - **目的:** Gradio APIのエンドポイントが、APIの契約通りに正しくリクエストを処理し、レスポンスを返すことを検証します。コンポーネント間の連携を確認します。
434
  - **対象:** ローカルで起動したGradioアプリケーションの `/api/predict/` エンドポイント。
435
 
436
  #### 9.8.2. テストケースの計画
437
- - **ユニットテスト (`tests/core/test_inference.py`):**
438
  - **正常系:** 通常のテキストが入力された場合に、期待される形式(文字列)の要約が返ることを確認する。
439
  - **異常系:** 空文字列や不正なデータ型が入力された場合に、設計通り`ValueError`等の例外が発生することを確認する。
440
- - **インテグレーションテスト (`tests/test_api.py`):**
441
  - **正常系:** APIに有効なリクエストを送信し、HTTPステータスコード`200`と、設計通りのJSONレスポンスが返ることを確認する。
442
  - **異常系:** 不正なリクエストを送信した場合に、適切なHTTPエラーステータスコード(例: `4xx`)が返ることを確認する。
443
 
@@ -497,13 +497,13 @@ sequenceDiagram
497
  - **クラス:** `SummarizeReviewsView(View)`
498
  - **メソッド:** `get(self, request, playground_id)`
499
  - **責務:** リクエストを受け付け、AI APIとの通信を制御する司令塔。
500
- - **処理フロー:**
501
  1. DBから口コミを取得し、件数や文字数を検証する。
502
  2. `_call_summary_api` メソッドを呼び出し、AI APIにリクエストを送信する。
503
  3. 返ってきた結果(成功またはエラー)を整形し、`JsonResponse`としてフロントエンドに返す。
504
  - **メソッド:** `_call_summary_api(self, text)`
505
  - **責務:** `requests`ライブラリを用いて、AI APIとのHTTP通信を実際に担当する。
506
- - **実装詳細:**
507
  - `settings.AI_SUMMARY_API_URL` からAPIのエンドポイントURLを取得する。
508
  - `requests.post()` を使い、タイムアウトを設定してPOSTリクエストを送信する。
509
  - 通信エラーや、APIが返すエラーステータスコードを`try...except`で捕捉し、適切に処理する。
@@ -513,7 +513,7 @@ sequenceDiagram
513
 
514
  - **ファイル:** `main.py` (エントリーポイント)
515
  - **責務:** Gradioアプリケーションを起動し、HTTPリクエストを受け付ける窓口。
516
- - **実装詳細:**
517
  1. 起動時に一度だけ、`core.inference.Summarizer` クラスのインスタンスを生成する。
518
  2. `functools.partial` を使い、API処理関数に `Summarizer` のインスタンスを注入(バインド)する。
519
  3. `gradio.Interface` を定義し、リクエストを処理する関数として上記でバインドした関数を渡す。これにより、Gradioは `/api/predict/` というエンドポイントを自動的に作成する。
@@ -527,16 +527,16 @@ sequenceDiagram
527
 
528
  この設計が、いかにして疎結合(Loose Coupling)を担保しているかを以下に示します。
529
 
530
- - **通信の抽象化:**
531
  - DjangoとAIアプリは、**HTTPとJSON**という標準化された技術でのみ通信します。
532
  - DjangoはAIアプリがGradioで実装されていることを知る必要はなく、逆もまた然りです。知っているのは「どのURLに、どんなJSONを送れば、どんなJSONが返ってくるか」というAPI契約だけです。
533
 
534
- - **独立した実行とテスト:**
535
- - **AIアプリケーション:**
536
  - `src/ai_api/` ディレクトリは、それ自体が完結したPythonプロジェクトです。
537
  - `python src/ai_api/main.py` を実行すれば、Djangoとは無関係に単体で起動できます。
538
  - 起動したAIアプリに対し、ブラウザでUIを操作したり、`curl`コマンドでAPIを直接叩いたりすることで、単体での動作テストが可能です。
539
- - **Djangoアプリケーション:**
540
  - `SummarizeReviewsView` のテストを書く際、`unittest.mock.patch` を使って `_call_summary_api` メソッドをモック(偽のオブジェクトに差し替え)します。
541
  - これにより、AI APIへ実際にネットワーク通信を発生させることなく、「APIが成功を返した場合」「タイムアウトした場合」「エラーを返した場合」など、あらゆる状況を想定したビューのロジックを高速にテストできます。
542
 
@@ -556,7 +556,7 @@ sequenceDiagram
556
 
557
  ##### 設定案 (`[tool.ruff]`セクション)
558
  - `line-length = 88`: 1行の最大長を`black`に合わせて88文字に設定。
559
- - `select = ["E", "W", "F", "I", "B", "UP"]`:
560
  - `E`, `W`, `F`: `pycodestyle`と`Pyflakes`の基本的なエラー・警告(必須)。
561
  - `I`: `isort`互換のimport文の自動ソート(必須)。
562
  - `B`: `flake8-bugbear`の、バグの温床となりやすいコードパターンを検出するルール。
@@ -673,7 +673,7 @@ git commit -m "Initial commit"
673
  - [x] **中タスク0.3: GitHubリポジトリの作成と連携**
674
  - **担当:** 人間
675
  - **内容:** GitHub上に新しいリポジトリを作成し、ローカルリポジトリと連携させます。
676
- - **指示:**
677
  1. GitHubにログインし、新しいリポジトリを作成します(リポジトリ名は `kids-playground-ai-api` など)。READMEや.gitignore、ライセンスの追加はスキップしてください。
678
  2. 作成されたリポジトリページに表示される指示に従い、ローカルリポジトリをリモートにプッシュします。
679
  ```bash
@@ -685,7 +685,7 @@ git push -u origin main
685
  - [x] **中タスク0.4: Hugging Faceアカウントの登録とSpacesの準備**
686
  - **担当:** 人間
687
  - **内容:** Hugging Faceアカウントを登録し、AIモデルをデプロイするためのHugging Face Spaceを準備します。
688
- - **指示:**
689
  1. Hugging Faceのウェブサイト (huggingface.co) にアクセスし、アカウントを登録します。
690
  2. ログイン後、「Spaces」セクションに移動し、「Create new Space」をクリックします。
691
  3. Space名、ライセンス、SDK(Gradioを選択)、公開/非公開設定などを適切に設定し、Spaceを作成します。
@@ -752,7 +752,7 @@ mypy
752
  - [x] **小タスク1.3.2: Docker開発環境の構築と起動**
753
  - **担当:** 人間
754
  - **内容:** Docker Desktopを利用して、プロジェクトのDocker開発環境を構築し、起動します。これにより、必要な依存関係がコンテナ内に自動的にインストールされます。
755
- - **指示:**
756
  1. Docker Desktopがインストールされ、起動していることを確認してください。
757
  2. プロジェクトのルートディレクトリで、以下のコマンドを実行してDockerコンテナをビルドし、バックグラウンドで起動します。
758
  ```bash
@@ -907,4 +907,4 @@ docker-compose up --build -d
907
  - **担当:** 人間
908
  - **内容:** DjangoサーバーとAIアプリ(Gradio)を両方ローカルで起動し、ブラウザから最初の要約ボタンをクリックして、全体の流れが正しく動作���るかを確認します。
909
 
910
- ```
 
9
  本プロジェクトの目的は、「親子で遊ぼうナビ」にAIを活用した**口コミの自動要約機能**を実装することです。
10
 
11
  - **目的:** ユーザーが多数の口コミを全て読んでも、施設の全体的な評判を素早く、かつ客観的に把握できるようにする。
12
+ - **ユーザー体験:**
13
  1. ユーザーは施設の詳細ページで「口コミをAI要約」ボタンをクリックします。
14
  2. AIがその施設の全口コミを分析し、「ポジティブな点」と「注意が必要な点」などをまとめた中立的な要約文を生成します。
15
  3. 生成された要約がモーダルウィンドウ等で表示され、ユーザーは短時間で施設の長所と短所を理解できます。
 
25
 
26
  ### 1.3. 技術選定理由
27
 
28
+ - **Hugging Face Spaces:**
29
  - **選定理由:** 無料枠が利用可能で、迅速なプロトタイピングに適しています。また、GitHubリポジトリと連携した自動デプロイ機能(CI/CD)が標準で提供されており、開発体験が非常にスムーズです。
30
  - **代替案との比較:** AWS LambdaやGoogle Cloud Functionsのようなサーバーレス環境も考えられますが、これらはコンテナイメージのサイズ制限が厳しく、大規模なAIモデルのデプロイには追加の工夫が必要です。Hugging Face Inference APIは最も手軽ですが、カスタムロジックの追加やUIの提供ができないため、今回はGradioと組み合わせられるSpacesを選択しました。
31
+ - **Gradio:**
32
  - **選定理由:** 数行のコードでAIモデルのデモUIとAPIエンドポイントを同時に作成できるため、開発速度を大幅に向上させます。特に、動作確認用のUIが自動で生成される点は、開発初期段階での実験やデバッグにおいて大きな利点となります。
33
 
34
  ### 1.4. アーキテクチャ図
 
159
  * **PEP 8準拠**: PythonコードはPEP 8スタイルガイドに厳密に準拠します。`flake8` および `black` による自動フォーマットとリントを徹底し、コードの一貫性を保ちます。
160
  * **型ヒントの活用**: `mypy` を用いた型チェックを徹底し、コードの堅牢性、可読性、およびIDEによる補完の恩恵を最大化します。
161
  * **Djangoとの連携**: AI機能がDjangoアプリケーションと連携する場合、Djangoのモデル、ビュー、フォーム、URLパターンなどの既存の命名規則と構造に準拠し、シームレスな統合を図ります。
162
+ * **AI/MLコードの構造**:
163
  - データ処理(前処理、特徴量エンジニアリング)、モデル定義、学習ロジック、推論ロジックは明確に分離し、それぞれが単一の責務を持つモジュールとして設計します。
164
  - 設定値やハイパーパラメータはコードから分離し、Djangoの`settings.py`、または専用のYAML/JSONファイルなどで一元的に管理します。
165
  * **ドキュメンテーション**: 関数、クラス、複雑なアルゴリズム、およびAIモデルの設計意図には、適切なDocstringを記述し、コードの意図と振る舞いを明確にします。
 
194
 
195
  ### 6.2. 設定
196
  - **GitHub Actions:** `.github/workflows/ci.yml`にテストとデプロイのワークフローを定義します。
197
+ - **Hugging Face Hub & GitHub Secrets:**
198
  1. Hugging Faceの個人設定で、`write`権限を持つアクセストークンを発行します。
199
  2. 発行したトークンを、GitHubリポジトリの`Settings > Secrets and variables > Actions`に`HF_TOKEN`として登録します。
200
  3. `ci.yml`内のデプロイジョブは、この`HF_TOKEN`を安全に利用してHugging Faceへの認証を行います。
 
212
  2. **Secretsへの登録:** 生成したキーを、DjangoとGradio APIの両方の環境変数(Hugging Face SpacesのSecrets)として登録します。
213
  - `AI_API_KEY`: Gradio側でリクエストを検証するためのキー
214
  - `DJANGO_AI_API_KEY`: Djangoがリクエスト時に送信するキー
215
+ 3. **認証の実装:**
216
  - **Django側:** APIを呼び出す際、HTTPヘッダー(例: `Authorization: Bearer <APIキー>`)にAPIキーを含めて送信します。
217
  - **Gradio側:** FastAPIの依存性注入(`Depends`)などを利用して、リクエストヘッダーをチェックする認証関数を定義します。APIキーが一致しない場合は、HTTP 401または403エラーを返却します。
218
 
 
329
  | **ブラウザ ⇔ Django間** | ユーザーのオフライン | jQuery Ajaxの`.fail()`コールバックで捕捉。 | 「通信に失敗しました」 |
330
 
331
  #### 9.4.2. 実装のポイント
332
+ - **Django (司令塔) の役割:**
333
  - Djangoのビューは、Gradio APIとの通信部分を必ず`try...except`ブロックで囲み、タイムアウト(例: 30秒)を設定します。
334
  - APIから返されたHTTPステータスコードを常にチェックし、200番台以外はエラーとして処理します。
335
  - 発生したエラーは、**必ずサーバーログに記録**し、原因調査に役立てます。
336
  - ユーザーには、技術的なエラー詳細(スタックトレース等)を直接見せず、「AIサーバーで問題が発生しました」のような抽象的で分かりやすいメッセージを返します。
337
 
338
+ - **Gradio API (専門家) の役割:**
339
  - AIの推論処理など、失敗する可能性のあるコードは`try...except`ブロックで囲みます。
340
  - エラー発生時は、`raise gr.Error("具体的なエラー原因")`を呼び出し、APIの契約通りにエラー情報を返却します。
341
 
 
426
  アプリケーションの品質と信頼性を保証するため、以下の通り多層的なテスト戦略を設計します。
427
 
428
  #### 9.8.1. テストの種類と目的
429
+ - **ユニットテスト (Unit Tests):**
430
  - **目的:** 個々の部品(クラス、メソッド)が単体で正しく動作することを検証します。高速に実行できるため、開発中の頻繁な確認に適しています。
431
  - **対象:** `core/inference.py` の `Summarizer` クラスなど、ビジネスロジックの中核を担う部分。
432
+ - **インテグレーションテスト (Integration Tests):**
433
  - **目的:** Gradio APIのエンドポイントが、APIの契約通りに正しくリクエストを処理し、レスポンスを返すことを検証します。コンポーネント間の連携を確認します。
434
  - **対象:** ローカルで起動したGradioアプリケーションの `/api/predict/` エンドポイント。
435
 
436
  #### 9.8.2. テストケースの計画
437
+ - **ユニットテスト (`tests/core/test_inference.py`):**
438
  - **正常系:** 通常のテキストが入力された場合に、期待される形式(文字列)の要約が返ることを確認する。
439
  - **異常系:** 空文字列や不正なデータ型が入力された場合に、設計通り`ValueError`等の例外が発生することを確認する。
440
+ - **インテグレーションテスト (`tests/test_api.py`):**
441
  - **正常系:** APIに有効なリクエストを送信し、HTTPステータスコード`200`と、設計通りのJSONレスポンスが返ることを確認する。
442
  - **異常系:** 不正なリクエストを送信した場合に、適切なHTTPエラーステータスコード(例: `4xx`)が返ることを確認する。
443
 
 
497
  - **クラス:** `SummarizeReviewsView(View)`
498
  - **メソッド:** `get(self, request, playground_id)`
499
  - **責務:** リクエストを受け付け、AI APIとの通信を制御する司令塔。
500
+ - **処理フロー:**
501
  1. DBから口コミを取得し、件数や文字数を検証する。
502
  2. `_call_summary_api` メソッドを呼び出し、AI APIにリクエストを送信する。
503
  3. 返ってきた結果(成功またはエラー)を整形し、`JsonResponse`としてフロントエンドに返す。
504
  - **メソッド:** `_call_summary_api(self, text)`
505
  - **責務:** `requests`ライブラリを用いて、AI APIとのHTTP通信を実際に担当する。
506
+ - **実装詳細:**
507
  - `settings.AI_SUMMARY_API_URL` からAPIのエンドポイントURLを取得する。
508
  - `requests.post()` を使い、タイムアウトを設定してPOSTリクエストを送信する。
509
  - 通信エラーや、APIが返すエラーステータスコードを`try...except`で捕捉し、適切に処理する。
 
513
 
514
  - **ファイル:** `main.py` (エントリーポイント)
515
  - **責務:** Gradioアプリケーションを起動し、HTTPリクエストを受け付ける窓口。
516
+ - **実装詳細:**
517
  1. 起動時に一度だけ、`core.inference.Summarizer` クラスのインスタンスを生成する。
518
  2. `functools.partial` を使い、API処理関数に `Summarizer` のインスタンスを注入(バインド)する。
519
  3. `gradio.Interface` を定義し、リクエストを処理する関数として上記でバインドした関数を渡す。これにより、Gradioは `/api/predict/` というエンドポイントを自動的に作成する。
 
527
 
528
  この設計が、いかにして疎結合(Loose Coupling)を担保しているかを以下に示します。
529
 
530
+ - **通信の抽象化:**
531
  - DjangoとAIアプリは、**HTTPとJSON**という標準化された技術でのみ通信します。
532
  - DjangoはAIアプリがGradioで実装されていることを知る必要はなく、逆もまた然りです。知っているのは「どのURLに、どんなJSONを送れば、どんなJSONが返ってくるか」というAPI契約だけです。
533
 
534
+ - **独立した実行とテスト:**
535
+ - **AIアプリケーション:**
536
  - `src/ai_api/` ディレクトリは、それ自体が完結したPythonプロジェクトです。
537
  - `python src/ai_api/main.py` を実行すれば、Djangoとは無関係に単体で起動できます。
538
  - 起動したAIアプリに対し、ブラウザでUIを操作したり、`curl`コマンドでAPIを直接叩いたりすることで、単体での動作テストが可能です。
539
+ - **Djangoアプリケーション:**
540
  - `SummarizeReviewsView` のテストを書く際、`unittest.mock.patch` を使って `_call_summary_api` メソッドをモック(偽のオブジェクトに差し替え)します。
541
  - これにより、AI APIへ実際にネットワーク通信を発生させることなく、「APIが成功を返した場合」「タイムアウトした場合」「エラーを返した場合」など、あらゆる状況を想定したビューのロジックを高速にテストできます。
542
 
 
556
 
557
  ##### 設定案 (`[tool.ruff]`セクション)
558
  - `line-length = 88`: 1行の最大長を`black`に合わせて88文字に設定。
559
+ - `select = ["E", "W", "F", "I", "B", "UP"]`:
560
  - `E`, `W`, `F`: `pycodestyle`と`Pyflakes`の基本的なエラー・警告(必須)。
561
  - `I`: `isort`互換のimport文の自動ソート(必須)。
562
  - `B`: `flake8-bugbear`の、バグの温床となりやすいコードパターンを検出するルール。
 
673
  - [x] **中タスク0.3: GitHubリポジトリの作成と連携**
674
  - **担当:** 人間
675
  - **内容:** GitHub上に新しいリポジトリを作成し、ローカルリポジトリと連携させます。
676
+ - **指示:**
677
  1. GitHubにログインし、新しいリポジトリを作成します(リポジトリ名は `kids-playground-ai-api` など)。READMEや.gitignore、ライセンスの追加はスキップしてください。
678
  2. 作成されたリポジトリページに表示される指示に従い、ローカルリポジトリをリモートにプッシュします。
679
  ```bash
 
685
  - [x] **中タスク0.4: Hugging Faceアカウントの登録とSpacesの準備**
686
  - **担当:** 人間
687
  - **内容:** Hugging Faceアカウントを登録し、AIモデルをデプロイするためのHugging Face Spaceを準備します。
688
+ - **指示:**
689
  1. Hugging Faceのウェブサイト (huggingface.co) にアクセスし、アカウントを登録します。
690
  2. ログイン後、「Spaces」セクションに移動し、「Create new Space」をクリックします。
691
  3. Space名、ライセンス、SDK(Gradioを選択)、公開/非公開設定などを適切に設定し、Spaceを作成します。
 
752
  - [x] **小タスク1.3.2: Docker開発環境の構築と起動**
753
  - **担当:** 人間
754
  - **内容:** Docker Desktopを利用して、プロジェクトのDocker開発環境を構築し、起動します。これにより、必要な依存関係がコンテナ内に自動的にインストールされます。
755
+ - **指示:**
756
  1. Docker Desktopがインストールされ、起動していることを確認してください。
757
  2. プロジェクトのルートディレクトリで、以下のコマンドを実行してDockerコンテナをビルドし、バックグラウンドで起動します。
758
  ```bash
 
907
  - **担当:** 人間
908
  - **内容:** DjangoサーバーとAIアプリ(Gradio)を両方ローカルで起動し、ブラウザから最初の要約ボタンをクリックして、全体の流れが正しく動作���るかを確認します。
909
 
910
+ ```
docs/environment_setup_article.md CHANGED
@@ -40,4 +40,3 @@ Gemini CLIは、単にコマンドを指示するだけでなく、エラーの
40
  ## 結論
41
 
42
  Gemini CLIとの共同作業により、AI API開発のための堅牢で再現性の高いDockerベースの開発環境を無事に構築することができました。この環境は、今後のAI APIの実装とテストを効率的に進めるための強固な基盤となります。
43
-
 
40
  ## 結論
41
 
42
  Gemini CLIとの共同作業により、AI API開発のための堅牢で再現性の高いDockerベースの開発環境を無事に構築することができました。この環境は、今後のAI APIの実装とテストを効率的に進めるための強固な基盤となります。
 
pyproject.toml CHANGED
@@ -20,4 +20,3 @@ explicit_package_bases = true
20
  testpaths = ["tests"]
21
  addopts = "-ra --strict-markers"
22
  minversion = "7.0"
23
-
 
20
  testpaths = ["tests"]
21
  addopts = "-ra --strict-markers"
22
  minversion = "7.0"
 
requirements.txt CHANGED
@@ -1,5 +1,5 @@
1
  # AI App
2
- gradio==4.37.2
3
  transformers==4.42.1
4
  torch==2.3.1
5
 
@@ -8,6 +8,9 @@ pytest==8.2.2
8
  requests==2.32.3
9
 
10
  # Linting & Formatting
11
- ruff==0.4.10
12
  mypy==1.10.0
13
  pre-commit==3.7.1
 
 
 
 
1
  # AI App
2
+ gradio>=4.44.1
3
  transformers==4.42.1
4
  torch==2.3.1
5
 
 
8
  requests==2.32.3
9
 
10
  # Linting & Formatting
11
+ ruff==0.5.5
12
  mypy==1.10.0
13
  pre-commit==3.7.1
14
+ httpx
15
+ sentencepiece
16
+ protobuf
src/ai_api/config.py CHANGED
@@ -5,5 +5,5 @@ from dataclasses import dataclass
5
  class ModelConfig:
6
  """使用するAIモデルに関する情報を一元管理します。"""
7
 
8
- NAME: str = "llm-jp/t5-small-japanese-finetuned-sum"
9
  REVISION: str = "main"
 
5
  class ModelConfig:
6
  """使用するAIモデルに関する情報を一元管理します。"""
7
 
8
+ NAME: str = "tsmatz/mt5_summarize_japanese"
9
  REVISION: str = "main"
src/ai_api/core/inference.py CHANGED
@@ -1,6 +1,6 @@
1
  from typing import cast
2
 
3
- from transformers import pipeline
4
 
5
  from ai_api.config import ModelConfig
6
 
@@ -12,17 +12,22 @@ class Summarizer:
12
 
13
  def __init__(self, config: ModelConfig) -> None:
14
  self.config = config
15
- # __init__で一度だけpipelineを初期化
16
- self.summarizer = pipeline(
17
- "summarization",
18
- model=self.config.NAME,
19
- revision=self.config.REVISION,
 
20
  )
21
 
22
  def summarize(self, text: str) -> str:
23
  """
24
  与えられたテキストを要約する。
25
  """
26
- # 保持しているsummarizerを使って要約
27
- result = self.summarizer(text)
28
- return cast(str, result[0]["summary_text"])
 
 
 
 
 
1
  from typing import cast
2
 
3
+ from transformers import T5ForConditionalGeneration, T5Tokenizer
4
 
5
  from ai_api.config import ModelConfig
6
 
 
12
 
13
  def __init__(self, config: ModelConfig) -> None:
14
  self.config = config
15
+ # __init__で一度だけtokenizerとmodelを初期化
16
+ self.tokenizer = T5Tokenizer.from_pretrained(
17
+ self.config.NAME, revision=self.config.REVISION
18
+ )
19
+ self.model = T5ForConditionalGeneration.from_pretrained(
20
+ self.config.NAME, revision=self.config.REVISION
21
  )
22
 
23
  def summarize(self, text: str) -> str:
24
  """
25
  与えられたテキストを要約する。
26
  """
27
+ # 保持しているtokenizerとmodelを使って要約
28
+ input_ids = self.tokenizer.encode(text, return_tensors="pt")
29
+ output_ids = self.model.generate(
30
+ input_ids, max_length=50, min_length=10, do_sample=False
31
+ )
32
+ summary = self.tokenizer.decode(output_ids[0], skip_special_tokens=True)
33
+ return cast(str, summary)
src/ai_api/main.py CHANGED
@@ -1,16 +1,39 @@
 
 
1
  import gradio as gr
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
- def greet(name: str) -> str:
5
- """
6
- 名前を受け取り、挨拶文を返す簡単な関数。
7
- """
8
- return "Hello, " + name + "!"
9
 
10
 
11
  # Gradioインターフェースの定義
12
- iface = gr.Interface(fn=greet, inputs="text", outputs="text")
 
 
 
 
 
 
 
13
 
14
- # スクリプトとして実行された場合にUIを起動
15
  if __name__ == "__main__":
16
- iface.launch()
 
1
+ from typing import cast
2
+
3
  import gradio as gr
4
 
5
+ from ai_api.config import ModelConfig
6
+ from ai_api.core.inference import Summarizer
7
+
8
+ # Summarizerのインスタンスをシングルトンとして管理
9
+ summarizer_instance: Summarizer | None = None
10
+
11
+
12
+ def get_summarizer() -> Summarizer:
13
+ """Summarizerのインスタンスを一度だけ生成して返します。"""
14
+ global summarizer_instance
15
+ if summarizer_instance is None:
16
+ config = ModelConfig()
17
+ summarizer_instance = Summarizer(config=config)
18
+ return summarizer_instance
19
+
20
 
21
+ def summarize_text(text: str) -> str:
22
+ """推論を実行するためのトップレベル関数。"""
23
+ summarizer = get_summarizer()
24
+ # castを使って、戻り値がstrであることをMypyに明示的に伝える
25
+ return cast(str, summarizer.summarize(text))
26
 
27
 
28
  # Gradioインターフェースの定義
29
+ iface = gr.Interface(
30
+ fn=summarize_text,
31
+ inputs=gr.Textbox(lines=10, placeholder="要約したいテキストを入力してください..."),
32
+ outputs="text",
33
+ title="口コミ要約AI",
34
+ description="入力されたテキスト(口コミ)をAIが分析し、要約を生成します。",
35
+ api_name="predict",
36
+ )
37
 
 
38
  if __name__ == "__main__":
39
+ iface.launch(server_name="0.0.0.0")
tests/core/test_inference.py CHANGED
@@ -7,7 +7,7 @@ def test_summarizer_initialization_with_test_model() -> None:
7
  テスト用の軽量モデルでSummarizerが初期化できることをテストする。
8
  """
9
  # Arrange: テスト専用の軽量モデルを指定
10
- config = ModelConfig(NAME="sshleifer/distilbart-cnn-6-6", REVISION="main")
11
 
12
  # Act: 実際にモデルをロードして初期化
13
  summarizer = Summarizer(config=config)
@@ -21,7 +21,7 @@ def test_summarize_with_test_model() -> None:
21
  テスト用の軽量モデルでsummarizeメソッドが動作することをテストする。
22
  """
23
  # Arrange: テスト専用の軽量モデルを指定
24
- config = ModelConfig(NAME="sshleifer/distilbart-cnn-6-6", REVISION="main")
25
  summarizer = Summarizer(config=config)
26
  text = "This is a test sentence. It is a very nice sentence to summarize."
27
 
 
7
  テスト用の軽量モデルでSummarizerが初期化できることをテストする。
8
  """
9
  # Arrange: テスト専用の軽量モデルを指定
10
+ config = ModelConfig(NAME="megagonlabs/t5-base-japanese-web", REVISION="main")
11
 
12
  # Act: 実際にモデルをロードして初期化
13
  summarizer = Summarizer(config=config)
 
21
  テスト用の軽量モデルでsummarizeメソッドが動作することをテストする。
22
  """
23
  # Arrange: テスト専用の軽量モデルを指定
24
+ config = ModelConfig(NAME="megagonlabs/t5-base-japanese-web", REVISION="main")
25
  summarizer = Summarizer(config=config)
26
  text = "This is a test sentence. It is a very nice sentence to summarize."
27
 
tests/test_main.py DELETED
@@ -1,8 +0,0 @@
1
- from ai_api.main import greet
2
-
3
-
4
- def test_greet() -> None:
5
- """
6
- greet関数が期待される挨拶文を返すことをテストする。
7
- """
8
- assert greet("World") == "Hello, World!"