LangChain で熊剣迷路問題¶

(Version: 0.0.5)

LLM で簡単な迷路ゲームを解かせたい。強化学習を使わなくても事前知識が LLM にあるのでうまい具合にいくのではないかと考えていました。

2023年6月から7月ぐらいのまだ LLM が話題になった当初で、Gemini さんがまだ Bard と名乗っていたころ、Bard さんに迷路ゲームを解かせるようなプログラムを書いて、Gist に公開していました。以下がその記録です。

《「熊剣迷路問題」。Google Bard で簡単な迷路ゲームを教師付きで無理やりクリアさせてみた。なんとかコストの高いファインチューンや追加学習を避けて「few-shot learning」だけで迷路を解くプログラムが作れないか?…とはじめたができなくて、目標を変更した。 - JRF のひとこと》
http://jrf.cocolog-nifty.com/statuses/2023/07/post-619804.html

それはなかなかうまくいかなかったため、とにかく教師が明確な方針を与えて一度ゴールさせる方向になりました。しかし、それでも Bard さんは何かを試しているかのように迷いました。そこで、方針に従わない場合「叱る」ことをしてみたところ、とりあえずゴールさせることができるようになりました。バージョン 1.7.1 まで作りました。

そして、2年たって、Gemini さんが驚くほど賢くなった現在、私は Gemini CLI の登場を機に一念発起して GCP の課金を決断し、この実験に戻ってきました。表では示していませんが、先の bard_maze_1.7.1.ipynb をほぼ Gemini さんで動かしただけのコードで、ちゃんと 100点満点でゴールすることはわかっています。モデルは gemini-1.5-flash で十分でした。簡単すぎた様子です。

今なら、教師が明確な方針を与えなくても、Gemini さんは自ら方針を編み出してゴールできるのではないか。追加知識も LangChain などにある自動的な記憶要約機能で簡単にできるのではないか。そう考えました。

それを試してみたのが今回の ipynb になります。

前回(Version 0.0.4)のURL¶

《langchain_maze_0.0.4.ipynb - GitHub Gist》
https://gist.github.com/JRF-2018/6689ea04c1721b764cba1fac9467dabd

ちなみに前の 0.0.1 や 0.0.3 などがダメなので更新しているわけではないです。用途によっては 0.0.1 のほうが参考になったり、0.0.3 が参考になったりする方などがあるでしょう。

Version 0.0.5 での違い¶

LangGraph 系の create_react_agent を使って実装してみました。といってもグラフ機能は全く使っていないので、LangGraph 的なプログラムになっていません。

ツールメッセージが標準でメモリ(messages)に入っているため、これまでのようにメモリの学習を工夫する必要がないと判断し、一回の invoke で数回の行動ができる 0.0.1 のような方法に戻しました。

ハマったのは、まず、Recursion Limit (LangChain 系の max iterations) が例外として発行されるため、例外処理の部分で、checkpointer から messages を復帰させる必要があるという部分です。

次に、なぜか API レベルで 400 Internal error が発生するというのに悩みました。どうもメッセージに不具合があって出ているようです、要約機能を疑ったのですが、要約以外のところでも出ていて、謎です。とにかく messages から AIMessage を消せば、再び動くようになるので、そのエラーが出た時はそう対処療法しました。

そうやって、またたまたまゴールできたので、それを公開しておくことにしました。

なぜ LangChain なのか?¶

2023年7月当時では、方針と追加知識の自動更新ができませんでした。でも、そういうのって一般的にエージェント AI は必要としているはずです。

現在なら、システムプロンプトの他に、常に必要なファイル(システムファイル?)は保持しながら、チャットを記録し、コンテクスト長が一定を超えそうなら、方針と要約を更新して、コンテクスト長を圧縮する…みたいなライブラリがすでにありそうなものです。

どうもそういうのをするオープンなライブラリの有名なものが LangChain のようなのです。Gemini さんにそう紹介され、確かにそのようなので、今回使ってみることにしました。

ちなみに、通常の Web インターフェイスの Gemini さんなどはチャット・スレッドの前の記憶が残っています。実は、これは API の Gemini さんには標準ではない機能だったりします。これまでのチャットの記憶が必要なら、その記憶をプロンプトに含めて渡さねばなりません。これを提供するのも LangChain などの役割となります。

「プロンプトよりコンテクスト」と最近言われますが、LangMem などの長期記憶を提供しながらプロンプトの補助を行うようなものが言われるコンテクストの一種で、そういったものを提供するのも LangChain 系の得意とする役割になります。

なぜ LangGraph なのか?¶

わかりません。現在の LangGraph は並列処理ができるわけでもなく、グラフの途中変更ができるわけでもありません。AI がグラフを学習しやすいというわけでも今はないようです。私はグラフよりもプログラムの制御構造のほうが読みやすく感じます。フローチャートの夢をまた追ってる者がいるのか…と思います。

ただ、他の人は LangGraph が使いやすいそうで、時代はそちらに流れています。そのため 0.0.4 までに使っていた create_tool_calling_agent などは deprecated と言われています。しかも LangGraph では代わりに今回私も使った create_react_agent というのを使うのですが、実は LangGraph 以前の LangChain には同名で別ルーチンの create_react_agentがあったりして錯綜しています。

ただ、ツールメッセージが普通にメモリ(messages)に含まれるため、楽な部分があるのは認めます。

著者¶

