【python3】GPT-3.5 APIを、本家サイト風に使う

## 背景
- chatGPTを使って業務でラクしたり、色々実験したりしたいが、実行に時間がかかるし、やりとりが学習に使われる可能性があり機密情報に気を使わないといけない、サイトに訪問するのも面倒臭い(たまに繋がらないし)
- gpt-3.5-turbo のAPIが公開されたので、これを使えばchatGPTをCLI上でも使える。しかしこれだけでは色々問題があった。具体的には
    - いちいちHTTPリクエスト書くのは面倒だし、実験の速度が重要なので、API実行速度以外の障壁をなるべく無くしたい
    - 出力をmarkdown記法としてレンダリングしてほしい
    - 過去のやり取りを読み込んだり、スレッドを複数作成して並行して話をしたり、気に入らない回答の再生成をしたり、本家サイトと同様の機能が欲しい。リアルタイム出力は遅くなるならいらない。
- そこで、"pythonなら書けるし、きっとjupyterなら出力をmarkdownとしてレンダリングしてくれるだろう"と思い至り、「ぼくだけのChat-GPT環境」を作ってみることにした。

## 前提
筆者はM1MacBookを使っており、バージョンはMonterey(12.6.2)です。
python3が最初から使える状態でスタートします

## APIKeyの発行
まずはAPIKeyの発行です。APIKeyは、 OpenAIにログインの後、View APIKeyページのCreate new secret keyというボタンから発行することができます。(発行したキーを保管しておきましょう)

## ライブラリのインストール
pythonのパッケージ管理ツールであるpipを使って、必要なライブラリをインストールします

```bash
$ python3 -m pip install openai
$ python3 -m pip install jupyterlab
```
openaiは、その名の通りopenAIのAPIを扱うためのpythonライブラリで
jupyterlabは、ブラウザで動くpythonの開発環境です。
（IPythonをつかってmarkdownを扱うため、SpyderでもPyCharmでも、VSCodeでも、IPython環境さえあれば問題ないです。）

## jupyterlabの起動
```bash
python3 -m jupyterlab -port 8888
```
これでおそらくブラウザが立ち上がり
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/625390/a148e073-b226-c369-535a-c3f3b7b36471.png)
こんな感じの画面になると思います。

ここで、 まずOtherのPython Fileをクリックして、 gptAPIを使うための自作モジュールを作ります

## gpt.pyの作成

```python
import openai
from IPython.display import Markdown, display
openai.api_key = "apikey"  # ここに前工程で作成したAPIKeyをいれる

def role_content(content, role="user"):
    return {"role": role, "content": content}


class Thread:
    content_tree = []
    latest_response = None
    # APIのstopパラメータに入れても良いのだが、出力が途中で切れるのが悲しいので、プロンプトにしてみた。
    system_prompt = role_content("質問には最長でも２000文字程度で回答してください","system")
    max_depth = 10


    def set_system_prompt(self, content):
        self.system_prompt = role_content(content, "system")
    

    def question(self, content=None):

        if self.latest_response:
            self.content_tree.append(self.latest_response)
            self.latest_response = None
        if content:
            self.content_tree.append(role_content(content, "user"))
        if len(self.content_tree) > self.max_depth:
            self.content_tree = self.content_tree[-selfmax_depth:]

        response = openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=[self.system_prompt]+self.content_tree)

        self.latest_response = role_content(response['choices'][0]["message"]["content"], "assistant")
        display(Markdown(self.latest_response["content"]))
        return(self.latest_response["content"])


    def regenelate(self):

        self.latest_response = None
        response = openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=[self.system_prompt]+self.content_tree)
        self.latest_response = role_content(response['choices'][0]["message"]["content"], "assistant")
        display(Markdown(self.latest_response["content"]+"\n（再生成）"))

        return(self.latest_response["content"])


def q(content, prompt=None, thread=None):
    if not thread:
        thread = Thread()
    if prompt:
        thread.set_system_prompt(prompt)
    thread.question(content)
    return thread


def r(thread):
    thread.regenelate()

```
untitled.pyとしてファイルが作成されるはずなので、 これをgpt.pyなど分かりやすい名前にしておく

## いざ実行
これで下準備は全て終わり。 
### 使い方

上タブから、ランチャーを開き、一番上のNotebook(python3)をクリック

まず
```python
import gpt
```
で先ほど作ったモジュールを読み込み、
```python
t1 = gpt.q("質問文字列")
```
で出力される というのがメインの使い方です。 この続きを出力したいときは

```python
t1 = gpt.q("つづきの質問", thread=t1)
```
とでき、 またプロンプトを与えたい時は

```python
t1 = gpt.q("つづきの質問", prompt="あなたは哲学者です", thread=t1)
```
のような形で使えます。
この場合 `t1` がスレッドのインスタンスなので、 別のスレッドで会話を始めたい時は 


```python
t2 = gpt.q("別の質問")
```
とすれば、別スレッドが作成されるし、t1での最後のやり取りが気に入らなかったら
　
```python
gpt.r(t)
```
で再度出力を吐き出します
別スレッドは別notebookを作成して実行するのがグラフィカルにもよさそうですね。

### 実行例
こんな感じで、ちゃんとマークダウン出力をレンダリングできました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/625390/0e55899f-57e0-3b4d-33f2-1a9c33160dc4.png)


## さいごに
結構細かい操作ができるGPT環境ができて個人的には満足です。
やっていることはpythonコードの実行なので、pythonが苦痛なく使える人は、結構使いやすいんじゃないかなと思います。
また今回はサボりましたが、APIのパラメータ類ももっと入れられるはずなので、実験が進んだらそこらへんも調整しやすい仕組みを作ろうかと思います。