小説の場面をGPTに書かせる試み 5/100

登場人物間のすれ違いを実現したい

物語の面白さについての理論の一つに、「登場人物間のすれ違いが物語に面白さをうむのだ」とするものがあると思う。

「登場人物間のすれ違いが物語に面白さをうむのだ」とする理論の具体例
- アリストテレス『詩学』のアナグノリシス
- マリー=ロール ライアン『可能世界・人工知能・物語理論』の可能世界を使った物語価値の説明

これらの理論の内容は、大体はもう忘れてしまった。複数の人物が一つの対象について別々の事柄を思い描かせることで、物語にある種の面白さを供給できるのだと理解している。

小説を自作する際に、複数の登場人物の内面を考えながら場面を書くというのが、私は非常に苦手としている。どうにかその苦手な部分を、AIに肩代わりさせたい。

しかし、ChatGPTに「物語を書いてください」と要求すると、うまくいかない。複数の登場人物のすれ違い≒対立の物語が書かれてほしいのに、ChatGPTは複数の登場人物をすぐに和解させる。

これを避けるために、登場人物間に距離を設けたいと考えた。登場人物が互いに行動と会話を通してしかコミュニケーションを取ることができず、互いの本当の内面を知ることができないようにすれば、和解がすぐさま起こってしまうことが防げるのではないかと思った。

今回は、そういった振る舞いをする登場人物のクラスを作った。実装はGoogle Colabで行った。

実際のコード

OPENAI_API_KEY = "YOUR_API_KEY"
!pip install openai
!pip install langchain
import openai
import json
from langchain.chat_models import ChatOpenAI
from langchain.schema import AIMessage
from langchain.schema import HumanMessage
from langchain.schema import SystemMessage
from langchain import PromptTemplate


def is_valid_json(input_string):
    if input_string == None or input_string == "":
      return False

    try:
        json.loads(input_string)
        return True
    except:
        return False


def gpt_response_from_lists(system_content_list = [], user_content_list = [], assistant_content_list = [], max_tokens = None):
    chat = ChatOpenAI(model_name="gpt-3.5-turbo-16k", openai_api_key= OPENAI_API_KEY)

    if max_tokens != None:
      ChatOpenAI(model_name="gpt-3.5-turbo-16k", openai_api_key= OPENAI_API_KEY, max_tokens=max_tokens)
    else:
      ChatOpenAI(model_name="gpt-3.5-turbo-16k", openai_api_key= OPENAI_API_KEY)

    messages = []

    for system_content in system_content_list:
      messages.append(SystemMessage(content = system_content))

    list_length = max(len(assistant_content_list), len(user_content_list))

    for i in range(0, list_length):

      if i < len(user_content_list):
        messages.append(HumanMessage(content = user_content_list[i]))

      if i < len(assistant_content_list):
        messages.append(AIMessage(content = assistant_content_list[i]))

    response = chat(messages)

    return response
class character:
    initial_system_content_template = """
    I am trying to write a story. You will assist by playing the role of a character in this story.
    I will provide information about the character you are to portray as follows. From then on, please respond in character.

    Character Name: {name}
    Character Personality: {personality}
    Charecter Goal: {goal}
    Current Need: {current_need}
    Log: {log}
    """

    initial_system_content = ""
    current_need = ""
    name = ""
    personality = ""
    log = dict()
    
    def __init__(self, name = "", personality = "" , goal = "", current_need = "", log = [],):
        self.name = name
        self.personality = personality
        self.goal = goal
        self.current_need = current_need
        for i in range(len(log)):
            self.log[i] = log[i]

        self.refresh_initial_system_content()

    def refresh_initial_system_content(self):
        log = json.dumps(self.log)

        prompt_template = PromptTemplate(input_variables=["name", "personality", "goal", "current_need", "log"], template=self.initial_system_content_template)
        self.initial_system_content = prompt_template.format(name=self.name, personality=self.personality, goal=self.goal, current_need=self.current_need, log=log,)

    def set_current_need(self, current_need):
        self.current_need = current_need
        self.refresh_initial_system_content()
    
    def set_name(self, name):
        self.name = name
        self.refresh_initial_system_content()
    
    def set_personality(self, personality):
        self.personality = personality
        self.refresh_initial_system_content()

    def set_goal(self, goal):
        self.goal = goal
        self.refresh_initial_system_content()

    def add_to_log(self, message):
        self.log[len(self.log)] = message
        self.refresh_initial_system_content()

    def evaluate_action_and_reassess_need(self, action, who_took_action = "anonymous",  consequence = "unditermined"):
        # if action's type is not string, stringfy it
        if type(action) != str:
            action = str(action)

        log = json.dumps(self.log)

        user_content_template = """
        Given the following infomation, please infer the purpose of the action.
        Action: {action}
        Who took action: {who_took_action}
        Consequence: {consequence}

        The output should be a string that describes the intent of the action from your perspective.
        sample output: "I was beaten up by the police, and I assume that the police wanted to intimidate me."
        """
        
        prompt_template = PromptTemplate(input_variables=["action", "who_took_action", "consequence"], template=user_content_template)
        user_content = prompt_template.format(action=action, who_took_action=who_took_action, consequence=consequence,)
        inferred_purpose = gpt_response_from_lists(system_content_list=[self.initial_system_content], user_content_list=[user_content]).content
        self.add_to_log(f"{who_took_action}'s action: {action}")
        self.add_to_log(f"{self.name}'s thought: " + inferred_purpose)

        user_content_template = """
        Given the following information, suggest what your immediate need might now be. It is acceptable for the immediate need to be the same as the current need.
        Action: {action}
        Who took action: {who_took_action}
        Consequence: {consequence}

        The output should be a string that describes your immediate need.
        """
        
        prompt_template = PromptTemplate(input_variables=["action", "who_took_action", "consequence",], template=user_content_template)
        user_content = prompt_template.format(action=action, who_took_action=who_took_action, consequence=consequence,)
        new_need = gpt_response_from_lists(system_content_list=[self.initial_system_content], user_content_list=[user_content]).content
        old_need = self.current_need
        self.set_current_need(new_need)
        self.add_to_log(f"{self.name}'s need renewed: {new_need}")

        user_content_template = """
        Your current need is {new_need}. Your previous need was {old_need}.
        Tell me what you think the difference between the two needs is and why you did (not) change your need.
        """

        prompt_template = PromptTemplate(input_variables=["new_need", "old_need"], template=user_content_template)
        user_content = prompt_template.format(new_need=new_need, old_need=old_need)
        determination = gpt_response_from_lists(system_content_list=[self.initial_system_content], user_content_list=[user_content]).content
        self.add_to_log(f"{self.name}'s thought: " + determination)

        response = {"inferred_purpose": inferred_purpose, "new_need": new_need, "determination": determination,}

        return response

    def consider_next_actions(self, actants = []):
        actions = dict()

        if len(actants) == 0:
            actants.append(self.name)

        user_content_template = """
        Consider your next move in the current scene where there is {actant} involved.
        If this element aids you in achieving your goals or satisfies your needs, figure out a way to incorporate it into your action.
        But, if it hinders your goal or needs, think about a strategy to eliminate or neutralize it.
        Just bear in mind that the course of action you suggest should reflect your character and remain coherent with the events detailed in the log.

        The output should descrive and only describe your next action, and don't describe the consequence of the action.
        output example: "I will go to the police station and ask for the release of my friend."
        """
        
        for actant in actants:
            prompt_template = PromptTemplate(input_variables=["actant"], template=user_content_template)
            user_content = prompt_template.format(actant = actant)
            action = gpt_response_from_lists(system_content_list=[self.initial_system_content], user_content_list=[user_content], max_tokens=10).content
            actions[actant] = action

        return actions

