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

OPENAI_API_KEY = "YOUR_KEY"
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 Action_Object:
    def __init__(self, action = "", who_took_action = "Anonymous", consequence = "Undetermined", score = 0.0):
        self.action = action
        self.who_took_action = who_took_action
        self.consequence = consequence
        self.score = score
    
    def __str__(self):
        return str({"action": self.action, "who_took_action": self.who_took_action, "consequence": self.consequence})
global_log = dict()

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 __str__(self):
        return self.name

    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_logs(self, message):
        global global_log
        self.log[len(self.log)] = message
        global_log[len(global_log)] = message
        self.refresh_initial_system_content()

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

    def evaluate_action_and_reassess_need(self, action_object):
        action = action_object.action
        who_took_action = action_object.who_took_action
        consequence = action_object.consequence

        # 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_self_log(f"{who_took_action}'s action: {action}.")
        self.add_to_self_log(f"Consequence of previous action: {consequence}.")
        self.add_to_logs(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_logs(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_logs(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)

        action_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.
        To determine whether the element is helpful or harmful, it might helpful to refer to 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."
        """

        consequence_user_content_template = """
        Consider the consequence of your action.
        To determine the consequence, it might helpful to refer to the log.
        output example: "I went to the police station and ask for the release of my friend, then I was arrested."

        Action you took: {action}
        """
        
        for actant in actants:
            # if actant is not string, stringfy it
            if type(actant) != str:
                actant = str(actant)

            # determine the action
            prompt_template = PromptTemplate(input_variables=["actant"], template=action_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

            # determine the consequence
            prompt_template = PromptTemplate(input_variables=["action"], template=consequence_user_content_template)
            user_content = prompt_template.format(action = action)
            consequence = gpt_response_from_lists(system_content_list=[self.initial_system_content], user_content_list=[user_content], max_tokens=10).content

            # store the action to the actions dictionary
            actions[actant] = Action_Object(action=action, who_took_action=self.name, consequence=consequence)

        return actions
class Game_Master:
    def __init__(self, actants = [], schedule_stack = [],):
        self.actants = actants
        self.schedule_stack = schedule_stack

    def add_actant(self, actant):
        self.actants.append(actant)

    def add_schedule_stack(self, situation):
        self.schedule_stack.append(situation)

    def remove_actant(self, name):
        for i in range(len(self.actants)):
            if str(self.actants[i]) == str(name)
                self.actants.pop(i)
                break
    
    def pop_schedule_stack(self, situation = None):
        if situation == None:
            return self.schedule_stack.pop()

        for i in range(len(self.schedule_stack)):
            if str(self.schedule_stack[i]) == str(situation):
                return self.schedule_stack.pop(i)

    def aggregate_action_candidates(self, action_object):
        action_candidates = []
        for actant in self.actants:
            if type(actant) == Character:
                action_candidates.append(actant.evaluate_action_and_reassess_need(action_object))

        return action_candidates


    def determine_next_action(self, action_candidates):
        # Consequenseとsheduled_situationが最もマッチするものを選択する。
        # sheduled_situationを取得する。
        sheduled_situation = self.schedule_stack[-1]

        # 点数をつける。点数が付けられていない場合は、繰り返す。
        max_score = 0
        max_scored_action = None
        for key in list(action_candidates.keys()):
            action_candidates[key] = self.calculate_score(sheduled_situation, action_candidates[key])

            if action_candidates[key].score >= max_score:
                max_score = action_candidates[key].score
                max_scored_action = action_candidates[key]

        # 点数が最も高いものが9点未満の場合、スタックはそのままにする。9点の場合、スタックからpopする。
        if max_score == 9:
            self.pop_schedule_stack()

        # consequenceによって破壊されたactantを削除する。
        # 未作成

        # consequenceによって追加されたactantを追加する。
        # 未作成

        return max_scored_action

    def calculate_score(self, sheduled_situation, action_candidate):
        system_content = """
        The information provided to you includes the scheduled_situation, action_candidate, and global_log.
        scheduled_situation: This is the situation that should be accomplished next in the narrative. It is typically provided as a string.
        action_candidate: This is a candidate for the next action to be taken in the narrative. It is given as a parsed string in JSON format.
            An example of an action_candidate: {"action": "This item represents the action taken.", "who_took_action": "This represents who took the action.", "consequence": "This roughs out the result of the action."}
        global_log: This is a record of the actions that have occurred in the narrative. It is given as a parsed string in JSON format.

        You need to consider the combination of the scheduled_situation and action_candidates and provide a score. If the scheduled_situation has been achieved, give a score of 9. If there has been no progress towards achieving the scheduled_situation, give a score of 0. The output must always be parsable as JSON.

        Output example: '{"matchness": "The scheduled_situation has been achieved. However, it is inconsistent with the global_log", "score": 5}'
        Output example: '{"matchness": "It is consistent with the global_log. However, the scheduled_situation has not been achieved.", "score": 5}'
        Output example: '{"matchness": "It is inconsistent with the global_log, and the scheduled_situation has not been achieved.", "score": 0}'
        Output example: '{"matchness": "It is inconsistent with the global_log, and the scheduled_situation has not been achieved. However, it seems likely to approach a situation where the scheduled_situation can be achieved.", "score": 7}'
        Output example: '{"matchness": "It is consistent with the global_log. In addition, the scheduled_situation has been achieved.", "score": 9}'
        """

        # 点数をつける。点数が付けられていない場合は、繰り返す。
        system_contents = [system_content]
        stringified_action_candidate = str(action_candidate)

        user_content_template = """
        sheduled_situation: {scheduled_situation}
        action_candidate: {action_candidate}
        global_log: {global_log}
        """

        user_content = user_content_template.format(scheduled_situation = sheduled_situation, action_candidate = stringified_action_candidate, global_log = str(global_log))
        
        has_result_valid_score = False
        score = None

        trial_count = 0

        while not has_result_valid_score and trial_count < 1:
            trial_count += 1
            print("trial_count: ", trial_count)

            result = gpt_response_from_lists(system_contents, [user_content]).content
            print("result: ", result)
            if is_valid_json(result):
                result = json.loads(result)
                print("type(result): ", type(result))

                #check if "score" is in result
                if "score" not in result:
                    continue

                #check if result["score"] can be interpreted as a integer
                try:
                    score = int(result["score"])
                    has_result_valid_score = True
                except:
                    continue

        action_candidate.score = score

        return action_candidate

    

使い方

Game_Masterのインスタンスに色々つっこんだうえでdetermine_next_actionをゴリゴリ回せば、global_logに物語がゴリゴリ溜まっていく・・・はず。

Game_Masterのインスタンスにつっこむ色々なもの:
actants:Characterのインスタンスか、場面に存在する事物の名称
schedule_stack: その場面で起きてほしい出来事

問題点

  • 登場人物がschedule_stackの最後の状態に合わない行動ばかりを提案すると、物語が永久に先に進まない。

  • determine_next_actionをゴリゴリ回す部分を書いていない

  • 物語の展開によってactantsの要素が追加・削除された場合に対応していない。

  • global_logに集められた文章を小説の形に記述し直す必要がある

ここから先は

0字

¥ 9,000

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