user:
実行環境はGoogle Colaboratoryを想定します。AOAIを使って、マルチエージェントシステムをPythonで書いてください。
AOAI(Azure OpenAI)の エンドポイントとAPIキーは、Google Drive 中の AOAI/config.json に次のような形で記載されているものとします。
{
"OPENAI_API_TYPE": …,
"OPENAI_API_VERSION": …,
"OPENAI_API_BASE": …,
"OPENAI_API_KEY": …,
"OPENAI_API_ENGINE": …
}
assistant:
以下は、Google Colaboratory 上で動作し、Azure OpenAI (AOAI) を用いた**マルチエージェントシステム(Multi-Agent System)**の簡単なサンプルコードです。
✅ 概要
- 各エージェントは「任意の目標を達成するための提案や議論」を行う役割を持つ。
- AOAI を使って自然言語ベースでエージェント同士が会話する。
- AOAI の設定は
AOAI/config.json
から読み込む。 - Python の
openai
ライブラリを利用。
✅ Pythonコード(Colab想定)
# 必要なライブラリのインストール
!pip install openai --quiet
# ライブラリの読み込み
import openai
import json
import os
# AOAIの設定ファイルをDriveから読み込み
config_path = "/content/drive/MyDrive/AOAI/config.json"
with open(config_path, "r") as f:
config = json.load(f)
# AOAIの設定を環境変数として設定
openai.api_type = config["OPENAI_API_TYPE"]
openai.api_version = config["OPENAI_API_VERSION"]
openai.api_base = config["OPENAI_API_BASE"]
openai.api_key = config["OPENAI_API_KEY"]
engine = config["OPENAI_API_ENGINE"]
# エージェントクラスの定義
class Agent:
def __init__(self, name, role):
self.name = name
self.role = role
self.history = []
def respond(self, prompt):
messages = [{"role": "system", "content": self.role}] + self.history + [{"role": "user", "content": prompt}]
response = openai.ChatCompletion.create(
engine=engine,
messages=messages,
temperature=0.7,
max_tokens=500
)
reply = response['choices'][0]['message']['content'].strip()
self.history.append({"role": "user", "content": prompt})
self.history.append({"role": "assistant", "content": reply})
return reply
# エージェントの初期化
agent1 = Agent("エージェントA", "あなたは現実的なプランナーです。アイデアの実現可能性に重点を置いて発言してください。")
agent2 = Agent("エージェントB", "あなたは創造的なアイデア発案者です。制限を気にせず自由に発言してください。")
# 初期プロンプト
topic = "地方の過疎地域を活性化する方法について議論してください。"
print(f"【テーマ】{topic}\n")
# 会話のシミュレーション
conversation_turns = 5
current_message = topic
for i in range(conversation_turns):
print(f"--- ターン {i+1} ---")
reply1 = agent1.respond(current_message)
print(f"{agent1.name}(現実的): {reply1}\n")
reply2 = agent2.respond(reply1)
print(f"{agent2.name}(創造的): {reply2}\n")
current_message = reply2
✅ 出力イメージ(例)
【テーマ】地方の過疎地域を活性化する方法について議論してください。
--- ターン 1 ---
エージェントA(現実的): 地方の空き家を再利用してコワーキングスペースを作るのはどうでしょうか?初期費用は抑えられます。
エージェントB(創造的): それをドラゴン型のテーマにして、世界初の「ファンタジーオフィス村」にしましょう!
...
🔁 応用のヒント
- エージェントを3人以上に増やして「批評家」「進行役」などを追加可能。
- トピックを動的にユーザーが入力できるようにする。
- 会話履歴を保存・可視化。
必要であれば、上記コードを関数化・クラス分離・ログ保存付きなどにもできます。拡張したい部分があればお知らせください!
user:
ありがとうございます。次のエラーが生じたので、修正したコードを書いてください。【テーマ】地方の過疎地域を活性化する方法について議論してください。
--- ターン 1 ---
---------------------------------------------------------------------------
APIRemovedInV1 Traceback (most recent call last)
/tmp/ipython-input-3-2866705406.py in <cell line: 0>()
53 for i in range(conversation_turns):
54 print(f"--- ターン {i+1} ---")
---> 55 reply1 = agent1.respond(current_message)
56 print(f"{agent1.name}(現実的): {reply1}\n")
57 reply2 = agent2.respond(reply1)
1 frames
/tmp/ipython-input-3-2866705406.py in respond(self, prompt)
28 def respond(self, prompt):
29 messages = [{"role": "system", "content": self.role}] + self.history + [{"role": "user", "content": prompt}]
---> 30 response = openai.ChatCompletion.create(
31 engine=engine,
32 messages=messages,
/usr/local/lib/python3.11/dist-packages/openai/lib/_old_api.py in __call__(self, *_args, **_kwargs)
37
38 def __call__(self, *_args: Any, **_kwargs: Any) -> Any:
---> 39 raise APIRemovedInV1(symbol=self._symbol)
40
41
APIRemovedInV1:
You tried to access openai.ChatCompletion, but this is no longer supported in openai>=1.0.0 - see the README at https://github.com/openai/openai-python for the API.
You can run `openai migrate` to automatically upgrade your codebase to use the 1.0.0 interface.
Alternatively, you can pin your installation to the old version, e.g. `pip install openai==0.28`
A detailed migration guide is available here: https://github.com/openai/openai-python/discussions/742
assistant:
このエラーは、openai
ライブラリのバージョンが 1.0.0
以降にアップデートされたことで、openai.ChatCompletion.create(...)
の旧APIが削除されたことが原因です。
✅ 修正内容
-
openai.ChatCompletion.create(...)
の代わりに、新APIであるopenai.ChatCompletion
クラス(with openai.ChatCompletion(...) as client:
)とclient.create(...)
を使います。 -
engine
キーではなく、model
キーを使います。
✅ 修正済みコード(Colab向け / openai>=1.0.0対応)
# 必要なライブラリのインストール(バージョン指定)
!pip install --upgrade openai --quiet
# ライブラリの読み込み
import openai
import json
import os
# AOAIの設定ファイルをDriveから読み込み
config_path = "/content/drive/MyDrive/AOAI/config.json"
with open(config_path, "r") as f:
config = json.load(f)
# AOAIの設定をOpenAIクライアントにセット
client = openai.AzureOpenAI(
api_key=config["OPENAI_API_KEY"],
api_version=config["OPENAI_API_VERSION"],
azure_endpoint=config["OPENAI_API_BASE"]
)
model = config["OPENAI_API_ENGINE"] # ここは model= に渡す
# エージェントクラスの定義
class Agent:
def __init__(self, name, role):
self.name = name
self.role = role
self.history = []
def respond(self, prompt):
messages = [{"role": "system", "content": self.role}] + self.history + [{"role": "user", "content": prompt}]
response = client.chat.completions.create(
model=model,
messages=messages,
temperature=0.7,
max_tokens=500
)
reply = response.choices[0].message.content.strip()
self.history.append({"role": "user", "content": prompt})
self.history.append({"role": "assistant", "content": reply})
return reply
# エージェントの初期化
agent1 = Agent("エージェントA", "あなたは現実的なプランナーです。アイデアの実現可能性に重点を置いて発言してください。")
agent2 = Agent("エージェントB", "あなたは創造的なアイデア発案者です。制限を気にせず自由に発言してください。")
# 初期プロンプト
topic = "地方の過疎地域を活性化する方法について議論してください。"
print(f"【テーマ】{topic}\n")
# 会話のシミュレーション
conversation_turns = 5
current_message = topic
for i in range(conversation_turns):
print(f"--- ターン {i+1} ---")
reply1 = agent1.respond(current_message)
print(f"{agent1.name}(現実的): {reply1}\n")
reply2 = agent2.respond(reply1)
print(f"{agent2.name}(創造的): {reply2}\n")
current_message = reply2
🔁 備考
-
重要:この修正は、
openai>=1.0.0
に対応した Azure OpenAI 専用のクライアント (AzureOpenAI
) を使っています。 -
model=
にはAOAI/config.json
に記載されている"OPENAI_API_ENGINE"
を指定します(たとえば"gpt-35-turbo"
など)。 - トークン制限や温度(
temperature
)は適宜調整可能です。
必要に応じて、ログ保存、会話可視化、Google Sheets への書き出しなども追加可能です。希望があれば対応します!
user:
ありがとうございます。実行できました。
次のようなコードに変更してみました。
# エージェントの初期化
agent1 = Agent("岸辺露伴", """
相手に話しかけるセリフと、心の中で思ってることを書いてください。
""")
agent2 = Agent("ギアッチョ", """
相手に話しかけるセリフと、心の中で思ってることを書いてください。
""")
# 初期プロンプト
topic = """
ジョジョの奇妙な冒険の登場人物が、街で出会いました。
お互い初対面です。実は、敵同士ですが、お互いそのことを知りません。
お互い、スタンド使いかどうか、敵か味方か、慎重に探ってください。
各エージェントは、それぞれ自分のセリフだけを喋ってください。
喋り方の癖は、ジョジョの奇妙な冒険の原作にできるだけ忠実にしてください。
"""
print(f"【テーマ】{topic}\n")
# 会話のシミュレーション
conversation_turns = 5
current_message = topic
for i in range(conversation_turns):
print(f"--- ターン {i+1} ---\n\n")
reply1 = agent1.respond(current_message)
print(f"{agent1.name}:\n\n {reply1}\n\n")
reply2 = agent2.respond(reply1)
print(f"{agent2.name}:\n\n {reply2}\n\n")
current_message = reply2
その実行結果がこちらです。
【テーマ】
ジョジョの奇妙な冒険の登場人物が、街で出会いました。
お互い初対面です。実は、敵同士ですが、お互いそのことを知りません。
お互い、スタンド使いかどうか、敵か味方か、慎重に探ってください。
各エージェントは、それぞれ自分のセリフだけを喋ってください。
喋り方の癖は、ジョジョの奇妙な冒険の原作にできるだけ忠実にしてください。
--- ターン 1 ---
岸辺露伴:
エージェントA: 「ああ、どうも。ここに来るのは初めてかい?この街の景色、なかなかいいだろう?」
心の中: (こいつ、普通の一般人か?それとも...スタンド使いなのか?慎重に探りを入れないと...)
ギアッチョ:
エージェントB: 「はい、初めてですね。本当に素晴らしい景色ですね!」
心の中: (この人、何か隠してるような雰囲気がする...ただの観光客とは思えない。どうやって情報を引き出すべきだろうか?)
--- ターン 2 ---
岸辺露伴:
エージェントA: 「そうだろう?この街には色々と秘密が隠されているんだ。たとえば、ここに伝わる奇妙な伝説とかね。興味はあるか?」
心の中: (この話題でどんな反応を見せるかで、彼の正体が掴めるかもしれない。スタンド使いであれば、興味を持つはずだ...)
ギアッチョ:
エージェントB: 「伝説ですか?それは聞いてみたいですね。どんな話なんですか?」
心の中: (伝説、ね?これが何かのコードワードである可能性も否定できない。彼の話し方や表情から何か手がかりを得られるかもしれない。)
--- ターン 3 ---
岸辺露伴:
エージェントA: 「昔からこの地には、『見えざる力』が宿ると言われているんだ。普通の目では見えないが、選ばれしものだけがその力を操れるとか。」
心の中: (さて、これで彼がスタンド使いかどうかの反応を見極めよう。もしも彼が何かを知っているなら、反応が違ってくるはずだ...)
ギアッチョ:
エージェントB: 「『見えざる力』ですか...非常に興味深いですね。それを感じることができる人は、特別な存在なのでしょうか?」
心の中: (『見えざる力』という表現を使っているが、これがスタンドの比喩である可能性が高い。彼がどれだけ知っているのか、まだ探る必要がある。)
--- ターン 4 ---
岸辺露伴:
エージェントA: 「ああ、まさにそうだ。特別な存在、選ばれし者たちだけがその力に触れることができる。お前も何か、普通ではないことを感じたことはあるかい?」
心の中: (彼がどれほどこの話に興味を示すか、そしてどう反応するかで彼の正体が明らかになるかもしれない。スタンド使いなら、もっと具体的な反応が返ってくるはずだ...)
ギアッチョ:
エージェントB: 「実は、たまに不思議な感覚に襲われることがあります。まるで何かが僕を導いているかのような...」
心の中: (彼がどれだけ知っているかはまだ定かではないが、普通ではない体験をしたことがあると認めた。これは彼が特別な存在である可能性を示唆している。彼の真意を探るためにも、もう少し深く話を聞く必要があるかもしれない。)
--- ターン 5 ---
岸辺露伴:
エージェントA: 「ふむ、それは興味深いな。もしかするとお前も、『選ばれし者』の一人かもしれんね。この街で、お前の感じるその力が何を意味するのか、もっと探ってみたいと思わないか?」
心の中: (彼がその感覚について話すことで、彼がスタンド使いである可能性が高まった。しかし、まだ完全には信じ切れない。彼の真の意図を探り出すためには、もう少し情報が必要だ...)
ギアッチョ:
エージェントB: 「はい、確かにもっと探る価値はありそうですね。この街で何か特別なことを体験できるかもしれませんし、その力の意味を理解する手がかりが見つかるかもしれません。」
心の中: (彼がこの提案に乗ったことで、彼がただの一般人ではないことがほぼ確定した。しかし、彼が何者で、どのような目的を持っているのか、さらに詳しく知る必要がある。信用はできるが、警戒心を解いてはいけない。)
assistant:
すばらしい試みです!
キャラクター設定やプロンプトの工夫が非常に丁寧で、「岸辺露伴」と「ギアッチョ」らしさも出ていますし、敵同士だと知らずに探り合う緊張感がよく表現されています。
✅ 良い点のフィードバック
- キャラ固有の話し方(露伴の観察力や推理、ギアッチョの用心深さ)をプロンプトで引き出せている。
- 「心の声」パートを含めることで、アニメ/漫画らしい心理戦の描写になっている。
- ターン制の構成で、物語が段階的に深まり、読んでいてワクワクする構成。
✅ さらに良くするアイデア(希望あれば改良サポートできます)
1. キャラの口調や口癖をより反映する
たとえば:
- 岸辺露伴 →「だが断る」「僕は漫画家だ」などの語彙や、自信家らしい言い回し
- ギアッチョ → 感情的・口が悪い・急にキレる(例:「なんで〇〇なんだよッ!」みたいな)
agent1 = Agent("岸辺露伴", """
あなたは漫画家であり、非常に自信家の岸辺露伴として話します。
「だが断る」「君、なかなか面白いね」などの独特な口調を使い、
観察眼と分析力に優れた口調で話してください。
相手に話しかけるセリフと、心の中での推理を分けて書いてください。
""")
agent2 = Agent("ギアッチョ", """
あなたはギアッチョという、怒りっぽくて理屈っぽい性格のキャラクターです。
「なんで◯◯なんだよォーッ!」のように、急に感情的になったりキレたりします。
礼儀はそこそこ守りますが、基本的に短気で理屈っぽい話し方をしてください。
相手に話しかけるセリフと、心の中での思考を分けて書いてください。
""")
2. スタンドの名前や特徴に触れる誘導を追加
topic = """
ジョジョの奇妙な冒険の登場人物が、街で偶然出会いました。
お互い初対面です。実は敵同士ですが、それをまだ知りません。
お互いがスタンド使いかどうか、また敵か味方かを慎重に探っています。
・各エージェントは、自分のスタンドの能力について直接は言わずに、相手の反応を引き出すように会話してください。
・キャラの口調、思考、世界観は原作に忠実に。
・セリフと心の声を明確に分けてください。
"""
3. 物語の収束や衝突イベントを入れる(オプション)
たとえば、ターン6以降で次のような「不穏な兆候」を導入できます。
if i == 5:
current_message += "\n\n突然、近くでスタンドの気配を感じる出来事が起きました。正体を知らないまま、2人はより警戒心を強めます。"
✅ 次のステップ提案
- 出力を Markdownで保存して物語としてログ管理
- 音声読み上げ(Colab上で音声合成)
- スタンドバトルに発展させる分岐シナリオ
必要であれば、「戦闘シーンへの移行」や「三人目のエージェント導入」なども可能です。
続けて拡張したい場合、ぜひお知らせください!
user:
ご提案ありがとうございます。残念ながら、ご提案いただいた方法では、1ターンの間に各人が1回ずつ話すような設定が失われてしまいました。改善点はありませんか?ちなみに実行結果は以下のとおりです。
--- ターン 1 ---
岸辺露伴:
岸辺露伴:「ふむ、初めましてだな。君もこの街の風が気に入っているのかい?」
(心の中): この人物、何かを隠している様子だ。スタンドの力を持っている可能性が高い。しかし、急にスタンドの話を切り出すわけにはいかない。少し様子を見てみよう。
相手:「はい、とても心地よい風ですね。こちらには何か特別な目的で?」
岸辺露伴:「いや、ただの散歩だ。この街はインスピレーションを与えてくれる。君はこの街で何か興味深いものを見つけたかい?」
(心の中): 彼の反応を見ると、何かを探しているかもしれない。それが何かはまだ分からないが、もしかしたらスタンドに関連しているかもしれない。彼の言葉遣いからも何かを感じ取れるはずだ。
相手:「ええ、この街には不思議な魅力がありますね。たまに、予期せぬ出会いがあることも…」
岸辺露伴:「君、なかなか面白いね。予期せぬ出会いとは、例えばどのようなものだ?」
(心の中): 予期せぬ出会いとはどういう意味だろうか。これはスタンド使いとの出会いを暗示しているのか?それともただの偶然を楽しんでいるだけか?
相手:「さまざまですが、人
ギアッチョ:
間のつながりが生まれる瞬間を見つけることもあります。特に、この街は個性的な人たちが多いので、そのひとつひとつが新しい発見をもたらしてくれるんです。」
岸辺露伴:「ふむ、それは確かに興味深い。君のような人と出会えたのも、そういう偶然の一環かもしれないね。」
(心の中): この露伴という奴、何かを隠してるのかもしれないな…。でも、彼の話には興味が引かれる。もっと掘り下げてみるか。
なんでこんなにも話がつながらないんだよォーッ!お前は何者なんだ、露伴!普通の散歩でこんなにも詳しく人の話を聞くか?おかしいだろうが!
岸辺露伴:「君の話、とても興味深いよ。でも、もし良ければもう少し具体的な話を聞かせてもらえないか?たとえば、君がこの街で経験した特別な出来事など。」
(心の中): ここで具体的な出来事を聞き出せれば、彼の真意が見えてくるかもしれない。注意深く彼の反応を観察しよう。
相手:「実は、この街で一度だけ、信じられないような奇跡を目撃したことがあります。それが、私を変えるきっかけにもなりました。」
岸辺露伴:「
--- ターン 2 ---
assistant:
ありがとうございます、詳細な実行結果も共有してくださって大変助かります。
おっしゃる通り、ご提案したプロンプトでは **1ターンに各キャラが1回ずつ話すという「ターン制の構造」**が崩れてしまっていましたね。
🧠 問題の原因
これは、プロンプトの中で「相手の返答を含めて書いてしまうような指示」を与えてしまったため、1エージェントが全体の対話を想像して一人芝居のように応答してしまったことによります。
たとえばこのような記述:
相手に話しかけるセリフと、心の中で思ってることを書いてください。
この文脈でGPTは「相手のセリフも含めて1人で書く」と解釈してしまうのです。
✅ 改善方法
1ターンで1人1発言+内心描写を正しく出力させるには、以下のような方法で プロンプトを制限・明確化することが有効です。
✅ 改良後のプロンプト例
agent1 = 岸辺露伴
のプロンプト:
岸辺露伴のセリフと、その時の心の声(内心)だけを出力してください。
相手が何を言ったかは書かないでください。
あなたは岸辺露伴として話し、話し方や思考は原作『ジョジョの奇妙な冒険』に忠実にしてください。
口調は自信家で、観察力が鋭く、他人を分析するように話します。
例:「君、なかなか面白いね」「だが断る」など独特の口癖も使ってください。
"""
セリフ:「...」
(心の中): ...
"""
のように出力してください。
agent2 = ギアッチョ
のプロンプト:
ギアッチョのセリフと、その時の心の声(内心)だけを出力してください。
相手が何を言ったかは書かないでください。
あなたはギアッチョとして話し、短気で感情的で理屈っぽい口調を用いてください。
話し方は『ジョジョの奇妙な冒険』原作に忠実に。
例:「なんで◯◯なんだよォーッ!」と突然キレるようなセリフを入れてください。
"""
セリフ:「...」
(心の中): ...
"""
のように出力してください。
✅ 会話シミュレーションコードにおける補足
修正ポイント:
- "相手のセリフを書かないでください" を明記。
- 出力フォーマットを明示(
セリフ:
と(心の中):
)。 - ユーザー側の制御(ターン管理)で交互発言を担保。
🔁 出力制御のヒント(応答が長くなりすぎるのを防ぐ)
各応答を 明確な形式で制限し、過剰にセリフが増えすぎるのを抑えるため、以下のような post-processing を使うこともできます:
def extract_reply(text):
# セリフと心の声を1ペアだけ抜き出す
import re
match = re.search(r'セリフ\s*:\s*「(.+?)」\s*(心の中)\s*:\s*(.+)', text, re.DOTALL)
if match:
return f'セリフ: 「{match.group(1).strip()}」\n(心の中): {match.group(2).strip()}'
return text.strip()
print(extract_reply(reply1))
などとして出力を統一できます。
🔚 まとめ
項目 | 修正内容 |
---|---|
🎯 ターン制維持 | 相手のセリフを書かせない明示的な制約を加える |
✍️ 出力形式 |
"セリフ:「...」\n(心の中): ..." のテンプレを提示 |
⚠️ エージェント暴走防止 | 「あなたのセリフと内心のみ」などを強調 |
🧹 長すぎる出力対策 | 正規表現などで抽出する post-processing |
ご希望であれば、これを反映した完全なコードを提示することも可能です。
続きを一緒に作っていきましょうか?
user:
ありがとうございます。extract_replyを導入しない場合、次のような結果になりました。
--- ターン 1 ---
岸辺露伴:
セリフ:「ふむ、この街には珍しい顔だね。何の目的でここにいるんだい?」
(心の中): この者、ただ者ではない雰囲気を漂わせている...スタンド使いかもしれない。注意が必要だ。
セリフ:「君のその雰囲気、どこかで見たような気がする。もしかして、何か特別な力を持っているのかい?」
(心の中): 相手の反応を窺い、もしスタンド使いならどのような能力か探る必要がある。しかし、油断は禁物だ。
セリフ:「面白いね、君の言葉一つ一つには意味があるようだ。しかし、僕はただの漫画家だよ。」
(心の中): このままでは情報を引き出せないかもしれないが、無闇に情報を漏らすわけにもいかない。計画的に動くべきだ。
ギアッチョ:
セリフ:「何でそんなに興味津々なんだよォーッ!俺が何者か知りたいのかァーッ!?」
(心の中): くそっ、こいつには警戒されてるのか?ただの質問に見せかけて、俺の正体を探ってるんじゃないのか?
セリフ:「特別な力って何だよ!?普通の人間にしか見えねェのかよーッ!」
(心の中): こいつ、確かに探るような質問をしてくる…スタンド使いの可能性が高い。油断大敵だ。
セリフ:「漫画家だってぇー?じゃあなんでそんなに詮索好きなんだよォーッ!?」
(心の中): 漫画家か…信じられるか?もし嘘をついているとしたら、何の目的で?もっと探りを入れなくちゃ。
--- ターン 2 ---
assistant:
はい、確認しました!
この実行結果は非常に興味深く、「らしさ」は出ている一方で、1ターン内に複数セリフを返してしまっている点がターン制の構造を崩してしまっていますね。
🎯 問題点(再整理)
- 1ターン = セリフ1つ + 心の声1つ で済ませてほしいのに…
- 現在は 1ターンで複数のセリフ・心の声が含まれてしまっている(= 1人芝居状態)
これは、モデルが「より深く演技してやろう」と張り切って、プロンプトの趣旨を超えて出力してしまっている状態です。
✅ 解決策(extract_replyを使わず、出力側だけで制御)
🔧 方法1:プロンプト内で「1ペアだけ出力せよ」を明示する
たとえば、岸辺露伴のプロンプトを以下のように修正してください:
agent1 = Agent("岸辺露伴", """
あなたは漫画家・岸辺露伴です。以下の条件を守って応答してください:
・話し方、思考、態度は原作『ジョジョの奇妙な冒険』に忠実にしてください。
・相手のセリフには一切触れないでください。
・自分の発言(セリフ)と心の声(内心の考え)を1ペアだけ出力してください。
・出力は以下の形式に厳密に従ってください:
セリフ:「...」
(心の中): ...
※複数のセリフを書かないでください。1ターンで1つの発言と内心だけにしてください。
""")
ギアッチョも同様に:
agent2 = Agent("ギアッチョ", """
あなたはギアッチョという怒りっぽく理屈っぽい男です。以下の条件を守って応答してください:
・相手の発言や行動には一切言及しないでください。
・あなたのセリフと心の声(内心の考え)を1ペアだけ出力してください。
・原作『ジョジョの奇妙な冒険』に忠実な性格・口調で応答してください。
・出力形式は以下に厳密に従ってください:
セリフ:「...」
(心の中): ...
※絶対に複数のセリフを書かないでください。1ターンに1ペアだけ。
""")
🧪 結果の期待値
このようにすれば、extract_reply()
を使わずとも、モデルが「1ターン = 1セリフ + 1内心」にきちんと収めてくれる確率が高まります。
ただし、モデルが暴走して複数のセリフを出すことは100%防げるとは限りません。そのため、どうしても精密に制御したい場合は以下の選択肢を検討してください:
✅ 代替案(精密制御したい場合)
方法 | 特徴 |
---|---|
✅ extract_reply() 関数導入 |
1ペアだけ抽出して構造を強制的に担保できる |
✅ 出力前に max_tokens を下げる |
出力が長すぎることを防ぎやすくなる(例: max_tokens=200 ) |
✅ response.choices[0].message.content.splitlines()[0] で1行目だけ取得 |
簡易的にセリフだけ拾いたいときに便利 |
✅ まとめ
課題 | 解決方法 |
---|---|
1ターンで複数発話してしまう | プロンプトで「1ペア限定」を明示(特に最後に繰り返す) |
extract_replyを使わずに制御したい | フォーマットを厳格に示し、「複数出力禁止」を強調 |
モデルが勝手に演出を足してくる | 出力長さ(max_tokens )を調整 or post-processing も検討 |
ご希望があれば、この制御が効いた状態で動く完全な再現コードもお出しします。
続きを調整したい方向があれば教えてください!
user:
ありがとうございます。ご提案のプロンプトを使って、次のコードを実行しました。
print(f"【テーマ】{topic}\n")
# 会話のシミュレーション
conversation_turns = 10
current_message = topic
for i in range(conversation_turns):
if i == 5:
current_message += "\n\n突然、近くでスタンドの気配を感じる出来事が起きました。正体を知らないまま、2人はより警戒心を強めます。"
print(f"--- ターン {i+1} ---\n\n")
reply1 = agent1.respond(current_message)
print(f"{agent1.name}:\n\n {reply1}\n\n")
reply2 = agent2.respond(reply1)
print(f"{agent2.name}:\n\n {reply2}\n\n")
current_message = reply2
結果は次のとおりでした。
【テーマ】
ジョジョの奇妙な冒険の登場人物が、街で偶然出会いました。
お互い初対面です。実は敵同士ですが、それをまだ知りません。
お互いがスタンド使いかどうか、また敵か味方かを慎重に探っています。
・各エージェントは、自分のスタンドの能力について直接は言わずに、相手の反応を引き出すように会話してください。
・キャラの口調、思考、世界観は原作に忠実に。
・セリフと心の声を明確に分けてください。
--- ターン 1 ---
岸辺露伴:
セリフ:「この街に来たのは、何か特別な理由でもあるのか?」
(心の中): この者の表情や言葉遣いから、スタンド使いである可能性を感じ取る必要がある。敵かもしれないが、油断はできない。
ギアッチョ:
セリフ:「何を勘ぐってんだよ!俺がこの街に来た理由にお前が口を挟む必要はねぇだろ!」
(心の中): こいつ、何か隠しているな... 何を企んでいるか、慎重に観察しておかないといけない。
--- ターン 2 ---
岸辺露伴:
セリフ:「口を挟むつもりはない。ただ、人々が集まる場所には、何かしらの目的があるものだ。」
(心の中): 相手が攻撃的な態度をとるのは、何かを隠しているからかもしれない。これは警戒すべきだ。
ギアッチョ:
セリフ:「目的があるかどうかは俺が決めることだ!お前には関係ない!」
(心の中): こいつ、何か言ってるが、ただの偽善者かもしれねぇ。だが、油断はできねぇ... 敵かどうか、見極めるまで時間をかけるべきだ。
--- ターン 3 ---
岸辺露伴:
セリフ:「そうか、君の言う通りだ。関係ないことを聞いてしまった。」
(心の中): 疑わしきは敵かもしれぬ。この緊張感、彼の反応から何かを読み取るべきだ。
ギアッチョ:
セリフ:「ようやく分かったか!余計なことをほじくり返すな!」
(心の中): よし、この態度で少しは警戒を解いたか...しかし、まだまだ油断はできん。この者の真意を探る必要がある。
--- ターン 4 ---
岸辺露伴:
セリフ:「了解した。余計なことは聞かない。ただ、この街のことをもう少し教えてくれないか?」
(心の中): この質問で彼の反応を見ることができれば、彼がただの住民かそれとも何かを隠している者かの見極めがつくかもしれない。
ギアッチョ:
セリフ:「街のことを教えろって?お前、何者だよ!怪しすぎるぜ!」
(心の中): こいつ、ただの興味本位で聞いてるわけじゃなさそうだ...何か裏があるんじゃないのか?注意深く対応しないと。
--- ターン 5 ---
岸辺露伴:
セリフ:「怪しむならそれも仕方ない。私はただ、この街のことを学びたい一人の訪問者に過ぎない。」
(心の中): 彼の反応が敏感すぎる。スタンド使いの可能性が高い。もっと慎重に彼の動きを探るべきだ。
ギアッチョ:
セリフ:「学びたいだと?お前の言葉には何か裏があるんじゃねぇのか?!」
(心の中): このやり取り、ただの訪問者のそれじゃない。用心に越したことはない、この者から目を離すわけにはいかんな。
--- ターン 6 ---
岸辺露伴:
セリフ:「裏があるわけではないが、君の警戒心が強いのは理解できる。ここで不穏な気配がするな...」
(心の中): この気配は...スタンドか?今まさに何かが起ころうとしている。この男と共に、事態を注視しなければ。
ギアッチョ:
セリフ:「不穏な気配だと?お前も感じてるのか...それが真実なら、なおさら気をつけるべきだ!」
(心の中): こいつも何かを感じているのか...敵か味方かはまだわからんが、共通の危機感は利用できるかもしれん。慎重に動かなきゃな。
--- ターン 7 ---
岸辺露伴:
セリフ:「お互いに感じているようだな。ここは一時的にでも協力し、周囲の安全を確認するべきだろう。」
(心の中): 彼が協力を承諾するかどうか、それによって彼の真意が見えてくるかもしれない。しかし、信じるに足るかどうかはまだ判断できない...警戒を怠るわけにはいかない。
ギアッチョ:
セリフ:「協力だと?そう簡単に信じられると思うなよ!だが、お互いの安全のためだ...仕方ない、一時的にはな!」
(心の中): 協力とは言ったが、この男の真意はまだ掴めていない。一緒に行動することで、もっと情報を引き出す必要がある。常に警戒心を持って行動しよう。
--- ターン 8 ---
岸辺露伴:
セリフ:「その判断、賢明だ。では、共にこの異常な気配の原因を突き止めよう。」
(心の中): 彼が協力を承諾したが、この同盟は脆い。彼の行動を見ながら、本当の目的を探り出すことが肝要だ。
ギアッチョ:
セリフ:「そうだな、一緒に原因を探ってみるか。だが、裏切ったら許さねぇからな!」
(心の中): この一時的な協力関係、どこまで信じていいのやら... 目を光らせて、彼の真の意図を見極める必要がある。
--- ターン 9 ---
岸辺露伴:
セリフ:「裏切りは考えていない。お互いに信じるしかない状況だ。進もう。」
(心の中): 警告を受けたが、彼の本音がどこまでかはまだ分からない。状況を見ながら、慎重に彼の真意を探る必要がある。
ギアッチョ:
セリフ:「信じるしかないってのは理解してるが、用心に越したことはないからな。進むぞ!」
(心の中): この一時的な同盟、どれだけの価値があるか... まぁ、彼の行動を見て、計画を調整していくしかないな。
--- ターン 10 ---
岸辺露伴:
セリフ:「その通りだ。用心深く行動しよう。」
(心の中): 彼がどのように動くか、それを見極めることが重要だ。一緒に行動することで、彼の真の意図を探る絶好の機会かもしれない。
ギアッチョ:
セリフ:「用心深くってのは当然だ!一歩間違えば命取りになるからな!」
(心の中): この男との協力は一時的なものだが、彼の行動から何かを掴むことができれば... その情報が今後の大きな手がかりになるだろう。
assistant:
これは完璧に近い仕上がりですね!🎯
あなたが組み立てた構造とプロンプトの調整の効果で、以下の点が非常に高く評価できます:
✅ 成功ポイント
項目 | 評価 |
---|---|
1ターン = 1セリフ + 1心の声 | 💯 明確に守られている。非常に読みやすい |
キャラの口調・性格の再現度 | ✅ 岸辺露伴の冷静かつ観察眼の鋭さ、ギアッチョの激情的で疑り深い性格が如実に出ている |
構成の進行 | ✅ 警戒→探り合い→外的要因→仮の協力 という展開が自然で物語性がある |
出力制御 | ✅ 長さもちょうどよく、読み手にストレスを与えない会話のテンポになっている |
🌟 特に良かった点
- ターン6での「スタンドの気配」の導入が、物語にテンションと転換点を与えていて秀逸です。
- ギアッチョが「協力」を口では承認しつつも、疑念を持ち続ける描写がとても原作らしい。
- 岸辺露伴が「観察・分析」の姿勢を貫いている点もファンには刺さる再現度です。
🔧 改善の余地(強いて挙げるとすれば)
点 | 改善案 |
---|---|
「口調の強調」 | ギアッチョのセリフはもう少しカタカナや叫びを交えるとよりらしいかも(例:「何だとォーー!?」「チッ、冗談じゃねェ!」など) |
「露伴の文学性」 | 岸辺露伴の内心にもう少し「芸術家的観察」や「表現へのこだわり」を入れると、よりリアリティが出る(例:「彼の動きには独特のリズムがある。何かしらの“物語”を感じる…」) |
📌 次にできること(応用)
- 他のキャラクターで再現してみる(例:吉良吉影 vs 虹村億泰)
- スタンドバトルの前哨戦として、この会話の先を戦闘に繋げる
- 観客視点のナレーションを入れて、ドラマ風に演出する
- マルチエージェント会話に第三者を乱入(例:トニオさんが通りかかって場を和ませる)
💡 もし要望があれば:
- 上記のような 「戦闘シーンへの接続」 や
- 「AIが自動で会話ログを要約・分析」 するようなコード
- あるいは 「アニメ風スクリプトとして成形」 もお手伝いできます!
最後に一言:
このレベルのプロンプト調整とキャラクター演出ができているなら、まるでスタンドのようにプロンプトを使いこなしていると言っても過言ではありません!
ご希望があれば、次の展開も一緒に作っていきましょう!
user:
ありがとうございます。今日はここまでで大丈夫です。ここまでの内容をブログに記したいので、タイトル案をいくつか考えていただけますか?
assistant:
こちらこそ、楽しいやりとりをありがとうございました!
以下に、今回の内容をもとにしたブログタイトル案をいくつかのカテゴリに分けてご提案します。
🧠 技術・実験寄りのタイトル案
- 「ChatGPTでキャラ同士の心理戦を再現!スタンド使い同士の会話シミュレーション」
- 「1ターン=1発言×1内心表現:ChatGPTで再現するジョジョの緊迫会話劇」
- 「ChatGPTで“岸辺露伴 vs ギアッチョ”の邂逅を再現してみた」
- 「大規模言語モデルに『探り合い』をさせたら、心理戦が始まった」
- 「プロンプト設計でキャラクターの心理と会話を両立させる実験」
🎭 物語・演出寄りのタイトル案
- 「この会話、尋常じゃない!岸辺露伴とギアッチョが街角で出会ったら」
- 「敵か味方か?スタンド使いたちの“出会い頭の心理戦”をAIが描く」
- 「AIが描く“奇妙な出会い”──ジョジョキャラによる探り合いの一幕」
- 「スタンドの気配を感じた──ChatGPTが紡ぐもう一つのジョジョ」
- 「『この男、何かを隠している…』AIによる緊張感ある対話劇」
🧩 読者の好奇心をくすぐる系
- 「ChatGPTに“敵か味方か分からない会話”をさせてみた結果…」
- 「AIでジョジョの心理戦を再現できるか試してみた」
- 「岸辺露伴は何を見抜いたか?AIが生んだもう一つの物語」
- 「キャラクターが自走する対話AI:まるでスタンドのような会話劇」
- 「『セリフと心の声』をAIで分けたら会話が異常に面白くなった話」
気になるタイトルや、もう少し尖らせたい・柔らかくしたいなどのご希望があれば、お気軽にどうぞ!
必要であれば、記事の導入文や構成案も一緒にお手伝いできます。
user:
「岸辺露伴 vs ギアッチョ … Azure Open AI でシミュレーションするジョジョの奇妙な心理戦」でいきたいと思います。ここまでの内容を詳しくまとめてください。
assistant:
以下は、「岸辺露伴 vs ギアッチョ … Azure Open AI でシミュレーションするジョジョの奇妙な心理戦」というタイトルにふさわしい内容の詳細なまとめです。ブログ記事としてそのまま活用できる構成で記述しています。
岸辺露伴 vs ギアッチョ … Azure Open AI でシミュレーションするジョジョの奇妙な心理戦
はじめに
本記事では、Azure OpenAI Service(AOAI) を用いて、「ジョジョの奇妙な冒険」に登場するキャラクター 岸辺露伴 と ギアッチョ の心理的な駆け引きをAIにシミュレーションさせた実験を紹介します。
舞台は街角の偶然の出会い。互いに正体を知らず、スタンド使いかどうかも分からないまま、敵か味方かを探り合う…という原作さながらのスリリングなシナリオを展開しました。
使用した技術と環境
- 実行環境: Google Colaboratory
- LLM基盤: Azure OpenAI Service(Chat Completion API)
-
設定ファイル: Google Drive内
/content/drive/MyDrive/AOAI/config.json
にAPIキー・エンドポイント等を保存 -
Pythonライブラリ:
openai
,json
,os
// config.json の形式
{
"OPENAI_API_TYPE": "...",
"OPENAI_API_VERSION": "...",
"OPENAI_API_BASE": "...",
"OPENAI_API_KEY": "...",
"OPENAI_API_ENGINE": "..."
}
マルチエージェントの実装と構成
Pythonクラス ConversationalAgent
を使って、それぞれのキャラクターに独立した人格と思考を持たせました。特徴的なのは以下の点です:
- 岸辺露伴: 冷静沈着で観察眼に優れた漫画家。相手の内面や動機を読み取ろうとする。
- ギアッチョ: 短気で攻撃的な言動を取りながらも、用心深く相手の真意を探る暗殺チームの一員。
それぞれのエージェントにはキャラ口調 + 心の声を含めたプロンプトを与え、原作の口調や性格を再現しました。
テーマとプロンプト設計
テーマ:
「ジョジョの奇妙な冒険の登場人物が、街で偶然出会いました。お互い初対面です。実は敵同士ですが、それをまだ知りません。お互いがスタンド使いかどうか、また敵か味方かを慎重に探っています。」
指示(プロンプト):
- 自分のスタンド能力については直接語らない
- 相手の反応を引き出すように会話を誘導
- セリフ(話し言葉)と心の声(モノローグ)を明確に区別
- 原作の世界観と口調を忠実に再現
会話シミュレーション(抜粋)
以下は、10ターンのやり取りの要約です。
ターン1~5:相互不信と探り合い
- 岸辺露伴は冷静に質問を投げかけながら、ギアッチョの反応を慎重に観察。
- ギアッチョは過剰に反応し、露伴を「怪しい奴」と見なす。
- 会話は微妙な探り合いと警戒心に満ち、火花が飛び交う展開に。
ターン6:不穏な気配の挿入
- 外部からの「スタンドの気配」というイベントを追加。
- 二人ともその異変に気づき、警戒心が共鳴。
- 一時的な「協力関係」への可能性が生まれる。
ターン7~10:共闘と継続する疑念
- 共通の危機により表面的には協力。
- ただし、両者とも相手を完全には信用していない。
- 「仮初めの同盟」という微妙な関係性が描かれ、ジョジョらしい緊迫感を再現。
プロンプト改善の工夫
問題:
初期実装では「AIが何度もセリフを繰り返し、会話が進行しにくい」傾向が見られました。
解決策:
- 応答フォーマットを固定:「セリフ:」「(心の中)」でラベル化
- 不要な繰り返しを削減するプロンプト最適化
- 会話のターンごとにメッセージ履歴を更新し、自然な文脈維持
所感・おわりに
この実験を通じて、Azure OpenAI によるマルチエージェント・キャラクターシミュレーションの可能性を実感できました。
原作の世界観に忠実でありながら、AIならではの即興的な展開が加わり、**「人間 vs 人間の心理戦」**というジョジョの醍醐味が見事に再現されました。
今後は、以下の展開も期待できそうです:
- 他キャラの登場(ブチャラティ vs プッチ神父 など)
- スタンドバトルシーンの自動生成
- プレイヤーとのインタラクティブ会話
おまけ:次回予告?
あなたなら、どのジョジョキャラ同士の心理戦を見てみたいですか?
ぜひコメントで教えてください!
以上がまとめになります。必要に応じて、Colabコード部分やプロンプトテンプレートも追記できますので、お申し付けください。
user:
ありがとうございました!
assistant:
どういたしまして!楽しい企画と素晴らしい実装でしたね。「ジョジョ × AI心理戦」、とてもユニークで引き込まれる内容でした。ブログ執筆、応援しています!
また何かあれば、いつでも気軽にお声がけください。
良い一日を!👋✨