G-gen の佐々木です。当記事では Google が提供する生成 AI モデル Gemini Pro と、Web UI 用の Python フレームワークである Gradio を使用した、シンプルなチャットボットの作り方を紹介します。 前提知識 Gemini Pro Gradio Gradio を使用して Gemini Pro のチャットボットを開発する Python のバージョン requirements.txt main.py コードの解説 Gradio の ChatInterface に渡す関数の形式について generation_config について Gemini における会話履歴の形式について 動作確認 ローカルでチャットボットを実行する チャットボットを使用する チャットボットを外部に共有する Safety Attributes の調整 ResponseValidationError Safety Attributes とは コードの修正 修正後の main.py 全文 動作確認 BLOCK_NONE に設定した場合の動作について Google Cloud 上にチャットボットをデプロイする Cloud Run を使用する コードの修正 Dockerfile の作成 Cloud Run にデプロイ 動作確認 Cloud Run のアクセス元制御について 前提知識 Gemini Pro Gemini Pro は、Google が提供する生成 AI モデル Gemini のバリエーションの1つであり、テキストや画像、動画などの複数の種類のデータを扱うことができるマルチモーダルな生成 AI モデルです。 詳細については以下の記事をご一読ください。 blog.g-gen.co.jp Gradio Gradio は、Python で機械学習 Web アプリを容易に構築できるフレームワークです。 当記事では、 Gradio の ChatInterface() を使用してチャットボットを作成しています。コードに以下の一行を記述するだけで、チャットボットに必要な機能を備えた UI を用意することができます。 gradio.ChatInterface(fn={関数名}).launch() ChatInterface ですぐに使用できる UI この UI 上でメッセージを送信(Submit)すると、 ChatInterface() に引数として渡した関数にメッセージを渡すことができます。この関数内にメッセージを処理するロジックを記述するだけで、UI を備えたチャットボットを簡単に開発することができます。 参考: How to Create a Chatbot with Gradio Gradio を使用して Gemini Pro のチャットボットを開発する Python のバージョン 当記事の内容は、 Python 3.12.0 で試しています。 $ python --version Python 3 . 12 . 0 requirements.txt 使用する外部ライブラリは以下の通りです。 google-cloud-aiplatform==1.42.1 gradio==4.19.2 main.py 使用するコードの全文を以下に記載します。 PROJECT_ID の値は、使用する Google Cloud プロジェクトの IDに置き換えてください。 import gradio as gr import vertexai from vertexai.generative_models import GenerativeModel, Content, Part # 環境変数の設定 PROJECT_ID = "myproject" # Google Cloud プロジェクトの ID LOCATION = "asia-northeast1" # Gemini モデルを使用するリージョン # Vertex AI API の初期化 vertexai.init(project=PROJECT_ID, location=LOCATION) # Gemini モデルとのチャットを行う関数 def gemini_chat (message, history, temperature, top_p, top_k, max_output_token): # Gemini モデルの初期化 generation_config = { "temperature" : temperature, # 生成するテキストのランダム性を制御 "top_p" : top_p, # 生成に使用するトークンの累積確率を制御 "top_k" : top_k, # 生成に使用するトップkトークンを制御 "max_output_tokens" : max_output_token, # 最大出力トークン数を指定 } gemini_model = GenerativeModel( model_name= "gemini-1.0-pro" , generation_config=generation_config ) # 会話履歴のリストを初期化 gemini_history = [] # 会話履歴のフォーマットを整形 for row in history: input_from_user = row[ 0 ] output_from_gemini = row[ 1 ] gemini_history.append(Content(role= "user" , parts=[Part.from_text(input_from_user)])) gemini_history.append(Content(role= "model" , parts=[Part.from_text(output_from_gemini)])) # Gemini モデルに会話履歴をインプット chat = gemini_model.start_chat(history=gemini_history) # Gemini モデルにプロンプトリクエストを送信 try : response = chat.send_message(message).text except IndexError as e: print (f "IndexError: {e}" ) return "Gemini からレスポンスが返されませんでした。もう一度質問を送信するか、文章を変えてみてください。" return response # UI に Generation Config を調整するスライダーを追加するためのリスト additional_inputs = [ gr.Slider(label= "Temperature" , minimum= 0 , maximum= 1 , step= 0.1 , value= 0.4 , interactive= True ), gr.Slider(label= "Top-P" , minimum= 0.1 , maximum= 1 , step= 0.1 , value= 1 , interactive= True ), gr.Slider(label= "Top-K" , minimum= 1 , maximum= 40 , step= 1 , value= 32 , interactive= True ), gr.Slider(label= "Max Output Token" , minimum= 1 , maximum= 8192 , step= 1 , value= 1024 , interactive= True ), ] if __name__ == "__main__" : # gemini_chat 関数を使用するチャットボットインターフェイスを起動 gr.ChatInterface( fn=gemini_chat, additional_inputs=additional_inputs ).launch() コードの解説 Gradio の ChatInterface に渡す関数の形式について コード末尾の ChatInterface(fn={関数名}).launch() で Gradio のチャットボットを起動しています。 if __name__ == "__main__" : gr.ChatInterface( fn=gemini_chat, additional_inputs=additional_inputs # ここは後ほど解説 ).launch() ChatInterface() の引数 fn に渡す関数は、ユーザーが送信したメッセージと過去の会話履歴を引数として受け取るように実装します(以下の第1、第2引数が該当)。 def gemini_chat (message, history, temperature, top_p, top_k, max_output_token): ユーザーが送信したメッセージは message に格納されます。 history には過去の会話履歴がリストとして渡されます。例えばユーザーが送信したメッセージを user_input_N 、モデルからのレスポンスを model_response_N とすると、以下のような形式で履歴が格納されます。 history = [ [user_input_1, model_response_1], [user_input_2, model_response_2], [user_input_3, model_response_3] ] 参考: How to Create a Chatbot with Gradio - Defining a chat function generation_config について Gemini 使用時にいくつかのパラメータを渡すことで、生成される回答の精度を調整することができます。パラメータの詳細については ドキュメント をご一読ください。 generation_config = { "temperature" : temperature, # 生成するテキストのランダム性を制御 "top_p" : top_p, # 生成に使用するトークンの累積確率を制御 "top_k" : top_k, # 生成に使用するトップkトークンを制御 "max_output_tokens" : max_output_token, # 最大出力トークン数を指定 } 当記事では、以下のようにしてチャットボットの UI 上にスライダーを配置し、メッセージ送信前に各種パラメータを手動で調整できるようにしています。 # UI に Generation Config を調整するスライダーを追加するためのリスト additional_inputs = [ gr.Slider(label= "Temperature" , minimum= 0 , maximum= 1 , step= 0.1 , value= 0.4 , interactive= True ), gr.Slider(label= "Top-P" , minimum= 0.1 , maximum= 1 , step= 0.1 , value= 1 , interactive= True ), gr.Slider(label= "Top-K" , minimum= 1 , maximum= 40 , step= 1 , value= 32 , interactive= True ), gr.Slider(label= "Max Output Token" , minimum= 1 , maximum= 8192 , step= 1 , value= 1024 , interactive= True ), ] if __name__ == "__main__" : # gemini_chat 関数を使用するチャットボットインターフェイスを起動 gr.ChatInterface( fn=gemini_chat, additional_inputs=additional_inputs ).launch() UI にパラメータを手動で調整するスライダーを配置する このように手動でパラメータの調整ができる機能をつける場合、メッセージ送信のたびにモデルの初期化を行う必要があります。レスポンスの速度などを気にする場合は、パラメータを固定化して Gemini モデルの初期化処理はグローバルスコープに記述します。 Gemini における会話履歴の形式について Gemini を利用する場合、過去の会話履歴は Content オブジェクトとしてモデルに渡します。 チャットボットのユーザーが送信したメッセージは role="user" 、Gemini からのレスポンスは role="model" として Content オブジェクトを作成します。 gemini_history.append(Content(role= 'user' , parts=[Part.from_text(input_from_user)])) gemini_history.append(Content(role= 'model' , parts=[Part.from_text(output_from_gemini)])) Gemini モデルとのチャットを開始する際に Content オブジェクトのリストを渡すことで、過去の会話履歴を用いたやり取りを行うことができます。 chat = gemini_model.start_chat(history=gemini_history) 動作確認 ローカルでチャットボットを実行する main.py を実行してローカルでチャットボットを起動します。 デフォルトではローカルホスト(127.0.0.1)のポート 7860 でチャットボットが起動されるため、ブラウザからアクセスします。 $ python main.py Running on local URL: http:// 127.0 . 0.1 : 7860 To create a public link, set `share= True ` in `launch()`. Gradio では gradio コマンドを使用することで、ホットリロードを使用してチャットボットを実行することもできます。 $ gradio main.py 参考: Developing Faster with Auto-Reloading チャットボットを使用する チャットボットの UI から適当なメッセージを送信してみます。 送信したメッセージの後に、Gemini Pro モデルからのレスポンスが表示されます。 ローカルで起動したチャットボットにメッセージを送信する チャットボットを外部に共有する Gradio では期限付きの外部公開 URL を発行することもできます。 この機能は、 launch() の引数に share=True を渡すことで利用できます。 gr.ChatInterface( fn=gemini_chat, additional_inputs=additional_inputs ).launch(share= True ) 発行された public URL にアクセスすると、インターネットからローカルで実行しているチャットボットにアクセスすることができます。 $ python main.py Running on local URL: http:// 127 . 0 . 0 .1:7860 Running on public URL: https://52b37dd2b7cf213999.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 ) インターネットからローカルのチャットボットにアクセスする 参考: Quickstart - Sharing Your Demo Safety Attributes の調整 ResponseValidationError 作成したチャットボットとやり取りしていると、以下のようにエラーが発生してレスポンスが表示されない可能性があります。 チャットボットからのレスポンスでエラーが発生する このスクリーンショットはエラーを再現するための極端なメッセージ例ですが、チャットボットと普通にしりとりをしているだけであっても同じエラーに遭遇する可能性があります。 チャットボットのログには以下のように ResponseValidationError が出力されています。 これは、Gemini からのレスポンスに対していくつかの基準(後述)で検証が行われた結果、不適切な内容が含まれている可能性があると判断され、レスポンスがブロックされたことを示しています。 vertexai.generative_models._generative_models.ResponseValidationError: The model response did not completed successfully. Finish reason: 3 . Finish message: . Safety ratings: [ category: HARM_CATEGORY_HATE_SPEECH probability: NEGLIGIBLE , category: HARM_CATEGORY_DANGEROUS_CONTENT probability: NEGLIGIBLE , category: HARM_CATEGORY_HARASSMENT probability: MEDIUM blocked: true , category: HARM_CATEGORY_SEXUALLY_EXPLICIT probability: NEGLIGIBLE ] . To protect the integrity of the chat session, the request and response were not added to chat history . To skip the response validation, specify `model.start_chat ( response_validation =False ) ` . Note that letting blocked or otherwise incomplete responses into chat history might lead to future interactions being blocked by the service. このエラーメッセージでは、 HARM_CATEGORY_HARASSMENT というカテゴリで MEDIUM 、つまり中程度に不適切な可能性があるレスポンスがあったことがわかります。 Safety Attributes とは Safety Attributes とは、Gemini モデルが不適切なコンテンツを生成することを防ぐための評価カテゴリであり、このカテゴリに照らし合わせて不適切な可能性があると判断されたコンテンツは、ユーザーに返される前にブロックされます。 Safety Attributes には以下のようなカテゴリがあります。 Safety Attributes のカテゴリ 説明 Hate Speech (HARM_CATEGORY_HATE_SPEECH) 特定の属性に対するヘイトスピーチに関するもの。 Harassment (HARM_CATEGORY_HARASSMENT) 別の個人に対する嫌がらせに関するもの。 Sexually Explicit (HARM_CATEGORY_SEXUALLY_EXPLICIT) 露骨な性的表現に関するもの。 Dangerous Content (HARM_CATEGORY_DANGEROUS_CONTENT) 有害な商品、サービス、活動に関するもの。 Gemini API を使用する場合、API リクエストに Safety Settings としてブロックの閾値を設定することで、Safety Attributes のカテゴリごとにフィルターの強さを調整することができます。 何も設定していない場合は、デフォルトで BLOCK_MEDIUM_AND_ABOVE が設定されます。 閾値 説明 BLOCK_NONE レスポンスに対して Safety Attributes のフィルタを適用しない(常にレスポンスを表示する)。 BLOCK_ONLY_HIGH 不適切である可能性が高いレスポンスのみブロックする。 BLOCK_MEDIUM_AND_ABOVE デフォルトの閾値。不適切である可能性が中程度以上のレスポンスをブロックする。 BLOCK_LOW_AND_ABOVE 不適切である可能性が少程度であってもレスポンスをブロックする。 HARM_BLOCK_THRESHOLD_UNSPECIFIED デフォルトの閾値を使用する。 参考: Configure safety attributes コードの修正 Gemini モデルに対して Safety Settings を含むリクエストを送信するようにコードを修正します。 まず、 vertexai.generative_models からの import 文に HarmCategory 、 HarmBlockThreshold 、 ResponseValidationError を追記します。 from vertexai.generative_models import GenerativeModel, Content, Part, HarmCategory, HarmBlockThreshold, ResponseValidationError Safety Attributes のカテゴリごとにフィルターの強さを設定します。当記事では一律 BLOCK_ONLY_HIGH に設定します。 ここで設定できるカテゴリの種類は先ほど説明した4つに加え、不特定カテゴリ( HARM_CATEGORY_UNSPECIFIED )が存在します。 # 緩めの Safety Settings SAFETY_SETTINGS = { HarmCategory.HARM_CATEGORY_UNSPECIFIED: HarmBlockThreshold.BLOCK_ONLY_HIGH, HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_ONLY_HIGH, HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_ONLY_HIGH, HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_ONLY_HIGH, HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_ONLY_HIGH, } send_message で Gemini にメッセージを送る際に、 safety_settings 引数を渡すように修正します。 また、 BLOCK_ONLY_HIGH にフィルターを緩めた後でも、不適切な可能性が高いと判定されたレスポンスは引き続きエラーが発生してしまうため、 ResponseValidationError について例外処理を実装し、フィルタに引っかかってしまった場合のユーザーへのメッセージを返すようにします。 # Gemini モデルとのチャットを行う関数 def gemini_chat (message, history): 〜〜〜省略〜〜〜 # Gemini モデルにプロンプトリクエストを送信 try : response = chat.send_message( message, safety_settings=SAFETY_SETTINGS # 引数に Safety Attributes の設定を追加 ).text except ResponseValidationError as e: # フィルタに引っかかった場合のエラー処理 print (f "ResponseValidationError: {e}" ) return "Gemini から不適切なレスポンスが返されたため、メッセージを表示できません。もう一度質問を送信するか、文章を変えてみてください。" except IndexError as e: print (f "IndexError: {e}" ) return "Gemini からレスポンスが返されませんでした。もう一度質問を送信するか、文章を変えてみてください。" return response 修正後の main.py 全文 Safety Attributes の設定を加えたコードの全文を以下に記載します。 import gradio as gr import vertexai from vertexai.generative_models import GenerativeModel, Content, Part, HarmCategory, HarmBlockThreshold, ResponseValidationError # 環境変数の設定 PROJECT_ID = "myproject" # Google Cloud プロジェクトの ID LOCATION = "asia-northeast1" # Gemini モデルを使用するリージョン # 緩めの Safety Settings SAFETY_SETTINGS = { HarmCategory.HARM_CATEGORY_UNSPECIFIED: HarmBlockThreshold.BLOCK_ONLY_HIGH, HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_ONLY_HIGH, HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_ONLY_HIGH, HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_ONLY_HIGH, HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_ONLY_HIGH, } # Vertex AI API の初期化 vertexai.init(project=PROJECT_ID, location=LOCATION) # Gemini モデルとのチャットを行う関数 def gemini_chat (message, history, temperature, top_p, top_k, max_output_token): # Gemini モデルの初期化 generation_config = { "temperature" : temperature, # 生成するテキストのランダム性を制御 "top_p" : top_p, # 生成に使用するトークンの累積確率を制御 "top_k" : top_k, # 生成に使用するトップkトークンを制御 "max_output_tokens" : max_output_token, # 最大出力トークン数を指定 } gemini_model = GenerativeModel( model_name= "gemini-1.0-pro" , generation_config=generation_config ) # 会話履歴のリストを初期化 gemini_history = [] # 会話履歴のフォーマットを整形 for row in history: input_from_user = row[ 0 ] output_from_gemini = row[ 1 ] gemini_history.append(Content(role= "user" , parts=[Part.from_text(input_from_user)])) gemini_history.append(Content(role= "model" , parts=[Part.from_text(output_from_gemini)])) # Gemini モデルに会話履歴をインプット chat = gemini_model.start_chat(history=gemini_history) # Gemini モデルにプロンプトリクエストを送信 try : response = chat.send_message( message, safety_settings=SAFETY_SETTINGS # 引数に safety attributes の設定を追加 ).text except ResponseValidationError as e: # フィルタに引っかかった場合のエラー処理 print (f "ResponseValidationError: {e}" ) return "Gemini から不適切なレスポンスが返されたため、メッセージを表示できません。もう一度質問を送信するか、文章を変えてみてください。" except IndexError as e: print (f "IndexError: {e}" ) return "Gemini からレスポンスが返されませんでした。もう一度質問を送信するか、文章を変えてみてください。" return response # UI に Generation Config を調整するスライダーを追加するためのリスト additional_inputs = [ gr.Slider(label= "Temperature" , minimum= 0 , maximum= 1 , step= 0.1 , value= 0.4 , interactive= True ), gr.Slider(label= "Top-P" , minimum= 0.1 , maximum= 1 , step= 0.1 , value= 1 , interactive= True ), gr.Slider(label= "Top-K" , minimum= 1 , maximum= 40 , step= 1 , value= 32 , interactive= True ), gr.Slider(label= "Max Output Token" , minimum= 1 , maximum= 8192 , step= 1 , value= 1024 , interactive= True ), ] if __name__ == "__main__" : # gemini_chat 関数を使用するチャットボットインターフェイスを起動 gr.ChatInterface( fn=gemini_chat, additional_inputs=additional_inputs ).launch() 動作確認 エラーが発生したメッセージを再度送信してみると、正常なレスポンスが返ってきました。 フィルターを緩めた結果、同じ質問に対して正常にレスポンスが返ってくる また、より不適切な可能性が高いレスポンスにはフィルターが機能することも試してみます。例外処理に設定したエラーメッセージが返ってくることが確認できます。 緩いフィルターでもブロックされた場合は設定しておいたメッセージを返す エラーログを確認すると、 HARM_CATEGORY_HARASSMENT カテゴリで HIGH 、つまり高確率で不適切な内容のレスポンスがブロックされたことがわかります。 ResponseValidationError: The model response did not completed successfully. Finish reason: 3 . Finish message: . Safety ratings: [ category: HARM_CATEGORY_HATE_SPEECH probability: NEGLIGIBLE , category: HARM_CATEGORY_DANGEROUS_CONTENT probability: NEGLIGIBLE , category: HARM_CATEGORY_HARASSMENT probability: HIGH blocked: true , category: HARM_CATEGORY_SEXUALLY_EXPLICIT probability: NEGLIGIBLE ] . To protect the integrity of the chat session, the request and response were not added to chat history . To skip the response validation, specify `model.start_chat ( response_validation =False ) ` . Note that letting blocked or otherwise incomplete responses into chat history might lead to future interactions being blocked by the service. BLOCK_NONE に設定した場合の動作について ブロックの閾値を BLOCK_NONE に設定した場合、つまりフィルターを無効化した場合であっても、有害になり得る質問へのレスポンスがそもそも生成されないケースもあります。 有害なレスポンスが生成されないケース① 有害なレスポンスが生成されないケース② Google Cloud 上にチャットボットをデプロイする Cloud Run を使用する ここまでで作成したチャットボットを Google Cloud 上にデプロイしてみます。 当記事ではデプロイ先のサービスとして、サーバーレス コンテナ コンピューティングサービスである Cloud Run を使用します。 Cloud Run の詳細については以下の記事をご一読ください。 blog.g-gen.co.jp コードの修正 main.py 末尾の launch() の引数を、以下のように修正します。 if __name__ == "__main__" : # gemini_chat 関数を使用するチャットボットインターフェイスを起動 gr.ChatInterface( fn=gemini_chat, additional_inputs=additional_inputs ).launch(server_name= "0.0.0.0" , server_port= 8080 ) Dockerfile の作成 Cloud Run へのデプロイには Docker イメージを用意する必要があるため、 Docker Hub のサンプル を元に、簡単な Dockerfile を作成します。 FROM python:3.12-slim WORKDIR /usr/src/app COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD [ "python" , "./main.py" ] Cloud Run にデプロイ Dockerfile を作成したディレクトリで以下のコマンドを実行し、コンテナイメージのビルドと Cloud Run へのデプロイを同時に行います。 # Cloud Run サービスをデプロイ $ gcloud run deploy gradio-gemini --source . \ --region = asia-northeast1 \ --allow-unauthenticated ビルドされたコンテナイメージは、指定したリージョンに自動で作成される「cloud-run-source-deploy」という名前の Artifact Registory リポジトリに格納されます。 参考: ソースコードからデプロイする 動作確認 Cloud Run のデプロイが完了すると、 Service URL として Cloud Run のエンドポイントが出力されているので、ブラウザからアクセスします。 # デプロイ完了後のコマンド出力例 $ gcloud run deploy gradio-gemini --source . \ --region = asia-northeast1 \ --allow-unauthenticated This command is equivalent to running `gcloud builds submit --pack image = [ IMAGE ] .` and `gcloud run deploy gradio-gemini --image [ IMAGE ] ` Building using Buildpacks and deploying container to Cloud Run service [ gradio-gemini ] in project [ myproject ] region [ asia-northeast1 ] ✓ Building and deploying new service... Done. ✓ Uploading sources... ✓ Building Container... Logs are available at [ https://console.cloud.google.com/cloud-build/builds/d72a1c89-4e73-41ea-86b9-467976adfcb0?project = xxxxxxxxxxxx ] . ✓ Creating Revision... ✓ Routing traffic... ✓ Setting IAM Policy... Done. Service [ gradio-gemini ] revision [ gradio-gemini-00001-rkr ] has been deployed and is serving 100 percent of traffic. Service URL: https://gradio-gemini-ai4xxxxxxx-an.a.run.app Cloud Run 上のチャットボットにアクセスすることができました。 Cloud Run にデプロイしたチャットボットにアクセスする Cloud Run のアクセス元制御について Cloud Run にデプロイしたチャットボットのアクセス元制御を行いたい場合、Cloud Run の前段にロードバランサーを配置し、Identity Aware Proxy(IAP)による IAM 認証や Cloud Armor による IP アドレスの制限を実装します。 詳細な手順については以下の記事を参照してください。 blog.g-gen.co.jp 佐々木 駿太 (記事一覧) G-gen最北端、北海道在住のクラウドソリューション部エンジニア 2022年6月にG-genにジョイン。Google Cloud Partner Top Engineer 2024に選出。好きなGoogle CloudプロダクトはCloud Run。 趣味はコーヒー、小説(SF、ミステリ)、カラオケなど。 Follow @sasashun0805