この記事は何?
サーバーレスコンピューティング で実現する 生成AIアプリケーション の作り方を紹介します。
このブログの概要を80秒でご紹介する動画です (画像をクリックしてください) |
大規模言語モデル(LLM)のAPIサービス、FaaS(Function-as-a-Service)、APIゲートウェイサービスとサーバーレスなUIフレームワークの Gradio Lite を組み合わせて end-to-end でサーバーレスコンピューティングを活用したアプリケーションをステップバイステップで育てていきます
この記事では、生成AIアプリケーション自体は、入力された文章をLLMを使って日英、もしくは、日本語以外から日本語へ翻訳するシンプルなアプリケーションを例にしています
この記事で使用しているコードはすべて記事内に記載していますが GitHub でも公開していますのでご活用ください
https://github.com/kutsushitaneko/serverless_generative_ai_gradio_lite
git clone https://github.com/kutsushitaneko/serverless_generative_ai_gradio_lite
cd serverless_generative_ai_gradio_lite
アーキテクチャ
この記事で作成するサーバーレス生成AIアプリのアーキテクチャは、下図のとおりです
活用するサービス
このアーキテクチャを実装するために以下のようなサービスやフレームワークを活用します
大規模言語モデル(LLM) の API サービス
LLM の推論機能を クラウドベースの API で利用できるサービスとして、OCI Generative AI サービスを利用します。OCI Generative AI サービスは、Cohere 社や Meta 社の LLM や 埋め込みモデル(Emedding Model)を API で利用できるサービスです。課金体系は、コンサンプションベースの課金で利用できるオンデマンドに加えて、専用クラスタ構成でのサービスもありテナント(アカウント)に閉じたセキュアで安定した性能を確保できる環境を利用することもできます。今回は、LLM として、Cohere の Command R+ をオンデマンドで使用します。オンデマンドサービスは、LLMをホストする仮想マシンやクラスタをプロビジョニングする必要なく、APIを呼び出すことですぐに利用することができます。
OCI Generative AI(生成AI)の 公式ドキュメント
FaaS(Function-as-a-Service)
ビジネスロジックを実行するサーバーレスコンピューティングプラットフォーム(Function-as-a-Service:FaaS)には、OCI Functions を利用します。OCI Functions は、オープンソースのサーバーレスコンピューティングフレームワークの Fn Project をOCI クラウドのマネージドサービスで利用できるものです。コンテナが稼働する仮想マシンなどのサーバー環境をあらかじめ準備する必要がなく、Java や Python などのプログラムを実行することができます。また、スケールアウトもサービス側で自動的に行われるためスケーラブルなアプリケーションを構築することができます。なお、OCIのドキュメントの中では、"Functions"、"ファンクション"、"関数"などと表現されています。この記事では原則的にサービス名は "Functions"、実行される個々のプログラムを"ファンクション"と表現しています。
OCI Functions(ファンクション:関数)の 公式ドキュメント
APIゲートウェイサービス
バックエンドのアプリケーションを REST API として公開し、ロードバランシングなどの機能を提供するAPIゲートウェイサービスとしては、OCI API Gateway を利用します。ロードバランサや API サーバーを自分で構築する必要なく OCI クラウドのマネージドサービスで APIゲートウェイ機能を利用できるサービスです。OCI API Gatewayを活用することでバックエンドの機能を直接インターネットにさらす必要がなくなる上、APIゲートウェイ自体のスケールアウトもサービス側で自動的に実行されるためセキュアでスケーラブルな API サービスを開発することができます。
OCI API Gateway(API ゲートウェイ)の 公式ドキュメント
サーバーレスなUIフレームワーク
機械学習/生成AI分野のデモアプリケーションや PoC のプロトタイプ開発において、Python ベースの UI 開発ツールとして Gradio が人気です。この Gradio のサーバーレス版である Gradio Lite を使用します。Python コードを HTML ファイルに埋め込むことで、コードをブラウザ内で実行することができフロントエンドを稼働させるサーバーが不要です。UIを手軽に構築できるフレームワークとしてとても便利なものです。
オブジェクトストレージ
Gradio Lite の HTML ファイルを公開するための Webサーバーとして、 OCI Object Storage サービスを利用します。バケットに HTML ファイルをアップロードして公開することで Webサーバーとして機能させることができます。
OCI Object Storage(オブジェクト・ストレージ) の公式ドキュメント
事前準備:ユーザーに対する IAM のポリシー設定
ポリシーとは?
OCIでは、ユーザーがサービスへアクセスできるかどうか、サービスが他のサービスへアクセスできるかどうかを「ポリシー」を利用して制御します。ポリシーは、各リソースに誰(もしくはどのリソース)がアクセスできるかを指定することができます。
使用するユーザーがOCIの各サービスへのアクセス権を持っていない場合には、ポリシーを設定してアクセス権を付与する必要があります。
サービスへのアクセス権がない場合には、コンソールにアクセスした際、もしくは、何か操作した際に下記の2つの画像のような表示が出ます。
テナンシ管理者へ連絡して必要なポリシーを設定してもらいましょう。
OCI Functions(ファンクション、関数) へのアクセス権の設定
OCI公式チュートリアル【Oracle Functions ハンズオン】の事前準備 を参考にポリシーを設定します
OCI公式ドキュメントは ネットワークおよびファンクション関連リソースへのアクセスを制御するポリシーの作成 です
OCI Generative AI(生成AI) へのアクセス権の設定
OCI公式ドキュメントの生成AIへのアクセス を参考にポリシーを設定します
OCI API Gateway へのアクセス権の設定
OCI公式ドキュメントのネットワークおよびAPIゲートウェイ関連リソースへのアクセスを制御するポリシーの作成 を参考にポリシーを設定します
OCI Object Storage へのアクセス権の設定
OCI公式ドキュメントのオブジェクト・ストレージ、アーカイブ・ストレージおよびデータ転送の詳細 を参考にポリシーを設定します
OCI Functions の開発環境のセットアップ
OCI Functions の開発環境のセットアップ手順は、OCI公式チュートリアル【Oracle Functions ハンズオン】の1.Cloud Shellのセットアップを参考にしています
必要な情報の収集
認証トークンの作成
Oracle Cloud Infrastructure Registry(OCIR)にログインするための認証トークンを作成します。OCIRは、OCI 提供のプライベートDockerイメージレジストリで、今回は、Oracle Functionsのファンクションのイメージを保存するために使用します
手順
OCIダッシュボードの画面右上の人型のマークをクリックします。テナンシの認証管理システムにより下図のどちらかの形式でプロファイルメニューが表示されます。「アイデンティティ・ドメイン:XXXX」という項目の有無で見分けます
アイデンティティ・ドメイン | IDCS/フェデレーション |
アイデンティティ・ドメインの場合の手順
- "アイデンティティ・ドメイン:xxxx"の下の "自分のプロファイル" をクリックします
- "自分のプロファイル" 画面の左下のメニューから "認証トークン" をクリックします
- ”トークンの生成” をクリックします
IDCS/フェデレーションの場合の手順
- "プロファイル"の"直下の ユーザー名 をクリックします
- "ユーザーの詳細"画面の左側のメニューで、 ”認証トークン” をクリックします
- ”トークンの生成” をクリックします
共通の手順
次のような画面が表示されているはずです
“説明”に"GenAI Translate ファンクション用"等の説明文を入力し、”トークンの生成”をクリックします
"コピー"をクリックしてトークンをクリップボード経由でメモ帳などに転記します
コンパートメントOCIDの確認
- OCIダッシュボードの画面左上のナビゲーション・メニュー(ハンバーガー・メニュー)から"アイデンティティとセキュリティ"⇒"コンパートメント"の順に選択します
- 使用するコンパート名をクリックします
- "コピー" をクリックしてOCIDをクリップボード経由でメモ帳などに転記します
オブジェクト・ストレージ・ネームスペースの確認
- OCIダッシュボードの画面右上の人型のマークをクリックし、テナンシをクリックして、"テナンシ詳細"を表示します
- 右側の "オブジェクト・ストレージ設定"のオブジェクト・ストレージ・ネームスペース: に表示されている文字列をメモ帳などに転記します
Cloud Shell の起動
ここでは、Cloud Shell を開発環境しとして使用します
OCI Functions CLI context の設定
OCI Functions CLI contextは、OCI Functionsを使用するための設定情報をまとめたものです。以下のような情報を設定します
- 使用するOCIリージョン
- ファンクションをデプロイするコンパートメントID
- OCIRのアドレス
- 認証情報
Cloud Shell には事前に CLI context が設定されています
現在の CLI context を確認
Cloud Shell で以下のコマンドを実行します
fn list context
$ fn list context
CURRENT NAME PROVIDER API URL REGISTRY
* ap-tokyo-1 oracle-cs https://functions.ap-tokyo-1.oci.oraclecloud.com
default oracle-cs
us-chicago-1 oracle-cs https://functions.us-chicago-1.oci.oraclecloud.com
"*" が付いている context が現在の CLI context です。これからご利用されるリージョンと異なる context に "*" が付いている場合は、次のコマンドで適切な context を選択します
fn use context <region-context>
$ fn use context us-chicago-1
Now using context: us-chicago-1
$ fn list context
CURRENT NAME PROVIDER API URL REGISTRY
ap-tokyo-1 oracle-cs https://functions.ap-tokyo-1.oci.oraclecloud.com
default oracle-cs
* us-chicago-1 oracle-cs https://functions.us-chicago-1.oci.oraclecloud.com
CLI context にコンパートメントIDを設定
fn update context oracle.compartment-id [compartment-ocid]
[compartment-ocid]は、先程確認してメモしたコンパートメントIDです。[]は不要です
$ fn update context oracle.compartment-id ocid1.compartment.oc1..xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Current context updated oracle.compartment-id with ocid1.compartment.oc1..xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
CLI context に OCIR を設定
fn update context registry [region-key].ocir.io/[tenancy-namespace]/[repo-name-prefix]
- [region-key] : 利用するリージョンのコード。[]は不要です
region-keyは、こちら で確認できます
region-key は英小文字で入力します。大文字で入力するとファンクションの OCIRへのデプロイに失敗します
- [tenancy-namespace] : 先程確認してメモしたオブジェクト・ストレージ・ネームスペース。テナンシの名前ではありません
- [repo-name-prefix] : (オプション)ファンクションのイメージを格納するOCIR(Oracle Cloud Infrastructure Registry)リポジトリの任意のプレフィックス。リポジトリ名とプレフィックス(接頭辞)についてはドキュメントのリポジトリ名およびリポジトリ名の接頭辞に関するノートを参照
$ fn update context registry ord.ocir.io/xxxxxxxxxx/ya_genai
Current context updated registry with ord.ocir.io/xxxxxxxxxx/ya_genai
fn list context
$ fn list context
CURRENT NAME PROVIDER API URL REGISTRY
ap-tokyo-1 oracle-cs https://functions.ap-tokyo-1.oci.oraclecloud.com
default oracle-cs
* us-chicago-1 oracle-cs https://functions.us-chicago-1.oci.oraclecloud.com ord.ocir.io/orasejapan/ya_genai
CLI context に OCI CLI プロファイルを設定
このCLI context で使用する OCI CLI プロファイルを設定します
fn update context oracle.profile "プロファイル名"
Cloud Shell が自動的にセットアップした OCI CLI Profile は、/etc/oci/config に定義されています
以下は、DEFAULTプロファイルを使用する例です
fn update context oracle.profile "DEFAULT"
CLI context 設定内容の確認
more ~/.fn/contexts/[CLI context].yaml
[CLI context] : fn list context
で確認した現在(CURRENT)の CLI context の名前(NAME)
$ more ~/.fn/contexts/us-chicago-1.yaml
api-url: https://functions.us-chicago-1.oci.oraclecloud.com
oracle.compartment-id: ocid1.compartment.oc1..aaaaaaaaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
provider: oracle-cs
registry: ord.ocir.io/xxxxxxxxxx/ya_genai
OCIR へのログインの確認
docker login [region-key].ocir.io
このコマンドを実行すると Username: と Password: を聞かれますので以下のように応答します。
-
Username: は、OCIのIdentity and Access Management(IAM) サービスで直接作成および管理される IAM ユーザーの場合とOracle Identity Cloud Service (IDCS)と統合されたフェデレーテッド・ユーザーの場合で形式が異なります。IAM ユーザーの場合は、[tenancy-namespace]/[username] の形式でユーザー名を応答します。フェデレーテッド・ユーザーの場合は、oracleidentitycloudservice/[tenancy-namespace]/[username] の形式でユーザー名を応答します。
- [tenancy-namespace] : オブジェクト・ストレージ・ネームスペース[tenancy-namespace]/
- [username] : OCIコンソール画面右上の人型のアイコンをクリックし、展開されたメニューにある”プロファイル”直下に表示される文字列
- Password: には上で作成してメモした認証トークンを応答します
$ docker login ord.ocir.io
Username: xxxxxxxxxx/oracleidentitycloudservice/XXXXXXXXXXXXXXXXXXXXXXXX
Password:
WARNING! Your password will be stored unencrypted in /home/xxxxxxxx/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
"Login Succeeded" と表示されればログイン成功です
VCN(仮想クラウドネットワーク) の作成
OCI Functionsのアプリケーションを動作させる仮想クラウドネットワークを作成します
- OCIのダッシュボードの画面左上のナビゲーション・メニュ(ハンバーガー・メニュー)から"ネットワーキング"⇒"仮想クラウド・ネットワーク"の順に選択します
- 画面左側のメニューの下の方にあるプルダウンでコンパートメントを選択します
- "VCNウィザードの起動"をクリックします
- “インターネット接続性を持つVCN”を選択し、”ワークフローの起動”をクリックします
- 以下の情報を入力します(CIDRなどは適宜変更してください)
- VCN名:任意の名前(例:”OCI Generative AI Hands-on”)
- コンパートメント:上で選択したコンパートメント名が表示されています
- VCN CIDRブロック:10.0.0.0/16(デフォルト)
- パブリック・サブネットCIDRブロック:10.0.0.0/24(デフォルト)
- プライベート・サブネットCIDRブロック:10.0.1.0/24(デフォルト)
-
"次"をクリックします
-
"確認および作成"画面で設定内容を確認したら“作成”をクリックします
- "VCNが作成されました"と表示されたら画面左下の"VCNの表示"をクリックします
- VCNが"使用可能"となっていること、プライベートとパブリックの2つのサブネットがコンパートメント内に作成されていることを確認します
以上で、VCN(仮想クラウドネットワーク)が作成されました
OCI Functions ファンクションの作成
アプリケーションの作成
アプリケーションとは、複数のファンクションの論理グループで VCN や サブネットといった属性を共有するファンクションをまとめて管理するものです。ファンクションを作成する前に必ずアプリケーションを作成する必要があります
- OCIのダッシュボードの画面左上のナビゲーション・メニュ(ハンバーガー・メニュー)から”開発者サービス”⇒”ファンクション”の順に選択します
- ファンクション画面が表示されます
- ”アプリケーションの作成”をクリックします
- 以下の情報を入力、選択します
- 名前:OCI Functions アプリケーションの任意の名前を入力します(例:"fn_genai_translate_app")
- XXXXXXXのVCN:(XXXXXXX)にはコンパートメント名がセットされています。プルダウンで先程作成した VCN を選択します
- XXXXXXXのサブネット:(XXXXXXX)にはコンパートメント名がセットされています。プルダウンで先程作成した VCN のパブリックサブネットを選択します
- "作成"をクリックします
以上で、OCI Functionsアプリケーションの作成は完了です。アプリケーションの中身は空です。次にこのアプリケーションに入れるファンクションを作成します
ファンクションの作成
ファンクションとは?
Fn Project や OCI Functions におけるファンクションとは、Dockerイメージとしてレジストリに格納されて、HTTPリクエストやCLIによって呼び出されて実行されるコード・ブロックです
このブログでは、Python でファンクションを開発します
- ファンクションの初期化
fn init --runtime [programming language runtime] [function-subdirectory]
- fn init のパラメータ
- [programming language runtime] : ファンクションのランタイム
- [function-subdirectory] : サンプルコードや設定ファイル等を配置するディレクトリ
$ fn init --runtime python genai_translate_func
Creating function at: ./genai_translate_func
Function boilerplate generated.
func.yaml created.
$ ls genai_translate_func
func.py func.yaml requirements.txt
fn init
のリファレンスはこちら
Python のファンクションを初期化するとサンプル Python スクリプト(func.py)、設定ファイル(func.yaml)、requirements.txt が生成されます
$ cat genai_translate_func/func.py
import io
import json
import logging
from fdk import response
def handler(ctx, data: io.BytesIO = None):
name = "World"
try:
body = json.loads(data.getvalue())
name = body.get("name")
except (Exception, ValueError) as ex:
logging.getLogger().info('error parsing json payload: ' + str(ex))
logging.getLogger().info("Inside Python Hello World function")
return response.Response(
ctx, response_data=json.dumps(
{"message": "Hello {0}".format(name)}),
headers={"Content-Type": "application/json"}
)
ファンクションのデプロイ
ここでは一旦サンプル Python スクリプトのファンクションをデプロイして、OCIRへプッシュされることを確認します
fn deploy --app [app-name] [function-subdirectory]
- [app-name] : 先程作成したアプリケーション名
- [function-subdirectory] : デプロイで指定したプロジェクトの サンプルコードやyaml ファイル等を配置するディレクトリ
$ fn deploy --app fn_genai_translate_app genai_translate_func
Deploying function at: ./genai_translate_func
Deploying genai_translate_func to app: fn_genai_translate_app
Bumped to version 0.0.4
Using Container engine docker
Building image ord.ocir.io/xxxxxxxxxx/ya_genai/genai_translate_func:0.0.4 TargetedPlatform: amd64HostPlatform: arm
........................................................................
Updating function genai_translate_func using image ord.ocir.io/xxxxxxxxxx/ya_genai/genai_translate_func:0.0.4...
Successfully created function: genai_translate_func with ord.ocir.io/xxxxxxxxxx/ya_genai/genai_translate_func:0.0.4
fn deploy
のリファレンスはこちら
デプロイの確認
OCIR にファンクションがアップロードされていることを確認
- OCIダッシュボードの左上ナビゲーションメニューから”開発者サービス”⇒”コンテナ・レジストリ”を選択します
- OCIRのレジストリに先ほどデプロイしたファンクションがアップロードされていることを確認します
- 左側の"Compartment" でルート・コンパートメントが選ばれていることを確認します。"リポジトリおよびイメージ"にリポジトリが表示されない場合は、"リポジトリおよびイメージ"直下のプルダウン(白い部分)をクリックします
OCI Functions アプリケーションにファンクションがデプロイされていることを確認
- OCIダッシュボードの左上ナビゲーションメニューから”開発者サービス”⇒”ファンクション”を選択します
- 左側の"コンパートメント"で アプリケーションを作成したコンパートメント を選択します
- 右側のアプリケーションの一覧エリアに作成したアプリケーション名(例では fn_genai_translate_app)が表示されているはずです。このアプリケーション名をクリックします
画面下部の"ファンクション"の下に先程作成したファンクション(例では、genai_translate_func)が表示されていればデプロイに成功しています
ファンクションの実行
以下のコマンドを実行し、ファンクションが正常に実行されることを確認します
fn invoke [app-name] [function-name]
-[app-name] : アプリケーション名
-[function-name] : ファンクション名
fn invoke
のリファレンスはこちら
$ fn invoke fn_genai_translate_app genai_translate_func
{"message": "Hello World"}
$ echo -n '{"name": "Tokyo"}' | fn invoke fn_genai_translate_app genai_translate_func
{"message": "Hello Tokyo"}
ファンクションの修正
サンプルの func.py をOCI Generative AI サービスの Cohere Command-R+ を呼び出すように修正して再度デプロイします
import io
import json
import logging
import oci.auth.signers
import oci.generative_ai_inference
import os
from fdk import response
model_id = "cohere.command-r-plus"
try:
endpoint = os.getenv("OCI_GENAI_ENDPOINT")
compartment_id = os.getenv("COMPARTMENT_OCID")
if not endpoint:
raise ValueError("ERROR: Missing configuration key OCI_GENAI_ENDPOINT")
if not compartment_id:
raise ValueError("ERROR: Missing configuration key COMPARTMENT_OCID")
signer = oci.auth.signers.get_resource_principals_signer()
generative_ai_inference_client = oci.generative_ai_inference.GenerativeAiInferenceClient(config={}, service_endpoint=endpoint, signer=signer,retry_strategy=oci.retry.NoneRetryStrategy(), timeout=(10,240))
except Exception as e:
logging.getLogger().error(e)
raise
def inference(message):
chat_request = oci.generative_ai_inference.models.CohereChatRequest()
chat_request.message = f'''
##あなたは翻訳の専門家です。与えられた原文が日本語かどうかを判断して以下の指示のとおりに翻訳することが仕事です。
##以下の文章は翻訳対象の原文です。
##原文:{message}
##指示:以下のSTEPに従って原文を翻訳してください。
###STEP-1:原文が主に日本語であるかどうかを判断します。
###STEP-2:原文が主に日本語の場合は英語に翻訳します。
###STEP-3:原文が主に日本語以外の場合は日本語に翻訳します。
###STEP-4:以下の出力フォーマットに従って出力します。途中のSTEPの結果は出力しません。
##出力フォーマット:{{"input": "原文","output": "翻訳文"}}
##以下は原文とそれに対するあなたの出力の例です。
###Example-1:原文:こんにちは!
出力:{{"input": "こんにちは!","output": "Hello!"}}
###Example-2:原文:Good morning.
出力:{{"input": "Good morning.","output": "おはようございます。"}}
'''
chat_request.max_tokens = 2000
chat_request.is_stream = False
chat_request.temperature = 0.0
chat_request.top_p = 0.7
chat_request.top_k = 0 # Only support topK within [0, 500]
chat_request.frequency_penalty = 1.0
chat_request.is_echo = False
chat_detail = oci.generative_ai_inference.models.ChatDetails()
chat_detail.serving_mode = oci.generative_ai_inference.models.OnDemandServingMode(model_id=model_id)
chat_detail.compartment_id = compartment_id
chat_detail.chat_request = chat_request
try:
chat_response = generative_ai_inference_client.chat(chat_detail)
return chat_response.data.chat_response.text
except Exception as e:
logging.getLogger().error(e)
raise
def handler(ctx, data: io.BytesIO = None):
message = "こんにちは!"
try:
body = json.loads(data.getvalue())
message = body["message"]
except (Exception, ValueError) as ex:
logging.getLogger().info('error parsing json payload: ' + str(ex))
logging.getLogger().info("Inside Python Hello World function")
inference_response = inference(message)
return response.Response(
ctx, response_data=json.dumps(
{"message": "{0}".format(inference_response)},
ensure_ascii=False
),
headers={"Content-Type": "application/json; charset=utf-8"}
)
fdk>=0.1.75
oci
OCI Generative AI サービスを使った Python アプリケーションの書き方については以下の記事を参考にしてみてください
- この新しい func.py と requirements.txt をローカルのファイルの保存します
- 保存した func.py と requirements.txt を Cloud Shell にアップロードします。Cloud Shell の右上の歯車マークから"アップロード"を選択します
- ファイルは、ホームディレクトリはアップロードされます
- mv コマンドでプロジェクトディレクトリ(例では、genai_translate_func)へ移動します
mv func.py genai_translate_func/
mv requirements.txt genai_translate_func/
- 修正したファンクションをデプロイします
$ fn deploy --app fn_genai_translate_app genai_translate_func
Deploying function at: ./genai_translate_func
Deploying genai_translate_func to app: fn_genai_translate_app
Bumped to version 0.0.7
Using Container engine docker
Building image ord.ocir.io/xxxxxxxxxx/ya_genai/genai_translate_func:0.0.7 TargetedPlatform: amd64HostPlatform: arm
...........................................................................................................................................................................................................................................................................................................................................
Updating function genai_translate_func using image ord.ocir.io/xxxxxxxxxx/ya_genai/genai_translate_func:0.0.7...
環境変数をアプリケーションの構成に設定
修正した func.py は、2つの環境変数 OCI_GENAI_ENDPOINT と COMPARTMENT_OCID を参照しています。ファンクションに環境変数を渡すためにアプリケーションの構成にキーと値と設定します
- 左上のナビゲーションメニューから"開発者サービス"⇒"アプリケーション"を選択します
- "アプリケーションの作成"の下の一覧エリアに表示されているアプリケーションの名前(例では、fn_genai_translate_app)をクリックします
- 画面左側の"リソース"の下の"構成"をクリックします
- キーと値の組み合わせに以下の2つを定義します
- OCI_GENAI_ENDPOINT : https://inference.generativeai.us-chicago-1.oci.oraclecloud.com
- COMPARTMENT_OCID : コンパートメントのOCID
リソース・プリンシパルによる認証・認可設定
今回作成している生成AIアプリは、OCI Functios サービスのファンクションから OCI Generative AI サービスの大規模言語モデル(Cohere Command-R+)を呼び出して翻訳を行います。そのため、作成したファンクションに対して、OCI Generative AI サービスの推論機能にアクセスする権限を与える必要があります。
OCI では、ユーザーに対する認証認可に加えてファンクションのようなサービスのリソースに対して認証認可を行うためのリソース・プリンシパルという機能があります。APIキーをプログラムコードや Docker イメージに埋め込む必要がなくセキュアにサービスを利用できます
リソース・プリンシパルを使うためには、動的グループと、動的グループに権限を付与するIAMポリシーを作成する必要があります。OCI Functions におけるリソース・プリンシパルについては、OCIドキュメントのファンクションの実行による他のOracle Cloud Infrastructureリソースへのアクセス に詳しい説明があります
ファンクション用動的グループの作成
動的グループとは指定したルールに合致するリソースのグループです。動的とあるようにグループ作成後に作られたリソースであってもルールに合致すれば動的グループの一員となります。この動的グループに対して権限を与えるポリシーを付与することで柔軟かつ確実な権限管理を実現できます
アイデンティティ・ドメインの場合の手順
- OCIダッシュボード左上のナビゲーションメニューから"アイデンティティとセキュリティ"⇒"ドメイン"⇒ドメイン名("Defaultドメイン"等)⇒"動的グループ"を選択します
- "動的グループの作成"をクリックします
IDCS/フェデレーションの場合の手順
- OCIダッシュボード左上のナビゲーションメニューから"アイデンティティとセキュリティ"⇒"動的グループ"を選択します
- "動的グループの作成"をクリックします
共通の手順
- 任意の名前を入力します(例:Functions-dg)
- 任意の説明を入力します(例:ファンクション動的グループ)
- 以下の一致ルールを入力します(この一致ルールは、リソースのタイプが OCI Functions(
resource.type='fnfunc'
) のファンクションで、リソースのコンパートメントが指定したコンパートメントIDを持つものをグループ化しています。ocid1.compartment.oc1..aaaaaaaa...
の部分はファンクションを作成した際のコンパートメントのIDを指定します)
All {resource.type='fnfunc', resource.compartment.id = 'コンパートメントID'}
- "作成"をクリックします
ファンクション用 IAM ポリシーの作成
ポリシーを作成して上で作成した動的グループ(例では、Functions-dg)に OCI Generative AI の推論エンドポイントを呼び出す権限を付与します
- OCIダッシュボード左上のナビゲーションメニューから"アイデンティティとセキュリティ"⇒"ポリシー"を選択します
- ポリシーの作成をクリックします
- 任意の名前を入力します(例:genai4functions-policy)
- 任意の説明を入力します(例:ファンクションに OCI GenAI の使用権限を与えるポリシー)
- ポリシー・ビルダーの右横の"手動エディタの表示"のスライダーを右にスライドして手動エディタを表示します
- 以下のポリシーを入力します
- 動的グループ名は、上で作成したファンクション用動的グループの名前を指定します(例では、Functions-dg)
- コンパートメント名は、ファンクションを作成したコンパートメントの名前を指定します
allow dynamic-group 動的グループ名 to use generative-ai-family in compartment コンパートメント名
- "作成"をクリックします
Generative AI 対応版ファンクションをテスト
動的グループの作成とポリシーの作成が完了すると先程デプロイしたファンクションから OCI Generative AI サービスを呼び出すことができるようになっています
ただし、お使いのリージョンがホーム・リージョンでない場合には、IAM の情報が伝播するまでに数分から15分程度かかります。下記のテストを実施して "Error invoking function. status: 502 message: function failed" となる場合にはしばらく待ってから再実行してみてください。15分以上待っても変わらない場合は、動的グループとポリシーの設定に間違いがあると考えられますので見直してみてください
$ echo -n '{"message": "こんばんは!"}' | fn invoke fn_genai_translate_app genai_translate_func
{"message": "こんばんは!今日はどんな一日でしたか?何かお手伝いできることはありますか?"}
OCI API Gateway の設定
OCI API Gateway を使ってファンクションを RESTful API として公開します。
公式ドキュメントはこちら↓です。
ネットワーク・セキュリティ・グループの設定
API Gateway 自体の設定の前にインターネットから API Gateway にアクセスできるようにネットワーク・セキュリティ・グループを設定します
- 左上のナビゲーションメニューから "ネットワーキング"⇒"仮想クラウド・ネットワーク"と選択します
- 左側のメニューの Compartment が正しく選択されていることを確認します
- "VCNの作成"の下に表示されている本アプリケーション用に作成した仮想クラウド・ネットワークの名前(例では、OCI Generative AI Hands-on)をクリックします
- 左側のメニューで"ネットワーク・セキュリティ・グループ"をクリックします
- "ネットワーク・セキュリティ・グループの作成"をクリックします
- 任意の名前を設定します(例:OCI Generative AI Hands-on Security Group)
- コンパートメントは、コンパートメントOCIDの確認を で確認したコンパートメントを選択します
- "Next"をクリックします
- ソース・タイプは、"CIDR" を選択
- ソースCIDRは、アクセスを許可する CIDR を設定します。インターネット上の任意のアドレスからのアクセスを許可する場合は
0.0.0.0/0
と入力します - IPプロトコルは、"TCP"
- 宛先ポート範囲は、"443" を指定します
- "作成" をクリックします
リソース・プリンシパルによる認証・認可設定
API Gateway 用動的グループの作成
動的グループの作成手順は、ファンクション用動的グループの作成 と同じです
API Gateway 用動的グループの場合の"動的グループの作成"画面での設定項目は次のようになります。
- 任意の名前を入力します(例:API-GW-dg)
- 任意の説明を入力します(例:API Gateway動的グループ)
- 以下の一致ルールを入力します(この一致ルールは、リソースのタイプが OCI API Gateway(
resource.type = 'ApiGateway'
) で、リソースのコンパートメントが指定したコンパートメントIDを持つ API Gateway インスタンスをグループ化しています。ocid1.compartment.oc1..aaaaaaaa...
の部分はファンクションを作成した際のコンパートメントのIDを指定します)
ALL {resource.type = 'ApiGateway', resource.compartment.id = 'コンパートメントID'}
API Gateway 用 IAM ポリシーの作成
API Gateway 用 IAM ポリシーの作成手順は、ファンクション用 IAM ポリシーの作成 と同じです
API Gateway 用動的グループの場合の"動的グループの作成"画面での設定項目は次のようになります。
- 任意の名前を入力します(例:function4api-gateway-policy)
- 任意の説明を入力します(例:API Gateway に ファンクションの使用権限を与えるポリシー)
- ポリシー・ビルダーの右横の"手動エディタの表示"のスライダーを右にスライドして手動エディタを表示します
- 以下のポリシーを入力します
- 動的グループ名は、先程作成した動的グループの名前を指定します(例では、API-GW-dg)
- コンパートメントは、コンパートメントOCIDの確認で確認したコンパートメントを選択します
Allow dynamic-group 動的グループ名 to use functions-family in compartment コンパートメント名
API Gateway の作成
- OCIダッシュボード左上のナビゲーションメニューから"開発者サービス"⇒"API管理"⇒"ゲートウェイ"の順に選択します
- "ゲートウェイの作成" をクリックします
- 任意の名前を入力します(例:ServerlessGenAI-apigw)
- タイプは、"パブリック"を選択します
- コンパートメントは、コンパートメントOCIDの確認を で確認したコンパートメントを選択します
- 仮想クラウド・ネットワークはVCN(仮想クラウドネットワーク) の作成 で作成した仮想クラウド・ネットワーク(VCN)を選択します(例では、"OCI Generative AI Hands-on")
- サブネットは、パブリックサブネットを選択します(例では、パブリック・サブネット-OCI Generative AI Hands-on)
- "ネットワーク・セキュリティ・グループの有効化"にチェックを入れます。
- "(コンパートメント名)のネットワークセキュリティグループ"のプルダウンでネットワーク・セキュリティ・グループの設定で作成したネットワーク・セキュリティ・グループを選択します
- "ゲートウェイの作成"をクリックします
左上の円が緑色となりその下に"アクティブ"と表示されることを確認します
デプロイメントの作成
- 画面左側の"リソース"の下のメニューにある"デプロイメント"をクリックします
- "デプロイメントの作成"をクリックします
- "最初から"が選択されたままにします
- 任意の名前を入力します(例:ServerlessGenAI-apigw-deployment)
- 任意のパス接頭辞を入力します(例:/v1)
- パス接頭辞は"/" で始まる必要があります
- コンパートメントは、コンパートメントOCIDの確認を で確認したコンパートメントを選択します
- "CORS" の追加をクリックして以下のように設定します
- 許可されるオリジン: https://objectstorage.us-chicago-1.oraclecloud.com
- メソッド: POST
- 許可されるヘッダー:Content-Type
- その他はデフォルトのまま
- 変更の適用ボタンをクリックします
- "次" をクリックします
- 認証画面では、"認証なし"が選択された状態のまま "次"をクリックします
- パスに任意のパス名を入力します(例:/translate)
- メソッドは、
POST
を選択します - "単一のバックエンド"が選択された状態のままとします
- バックエンドタイプは、"Oracle ファンクション" を選択します
- アプリケーションは、OCI Functions アプリケーションの作成 で作成したアプリケーションの名前を指定します(例では、fn_genai_translate_app)
- 関数名は、ファンクションの作成 で作成したファンクションの名前を指定します(例では、genai_translate_func)
- "次"をクリックします
- "作成"をクリックします
- "ゲートウェイの詳細"画面に遷移し、状態がデプロイメントの"作成中"となります
- 状態が "アクティブ"となるまで待ちます
API Gateway を経由したファンクション呼び出しのテスト
- ゲートウェイの詳細"画面の下部"デプロイメント"で作成したデプロイメントの名前(例では、ServerlessGenAI-apigw-deployment)の列にあるエンドポイントをコピーします("コピー"リンクをクリックします
curl コマンドを使ったテスト
HTTPリクエストの body に翻訳したいテキストを設定して、翻訳機能を確認します
curl -X POST -H "Content-Type: application/json" --data '{"key":"value"}' エンドポイント/パス名
- "key" に "message" を入力します
- "value" に翻訳したい文字列を入力します
環境によりエスケープシーケンスに癖があるため以下の実行例を参考にしてください(Macが手元にないため Mac の例がありません...)
$ $ echo '{"message":"A long time ago in a galaxy far, far away…"}' | curl -X POST -H "Content-Type: application/json" --data @- https://xxx...xxx.apigateway.us-chicago-1.oci.customer-oci.com/v1/translate
{"message": "{\"input\": \"A long time ago in a galaxy far, far away...\",\"output\": \"遠い昔、はるか彼方の銀河系で...\"}"}
{"message": "遠い昔、はるか彼方の銀河系で..."}
$ echo '{"message":"こんばんは!あなたのお名前は?"}' | curl -X POST -H "Content-Type: application/json" --data @- https://xxx...xxx.apigateway.us-chicago-1.oci.customer-oci.com/v1/translate
{"message": "{\"input\": \"こんばんは!あなたのお名前は?\",\"output\": \"Good evening! What's your name?\"}"}
> curl.exe -X POST -H "Content-Type: application/json" https://hpahxbuqsuhbnbibunoesy55ju.apigateway.us-chicago-1.oci.customer-oci.com/v1/translate --data '{\"message\": \"A long time ago in a galaxy far, far away..\"}'
{"message": "{\"input\": \"A long time ago in a galaxy far, far away..\",\"output\": \"遠い昔、はるか彼方の銀河系で...\"}"}
> curl.exe -X POST -H "Content-Type: application/json" https://xxx...xxx.apigateway.us-chicago-1.oci.customer-oci.com/v1/translate --data '{\"message\": \"こんばんは!あなたのお名前は?\"}'
{"message": "{\"input\": \"こんばんは!あなたのお名前は?\",\"output\": \"Good evening! What's your name?\"}"}
"A long time ago in a galaxy far, far away…" という英文が「遠い昔、はるか彼方の銀河系で」という和文に翻訳されたことが確認できました。
{"message": "Hello!"}`と表示される場合は、HTTP Body の JSON を受け渡せていません。恐らくダブルクォーテーションのエスケープに失敗しています。ターミナルの環境によってエスケープに癖があるためうまくいかない場合は、次の Podman を使うことをおススメします
Postman によるテスト
- Postman を起動したら "+" をクリックして新しいリクエストを作成
- メソッドはデフォルトの "GET"のままにする
- URL にエンドポイントをペーストして、URLの末尾に先程設定したパス名(例:/translate)を追加します(パス接頭辞(例では、/v1)はコピーしたエンドポイントに記載されています)
- "送信"をクリックします
- レスポンスのボディに以下のように表示されれば API Gateway を経由したファンクションの呼び出しは成功です
続いて、message
パラメータに翻訳したいテキストを設定して、翻訳機能を確認します
- メソッドは"POST"を選択する(GETでも動作します)
- "パラメータ"とある行の"ボディ"を選択します
- ラジオボタンの "Raw" 選択します
- 入力欄に
{"message":"サーバーレス生成AIアプリの作成を楽しんでいますか?"}
と入力します - 送信をクリックします
- レスポンスのボディに以下のように表示されれば API Gateway を経由したファンクションの呼び出しと翻訳機能のテストは成功です
いずれの方法においても、API Gateway からレスポンスが返らずタイムアウトする場合には、インターネットから API Gateway へアクセスするためのセキュリティ・グループのインバウンド設定に不備があると思われます。また、{"message":"Internal Server Error","code":500}
というエラーが返ってきた場合には、API Gateway 用動的グループか API Gateway に ファンクションへのアクセス権を与えるIAM のポリシーの設定に不備がある可能性が高いのでこれらを再確認しましょう
Gradio Lite による UI
Gradio Python コード
import gradio as gr
import requests
import json
import re
def translate(text):
url = "エンドポイント名/v1/translate"
headers = {"Content-Type": "application/json"}
data = {"message": text}
try:
response = requests.post(url, headers=headers, data=json.dumps(data))
response.raise_for_status()
result = response.json()
message = result["message"]
# JSONデータを抽出し、改行をエスケープ
json_match = re.search(r'\{.*\}', message, re.DOTALL)
if json_match:
try:
json_string = json_match.group().replace('\n', '\\n')
json_data = json.loads(json_string)
return json_data.get("output", message)
except json.JSONDecodeError:
return message
else:
return message
except requests.exceptions.RequestException as e:
return f"エラーが発生しました: {str(e)}"
with gr.Blocks() as demo:
gr.Markdown("# とにかく翻訳する青山アイさん")
with gr.Row():
input_text = gr.Textbox(label="翻訳したい文章", lines=5, scale=1, show_copy_button=True)
translate_button = gr.Button("翻訳", scale=0, min_width=100)
output_text = gr.Textbox(label="翻訳結果", lines=5, scale=1, show_copy_button=True)
with gr.Row():
clear_button = gr.ClearButton(components=[input_text], value="クリア")
translate_button.click(fn=translate, inputs=input_text, outputs=output_text)
demo.launch(share=True)
-
この Python コード をコピーしてローカルに serverless_generative_ai_gradio_lite_translate.py という名前(例)で保存します
-
"エンドポイント名" をAPI Gatewayのデプロイメントで確認して書き換えます
- OCIダッシュボード左上のナビゲーション・メニューから"開発者サービス"⇒"ゲートウェイ"と選択し、左側の"リソース"の下のメニューから"デプロイメント"をクリックします
- "デプロイメント"に表示されたリストの中の先程作成したデプロイメントの名前の行の"エンドポイント"欄の"コピー"をクリックします
-
コード の "エンドポイント名" の部分へペーストします
-
url = "https://xxxx......xxxxxx.apigateway.us-chicago-1.oci.customer-oci.com/v1/translate" のような記述になります
-
ローカル環境や Cloud Shell で動作確認します
python serverless_generative_ai_gradio_lite_translate.py
Running on local URL: http://127.0.0.1:7860
Running on public URL: https://2e467eacb8694dfce1.gradio.live
This share link expires in 72 hours. For free permanent hosting and GPU upgrades, run `gradio deploy` from Terminal to deploy to Spaces (https://huggingface.co/spaces)
- ここで、操作している PC 上で Serverless_generative_ai_gradio_lite_translate.py を実行している場合は、local URL: の http://127.0.0.1:7860 を ブラウザで開きます(ターミナルによっては Ctrlキーを押しながらマウスクリックで開くことができます。)。Serverless_generative_ai_gradio_lite_translate.py を Cloud Shell などのリモートで実行している場合は、public URL: の後に表示されている URL をブラウザで開きます
- 以下のような画面がブラウザに表示されます
- "翻訳したい文章" の下のテキストボックスに "ある春の日に、俺は運命と出会った......" などと日本語の文章を入力して、"翻訳"ボタンをクリックします
- "翻訳結果"のテキストボックスに "On a spring day, I met my destiny..." などと英語が表示されれるはずです(初回は少し時間がかかります。また、翻訳文は厳密に同一とは限りません)
- 次に、クリアボタンをクリックして "翻訳したい文章" の下のテキストボックスをクリアした後、 "A long time ago in a galaxy far, far away…" などと入力して、"翻訳"ボタンをクリックします
-
- "翻訳結果"のテキストボックスに "遠い昔、遥か彼方の銀河系で。" などと英語が表示されれるはずです(翻訳文は厳密に同一とは限りません)
- 以上で、Gradio Python コードの作成と動作確認は完了です
Gradio Lite 化
Gradio Lite の公式ドキュメントはこちら↓です
作成した Gradio のPython コードをブラウザで動作するように Gradio Lite 対応の HTML へ埋め込みインターネットに公開します
Gradio の Python コードを Gradio Lite に対応させる手順は以下のとおりです。
- Gradio Lite の書式に従った HTML に Python コードと Python の requirements.txt を埋め込む
- HTML を Webサーバーやオブジェクトストレージを使って HTTP(S) でアクセスできるように公開する
HTML への埋め込み()
<html>
<head>
<script type="module" crossorigin src="https://cdn.jsdelivr.net/npm/@gradio/lite/dist/lite.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@gradio/lite/dist/lite.css" />
</head>
<body>
<gradio-lite>
<gradio-requirements>
(gradio 以外の Python パッケージのリスト)
</gradio-requirements>
(Gradio Python コード)
</gradio-lite>
</body>
</html>
- (gradio 以外の Python パッケージのリスト) の部分に、requirements.txt の gradio 以外のパッケージ名を改行で区切って列挙します。今回は、 requests だけです
- (Gradio Python コード) の部分に Gradio Python コードを丸ごと挿入します
以下が出来上がったHTMLコードです
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="module" crossorigin src="https://cdn.jsdelivr.net/npm/@gradio/lite/dist/lite.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@gradio/lite/dist/lite.css" />
</head>
<body>
<gradio-lite>
<gradio-requirements>
requests
</gradio-requirements>
import gradio as gr
import requests
import json
import re
def translate(text):
url = "エンドポイント名/v1/translate"
headers = {"Content-Type": "application/json"}
data = {"message": text}
try:
response = requests.post(url, headers=headers, data=json.dumps(data))
response.raise_for_status()
result = response.json()
message = result["message"]
# JSONデータを抽出し、改行をエスケープ
json_match = re.search(r'\{.*\}', message, re.DOTALL)
if json_match:
try:
json_string = json_match.group().replace('\n', '\\n')
json_data = json.loads(json_string)
return json_data.get("output", message)
except json.JSONDecodeError:
return message
else:
return message
except requests.exceptions.RequestException as e:
return f"エラーが発生しました: {str(e)}"
with gr.Blocks() as demo:
gr.Markdown("# とにかく翻訳する青山アイさん")
with gr.Row():
input_text = gr.Textbox(label="翻訳したい文章", lines=5, scale=1, show_copy_button=True)
translate_button = gr.Button("翻訳", scale=0, min_width=100)
output_text = gr.Textbox(label="翻訳結果", lines=5, scale=1, show_copy_button=True)
with gr.Row():
clear_button = gr.ClearButton(components=[input_text], value="クリア")
translate_button.click(fn=translate, inputs=input_text, outputs=output_text)
demo.launch(share=True)
</gradio-lite>
</body>
</html>
- この HTML をコピーしてローカルに serverless_generative_ai_gradio_lite_translate.html という名前(例)で保存します
- "エンドポイント名" をAPI Gatewayのデプロイメントで確認して書き換えます
- OCIダッシュボード左上のナビゲーション・メニューから"開発者サービス"⇒"ゲートウェイ"と選択し、左側の"リソース"の下のメニューから"デプロイメント"をクリックします
- "デプロイメント"に表示されたリストの中の先程作成したデプロイメントの名前の行の"エンドポイント"欄の"コピー"をクリックします
- HTML の "エンドポイント名" の部分へペーストします
- url = "https://xxxx......xxxxxx.apigateway.us-chicago-1.oci.customer-oci.com/v1/translate" のような記述になります
オブジェクトストレージへのアップロード
バケットの作成
- OCIダッシュボードの左上のナビゲーション・メニューから"ストレージ"⇒"バケット"に順に選択します
- "バケットの作成"をクリックします
- バケット名にに任意の名前を入力します(例:serverless_generativeai_gradio_lite_bucket)
- その他はデフォルトのまま作成ボタンをクリックします
- 作成したバケットの名前をクリックします
バケットの公開
- バケット名の下にある"可視性の編集"をクリックします
- "パブリック"をチェックします
- "ユーザーにこのバケットのオブジェクトのリスト表示を許可" のチェックを外します
- 変更の保存ボタンをクリックします
HTML のアップロード
- 上の画像の画面の下部のあるアップロードボタンをクリックします
- "ファイルを選択"をクリックして、serverless_generative_ai_gradio_lite_translate.html をアップロードします(その他はデフォルトのまま)
- 閉じるボタンをクリックします
- 画面下部の"オブジェクト"欄にアップロードした HTML ファイルの名前が表示されます
- HTML ファイルの名前の右側のメニュー(3つの点)をクリックします
- ”オブジェクト詳細の表示”を選びます
- "URLパス"に表示されているリンクをクリックします
- 以下のように Gradio Python コードのときと同じアプリケーションの UI が表示されるはずです
ここで、翻訳結果欄に 「エラーが発生しました: ('Connection aborted.', HTTPException("Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'https://hpahxbuqsuhbnbibunoesy55ju.apigateway.us-chicago-1.oci.customer-oci.com/v1/translate'."))」 のようなエラーメッセージが表示された場合は、API Gateway の CORSポリシー 設定に不備がある可能性が高いので API Gateway の設定を見直しましょう
- 先程と同じように"翻訳したい文章"に適当な文章を入力して"翻訳"ボタンをクリックしてテストします
お疲れさまでした!
この記事では、end-to-end でサーバーレスコンピューティングを活用する点を主題としているため生成AIアプリケーションは単純な翻訳アプリで何の工夫もありませんが、ファンクションに RAG を実装するなど育てていく予定です。なお、この記事ではできるだけ簡単にアプリ全体の構築を体験していただけるようにセキュリティへの配慮は厳密にしていません。ログイン機能・画面の実装や API Gateway と ファンクションの統合や CORS、バケットの公開など工夫いただく余地があります。みなさまのご要件に合わせて育ててあげてください。
おまけ
他にもいろいろ記事を書いていますので良かったらお立ち寄りください。