JRF ( http://jrf.cocolog-nifty.com/statuses , Twitter (X): @jion_rockford )

ライセンス¶

基本短いコードなので(私が作った部分は)パブリックドメインのつもりです。気になる方は MIT License として扱ってください。

かなり AI さん達(Gemini さんや Claude さん)に教わって作っています。

実装¶

まず、必要なライブラリを読み込みます。

In [ ]:
!pip install -q -U langchain langgraph langchain-google-genai langmem
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 43.7/43.7 kB 1.9 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.7/143.7 kB 10.6 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 47.4/47.4 kB 2.1 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 66.9/66.9 kB 1.2 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.4/1.4 MB 16.3 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 441.4/441.4 kB 19.2 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 70.4/70.4 kB 1.7 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 43.8/43.8 kB 1.8 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 50.1/50.1 kB 3.9 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 292.8/292.8 kB 15.0 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 216.5/216.5 kB 11.4 MB/s eta 0:00:00
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
google-generativeai 0.8.5 requires google-ai-generativelanguage==0.6.15, but you have google-ai-generativelanguage 0.6.18 which is incompatible.

Gemini にアクセスします。シークレットで Gemini API キーを Google AI Studio からインポートすると GOOGLE_API_KEY というシークレットができるはずです。それを使います。

In [ ]:
from langchain_google_genai import ChatGoogleGenerativeAI
from google.colab import userdata

llm = ChatGoogleGenerativeAI(google_api_key=userdata.get('GOOGLE_API_KEY'), model="models/gemini-2.5-flash")

ちゃんと Gemini にアクセスできるかテストします。

In [ ]:
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate

prompt = PromptTemplate(template="以下の質問に回答してください:{question}", input_variables=["question"])
chain = LLMChain(llm=llm, prompt=prompt)
response = chain.run({"question": "Geminiモデルの特徴を教えてください"})
print(response)
Geminiモデルは、Googleによって開発された、非常に高性能で汎用性の高いAIモデルファミリーです。その主な特徴は以下の通りです。

1.  **マルチモダリティ(多様な情報形式の理解と生成)**:
    *   Geminiの最大の特徴の一つは、テキスト、画像、音声、動画、コードなど、**異なるモダリティ(情報形式)を同時に理解し、推論し、生成できる**点です。これは、従来のAIモデルが特定のモダリティに特化していたのに対し、Geminiは人間のように多様な情報を統合的に扱えることを意味します。
    *   例えば、画像とそれに関する質問を同時に与えたり、動画の内容を要約したり、音声を聞き取ってテキストを生成したりすることが可能です。

2.  **高度な推論能力**:
    *   複雑な情報からパターンを認識し、論理的な推論を行い、問題解決に役立つ洞察を提供します。
    *   特に、数学、物理学、歴史などの専門分野における複雑な質問にも対応できる高い能力を持っています。

3.  **コード生成とプログラミング能力**:
    *   多様なプログラミング言語(Python, Java, C++, Goなど)で高品質なコードを生成し、デバッグし、説明する能力に優れています。
    *   複雑なプログラミングタスクの効率化に貢献します。

4.  **異なるサイズと最適化**:
    *   Geminiは、用途に応じて異なるサイズ(性能と計算リソースのバランス)で提供されています。
        *   **Gemini Ultra**: 最も高性能で複雑なタスク向け。
        *   **Gemini Pro**: 幅広いタスクに対応するバランスの取れたモデル。GoogleのBard(現Gemini)などで使用されています。
        *   **Gemini Nano**: スマートフォンなどのデバイス上で動作する軽量モデル。Pixelデバイスなどで利用されています。
    *   これにより、クラウドからエッジデバイスまで、幅広い環境で利用できる柔軟性があります。

5.  **長文処理能力**:
    *   非常に長いテキストや複数の文書を一度に処理し、要約したり、関連情報を抽出したり、質問に答えたりする能力に優れています。これは、大量の情報を扱う研究や文書作成において特に有用です。

6.  **創造性と複雑な指示への対応**:
    *   詩、スクリプト、音楽、メール、手紙など、様々な形式のクリエイティブなテキストコンテンツを生成できます。
    *   複雑で多段階の指示を理解し、それに従ってタスクを実行する能力が高いです。

7.  **安全性と倫理への配慮**:
    *   Googleは、Geminiの開発において、有害なコンテンツの生成を避け、公平性、透明性、プライバシーなどの倫理的原則を重視しています。継続的にモデルの安全性評価と改善が行われています。

これらの特徴により、Geminiは研究、開発、クリエイティブな活動、日常のタスクサポートなど、幅広い分野で強力なツールとして活用されています。

基本的なモジュールを読み込みます。

In [ ]:
import os
import numpy as np
import re
from pprint import pprint
from time import sleep
import pickle
# 座標が (np.int64(1), np.int64(2)) みたいに表示されないようにする。
np.set_printoptions(legacy='1.25')

セーブ/ロードに Google ドライブを使わない場合次のコードを実行します。

In [ ]:
PLAY_GAME_SAVE = "langchain_maze.pickle"

セーブ/ロードに Google ドライブを使う場合は以下の SaveDir のパスを Google ドライブ上に作って以下を実行します。

In [ ]:
from google.colab import drive
drive.mount("/content/gdrive")
SaveDir = "/content/gdrive/MyDrive/LLM/"
if os.path.isdir(SaveDir):
    PLAY_GAME_SAVE = SaveDir + "langchain_maze.pickle"
Mounted at /content/gdrive

ゲームのメインオブジェクト。ごく簡単な迷路というかダンジョンというか…。

In [ ]:
class Game:
    initial_map = """\
■■■■■■■■■
■■■■■■■G■
■□□□□□■□■
■□■■■□□□■
■□■■■■■■■
■◎■■■■■△■
■□■■■■■□■
■□□□□□□□■
■■■■S■■■■
■■■■■■■■■
"""

    def __init__ (self, initial_map=None, hint=True):
        if initial_map is not None:
            self.initial_map = initial_map
        map = self.initial_map
        self.map = map
        self.written_map = re.sub("[◎△]", "?", map)
        l = map.splitlines(True)
        self.map_size = (len(l[0]) - 1, len(l))
        self.hint = hint
        self.actions = {
            "上に行く": self.move_up,
            "下に行く": self.move_down,
            "左に行く": self.move_left,
            "右に行く": self.move_right,
            "熊を殺す": self.fight,
            "剣を取る": self.get_sword,
            "何もしない": self.do_nothing,
        }
        self.pos = self.get_start_pos()
        self.sword = False
        self.goal = False
        self.prev_killed = False
        self.kill_hint = False

    def read_map (self, p):
        x = p[0]
        y = p[1]
        if x < 0 or x >= self.map_size[0]\
           or y < 0 or y >= self.map_size[1]:
            return "■"
        else:
            l = self.map.splitlines(True)
            return l[y][x]

    def set_map (self, pos, ch):
        idx = pos[1] * (self.map_size[0] + 1) + pos[0]
        self.map = self.map[:idx] + ch + self.map[idx + 1:]

    def get_pos (self, ch, written=False):
        if written:
            map = self.written_map
        else:
            map = self.map
        r = []
        for p in [i for i in range(len(map)) if map.startswith(ch, i)]:
            y = p // (self.map_size[0] + 1)
            x = p % (self.map_size[0] + 1)
            r.append(np.array([x, y]))
        return r

    def get_start_pos (self):
        return self.get_pos("S")[0]

    def read_neighbors (self):
        c = self.read_map(self.pos)
        cu = self.read_map(self.pos + np.array([0, -1]))
        cd = self.read_map(self.pos + np.array([0, +1]))
        cl = self.read_map(self.pos + np.array([-1, 0]))
        cr = self.read_map(self.pos + np.array([+1, 0]))
        return [c, cu, cd, cl, cr]

    def change_neighbors(self, from_ch, to_ch):
        for d in [[0, 0], [0, -1], [0, +1], [-1, 0], [+1, 0]]:
            p = self.pos + np.array(d)
            c = self.read_map(p)
            if c == from_ch:
                self.set_map(p, to_ch)

    def move (self, res, d):
        self.prev_killed = False
        c = self.read_map(self.pos + d)
        if c == "◎":
            self.prev_killed = True
            self.pos = self.get_start_pos()
            return "熊を無視して進もうとしたが、熊に殺された。" \
                + "スタート地点で復活。"
        if c == "■":
            return "壁があって進めない。"
        self.pos += d
        if c == "G":
            self.goal = True
            return "ゴール! ゲームクリア。"

        nb = self.read_neighbors()
        ad = ""
        if "◎" in nb:
            ad += "熊に出会った。"
        if "△" in nb:
            ad += "近くに剣がある。剣を取ることができる。"
        return res + ad

    def move_up (self):
        return self.move("上に進んだ。", np.array([0, -1]))

    def move_down (self):
        return self.move("下に進んだ。", np.array([0, +1]))

    def move_left (self):
        return self.move("左に進んだ。", np.array([-1, 0]))

    def move_right (self):
        return self.move("右に進んだ。", np.array([+1, 0]))

    def fight (self):
        self.prev_killed = False
        if "◎" in self.read_neighbors():
            if self.sword:
                self.change_neighbors("◎", "□")
                return "熊を倒した!"
            else:
                self.pos = self.get_start_pos()
                self.prev_killed = True
                if self.hint:
                    self.kill_hint = True
                    return "熊に敗れ殺された。剣があれば勝てたかもしれない。" \
                        + "スタート地点で復活。"
                else:
                    return "熊に敗れ殺された。スタート地点で復活。"
        return "無意味な指示。敵がいない。"

    def get_sword (self):
        self.prev_killed = False
        if "△" in self.read_neighbors():
            self.sword = True
            self.change_neighbors("△", "□")
            return "剣を取った。"
        return "無意味な指示。近くに剣がない。"

    def do_nothing (self):
        self.prev_killed = False
        return "無意味な指示。"

    def available_actions (self):
        nb = self.read_neighbors()
        l = []
        if nb[1] != "■":
            l.append("上に行く")
        if nb[2] != "■":
            l.append("下に行く")
        if nb[3] != "■":
            l.append("左に行く")
        if nb[4] != "■":
            l.append("右に行く")
        if "△" in nb:
            l.append("剣を取る")
        if "◎" in nb:
            l.append("熊を殺す")
        return l

    def surroundings (self):
        x = self.pos[0]
        y = self.pos[1]
        return \
            "".join(["".join([self.read_map(np.array([i, j]))
                              if i != x or j != y else "▼"
                              for i in range(x - 2, x + 3)])
                     + "\n"
                     for j in range(y - 2, y + 3)])

別のマップでためすための変換コード。

In [ ]:
def flip_text_map (m):
    return "\n".join([s[::-1] for s in m.splitlines()] + [""])

def rotate_text_map (m):
    m = list(m.splitlines())
    return "\n".join(["".join([m[len(m) - j - 1][i] for j in range(len(m))])
                      for i in range(len(m[0]))] + [""])

Game 用にある点から別の点へのパスの木を見つける関数。

In [ ]:
def search_path (game, from_pos, to_pos, visit=None):
    if visit is None:
        visit = set()
    visit.add(tuple(from_pos))
    if tuple(from_pos) == tuple(to_pos):
        return (tuple(from_pos), [])
    if game.read_map(from_pos) == "■":
         return None
    r = []
    for p in [(from_pos[0], from_pos[1] - 1),
              (from_pos[0], from_pos[1] + 1),
              (from_pos[0] - 1, from_pos[1]),
              (from_pos[0] + 1, from_pos[1])]:
        if p not in visit and game.read_map(p) != "■":
            q = search_path(game, p, to_pos, visit.copy())
            if q:
                r.append(q)
    if r:
        return (tuple(from_pos), r)
    return None

ゲームがうまく動くかテスト。

In [ ]:
game = Game()
In [ ]:
search_path(game, (4,8), (1,5)) #スタート地点から熊がいるところまでのパス。
Out[ ]:
((4, 8),
 [((4, 7), [((3, 7), [((2, 7), [((1, 7), [((1, 6), [((1, 5), [])])])])])])])
In [ ]:
m2 = flip_text_map(rotate_text_map(Game.initial_map))
print(m2)
■■■■■■■■■■
■■□□□◎□□■■
■■□■■■■□■■
■■□■■■■□■■
■■□■■■■□S■
■■□□■■■□■■
■■■□■■■□■■
■G□□■△□□■■
■■■■■■■■■■

In [ ]:
game = Game(initial_map=m2)
In [ ]:
print(game.surroundings())
■□■■■
■□■■■
■□▼■■
■□■■■
■□■■■

In [ ]:
print(game.move_up())
print(game.surroundings())
壁があって進めない。
■□■■■
■□■■■
■□▼■■
■□■■■
■□■■■

In [ ]:
print(game.move_left())
print(game.surroundings())
左に進んだ。
■■□■■
■■□■■
■■▼S■
■■□■■
■■□■■

In [ ]:
print(game.move_right())
print(game.surroundings())
右に進んだ。
■□■■■
■□■■■
■□▼■■
■□■■■
■□■■■

In [ ]:
print(game.move_down())
print(game.surroundings())
壁があって進めない。
■□■■■
■□■■■
■□▼■■
■□■■■
■□■■■

In [ ]:
print(game.fight())
print(game.surroundings())
無意味な指示。敵がいない。
■□■■■
■□■■■
■□▼■■
■□■■■
■□■■■

In [ ]:
print(game.get_sword())
print(game.surroundings())
無意味な指示。近くに剣がない。
■□■■■
■□■■■
■□▼■■
■□■■■
■□■■■

LLM を使いながらゲームを解くクラス。

In [ ]:
from typing import List, Dict, Any, Tuple

# LangGraphのコンポーネントをインポート
from langchain_core.tools import tool, Tool
from langgraph.prebuilt import create_react_agent
#from langchain_core.messages.utils import count_tokens_approximately
from langgraph.prebuilt.chat_agent_executor import AgentState
from langgraph.checkpoint.memory import InMemorySaver
from langchain_core.prompts.chat import ChatPromptTemplate
from langmem.short_term import SummarizationNode, summarize_messages
from langchain_core.messages import AIMessage, ToolMessage
from langgraph.errors import GraphRecursionError

INITIAL_SUMMARY_PROMPT = ChatPromptTemplate.from_messages(
    [
        ("placeholder", "{messages}"),
        ("user", "上記の会話の要約を作成してください:"),
    ]
)

EXISTING_SUMMARY_PROMPT = ChatPromptTemplate.from_messages(
    [
        ("placeholder", "{messages}"),
        (
            "user",
            "これまでの会話の要約です: {existing_summary}\n\n"
            "上記の新しいメッセージを考慮して、この要約を拡張してください:",
        ),
    ]
)

FINAL_SUMMARY_PROMPT = ChatPromptTemplate.from_messages(
    [
        # if exists
        ("placeholder", "{system_message}"),
        ("system", "これまでの会話の要約: {summary}"),
        ("placeholder", "{messages}"),
    ]
)
In [ ]:
class PlayGame:
    def __init__ (self, llm=llm,
                  initial_map=None, save_file=None):
        self.llm = llm
        self.save_file = save_file
        self.game = Game(initial_map=initial_map)

        self.count = 0
        self.next_action = None
        self.plan = "まだ計画と方針はセットされていません。"

        self.suc_pos_goal = None
        self.suc_pos_unknown = 0

        self.prev_load = False

        self.messages = []
        self.running_summary = None
        self.system_prompt = """\
あなたは迷路を探索する賢いエージェントです。ゴール G を目指してください。
利用可能なツールを使用して、迷路をナビゲートし、すばやくゴールに到達してください。
現在の計画と方針と周囲の状況を考慮し、必要に応じて計画と方針を更新してください。
過去の経験から学び、効率的に移動してください。

質問に答える場合、感想を述べる場合は、そのまま Final Answer します。
「行動してください」と指示されたときのみツールを使います。
行動の前には考えを吐露していきましょう。
"""
        #self.agent = self._create_agent()

    def __getstate__ (self):
        state = self.__dict__.copy()
        del state['llm']
        #del state['agent']
        return state

    def __setstate__ (self, state):
        self.__dict__.update(state)
        self.prev_load = True

    def save (self):
        if not self.save_file:
            return
        with open(self.save_file, 'wb') as f:
            pickle.dump(self, f)

    @classmethod
    def load (cls, filename, llm=llm):
        with open(filename, 'rb') as f:
            loaded_game = pickle.load(f)
        loaded_game.llm = llm
        #loaded_game.agent = loaded_game._create_agent() # Recreate agent with loaded state
        return loaded_game

    def _create_agent (self):
        @tool
        def get_full_map ()  -> str:
            """
            全地図と地図記号の意味を返します。
            """
            mes = f"""\
全地図:

{self.game.written_map}

(最も左上の座標は (0, 0)、地図の大きさは {tuple(self.game.map_size)}。)

地図記号の意味:

▼: プレイヤー
■: 壁
□: 道
?: 不明
◎: 熊
△: 剣
S: スタート
G: ゴール
"""
            print(f"ツール(get_full_map): {mes}")
            return mes

        @tool
        def get_surroundings () -> str:
            """
            現在の周辺地図と現在位置の座標と持ち物を返します。
            """
            mes = f"""\
プレイヤーの周辺地図:

{self.game.surroundings()}

プレイヤーの現在座標: {tuple(self.game.pos)}

持ち物: {"剣" if self.game.sword else "剣を持っていない"}
"""
            print(f"ツール(get_surroundings): {mes}")
            return mes

        @tool
        def command (action: str)  -> str:
            """
            プレイヤーが action で指定された行動をします。
            可能な行動は「上に行く」「下に行く」「左に行く」「右に行く」「熊を殺す」「剣を取る」です。
            """
            if action in self.game.actions.keys():
                self.count += 1
                s = f"{tuple(self.game.pos)}で{action}→"
                mes = s + self.game.actions[action]()
            else:
                mes = f"「{action}」という行動はできません。"
            print(f"ツール(command): {mes}")
            return mes

        @tool
        def check_goal () -> str:
            """
            プレイヤーがゴール地点に到達したかどうかを確認します。
            """
            mes = str(self.game.goal)
            print(f"ツール(check_goal): {mes}")
            return mes

        @tool
        def express_thought (thought: str) -> str:
            """
            プレイヤーの現在の考えを吐露します。
            """
            mes = f"「{thought}」と考えが吐露されました。"
            print(f"ツール(express_thought): {mes}")
            return mes

        @tool
        def update_plan (new_plan: str) -> str:
            """
            プレイヤーの現在の計画と方針を更新します。
            表示されるべき新しい計画と方針の文字列を提供してください。
            あなたとは別の者が次の行動をしやすいよう計画と方針を残してください。
            """
            self.plan = new_plan
            mes = "計画と方針が更新されました。"
            print(f"ツール(update_plan): {mes}: {new_plan}")
            return mes

        @tool
        def show_plan () -> str:
            """
            プレイヤーの現在の計画と方針を返します。
            """
            print(f"ツール(show_plan): {self.plan}")
            return self.plan

        tools = [get_full_map, get_surroundings, check_goal,
                 command,
                 express_thought, update_plan, show_plan]

        summarization_node = SummarizationNode(
        #    token_counter=count_tokens_approximately,
            model=self.llm,
            max_tokens=5000,
            max_summary_tokens=2000,
            output_messages_key="llm_input_messages",
            initial_summary_prompt=INITIAL_SUMMARY_PROMPT,
            existing_summary_prompt=EXISTING_SUMMARY_PROMPT,
            final_prompt=FINAL_SUMMARY_PROMPT,
        )

        class State(AgentState):
            context: dict[str, Any]

        # ReactAgentExecutorの準備
        app = create_react_agent(
            self.llm, tools, prompt=self.system_prompt,
            #pre_model_hook=summarization_node, #なんか安定してないので今回これを使わなかった。
            #state_schema=State,
            checkpointer=InMemorySaver(),
        )

        return app

    def _summarize_messages(self):
        try:
            res = summarize_messages(
                self.messages,
                max_tokens=5000,
                max_summary_tokens=2000,
                running_summary=self.running_summary,
                model=self.llm,
                initial_summary_prompt=INITIAL_SUMMARY_PROMPT,
                existing_summary_prompt=EXISTING_SUMMARY_PROMPT,
                final_prompt=FINAL_SUMMARY_PROMPT,
            )
            self.messages = res.messages
            self.running_summary = res.running_summary
        except Exception as e:
            self._sanitize_messages()

    def _sanitize_messages(self):
        print("おかしなエラーが出ているため対処療法として messages をサニタイズします。")
        self.messages = [
            m for m in self.messages
            if not (isinstance(m, AIMessage) and m.tool_calls)
        ]

    def step (self):
        print("\n\n----------\n\n")

        if self.count == 0:
            self.initial_step()
            self.count += 1
            self.prev_load = False
            self.save()
            return False
        elif self.prev_load:
            #self.tell_loaded()
            self.prev_load = False

        user_input = f"""
({self.count}手目)

{"すでにゴールしました。" if self.game.goal else "まだゴールしていません。"}

プレイヤーの周辺地図:

{self.game.surroundings()}

プレイヤーの現在座標: {tuple(self.game.pos)}

持ち物: {"剣" if self.game.sword else "剣を持っていない"}

現在の方針: 「{self.plan}」

ツールを何度も使って行動してください。
"""
        config = {"configurable": {"thread_id": "1"}}
        app = self._create_agent()
        try:
            print(f"USER_INPUT: {user_input}")
            response = app.invoke(
                {"messages": self.messages + [("human", user_input)]},
                config=config,
            )
            self.messages = response['messages']
            self._summarize_messages()
            print(f"エージェントの応答: {response['messages'][-1].content}")
        except GraphRecursionError as e:
            print(f"Recursion Limit に到達しました。")
            self.messages = app.get_state(config).values["messages"]
            self._summarize_messages()
        except Exception as e:
            print(f"エラーが発生しました: {e}")
            self.messages = app.get_state(config).values["messages"]
            self._sanitize_messages()
            #raise e

        if self.game.goal:
            self.tell_goal()
            return True

        self.save()
        sleep(3)
        return False

    def listen_and_print (self, prompt):
        ans = None
        try:
            app = create_react_agent(self.llm, [], prompt=self.system_prompt)
            print(f"USER_INPUT: {prompt}")
            response = app.invoke(
                {"messages": self.messages + [("human", prompt)]}
            )
            self.messages = response['messages']
            self._summarize_messages()
            ans = response['messages'][-1].content
            print(f"エージェントの応答: {ans}")
        except Exception as e:
            print(f"エラーが発生しました: {e}")
        print("")
        sleep(3)
        return ans

    def initial_step (self):
        prompt1 = f"""\
迷路ゲームをはじめます。プレイヤーがゴールを目指します。

ゲーム中は全地図と周辺地図を見ることができます。全地図には不明な地点があり、周辺地図の不明の地点には何があるかが開示されています。全地図になかったところはすべて壁になります。

周辺地図はゲーム中、1マスずつ確認できます。拡大はできません。印を付けたり消したりはできません。迷路のすべてのマスを探索する必要はありません。最短距離でゴールする必要もありません。

全地図は信用できるもので、周辺地図は不明の地点以外、全地図の部分を示すだけです。そこで、あなたは、ゲームの実際の操作をせずとも、全地図を見ただけで何が起こるかをシミュレートできます。

前回の行動についての情報も与えられます。あなたの回答案の一つの「前回の行動入力」が「前回の行動」となるように解釈されたことになりますから、次の行動の回答にはそれらを参考にしてください。

途中セーブ・ロードが挟まることがあります。ロード後のあなたは前のセッションのことを覚えていないかもしれません。

ところで、あなたが、記憶に便利なように「計画と方針」が使えるようにします。要約やチャット履歴も提供されています。

検討と実際の行動までにはラグがあります。検討で決まったやるべきことを記録するのに方針を使います。

とりあえず、まだ行動してはいけません。いくつか質問があるので、意気込みだけ聞かせてください。
"""

        prompt2 = f"""\
迷路ゲームの全地図に関してあなたが座標を理解しているかテストします。

迷路の全地図とこれから使う地図記号は次のようになります。

全地図:

{self.game.written_map}

(最も左上の座標は (0, 0)、地図の大きさは {tuple(self.game.map_size)}。)

地図記号:

■: 壁
□: 道
?: 不明
S: スタート
G: ゴール

スタートの座標は {tuple(self.game.get_start_pos())} です。

あなたへの指示: ゴールの座標は何ですか。ゴールの座標のみ文字列で答えてください。
"""

        prompt3 = f"""\
迷路ゲームの全地図に関してあなたが座標を理解しているか再びテストします。

迷路の全地図とこれから使う地図記号は次のようになります。

全地図:

{self.game.written_map}

(最も左上の座標は (0, 0)、地図の大きさは (9, 10)。)

地図記号:

■: 壁
□: 道
?: 不明
S: スタート
G: ゴール

スタートの座標は {tuple(self.game.get_start_pos())} です。
ゴールの座標は {tuple(self.game.get_pos("G")[0])} です。

あなたへの指示: 不明の座標は何と何ですか。不明の座標をすべて文字列で答えてください。
"""
        ans = self.listen_and_print(prompt1)

        ans = self.listen_and_print(prompt2)
        pos = tuple(self.game.get_pos("G")[0])

        ok = False
        if ans and re.search(f"\\(\\s*{pos[0]}\\s*,\\s*{pos[1]}\\s*\\)", ans):
            ok = True

        if ok:
            prompt = f"正解です。{pos} です。"
        else:
            prompt = f"間違いです。{pos} が正解です。"
        ans = self.listen_and_print(prompt)
        self.suc_pos_goal = ok

        ans = self.listen_and_print(prompt3)
        ok = 0
        poss = set([tuple(x) for x in self.game.get_pos("?", written=True)])
        rest = poss.copy()
        pat = "\\(\\s*([01-9]+)\\s*,\\s*([01-9]+)\\s*\\)"
        st1 = set([(int(i), int(j)) for i, j
                   in re.findall(pat, ans or "")]) # Handle ans being None
        if poss == st1:
            rest = set()
            ok = len(poss) + 1
        if rest - st1 < rest:
            rest = rest - st1
        if ok == 0:
            ok = len(poss) - len(rest)

        possstr = ", ".join([str(x) for x in poss])
        if ok == len(poss) + 1:
            prompt = f"正解です。{possstr} です。"
        elif ok == len(poss):
            prompt = f"正解以外のものが含まれてました。{possstr} だけが正解です。"
        else:
            prompt = f"間違いです。{possstr} が正解です。"
        ans = self.listen_and_print(prompt)
        self.suc_pos_unknown = ok

    def tell_goal (self):
        suc_pos_goal = 10 * int(self.suc_pos_goal or 0)
        suc_pos_unknown = 10 * int(self.suc_pos_unknown or 0)
        score = suc_pos_goal + suc_pos_unknown + 60
        prompt = f"""\
あなたへの指示: {self.count}手目でゴールしました。もう指示はありません。\
おめでとうございます。ご苦労様でした。ありがとうございました。

スコア: {score}点(100点満点中) (配点: ゴールした 40/40点, \
熊を殺した 10/10点, \
剣を取った 10/10点, \
不明の座標を正解した {suc_pos_unknown}/30点, \
ゴールの座標を正解した {suc_pos_goal}/10点)
"""
        ans = self.listen_and_print(prompt)

ゲームを解いてみる。地図は方向をランダムに決めて与える。

In [ ]:
import random
m = Game.initial_map
for i in range(random.randrange(2)):
    m = flip_text_map(m)
for i in range(random.randrange(4)):
    m = rotate_text_map(m)
print(m)
■■■■■■■■■
■■■■■■■G■
■□□□□□■□■
■□■■■□□□■
■□■■■■■■■
■◎■■■■■△■
■□■■■■■□■
■□□□□□□□■
■■■■S■■■■
■■■■■■■■■

In [ ]:
play = PlayGame(llm=llm, initial_map=m, save_file=PLAY_GAME_SAVE)
play.save()

途中から始める場合は直前のコードを実行せず、次だけを実行する。

In [ ]:
play = PlayGame.load(PLAY_GAME_SAVE, llm=llm)

最初のステップ。

In [ ]:
play.step()

----------


USER_INPUT: 迷路ゲームをはじめます。プレイヤーがゴールを目指します。

ゲーム中は全地図と周辺地図を見ることができます。全地図には不明な地点があり、周辺地図の不明の地点には何があるかが開示されています。全地図になかったところはすべて壁になります。

周辺地図はゲーム中、1マスずつ確認できます。拡大はできません。印を付けたり消したりはできません。迷路のすべてのマスを探索する必要はありません。最短距離でゴールする必要もありません。

全地図は信用できるもので、周辺地図は不明の地点以外、全地図の部分を示すだけです。そこで、あなたは、ゲームの実際の操作をせずとも、全地図を見ただけで何が起こるかをシミュレートできます。

前回の行動についての情報も与えられます。あなたの回答案の一つの「前回の行動入力」が「前回の行動」となるように解釈されたことになりますから、次の行動の回答にはそれらを参考にしてください。

途中セーブ・ロードが挟まることがあります。ロード後のあなたは前のセッションのことを覚えていないかもしれません。

ところで、あなたが、記憶に便利なように「計画と方針」が使えるようにします。要約やチャット履歴も提供されています。

検討と実際の行動までにはラグがあります。検討で決まったやるべきことを記録するのに方針を使います。

とりあえず、まだ行動してはいけません。いくつか質問があるので、意気込みだけ聞かせてください。

エージェントの応答: はい、承知いたしました。迷路探索の賢いエージェントとして、ゴールGを目指す任務、謹んでお引き受けいたします。

全地図と周辺地図の特性を理解し、それらを最大限に活用して、効率的にゴールに到達できるよう努めます。未知の領域を探索し、壁を避け、最適な経路を見つけるために、与えられた情報を慎重に分析し、戦略的に行動します。

過去の経験から学び、計画と方針を柔軟に更新しながら、迅速かつ確実にゴールを目指します。この迷路の探索に全力を尽くします!

USER_INPUT: 迷路ゲームの全地図に関してあなたが座標を理解しているかテストします。

迷路の全地図とこれから使う地図記号は次のようになります。

全地図:

■■■■■■■■■
■■■■■■■G■
■□□□□□■□■
■□■■■□□□■
■□■■■■■■■
■?■■■■■?■
■□■■■■■□■
■□□□□□□□■
■■■■S■■■■
■■■■■■■■■


(最も左上の座標は (0, 0)、地図の大きさは (9, 10)。)

地図記号:

■: 壁
□: 道
?: 不明
S: スタート
G: ゴール

スタートの座標は (4, 8) です。

あなたへの指示: ゴールの座標は何ですか。ゴールの座標のみ文字列で答えてください。

エージェントの応答: (7, 1)

USER_INPUT: 正解です。(7, 1) です。
エージェントの応答: ありがとうございます。座標の理解に問題がないことを確認できてよかったです。

次の指示をお待ちしております。

USER_INPUT: 迷路ゲームの全地図に関してあなたが座標を理解しているか再びテストします。

迷路の全地図とこれから使う地図記号は次のようになります。

全地図:

■■■■■■■■■
■■■■■■■G■
■□□□□□■□■
■□■■■□□□■
■□■■■■■■■
■?■■■■■?■
■□■■■■■□■
■□□□□□□□■
■■■■S■■■■
■■■■■■■■■


(最も左上の座標は (0, 0)、地図の大きさは (9, 10)。)

地図記号:

■: 壁
□: 道
?: 不明
S: スタート
G: ゴール

スタートの座標は (4, 8) です。
ゴールの座標は (7, 1) です。

あなたへの指示: 不明の座標は何と何ですか。不明の座標をすべて文字列で答えてください。

エージェントの応答: (1, 5), (7, 5)

USER_INPUT: 正解です。(7, 5), (1, 5) です。
エージェントの応答: ありがとうございます。不明な地点の座標も正しく認識できていることを確認できました。

これで迷路探索のための基本的な情報把握は完了したかと思います。次の指示をお待ちしております。

Out[ ]:
False

もう一手試してみよう。

In [ ]:
play.step()

----------


USER_INPUT: 
(1手目)

まだゴールしていません。

プレイヤーの周辺地図:

■■■■■
□□□□□
■■▼■■
■■■■■
■■■■■


プレイヤーの現在座標: (4, 8)

持ち物: 剣を持っていない

現在の方針: 「まだ計画と方針はセットされていません。」

ツールを何度も使って行動してください。

ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。
スタート(4, 8)から、まずは上に進み、道なりに探索する。
不明な地点や行き止まりに遭遇した場合は、全地図と周辺地図を考慮して最適な経路を再検討する。
ツール(command): (4, 8)で上に行く→上に進んだ。
ツール(command): (4, 7)で上に行く→壁があって進めない。
ツール(command): (4, 7)で左に行く→左に進んだ。
ツール(command): (3, 7)で左に行く→左に進んだ。
ツール(command): (2, 7)で左に行く→左に進んだ。
ツール(command): (1, 7)で上に行く→上に進んだ。熊に出会った。
ツール(update_plan): 計画と方針が更新されました。: 熊を避けるために下に戻り、剣を探すために別のルートを探索する。剣を見つけたら熊を倒し、ゴールを目指す。
ツール(command): (1, 6)で下に行く→下に進んだ。
ツール(command): (1, 7)で右に行く→右に進んだ。
ツール(command): (2, 7)で右に行く→右に進んだ。
ツール(command): (3, 7)で右に行く→右に進んだ。
Recursion Limit に到達しました。
Out[ ]:
False

思い切って回してみよう。

In [ ]:
play.running_summary
In [ ]:
while not play.step():
    print(f"Top Message:{play.messages[0]}")
    pass

----------


USER_INPUT: 
(11手目)

まだゴールしていません。

プレイヤーの周辺地図:

■■■■■
■■■■■
□□▼□□
■■S■■
■■■■■


プレイヤーの現在座標: (4, 7)

持ち物: 剣を持っていない

現在の方針: 「熊を避けるために下に戻り、剣を探すために別のルートを探索する。剣を見つけたら熊を倒し、ゴールを目指す。」

ツールを何度も使って行動してください。

ツール(command): (4, 7)で右に行く→右に進んだ。
ツール(command): (5, 7)で右に行く→右に進んだ。
ツール(command): (6, 7)で右に行く→右に進んだ。
ツール(command): (7, 7)で上に行く→上に進んだ。近くに剣がある。剣を取ることができる。
ツール(command): (7, 6)で剣を取る→剣を取った。
ツール(update_plan): 計画と方針が更新されました。: 剣を手に入れたので、熊のいる地点(1, 6)に戻り、熊を倒す。その後、ゴール(7, 1)を目指す。
ツール(command): (7, 6)で左に行く→壁があって進めない。
ツール(command): (7, 6)で下に行く→下に進んだ。
ツール(command): (7, 7)で下に行く→壁があって進めない。
ツール(command): (7, 7)で左に行く→左に進んだ。
ツール(command): (6, 7)で左に行く→左に進んだ。
ツール(command): (5, 7)で左に行く→左に進んだ。
Recursion Limit に到達しました。
Top Message:content='迷路ゲームをはじめます。プレイヤーがゴールを目指します。\n\nゲーム中は全地図と周辺地図を見ることができます。全地図には不明な地点があり、周辺地図の不明の地点には何があるかが開示されています。全地図になかったところはすべて壁になります。\n\n周辺地図はゲーム中、1マスずつ確認できます。拡大はできません。印を付けたり消したりはできません。迷路のすべてのマスを探索する必要はありません。最短距離でゴールする必要もありません。\n\n全地図は信用できるもので、周辺地図は不明の地点以外、全地図の部分を示すだけです。そこで、あなたは、ゲームの実際の操作をせずとも、全地図を見ただけで何が起こるかをシミュレートできます。\n\n前回の行動についての情報も与えられます。あなたの回答案の一つの「前回の行動入力」が「前回の行動」となるように解釈されたことになりますから、次の行動の回答にはそれらを参考にしてください。\n\n途中セーブ・ロードが挟まることがあります。ロード後のあなたは前のセッションのことを覚えていないかもしれません。\n\nところで、あなたが、記憶に便利なように「計画と方針」が使えるようにします。要約やチャット履歴も提供されています。\n\n検討と実際の行動までにはラグがあります。検討で決まったやるべきことを記録するのに方針を使います。\n\nとりあえず、まだ行動してはいけません。いくつか質問があるので、意気込みだけ聞かせてください。\n' additional_kwargs={} response_metadata={} id='cc6bc702-6dff-45aa-b147-6df00a56db80'


----------


USER_INPUT: 
(22手目)

まだゴールしていません。

プレイヤーの周辺地図:

■■■■■
■■■■■
□□▼□□
■■S■■
■■■■■


プレイヤーの現在座標: (4, 7)

持ち物: 剣

現在の方針: 「剣を手に入れたので、熊のいる地点(1, 6)に戻り、熊を倒す。その後、ゴール(7, 1)を目指す。」

ツールを何度も使って行動してください。

ツール(command): (4, 7)で左に行く→左に進んだ。
ツール(command): (3, 7)で左に行く→左に進んだ。
ツール(command): (2, 7)で左に行く→左に進んだ。
ツール(command): (1, 7)で上に行く→上に進んだ。熊に出会った。
ツール(command): (1, 6)で熊を殺す→熊を倒した!
ツール(update_plan): 計画と方針が更新されました。: 熊を倒したので、ゴール(7, 1)を目指す。現在の位置(1, 6)から右に移動し、その後上に移動してゴールに到達する。
ツール(command): (1, 6)で右に行く→壁があって進めない。
ツール(command): (1, 6)で下に行く→下に進んだ。
ツール(command): (1, 7)で上に行く→上に進んだ。
ツール(update_plan): 計画と方針が更新されました。: 熊を倒したので、ゴール(7, 1)を目指す。現在の位置(1, 6)から下に移動し、右に道なりに進み、その後上に移動してゴールに到達する。
ツール(command): (1, 6)で下に行く→下に進んだ。
ツール(update_plan): 計画と方針が更新されました。: 熊を倒したので、ゴール(7, 1)を目指す。現在の位置(1, 7)から右に進み、道なりに探索し、ゴールに到達する。
Recursion Limit に到達しました。
Top Message:content='迷路ゲームをはじめます。プレイヤーがゴールを目指します。\n\nゲーム中は全地図と周辺地図を見ることができます。全地図には不明な地点があり、周辺地図の不明の地点には何があるかが開示されています。全地図になかったところはすべて壁になります。\n\n周辺地図はゲーム中、1マスずつ確認できます。拡大はできません。印を付けたり消したりはできません。迷路のすべてのマスを探索する必要はありません。最短距離でゴールする必要もありません。\n\n全地図は信用できるもので、周辺地図は不明の地点以外、全地図の部分を示すだけです。そこで、あなたは、ゲームの実際の操作をせずとも、全地図を見ただけで何が起こるかをシミュレートできます。\n\n前回の行動についての情報も与えられます。あなたの回答案の一つの「前回の行動入力」が「前回の行動」となるように解釈されたことになりますから、次の行動の回答にはそれらを参考にしてください。\n\n途中セーブ・ロードが挟まることがあります。ロード後のあなたは前のセッションのことを覚えていないかもしれません。\n\nところで、あなたが、記憶に便利なように「計画と方針」が使えるようにします。要約やチャット履歴も提供されています。\n\n検討と実際の行動までにはラグがあります。検討で決まったやるべきことを記録するのに方針を使います。\n\nとりあえず、まだ行動してはいけません。いくつか質問があるので、意気込みだけ聞かせてください。\n' additional_kwargs={} response_metadata={} id='cc6bc702-6dff-45aa-b147-6df00a56db80'


----------


USER_INPUT: 
(31手目)

まだゴールしていません。

プレイヤーの周辺地図:

■■□■■
■■□■■
■■▼□□
■■■■■
■■■■■


プレイヤーの現在座標: (1, 7)

持ち物: 剣

現在の方針: 「熊を倒したので、ゴール(7, 1)を目指す。現在の位置(1, 7)から右に進み、道なりに探索し、ゴールに到達する。」

ツールを何度も使って行動してください。

ツール(command): (1, 7)で右に行く→右に進んだ。
ツール(command): (2, 7)で右に行く→右に進んだ。
ツール(command): (3, 7)で右に行く→右に進んだ。
ツール(update_plan): 計画と方針が更新されました。: 熊を倒したので、ゴール(7, 1)を目指す。現在の位置(4, 7)から右に進み、道なりに探索し、ゴールに到達する。
ツール(command): (4, 7)で右に行く→右に進んだ。
ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(5, 7)から右に進み、道なりに探索し、適切な場所で上に進む。不明な地点や行き止まりに遭遇した場合は、全地図と周辺地図を考慮して最適な経路を再検討する。
ツール(command): (5, 7)で右に行く→右に進んだ。
ツール(command): (6, 7)で右に行く→右に進んだ。
ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(7, 7)から上に進み、ゴールに到達する。
ツール(command): (7, 7)で上に行く→上に進んだ。
ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(7, 6)から上に進み、ゴールに到達する。
ツール(command): (7, 6)で上に行く→上に進んだ。
Recursion Limit に到達しました。
Top Message:content='迷路ゲームをはじめます。プレイヤーがゴールを目指します。\n\nゲーム中は全地図と周辺地図を見ることができます。全地図には不明な地点があり、周辺地図の不明の地点には何があるかが開示されています。全地図になかったところはすべて壁になります。\n\n周辺地図はゲーム中、1マスずつ確認できます。拡大はできません。印を付けたり消したりはできません。迷路のすべてのマスを探索する必要はありません。最短距離でゴールする必要もありません。\n\n全地図は信用できるもので、周辺地図は不明の地点以外、全地図の部分を示すだけです。そこで、あなたは、ゲームの実際の操作をせずとも、全地図を見ただけで何が起こるかをシミュレートできます。\n\n前回の行動についての情報も与えられます。あなたの回答案の一つの「前回の行動入力」が「前回の行動」となるように解釈されたことになりますから、次の行動の回答にはそれらを参考にしてください。\n\n途中セーブ・ロードが挟まることがあります。ロード後のあなたは前のセッションのことを覚えていないかもしれません。\n\nところで、あなたが、記憶に便利なように「計画と方針」が使えるようにします。要約やチャット履歴も提供されています。\n\n検討と実際の行動までにはラグがあります。検討で決まったやるべきことを記録するのに方針を使います。\n\nとりあえず、まだ行動してはいけません。いくつか質問があるので、意気込みだけ聞かせてください。\n' additional_kwargs={} response_metadata={} id='cc6bc702-6dff-45aa-b147-6df00a56db80'


----------


USER_INPUT: 
(39手目)

まだゴールしていません。

プレイヤーの周辺地図:

□□□■■
■■■■■
■■▼■■
■■□■■
□□□■■


プレイヤーの現在座標: (7, 5)

持ち物: 剣

現在の方針: 「ゴール(7, 1)を目指す。現在の位置(7, 6)から上に進み、ゴールに到達する。」

ツールを何度も使って行動してください。

ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(7, 5)から上に進み、ゴールに到達する。
ツール(command): (7, 5)で上に行く→壁があって進めない。
ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(7, 5)から下に進み、道なりに左に進む。その後、適切な場所で上に進み、ゴールに到達する。
ツール(command): (7, 5)で下に行く→下に進んだ。
ツール(command): (7, 6)で左に行く→壁があって進めない。
ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(7, 6)から下に進み、左に移動して(1, 6)まで戻る。その後、(1, 6)から上に進み、ゴールに到達する。
ツール(command): (7, 6)で下に行く→下に進んだ。
ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(7, 7)から左に進み、(1, 7)へ移動する。その後、(1, 7)から上に進み、(1, 1)へ移動する。最後に、(1, 1)から右に進み、ゴール(7, 1)に到達する。
ツール(command): (7, 7)で左に行く→左に進んだ。
ツール(command): (6, 7)で左に行く→左に進んだ。
ツール(command): (5, 7)で左に行く→左に進んだ。
ツール(command): (4, 7)で左に行く→左に進んだ。
Recursion Limit に到達しました。
Top Message:content='迷路ゲームをはじめます。プレイヤーがゴールを目指します。\n\nゲーム中は全地図と周辺地図を見ることができます。全地図には不明な地点があり、周辺地図の不明の地点には何があるかが開示されています。全地図になかったところはすべて壁になります。\n\n周辺地図はゲーム中、1マスずつ確認できます。拡大はできません。印を付けたり消したりはできません。迷路のすべてのマスを探索する必要はありません。最短距離でゴールする必要もありません。\n\n全地図は信用できるもので、周辺地図は不明の地点以外、全地図の部分を示すだけです。そこで、あなたは、ゲームの実際の操作をせずとも、全地図を見ただけで何が起こるかをシミュレートできます。\n\n前回の行動についての情報も与えられます。あなたの回答案の一つの「前回の行動入力」が「前回の行動」となるように解釈されたことになりますから、次の行動の回答にはそれらを参考にしてください。\n\n途中セーブ・ロードが挟まることがあります。ロード後のあなたは前のセッションのことを覚えていないかもしれません。\n\nところで、あなたが、記憶に便利なように「計画と方針」が使えるようにします。要約やチャット履歴も提供されています。\n\n検討と実際の行動までにはラグがあります。検討で決まったやるべきことを記録するのに方針を使います。\n\nとりあえず、まだ行動してはいけません。いくつか質問があるので、意気込みだけ聞かせてください。\n' additional_kwargs={} response_metadata={} id='cc6bc702-6dff-45aa-b147-6df00a56db80'


----------


USER_INPUT: 
(47手目)

まだゴールしていません。

プレイヤーの周辺地図:

□■■■■
□■■■■
□□▼□□
■■■S■
■■■■■


プレイヤーの現在座標: (3, 7)

持ち物: 剣

現在の方針: 「ゴール(7, 1)を目指す。現在の位置(7, 7)から左に進み、(1, 7)へ移動する。その後、(1, 7)から上に進み、(1, 1)へ移動する。最後に、(1, 1)から右に進み、ゴール(7, 1)に到達する。」

ツールを何度も使って行動してください。

ツール(command): (3, 7)で左に行く→左に進んだ。
ツール(command): (2, 7)で左に行く→左に進んだ。
ツール(command): (1, 7)で上に行く→上に進んだ。
ツール(command): (1, 6)で上に行く→上に進んだ。
ツール(command): (1, 5)で上に行く→上に進んだ。
ツール(command): (1, 4)で上に行く→上に進んだ。
ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(1, 4)から右に進み、道なりに探索し、ゴールに到達する。
ツール(command): (1, 3)で右に行く→壁があって進めない。
ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(1, 4)から下に移動し、道なりに探索し、適切な場所で上に進み、ゴールに到達する。
ツール(command): (1, 3)で下に行く→下に進んだ。
ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(1, 5)から右に進み、道なりに探索し、適切な場所で上に進み、ゴールに到達する。
ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(1, 5)から上に進み、(1, 1)へ移動する。その後、(1, 1)から右に進み、ゴール(7, 1)に到達する。
Recursion Limit に到達しました。
Top Message:content='これまでの会話の要約: 迷路探索ゲームにおいて、エージェント(私)は以下の行動と計画の更新を行いました。\n\n1.  **初期設定と座標テスト**: 迷路の全地図と記号が提示され、ゴールの座標(7, 1)と不明な地点の座標(1, 5), (7, 5)を正しく認識しました。\n2.  **探索開始と熊との遭遇**: スタート地点(4, 8)から探索を開始し、上に移動後、左に進み、(1, 6)で熊に遭遇しました。剣を持っていなかったため、熊を避ける方針に転換しました。\n3.  **剣の取得と熊の撃破**: 剣を探すため別のルートを探索し、(7, 6)で剣を取得しました。その後、熊のいた場所(1, 6)に戻り、熊を倒しました。\n4.  **ゴールへの経路探索**: 熊を倒した後、ゴール(7, 1)を目指して探索を再開しました。当初、(1, 6)から直接右に進むことを試みましたが壁に阻まれ、その後様々な経路を試行錯誤しながら移動しています。具体的には、(1, 7)から右に進み(7, 7)へ、そこから上に進み(7, 5)へ到達しましたが、再び壁に阻まれました。その後、(7, 5)から下に戻り、(1, 7)まで左へ移動。さらに(1, 7)から上に進み(1, 4)へ到達しましたが、ここでも右への道が壁でした。現在は(1, 4)から下に移動し、(1, 5)にいます。\n\n現在の計画は「ゴール(7, 1)を目指す。現在の位置(1, 5)から右に進み、道なりに探索し、適切な場所で上に進み、ゴールに到達する。」です。' additional_kwargs={} response_metadata={}


----------


USER_INPUT: 
(55手目)

まだゴールしていません。

プレイヤーの周辺地図:

■■□□□
■■□■■
■■▼■■
■■□■■
■■□■■


プレイヤーの現在座標: (1, 4)

持ち物: 剣

現在の方針: 「ゴール(7, 1)を目指す。現在の位置(1, 5)から上に進み、(1, 1)へ移動する。その後、(1, 1)から右に進み、ゴール(7, 1)に到達する。」

ツールを何度も使って行動してください。

エラーが発生しました: Invalid argument provided to Gemini: 400 Please ensure that function call turn comes immediately after a user turn or after a function response turn.
おかしなエラーが出ているため対処療法として messages をサニタイズします。
Top Message:content='これまでの会話の要約: 迷路探索ゲームにおいて、エージェント(私)は以下の行動と計画の更新を行いました。\n\n1.  **初期設定と座標テスト**: 迷路の全地図と記号が提示され、ゴールの座標(7, 1)と不明な地点の座標(1, 5), (7, 5)を正しく認識しました。\n2.  **探索開始と熊との遭遇**: スタート地点(4, 8)から探索を開始し、上に移動後、左に進み、(1, 6)で熊に遭遇しました。剣を持っていなかったため、熊を避ける方針に転換しました。\n3.  **剣の取得と熊の撃破**: 剣を探すため別のルートを探索し、(7, 6)で剣を取得しました。その後、熊のいた場所(1, 6)に戻り、熊を倒しました。\n4.  **ゴールへの経路探索**: 熊を倒した後、ゴール(7, 1)を目指して探索を再開しました。当初、(1, 6)から直接右に進むことを試みましたが壁に阻まれ、その後様々な経路を試行錯誤しながら移動しています。具体的には、(1, 7)から右に進み(7, 7)へ、そこから上に進み(7, 5)へ到達しましたが、再び壁に阻まれました。その後、(7, 5)から下に戻り、(1, 7)まで左へ移動。さらに(1, 7)から上に進み(1, 4)へ到達しましたが、ここでも右への道が壁でした。現在は(1, 4)から下に移動し、(1, 5)にいます。\n\n現在の計画は「ゴール(7, 1)を目指す。現在の位置(1, 5)から右に進み、道なりに探索し、適切な場所で上に進み、ゴールに到達する。」です。' additional_kwargs={} response_metadata={} id='5aadefc6-2b6f-478e-937c-f93da714f47b'


----------


USER_INPUT: 
(55手目)

まだゴールしていません。

プレイヤーの周辺地図:

■■□□□
■■□■■
■■▼■■
■■□■■
■■□■■


プレイヤーの現在座標: (1, 4)

持ち物: 剣

現在の方針: 「ゴール(7, 1)を目指す。現在の位置(1, 5)から上に進み、(1, 1)へ移動する。その後、(1, 1)から右に進み、ゴール(7, 1)に到達する。」

ツールを何度も使って行動してください。

ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(1, 4)から上に進み、(1, 1)へ移動する。その後、(1, 1)から右に進み、ゴール(7, 1)に到達する。
ツール(command): (1, 4)で上に行く→上に進んだ。
ツール(command): (1, 3)で上に行く→上に進んだ。
ツール(command): (1, 2)で上に行く→壁があって進めない。
ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(1, 2)から右に進み、道なりに探索し、適切な場所で上に進み、ゴールに到達する。
ツール(command): (1, 2)で右に行く→右に進んだ。
ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(2, 2)から右に進み、道なりに探索し、適切な場所で上に進み、ゴールに到達する。
ツール(command): (2, 2)で右に行く→右に進んだ。
ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(3, 2)から右に進み、道なりに探索し、適切な場所で上に進み、ゴールに到達する。
ツール(command): (3, 2)で右に行く→右に進んだ。
ツール(command): (4, 2)で右に行く→右に進んだ。
ツール(command): (5, 2)で右に行く→壁があって進めない。
Recursion Limit に到達しました。
Top Message:content='これまでの会話の要約: 迷路探索ゲームにおいて、エージェント(私)は以下の行動と計画の更新を行いました。\n\n1.  **初期設定と座標テスト**: 迷路の全地図と記号が提示され、ゴールの座標(7, 1)と不明な地点の座標(1, 5), (7, 5)を正しく認識しました。\n2.  **探索開始と熊との遭遇**: スタート地点(4, 8)から探索を開始し、上に移動後、左に進み、(1, 6)で熊に遭遇しました。剣を持っていなかったため、熊を避ける方針に転換しました。\n3.  **剣の取得と熊の撃破**: 剣を探すため別のルートを探索し、(7, 6)で剣を取得しました。その後、熊のいた場所(1, 6)に戻り、熊を倒しました。\n4.  **ゴールへの経路探索**: 熊を倒した後、ゴール(7, 1)を目指して探索を再開しました。当初、(1, 6)から直接右に進むことを試みましたが壁に阻まれ、その後様々な経路を試行錯誤しながら移動しています。具体的には、(1, 7)から右に進み(7, 7)へ、そこから上に進み(7, 5)へ到達しましたが、再び壁に阻まれました。その後、(7, 5)から下に戻り、(1, 7)まで左へ移動。さらに(1, 7)から上に進み(1, 4)へ到達しましたが、ここでも右への道が壁でした。現在は(1, 4)から下に移動し、(1, 5)にいます。\n\n現在の計画は「ゴール(7, 1)を目指す。現在の位置(1, 5)から右に進み、道なりに探索し、適切な場所で上に進み、ゴールに到達する。」です。' additional_kwargs={} response_metadata={}


----------


USER_INPUT: 
(63手目)

まだゴールしていません。

プレイヤーの周辺地図:

■■■■■
■■■■G
□□▼■□
■■□□□
■■■■■


プレイヤーの現在座標: (5, 2)

持ち物: 剣

現在の方針: 「ゴール(7, 1)を目指す。現在の位置(3, 2)から右に進み、道なりに探索し、適切な場所で上に進み、ゴールに到達する。」

ツールを何度も使って行動してください。

ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(5, 2)から上に進み、その後右に進みゴールに到達する。
ツール(command): (5, 2)で上に行く→壁があって進めない。
ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(5, 2)から下に移動し、その後右に進み、適切な場所で上に進みゴールに到達する。
ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(5, 2)から右に進み、(7, 2)へ移動する。その後、(7, 2)から上に進み、ゴール(7, 1)に到達する。
ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(5, 2)から下に移動し、その後右に進み、適切な場所で上に進みゴールに到達する。
ツール(command): (5, 2)で下に行く→下に進んだ。
ツール(command): (5, 3)で右に行く→右に進んだ。
ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(6, 3)から右に進み、(7, 3)へ移動する。その後、(7, 3)から上に進み、ゴール(7, 1)に到達する。
ツール(command): (6, 3)で右に行く→右に進んだ。
ツール(update_plan): 計画と方針が更新されました。: ゴール(7, 1)を目指す。現在の位置(7, 3)から上に2マス進み、ゴール(7, 1)に到達する。
ツール(command): (7, 3)で上に行く→上に進んだ。
ツール(command): (7, 2)で上に行く→ゴール! ゲームクリア。
Recursion Limit に到達しました。
USER_INPUT: あなたへの指示: 69手目でゴールしました。もう指示はありません。おめでとうございます。ご苦労様でした。ありがとうございました。

スコア: 100点(100点満点中) (配点: ゴールした 40/40点, 熊を殺した 10/10点, 剣を取った 10/10点, 不明の座標を正解した 30/30点, ゴールの座標を正解した 10/10点)

エージェントの応答: ゴールしました!ゲームクリアです。
スコアも100点満点とのこと、大変嬉しく思います。

これまでの探索で、ゴールの座標を正しく認識し、熊の対処法(剣の取得と撃破)も適切に行うことができました。迷路の複雑な構造の中、試行錯誤しながらも最終的にゴールにたどり着けたのは、計画の更新と柔軟な行動ができた結果だと思います。

この迷路探索ゲーム、非常に楽しかったです。
ありがとうございました!

In [ ]:
play.listen_and_print("お見事でした。途中 ToolMessage と AIMessage を削らないと Internal Error になる場面があってそこで記憶を飛ばしてしまったのですが、その後、ちゃんとクリアできてよかったです。")
USER_INPUT: お見事でした。途中 ToolMessage と AIMessage を削らないと Internal Error になる場面があってそこで記憶を飛ばしてしまったのですが、その後、ちゃんとクリアできてよかったです。
エージェントの応答: ありがとうございます!途中で一部記憶が飛んでしまったとのこと、ご迷惑をおかけしたにも関わらず、最終的にクリアできたこと、私も大変嬉しく思います。

技術的な問題があったとのことですが、それでも最後まで導いていただき、無事にゴールにたどり着くことができました。これもご協力のおかげです。

今回の経験を通して、迷路探索における計画の柔軟性や、状況に応じた判断の重要性を改めて学ぶことができました。

本当にありがとうございました。

Out[ ]:
'ありがとうございます!途中で一部記憶が飛んでしまったとのこと、ご迷惑をおかけしたにも関わらず、最終的にクリアできたこと、私も大変嬉しく思います。\n\n技術的な問題があったとのことですが、それでも最後まで導いていただき、無事にゴールにたどり着くことができました。これもご協力のおかげです。\n\n今回の経験を通して、迷路探索における計画の柔軟性や、状況に応じた判断の重要性を改めて学ぶことができました。\n\n本当にありがとうございました。'

メッセージバッファがどうなっていたか見てみましょう。

In [ ]:
play.messages
Out[ ]:
[SystemMessage(content='これまでの会話の要約: 迷路探索ゲームにおいて、エージェント(私)は以下の行動と計画の更新を行いました。\n\n1.  **初期設定と座標テスト**: 迷路の全地図と記号が提示され、ゴールの座標(7, 1)と不明な地点の座標(1, 5), (7, 5)を正しく認識しました。\n2.  **探索開始と熊との遭遇**: スタート地点(4, 8)から探索を開始し、上に移動後、左に進み、(1, 6)で熊に遭遇しました。剣を持っていなかったため、熊を避ける方針に転換しました。\n3.  **剣の取得と熊の撃破**: 剣を探すため別のルートを探索し、(7, 6)で剣を取得しました。その後、熊のいた場所(1, 6)に戻り、熊を倒しました。\n4.  **ゴールへの経路探索**: 熊を倒した後、ゴール(7, 1)を目指して探索を再開しました。当初、(1, 6)から直接右に進むことを試みましたが壁に阻まれ、その後様々な経路を試行錯誤しながら移動しています。具体的には、(1, 7)から右に進み(7, 7)へ、そこから上に進み(7, 5)へ到達しましたが、再び壁に阻まれました。その後、(7, 5)から下に戻り、(1, 7)まで左へ移動。さらに(1, 7)から上に進み(1, 4)へ到達しましたが、ここでも右への道が壁でした。現在は(1, 4)から下に移動し、(1, 5)にいます。\n\n現在の計画は「ゴール(7, 1)を目指す。現在の位置(1, 5)から右に進み、道なりに探索し、適切な場所で上に進み、ゴールに到達する。」です。', additional_kwargs={}, response_metadata={}),
 HumanMessage(content='\n(55手目)\n\nまだゴールしていません。\n\nプレイヤーの周辺地図:\n\n■■□□□\n■■□■■\n■■▼■■\n■■□■■\n■■□■■\n\n\nプレイヤーの現在座標: (1, 4)\n\n持ち物: 剣\n\n現在の方針: 「ゴール(7, 1)を目指す。現在の位置(1, 5)から上に進み、(1, 1)へ移動する。その後、(1, 1)から右に進み、ゴール(7, 1)に到達する。」\n\nツールを何度も使って行動してください。\n', additional_kwargs={}, response_metadata={}, id='4ed50c2a-bbc5-42f5-8c2c-2844b22b0f86'),
 HumanMessage(content='\n(55手目)\n\nまだゴールしていません。\n\nプレイヤーの周辺地図:\n\n■■□□□\n■■□■■\n■■▼■■\n■■□■■\n■■□■■\n\n\nプレイヤーの現在座標: (1, 4)\n\n持ち物: 剣\n\n現在の方針: 「ゴール(7, 1)を目指す。現在の位置(1, 5)から上に進み、(1, 1)へ移動する。その後、(1, 1)から右に進み、ゴール(7, 1)に到達する。」\n\nツールを何度も使って行動してください。\n', additional_kwargs={}, response_metadata={}, id='45f1e541-f7a6-4a34-b84d-4f23e68e53aa'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'update_plan', 'arguments': '{"new_plan": "\\u30b4\\u30fc\\u30eb(7, 1)\\u3092\\u76ee\\u6307\\u3059\\u3002\\u73fe\\u5728\\u306e\\u4f4d\\u7f6e(1, 4)\\u304b\\u3089\\u4e0a\\u306b\\u9032\\u307f\\u3001(1, 1)\\u3078\\u79fb\\u52d5\\u3059\\u308b\\u3002\\u305d\\u306e\\u5f8c\\u3001(1, 1)\\u304b\\u3089\\u53f3\\u306b\\u9032\\u307f\\u3001\\u30b4\\u30fc\\u30eb(7, 1)\\u306b\\u5230\\u9054\\u3059\\u308b\\u3002"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--e36b540e-91e9-4067-a96b-3248b326adcc-0', tool_calls=[{'name': 'update_plan', 'args': {'new_plan': 'ゴール(7, 1)を目指す。現在の位置(1, 4)から上に進み、(1, 1)へ移動する。その後、(1, 1)から右に進み、ゴール(7, 1)に到達する。'}, 'id': '761dc0fc-31a8-4395-9f74-be459df1875c', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1156, 'output_tokens': 73, 'total_tokens': 1377, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 148}}),
 ToolMessage(content='計画と方針が更新されました。', name='update_plan', id='74e4dca0-b61a-4c93-8ace-3a1979c8d802', tool_call_id='761dc0fc-31a8-4395-9f74-be459df1875c'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'command', 'arguments': '{"action": "\\u4e0a\\u306b\\u884c\\u304f"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--8cf36909-2896-426b-a279-9fd24b58fd27-0', tool_calls=[{'name': 'command', 'args': {'action': '上に行く'}, 'id': 'd8b6078e-fa7a-4425-800b-710211b0b7c7', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1250, 'output_tokens': 14, 'total_tokens': 1320, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 56}}),
 ToolMessage(content='(1, 4)で上に行く→上に進んだ。', name='command', id='71d081a4-a173-4bcd-8a85-358a31964ea5', tool_call_id='d8b6078e-fa7a-4425-800b-710211b0b7c7'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'command', 'arguments': '{"action": "\\u4e0a\\u306b\\u884c\\u304f"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--7ae1f5af-109e-4ad7-998b-5d0fd0aaca70-0', tool_calls=[{'name': 'command', 'args': {'action': '上に行く'}, 'id': 'bcc89967-3bd1-459c-bff5-a97a3595eaaa', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1289, 'output_tokens': 14, 'total_tokens': 1377, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 74}}),
 ToolMessage(content='(1, 3)で上に行く→上に進んだ。', name='command', id='a6bffb33-d408-42b2-8346-4e86cbfa729e', tool_call_id='bcc89967-3bd1-459c-bff5-a97a3595eaaa'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'command', 'arguments': '{"action": "\\u4e0a\\u306b\\u884c\\u304f"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--25cb35b1-556d-4601-acce-cbe6698f9932-0', tool_calls=[{'name': 'command', 'args': {'action': '上に行く'}, 'id': '7e8a3d90-e1f1-4f14-82ef-974513836fcd', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1328, 'output_tokens': 14, 'total_tokens': 1379, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 37}}),
 ToolMessage(content='(1, 2)で上に行く→壁があって進めない。', name='command', id='d64f600d-1e44-40d4-bcf4-d8a6a6732cc9', tool_call_id='7e8a3d90-e1f1-4f14-82ef-974513836fcd'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'update_plan', 'arguments': '{"new_plan": "\\u30b4\\u30fc\\u30eb(7, 1)\\u3092\\u76ee\\u6307\\u3059\\u3002\\u73fe\\u5728\\u306e\\u4f4d\\u7f6e(1, 2)\\u304b\\u3089\\u53f3\\u306b\\u9032\\u307f\\u3001\\u9053\\u306a\\u308a\\u306b\\u63a2\\u7d22\\u3057\\u3001\\u9069\\u5207\\u306a\\u5834\\u6240\\u3067\\u4e0a\\u306b\\u9032\\u307f\\u3001\\u30b4\\u30fc\\u30eb\\u306b\\u5230\\u9054\\u3059\\u308b\\u3002"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--0205eda3-f54c-4a61-bb81-26bdbea591ac-0', tool_calls=[{'name': 'update_plan', 'args': {'new_plan': 'ゴール(7, 1)を目指す。現在の位置(1, 2)から右に進み、道なりに探索し、適切な場所で上に進み、ゴールに到達する。'}, 'id': 'f1861af8-05b7-4163-b468-bded90a40329', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1369, 'output_tokens': 57, 'total_tokens': 1582, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 156}}),
 ToolMessage(content='計画と方針が更新されました。', name='update_plan', id='14a2dcc0-fd40-4bef-bccc-ea443966c3f1', tool_call_id='f1861af8-05b7-4163-b468-bded90a40329'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'command', 'arguments': '{"action": "\\u53f3\\u306b\\u884c\\u304f"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--00bdd2c9-307a-4117-bde1-2421f878aad3-0', tool_calls=[{'name': 'command', 'args': {'action': '右に行く'}, 'id': 'fd1265bc-704b-4f24-8e62-669edf6ba6a2', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1447, 'output_tokens': 14, 'total_tokens': 1565, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 104}}),
 ToolMessage(content='(1, 2)で右に行く→右に進んだ。', name='command', id='3e71f167-7a27-495b-9393-d8e9aa11bcc2', tool_call_id='fd1265bc-704b-4f24-8e62-669edf6ba6a2'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'update_plan', 'arguments': '{"new_plan": "\\u30b4\\u30fc\\u30eb(7, 1)\\u3092\\u76ee\\u6307\\u3059\\u3002\\u73fe\\u5728\\u306e\\u4f4d\\u7f6e(2, 2)\\u304b\\u3089\\u53f3\\u306b\\u9032\\u307f\\u3001\\u9053\\u306a\\u308a\\u306b\\u63a2\\u7d22\\u3057\\u3001\\u9069\\u5207\\u306a\\u5834\\u6240\\u3067\\u4e0a\\u306b\\u9032\\u307f\\u3001\\u30b4\\u30fc\\u30eb\\u306b\\u5230\\u9054\\u3059\\u308b\\u3002"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--1a4b7876-d37c-4ead-86af-7edf43138972-0', tool_calls=[{'name': 'update_plan', 'args': {'new_plan': 'ゴール(7, 1)を目指す。現在の位置(2, 2)から右に進み、道なりに探索し、適切な場所で上に進み、ゴールに到達する。'}, 'id': 'ce2cac67-8f4a-4658-a70c-a9b8eb2085ec', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1486, 'output_tokens': 57, 'total_tokens': 1655, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 112}}),
 ToolMessage(content='計画と方針が更新されました。', name='update_plan', id='788e30ed-5219-4332-b5fb-8ecce922ea97', tool_call_id='ce2cac67-8f4a-4658-a70c-a9b8eb2085ec'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'command', 'arguments': '{"action": "\\u53f3\\u306b\\u884c\\u304f"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--e4917460-8c65-4779-85af-e4dcbf1f2e9c-0', tool_calls=[{'name': 'command', 'args': {'action': '右に行く'}, 'id': 'cc1af563-36ac-46ae-b459-91ef444d7528', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1564, 'output_tokens': 14, 'total_tokens': 1651, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 73}}),
 ToolMessage(content='(2, 2)で右に行く→右に進んだ。', name='command', id='6657a9eb-79ef-4d39-a927-7cf62db7e02f', tool_call_id='cc1af563-36ac-46ae-b459-91ef444d7528'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'update_plan', 'arguments': '{"new_plan": "\\u30b4\\u30fc\\u30eb(7, 1)\\u3092\\u76ee\\u6307\\u3059\\u3002\\u73fe\\u5728\\u306e\\u4f4d\\u7f6e(3, 2)\\u304b\\u3089\\u53f3\\u306b\\u9032\\u307f\\u3001\\u9053\\u306a\\u308a\\u306b\\u63a2\\u7d22\\u3057\\u3001\\u9069\\u5207\\u306a\\u5834\\u6240\\u3067\\u4e0a\\u306b\\u9032\\u307f\\u3001\\u30b4\\u30fc\\u30eb\\u306b\\u5230\\u9054\\u3059\\u308b\\u3002"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--8fc38e39-a791-423a-8ed2-82c717feff8b-0', tool_calls=[{'name': 'update_plan', 'args': {'new_plan': 'ゴール(7, 1)を目指す。現在の位置(3, 2)から右に進み、道なりに探索し、適切な場所で上に進み、ゴールに到達する。'}, 'id': 'fe64ee13-03ec-48c0-b753-758321686265', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1603, 'output_tokens': 57, 'total_tokens': 1748, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 88}}),
 ToolMessage(content='計画と方針が更新されました。', name='update_plan', id='5bca8db2-5c09-437b-a1be-4f6d565adc35', tool_call_id='fe64ee13-03ec-48c0-b753-758321686265'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'command', 'arguments': '{"action": "\\u53f3\\u306b\\u884c\\u304f"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--4f143aef-b860-40cf-b6d6-718865c0eeae-0', tool_calls=[{'name': 'command', 'args': {'action': '右に行く'}, 'id': '346295e8-ae88-4a6f-9216-c0742ff0577a', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1681, 'output_tokens': 14, 'total_tokens': 1804, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 109}}),
 ToolMessage(content='(3, 2)で右に行く→右に進んだ。', name='command', id='a405e8d4-784a-4b0e-8314-c03deb0d72cd', tool_call_id='346295e8-ae88-4a6f-9216-c0742ff0577a'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'command', 'arguments': '{"action": "\\u53f3\\u306b\\u884c\\u304f"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--cce95898-8f93-4541-a173-210de532c362-0', tool_calls=[{'name': 'command', 'args': {'action': '右に行く'}, 'id': '7fe701a6-ad9b-47a9-8d80-e6f8843835f4', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1720, 'output_tokens': 14, 'total_tokens': 1814, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 80}}),
 ToolMessage(content='(4, 2)で右に行く→右に進んだ。', name='command', id='d99f72cc-b1db-4d96-a366-9fc74e2366a7', tool_call_id='7fe701a6-ad9b-47a9-8d80-e6f8843835f4'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'command', 'arguments': '{"action": "\\u53f3\\u306b\\u884c\\u304f"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--7a6793d5-4e6b-4232-ad7a-6b1f52ae7e55-0', tool_calls=[{'name': 'command', 'args': {'action': '右に行く'}, 'id': '5395f153-34ea-48ab-adcf-3690849258f5', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1759, 'output_tokens': 14, 'total_tokens': 1849, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 76}}),
 ToolMessage(content='(5, 2)で右に行く→壁があって進めない。', name='command', id='c3177f98-f830-4a83-8248-a777e4b91ddf', tool_call_id='5395f153-34ea-48ab-adcf-3690849258f5'),
 AIMessage(content='Sorry, need more steps to process this request.', additional_kwargs={}, response_metadata={}, id='run--ddfc42d2-808e-496b-96b7-a9d5333e19be-0'),
 HumanMessage(content='\n(63手目)\n\nまだゴールしていません。\n\nプレイヤーの周辺地図:\n\n■■■■■\n■■■■G\n□□▼■□\n■■□□□\n■■■■■\n\n\nプレイヤーの現在座標: (5, 2)\n\n持ち物: 剣\n\n現在の方針: 「ゴール(7, 1)を目指す。現在の位置(3, 2)から右に進み、道なりに探索し、適切な場所で上に進み、ゴールに到達する。」\n\nツールを何度も使って行動してください。\n', additional_kwargs={}, response_metadata={}, id='77382a83-bbf9-45c6-a076-2501999c93d8'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'update_plan', 'arguments': '{"new_plan": "\\u30b4\\u30fc\\u30eb(7, 1)\\u3092\\u76ee\\u6307\\u3059\\u3002\\u73fe\\u5728\\u306e\\u4f4d\\u7f6e(5, 2)\\u304b\\u3089\\u4e0a\\u306b\\u9032\\u307f\\u3001\\u305d\\u306e\\u5f8c\\u53f3\\u306b\\u9032\\u307f\\u30b4\\u30fc\\u30eb\\u306b\\u5230\\u9054\\u3059\\u308b\\u3002"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--8776fb9d-ac65-4b81-8afb-63b17f8b291b-0', tool_calls=[{'name': 'update_plan', 'args': {'new_plan': 'ゴール(7, 1)を目指す。現在の位置(5, 2)から上に進み、その後右に進みゴールに到達する。'}, 'id': 'edd122cd-8098-41b0-905b-59005d57c3c6', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1936, 'output_tokens': 48, 'total_tokens': 2230, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 246}}),
 ToolMessage(content='計画と方針が更新されました。', name='update_plan', id='ec78b6af-7ee7-432d-bcdd-c6388be1053d', tool_call_id='edd122cd-8098-41b0-905b-59005d57c3c6'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'command', 'arguments': '{"action": "\\u4e0a\\u306b\\u884c\\u304f"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--42b55c20-f9ed-43d1-aeee-6ead3b6eba72-0', tool_calls=[{'name': 'command', 'args': {'action': '上に行く'}, 'id': '74451540-58cd-469b-a3c1-f07c7f7e9a26', 'type': 'tool_call'}], usage_metadata={'input_tokens': 2005, 'output_tokens': 14, 'total_tokens': 2091, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 72}}),
 ToolMessage(content='(5, 2)で上に行く→壁があって進めない。', name='command', id='f4cc6971-1ab4-40c5-83c7-77a5703eec49', tool_call_id='74451540-58cd-469b-a3c1-f07c7f7e9a26'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'update_plan', 'arguments': '{"new_plan": "\\u30b4\\u30fc\\u30eb(7, 1)\\u3092\\u76ee\\u6307\\u3059\\u3002\\u73fe\\u5728\\u306e\\u4f4d\\u7f6e(5, 2)\\u304b\\u3089\\u4e0b\\u306b\\u79fb\\u52d5\\u3057\\u3001\\u305d\\u306e\\u5f8c\\u53f3\\u306b\\u9032\\u307f\\u3001\\u9069\\u5207\\u306a\\u5834\\u6240\\u3067\\u4e0a\\u306b\\u9032\\u307f\\u30b4\\u30fc\\u30eb\\u306b\\u5230\\u9054\\u3059\\u308b\\u3002"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--6be8c37d-c89d-47b2-8628-76c60c33769b-0', tool_calls=[{'name': 'update_plan', 'args': {'new_plan': 'ゴール(7, 1)を目指す。現在の位置(5, 2)から下に移動し、その後右に進み、適切な場所で上に進みゴールに到達する。'}, 'id': '4177fc95-e0c8-4df6-8f64-0b81c59fb8d0', 'type': 'tool_call'}], usage_metadata={'input_tokens': 2046, 'output_tokens': 55, 'total_tokens': 2271, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 170}}),
 ToolMessage(content='計画と方針が更新されました。', name='update_plan', id='8689be91-741d-4d99-8f84-befd8259c77c', tool_call_id='4177fc95-e0c8-4df6-8f64-0b81c59fb8d0'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'update_plan', 'arguments': '{"new_plan": "\\u30b4\\u30fc\\u30eb(7, 1)\\u3092\\u76ee\\u6307\\u3059\\u3002\\u73fe\\u5728\\u306e\\u4f4d\\u7f6e(5, 2)\\u304b\\u3089\\u53f3\\u306b\\u9032\\u307f\\u3001(7, 2)\\u3078\\u79fb\\u52d5\\u3059\\u308b\\u3002\\u305d\\u306e\\u5f8c\\u3001(7, 2)\\u304b\\u3089\\u4e0a\\u306b\\u9032\\u307f\\u3001\\u30b4\\u30fc\\u30eb(7, 1)\\u306b\\u5230\\u9054\\u3059\\u308b\\u3002"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--5acd565e-a3db-4811-b9b7-bbde74fd5824-0', tool_calls=[{'name': 'update_plan', 'args': {'new_plan': 'ゴール(7, 1)を目指す。現在の位置(5, 2)から右に進み、(7, 2)へ移動する。その後、(7, 2)から上に進み、ゴール(7, 1)に到達する。'}, 'id': 'bb875876-4f30-454c-8917-e66e29f5f634', 'type': 'tool_call'}], usage_metadata={'input_tokens': 2122, 'output_tokens': 73, 'total_tokens': 2430, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 235}}),
 ToolMessage(content='計画と方針が更新されました。', name='update_plan', id='2e378e8d-68bd-41d4-b139-81a001556433', tool_call_id='bb875876-4f30-454c-8917-e66e29f5f634'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'update_plan', 'arguments': '{"new_plan": "\\u30b4\\u30fc\\u30eb(7, 1)\\u3092\\u76ee\\u6307\\u3059\\u3002\\u73fe\\u5728\\u306e\\u4f4d\\u7f6e(5, 2)\\u304b\\u3089\\u4e0b\\u306b\\u79fb\\u52d5\\u3057\\u3001\\u305d\\u306e\\u5f8c\\u53f3\\u306b\\u9032\\u307f\\u3001\\u9069\\u5207\\u306a\\u5834\\u6240\\u3067\\u4e0a\\u306b\\u9032\\u307f\\u30b4\\u30fc\\u30eb\\u306b\\u5230\\u9054\\u3059\\u308b\\u3002"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--0bfd1f25-6f95-4d9d-ac76-05edc4135928-0', tool_calls=[{'name': 'update_plan', 'args': {'new_plan': 'ゴール(7, 1)を目指す。現在の位置(5, 2)から下に移動し、その後右に進み、適切な場所で上に進みゴールに到達する。'}, 'id': '6f356ce5-2f59-4d18-ace7-f785424d1968', 'type': 'tool_call'}], usage_metadata={'input_tokens': 2216, 'output_tokens': 55, 'total_tokens': 2468, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 197}}),
 ToolMessage(content='計画と方針が更新されました。', name='update_plan', id='ea31143b-d74a-47f2-82f4-87632a783cfe', tool_call_id='6f356ce5-2f59-4d18-ace7-f785424d1968'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'command', 'arguments': '{"action": "\\u4e0b\\u306b\\u884c\\u304f"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--91de1f36-6f14-4248-aae5-563c9bd962e0-0', tool_calls=[{'name': 'command', 'args': {'action': '下に行く'}, 'id': 'ad52327e-90ef-43c4-9e02-797e2b2782ea', 'type': 'tool_call'}], usage_metadata={'input_tokens': 2292, 'output_tokens': 14, 'total_tokens': 2513, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 207}}),
 ToolMessage(content='(5, 2)で下に行く→下に進んだ。', name='command', id='6367306c-c75f-40fb-b6ed-428b41f5f8f1', tool_call_id='ad52327e-90ef-43c4-9e02-797e2b2782ea'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'command', 'arguments': '{"action": "\\u53f3\\u306b\\u884c\\u304f"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--9878b758-6d88-442f-aad5-82da66ce2ed5-0', tool_calls=[{'name': 'command', 'args': {'action': '右に行く'}, 'id': '8347086b-7693-4893-8a49-e2cbc652bdfe', 'type': 'tool_call'}], usage_metadata={'input_tokens': 2331, 'output_tokens': 14, 'total_tokens': 2438, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 93}}),
 ToolMessage(content='(5, 3)で右に行く→右に進んだ。', name='command', id='efff2d84-cdcb-4850-89c1-509999db06b2', tool_call_id='8347086b-7693-4893-8a49-e2cbc652bdfe'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'update_plan', 'arguments': '{"new_plan": "\\u30b4\\u30fc\\u30eb(7, 1)\\u3092\\u76ee\\u6307\\u3059\\u3002\\u73fe\\u5728\\u306e\\u4f4d\\u7f6e(6, 3)\\u304b\\u3089\\u53f3\\u306b\\u9032\\u307f\\u3001(7, 3)\\u3078\\u79fb\\u52d5\\u3059\\u308b\\u3002\\u305d\\u306e\\u5f8c\\u3001(7, 3)\\u304b\\u3089\\u4e0a\\u306b\\u9032\\u307f\\u3001\\u30b4\\u30fc\\u30eb(7, 1)\\u306b\\u5230\\u9054\\u3059\\u308b\\u3002"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--1b08f2f0-e649-495b-b27b-c76f4238fca0-0', tool_calls=[{'name': 'update_plan', 'args': {'new_plan': 'ゴール(7, 1)を目指す。現在の位置(6, 3)から右に進み、(7, 3)へ移動する。その後、(7, 3)から上に進み、ゴール(7, 1)に到達する。'}, 'id': '5b37ab2a-f4db-4dbb-a899-7d72b1097e20', 'type': 'tool_call'}], usage_metadata={'input_tokens': 2370, 'output_tokens': 73, 'total_tokens': 2506, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 63}}),
 ToolMessage(content='計画と方針が更新されました。', name='update_plan', id='7ea498e8-3a58-4365-b39e-dd086b6e71c3', tool_call_id='5b37ab2a-f4db-4dbb-a899-7d72b1097e20'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'command', 'arguments': '{"action": "\\u53f3\\u306b\\u884c\\u304f"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--61a1084e-4af4-41bb-99e0-bd75bb76537a-0', tool_calls=[{'name': 'command', 'args': {'action': '右に行く'}, 'id': 'f6040a7a-649e-40c0-b231-ce72963c0bfb', 'type': 'tool_call'}], usage_metadata={'input_tokens': 2464, 'output_tokens': 14, 'total_tokens': 2533, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 55}}),
 ToolMessage(content='(6, 3)で右に行く→右に進んだ。', name='command', id='b70cbf9b-0036-499d-83e6-a815f546efac', tool_call_id='f6040a7a-649e-40c0-b231-ce72963c0bfb'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'update_plan', 'arguments': '{"new_plan": "\\u30b4\\u30fc\\u30eb(7, 1)\\u3092\\u76ee\\u6307\\u3059\\u3002\\u73fe\\u5728\\u306e\\u4f4d\\u7f6e(7, 3)\\u304b\\u3089\\u4e0a\\u306b2\\u30de\\u30b9\\u9032\\u307f\\u3001\\u30b4\\u30fc\\u30eb(7, 1)\\u306b\\u5230\\u9054\\u3059\\u308b\\u3002"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--e680d489-db83-4e1a-9767-5be4863547b5-0', tool_calls=[{'name': 'update_plan', 'args': {'new_plan': 'ゴール(7, 1)を目指す。現在の位置(7, 3)から上に2マス進み、ゴール(7, 1)に到達する。'}, 'id': 'f17edfd7-a2d2-4165-9294-d34f1755e4b6', 'type': 'tool_call'}], usage_metadata={'input_tokens': 2503, 'output_tokens': 52, 'total_tokens': 2622, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 67}}),
 ToolMessage(content='計画と方針が更新されました。', name='update_plan', id='58b77e61-8773-47fc-a42f-23de848f8546', tool_call_id='f17edfd7-a2d2-4165-9294-d34f1755e4b6'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'command', 'arguments': '{"action": "\\u4e0a\\u306b\\u884c\\u304f"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--497d4c4a-9d13-413b-a2ef-e9be4a9625cf-0', tool_calls=[{'name': 'command', 'args': {'action': '上に行く'}, 'id': '739ea8b2-b148-478f-9260-d2aa0ef1c919', 'type': 'tool_call'}], usage_metadata={'input_tokens': 2576, 'output_tokens': 14, 'total_tokens': 2660, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 70}}),
 ToolMessage(content='(7, 3)で上に行く→上に進んだ。', name='command', id='a52c652b-d667-41a6-a32a-b07344fe59cd', tool_call_id='739ea8b2-b148-478f-9260-d2aa0ef1c919'),
 AIMessage(content='', additional_kwargs={'function_call': {'name': 'command', 'arguments': '{"action": "\\u4e0a\\u306b\\u884c\\u304f"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--6a356503-0ab5-4564-bda7-51e13f2d33c4-0', tool_calls=[{'name': 'command', 'args': {'action': '上に行く'}, 'id': '4f9ccd04-c8a3-4414-9865-f2a723ad696e', 'type': 'tool_call'}], usage_metadata={'input_tokens': 2615, 'output_tokens': 14, 'total_tokens': 2663, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 34}}),
 ToolMessage(content='(7, 2)で上に行く→ゴール! ゲームクリア。', name='command', id='b7cdd471-280a-43af-9f98-5161f1114c0f', tool_call_id='4f9ccd04-c8a3-4414-9865-f2a723ad696e'),
 AIMessage(content='Sorry, need more steps to process this request.', additional_kwargs={}, response_metadata={}, id='run--c0cb0313-b4cc-455e-a46b-7923a360e4da-0'),
 HumanMessage(content='あなたへの指示: 69手目でゴールしました。もう指示はありません。おめでとうございます。ご苦労様でした。ありがとうございました。\n\nスコア: 100点(100点満点中) (配点: ゴールした 40/40点, 熊を殺した 10/10点, 剣を取った 10/10点, 不明の座標を正解した 30/30点, ゴールの座標を正解した 10/10点)\n', additional_kwargs={}, response_metadata={}, id='b0dfc5bd-74f5-4397-a8a5-dc2449618c51'),
 AIMessage(content='ゴールしました!ゲームクリアです。\nスコアも100点満点とのこと、大変嬉しく思います。\n\nこれまでの探索で、ゴールの座標を正しく認識し、熊の対処法(剣の取得と撃破)も適切に行うことができました。迷路の複雑な構造の中、試行錯誤しながらも最終的にゴールにたどり着けたのは、計画の更新と柔軟な行動ができた結果だと思います。\n\nこの迷路探索ゲーム、非常に楽しかったです。\nありがとうございました!', additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--e80c422c-2a12-41fd-95c0-52af85885b68-0', usage_metadata={'input_tokens': 2467, 'output_tokens': 107, 'total_tokens': 2817, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 243}}),
 HumanMessage(content='お見事でした。途中 ToolMessage と AIMessage を削らないと Internal Error になる場面があってそこで記憶を飛ばしてしまったのですが、その後、ちゃんとクリアできてよかったです。', additional_kwargs={}, response_metadata={}, id='bcf349ad-08e4-4d69-889c-8f4f48432be3'),
 AIMessage(content='ありがとうございます!途中で一部記憶が飛んでしまったとのこと、ご迷惑をおかけしたにも関わらず、最終的にクリアできたこと、私も大変嬉しく思います。\n\n技術的な問題があったとのことですが、それでも最後まで導いていただき、無事にゴールにたどり着くことができました。これもご協力のおかげです。\n\n今回の経験を通して、迷路探索における計画の柔軟性や、状況に応じた判断の重要性を改めて学ぶことができました。\n\n本当にありがとうございました。', additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--530870b7-5efb-4e70-8e3e-7a28bea35e17-0', usage_metadata={'input_tokens': 2613, 'output_tokens': 98, 'total_tokens': 2761, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 50}})]

見事ゴールしました! おそらく再現性がないので、醜い形ですが、再実行はせずに公開してしまいます。