Spaces:
Sleeping
Sleeping
Commit
·
f079f59
1
Parent(s):
60b8094
口コミ要約機能の実装
Browse files- .github/workflows/ci.yml +1 -1
- .pre-commit-config.yaml +1 -1
- Dockerfile +5 -1
- GEMINI.md +3 -3
- docs/ai_api_development_guide.md +24 -24
- docs/environment_setup_article.md +0 -1
- pyproject.toml +0 -1
- requirements.txt +5 -2
- src/ai_api/config.py +1 -1
- src/ai_api/core/inference.py +14 -9
- src/ai_api/main.py +31 -8
- tests/core/test_inference.py +2 -2
- tests/test_main.py +0 -8
.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 |
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 |
-
|
|
|
|
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
|
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.
|
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 = "
|
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
|
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__で一度だけ
|
16 |
-
self.
|
17 |
-
|
18 |
-
|
19 |
-
|
|
|
20 |
)
|
21 |
|
22 |
def summarize(self, text: str) -> str:
|
23 |
"""
|
24 |
与えられたテキストを要約する。
|
25 |
"""
|
26 |
-
# 保持している
|
27 |
-
|
28 |
-
|
|
|
|
|
|
|
|
|
|
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
|
5 |
-
"""
|
6 |
-
|
7 |
-
|
8 |
-
return
|
9 |
|
10 |
|
11 |
# Gradioインターフェースの定義
|
12 |
-
iface = gr.Interface(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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="
|
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="
|
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!"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|