Discord で gpt-4

備忘

```python
# %%
import discord
import openai
import os
import tiktoken
import logging


def num_tokens_from_messages(messages, model="gpt-4-0314"):
    """Returns the number of tokens used by a list of messages.
    from https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
    """
    try:
        encoding = tiktoken.encoding_for_model(model)
    except KeyError:
        print("Warning: model not found. Using cl100k_base encoding.")
        encoding = tiktoken.get_encoding("cl100k_base")
    if model == "gpt-3.5-turbo":
        print("Warning: gpt-3.5-turbo may change over time. Returning num tokens assuming gpt-3.5-turbo-0301.")
        return num_tokens_from_messages(messages, model="gpt-3.5-turbo-0301")
    elif model == "gpt-4":
        print(
            "Warning: gpt-4 may change over time. Returning num tokens assuming gpt-4-0314.")
        return num_tokens_from_messages(messages, model="gpt-4-0314")
    elif model == "gpt-3.5-turbo-0301":
        # every message follows 
{role/name}\n{content}
\n
        tokens_per_message = 4
        tokens_per_name = -1  # if there's a name, the role is omitted
    elif model == "gpt-4-0314":
        tokens_per_message = 3
        tokens_per_name = 1
    else:
        raise NotImplementedError(
            f"""num_tokens_from_messages() is not implemented for model {model}. See https://github.com/openai/openai-python/blob/main/chatml.md for information on how messages are converted to tokens.""")
    num_tokens = 0
    for message in messages:
        num_tokens += tokens_per_message
        for key, value in message.items():
            num_tokens += len(encoding.encode(value))
            if key == "name":
                num_tokens += tokens_per_name
    num_tokens += 3  # every reply is primed with 
assistant

    return num_tokens


def cgpt(message_content, message_history):
    # 環境変数 OPENAI_API_KEY を取得して、 openai.api_key にセットする
    openai.api_key = os.environ["OPENAI_API_KEY"]

    # その他、パラメータを設定する
    MODEL_NAME = 'gpt-4'
    MAX_TOKENS = 1200  # 1回のリクエストで返ってくるトークン数の上限
    model_token_limit = {
        'gpt-4': 8192,
        'gpt-3.5-turbo': 4096,
    }

    # 入力を message_history に user として追加する
    message_history.append({"role": "user", "content": message_content})

    # message_history の token 数を数える
    num_tokens = num_tokens_from_messages(message_history)
    print(f"num_tokens before: {num_tokens}")

    # ここで num_tokens + max_tokens が model_token_limit[model_name] を超える場合は、
    # num_tokens が model_token_limit[model_name] を下回るまで、 message_history の先頭を削除する
    while num_tokens + MAX_TOKENS > model_token_limit[MODEL_NAME]:
        print(f"trimming message_history: {message_history[0]}")
        message_history = message_history[1:]
        num_tokens = num_tokens_from_messages(message_history)
        print(f"num_tokens after trimming: {num_tokens}")

    # OpenAI GPT に入力を渡す
    r = openai.ChatCompletion.create(
        model=MODEL_NAME,
        messages=message_history,
        temperature=0.9,
        max_tokens=MAX_TOKENS,
        top_p=1,
        frequency_penalty=0.5,
        presence_penalty=0.5,
    )

    # 返答を表示する
    res_text = r.choices[0].message.content

    # 返答をメッセージに追加する
    message_history.append({"role": "assistant", "content": res_text})

    # message_history の token 数を数える
    num_tokens = num_tokens_from_messages(message_history)
    print(f"num_tokens after: {num_tokens}")

    return res_text, message_history


class ChatGPTDiscord():
    def __init__(self) -> None:
        self.message_history = []

    def chatevent(self):
        # 環境変数 DISCORD_BOT_SIMPLE を取得して TOKEN にセットする
        TOKEN = os.environ["DISCORD_BOT_SIMPLE"]

        # インテントの生成
        intents = discord.Intents.default()
        intents.message_content = True

        # クライアントの生成
        client = discord.Client(intents=intents)

        # discordと接続した時に呼ばれる
        @client.event
        async def on_ready():
            print(f'We have logged in as {client.user}')

        # メッセージを受信した時に呼ばれる
        @client.event
        async def on_message(message):

            # 自分のメッセージを無効
            if message.author == client.user:
                return

            # メッセージを返す
            res_text, message_history = cgpt(
                message.content, self.message_history)
            self.message_history = message_history
            await message.channel.send(res_text)

        # クライアントの実行
        client.run(token=TOKEN, log_level=logging.ERROR)


c = ChatGPTDiscord()
c.chatevent()
```

point
* できるだけ長く文脈を保つように、 token の数をちゃんと計算するようにする
* system のところに「あなたは優秀な助手です」みたいなことを入れてたけど、 gpt-4 がすでにその使命を帯びていて意味ないのでやめた。