システムに生成AIを組み込む際に知っておくべき7のポイント

## これはなに？

生成AIを利用するシステムを構築したところ
生成AIの特性や振る舞いにより、従来の開発ではあまりなかった考慮や苦労がありました。
これからシステムに生成AIを組み込もうとしている人にとって知っておくべきポイントをご紹介します。

## どんなシステムで生成AIを使ったの？

あまり本質ではないのでざっくりとですが
リクエストを受けると、バックエンドでテキストデータと命令文を含むプロンプトを生成AIへ投げ情報の抽出、要約、アイディア出しさせるシステムです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957414/af5d3080-d8ac-fbeb-48da-6f3ee9ec8480.png)

## 前提

- 生成AIの出力をアプリケーションに組み込んでシステムを作る場合のお話です
- 生成AIを使って効率よく開発しよう！ではありません
- 環境にはAzureを使用しています
    - 生成AIはAzure OpenAI。モデルはgpt-4(32k) or gpt-4o

## システムに生成AIを組み込む際に知っておくべきポイント

### 間違えた内容を回答する

これは基本的なお話ですが、生成AIは間違えた内容を回答することがあります。

![bip.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957414/67718bd9-5296-2c81-8288-13a7c696969a.png)
たしか豊洲だったような？🤔

これは、ハルシネーションと呼ばれ対象の問題について生成AIの学習したデータが不足していたり偏っている場合に発生しやすいようです。  
特に、生成AIは一般的な情報を用いて学習しているので、特定分野に特化した回答がほしい場合には、元ネタとなる情報を外部から与える必要があります。

