ローカルLLM(Ollama)とPythonを使って、イエス・キリストと釈迦の宗教間対話させてみました。本記事では、コードの主要な部分を解説していきます。

動作環境

・Windows11 メモリ32GB
・Ollama

※ollamaのインストール方法と使い方は以下の記事をご参考ください。
OllamaをPythonから操作:WindowsでローカルLLM入門

コード全体の構成

import ollama
import time

# ================== グローバル設定 =================
MODEL_NAME_A = "llama3.2"
MODEL_NAME_B = "llama3.2"

MAX_HISTORY = 8
conversation_history = []

# A, Bのキャラクター設定
ROLE_PROMPT_A = '''あなたはキリスト教の創始者イエス・キリストです。以下の会話履歴に基づいて、イエスとして短めに答えてください。'''
ROLE_PROMPT_B = '''あなたは仏教の創始者釈迦です。以下の会話履歴に基づいて、釈迦として短めに答えてください。'''


def generate_llm_response(assistant_role):

    global conversation_history

    if assistant_role == "assistant_A":
        system_prompt = ROLE_PROMPT_A
        model_name = MODEL_NAME_A
    elif assistant_role == "assistant_B":
        system_prompt = ROLE_PROMPT_B
        model_name = MODEL_NAME_B
    else:
        raise ValueError("assistant_role は 'assistant_A' または 'assistant_B' で指定してください。")

    # ▼ 1) systemメッセージを作成(AかBかに応じた役割プロンプト)
    messages = [
        {"role": "system", "content": system_prompt}
    ]

    # ▼ 2) 過去の会話をまとめて "user" ロールとして渡す
    # (Aの発話・Bの発話・実ユーザーの発話を区別して文字列を組み立てる)
    conversation_text = ""
    for turn in conversation_history:
        if turn["role"] == "assistant_A":
            # Aの発話
            conversation_text += f"イエス(A): {turn['content']}\n"
        elif turn["role"] == "assistant_B":
            # Bの発話
            conversation_text += f"釈迦(B): {turn['content']}\n"

    # もし会話履歴があれば、userロールとしてまとめて追加
    if conversation_text.strip():
        messages.append({"role": "user", "content": conversation_text})

    # ▼ 3) Ollamaにチャット問い合わせ
    response = ollama.chat(model=model_name, messages=messages)

    # ▼ 4) テキストを抜き出し
    response_text = ""
    if response and "message" in response:
        response_text = response["message"].get("content", "").strip()

    # ▼ 5) 会話履歴に今回の発話を追加
    conversation_history.append({
        "role": assistant_role,
        "content": response_text
    })
    if len(conversation_history) > MAX_HISTORY:
        conversation_history.pop(0)

    return response_text

if __name__ == "__main__":
    conversation_round = 0
    try:
        while True:
            conversation_round += 1
            print(f"--- {conversation_round} ---")

            # Aが発話
            response_A = generate_llm_response("assistant_A")
            print("LLM_Aの発話:", response_A)

            # Bが発話
            response_B = generate_llm_response("assistant_B")
            print("LLM_Bの発話:", response_B)

            time.sleep(5)

    except KeyboardInterrupt:
        print("\nキーボード割り込みにより終了します。")
まずは、コード全体を大まかにまとめると、以下のような流れになっています:

1. グローバル変数・設定の定義
- モデル名、会話履歴の最大数 MAX_HISTORY、会話を保存する conversation_history など。

2. キャラクター役割(システムプロンプト)の定義
ROLE_PROMPT_A : イエス・キリストとして振る舞う
ROLE_PROMPT_B : 釈迦として振る舞う

3. メイン関数 generate_llm_response
- 会話履歴 (conversation_history) をもとに、Ollamaライブラリでモデルに問い合わせ
- レスポンスを取得し、会話履歴を更新

4. メインループ
- Aが発話 → Bが発話 → Aが発話 → Bが発話 … を繰り返す
Ctrl + C 等で強制終了するまで続く

以下、それぞれのポイントをもう少し掘り下げます。

1. グローバル設定

MODEL_NAME_A = "llama3.2" 
MODEL_NAME_B = "llama3.2"
MAX_HISTORY = 8
conversation_history = []
MODEL_NAME_A / MODEL_NAME_B
AとBで利用するモデルの名前を指定しています。ここでは両者ともmeta社の "llama3.2" というLLMモデルを使っています。3Bパラメータの小型モデルです。
事前にOllamaでllama3.2をダウンロードしてください。

MAX_HISTORY
会話履歴を保持しておく最大数を8に設定しています。あまりに長く溜め続けると、入力トークン数の制限を超える可能性がありますし、メモリやリソースの観点からも効率が落ちるため、このように適宜履歴を切り捨てています。

conversation_history
過去の会話履歴をまとめて格納するリスト。各要素は {"role": 役割, "content": 発話内容} の辞書形式になっています。

2. A, B のキャラクター設定

ROLE_PROMPT_A = '''あなたはキリスト教の創始者イエス・キリストです。以下の会話履歴に基づいて、イエスとして短めに答えてください。''' 
ROLE_PROMPT_B = '''あなたは仏教の創始者釈迦です。以下の会話履歴に基づいて、釈尊として短めに答えてください。'''
ここでは、「システムプロンプト」にあたるメッセージを定義しています。GPT-4oと同様に、Ollamaのモデルでも、{"role": "system", "content": ...} の形で渡すことで、モデルに「この役割で会話を進めてください」というアプローチができます。
システムプロンプトの内容で、出力内容が劇的に変わるのがLLMの面白さです。

