ChatGPT初心者でもわかる 「Chain of Thought をPythonで100行で実装してみた」

最近、どっぷりLLMにハマっているsonesuke( https://twitter.com/sonesuke )です。

前回はChain of Thoughtの基本的な考え方を紹介しました。全体像を知りたい方は、ぜひ前回の記事も参照してください。

https://qiita.com/sonesuke/items/2dc3e316ff28e8a331b6


今回は、OpenAIのAPIを使って実装していきます。

# TL;DR

1. Chain of ThoughtのPython実装です
2. 成果物はこちら

https://github.com/sonesuke/explain-chain-of-thought




# 前提条件

- openai==0.27.0
- google-api-python-client==2.80.0
- requests==2.28.2
- beautifulsoup4==4.11.2

## OpenAIのAPI キー

親切な人が、APIキーの取得までの手順を書いてくださっているので、それを参考に取得します。

https://qiita.com/kotattsu3/items/b936a65d173a5d39dad0

終わったら、OpenAIのpipモジュールを取得しましょう。

```bash
pip install openai==0.27.0
```

今回使用する、`gpt-3.5-turbo` (ChatGPTのエンジン)は、0.27.0以上でないと使えないので0.27.0以上を取得しましょう。

OpenAIのAPIキーを取得する際にクレジットカードが必要になります。使いすぎると、そこそこお高いので、注意して使いましょう。

## Google Custom Search API キー

こちらも、親切な人がAPIキーと、Custom Search Engine IDの取得方法を書いてくださっているので参考にさせていただきましょう。

https://qiita.com/zak_y/items/42ca0f1ea14f7046108c

終わったら、Google APIのPython クライアントのpipモジュールを取得しましょう。

```bash
pip install google-api-python-client==2.80.0
```

ちなみに、Google Custom Search APIを使わずに、Googleをスクレイピングすると、利用規約違反になるらしいです。怖かったので素直に従っておきました。

## その他のモジュール

他のスクレイピングに使用するモジュールもインストールしてしまいます。

```bash
pip install requests beautifulsoup4
```

requestsはPythonでHTTP通信するためのモジュール、beautifulsoup4は、HTMLのパースに使用します。

# コーディング

まずは初期化とAPIキーのセットです。

```python
OPEN_AI_KEY = "ここにOpenAIのAPIキーをいれます"
GOOGLE_API_KEY = "ここにGoogle Custom SearchのAPIキーをいれます"
CUSTOM_SEARCH_ENGINE_ID = "ここにGoogle Custom Search EngineのIDをいれます"

openai.api_key = OPEN_AI_KEY
```
上のコードのキーの部分は、前提条件で取得した各種キーを入れましょう。

ユーザーからの問いは、前回記事と同様のチェンソーマンにします。

```python
query = "チェンソーマンのアニメ化はいつされた？"
```

## 最初の問い (Prompt)

では、早速、最初の問いをコーディングしていきます。

```python
template = """
次の入力文から検索すべきキーワードを3つ出し出力文に従って出力しなさい。

# 出力文
[キーワード], [キーワード], [キーワード]

# 入力文
{query}
"""
prompt = template.format(query=query)

# 検索キーワードをどうしたらよいかChatGPTに聞く
completion = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=[
    {"role": "user", "content": prompt}])
answer = completion.choices[0].message.content
print("1つめの問いに対する答え:\n" + answer)
```

ひな形(template)に質問(query)を流し込み、質問(prompt)を完成させ、openaiのAPIに、その質問を入れて、問い合わせをします。結果は、completionの中に返ってきます。

これが、1つめの問いに対する答え

```python
1つめの問いに対する答え:
チェンソーマン, アニメ化, いつ
```

ちゃんとカンマ区切りで答えてくれました。いい感じです。

## 2つめの問い(Prompt)

2つめの問いに行く前に、調べるべきWebページを検索して見つけましょう。

```python
# ググる
keywords = answer.split(",")
service = build("customsearch", "v1", developerKey=GOOGLE_API_KEY)
result = service.cse().list(
     q= " ".join(keywords),
     cx=CUSTOM_SEARCH_ENGINE_ID,
     lr='lang_ja', # 日本語で
     num=3, # とりあえず 3 ウェブサイト
     start=1 # 検索結果1ページ目
     ).execute()
```

1つめの結果からキーワードを抽出し、Google Custom Search APIを使用して、Google検索を行います。とりあえず、3ページぐらい取得してみました。

その後、各ページごとにスクレイピングして、bodyタグ内の文字列を集めます。

```python
for item in result["items"]:
    url = item["link"]

    # ヒットしたURLをスクレイピングします
    response = requests.get(url)
    soup = BeautifulSoup(response.text, "html.parser")
    body = soup.body # body要素の取得
    text = body.get_text() # body要素内のテキストを抜き出す
```

取れた文字列をコンテキストとして渡して、ChatGPTに問い合わせます。

```python
    template = """
    次のコンテキストを読み取って、入力文に関する情報を箇条書きで3つ書いてください。

    # 入力文
    {query}

    # 出力文
    - [情報]
    - [情報]
    - [情報]

    # コンテキスト
    {context}
    """
    prompt = template.format(context=text[:2000], query=query)

    # スクレイピングした情報を与えて関係ある情報を集める
    completion = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
        {"role": "user", "content": prompt}])
    answer = completion.choices[0].message.content
    print("2つめの問いに対する答え:\n" + answer)
    answers.append(answer)
```

こんな感じの答えが返ってきました。

```python
2つめの問いに対する答え:
- アニメ化された
- 放送開始時期は2022年10月11日
- 最速配信はPrime Videoにて25:00より実施

2つめの問いに対する答え:
- 入力文は「チェンソーマンのアニメ化はいつされた？」である。
- アニメ「チェンソーマン」1期は2022年10月〜12月に放送されたことがわかる。
- 現時点でアニメ「チェンソーマン」2期の制作は未定だが、原作コミックのストーリーが残っていることから2期が制作される可能性は高いとされている。

2つめの問いに対する答え:
- 入力文はチェンソーマンに関する情報を問い合わせている。
- 入力文の内容はアニメ化に関する情報を求めている。
- 入力文に詳しい回答が得られなかった可能性がある。
```

質問文に対する答えが入っていますね。

## 3つ目の問い(Prompt)

最後にこれまでの調査結果を使って、質問の答えを完成させます。

```python
template = """
入力文にたいし、コンテキストから推測される回答を答えなさい。

# コンテキスト
{context}

# 入力文
{query}
"""
prompt = template.format(context="\n".join(answers), query=query)

# 集めた情報をコンテキストとして渡して最終の答えを出させる。
completion = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=[
    {"role": "user", "content": prompt}])
answer = completion.choices[0].message.content
print("最終の答え:\n" + answer)
```

2つめの問いで取得したanswersを連結してコンテキストとして、promptに埋め込みます。
返ってきた答えがこちら。

```python
最終の答え:
2. アニメ「チェンソーマン」の放送開始時期は2022年10月11日である。
また、最速配信はPrime Videoにて25:00より実施される。
```

いいですね。

想定されていた答えが出ました。一部、「2.」とかよくわからないものがついていたりしますが、丁寧にやれば除去できるでしょう。今回は複雑になってしまうのでこの辺にしておきます。

# まとめ

いかがだったでしょうか？
プログラム的に質問を連鎖させることでChain of Thought感が出てきましたね。

今回は、前回紹介したChain of Thoughtを、OpenAIのAPIを使ってChatGPTを使用し、Google Custom Search APIを使用し最新情報をChatGPTに教えながら、正しい回答を出すことができました。

とはいえ・・・・・プログラム書くのめんどくさくね？って思ったあなた。いい勘しています。
次回は、これらをフレームワークしてきたライブラリをご紹介します。