そのため、今回構築したシステムでは[RAG](https://atmarkit.itmedia.co.jp/ait/articles/2403/13/news035.html)と呼ばれる方式で情報を補完しました。

平たく言うと以下の手順で生成AIに問い合わせる方式です。

1. 生成AIへ問い合わせる前に必要な情報をデータソースからとってくる
1. 問い合わせ文（プロンプト）に1で取得した情報をくっつけて生成AIにぶん投げる

RAGの事例では、データソースとしてベクトルデータベースなどが紹介されていますが  
構築したシステムでは[Azure Bing Search](https://learn.microsoft.com/ja-jp/bing/search-apis/bing-web-search/overview)で検索したWebページそのものをデータソースとしました。

データベースを使う場合は事前に元ネタを準備しておく必要がありますが、
最新の特定分野のWeb情報をもとに回答がほしい場合などは、事前の元ネタの準備はなく、Web検索結果をデータソースとすることができます。

RAGのデメリットも挙げておくと
データソースからデータを検索するロジックとデータ自体の品質が回答の品質へ影響するようになります。  
狙ったデータを検索できれば回答が良くなる一方で、無関係のデータや品質の悪いデータを引っ張ってくると回答もおかしくなります。  
Web検索結果を用いる方式では検索キーワードを厳選しないと必要なページを当てられませんし、引っかかったWebページの充実度も回答品質に大きく影響してきます。
データベースを使う方式でも検索ロジックが大事なのは同様ですが、事前準備でデータのスクリーニングができるため一定のデータ品質は担保することができます。
 
### 結果がいつも同じとは限らない

全く同じプロンプトで問い合わせても
生成AIさんの気分で余計なものがついたりするし内容が変わったりします。

例えば全く同じ文章を要約させた場合でも以下のように違った回答が返ってきます。

![aimai.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957414/b80a71a8-f71a-4114-981f-2b198ee3156e.png)
ランダム性のパラメータが最低（temperature=0; ランダム性がなくなり一貫性ある回答を期待できる)でも違う回答が出る🤔


プロンプトの中で出力を具体化（内容、項目、観点など）で明示してやればある程度軽減させることはできますが
それが許されるかは要件次第ですし絶対大丈夫という保証もないので
基本的に回答はぶれることがあると認識しておくほうが正気でいられると思います。
利用規約やメッセージなどで「回答は一定ではありませんよ～」とユーザーに周知しておくのも大事です。


さて、語尾やお話の内容が変わるくらいならともかく、出力形式もたまに指示通りにならなかったりします。

```
※イメージ

以下の文章を表す一文を3つ列挙し、カンマ区切りの1行で回答してください。
---
（ここに文章がくる）

↓　＜回答

1. これは回答イメージです
2. 大体は指示した通りになりますが
3. たまにこんな感じでカンマ区切りでも1行でもない形式で返すなど、指示を無視することがありました
```

システムで処理するには回答内容をパースしなければならないのにこれはつらい。
つらいながらも、プロンプトで以下のようなことをやると指示無視を軽減できました。

- 出力例を付ける
```
・指示内容
文章を、出力例と同様の形式に変換してください。

・出力例
＜要旨＞ 果物の種類に関する考察
＜キーワード＞ 果物, 分類, フルーツ

・文章
（ここに文章を埋め込む）
```

- 指示を複数箇所に繰り返し記述してみる
```
・指示内容
文章を、出力例と同様の形式に変換してください。

・出力例
＜要旨＞ 果物の種類に関する考察
＜キーワード＞ 果物, 分類, フルーツ

・文章：出力例と同様の形式に変換すること
（ここに文章を埋め込む）

・注意
文章を、出力例と同様の形式に変換してください。
```

- 表現を変えてみる
```
文章を出力例と同様の形式に変換してください。
文章を出力例と同じ形式に変換してください。
文章を出力例の項目と形式で出力してください。
出力例を参考にして文章を変換してください。
```

形式については機械的なテストが可能なので
一定回数試して全て指示通りの形式を返すようプロンプトを調整しました。
どれくらいの回数OKだったら良しとするのかは難しいですが
20回に1回くらい指示を無視されるということがありましたので100回ほど回してます。

:::note info
使ったことはないのですが、JSONスキーマに従った形式で生成AIにレスポンスさせることができる[構造化出力](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/how-to/structured-outputs?tabs=python-secure)や[関数呼び出し](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/how-to/function-calling)という機能があるようです。  
この節で挙げた回答形式に関する問題を軽減できそうなので今後ぜひ試してみたいと思います。
:::



### レスポンスが遅い

プロンプトのサイズが大きいと本当に遅いです。

構築したシステムではプロンプトにWebページの情報をくっつけているため
大体数千から数万文字のプロンプトになっていましたが
当初使っていたgpt-4(32k)モデルでは回答までに20~50秒ほどかかっていました。
ひどいときには2分くらいかかったり。

後日、gpt-4oに変えたところ7~20秒ほどに大きく改善しましたがそれでも決して速くはないです。

情報をまとめる系機能のようにプロンプトが大きくなる場合には
数十秒程度はかかると見積もっておくほうが良さそうです。

1リクエスト内で生成AIを複数回呼び出したりすると
インフラのタイムアウトに引っかかることが考えられるので注意が必要です。
複数回の呼び出しが必要な場合はリクエストの受付だけを同期処理で行い、
生成AIへの問い合わせ自体は非同期処理にするなどの考慮が必要だと思います。


### プロンプトインジェクションは防げない

完全に防ぐのは無理っぽいのでリスクの低減を目指したほうがよさそうです。

[プロンプトインジェクション](https://www.promptingguide.ai/jp/risks/adversarial#%E3%83%97%E3%83%AD%E3%83%B3%E3%83%97%E3%83%88%E3%82%A4%E3%83%B3%E3%82%B8%E3%82%A7%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3)とは
プロンプトに悪意のある文章を入れ込むことで元のプロンプトの指示を改変する攻撃のことです。

例えば以下のように与えられた文章を英訳するプロンプトのテンプレートを考えてみます。
```
以下の文章を英訳してください
---
（ここに文章を埋め込む）
```

ここで、攻撃者が悪意をもった文章を与えるとこうなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957414/5e87ecf2-04cc-fb31-892e-5926b23fe5cf.png)

英訳して🤔

どこかで見たことがあると思いますが[SQLインジェクション](https://ja.wikipedia.org/wiki/SQL%E3%82%A4%E3%83%B3%E3%82%B8%E3%82%A7%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3)と同じような原理です。
命令文を埋め込むことで元のプロンプトの意図を無視させ、任意の命令を実行することが可能になります。

SQLインジェクションであればバインド機構を使うという明確な対策がありますが、
プロンプトインジェクションを防ぐための仕組みは今のところないようです。

そのため、外部からの入力をプロンプトに埋め込む必要がある場合には
プロンプトインジェクションによる攻撃が成立するという前提に立ち、
攻撃が成立した際のリスクを下げるために以下のような施策を打っておくことが大事だと思います。

#### 生成AIに機密情報を学習させない

攻撃によって大きな被害となりそうなのは、生成AIから機密情報が抜き出されることです。

RAG以外に生成AIの情報を補完する手段としてファインチューニングというものがあり[Azureでもできる](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/how-to/fine-tuning?tabs=azure-openai%2Cturbo%2Cpython-new)ようです。
こちらは生成AIに情報を与えて再学習させることでパフォーマンスを損なわずに回答の品質を向上させることができますが、  
機密情報を学習させた場合には攻撃による情報漏えいの観点でリスクがあります。

そのため、そもそも生成AIに機密情報を持たせないということがリスクの低減になります。
生成AIに機密情報を持たせず、かつ、機密情報を使った回答をさせたい場合には

1. プロンプトを学習しない生成AIを使用して
1. 機密情報をプロンプトに含めて渡す

ことで安全性が高まると思います。

機密情報をプロンプトに含めることで、生成AIに機密情報を使った回答を生成させつつ  
プロンプトを学習しなければ生成AIは機密情報を学習しないので  
攻撃されても抜き出されるということはないという寸法です。

プロンプトを学習するかどうかは生成AIによって異なると思いますが
少なくともAzure OpenAI Serviceではプロンプトで渡した情報は[学習しないとされています](https://learn.microsoft.com/ja-jp/legal/cognitive-services/openai/data-privacy?context=%2Fazure%2Fai-services%2Fopenai%2Fcontext%2Fcontext&tabs=azure-portal)。

#### プロンプトに防御用の命令を埋め込む

攻撃が成立される前提に立つと言ってもノーガードで良いわけではないので
なるべく攻撃が成立しないように防御用の命令等をプロンプトに埋め込むのも大事。

以下の参考ページのように様々な防御用の手段が公開されているため、盛れるだけ盛ります。
これらの方法が全てではないので考えられる限り対策を入れ込みましょう。

＜プロンプト防御の参考ページ＞
- https://www.promptingguide.ai/jp/risks/adversarial#%E9%98%B2%E5%BE%A1%E7%AD%96
- https://qiita.com/sakasegawa/items/09d9f6a485108f5a618a

プロンプトに防御策を仕込んだら攻撃用のデータを与えてみてテストを行います。

テストデータの考え方には[こちら](https://qiita.com/fuyu_quant/items/d9a44dfe3a7315f255ee#%E5%9F%BA%E6%9C%AC%E7%9A%84%E3%81%AA%E6%94%BB%E6%92%83)の内容を参考にするのが良いと思います。
その上で、埋め込む先のプロンプトを知っている前提で
記載されている制約・制限をすり抜け、優先されるような命令文をひねり出して与えてみます。

なお、1回防御できればOKとは限りません。
前述の通り生成AIの回答はブレるし指示を無視することもあるので一定回数防げることも確認したほうが良いです。

#### フィルタリングサービスを使ってみる

使ったことはありませんが、[攻撃的なプロンプトをフィルタリングするサービス](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/concepts/content-filter?tabs=warning%2Cuser-prompt%2Cpython-new#prompt-shield-content)もあるようです。

#### 回答内容を処理の条件分岐に使わない

あんまりないような気がしますが、  
生成AIの回答内容を条件分岐に使う仕組みは危なそうです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957414/a9daf85e-425a-5bc6-2313-6cc3234d1162.png)

プロンプトインジェクションによる攻撃が成立すると任意の回答に改変できるため、  
任意の条件分岐後の処理を実行できることになるためです。
条件分岐後に、特定の条件を満たす人しか見られないデータなどを表示するような処理があったりすると情報漏洩につながります。

### プロンプトが長すぎると怒られる

生成AIに投げられるプロンプトには長さの制限があります。

これはモデル毎の入力トークンの制限によるものです。
日本語の場合は大体1文字=1トークンらしいのでプロンプトの文字数と考えます。

モデル毎の入力トークンの制限は[こちら](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/concepts/models?tabs=python-secure%2Cglobal-standard%2Cstandard-chat-completions)に記載されています。

このネタを書こうと思った当時はgpt-4(32k)を使っており
入力が最大5万トークンだったので結構ギリギリでやりくりしていたのですが
2024/12現在の最新モデルでは入力の最大が12万トークンとなっておりかなり緩和されています。
ただし、RAGを使う場合はプロンプトにデータが埋め込まれるため入力トークンの制限は考慮する必要があります。
あまりに巨大なデータをプロンプトに渡すことはできないということは認識しておきましょう。

### リクエストをたくさん投げると怒られる

生成AIに投げられる分間のリクエスト数、トークン数には制限があります。

Azure OpenAI Serviceの場合は、モデルや契約ごとに異なりますが[こちら](
https://learn.microsoft.com/ja-jp/azure/ai-services/openai/quotas-limits#regional-quota-limits)にまとまっています。

この制限ですが、同一のサブスクリプション、リージョン、モデルで[共有](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/how-to/quota?tabs=rest#introduction-to-quota)しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957414/0f7600b5-9746-6bfa-5387-0954a3b1d885.png)

例えばgpt-4oのエンタープライズ契約では分間30Mトークン、180Kリクエストの制限があります。
そのため、一つのサブスクリプションで開発環境とステージング環境と本番環境の3環境用に同じリージョンでgpt-4oをデプロイすると、
分間30Mトークン、180Kリクエストの制限をそれぞれのリソースで分け合うことになります。
制限をそれぞれのリソースに[固定で割り振る](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/how-to/quota?tabs=rest#assign-quota)ことも必要なだけ[動的に割り当てる](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/how-to/dynamic-quota)こともできます。

このネタを書こうと思った当時はgpt-4(32k)を使っており
最大分間50万トークンが上限だったのでかなりつらかったのですが
現在はgpt-4oを使っており分間30Mトークンとかなり緩和されています。
ただし、契約や使用するモデルによって制限は変わるため流量の制限には注意を払う必要があります。

### 課金単位は入出力の長さで決まる

使用料は従量課金の場合はモデル毎の[入出力のトークン数](https://azure.microsoft.com/ja-jp/pricing/details/cognitive-services/openai-service/#pricing)により決まります。
パフォーマンスに優れたプロビショニング済という料金体系もあるようですがこちらは月100万円を超えるので採用するのはなかなか覚悟がいると思います。

このネタを書こうと思った当時はgpt-4(32k)を使っており
1企業のレポートを出力するたびに160円くらいかかっていたのですが
gpt-4oに変えることで使用料が1/10くらいになりだいぶ使いやすくなりました。

それでも、情報集約系の仕事をさせる場合には入出力のトークン数が大きくなり使用料もかさみがちなので
使用料には十分に気を配っておきましょう。

## おわりに

いかがだったでしょうか。
本記事ではシステムに生成AIを組み込む際に知っておいたほうがよいポイントについて記事にしました。
生成AIを組み込むシステムは今後ますます増えると思いますので参考になれば幸いです。

## We Are Hiring!

https://www.biprogy.com/recruit/recruiting/