KongのAPI Gatewayを使ってトークン量ベースでLLMプロバイダへの流量制御を行う

LLMのAIを使う際、課金方法は基本的にトークン量で課金される。
（参考：[ChatGPTやAzure OpenAI Serviceの課金単位「トークン」とは？ 計算してみよう](https://www.creationline.com/tech-blog/71957)）
従来のAPIは流量で課金されることがほとんであるため、流量制御のプラグインで課金額を調整できたが、AIの場合は使いすぎ防止などの観点からトークン量でリクエストを制限したくなる。

Kong Gatewayの[AI Rate Limiting Advanced Plugin](https://docs.konghq.com/hub/kong-inc/ai-rate-limiting-advanced/)を使うと、LLMプロバイダーから返ってきたトークンデータを使用してリクエスト数を制限することが出来る。
通常の流量制御のプラグインと大きく違う点は、流量ではなくトークン数をベースに制限出来る点であり、AIの使いすぎによる課金などを防ぐことが出来そうだ。
今回はこの動作を確認してみる。

なお、通常の流量制御を行うRate Limiting Pluginについては「[KongのRate Limiting Advanced PluginとRedis Sentinelを組み合わせて使う](https://qiita.com/ipppppei/items/3028bf3f55b26df5ec60)」という記事で紹介しているので、興味がある人は参照して欲しい。

## AI Rate Limiting Advanced Pluginとは
[AI Rate Limiting Advanced Plugin](https://docs.konghq.com/hub/kong-inc/ai-rate-limiting-advanced/)とはKong Gatewayのプラグインで前述の通りLLMプロバイダーのトークン利用量によってリクエストを制御を行う事が出来る。
導入前後のイメージはざっくり以下のような感じとなる。
![20240719084300.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/733469ce-9f0f-deaf-d205-172421973154.png)

利用者はKong Gateway経由でLLMプロバイダーにアクセスし、Kong Gatewayはレスポンス取得時にトークン利用量を記録する。
トークンの記録方法としては、[Rate Limiting Advanced Plugin](https://docs.konghq.com/hub/kong-inc/rate-limiting-advanced/)と同じで`local`、`cluster`、`redis`の3つから選べる（これらの説明に関しては[旧記事](https://qiita.com/ipppppei/items/3028bf3f55b26df5ec60)参照）。
またトークンの計算方法として3つの方法が用意されている。
|方法|意味|
|---|---|
|`prompt_tokens`|Prompt（入力）のトークン数|
|`completion_tokens`|Completion（出力）のトークン数|
|`total_tokens`|`prompt_tokens`と`completion_tokens`の合計、デフォルトで使用|

トークン数についてはAIにAPIでリクエストを発行した際、レスポンスの中から以下のような出力が確認でき、これを利用しているものと思われる。
```json
  "usage": {
    "prompt_tokens": 22,
    "completion_tokens": 10,
    "total_tokens": 32
  },
```
またトークンの上限(Limit)と現在の残量(Remaining)については以下のようにLLMプロバイダごとの情報がレスポンスヘッダから確認できる（設定で隠すことも可能）
```yaml
X-AI-RateLimit-Remaining-100-cohere: 100
X-AI-RateLimit-Limit-1000-openai: 3600
X-AI-RateLimit-Remaining-1000-openai: 3577
X-AI-RateLimit-Limit-100-cohere: 100
```
なお、`AI`がついていない以下のようなヘッダについてはLLMプロバイダが付与するヘッダであり、Kong Gatewayは関与していないので注意。（参考：[Rate limits in headers](https://platform.openai.com/docs/guides/rate-limits/rate-limits-in-headers)）
```yaml
x-ratelimit-limit-requests: 500
x-ratelimit-limit-tokens: 30000
x-ratelimit-remaining-requests: 499
x-ratelimit-remaining-tokens: 29475
x-ratelimit-reset-requests: 120ms
x-ratelimit-reset-tokens: 1.05s
```

'24/7時点で対応しているLLMプロバイダは
openai, azure, anthropic, cohere, mistral, llama2, requestPrompt
となっている。

その他の設定項目については多くがRate Limiting Advanced Pluginのものを引き継いでいるのでそちらも併せて参考にするとよい。

## 検証環境
検証を行うにあたり、ここでは既に以下があるものとして進める。
- Kong Gateway Enterprise v3.7
- LLMサービスのAPIトークン
- [deckコマンド](https://docs.konghq.com/deck/latest/)

Kong GatewayのAdmin APIは公開されており、deckコマンドで利用可能になっているものとする。
LLMサービスのAPIトークンはここでは[OpenAI](https://platform.openai.com/api-keys)のものを利用する。
お金をかけずに試したい人は[Cohere](https://dashboard.cohere.com/welcome/login)かローカルにLLMサービスを立てて使うとよい。


## Pluginの導入
以下のような感じでKong Gatewayが提供するProxyの`/ai-ratelimit`にアクセスすると、Service側のAI Rate Limit Pluginでトークン量を制限するように構築する。
![20240719094859.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/4e41589a-698e-2f51-b040-ab6c1559639a.png)
なお、[AI Proxy Plugin](https://docs.konghq.com/hub/kong-inc/ai-proxy/)をRouteで設定しているが、AI Rate Limit Pluginのの利用が前提となっているので、併せて設定している。
Kong Gatewayに各種プラグインの設定をするために、以下のdeck用YAMLを作成する。
```yaml:ai-ratelimit.yaml
services: 
- name: common-ai-for-ratelimit
  host: localhost
  port: 8000
  plugins: 
  - name: ai-rate-limiting-advanced
    config:
      tokens_count_strategy: total_tokens
      llm_providers:
      - limit: 100
        name: openai
        window_size: 100
  routes:
  - name: openai-for-ratelimit
    headers:
      llm:
      - openai
    paths:
    - "~/ai-ratelimit$"
    methods:
    - POST
    plugins:
    - name: ai-proxy
      config:
        route_type: "llm/v1/chat"
        auth:
          header_name: "Authorization"
          header_value: "Bearer sk-svcacct-xxxx"
        model:
          provider: "openai"
          name: "gpt-4-turbo"
          options:
            max_tokens: 512
            temperature: 1.0
```
`model.provider`に設定した値は[こちら](https://platform.openai.com/docs/models)から適当に選んだ。
`auth.header_value`の`sk-svcacct-xxxx`に関しては自身のOpenAIのAPIキーを設定する。
（分かりやすさの観点でベタ書きしているが、実際は[Secret Management](https://docs.konghq.com/gateway/latest/kong-enterprise/secrets-management/)を使って隠蔽することを推奨）
AI Rate Limiting Advanced Pluginの設定箇所は以下の部分になる。
```yaml
  plugins: 
  - name: ai-rate-limiting-advanced
    config:
      tokens_count_strategy: total_tokens
      llm_providers:
      - limit: 100
        name: openai
        window_size: 100
```
`tokens_count_strategy`は指定しなくてもデフォルトで`total_tokens`だが、分かりやすくするためにあえて設定している。
なお、OpenAIだと[出力の方が高くなる](https://openai.com/api/pricing/)ため、出力トークンの制御に該当する`completion_tokens`を設定して使うと良さそうだが、'24/7/19時点で`completion_tokens`指定時に正常に動作していないように見えるのでここでは選んでいない。

作成したdeck用YAMLを適用する。
```sh
deck gateway dump -o /tmp/dump.yaml
deck gateway sync /tmp/dump.yaml ./ai-ratelimit.yaml
```

## 検証
Kong Gateway経由でOpenAIにプロンプトを数回投げて、トークン量ベースの流量制御が効くかを確認する。
まず最初にアクセス先とプロンプトを環境変数に設定する。
```sh
PROXY=https://192.168.64.202
ROUTE_PATH=ai-ratelimit
PROMPT="1+1は？"
```
以下の`curl`コマンドを2回実行する。
```sh
curl -k -i --compressed --http1.1 -X POST ${PROXY}/${ROUTE_PATH} \
  -H "llm: openai" \
  -H "Content-Type: application/json" -d "{
    \"messages\": [{
    \"role\": \"user\",
    \"content\": \"$PROMPT\"
  }] }"
```

1回目の出力はおおよそ以下のような感じになっていると思う。
```sh
：（省略）
X-AI-RateLimit-Limit-100-openai: 100
X-AI-RateLimit-Remaining-100-openai: 100
；（省略）
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "1+1は2です。"
      },
      "logprobs": null,
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 12,
    "completion_tokens": 7,
    "total_tokens": 19
  },
```
`X-AI-RateLimit-Remaining`に関してはまだ利用トークン数がカウントされていないので100のままとなる。
利用したトークン数の合計は19なので、次回実行時の残トークン数は100-19=81となるはずだ。
2回目の出力は以下のような感じとなる。
```sh
：（省略）
X-AI-RateLimit-Limit-100-openai: 100
X-AI-RateLimit-Remaining-100-openai: 81
：（省略）
```
期待した通り、前回のトークンの総数を引いた数が残数となっている。
ここで利用トークンが多そうな内容に変えてリクエストを送ってみる。
```sh
PROMPT="日本の観光地の人気トップ3と特長を教えて"
curl -k -i --compressed --http1.1 -X POST ${PROXY}/${ROUTE_PATH} \
  -H "llm: openai" \
  -H "Content-Type: application/json" -d "{
    \"messages\": [{
    \"role\": \"user\",
    \"content\": \"$PROMPT\"
  }] }"
```
実行結果は以下となった。
```sh
  "usage": {
    "prompt_tokens": 31,
    "completion_tokens": 512,
    "total_tokens": 543
  },
```
利用トークン数(543)が制限(100)を上回った。
この状態でリクエストを発行しても失敗するはずだ。
実施に実行した結果が以下となる。
```sh
HTTP/1.1 429 Too Many Requests
Date: Fri, 19 Jul 2024 01:53:59 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
X-AI-RateLimit-Reset-100-openai: 130
X-AI-RateLimit-Retry-After-100-openai: 130
X-AI-RateLimit-Limit-100-openai: 100
X-AI-RateLimit-Remaining-100-openai: 0
X-AI-RateLimit-Retry-After: 130
X-AI-RateLimit-Reset: 130
X-Kong-Response-Latency: 1
Server: kong/3.7.1.2-enterprise-edition
X-Kong-Request-Id: 54e199023349775c0c3ca81979db6a25

{"message":"API rate limit exceeded for provider(s): openai"}
```
残数(`X-AI-RateLimit-Remaining-100-openai`)が0となり、429が返ってきた。
これにより、リクエストの制限をトークン量で制限出来ることが確認できた。

## 所感
組織でAIを活用する上で、使いすぎをどう抑止するかは課題になると思われる。
アカウントごとに設定するのであれば容易だが、同一アカウントを利用用途などで更に細かく設定したい場合などユースケースによってはLLMプロバイダが提供している手段だけだと使いすぎを防ぐのが難しくなってくる。
利用規模が大きくなった時など、複雑かつガバナンスをしっかり効かせるようなケースが出てきた場合はこのプラグインを大いに活用できそうだ。