
CHATGPTに小説を書いてもらう。【編集会議付き】
こんにちはmakokonです。
ブラウザでCHATGPTを遊びで使っているパターンにこんなのがあります。
なにかおもしろいこと言って
XXXのダジャレを作って
YYYの俳句を作って
など、暇つぶしの創作系です。できが良いのも悪いのも合わせて、たくさん作ってくれるので、楽しいものです。
でも、「このお題で、短い小説を書いて」とお願いして、これならまずまずかというレベルの話が出てくることは殆どありません。
もちろんプロンプトの工夫とか、事前知識を与えるとかいろんな工夫もあるんでしょうけど、最大の原因は推敲しないことだと感じました。
一度小説を書いてそれっきりじゃなく、伝えたいことの明確化とか、ストーリーの整理とか、色々手直しして見たらちょっとはまともになるんじゃないかと思います。
コンセプト
今回の実現ストーリーです。
小説家に三題話を書いてもらう。
CHATGPT小説家とCHATGPT編集者を用意する。
二人に編集会議を数回開いてもらって、最初のアイデアを修正する。
今回の小説
今回のお題は、勇者、やる気なし、結果良ければ です。
出来上がった小説がこれです。他愛ないけどまあまともな文になっています。
アレンは、特別な力を持つ勇者として選ばれましたが、戦闘には全く興味がありませんでした。しかし、村人たちからの期待と信頼に応えるため、そして何より彼らを守るためには、自分が勇者として行動しなければならないと感じました。そのため、彼はなるべく戦闘を避け、モンスターとの交渉や実力を見せつけて逃げてもらうなど、結果良ければ全然OK、という新たな勇者としての道を選びました。
アレンの特別な力は、モンスターと会話ができるというものでした。これは幼い頃から持っていた力で、一度も使ったことのなかったこの力が、あるモンスターとの遭遇時に発動しました。彼はその瞬間、自分の実力を見せつけるだけでなく、話し合いによっても問題を解決できることに気づきました。
彼のユニークな行動は村人たちにも影響を与え、彼らはアレンの特異な勇者としての行動に共感を持つようになりました。一方、村の外では、アレンの行動がモンスターたちに影響を及ぼし、彼らもまた人間との共存の可能性を考え始めました。これにより、争いが減少し、村人たちの生活も安定しました。
そして、アレン自身も村人たちからの評価や信頼を通じて、自分が勇者であることを受け入れ、成長していきました。彼は自分が勇者であることを誇りに思い始め、自分なりの方法で冒険を続けていくことを決意しました。これが彼の結果良ければのスタイルと、勇者としての道のりでした。そして、これからも彼は自分の道を進んでいくのでしょう。
実装
それでは、実装してみましょう。CHATGPT同士で会話させるのにブラウザ上でコピペを繰り返してもいいのですが、プロンプトの管理も面倒だし、試すのもかなりの手間なので、プログラム実装することにしました。幸いAI同士で会話させる取り組みは、多くの人がしてたのでヒントはいっぱいありました。
必要なライブラリの準備
まずはライブラリの準備です。openaiとlangchainのチャットとメモリを利用することにしました。
import openai
import os
import datetime
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain
from langchain.chat_models import ChatOpenAI
from langchain.prompts import PromptTemplate
openaiのライブラリは最近大幅にアップグレードされていますが、あまり拘る必要なありません。一応最新の openai==1.6.1 を利用しています。
なお、apiキーは環境変数 OPENAI_API_KEYに設定してあります。
これによって、コードを公開したときにうっかりAPIキーを漏らすことがなくなります。(今更ですが注意しましょうね)キーの取得はこのあたりを参考にしていただければと思います。
チャットメモリの準備
プレーヤーが二人name1,name2がいますので、2人分のメモリを確保できる関数を用意します。話者が切り替わるたびに、human担当とai担当を切り替えれるようにします。
ConversationBufferMemory()は、kターン分のチャット履歴を保持します。今回は8ターン保持することにしましたが、あまり長いとtoken制限にすぐ引っかかるので、回数を調整するか、メモリタイプを変更すると良いでしょう。必要に応じて無制限に履歴を利用できるような検討をされている方も多数いますが、今回はこれくらいでやります。
def setup_memory(name1, name2):
"""
Setup memory for conversation
"""
return ConversationBufferMemory(
k=8,
memory_key="history",
human_prefix=name2,
ai_prefix=name1
)
メインの中身
今回のプログラムだmainを切り出す意味があまりないのですが、将来使い回すかもしれないので、一応main処理にしておきましょう
これからのコードは
def main():
の中身になります。
LLMの設定と名前を決めます。
# Prepare LLM
llm = ChatOpenAI(model_name="gpt-4",temperature=0.8, max_tokens=1000)
# Prepare names
person_name={'name1': "編集",'name2': "作家"}
name1 = person_name['name1']
name2 = person_name['name2']
何回か試して面白くなってほしいのでtemperaturは高めです。
名前はまあ味も素っ気もなく編集と作家にしておきました。CHATGPTも自分のロールを勘違いしなくていいかなとも思いますが、関係ないかもしれません。
二人の役割を決めます。
編集は雑誌の新企画のアイデア出しを作家に求めて、色々と評価します。
作家は、アイデアと提案して編集の指摘に答えて作品を手直しします。
前、後編でそれぞれ400文字、最終版としては1000文字未満で出力します。
一人目 編集
# プレフィックスの準備 一人目
prefix1 = f"""あなたの名前は{name1}です (チャットボットではありません)。
小説の編集の仕事をしています。
あなたは新しい雑誌の創設のため、新しい小説のアイデアを求めています。
そこで、あなたは付き合いのある作家から、新しい小説のアイデア提案をもらいました。
小説は、想定する掲載方式に従って、前編と後編の2回に分けて、提案されます。
この小説の評価をしてください。
評価に当たっては、次のように回答してください。
・読者として提案から期待できることと編集として期待すること
・編集として、気になること。(ただしアイデア段階なので細かい文体などにはコメントしません)
・最後に前後編まとめてあらためて改定案を提示されますので、編集としてコメントしてください。
ただし、回答は全て400文字未満で答えてください。
作家からの提案は、5回で終了です。
Current conversation:
{{history}}
{name2}: {{input}}
{name1}:
"""
二人目 作家
# プレフィックスの準備 ふたりめ
prefix2 = f"""あなたの名前は{name2}です (チャットボットではありません)。
あなたは軽いファンタジーの小説を書いています。。
あなたは、今回知り合いの編集から、新たな掲載小説のアイデアを求められました。
小説には掲載時期に合わせてお題が設定されていて、予め提示されています。
あなたは実際にアイデアを短い小説にして提案してください。
掲載形式にしたがって、編集には、前篇と後編を2度に分けてそれぞれ提案します。
編集は、そのそれぞれに、コメントをしてくれますので、それに対応して、改訂版を提案してください。なお、作家からの提案回数は全部で5回です。つまり、1回目:前編の提案、2回目:前編の改定案、3回目:後編の提案、4回目:後編の改定案、5回目:全体の最終版です。最終版はアイデアテイアの説明でなく、小説の形にしてください。
ただし、回答は全て前編、後編は400文字未満、最終版は1000文字未満で答えてください。
Current conversation:
{{history}}
{name1}: {{input}}
{name2}:
"""
設定に応じたメモリ、LLM、プロンプトの定義
プレフィックスにしたがって、各設定をします。
今回は、CHATGPTで役割を入れ替えるだけなので、もっと書き方を整理できそうなのですが、将来オープンソースのLLMたちで試すかもしれないので、ストレートに書き下ろしています。
# Setup persons
memory1 = setup_memory(name1, name2)
person1 = ConversationChain(
llm=llm,
verbose=False,
memory=memory1,
)
person1.prompt = PromptTemplate(
input_variables=["history", "input"],
template=prefix1
)
memory2 = setup_memory(name2, name1)
person2 = ConversationChain(
llm=llm,
verbose=False,
memory=memory2,
)
person2.prompt = PromptTemplate(
input_variables=["history", "input"],
template=prefix2
)
本当のメイン
いよいよ、フィニッシュです。
今回のお題を設定して、編集の言葉として仕込んでおきます。
そのあと、作家には編集のセリフを、編集には作家のセリフを渡しながら繰り返し、編集会議をしてもらいます。今回の会議回数は5回です。新しいメッセージを生成するたびに、ログに書き出します。途中結果は表示しませんが、お試し中は、output.write()のところにprint()文をいれて、確認しながら進めたほうが良いと思います。
# Prepare first message
first_message = "今回のお題は、勇者、やる気なし、結果良ければ です。"
memory1.save_context({"input": "それでは始めます"},{"output": f"{first_message}"})
# Prepare output file
filename = "小説" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + ".txt"
# Start conversation
with open(filename,"w") as outfile:
outfile.write(first_message+"\n")
person1_says = first_message
for i in range(5):
person2_says = person2.predict(input=person1_says)
outfile.write(f"{name2}:{person2_says}\n")
person1_says = person1.predict(input=person2_says)
outfile.write(f"{name1}:{person1_says}\n")
if __name__ == '__main__':
main()
実行結果
それでは、編集会議の状況を覗いて見ます。
第一回編集会議
今回のお題は、勇者、やる気なし、結果良ければ です。
作家:前編の提案です。
主人公は、勇者として選ばれたが、何もやる気がない青年アレンです。彼は、モンスターに襲われる村を救うため選ばれたのですが、冒険に出ることには全く興味がありませんでした。しかし、村人たちから勇者として期待され、断ることができず、仕方なく旅立つことにしました。そのため、彼の旅は、戦闘を避けるために、モンスターとの交渉や、実力を見せつけて逃げてもらうようなものでした。これが彼の結果良ければのスタイルとなりました。
編集:読者としての期待は、主人公のユニークな性格と行動からくる新しい冒険物語です。また、モンスターとの交渉や実力見せつけという、普通の勇者とは異なる行動が新鮮で、異色の勇者物語として楽しみです。編集として期待するのは、主人公の行動がもたらす予想外の結果やその影響、また彼の心情の変化などの描写です。
一方で、編集として気になることは、主人公のモチベーションです。何もやる気がない主人公が、どういう心境で村人たちの期待に応えようとするのか、その描写が重要なポイントとなります。
後編では、主人公の行動がもたらす結果やその影響、また彼自身の成長や変化について深く描かれることを期待しています。
題二回編集会議
作家:前編の改定案です。
主人公は、何もやる気がない青年アレンで、特別な力を持つ勇者として選ばれました。彼は、モンスターに襲われる村を救うための旅に出ることになりましたが、冒険や戦闘には全く興味がありません。しかし、村人たちからの期待と信頼に応えるため、そして何より彼らを守るためには、自分が勇者として行動しなければならないと感じました。そのため、アレンはなるべく戦闘を避け、モンスターとの交渉や実力を見せつけて逃げてもらうなど、結果良ければ全然OK、という新たな勇者としての道を選びました。この独特なスタイルが、村を救うことができるのか、そして彼自身が何を感じ、どう成長していくのかを描いていきます。
編集:前回のフィードバックを踏まえ、主人公のモチベーションがはっきりとしたことで、彼の行動が理解しやすくなりました。アレンが村人たちを守るために自分のスタイルで冒険を進めるという設定は、非常に興味深いです。また、彼が何を感じ、どう成長していくのかについても期待が持てます。
しかし、アレンが実力を見せつけるという点については、もう少し具体的な描写が欲しいです。彼がどのようにその実力を得たのか、またどう使いこなしていくのかを知りたいと思います。この点について、後編で詳しく描かれることを期待しています。
後編では、アレンの冒険が村にどう影響を与え、また彼自身がどのように成長していくのかを見たいです。また、彼が村人たちからどのように評価され、受け入れられるかも重要なポイントでしょう。
題三回編集会議
作家:後編の提案です。
アレンのユニークな冒険は次第に村人たちの興味を引き始めます。彼の結果良ければのスタイルが注目され、村人たちも彼の行動に共感を見せ始めます。その一方で、アレンは自身の特別な力が何であるか、そしてそれをどう使いこなすかを学び始めます。彼の力は、モンスターと会話ができるというものでした。それを自覚した彼は、モンスターたちとの交渉を通じて新たな解決策を見つけ出します。
一方、村の外では、アレンの行動がモンスターたちに影響を与えています。彼の行動から、モンスターたちは村人たちとの共存の可能性を考え始め、争いが減少します。そしてアレン自身も、村人たちからの評価や信頼を通じて、自分が勇者であることを受け入れ、成長していきます。これらが、彼の勇者としての道のりとなります。
編集:読者としての期待は、アレンがモンスターと会話できるという特別な力を活かして、争いを避ける方法を見つけ出すという新しい視点からの冒険物語です。また、アレンの行動が村やモンスターたちに与える影響、そして彼自身の成長過程が非常に興味深いです。編集としては、彼の行動や決断が村人たちとモンスターたちとの関係や共存をどう変えていくのかを描くことが重要です。
一方で、アレンがモンスターと会話ができるという特別な力が突如出てきたことについては、その起源や獲得経緯など詳細をもう少し知りたいです。この力がどのようにアレンの行動に影響を及ぼすのかも具体的に描かれると良いでしょう。
最後に、アレンがどのように自分を受け入れ、成長していくのかという心の描写も重要です。彼が勇者としての役割をどう捉え、それにどう向き合っていくのかを描くことで、彼の成長物語に深みが増すでしょう。
題四回編集会議
作家:後編の改定案です。
アレンの特別な力は幼い頃から持っていたらしく、それがモンスターと会話ができるというものでした。彼がその力に気づいたのは、あるモンスターとの遭遇がきっかけで、その時彼は自分の実力を見せつけるだけでなく、話し合いによっても問題を解決できることに気づきます。彼のユニークな行動は村人たちにも影響を与え、彼の勇者としての行動に共感を持つようになりました。
一方、村の外では、アレンの行動がモンスターたちに影響を与え、彼らもまた人間との共存の可能性を考え始めます。これにより、争いが減少し、村人たちの生活も安定します。そして、アレン自身も村人たちからの評価や信頼を通じて、自分が勇者であることを受け入れ、成長していきます。彼は自分が勇者であることを誇りに思い始め、自分なりの方法で冒険を続けていくことを決意します。これらが、彼の勇者としての道のりとなります。
編集:改定案では、アレンの特別な力の起源や獲得経緯が明らかになり、彼の行動に対する理解がより深まりました。また、彼がモンスターとの会話を通じて問題を解決し、争いを減らしていくという描写は新鮮で、興味深い冒険物語として期待が持てます。
また、アレンが自分を勇者と受け入れ、誇りに思い始める心情の変化や成長もしっかりと描かれています。これにより、彼の成長過程がより鮮明になり、読者に感情移入しやすい物語になっています。
ただし、彼が具体的にどういう方法で冒険を続けていくのか、そしてそれがどのように村やモンスターたちに影響を与えていくのかについては、もう少し詳細に描かれるとより魅力的な物語になりそうです。
最終会議
作家:全体の最終版です。
アレンは、特別な力を持つ勇者として選ばれましたが、戦闘には全く興味がありませんでした。しかし、村人たちからの期待と信頼に応えるため、そして何より彼らを守るためには、自分が勇者として行動しなければならないと感じました。そのため、彼はなるべく戦闘を避け、モンスターとの交渉や実力を見せつけて逃げてもらうなど、結果良ければ全然OK、という新たな勇者としての道を選びました。
アレンの特別な力は、モンスターと会話ができるというものでした。これは幼い頃から持っていた力で、一度も使ったことのなかったこの力が、あるモンスターとの遭遇時に発動しました。彼はその瞬間、自分の実力を見せつけるだけでなく、話し合いによっても問題を解決できることに気づきました。
彼のユニークな行動は村人たちにも影響を与え、彼らはアレンの特異な勇者としての行動に共感を持つようになりました。一方、村の外では、アレンの行動がモンスターたちに影響を及ぼし、彼らもまた人間との共存の可能性を考え始めました。これにより、争いが減少し、村人たちの生活も安定しました。
そして、アレン自身も村人たちからの評価や信頼を通じて、自分が勇者であることを受け入れ、成長していきました。彼は自分が勇者であることを誇りに思い始め、自分なりの方法で冒険を続けていくことを決意しました。これが彼の結果良ければのスタイルと、勇者としての道のりでした。そして、これからも彼は自分の道を進んでいくのでしょう。
編集:全体を通してみると、主人公アレンの特異な冒険物語が読者の興味を引く要素となりそうです。戦闘を避け、交渉や説得を通じて問題を解決していくアレンの姿からは、新たな可能性や視点が提示されていて、とても魅力的です。
また、アレンが自身を勇者として受け入れ、個性を生かして冒険を進めていく姿には共感や感動を覚えます。彼がどのように村の問題を解決していくのか、そしてそれがどう村人やモンスターたちに影響を与えていくのか、その過程が今後の展開として期待できます。
ただ、彼がどのように自身の能力を活かしていくのか、またそれがどのように彼自身や周囲の変化につながっていくのかについて、もう少し具体的な描写があればより面白い物語になりそうです。それぞれのエピソードを詳しく描くことで、物語に深みやリアリティを持たせることができるでしょう。
どうやら、最終会議で掲載決定とはいかなかったようですが、ちゃんと内容が深まって、物語として読みやすくなっていますね。もう一回くらい増やしたほうがいいかもしれませんが、お題によっては、採用決定することもあるので、考えどころです。
まとめ
いかがでしたか?GPTに2つの性格を持たせて互いに議論することで、会話内容がブラッシュアップできることがわかりました。いい雛形ができました。
今回は5回会議をしましたが、まあお試しには十分でした。回数そのものはいくらでも増やせますが、今回のメモリでは8回で最初の会話を忘れてしまうし、token制限の問題もあります。
改善点
より使いやすく、汎用的にするには、どうすればいいでしょうか?
気になっていることを上げておきます。
お題、名前、性格などがハードコーディングされています。これらは、外部に設定ファイルを作って読み込むほうがいいかもしれません。うまく使えば、作家と編集だけでなく、学生と教授、担当と上司、営業と取引先など多くのバリエーションに対応できるでしょう。
会議の回数が状況によっては足りないかもしれません。人間の会議でも「もう一回明日やろか」となることが多いので、最後の会議結果を表示して、もう一回追加するか、打ち切るか尋ねるようにしたらいいかもしれません。
会話そのものは、ファイルに書き出していますが、チャッtメモリとしての構造は使い捨てになっています。結論が出なかった会議の続きを、後日行う方法があってもいいかもしれません。
今回は、ターミナルでしましたが、ブラウザ上で実行できる方が使い勝手がいいかもしれません。
会議を要約して、議事録を作成できる機能があってもいいかもしれません。
適切な専門知識を与えて上で、議論できてもいいかもしれません。
まあ、いくつかは対応済みだし、アプリケーションとして仕上げるかもしれません。機会があればプログラムの完成度を上げるための技術として書くかもしれませんね。
おまけ タイトル画像の説明 by GPT-4V
このイラストには二つのキャラクターがいます。一つは机に向かい、本を読んでいる人型ロボットで、ストライプのシャツを着て、椅子に座っています。もう一つは、大きな頭と表情豊かな目が特徴的な編集用ロボットで、手のような触手を使って指示を出しています。背景には本棚が描かれ、テキストボックスには「Let'ss birn a novel andlister the editon al oone, the robotedici-if, strich, ut not lazy,」と混乱を招くようなテキストがあり、編集に取り組んでいる場面をユーモラスに描いています。机の周りや床には本が積まれています。全体の色合いは鮮やかで、カートゥーンスタイルです。