Bing Chat モドキを作って ReAct の重要ポイントを考察してみた

# はじめに
大まかな動きとしては、[過去検証](https://qiita.com/shyamagu/items/8a2df72d13c6f8a97be1)したものと一緒ではありますが、Bing Chat と同じような動きを再現実装して動きを検証してみたいと思います。（検索ワードと合わせて、検索した結果の要約文章と、利用した参考URLを表示するものです。下図）

特に Bing Chat というある意味の正解があるので、素で同じようなサービスを作ることで検討ポイントなどが洗い出せたらなという思いです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/977156/58cdcfd3-794a-dcd2-40a7-26a0ae72f475.png)

# やったこと
ステップとしては4段階になります。
1. ユーザのリクエストを元に ChatGPT で検索用クエリの生成
1. Bing Search v7 を使って、検索クエリからサイト情報の取得
1. 取得したURLをもとにスクレイピングで本文情報を取得（複数）
1. 取得した本文情報（複数）を ChatGPT に入力して、回答を生成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/977156/4c0dcb88-3908-b493-83a2-e2c701145194.png)

以下それぞれの詳細です。

## 1. ユーザのリクエストを元に ChatGPT で検索用クエリの生成

以下のプロンプトをもとにユーザのリクエストを、検索ワードの配列に変換します。
サンプルとして簡単なものをいれていますが、本当はココをもっと充実すべきですね。

```:クエリ生成器プロンプト
あなたなBing検索のクエリ―生成器です。
ユーザの入力から、必要となるであろうBing検索クエリを以下の出力フォーマットで返します。
**出力フォーマットの配列文字列のみ回答してください**
**補足や説明は不要です**

出力フォーマット:
["query1","query2"]

サンプル)
入力:「Azureとは?」
出力:["Azure","概要"]
```

## 2. Bing Search v7 を使って、検索クエリからサイト情報の取得

今回は Bing Chat になるべく寄せたいので、Bing Search を使います。Azure ポータルの「リソースの作成」から Bing で検索して、Bing Search v7 を選んで作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/977156/d818a2dd-14d4-b9dd-4413-fef707cae91a.png)

昔のサービスが出てきたり、日本語がなかったりと使い方のドキュメントがちょっと見つけづらいのですが、[公式ドキュメント](https://learn.microsoft.com/ja-jp/bing/search-apis/bing-web-search/search-the-web)を元に実装します。

要は API KEY をいれてエンドポイントをたたくだけなんですが
とりあえずまずはのパラメータとして5件だけデータを取ってくるように指定してます。

```js
async function callBingApi(query) {
    const search_result = await axios.get("https://api.bing.microsoft.com/v7.0/search?q="+encodeURIComponent(query)+"&count=5&offset=0&mkt=ja-JP",
        {headers: {
        'Ocp-Apim-Subscription-Key': process.env.BING_API_KEY
        }
    });
    const res = search_result.data.webPages.value

    return res
}
```

## 3. 取得したURLをもとにスクレイピングで本文情報を取得（複数）

上の2.でBing Search 結果が取得できますので、取得できたURLを実際に見に行くパートです。
ここは本当に色々ありますが、とりあえず`jsdom`を使って生データを取っていく形にしました。

`<main>`があればそちらを、なければ`<body>`のテキストをとってきて、無駄な空白を取り除いてます。

あとスクレイピング禁止なURLはフィルターする仕組みが必要ですね。
汎用的に様々な URL を見に行ってしまう可能性があるので、ここは注意が必要なポイントです。

```js
async function GetHtmlAndAnalyze(url) {

    const html = await axios.get(url)
    const dom = new JSDOM(html.data)
    const document = dom.window.document

    const body = document.querySelector("body")
    const main = document.querySelector("main")

    let text;
    if(body) text = body.textContent
    if(main?.textContent) text = main.textContent

    //textから2文字以上続く空白を取り除く
    const trimed_text = text.replace(/\s{2,}/g, " ")
    
    return trimed_text
}
```

## 4. 取得した本文情報（複数）を ChatGPT に入力して、回答を生成

スクレイピングで取得したデータを分析するため、プロンプトに埋め込んでいます。少し整形した形ですが、検索結果URLから取得したテキストデータのみを使って回答を作ってもらいます。

```js:分析用プロンプト
const system_summary = `ユーザからの要望に対して、以下の入力情報**だけ**をつかって、質問に日本語で回答してください。
情報が見つからない場合は、Bing検索では見つからなかった旨を回答してください。

入力情報JSONフォーマット
[{"url":"url1","text","text1"},{"url":"url2","text","text2"}...]

### 入力情報
${JSON.stringify(htmlresult2)}
`
```

# やってみた結果と本家 Bing Chat との比較

再掲になりますが、こんなアプリができました。一見よさげですよね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/977156/58cdcfd3-794a-dcd2-40a7-26a0ae72f475.png)

