見出し画像

ChatGPTで「伺か」の掛け合いを生成する(5)・掛け合いを繰り広げる

目的

ChatGPT APIで掛け合いの台詞を生成し、「伺か」で表示します。
掛け合いの制御をします。

外部設計

この辺になります。
   応答に対する質問台詞生成
   質問に対する説明台詞生成
    →この2つを複数回繰り返す
    →オウム返しにならないように、話題を少しずつずらす
   〆の台詞生成
  SSTP用のフォーマットに変換

基本的な考え方

5種類の会話と2段階のChatGTPとのやり取り、それらを制御するコントローラで構成します。
■会話の種類
 会話を始めさせるためのネタを振る
 会話を始める(1番目の会話
 会話を続ける((2*n) + 1番目の会話、 話題に対して質問する
 会話の続きの応答する((2*n) + 2番目の会話、質問に対して回答する
 〆の言葉
■ChatGTPとのやり取り
 会話の内容を決める
 決めた会話の口調にキャラクター性を持たせる。

実装状況

コントローラ用クラス

    def conversations_loop(self, tmp_thema ,is_debug = False):
        LogControl.print_doing_info(is_debug)        

        personality_list = [PersonalityBase7.PersonalityBase7(0), PersonalityBase7.PersonalityBase7(1)]

        for j, i in enumerate(self.talker_list):
            # if is_debug:
            #     if j != target_cycle & target_cycle > 0:
            #         #デバッグモードでは特定のループのみ実施する、かもしれない
            #         continue
            # convert_tone = ''
            if  j == 0:
                # 会話のきっかけを振る
                perosnality_id, attitude_id, chatResult, surface_id = personality_list[i].start_how(self.first_thema, tmp_thema, self.element_dict, self.continuation_dict, self.now_timezone, self.callChatEvaluation)
            elif j == len(self.talker_list) -1 : #最後の会話をする
                perosnality_id, attitude_id, chatResult, surface_id = personality_list[i].cloase_talk(self.first_thema, tmp_thema, self.element_dict, self.continuation_dict, self.now_timezone, self.callChatEvaluation)
            elif  j == 1:
                # 会話を始める(1番目の会話
                perosnality_id, attitude_id, chatResult, surface_id = personality_list[i].first_talk(self.first_thema, tmp_thema, self.element_dict, self.continuation_dict, self.now_timezone, self.callChatEvaluation)
            elif j % 2 == 0:
                # 会話を続ける((2*n) + 1番目の会話
                perosnality_id, attitude_id, chatResult, surface_id = personality_list[i].response_talk(self.first_thema, tmp_thema, self.element_dict, self.continuation_dict, self.now_timezone, self.callChatEvaluation)
            else:
                # 会話の続きの応答する((2*n) + 2番目の会話
                perosnality_id, attitude_id, chatResult, surface_id = personality_list[i].answer_talk(self.first_thema, tmp_thema, self.element_dict, self.continuation_dict, self.now_timezone, self.callChatEvaluation)

            if chatResult == '':
                LogControl.print_doing_info('会話が成り立たなかったので終了')       
                break

            #今後を考えて、回答から名詞を抽出してリストに格納して置く
            s = self.callChat(-1, -1, "あなたは言語の専門家です。12文節以内で答えてください。", "次の文章から名詞を抽出してください。{}".format(chatResult))
            self.element_dict.update_element_dict(s, 0)  #単語を取得

            if perosnality_id >= 0 and perosnality_id <= len(self.player_list):
                t = [perosnality_id, attitude_id, chatResult, surface_id]
                #SSTPトークに変換する
                self.sstp_msg_buff.append(self.convert_talk_sstp2([t]))
                tmp_thema = chatResult

        self.continuation_dict.print_dict()
        self.element_dict.print_dict()

        return 

ここで注意すべきは、キャラクター毎にインスタンスが生成されている(personality_list[i])ということです。
例えば、first_talkを使用するキャラクターと、response_talkを使用するキャラクターは重なりません。

会話種類ごとの関数

■会話を始めさせるためのネタを振る
今日のイベントをもとに最初の台詞を作成します。

    def start_how(self, first_thema, tmp_thema, element_dict ,continuation_dict, time_zone , method_call_cahtGPT = None):
        LogControl.print_doing_info()        
        self.talk_cycle[0] = self.talk_cycle[0] +1 
        s = '今日は{}ですね。'.format(first_thema)
        #口調のキャラ付け
        s = self.convert_tone(method_call_cahtGPT, s)
        return self.perosnality_id, self.attitude_id, s, SURFACE_LIST[self.perosnality_id][self.attitude_id]

■会話を始める(1番目の会話
 今日のイベントについて説明します。

    def first_talk(self, first_thema, tmp_thema, element_dict ,continuation_dict, time_zone , method_call_cahtGPT = None):
        LogControl.print_doing_info()        
        self.talk_cycle[1] = self.talk_cycle[1] +1 
        chat_request = "あなたは{}に詳しいです。{}。{}について{}説明してください。".format(first_thema
            , self.talk_style
            , tmp_thema
            , ATTITUDE_LIST[self.attitude_id]
        )
        
        s = method_call_cahtGPT(self.situation, chat_request)
        #口調のキャラ付け
        s = self.convert_tone(method_call_cahtGPT, s)

        #会話履歴に登録(会話履歴はキャラごとに依存する)
        self.insert_chat_history(chat_request, s)

        return self.perosnality_id, self.attitude_id, s, SURFACE_LIST[self.perosnality_id][self.attitude_id]

■会話を続ける((2*n) + 1番目の会話、 話題に対して質問する
 発言数がN回目かにより、話題を切り替えて質問しようとします。
 1会話で複数回呼び出されます。

    def response_talk(self, first_thema, tmp_thema, element_dict, continuation_dict, time_zone , method_call_cahtGPT = None):
        LogControl.print_doing_info()        

        self.talk_cycle[2] = self.talk_cycle[2] +1 
        now_topic_cycle = self.talk_cycle[2] % 3
        chat_request =''

        tmp_thema = select_half_thema(tmp_thema)
        detail_word = first_thema

        #会話の回数によって処理を分ける
        if self.talk_cycle[2] == 1 :
#        if True:
            #最初はそのまま
            detail_word = first_thema
            None
        elif    now_topic_cycle == 1 :
            tmp_thema2 = method_call_cahtGPT('あなたは優秀な解説員です。次の文章を一言で言い表してください', tmp_thema)
            print('{}→{}'.format(tmp_thema, tmp_thema2))
            if tmp_thema2.endswith("。"):
                tmp_thema2 = tmp_thema2[:-1]
            tmp_thema = '{}と{}の関係'.format(first_thema,tmp_thema2)
        elif    now_topic_cycle == 2 :
            #以前の会話で使った単語があるかどうか
            tmp_words = element_dict.update_element_dict('', 5)  #単語を取得
            tmp_words = tmp_words.replace('、', 'と', 999) 
            if len(tmp_words) > 0:
                tmp_thema = '{}と{}'.format(tmp_words, tmp_thema)
            chat_request = "{}から連想される地名を1つ挙げて、一言で言い表してください。回答は地名だけを答えてください。".format(tmp_thema)
            s = method_call_cahtGPT('あなたは優秀な解説員です。', chat_request)
            print(s)   
            # リトアニア - ヴィリニュス というような回答が来るので、先頭を取得
            detail_word = get_itemization_first(s)

            s = re.split(r'[。!:、?]', tmp_thema)
            # 一番長い文字列を取得
            longest_string = max(s, key=len)

            tmp_thema = detail_word + 'と' + longest_string
            print(tmp_thema)   

        else  :
            tmp_words = element_dict.update_element_dict('', 3)  #単語を取得
            tmp_words = tmp_words.replace('、', 'と', 999) 
            if len(tmp_words) > 0:
                tmp_thema = '{}と{}'.format(tmp_words, tmp_thema)

            chat_request = "{}から連想される料理か食料を1つ挙げて、一言で言い表してください。回答は料理名だけを答えてください。".format(tmp_thema)
            s = method_call_cahtGPT('あなたは料理の専門家です。', chat_request)
            detail_word = get_itemization_first(s)

            s = re.split(r'[。!:、?]', tmp_thema)
            # 一番長い文字列を取得
            longest_string = max(s, key=len)

            tmp_thema = detail_word + 'と' + longest_string
            print(tmp_thema)   
 

        chat_request = "{}あなたは{}に詳しいです。次の説明に{}質問してください。{}ですが、{}".format(self.talk_style
            , detail_word                                                                       
            , ATTITUDE_LIST[self.attitude_id]
            , time_zone
            , tmp_thema
        )
        print(chat_request)
        s = method_call_cahtGPT(self.situation, chat_request, minimum_clause = 4, chatHistory = self.talk_chat_history)

        #口調のキャラ付け
        s = self.convert_tone(method_call_cahtGPT, s, time_zone)

        #会話履歴に登録(会話履歴はキャラごとに依存する)
        self.insert_chat_history(chat_request, s)

        return self.perosnality_id, self.attitude_id, s, SURFACE_LIST[self.perosnality_id][self.attitude_id]

■会話の続きの応答する((2*n) + 2番目の会話、質問に対して回答する
 こちらでは、シンプルにChatGTPに話題を変えさせようとします。
 1会話で複数回呼び出されます。

    def answer_talk(self, first_thema, tmp_thema, element_dict,continuation_dict, time_zone , method_call_cahtGPT = None):
        LogControl.print_doing_info()        

        self.talk_cycle[3] = self.talk_cycle[3] +1 
        chat_request =''

        #以前の会話で使った単語があるかどうか
        resuse_words = continuation_dict.update_element_dict('', 2, True)
        if len(resuse_words) > 0:
            #以前の会話の単語を蒸し返す
            if len(re.split(r'[。!:、?]', tmp_thema)) > 2:
                #直近の会話が長い時は、半分だけ使う
                tmp_thema = resuse_words + 'と' + select_half_thema(tmp_thema)
            else:
                tmp_thema = resuse_words + 'と' + tmp_thema

        #末尾が。か!なら?に変換する
        if tmp_thema.endswith("。"):
            tmp_thema = tmp_thema[:-1] + "?"
        elif tmp_thema.endswith("!"):
            tmp_thema = tmp_thema[:-1] + "?"
        elif tmp_thema.endswith("?"):
            tmp_thema = tmp_thema[:-1] + "?"
        else:
            tmp_thema = tmp_thema + "?"

        print(tmp_thema)   
        
        chat_request = "{}次の話題を考慮しつつ、{}関連する話題に切り替えてください。{}".format(self.talk_style
            , ATTITUDE_LIST[self.attitude_id]
            , tmp_thema
        )

        s = method_call_cahtGPT(self.situation, chat_request, chatHistory = self.talk_chat_history, maxhistory = 0)

        #口調のキャラ付け
        s = self.convert_tone(method_call_cahtGPT, s, time_zone)

        #会話履歴に登録
        self.insert_chat_history(chat_request, s)

        return self.perosnality_id, self.attitude_id, s, SURFACE_LIST[self.perosnality_id][self.attitude_id]

■〆の言葉
 話は終わりだ、飯に行くぞ。

    def cloase_talk(self, first_thema, tmp_thema, element_dict,continuation_dict, time_zone , method_call_cahtGPT = None):
        LogControl.print_doing_info()        
        self.talk_cycle[4] = self.talk_cycle[4] +1 

        chat_request =''

        tmp_words = element_dict.update_element_dict('', 3)  #単語を取得
        tmp_words = tmp_words.replace('、', 'と', 999) 

        s = re.split(r'[。!?]', tmp_thema)
        #末尾が。か!なら?に変換する
        if tmp_thema.endswith("。"):
            tmp_thema = tmp_thema[:-1] + "?"
        elif tmp_thema.endswith("!"):
            tmp_thema = tmp_thema[:-1] + "?"
        elif tmp_thema.endswith("?"):
            tmp_thema = tmp_thema[:-1] + "?"
        else:
            tmp_thema = tmp_thema + "?"

        chat_request = "{}から連想される料理か食料を1つ挙げて、一言で言い表してください。回答は料理名だけを答えてください。".format(tmp_thema)
        s = method_call_cahtGPT('あなたは料理の専門家です。', chat_request)

        tmp_words = s

        s = re.split(r'[。!:、?]', tmp_thema)
        # 一番長い文字列を取得
        longest_string = max(s, key=len)

        # tmp_thema = time_zone + 'の' + tmp_words + 'と' + longest_string
        tmp_thema = longest_string+ 'はともかく' + time_zone + 'なので' + tmp_words + 'を食べましょう'
        print(tmp_thema)   
        
        chat_request = "{}次のように話してください。{}".format(ATTITUDE_LIST[self.attitude_id]
            , tmp_thema
        )

        s = method_call_cahtGPT(self.situation, chat_request)
 
        #口調のキャラ付け
        s = self.convert_tone(method_call_cahtGPT, s)
       
        return self.perosnality_id, self.attitude_id, s, SURFACE_LIST[self.perosnality_id][self.attitude_id]

実行するとどうなるか

 こうなります。

■会話を始めさせるためのネタを振る 
	驚いたことですね!今日はナポレオンが……えっと、ロシアに侵攻したということですか?
■会話を始める(1番目の会話 
	そうなの?歴史って面白いよね。まぁ、話を聞いてみる価値はあるかな。ナポレオンがロシアに侵攻した話、確かに驚きよね。戦略的な挑戦を受け入れた側面もあったし。でも、その結果があまりよくなかったんだ
	けど、多くを学ぶ機会にもなったわ。
■会話を続ける((2*n) + 1番目の会話、 話題に対して質問する 
	なんていうか、本当に驚いちゃいますよね。なぜナポレオンがロシアに侵攻したんですか?その結果、彼にはどんな影響があったんでしょうか…?
■会話の続きの応答する((2*n) + 2番目の会話、質問に対して回答する 
	ナポレオンっていう人は、アクション映画のヒーローみたいな感じだったんじゃない?いろいろやり過ぎて足元すくわれちゃったのかしら。歴史って実際に体験しないとわからない部分もあるけど、ナポレオンの行
	動って今でも人々に影響を与えてるみたいね。さて、話変えて、最近見た映画の話でもしようかしら?
■会話を続ける((2*n) + 1番目の会話、 話題に対して質問する 
	モンテカルロの歴史や価値には、実際に体験しないと分からない部分があるのですが、その中で特に驚いたことは何ですか……?
■会話の続きの応答する((2*n) + 2番目の会話、質問に対して回答する 
	モンテカルロ、本当に歴史があって面白いよね。建物のデザインや内装、そして地中海の景色も本当に非常に魅力的だったよね。新しいカフェの話も、すごく楽しみにしてるからね。
■会話を続ける((2*n) + 1番目の会話、 話題に対して質問する 
	意外でございます…あの、ラタトゥイユとモンテカルロの建物のデザインや内装に関する情報に、実際はすごく驚かされたのでございます…わたくしの歓びを感じさせてくださいます。もう少し詳しく言うと、例えば
	、その建物の美しい彫刻や、内部の贅沢な装飾に、本当に驚嘆してしまいます…どうか許してください、これほど感情を表に出すことが変わり者だと思うのでございますが…
■会話の続きの応答する((2*n) + 2番目の会話、質問に対して回答する 
	なんだか面白い話ね!私も同じように驚いたことがあるわ。美しい彫刻には見とれてしまう魅力があるわよね。何て贅沢な装飾に囲まれた内部は、まるで夢の中にいるみたいでワクワクしちゃうわ!その建物のデザ
	インや内装に感じる喜びを共有できるって、やっぱり素敵なことよね。
■会話を続ける((2*n) + 1番目の会話、 話題に対して質問する 
	あれぇ、そんなことがあるんですか?ナポレオンのロシア侵攻と美しい建築の関係についての詳細、お聞きのこと、ちょっと私も気になりますね。
■〆の言葉
	いいね!ナポレオンのロシア侵攻や美しい建築の話は後回しにして、今日は午後だからこそボルシチを楽しもう!
	

比較実験

ブラウザからChatGTP 4oに同じ話題を投げてみます。
 ⇒今日はナポレオンがロシアに侵攻ですね。

ナポレオンがロシアに侵攻した日なんですね。これは歴史上非常に重要な出来事であり、ナポレオンの軍事戦略やその後の影響についても多くの議論があります。
ここから関連する話題に変えていきますね。
「ナポレオンのロシア侵攻は本当に歴史的な出来事ですよね。この戦いの結果がヨーロッパ全体に大きな影響を与えました。」
「歴史的な戦争といえば、第二次世界大戦も多くの国に影響を与えましたよね。特にノルマンディー上陸作戦は映画や本でもよく取り上げられています。」
「映画と言えば、戦争をテーマにした映画でおすすめの作品はありますか?私は『プライベート・ライアン』が印象に残っています。」
「『プライベート・ライアン』は確かに感動的な映画ですよね。戦争映画だけでなく、歴史を題材にした映画も興味深いです。例えば、『グラディエーター』など。」
「歴史映画も良いですが、ドキュメンタリーもまた違った視点で歴史を学べますよね。最近観たドキュメンタリーでおすすめのものはありますか?」
「ドキュメンタリーと言えば、自然をテーマにしたものも素晴らしいですよね。『プラネット・アース』シリーズは映像が美しくて、見ていると癒されます。」
「自然をテーマにした映像を見ると、実際に自然の中に行ってみたくなりますよね。最近ハイキングやキャンプに行ったことはありますか?」
「ハイキングやキャンプはリフレッシュできますよね。特に秋の季節は紅葉が綺麗で、自然の中で過ごすのが楽しいです。」
「紅葉を見に行くときは、温泉地も一緒に訪れるとさらに楽しめますよね。最近行った温泉でおすすめの場所はありますか?」
「温泉と言えば、温泉街で食べる地元の料理も楽しみの一つですよね。温泉卵や新鮮な魚料理など、美味しいものがたくさんあります。」
このように、話題を少しずつ関連させながら変えていくことで、自然な流れで会話を続けることができます。

うーん、自然ですね。細かいコード書かなくてもこっちでいいんじゃないかとも思いますが、「伺か」に渡すフォーマット変換を考えると、形式が一定しないのはちょっと……

今後の課題

 内容の正確性は全く要求されていないアプリなので、
・話題の切り替えをスムーズにする
・キャラクター毎の口調をそれっぽくする
の2つが重要点ですね。
 その次が
・キャラクター毎の仮想スケジュールに基づいた反応をさせる
  学生キャラに期末スケジュールのタイミングで歴史の話題を振ると反応が違うとか

この記事が気に入ったらサポートをしてみませんか?