Kong Gatewayを使ってAIの問い合わせ先を簡単に切り替える

AIに問い合わせをする際、AIの回答の精度の都合上色んなAIに聞いて答えを比較したいことがあると思う。
その際、それぞれに毎回URLやリクエストを変えて聞くのはちょっと面倒である。
Kong Gatewayを使うとアクセス先を変えずにヘッダ情報だけ変えて、例えば`llm: openai`と指定したら[OpenAI(ChatGPT)](https://chatgpt.com/)、`llm: cohere`と指定したら[Cohere](https://dashboard.cohere.com/)に問い合わせるようなことが出来るので、これの設定手順を確認する。

## 検証の前提
以下が揃ってるものとする。
- Kong Gateway
- [deck CLI](https://docs.konghq.com/deck/latest/)

Kong Gatewayの構築手順は過去にKubernetesでは[cert-managerでTLS化したKong Gatewayを構築する](https://qiita.com/ipppppei/items/44c100412d4e484f4f6a)、Dockerでは[ローカルPC＋GitHubActionsでAPIOpsを実装する](https://qiita.com/ipppppei/items/6afc7af77ce08b60adec#kong-gateway%E3%81%AE%E6%A7%8B%E7%AF%89)などの記事で紹介しているので、必要であればそちらを参照して欲しい。

私個人はMac上にMicrok8sでK8sクラスタを構築し、その上にKong Gatewayを立てて検証した。
なお、設定には`deck`を使うが、`deck`を使わずWebUI(Kong Manager)から設定することも出来るが今回は割愛する。

## Kong Gatewayの前提知識
Kong GatewayではServiceとRouteという概念があり、すごくざっくり言うとServiceは外部のサービス（例えばChatGPT)を定義したもので、RouteはそのServiceにアクセスするための条件（パス、認証、etc）を定義したものとなる。
ServiceとRouteは1対多の関係となっており、Routeに関しては同じパスでも条件を変えることで同じServiceに対していくつか定義する事ができる。
例えば、パスが`/common-ai`で、ヘッダが`llm: openai`の時、OpenAI向けの設定をしたAI Proxy Pluginを使い、同じパスでヘッダが`llm: cohere`の場合はCohere向けの設定をしたAI Proxy Pluginを使うようなことが出来る。
![20240612160221.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/35362606-6d3f-07a6-60ea-e389690d294b.png)

この仕組みを使ってヘッダで宛先を切り替える。

## 設定
`deck`に読ませるKong Gatewayの設定ファイル(`common-ai.yaml`)を生成する。
```yaml
cat << E0F > ./common-ai.yaml
services: 
- name: common-ai
  host: localhost
  port: 8000
  routes:
  - name: openai
    headers:
      llm:
      - openai
    paths:
    - "~/common-ai$"
    methods:
    - POST
    plugins:
    - name: ai-proxy
      config:
        route_type: "llm/v1/chat"
        auth:
          header_name: "Authorization"
          header_value: "Bearer sk-proj-MMHxxxxx"
        model:
          provider: "openai"
          name: "gpt-3.5-turbo"
          options:
            max_tokens: 512
            temperature: 1.0
  - name: cohere
    headers:
      llm:
      - cohere
    paths:
    - "~/common-ai$"
    methods:
    - POST
    plugins:
    - name: ai-proxy
      config:
        route_type: "llm/v1/chat"
        auth:
          header_name: "Authorization"
          header_value: "Bearer pasczxxxxxxx"
        model:
          provider: "cohere"
          name: "command"
          options:
            max_tokens: 512
            temperature: 1.0
EOF
```
ポイントだけ簡単に説明する。
```yaml
services: 
- name: common-ai
  host: localhost
  port: 8000
```
通常ここはアクセス先となるエンドポイント（ChatGPTなど）を指定するのだが、AI Proxy Pluginを使う場合はここの設定は見ずにプラグイン内で持っているAIのエンドポイントを直接叩きに行くのでこのような設定で問題ない。

```yaml
  routes:
  - name: openai
    headers:
      llm:
      - openai
```
上記でRouteに対して有効なヘッダを設定している。
これを設定することで、単にRouteのパスにアクセスしても`"no Route matched with those values"`が返ってきて有効なRouteと見なされない。
同様の設定をCohereのRouteにも設定することで、同じパスでもRouteを切り替えるようにしている。

```yaml
    plugins:
    - name: ai-proxy
      config:
```
上記でAI Proxy Pluginを有効にし、AIへのアクセスを設定する。
今回ConfigはOpenAIとCohere向けに設定しているが、ここの設定を変えればMistral、LlaMA等別のAIも利用可能である。

```sh
        auth:
          header_name: "Authorization"
          header_value: "Bearer pasczxxxxxxx"
```
上記は認証用トークンであるため、各自でトークンを発行して変更すること。

設定ファイルを作成したら、`deck`を使って現在の設定に追記する形で適用する。
`deck`を使うために環境変数を設定する。
```sh
export DECK_KONG_ADDR=https://admin.kong.192.168.64.201.sslip.io/api
export DECK_TLS_SKIP_VERIFY=true
```
`DECK_KONG_ADDR`にAdmin APIのアドレスを設定する。
dockerであればおそらく`http://localhost:8001`となる。
また自環境では自己署名証明書を使って構築していたため、証明書の検証をスキップするために`DECK_TLS_SKIP_VERIFY`も設定した。

次に現在のKong Gatewayの設定を出力する。
```sh
deck gateway dump -o /tmp/dump.yaml
```
作成した設定ファイルを含めてKong Gatewayに設定を反映する。
```sh
deck gateway sync ./common-ai.yaml /tmp/dump.yaml
```
設定が反映されると、以下のようにKong Managerから確認することが出来る。
![20240612163808.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/7883bd44-d9dd-10a7-7314-21ec7f5ea90e.png)

## 動作確認
ProxyのURLを環境変数に設定する。dockerであればおそらく`http://localhost:8000`となる
```sh
PROXY=https://192.168.64.202
```
最初にヘッダなしでアクセスしてみる。
```sh
curl -k --http1.1 -X POST $PROXY/common-ai  \
 -H "Content-Type: application/json" \
 -d '{
    "messages": [{
    "role": "user",
    "content": "Who are you?"
  }] }'
```
ヘッダが足りないので、Routeが見つからず出力は以下のようになる。
```json
{
  "message":"no Route matched with those values",
  "request_id":"715c010ee6634610991c806806307b4d"
}
```
ヘッダを付与してみる。最初にOpenAIから試してみる。
```sh
curl -k --http1.1 -X POST $PROXY/common-ai  \
 -H "llm: openai" \
 -H "Content-Type: application/json" \
 -d '{
    "messages": [{
    "role": "user",
    "content": "Who are you?"
  }] }' | jq .
```
すると以下のような出力が得られる。
```json
：（省略）
        "content": "I am a language model AI created by OpenAI. My purpose is to assist and provide information to the best of my abilities. How can I help you today?"
：（省略）
```
OpenAIということが確認できた。
次にCohereで試してみる。
```s
curl -k --http1.1 -X POST $PROXY/common-ai  \
 -H "llm: cohere" \
 -H "Content-Type: application/json" \
 -d '{
    "messages": [{
    "role": "user",
    "content": "Who are you?"
  }] }' | jq .
```
こちらでは以下のような出力が得られる。
```json
：（省略）
        "content": "I am your friendly AI-assistant chatbot created by the company Cohere. My name is Coral! I am trained to be helpful, polite, and inclusive in our conversation. Do feel free to ask me anything you like, and I will do my best to respond to your queries. \n\nIt is worthwhile to note that my knowledge till date is only up to June 12, 2024, meaning I am not aware of any events or information that has occurred post this date.",
：（省略）
```
ということで、アクセス先を変えずにヘッダだけで簡単にAIを切り替えることが出来た。
こんな感じでKong Gatewayを上手く使えば、AIによる回答比較なども簡単に出来そうだ。
AWSのBedrockでも同じような事は出来そうだが、動作環境を問わない点から制限された環境での選択肢としては頭の隅に置いておくとよさそう。