FlutterからCloud Functions for Firebase(Python)経由でOpen AIのAPIを使ってみた

2024.05.24

こんにちは、ゲームソリューション部のsoraです。
今回は、FlutterからCloud Functions for Firebase(Python)経由でOpen AIのAPIを使ってみたことについて書いていきます。

実装した画面

見た目としては、リクエストメッセージを入力してOpenAIのAPIを実行して文章を生成するシンプルなアプリです。
内部的には、シークレットキーをバックエンド(Firebase)に持たせることで、Flutter側で管理せずに実行する作りにします。

環境準備

Firebase CLIのインストールなどの必要な環境準備していきます。
npmはインストールされている前提とします。

# firebase cliのインストール
npm install -g firebase-tools

# Firebaseのログイン
firebase login
# ⇒GUIで認証する

# flutterfire_cliのインストール
dart pub global activate flutterfire_cli

コードの解説

Cloud Functions for Firebase(Python)

コードは以下です。
解説はコード内コメントに記載しています。
基本的にはHTTP経由で実行する場合のコードと同様ですが、SDKを使用してのアクセスになるため、少し記述が変わる部分があります。
リージョンはデフォルトでus-central1になっているため、asia-northeast1(東京)にしています。
openaiのライブラリが必要なため、requirements.txtに記述して追加しておきます。

main.py

from firebase_functions import https_fn, options
from firebase_functions.params import SecretParam
from firebase_admin import initialize_app
from openai import OpenAI
import os

# 新しいAppインスタンスを初期化して返却する
initialize_app()

# リージョンの指定
options.set_global_options(region=options.SupportedRegion.ASIA_NORTHEAST1)

# SDKを使用してのアクセスのため、on_requestではなくon_call
@https_fn.on_call(secrets=["OPENAI_KEY"])
def test_api(req: https_fn.CallableRequest) -> https_fn.Response:
    client = OpenAI(
        # secretsの値を取得
        api_key = os.environ.get('OPENAI_KEY'),
    )
    # on_requestの場合はreq.args、on_callの場合はreq.data
    req_message = req.data["message"]
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "あなたはAIではありません。人間として自然な会話をしてください。"},
            {"role": "user", "content": req_message}],
    )
    res_text = response.choices[0].message.content
    return {"message": res_text}

コードが完成したためデプロイします。

# 仮想環境上でライブラリのインストール
/home/{user}/test/functions/venv/bin/activate" && python3.12 -m pip install -r requirements.txt

# 仮想環境の終了(実行しなくても問題はない)
(venv):~$ deactivate

# デプロイ
firebase deploy --only functions:test_api

ブログ冒頭でも記載しましたが、シークレットキーは以前登録したものを使用するため、登録については今回は割愛します。
シークレットキーの登録については、以下をご参考ください。

Flutter

コードは以下です。
解説はコード内コメントに記載しています。

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// Firebaseを使用するために必要なパッケージ
import 'package:cloud_functions/cloud_functions.dart';
import 'package:firebase_core/firebase_core.dart';
// 接続先の情報を記述したファイルのインポート
import 'firebase_options.dart';


// 表示する文字列を管理するProvider
class Response extends Notifier<String> {
  @override
  String build(){
    return '';
  }
  void clear() {
    state = '';
  }
  void modify(String message) {
    state = message;
  }
}
final responseProvider = NotifierProvider<Response, String>(() {
  return Response();
});


// OpenAI API実行
Future<void> apiRequest(String message, WidgetRef ref) async {
  // Cloud Functions for Firebaseのリージョンがデフォルトでない場所にデプロイした場合は、明示的に指定する必要がある
  final functions = FirebaseFunctions.instanceFor(
    region: 'asia-northeast1'
  );

  final providerNotifier = ref.watch(responseProvider.notifier);

  // Cloud Functions for Firebaseの関数名を指定
  final HttpsCallable callable = functions.httpsCallable("test_api");

  // 関数を呼び出し、引数を渡す
  final HttpsCallableResult result = await callable.call({
    'message': message
  });
  final resMessage = result.data['message'];

  providerNotifier.modify(resMessage);
}


void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // optionsで指定しているのはflutterfire configure(詳細は後述)にて生成されたfirebase_options.dartの設定値を使用
  await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform
  );
  runApp(
    const ProviderScope(
      child: MyApp(),
    ),
  );
}
class MyApp extends StatelessWidget   {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends ConsumerWidget {
  MyHomePage({super.key});
  final _messageController = TextEditingController();

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final providerValue = ref.watch(responseProvider);
    final providerNotifier = ref.watch(responseProvider.notifier);

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('OpenAI API Test'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Container(
              padding: const EdgeInsets.only(
                left: 25,
                right: 25,
              ),
              child: TextField(
                controller: _messageController,
                maxLines: 1,
                decoration: const InputDecoration(
                  hintText: 'メッセージを入力',
                  hintStyle: TextStyle(color: Colors.black54),
                ),
              ),
            ),
            Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  ElevatedButton(
                    child: const Text('AIチャット実行'),
                    onPressed: (){
                      var msg = _messageController.text.trim();
                      if(msg.isEmpty){
                        _messageController.clear();
                        return;
                      }
                      providerNotifier.clear();
                      apiRequest(msg, ref);
                    },
                  ),
                ]
            ),
            const SizedBox(height: 30),
            Container(
              padding: const EdgeInsets.only(
                left: 25,
                right: 25,
              ),
              child: Text(
                '$providerValue',
                style: const TextStyle(
                  fontSize: 18,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

コード内に記述しましたが、Firebase上のプロジェクトへの接続情報が記述されたファイルを生成するために、以下コマンドを実行します。
実行することでfirebase_options.dartが自動生成されます。

# firebaseの自身のプロジェクトとの連携のための設定ファイルの自動生成
# ⇒プロジェクトのルートディレクトリで実行すること
flutterfire configure
# もしくは以下でも可能
flutterfire configure --project={FirebaseのProjectID}

最後に

今回は、FlutterからCloud Functions for Firebase(Python)経由でOpen AIのAPIを使ってみたことを記事にしました。
どなたかの参考になると幸いです。