小説の場面をGPTに書かせる試み 8/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_to_global_log(self, message):
global global_log
global_log[len(global_log)] = message
def main(self, catalyst_action_object, max_iterations=10):
global global_log
next_action = catalyst_action_object
iteretion_count = 0
while len(self.schedule_stack) > 0:
iteretion_count += 1
# check if there is a character in the actants list
character_count = 0
for actant in self.actants:
if type(actant) == Character:
character_count += 1
if character_count == 0:
print("There is no character in the actants list.")
break
action_candidates = self.aggregate_action_candidates(next_action)
next_action = self.determine_next_action(action_candidates)["max_scored_action"]
self.add_to_global_log(str(next_action))
if iteretion_count > max_iterations:
print(f"The iteration count exceeded the maximum iteration count: {max_iterations}")
break
print("finished")
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:
actant.evaluate_action_and_reassess_need(action_object)
action_candidates.append(actant.consider_next_actions(self.actants))
print(f"action_candidates: {action_candidates}")
return action_candidates
def determine_next_action(self, action_candidates):
# Consequenseとsheduled_situationが最もマッチするものを選択する。
# sheduled_situationを取得する。
sheduled_situation = self.schedule_stack[-1]
# 点数をつける。点数が付けられていない場合は、繰り返す。ここで、どのactionが取られるかが決定する。
max_score = 0
max_scored_action = None
print(f"action_candidates: {action_candidates}")
for item in action_candidates:
for key in list(item.keys()):
item[key] = self.calculate_score(sheduled_situation, item[key])
if item[key].score >= max_score:
max_score = item[key].score
max_scored_action = item[key]
# 点数が最も高いものが9点未満の場合、スタックはそのままにする。9点の場合、スタックからpopする。
if max_score == 9:
self.pop_schedule_stack()
# max_scored_actionによって削除・退場するactantの処理を行う。
removed_actants = self.remove_actant_with_action_object(max_scored_action)
print(f"removed_actants: {removed_actants}")
# consequenceによって追加されたactantを追加する。
added_actants = self.add_actant_with_action_object(max_scored_action)
print(f"added_actants: {added_actants}")
print(f"max_scored_action: {str(max_scored_action)}")
return {"max_scored_action": max_scored_action, "removed_actants": removed_actants, "added_actants": added_actants}
def remove_actant_with_action_object(self, action_object):
consequence = action_object.consequence
system_content = """
You will be presented with a list of elements of actants, and the next consequence of the scene.
Based on that information, list out people or things that have exited, been destroyed, died, or no longer function.
The listed actants should be separated by commas (,). Also, the output should only be a comma-separated string of actant names, do not output any other strings.
"""
user_content_example_1 = """
actants: Bocchi,Guitar,room
consequence: Bocchi awkwardly bowed and then quickly left the room.
"""
assistant_content_example_1 = """
Bocchi
"""
user_content_example_2 = """
actants: Frogman,cigarette
consequence: Surprisingly, Frogman ate the cigarette.
"""
assistant_content_example_2 = """
cigarette
"""
user_content_example_3 = """
actants: Dolton,Anna,village
consequence: A helicopter and a passenger plane crashed into the village. The village chief, Dolton, was caught in it and lost his life. Anna, the wife of the village chief, was terrified and fled.
"""
assistant_content_example_3 = """
Dolton,Anna
"""
actants_names = ",".join([str(actant) for actant in self.actants])
user_content = f"""
actants: {actants_names}
consequence: {consequence}
"""
system_contents = [system_content]
user_contents = [user_content_example_1, user_content_example_2, user_content_example_3, user_content]
assistant_contents = [assistant_content_example_1, assistant_content_example_2, assistant_content_example_3]
actants_to_remove = gpt_response_from_lists(system_content_list=system_contents, user_content_list=user_contents, assistant_content_list=assistant_contents).content.split(",")
for actant in actants_to_remove:
actant = actant.strip().strip("\"").strip("\n")
self.remove_actant(actant)
print(f"actants_to_remove: {actants_to_remove}")
return actants_to_remove
def add_actant_with_action_object(self, action_object):
consequence = action_object.consequence
system_content = """
You will be presented with a list of elements of actants, and the next consequence of the scene.
Based on that information, list out people or things that have newly entered, been created, or have begun to function.
Then, determine the actant has will or not and list them in json format.
The actant that is already in the actants list may not be included in the list, but it is not bad to include it.
output example: {"Alice" : {"has_will": true}, "Bycycle" : {"has_will": false}, "cat" : {"has_will": true}, "Doraemon" : {"has_will": true}}
"""
user_content_example_1 = """
actants: Bocchi,room
consequence: Bocchi awkwardly bowed and then quickly left the room, and a guitar was left behind.
"""
assistant_content_example_1 = """
{"Bocchi's guitar" : {"has_will": false}}'
"""
user_content_example_2 = """
actants: Laboratory
consequence: Suddenly, Frogman advented and vomited Spiderman and a cigarette.
"""
assistant_content_example_2 = """
{"Frogman" : {"has_will": true}, "Spiderman" : {"has_will": true}, "cigarette" : {"has_will": false}}
"""
user_content_example_3 = """
actants: Dolton,Anna,village
consequence: A helicopter and a passenger plane crashed into the village. The village chief, Dolton, was caught in it and lost his life. Anna, the wife of the village chief, was terrified and fled.
"""
assistant_content_example_3 = """
{"helicopter" : {"has_will": false}, "passenger plane" : {"has_will": false}}
"""
actants_names = ",".join([str(actant) for actant in self.actants])
user_content = f"""
actants: {actants_names}
consequence: {consequence}
"""
system_contents = [system_content]
user_contents = [user_content_example_1, user_content_example_2, user_content_example_3, user_content]
assistant_contents = [assistant_content_example_1, assistant_content_example_2, assistant_content_example_3]
success = False
trial_count = 0
while not success and trial_count < 5:
trial_count += 1
actants_to_add = gpt_response_from_lists(system_content_list=system_contents, user_content_list=user_contents, assistant_content_list=assistant_contents).content
if is_valid_json(actants_to_add):
success = True
if trial_count >= 5:
print("Failed to get valid json from GPT-3.")
return
actants_to_add_json = json.loads(actants_to_add)
existing_actants_names = [str(actant) for actant in self.actants]
added_actants = []
for key in list(actants_to_add_json.keys()):
if key in existing_actants_names:
continue
if actants_to_add_json[key]["has_will"] == False :
self.add_actant(key)
added_actants.append(key)
else:
new_character = self.create_character(name = key, first_log = consequence)
self.add_actant(new_character)
added_actants.append(new_character)
print(f"actants_to_add: {actants_to_add}")
return added_actants
def create_character(self, name, first_log = None):
system_content = """
You will be provided with a character's name (name), the record of the story so far (global_log), and the first record (first_log) that character holds in this story. The first_log also represents the most recent event for that character.
Based on this information, please determine the character's personality, goals, and current desires.
The output should be in JSON format.
Output example: {'name': 'Alice', 'personality': 'kind', 'goal': 'to become a better person', 'current_need': 'to feel loved'}
"""
user_content_example_1 = """
name: Alice
global_log: {"1": {"action": "Seiji saw a girl attempting suicide and falling into a river", "consequence": "the girl fell into the river"},
"2": {"action": "Alice was saved by Seiji.", "consequence": "Alice was saved by Seiji."},
"3": {"Seiji's thought": "What a beautiful girl she is! Why did she fall into the river?"}}
first_log: Alice was saved by Seiji.
"""
assistant_content_example_1 = """
{'name': 'Alice', 'personality': 'grateful', 'goal': 'to repay Seiji', 'current_need': 'to understand her own feelings'}
"""
user_content = f"""
name:{name}
global_log: {str(global_log)}
first_log: {first_log}
"""
system_contents = [system_content]
user_contents = [user_content_example_1, user_content]
assistant_contents = [assistant_content_example_1]
success = False
trial_count = 0
new_character = None
while not success and trial_count < 5:
trial_count += 1
character_content = gpt_response_from_lists(system_content_list=system_contents, user_content_list=user_contents, assistant_content_list=assistant_contents).content
if is_valid_json(character_content):
character_content_json = json.loads(character_content)
# check if character_content_json has all the keys
if "name" in character_content_json and "personality" in character_content_json and "goal" in character_content_json and "current_need" in character_content_json:
new_character = Character(name = character_content_json["name"],
personality = character_content_json["personality"],
goal = character_content_json["goal"],
current_need = character_content_json["current_need"],
log = [first_log])
success = True
if trial_count >= 5:
print("Failed to get valid json from GPT-3.")
return
print(f"new_character: {new_character}")
return new_character
def calculate_score(self, scheduled_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 = scheduled_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
実行例
DragonLord = Character("Dragon Lord", "Brutal", "Be the emperor of the entire world", "kill heroes of the world", ["I am the Dragon Lord. I am the emperor of the entire world. I am going to kill heroes of the world."])
PrincessAurora = Character("Princess Aurora", "beautiful, cunning and evil", "After Dragon Lord conquers the world, she will kill Dragon Lord and become the new emperor of the world.", "Help Dragon Lord to conquer the world", ["I am Princess Aurora. I am beautiful, cunning and evil. After Dragon Lord conquers the world, I will kill Dragon Lord and become the new emperor of the world."])
PresidentTramp = Character("President Tramp", "selfish", "To make America great again", "Save the world", ["I am President Tramp. I am selfish. I want to make America great again."])
action_object_instance = Action_Object("Princess Aurora enchanted Dragon Lord", "Princess Aurora", "Dragon Load became bigger")
game_master = Game_Master([DragonLord, PrincessAurora, PresidentTramp], ["Princess Aurora became the queen of the world", "Dragon Load destroyed Tramp's army"],)
game_master.main(action_object_instance, max_iterations=4)
端的に言って、悪い。物語が同じ場所を足踏みし続ける。
また、どの文が誰のものかがうまく表現されていない。
問題点
やはり、登場人物がゲームマスターの意思に沿う行動を提案しないと、いつまでも物語が進展しない。
ゲームマスターの意思に沿う行動をするか、ゲームマスター側を柔軟にするかの必要がある。
ここから先は
0字
¥ 334
この記事が気に入ったらサポートをしてみませんか?