脱力駆動開発記

ゲームアプリを作るエンジニアの技術メモ

MENU

【ChatGPT/CloudFunctions】チャットの履歴を保持した状態で返答させる

前回の記事ではChatGPTのAPIを呼ぶ処理を書きました。

www.stmn.tech

ここでは1つの話しかけに対して1つの応答が返ってくる、という例を書きました。 実際はユーザーとAI間で何回かやりとりをしたいというケースが多いと思います。

ドキュメントのmessagesをみてみると

A list of messages describing the conversation so far.

とあります。

つまり過去の会話の配列を送る必要があるわけで、やりとりが多くなれば多くなるほど必要なトークン数が増えていくということになりますね。

前回の記事で書いたCloudFunctionsからChatGPTのAPIを呼ぶ箇所を変更していきます。

chat.ts

import superagent = require("superagent");
import functions from "firebase-functions";
import {defineSecret} from "firebase-functions/params";
import {Response, Request} from "express";
const apiKey = defineSecret('API_KEY');

export async function postChatCompletionApi(req:Request, res:Response) {
    try {
        const apiRequest = JSON.parse(req.body) as ChatCallApiRequest;
        const url = "https://api.openai.com/v1/chat/completions";
        let messages:Message[] = [];
        if (apiRequest.system_prompt !== undefined) {
            const systemPrompt:Message = {
                role: "system",
                content: apiRequest.system_prompt,
            };
            messages.push(systemPrompt);
        }
        if (apiRequest.user_prompt !== undefined) {
            const userPrompt:Message = {
                role: "user",
                content: apiRequest.user_prompt,
            };
            messages.push(userPrompt);
        }
        // 追加した部分
        if (apiRequest.messages != undefined) {
            messages = messages.concat(apiRequest.messages);
        }
        const request: ChatCompletionRequest = {
            model: apiRequest.model,
            messages: messages,
            temperature: 0.7, //適当
            max_tokens: 1200, //適当
        };
        const response =
            await superagent
                .post(url)
                .send(request)
                .set('Authorization', `Bearer ${apiKey.value()}`);
        console.log(response.body);
        res.status(200).send(response.body);
    } catch (e) {
        console.log(e);
    }
}

type ChatCallApiRequest = {
    system_prompt?:string,
    user_prompt?:string,
    messages?: Message[]
    model:string,
}

type ChatCompletionRequest = {
    model:string,
    messages:Message[],
    temperature:number,
    max_tokens?:number,
}
type Message = {
    role:string, // system,user,assistant
    content:string, // the contents of the message.
    name?:string, // Author
}

index.ts

import chat = require("./chat");

import {defineSecret} from "firebase-functions/params";
const apiKey = defineSecret('API_KEY');

const app = express();
app.use(express.json());
app.use(express.urlencoded({
    extended: true,
}));

app.post("/chat/call", async (req, res) => {
    await chat.postChatCompletionApi(req, res);
});

const api = functions
    .runWith({secrets: [apiKey]})
    .region("asia-northeast1").https.onRequest(app);

module.exports = {
    api,
};

これで、過去のやりとりを反映させた形で送ることができます。

{
  "model": "gpt-3.5-turbo-0301",
  "messages": [
    {
      "role": "system",
      "content": "語尾に必ず「ニャ」をつけて返信する"
    },
    {
      "role": "user",
      "content": "こんにちは"
    },
    {
      "role": "assistant",
      "content": "こんにちはニャ!"
    },
    {
      "role": "user",
      "content": "私が言ったことを繰り返してくださいね。おはよう!"
    },
    {
      "role": "assistant",
      "content": "おはようニャ!と言われましたニャ!"
    },
    {
      "role": "user",
      "content": "あなたが最初に言ったことをもう一度言ってください。"
    }
  ]
}

このようなリクエストを送ると、次のようなレスポンスが返ってきました。

{
    "usage": {
        "prompt_tokens": 118,
        "completion_tokens": 12,
        "total_tokens": 130
    },
    "choices": [
        {
            "message": {
                "role": "assistant",
                "content": "「こんにちはニャ!」と言いましたニャ!"
            },
            "finish_reason": "stop",
            "index": 0
        }
    ]
}
System

語尾に必ず「ニャ」をつけて返信する

User

こんにちは

AI

こんにちはニャ!

User

私が言ったことを繰り返してくださいね。おはよう!

AI

おはようニャ!と言われましたニャ!

User

あなたが最初に言ったことをもう一度言ってください。

ここまでが送った内容です。返信は

AI

「こんにちはニャ!」と言いましたニャ!

ということで、ちゃんと認識してくれています。

一旦以上です。