ただし色々とっても多くの検討・改善ポイントが本家との比較で見えてきました。それぞれのステップごとに分けて整理します。

### 1. ユーザのリクエストを元に ChatGPT で検索用クエリの生成

まず、求めた検索ワードが中々生成されません。とても揺れてしまいます。例えば、「大谷翔平についておしえてください。」とやって上図の結果だと、「大谷翔平」「プロフィール」が返ってきたものですが、例えば「大谷翔平」「経歴」「インタビュー」みたいな形で色々追加してくれちゃったりします。

ここは **プロンプトの改善** が必須なポイントです。
こういう検索をしたいなら、こういう検索ワードがよい、というサンプルを大量に入れてあげれば解決するかなと思いますし、もう少し誘導しないとダメですね。

### 2. Bing Search v7 を使って、検索クエリからサイト情報の取得

無料枠もあるし、検索機能自体にほぼ不満はありませんでした。**`:site=`指定で、特定のサイトだけに特化させる**とかもできるので、便利ですね。ただキー認証のみの対応ぽいので、実際に使うときはキー管理が大事になりそうです。
（もう少しさわると検索結果自体に不満が出ることもあるかもしれませんけども）

### 3. 取得したURLをもとにスクレイピングで本文情報を取得（複数）

ここが一番改善が必要なところかと思いました。内部の処理的には、
1. テキストデータをとってくる
1. ChatGPT で利用するトークン数をカウントする
1. トークン数が2048を超えていれば2048まで削る

とかをやっています。GPT-4ならまだしも4096トークン制限がある ChatGPT で入力と回答分を考えるとここは2048くらいが限界なのかなと。

この中で、テキストデータが軒並み莫大なトークン数になっていまう問題が一番つらいところです。普通のサイトでも10000トークンを超えるようなテキスト文章がざらに返ってくるため、せっかく複数リソースの情報を統合できるのに、ほぼほぼ検索結果の最初の1件しか使われないことになっていまいました。

**機械学習型のテキスト分析**をつかって、本文テキストのみをうまく抽出する必要があります。例えば [BoilerNet](https://github.com/mrjleo/boilernet) みたいなものを使わないとならなそうです。素のテキストを分析してしまうとまわりに余分な情報が入りますし、その情報を含めてトークン解析をすると**めっちゃ遅く**なります。

しかしこの手の分析ツールがPythonに偏ってるんですよね。。なので、Flaskとかでサーバサイドは用意したほうが色々いいかもですね。

もしこういったツールを使わないのであれば、良く検索に引っ掛かるサイトの構造をある程度おさえておいて、うまく本文テキストを抜く手法が必要になりそうです。（メンテナンスを考えるととっても辛そうですね。。。）

### 4. 取得した本文情報（複数）を ChatGPT に入力して、回答を生成

ここはもう**プロンプトの改善**一択です。今回試したプロンプトでは、情報がないのに勝手に創作してしまったりといまいちな動きでした。入力フォーマットなんか余計な情報をいれず、テキスト文章をそのまま貼ったほうがよさそうです。ここはでもまぁ微調整でいける問題かなと

# まとめ

今回は Bing Chat と同じ(ような) 動きを再現実装してみた検証になります。いずれのポイントも ReAct で重要なポイントにもなると思いますし、どの辺が本家 Bing Chat で工夫してそうかなんかも少し見えたので面白い検証ではありました。

こういう検索をしたければ、こういう検索ワードがよい、っていうノウハウって結構個人所蔵みたいなところが多々あると思いますので、この辺のアイディアがあればもしかするともっとよいものができるかもですね。（シナリオ特化だととくに）