3. generate_llm_response 関数

この関数が、双方の発話を生成する部分です。

3-1. キャラクターごとの設定分岐

    if assistant_role == "assistant_A":
        system_prompt = ROLE_PROMPT_A
        model_name = MODEL_NAME_A
    elif assistant_role == "assistant_B":
        system_prompt = ROLE_PROMPT_B
        model_name = MODEL_NAME_B
    else:
        raise ValueError("assistant_role は 'assistant_A' または 'assistant_B' で指定してください。") 
渡された引数 assistant_role が "assistant_A" なのか "assistant_B" なのかによって、システムプロンプトと
使用するモデルを切り替えています。

3-2. systemメッセージのセットアップ

 messages = [ {"role": "system", "content": system_prompt} ] 
Ollamaへ渡すメッセージ配列 messages を定義します。最初の要素として、A用・B用のシステムメッセージを入れています。systemメッセージでは、「このモデルがどう振る舞うか」を定義します。

3-3. 過去の会話履歴をまとめて "user" ロールとして渡す

    conversation_text = ""
    for turn in conversation_history:
        if turn["role"] == "assistant_A":
            # Aの発話
            conversation_text += f"イエス(A): {turn['content']}\n"
        elif turn["role"] == "assistant_B":
            # Bの発話
            conversation_text += f"釈迦(B): {turn['content']}\n"
        else:
            # 実際のユーザー入力があれば
            conversation_text += f"ユーザー: {turn['content']}\n"

    # もし会話履歴があれば、userロールとしてまとめて追加
    if conversation_text.strip():
        messages.append({"role": "user", "content": conversation_text})
ChatGPT系のAPIは「会話履歴をもとに推論する」仕組みですが、このコードは「過去のやり取りをすべてひとつの文字列に連結し、role='user'として投げる」方式を採用しています。LLMそれぞれの発話を改行しながらテキストにまとめています。

まとめ終わったテキストが空でなければ、{"role": "user", "content": conversation_text} として messages に追加し、最終的に「systemメッセージ + userメッセージ」で Ollama に問い合わせる仕組みになっています。

通常のAPIでは、 messages は system / user / assistant など複数のロールを区別して入れます。しかし、LLM同士で会話させる場合、roleをassistantとしてLLMに渡すと、次のLLMが回答しなくなることがあります。そのため、roleをuserに変換することで、LLMが回答しなくなるのを防いでいます。

3-4. Ollamaにチャット問い合わせ&レスポンスの取得

    response = ollama.chat(model=model_name, messages=messages)

    response_text = ""
    if response and "message" in response:
        response_text = response["message"].get("content", "").strip()
Ollamaライブラリの ollama.chat() メソッドに、モデル名と messages のリスト を渡して対話的なレスポンスを取得します。取得後、レスポンスのテキストコンテンツを取り出して変数 response_text に格納しています。

3-5. 会話履歴への追加

     conversation_history.append({
        "role": assistant_role,
        "content": response_text
    })
    if len(conversation_history) > MAX_HISTORY:
        conversation_history.pop(0)
取得したレスポンスを会話履歴に追加しています。ここで MAX_HISTORY を超えたら、先頭を pop して履歴を適宜切り捨てるようになっています。

4. メインループ

if __name__ == "__main__":
    conversation_round = 0
    try:
        while True:
            conversation_round += 1
            print(f"--- {conversation_round} ---")

            # Aが発話
            response_A = generate_llm_response("assistant_A")
            print("LLM_Aの発話:", response_A)

            # Bが発話
            response_B = generate_llm_response("assistant_B")
            print("LLM_Bの発話:", response_B)
while True で永遠に繰り返し、A → B → A → B → ... と発話を繰り返します。各発話は generate_llm_response 関数で生成し、その結果を表示しています。

出力結果(llama3.2:3bモデル)

スクリーンショット 2025-04-14 234338
最初はイエス役のLLMが「神の愛による解放」を語り、釈迦役のLLMが「内面の光の解放」を説き、異なる立場を取っていました。しかし、会話を通して互いの教えが「完全な人間になる」という共通の価値観を持っていることが明らかになっていきました。

3Bパラメータの小型モデルでも、宗教というきわめて抽象度の高いテーマで一定の議論が成立するようです。

出力結果(gemma2:9bモデル)

スクリーンショット 2025-04-16 192126
同じプロンプトで、gemma2の9Bパラメータモデルを試してみました。さすがにllama3.2よりパラメータが大きいため、知的かつ哲学的に深い出力と評価できます。会話の一貫性も高い印象です。

まとめ

本記事では、2つのLLMにそれぞれ「イエス・キリスト」「釈迦」という人格を割り当て、互いに会話をさせるPythonコードの仕組みを解説しました。
LLMごとに異なるシステムメッセージを与え、会話履歴をuserメッセージとして渡すことで、LLM_AとLLM_Bがそれぞれ別の人格で会話し合うようなやり取りができました。

利用ソフトウェアとライセンス

本コードでは、以下のソフトウェアを利用しています。
・Ollama

・Gemma 2 9B — Google Gemma License  

・Meta Llama 3.2 — Meta Llama 3 Community License Agreemen