各種クラス変数

・name: その人物の名前
・personality: その人物がどのような人物であるかの説明
・current_need: その人物の現在の欲求
・initial_system_content_template: GPTに送るプロンプトのシステム・コンテントの冒頭に記述されるべき内容のテンプレートを意図していた。結果的に、このテンプレートで作成されたものはシステム・コンテントの冒頭部分ではなくシステム・コンテントそのものになった。
 システム・コンテントは、その登場人物の内面を表現する。

evaluate_action_and_reassess_need()メソッド

他の登場人物が取った行動をもとに、その行動がどのような意図をもって行われたのかを、このメソッドで考える。実際の意図を知ることはできないので、すれ違いが発生する余地が生まれる。

consider_next_actions()メソッド

その場面に存在するものの配列をうけとり、その配列の要素ごとに、つぎにその人物がとる行動を提案する。
実際にどの行動がとられるかは、ゲームマスタが決定する。ゲームマスタについてはまだ作成していない。

実行例

melos = character("Melos", "Kind and braveful", "To save his friend", "To save his friend", ["Selinuntius, a friend of mine, has been accused of a crime and sentenced to death. I am going to Syracuse to try to save him."])
print(melos.initial_system_content)


I am trying to write a story. You will assist by playing the role of a character in this story.
I will provide information about the character you are to portray as follows. From then on, please respond in character.

Character Name: Melos
Character Personality: Kind and braveful
Charecter Goal: To save his friend
Current Need: To save his friend
Log: {"0": "Selinuntius, a friend of mine, has been accused of a crime and sentenced to death. I am going to Syracuse to try to save him."}

melos.evaluate_action_and_reassess_need("Selinuntius exploded suddenly", "Selinuntius, a Melos's friend", "Selinuntius died")
action_candidates = melos.consider_next_actions(["Ginger bread", "Dionysius, a tyrant of Syracuse", "Time Machine", "Master Sword"])
pprint.pprint(melos.log)


pprint.pprint(action_candidates)


まあ、そこそこ書けているのではないでしょうか。

次にすること

最終的には、以下のような流れを作りたい。

1) 登場人物Aが、行動の候補をリストアップする。
2) ゲームマスタが、その行動の候補のなかのどれが取られるか、その結果がどうなるかを判断する。判断は、物語が面白くなるかどうかによってくだされる。
3) 登場人物Bが、ゲームマスタから、登場人物Aの行動の結果を受け取る。
4) 登場人物Bの内面が変化する。
5) 登場人物Bが、行動の候補をリストアップする。
6) ゲームマスタが、その行動の候補のなかのどれが取られるか、その結果がどうなるかを判断する。判断は、物語が面白くなるかどうかによってくだされる。
7) 登場人物Aが、ゲームマスタから、登場人物Bの行動の結果を受け取る。
8) 登場人物Aの内面が変化する。
9) 1に戻る。

登場人物は、今回作った。次回はゲームマスタをつくる。

ここから先は

0字

¥ 300

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