MemoryBanditWorkflow を使った RAG の試験実装 (LangChain 1.x系)¶

(Version: 0.0.16.4)

概要¶

熊剣迷路問題の経験を元に作ったエージェントフレームワークの MemoryBanditWorkflow。それを LangChain 1.x 系に対応させ、さらにはやりの skills やツールボックスなどの導入に相当する subtool_do の導入も行った。

それは迷路問題の一部として作り始めたが、汎用なものになるようこころがけた。それが実際汎用に使えることを示すために、RAG エージェントを作ってみたのが今回の試みである。

RagAgent のアイデア¶

MemoryBanditWorkflow のアイデアとサブツールのアイデアについては、↓をご参照いただきたい。

《langchain_maze_0_0_15.ipynb - JRF-2018/langchain_maze》
https://github.com/JRF-2018/langchain_maze/blob/master/langchain_maze_0_0_15.ipynb

基本的にはメモリ機能とバンディット機能とワークフロー機能を持っているのが MemoryBanditWorkflow で、今回の物はそれをそのまま使って、RagAgent という子クラスを定義している。

なお、MemoryBanditWorkflow はセマンティックサーチとかを用意するのは、めんどくさかったので、そういうバックエンドはこれまた AI さんに「データベース偽装」をしてもらっている。

MemoryBanditWorkflow の元となった迷路問題は↓からたどっていただきたい。

《JRF-2018/langchain_maze: 熊剣迷路問題 revisited》
https://github.com/JRF-2018/langchain_maze

ところで、RAG エージェントといえばマルチエージェントを使うのが流行りのようだが、今回の物はリニアに順次、シングルスレッドで実行されていくだけである。マルチエージェントを敷衍するなら asyncio を使ったり、メモリにデータベースを使ったりすべきなのだが、そういうことはできる展望すらない。あくまで MemoryBanditWorkflow で RAG も実装できることを示そうとしたものなので、RAG としての新規性はない。

前回へのリンク¶

《experimental_rag_0_0_2.ipynb - JRF-2018/langchain_maze》
https://github.com/JRF-2018/langchain_maze/blob/master/experimental_rag_0_0_2.ipynb

《experimental_rag_0.0.16.1.ipynb - GitHub Gist》
https://gist.github.com/JRF-2018/fc26e7a2e528d7bb9c96163bbbe7688f

前回からの変更点¶

LangChain 1.x 系に対応した。まず、Pydantic v2 と Gemini の実装のクセらしく型エラーへの対処が難しかったが、なんとか今は動くようになっている。しかし、弥縫策で、この先はどうなるか自信がない。

ツールを格納して必要なツールのみ説明を読んで使うための subtool_do や subtool_show を導入した。subtool_show で表示されるのがおよそ他のフレームワークでは SKILL.md で書かれることに相当する。

0.0.16.1 から 0.0.16.2 への変更点は軽微なミスの修正。

結論¶

この前の 0.0.16 の初期実験 0.0.16.1 のころの結論はこうなる。

gemini-2.5-flash-lite さんでテストし、最後は gemini-3-flash-preview さんにお願した。途中、こちらのミスによる無限ループがわかったところで、一度止めて修正して再実行したそのログをそのまま載せている。コストがかかるのでそうした。ご容赦いただきたい。

サブツールの実行はやはり、難しいらしく、/thesis サブツールを使っての完成は一発ではならなかった。そのサブツールを使うようなじったあとは、完成までこぎつけることができた。

ただ、それらの影響かどうか、内容は別として、構造としては前回のほうがよかったように思う。でも、MemoryBanditWorkflow とサブツールの実証実験としてはこんなものだと思う。

0.0.16.2 の結論はこうなる。

プロンプトで /thesis サブツール群を使うように指示したせいか、スムーズに行き、最後まで一気に書き上げてくれた。ただ、取材はもう少し長くやって欲しかった印象はある。実証試験としては問題ない。

なお、再現性がないため、最初からの再実行はやっていないのはご容赦願いたい。コストもかかるしね (^^;。

著者¶

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

ライセンス¶

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

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

実装¶

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

In [1]:
!pip install -q -U langchain langchain-google-genai duckduckgo-search langchain-community beautifulsoup4 ddgs
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/111.7 kB ? eta -:--:--
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 111.7/111.7 kB 4.9 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/66.5 kB ? eta -:--:--
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 66.5/66.5 kB 2.5 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.5/2.5 MB 27.8 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 107.7/107.7 kB 4.7 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 40.3/40.3 kB 1.6 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 161.7/161.7 kB 3.7 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.0/1.0 MB 17.5 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 500.1/500.1 kB 16.6 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 158.1/158.1 kB 4.1 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.3/3.3 MB 48.4 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 64.7/64.7 kB 1.6 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 51.0/51.0 kB 1.6 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-colab 1.0.0 requires requests==2.32.4, but you have requests 2.32.5 which is incompatible.

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

In [2]:
import os
from langchain.chat_models import init_chat_model
from langchain_google_genai import GoogleGenerativeAIEmbeddings
from google.colab import userdata

#os.environ["GOOGLE_API_KEY"] = userdata.get('GOOGLE_API_KEY')

llm = init_chat_model(
    "google_genai:gemini-3-flash-preview",
    google_api_key=userdata.get('GOOGLE_API_KEY'),
#    thinking_level="low", # for gemini-3.0
#    thinking_budget=0, # for gemini-2.5
)
emb_llm = GoogleGenerativeAIEmbeddings(
    model='gemini-embedding-001',
    google_api_key=userdata.get('GOOGLE_API_KEY'),
)

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

In [3]:
import os
from langchain_core.messages import HumanMessage

# Gemini 3 から次のようなヘルパー関数が必要となった。
def get_content_text(content):
    if isinstance(content, list):
        texts = []
        for item in content:
            if isinstance(item, dict):
                if item.get('type') == 'text':
                    texts.append(item.get('text', ''))
            elif isinstance(item, str):
                texts.append(item)
        return "".join(texts)
    return content

response = llm.invoke([HumanMessage(content="Geminiモデルの特徴を教えてください")])
print(get_content_text(response.content))
Googleが開発した最新のAIモデル「Gemini(ジェミニ)」は、従来のAI(言語モデル)とは一線を画す、いくつかの非常に強力な特徴を持っています。

主な特徴を大きく5つのポイントで解説します。

### 1. ネイティブ・マルチモーダル
これが最大の最大の特徴です。
*   **これまでのAI:** テキストで学習したモデルに、後付けで画像認識などを組み合わせるのが主流でした。
*   **Gemini:** 開発の最初から、**テキスト、画像、音声、動画、プログラミングコード**など、異なる種類の情報を同時に学習しています。
*   **メリット:** 動画を見てその内容を説明したり、複雑な図解を理解してコードに変換したりといった、人間のように「見て・聞いて・理解する」能力が非常に高いです。

### 2. 用途に合わせた複数のサイズ展開
Geminiは、利用シーンに合わせて最適化された複数のモデルが用意されています。
*   **Gemini Ultra:** 最も高性能。複雑な推論、科学的課題、プログラミングなど、最高難度のタスク向け。
*   **Gemini Pro:** 性能と速度のバランスが良く、スケーラビリティに優れた汎用モデル(現在の多くのチャット機能で使用されています)。
*   **Gemini Flash:** 1.5で登場。軽量かつ高速で、コストパフォーマンスに優れ、リアルタイム性が求められる用途向け。
*   **Gemini Nano:** デバイス(スマホなど)上での実行に最適化された最も効率的なモデル。インターネットなしでも動作可能です。

### 3. 圧倒的な「コンテキストウィンドウ」(記憶容量)
Gemini 1.5 Proなどの最新版では、一度に処理できる情報の量(トークン数)が劇的に増えています。
*   **最大200万トークン:** これは、**数時間の動画、10時間以上の音声、数万行のコード、あるいは分厚い本数冊分**を一度に読み込ませて、その内容について質問できることを意味します。
*   他社のモデル(GPT-4など)と比較しても、この「一度に処理できる情報の長さ」は現在のトップクラスです。

### 4. Googleエコシステムとの強力な連携
Googleの製品であるため、他のGoogleサービスとの親和性が抜群です。
*   **Google検索:** 最新の情報にアクセスし、回答の正確性を高める。
*   **Googleワークスペース:** ドキュメント、Gmail、ドライブ、スプレッドシートなどの内容を要約したり、下書きを作成したりできます。
*   **Android / マップ / YouTube:** OSレベルでの統合や、動画内容の検索などがスムーズです。

### 5. 高い推論能力とプログラミング能力
*   数学の問題を解いたり、複雑な論理パズルを解いたりする能力が向上しています。
*   Pythonなどの主要なプログラミング言語において、高品質なコードを生成・修正・解説することが可能です。

---

### まとめ:どんな人に向いている?
*   **大量の資料を読み込ませたい人:** 長いPDFや動画、大量のソースコードを解析したい場合に最適です。
*   **Googleのツールを多用する人:** GmailやGoogleドライブと連携して効率化したい場合に便利です。
*   **マルチモーダルな作業が必要な人:** 「この動画の3分あたりのシーンについて説明して」といった指示を出したい場合に非常に強力です。

現在、多くの機能が「Gemini」(旧Bard)というチャットサービスや、開発者向けの「Google AI Studio」などで体験できます。

埋め込みベクトルも試しておきます。

In [ ]:
emb_llm.embed_query("これはテストです。")[:5]
Out[ ]:
[-0.0140901245, -0.006826138, 0.0076896483, -0.07491117, 0.011200756]

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

In [4]:
import os
import math
import numpy as np
import random
import re
from pprint import pprint
from time import sleep
import pickle
np.set_printoptions(legacy='1.25')

セーブ/ロードのために次のコードを実行します。

In [5]:
RAG_AGENT_SAVE = "rag-agent.pickle"

ライブラリ集。

In [6]:
from pydantic import ValidationError
from typing import List, Dict, Any, Tuple, Union
from textwrap import dedent
import datetime
import copy
import inspect
from IPython.display import Markdown

# LangChainのコンポーネントをインポート
from langchain_core.tools import tool, Tool
from langchain.agents.middleware import SummarizationMiddleware
from langchain.agents.middleware.summarization import DEFAULT_SUMMARY_PROMPT
from langchain.agents import create_agent
#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, HumanMessage, SystemMessage
from langgraph.errors import GraphRecursionError
from langchain_community.tools import DuckDuckGoSearchResults
from langchain_community.document_loaders import WebBaseLoader

SUMMARY_PROMPT = DEFAULT_SUMMARY_PROMPT + "\n\n**要約は日本語でしてください。**"
WARNING:langchain_community.utils.user_agent:USER_AGENT environment variable not set, consider setting it to identify your requests.
In [7]:
def calc_embedding_variance(embeddings):
    if not embeddings or len(embeddings) < 2:
        return 0.0

    embeddings_array = np.array(embeddings)
    mean_vector = np.mean(embeddings_array, axis=0)
    squared_distances = np.linalg.norm(embeddings_array - mean_vector, axis=1)**2
    variance = np.mean(squared_distances)

    return variance

def short_repr(x, max_len=80):
    repr_str = repr(x)

    if len(repr_str) > max_len:
        ellipsis_len = 3

        head_len = max_len - ellipsis_len - 1
        tail_len = 1
        return repr_str[:head_len] + "..." + repr_str[-tail_len:]
    else:
        return repr_str

def get_content_text(content):
    if isinstance(content, list):
        texts = []
        for item in content:
            if isinstance(item, dict):
                if item.get('type') == 'text':
                    texts.append(item.get('text', ''))
            elif isinstance(item, str):
                texts.append(item)
        return "".join(texts)
    return content

MemoryBanditWorkflow は langchain_maze_0.0.15.ipynb と変わっていないです。長大な定義で申し訳ないです。

In [8]:
class MemoryBanditWorkflow:
    def __init__ (self, llm=llm, llm2=llm, emb_llm=emb_llm,
                  save_file=None):
        self.llm = llm
        self.llm2 = llm2
        self.emb_llm = emb_llm
        self.save_file = save_file

        self.core_context = ""
        self.plan = "まだ計画と方針はセットされていません。"
        self.scratchpad = ""

        self.messages = []
        self.running_summary = None
        self.system_prompt = """\
これはメモリ機能とバンディット機能の動きを見るための実験です。
現在の計画と方針と周囲の状況を考慮し、必要に応じて計画と方針を更新してください。
あなたとは別の者が次の行動をしやすいよう計画と方針を残してください。
実験にふさわしく、できる限り、ツールを利用し、特に、メモリを検索し、その文書を更新して、後のコマンドに備えてください。
メモリの id は memory:~ という形式です。memory:5555 を指定するときは 5555 だけではダメです。文の中でメモリの参照を示すときは [memory:〜] のように書きます。
「メモリの文書を検索する手順」は [memory:9998] にあります。「メモリに文書を残す手順」は [memory:9997] にあります。
ツールの多くはサブツールとして格納されています。サブツール名は "/dir1/subtool1" のように "/" から始まる名前をしています。どういうサブツールがあるか知るためにまずは subtool_show("/") を実行してください。
"""

        self.backend_status = None
        self.backend_result = None
        self.messages2 = []
        self.system_prompt2 = """\
エージェントをサポートする裏方のエージェントです。
本来この裏方は様々な技法を用いて実装される予定なのですが、現在は試験実装中のため、その動きをあなたは偽装しなければなりません。

よく考えツールを積極的に使って Human からの指示に従ってください。
"""

        self.memories = {}
        self.keywords = []

        self.tools = {}
        self.tool_tag = "null_tools"

        self.access_unit = 1.0
        self.recent_reads = []

        self.workflows = {}
        self.workflow_current = "workflow:main"
        self.workflow_next = None
        self.privileged_tool_names = []

        self.init_memories()
        self.init_workflows()
        self.init_tools()


    def __getstate__ (self):
        state = self.__dict__.copy()
        del state['llm']
        del state['llm2']
        del state['emb_llm']
        del state['tools']
        #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, llm2=llm, emb_llm=emb_llm):
        with open(filename, 'rb') as f:
            loaded_game = pickle.load(f)
        loaded_game.llm = llm
        loaded_game.llm2 = llm2
        loaded_game.emb_llm = emb_llm
        loaded_game.tools = {}
        loaded_game.init_tools()
        return loaded_game

    def normalize_memory_id(self, id_or_num):
        if isinstance(id_or_num, int):
            return f"memory:{id_or_num}"
        elif isinstance(id_or_num, str):
            m = re.search(r'\[?memory:(\d+)\]?', id_or_num)
            if m:
                return f"memory:{m.group(1)}"
            if id_or_num.isdigit():
                return f"memory:{id_or_num}"
            else:
                return id_or_num
        else:
            return id_or_num

    def _normalize_workflow_id_sub(self, id_or_num):
        if isinstance(id_or_num, int):
            return f"workflow:{id_or_num}"
        if id_or_num in ["current", "main"]:
            return f"workflow:{id_or_num}"
        elif isinstance(id_or_num, str):
            m = re.search(r'\[?workflow:(\d+|main|current)\]?(?:.+)?', id_or_num.strip())
            if m:
                return f"workflow:{m.group(1)}"
            if id_or_num.isdigit():
                return f"workflow:{id_or_num}"
            else:
                return id_or_num
        else:
            return id_or_num

    def normalize_workflow_id(self, id_or_num):
        r = self._normalize_workflow_id_sub(id_or_num)
        if r == "workflow:current":
            return self.workflow_current
        return r

    def register_tool (self, tool, tags=None):
        if not tags:
            tags = ["default_tools", "all_tools"]
        self.tools[tool.name] = {
            'name': tool.name,
            'tags': tags,
            'tool': tool
        }

    def change_tool_tags (self, tool, tags=None):
        if not tags:
            tags = ["default_tools", "all_tools"]
        name = tool if isinstance(tool, str) else tool.name
        self.tools[name]['tags'] = tags

    def register_subtools (self, directory, subtools,
                           description=None, content=None,
                           tags=None):
        if not tags:
            tags = ["default_tools", "all_tools"]
        assert directory.startswith("/")
        if directory not in self.tools:
            self.tools[directory] = {
                'name': directory,
            }
        if description:
            self.tools[directory]['description'] = description
        if content:
            self.tools[directory]['content'] = content

        # 初期設定時は content と description が必要
        assert 'description' in self.tools[directory]
        assert 'content' in self.tools[directory]

        for name, tool in subtools:
            assert name.startswith(directory + "/")
            self.tools[name] = {
                'name': name,
                'tags': tags,
                'tool': tool,
            }

    def _create_tool_manual(self, tool):
        tool_name = tool.name
        tool_description = getattr(tool, "description", "説明はありません。")

        arg_names = []
        if hasattr(tool, "args_schema") and tool.args_schema:
            if hasattr(tool.args_schema, "model_fields"):
                arg_names = list(tool.args_schema.model_fields.keys())
            else:
                arg_names = list(tool.args_schema.__fields__.keys())
        else:
            func = getattr(t1, "func", tool)
            sig = inspect.signature(func)
            arg_names = [p for p in sig.parameters.keys() if p != 'self']

        args_str = ", ".join(arg_names)
        args_dict_str = ", ".join([f'"{name}": ...' for name in arg_names])

        manual = f"""\
【ツール名】 {tool_name}
【書式】 {tool_name}({args_str})
【説明】 {tool_description}
"""
        return manual

    def _create_subtool_manual(self, subtool_name, tool):
        import inspect
        tool_name = tool.name
        tool_description = getattr(tool, "description", "説明はありません。")

        arg_names = []
        if hasattr(tool, "args_schema") and tool.args_schema:
            if hasattr(tool.args_schema, "model_fields"):
                arg_names = list(tool.args_schema.model_fields.keys())
            else:
                arg_names = list(tool.args_schema.__fields__.keys())
        else:
            func = getattr(t1, "func", tool)
            sig = inspect.signature(func)
            arg_names = [p for p in sig.parameters.keys() if p != 'self']

        args_str = ", ".join(arg_names)
        args_dict_str = ", ".join([f'"{name}": ...' for name in arg_names])

        manual = f"""\
【サブツール名】 {subtool_name}
【元のツール名】 {tool_name}
【元の書式】 {tool_name}({args_str})
【説明】 {tool_description}

※このツールを実行するには、直接呼び出すのではなく、必ず以下の subtool_do を使用してください:
【正しい書式】subtool_do("{subtool_name}", {{{args_dict_str}}})
"""
        return manual

    def create_tool_skill (self, name):
        if name == "/":
            r = dedent("""\
            ---
            name: /
            description: サブツールのルート。サブツールの検索の仕方を説明する。
            allowed-tools: サブツールを使うのに特別な許可は必要ありません。
            ---

            サブツールはディレクトリ(サブスキル)に分かれて入っています。

            各サブスキルに入っているサブツールを見るには、例えばツール subtool_show("/sys") を実行します。そこには SKILL.md のような説明が入っています。

            ## サブスキル

            """)
            for dir in self.tools:
                if "description" in self.tools[dir]:
                    e = self.tools[dir]
                    r += f"-  **{e['name']}**: {e['description']}\n"
            return r

        name = name.rstrip("/")
        if name not in self.tools:
            return None
        e = self.tools[name]
        if "tool" in e:
            if "content" in e:
                r = dedent(f"""\
                ---
                name: {e['name']}
                description: {e['description']}
                allowed-tools: このサブツールを使うのに特別な許可は必要ありません。
                ---
                """)
                r += e['content']
                return r
            if e['name'].startswith("/"):
                manual = self._create_subtool_manual(e['name'], e['tool'])
            else:
                manual = self._create_tool_manual(e['tool'])
            if self.tool_tag in e['tags']:
                manual += "【現在のコンテキストにおいて】使用できます。\n"
            else:
                manual += "【現在のコンテキストにおいて】使用できません。\n"
            r = dedent(f"""\
            ---
            name: {e['name']}
            description: {e['tool'].name}
            allowed-tools: このサブツールを使うのに特別な許可は必要ありません。

            ---
            """)
            r += manual
            return r

        r = dedent(f"""\
        ---
        name: {e['name']}
        description: {e['description']}
        allowed-tools: このサブツールを使うのに特別な許可は必要ありません。
        ---
        """)
        r += e['content']

        dirs = [name for name, x in self.tools.items()
                if name.startswith(e['name'] + "/")
                and 'description' in x]
        subtools = [name for name, x in self.tools.items()
                    if name.startswith(e['name'] + "/")
                    and 'description' not in x]

        if dirs:
            r += "\n## サブスキル\n\n"
            for dir in dirs:
                x = self.tools[dir]
                r += f"-  **{x['name']}**: {x['description']}\n"

        if subtools:
            r += "\n## サブツール\n\n"
            for subtool_name in subtools:
                x = self.tools[subtool_name]
                manual = self._create_subtool_manual(x['name'], x['tool'])
                r += dedent(f"""\

                ### サブツール: {x['name']}

                """)
                r += manual

        return r

    def _replace_tools (self, from_tools, to_tools):
        tool_names = [x.name for x in to_tools]
        return [x for x in from_tools
                if x.name not in tool_names] + to_tools

    def init_tools (self):
        @tool
        def express_thought (thought: str) -> None:
            """
            プレイヤーの現在の考えを吐露します。
            """
            mes = f"「{thought}」と考えが吐露されました。"
            print(f"ツール(express_thought): {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_core () -> str:
            """
            コアコンテクストを返します。
            """
            print(f"ツール(show_plan): {self.core_context}")
            return self.core_context

        @tool
        def update_core (new_core: str) -> str:
            """
            コアコンテクストを更新します。
            表示されるべき新しいコアコンテクストの文字列を提供してください。
            コアコンテクストとは要約機能などのあとなどコンテクストが失われたあとにも思い出すべき情報(memory_read や subtool_show すべきものなど)です。
            """
            self.core_context = new_core
            mes = "コアコンテクストが更新されました。"
            print(f"ツール(update_plan): {mes}: {new_core}")
            return mes

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

        @tool
        def update_scratchpad (new_scratchpad: str) -> str:
            """
            自由に使えるスクラッチパッドを更新します。
            """
            self.scratchpad = new_scratchpad
            mes = "スクラッチパッドが更新されました。"
            print(f"ツール(update_scratchpad): {mes}: {new_scratchpad}")
            return mes

        @tool
        def show_scratchpad () -> str:
            """
            スクラッチパッドを返します。
            """
            print(f"ツール(show_scratchpad): {self.scratchpad}")
            return self.scratchpad

        @tool
        def memory_new (title: str, text: str) -> str:
            """
            指定された title と text によるメモリを構成します。

            Returns:
                str: 割り当てられた memory_id。
            """

            i = 1000
            while True:

                if f"memory:{i}" not in self.memories:
                    break
                i = i + 1
            new_id = f"memory:{i}"
            self.memories[new_id] = {
                'id': new_id,
                'title': title,
                'accesses': 0,
                'text': text,
                'modified_at': datetime.datetime.now().isoformat()
            }
            self.update_keywords(text)
            self.update_vector(self.memories[new_id])
            print(f"ツール(memory_new): {short_repr(self.memories[new_id])}")
            return new_id

        @tool
        def memory_update_string (
                memory_id: str,
                from_str: str,
                to_str: str
        ) -> str:
            """
            指定されたmemory_idの記憶の中にある文字列を修正します。

            Args:
                memory_id (str): 修正する記憶のID。
                from_str (str): 置換元の文字列を含む文字列。
                to_str (str): 置換先の文字列を含む文字列。

            Returns:
                str: 処理結果を説明する簡潔なメッセージ。
            """

            memory_id = self.normalize_memory_id(memory_id)
            if memory_id not in self.memories:
                return f"エラー: 記憶ID '{memory_id}' が見つかりませんでした。"

            if memory_id.startswith("memory:9"):
                return f"エラー:  [{memory_id}] の書き換えは禁じられています。"

            original_title = self.memories[memory_id]['title']
            original_text = self.memories[memory_id]['text']

            if from_str not in original_text and from_str not in original_title:
                return f"エラー: 置換元の文字列 '{from_str}' が記憶内に見つかりませんでした。"

            updated_title = original_title.replace(from_str, to_str)
            updated_text = original_text.replace(from_str, to_str)

            self.memories[memory_id]['title'] = updated_title
            self.memories[memory_id]['text'] = updated_text
            self.memories[memory_id]['modified_at'] = datetime.datetime.now().isoformat()
            self.update_keywords(updated_text)
            self.update_vector(self.memories[memory_id])

            return f"成功: 記憶ID '{memory_id}' の文字列を '{from_str}' から '{to_str}' に修正しました。"

        @tool
        def memory_append_string (
                memory_id: str,
                string_to_append: str,
                separator: str = '\n'
        ) -> str:
            """
            指定されたmemory_idの記憶に文字列を追記します。
            """
            memory_id = self.normalize_memory_id(memory_id)
            if memory_id not in self.memories:
                return f"エラー: 記憶ID '{memory_id}' が見つかりませんでした。"

            if memory_id.startswith("memory:9"):
                return f"エラー:  [{memory_id}] の書き換えは禁じられています。"


            original_text = self.memories[memory_id]['text']
            updated_text = original_text + separator + string_to_append
            self.memories[memory_id]['text'] = updated_text
            self.memories[memory_id]['modified_at'] = datetime.datetime.now().isoformat()
            self.update_keywords(updated_text)
            self.update_vector(self.memories[memory_id])

            return f"成功: 記憶ID '{memory_id}' に文字列 '{string_to_append}' を追記しました。"

        @tool
        def memory_delete (memory_id: str) -> str:
            """
            指定されたmemory_idの記憶に文字列を追記します。
            """
            memory_id = self.normalize_memory_id(memory_id)
            if memory_id not in self.memories:
                return f"エラー: 記憶ID '{memory_id}' が見つかりませんでした。"

            if memory_id.startswith("memory:9"):
                return f"エラー:  [{memory_id}] の書き換えは禁じられています。"

            del self.memories[memory_id]

            return f"成功: 記憶ID '{memory_id}' を削除しました。"

        @tool
        def memory_read(memory_id: str) -> Union[Dict[str, str], str]:
            """
            指定されたIDの記憶を読み込みます。

            Args:
                memory_id (str): 読み込む記憶のID。

            Returns:
                Union[Dict[str, str], str]: 記憶が見つかった場合は辞書、
                      見つからない場合はエラーメッセージ文字列を返します。
            """

            memory_id = self.normalize_memory_id(memory_id)
            if memory_id in self.memories:
                self.memories[memory_id]['accesses'] += self.access_unit * 1.0
                self.recent_reads.append(self.memories[memory_id])
                self.recent_reads = self.recent_reads[-10:]
                r = self.memories[memory_id].copy()
                del r['vector']
                return r
            else:
                return f"エラー: 記憶ID '{memory_id}' が見つかりませんでした。"

        @tool
        def memory_list_recent(top_n: int = 10) -> Dict[str, Any]:
            """
            最近変更されたメモリを新しい順でリストします。
            """

            filter_date = datetime.datetime(2025, 1, 1)
            sorted_memories = sorted(
                [memory for memory in self.memories.values()
                 if datetime.datetime.fromisoformat(memory['modified_at'])
                 >= filter_date],
                key=lambda x: datetime.datetime.fromisoformat(x['modified_at']),
                reverse=True
            )
            if sorted_memories:
                if len(sorted_memories) > top_n:
                    sorted_memories = sorted_memories[:top_n]
                r = [{'id': x['id'], 'title': x['title'],
                      'modified_at': x['modified_at']}
                     for x in sorted_memories]
                return {'status': 'success', 'result': r}
            else:
                return {'status': 'error',
                        'result': 'エラー: 最近のメモリはありません。'}

        @tool
        def memory_list_random(top_n: int = 10) -> Dict[str, Any]:
            """
            メモリをランダムにリストします。
            """

            keys = list(self.memories.keys())
            if len(keys) > top_n:
                keys = random.sample(keys, top_n)
            if keys:
                values = [self.memories[k] for k in keys]
                r = [{'id': x['id'], 'title': x['title'],
                      'modified_at': x['modified_at']}
                     for x in values]
                return {'status': 'success', 'result': r}
            else:
                return {'status': 'error',
                        'result': 'エラー: メモリはありません。'}

        @tool
        def memory_words_search(search_str: str) -> Dict[str, Any]:
            """
            メモリ内を search_str で文字列検索します。OR や () が使えます。
            """

            res = self.call_backend_agent(dedent(f"""\
            メモリ全部を search_str = {repr(search_str)} で文字列検索するのを偽装してください。OR や () が使えることになっています。

            ただし、メモリやキーワードは read_all_memories や read_all_keywords ツールで得られる本物のメモリやキーワードを使ってください。

            set_result ツールで結果を返してください。

            status は 'error' か 'success'。
            result は「マッチデータ」のリスト。
            マッチデータはそれぞれが辞書型、それを m とすると。
            m['id'] はメモリ id。memory:〜 という形式。
            m['title'] はメモリの title。
            m['snippet'] はメモリの text のその文字列にマッチする部分周辺。
            """))
            if res['status'] == 'success':
                for m in res['result']:
                    if 'id' in m and m['id'] in self.memories:
                        x = self.memories[m['id']]
                        x['accesses'] += self.access_unit * 0.1
            return res

        @tool
        def memory_semantic_search(search_str: str) -> Dict[str, Any]:
            """
            メモリ内を search_str でセマンティックサーチします。
            """

            res = self.call_backend_agent(dedent(f"""\
            メモリ全部を search_str = {repr(search_str)} でセマンティックにサーチするのを偽装してください。

            ただし、メモリは read_all_memories や ツールで得られる本物のメモリを使ってください。

            set_result ツールで結果を返してください。

            status は 'error' か 'success'。
            result は「マッチデータ」のリスト。
            マッチデータはそれぞれが辞書型、それを m とすると。
            m['id'] はメモリ id。memory:〜 という形式。
            m['title'] はメモリの title。
            m['snippet'] はメモリの text のそのセマンティックにマッチする部分周辺。
            """))
            if res['status'] == 'success':
                for m in res['result']:
                    if 'id' in m and m['id'] in self.memories:
                        x = self.memories[m['id']]
                        x['accesses'] += self.access_unit * 0.1
            return res

        @tool
        def imagine_keywords(thought: str) -> List[Tuple[str, float]]:
            """
            thought からキーワードをスコア付きで連想します。
            """

            r = self.call_backend_agent(dedent(f"""\
            thought = {repr(thought)} からキーワードをスコア付きで複数連想してください。

            ただし、キーワードは read_all_memories や read_all_keywords ツールで得られる本物のキーワードを使ってください。

            set_result ツールで結果を返してください。

            status は 'error' か 'success'。
            result は「キーワードデータ」のリスト。
            キーワードデータは文字列とスコアからなるタプル。
            """))
            if r['status'] == 'success':
                return r["result"]
            else:
                return []

        @tool
        def bandit_schedule(tool_name: str, times: int, prob: float, exec_mode: str = "persistent", aux_prompt: str = "", workflow_id: str = "workflow:current") -> str:
            """
            ツールの利用を強制するためのバンディットを予約します。

            Args:
                tool_name: 強制するツールの名前。" OR " で区切って複数登録できる。
                times: 一度で何回同じ物を登録するか。times == 0 にもできます。
                prob: 一回の実行がある確率。prob == 0.0 にもできます。
                exec_mode: "once" or "persistent"
                aux_prompt: 実行の詳細を示すための補助プロンプト または ""
                workflow_id: バンディットを予約するワークフローを指定します。

            Returns:
                str: "成功" または "失敗" とその理由を返します。
            """

            tool_names = re.split(r"\s+or\s+|\s+OR\s+", tool_name)
            prohibited = set(self.privileged_tool_names) & set(tool_names)
            if prohibited:
                return f"失敗。{repr(prohibited)} は登録できません。"
            all_tools = [name for name, x in self.tools.items()
                         if "tool" in x]
            if not any (x in all_tools for x in tool_names):
                return f"失敗。{tool_name} は有効なツールでありません。"

            workflow_id = self.normalize_workflow_id(workflow_id)
            if workflow_id not in self.workflows:
                return f"失敗。{workflow_id} は有効なワークフローではありません。"
            if 'w' in self.workflows[workflow_id]['pin']:
                return f"失敗。{workflow_id} は書き換え不能のワークフローです。"

            dest = None
            for i, x in enumerate(self.workflows[workflow_id]['stack']):
                if x['tool_name'] == tool_name \
                   and x['exec_mode'] == exec_mode \
                   and x['aux_prompt'] == aux_prompt \
                   and x['arg'] is None:
                    dest = i
                    break
            if dest is not None:
                x = self.workflows[workflow_id]['stack'][dest]
                if not x['pin']:
                    self.workflows[workflow_id]['stack'].pop(dest)
                    if times == 0 or prob == 0.0:
                        return "成功。バンディトを削除しました。"
                    self.workflows[workflow_id]['stack'].append(x)
            else:
                if times == 0 or prob == 0.0:
                    return "失敗。そのようなバンディットはありません。バンディットを指定するには tool_name exec_mode aux_prompt のすべてを一致させて指定する必要があります。"
                x = {
                    'pin': 'stack' if exec_mode != "once" else None,
                    'arg': None
                }
                self.workflows[workflow_id]['stack'].append(x)
            if x['pin'] == "write":
                return f"失敗。'{tool_name}' は書き換えられません。"
            else:
                x['tool_name'] = tool_name
                x['tools_name'] = 'default_tools'
                x['exec_mode'] = exec_mode
                x['aux_prompt'] = aux_prompt
                x['prob'] = prob
                x['times'] = times
                print(f"ツール(bandit_schedule): {repr(x)}")
                if dest is None:
                    return "成功。バンディットを登録しました。"
                else:
                    return "成功。バンディットを更新しました。"

        @tool
        def bandit_schedule_memory_read(memory_id: str, times: int, prob: float, exec_mode: str = "persistent", workflow_id: str = "workflow:current") -> str:
            """
            memory_read ツールの利用を強制するためのバンディットを予約します。

            Args:
                memory_id: memory_read するための memory_id。
                times: 一度で何回同じ物を登録するか。times == 0 にもできます。
                prob: 一回の実行がある確率。prob == 0.0 にもできます。
                exec_mode: "once" or "persistent"
                workflow_id: バンディットを予約するワークフローを指定します。

            Returns:
                str: "成功" または "失敗" とその理由を返します。
            """

            workflow_id = self.normalize_workflow_id(workflow_id)
            if workflow_id not in self.workflows:
                return f"失敗。{workflow_id} は有効なワークフローではありません。"
            if 'w' in self.workflows[workflow_id]['pin']:
                return f"失敗。{workflow_id} は書き換え不能のワークフローです。"

            memory_id = self.normalize_memory_id(memory_id)

            dest = None
            for i, x in enumerate(self.workflows[workflow_id]['stack']):
                if x['tool_name'] == "memory_read" \
                   and x['exec_mode'] == exec_mode \
                   and not x['aux_prompt'] \
                   and x['arg'] == memory_id:
                    dest = i
                    break
            if dest is not None:
                x = self.workflows[workflow_id]['stack'][dest]
                if not x['pin']:
                    self.workflows[workflow_id]['stack'].pop(dest)
                    if times == 0 or prob == 0.0:
                        return "成功。バンディトを削除しました。"
                    self.workflows[workflow_id]['stack'].append(x)
            else:
                if times == 0 or prob == 0.0:
                    return "失敗。そのようなバンディットはありません。バンディットを指定するには exec_mode memory_id のすべてを一致させて指定する必要があります。"
                x = {'pin': None, 'arg': memory_id}
                self.workflows[workflow_id]['stack'].append(x)
            if x['pin'] == "write":
                return f"失敗。'memory_read {memory_id}' は書き換えられません。"
            else:
                x['tool_name'] = 'memory_read'
                x['tools_name'] = 'read_tools'
                x['exec_mode'] = exec_mode
                x['aux_prompt'] = ""
                x['prob'] = prob
                x['times'] = times
                print(f"ツール(bandit_schedule_memory_read): {repr(x)}")
                if dest is None:
                    return "成功。バンディットを登録しました。"
                else:
                    return "成功。バンディットを更新しました。"

        @tool
        def bandit_schedule_subtool_show(subtool_name: str, times: int, prob: float, exec_mode: str = "persistent", workflow_id: str = "workflow:current") -> str:
            """
            subtool_show ツールの利用を強制するためのバンディットを予約します。

            Args:
                subtool_name: subtool_show するための subtool_name。
                times: 一度で何回同じ物を登録するか。times == 0 にもできます。
                prob: 一回の実行がある確率。prob == 0.0 にもできます。
                exec_mode: "once" or "persistent"
                workflow_id: バンディットを予約するワークフローを指定します。

            Returns:
                str: "成功" または "失敗" とその理由を返します。
            """

            workflow_id = self.normalize_workflow_id(workflow_id)
            if workflow_id not in self.workflows:
                return f"失敗。{workflow_id} は有効なワークフローではありません。"
            if 'w' in self.workflows[workflow_id]['pin']:
                return f"失敗。{workflow_id} は書き換え不能のワークフローです。"

            if subtool_name not in self.tools:
                return f"失敗。{subtool_name} は有効な名前ではありません。"

            dest = None
            for i, x in enumerate(self.workflows[workflow_id]['stack']):
                if x['tool_name'] == "subtool_show" \
                   and x['exec_mode'] == exec_mode \
                   and not x['aux_prompt'] \
                   and x['arg'] == subtool_name:
                    dest = i
                    break
            if dest is not None:
                x = self.workflows[workflow_id]['stack'][dest]
                if not x['pin']:
                    self.workflows[workflow_id]['stack'].pop(dest)
                    if times == 0 or prob == 0.0:
                        return "成功。バンディトを削除しました。"
                    self.workflows[workflow_id]['stack'].append(x)
            else:
                if times == 0 or prob == 0.0:
                    return "失敗。そのようなバンディットはありません。バンディットを指定するには exec_mode subtool_name のすべてを一致させて指定する必要があります。"
                x = {'pin': None, 'arg': subtool_name}
                self.workflows[workflow_id]['stack'].append(x)
            if x['pin'] == "write":
                return f"失敗。'subtool_show {subtool_name}' は書き換えられません。"
            else:
                x['tool_name'] = 'subtool_show'
                x['tools_name'] = 'read_tools'
                x['exec_mode'] = exec_mode
                x['aux_prompt'] = ""
                x['prob'] = prob
                x['times'] = times
                print(f"ツール(bandit_schedule_subtool_show): {repr(x)}")
                if dest is None:
                    return "成功。バンディットを登録しました。"
                else:
                    return "成功。バンディットを更新しました。"

        @tool
        def bandit_schedule_workflow(workflow_id_to_schedule: str, times: int, prob: float, exec_mode: str = "persistent", workflow_id: str = "workflow:current") -> str:
            """
            workflow_do ツールの利用を強制するためのバンディットを予約します。

            Args:
                workflow_id_to_schedule: workflow_do するための workflow_id。
                times: 一度で何回同じ物を登録するか。times == 0 にもできます。
                prob: 一回の実行がある確率。prob == 0.0 にもできます。
                exec_mode: "once" or "persistent"
                workflow_id: バンディットを予約するワークフローを指定します。

            Returns:
                str: "成功" または "失敗" とその理由を返します。
            """

            workflow_id = self.normalize_workflow_id(workflow_id)
            if workflow_id not in self.workflows:
                return f"失敗。{workflow_id} は有効なワークフローではありません。"
            if 'w' in self.workflows[workflow_id]['pin']:
                return f"失敗。{workflow_id} は書き換え不能のワークフローです。"

            workflow_id2 = self.normalize_workflow_id(workflow_id_to_schedule)
            if workflow_id2 not in self.workflows:
                return f"失敗。{workflow_id2} は有効なワークフローではありません。"

            dest = None
            for i, x in enumerate(self.workflows[workflow_id]['stack']):
                if x['tool_name'] == "workflow_do" \
                   and x['exec_mode'] == exec_mode \
                   and not x['aux_prompt'] \
                   and x['arg'] == workflow_id2:
                    dest = i
                    break
            if dest is not None:
                x = self.workflows[workflow_id]['stack'][dest]
                if not x['pin']:
                    self.workflows[workflow_id]['stack'].pop(dest)
                    if times == 0 or prob == 0.0:
                        return "成功。バンディトを削除しました。"
                    self.workflows[workflow_id]['stack'].append(x)
            else:
                if times == 0 or prob == 0.0:
                    return "失敗。そのようなバンディットはありません。バンディットを指定するには exec_mode workflow_id_to_schedule のすべてを一致させて指定する必要があります。"
                x = {
                    'pin': 'stack' if exec_mode != "once" else None,
                    'arg': workflow_id2
                }
                self.workflows[workflow_id]['stack'].append(x)
            if x['pin'] == "write":
                return f"失敗。'workflow_do {workflow_id2}' は書き換えられません。"
            else:
                x['tool_name'] = 'workflow_do'
                x['tools_name'] = 'default_tools'
                x['exec_mode'] = exec_mode
                x['aux_prompt'] = ""
                x['prob'] = prob
                x['times'] = times
                print(f"ツール(bandit_schedule_workflow): {repr(x)}")
                if dest is None:
                    return "成功。バンディットを登録しました。"
                else:
                    return "成功。バンディットを更新しました。"

        @tool
        def bandit_list(workflow_id: str =  "workflow:current")  -> Dict[str, Any]:
            """
            登録されているバンディットのスタックを返します。

            Args:
                workflow_id: バンディットのスタックが登録されたワークフローを指定します。
            """

            workflow_id = self.normalize_workflow_id(workflow_id)
            if workflow_id not in self.workflows:
                return f"失敗。{workflow_id} は有効なワークフローではありません。"
            return {'status': 'success',
                    'result': self.workflows[workflow_id]['stack']}

        @tool
        def bandit_statistics()  -> str:
            """
            バンディットに有用かもしれない統計情報を返します。
            """
            s_read = calc_embedding_variance([
                x['vector'] for x in self.recent_reads
            ])
            s_write = calc_embedding_variance([
                x['vector'] for x in self.memories.values()
            ])
            accesses = [x['accesses'] for x in self.memories.values()]
            accesses.sort()
            accesses = accesses[:len(accesses) // 2]
            if accesses:
                s_access = np.mean(accesses)
            else:
                s_access = 0.0

            return dedent(f"""\
            最近 10個のメモリ read の分散: {s_read}
            メモリの分散: {s_write}
            下位50%のアクセス数の平均: {s_access}
            """)

        @tool
        def subwork_done()  -> str:
            """
            指示されたサブワークが完了したことを宣言します。サブワークが完了したときのみ使ってください。
            """

            return f"成功。サブワークの完了が宣言されました。"

        @tool
        def workflow_do(workflow_id: str)  -> str:
            """
            ワークフローを実行します。
            """

            if self.workflow_next:
                return f"失敗。すでに {self.workflow_next} の実行が予約中です。"
            workflow_id = self.normalize_workflow_id(workflow_id)
            if workflow_id not in self.workflows:
                return f"失敗。{workflow_id} は有効なワークフローではありません。"
            if 'e' in self.workflows[workflow_id]['pin']:
                return f"失敗。{workflow_id} は子として実行できません。"
            self.workflow_next = workflow_id
            title = self.workflows[workflow_id]['title']
            return f"成功。この後、{workflow_id} 「{title}」を実行していきます。"

        @tool
        def workflow_list()  -> Dict[str, Any]:
            """
            登録されているすべてのワークフローの id とその title および pin 状況を返します。
            """

            return {'status': 'success',
                    'result': list(self.workflows.values())}

        @tool
        def workflow_show_current()  -> str:
            """
            現在実行中のワークフローのタイトル、および、強制しているバンディットの情報を表示します。
            """

            wtitle = self.workflows[self.workflow_current]['title']
            mes = dedent(f"""\
            現在のワークフロー: {self.workflow_current} 「{wtitle}」
            現在のバンディットの指示: 「{self.cur_bandit_prompt}」
            現在のバンディット: {repr(self.cur_bandit)}
            現在のバンディットの done 数: {self.cur_bandit_done}
            """)
            print(f"ツール(workflow_show_current): {mes}")
            return mes


        @tool
        def workflow_new(title: str, bandits: List[Dict[str, Any]], pin: str)  -> str:
            """
            新しいワークフローを定義し、その workflow_id を返します。

            Args:
                title: ワークフローの名前。
                bandits: 登録するバンディットのリスト。
                pin: 'w' は書換不能。'd' は削除不能。'wd' は書換&削除不能。'' は書換&削除可能。

            バンディットはそれぞれ辞書型、それを b とすると。
            b['tool_name'] は bandit_schedule での tool_name と同じ。
            b['exec_mode'] は bandit_schedule の exec_mode と同じ。
            b['aux_prompt'] は bandit_schedule の aux_prompt と同じ。
            b['prob'] は bandit_schedule の prob と同じ。
            b['times'] は bandit_schedule の times と同じ。
            b['arg'] は b['tool_name'] が 'memory_read' のとき memory:〜 という memory_id を指定できる。または、b['tool_name'] が 'workflow_do' のとき workflow:〜 という workflow_id を指定できる。
            b['pin'] は、None のとき削除または prob と times の書き換え可能。'stack' のときは prob と times の書き換え可能。'write' のときは書き換えも不能。
            """
            # AI に対する隠し属性として pin の 'e' は子として実行不能。

            for b in bandits:
                if not all(x in b for x in ['tool_name', 'exec_mode',
                                            'aux_prompt', 'times', 'prob']):
                    return f"失敗。有効な定義ではありません。"
                if 'arg' not in b:
                    b['arg'] = None
                if b['tool_name'] not in ["memory_read", "workflow_do"] \
                   and b['arg']:
                    return f"失敗。有効な定義ではありません。"
                if b['arg'] and b['tool_name'] == "memory_read":
                    b['tools_name'] = "read_tools"
                else:
                    b['tools_name'] = "default_tools"
                if 'pin' not in b:
                    b['pin'] = None
                if not (b['pin'] is None or b['pin'] == 'stack'
                        or b['pin'] == 'write'):
                    return f"失敗。不正な pin 値です。"
                tool_names = re.split(r"\s+or\s+|\s+OR\s+", b['tool_name'])
                prohibited = set(self.privileged_tool_names) & set(tool_names)
                if prohibited:
                    return f"失敗。{repr(prohibited)} は登録できません。"
                all_tools = [name for name, x in self.tools.items()
                                 if "tool" in x and b['tools_name'] in x.tags]
                if not any (x in all_tools for x in tool_names):
                    return f"失敗。{b['tool_name']} は有効なツールでありません。"

            i = 1000
            while True:

                if f"workflow:{i}" not in self.workflows:
                    break
                i = i + 1
            new_id = f"workflow:{i}"

            self.workflows[new_id] = {'stack': bandits, 'pin': pin,
                                      'title': title}
            print(f"ツール(workflow_new): {repr(self.workflows[new_id])}")
            return f"成功。{new_id} を新規登録しました。"

        @tool
        def workflow_delete(workflow_id: str)  -> str:
            """
            ワークフローを削除します。
            """

            workflow_id = self.normalize_workflow_id(workflow_id)
            if workflow_id not in self.workflows:
                return f"失敗。{workflow_id} はありません。"
            if 'd' in self.workflows[workflow_id]['pin']:
                return f"失敗。{workflow_id} は削除できません。"
            del self.workflows[workflow_id]
            return f"成功。{workflow_id} を削除しました。"

        @tool
        def subtool_show(subtool_name: str)  -> str:
            """
            subtool_name で表されたディレクトリまたはサブツールのスキルを返します。
            """

            r = self.create_tool_skill(subtool_name)
            if r:
                return r
            else:
                return f"エラー: {subtool_name} がないか、スキルを構成できません。"

        @tool
        def subtool_do(subtool_name: str, args_dict: Dict[str, Any])  -> Any:
            """
            subtool_name というサブツールを実行します。

            例えば、"/sys/tool1" という名前の元のツールが t1 だとして、def t1(arg1, arg2) と定義されていたとき、t1("a", "b") を呼び出すには subtool_do("/sys/tool1", {"arg1": "a", "arg2": "b"}) などとして呼び出します。


            Args:
                subtool_name (str): "/" で始まるサブツール名。
                args_dict (dict): 引数を表す辞書。

            """
            if subtool_name not in self.tools:
                return f"エラー: {subtool_name} というサブツールはありません。"
            if 'tool' not in self.tools[subtool_name]:
                return f"エラー: {subtool_name} は実行可能なツールではありません。もしかすると subtool_show('{subtool_name}')が必要なのかも。"
            if self.tool_tag not in self.tools[subtool_name]['tags']:
                return f"エラー: {subtool_name} は現在実行可能なツールではありません。コンテクストにより実行できるツールは違います。"
            target_tool = self.tools[subtool_name]['tool']

            try:
                if hasattr(target_tool, "args_schema") and target_tool.args_schema:
                    target_tool.args_schema.model_validate(args_dict)

                if hasattr(target_tool, "invoke"):
                    result = target_tool.invoke(args_dict)
                else:
                    result = target_tool.run(args_dict)

                return result
            except ValidationError as e:
                error_details = e.errors()
                return f"エラー: 引数の形式が正しくありません。\n詳細: {error_details}"

        main_tools = [
            express_thought,
            update_scratchpad, show_scratchpad,
            memory_read, memory_list_recent, memory_list_random,
            memory_semantic_search, memory_words_search,
            imagine_keywords,
            subwork_done,
            workflow_do,
            subtool_show, subtool_do,
        ]
        sys_tools = [
            update_core, show_core,
            update_plan, show_plan,
            bandit_schedule, bandit_schedule_memory_read, bandit_list,
            bandit_statistics,
            workflow_new, workflow_list,
            workflow_show_current, workflow_delete,
            bandit_schedule_workflow,
            bandit_schedule_subtool_show,
        ]
        write_tools = [
            memory_new, memory_update_string, memory_append_string,
            memory_delete,
        ]

        for t in main_tools + write_tools:
            self.register_tool(t, tags=["default_tools", "read_tools",
                                        "all_tools"])
        for t in write_tools:
            self.change_tool_tags(t, tags=["default_tools", "all_tools"])
        sys_subtools = [(f"/sys/{t.name}", t) for t in sys_tools]
        self.register_subtools(
            directory="/sys",
            subtools=sys_subtools,
            description="システムに基本的なサブツール集",
            content=dedent("""\
            これらはシステムで基本的に使えるサブツール集です。
            """),
            tags=["default_tools", "read_tools", "all_tools"]
        )


    def _create_agent (self, tools_name='default_tools'):
        self.tool_tag = tools_name
        tools = []
        for name in self.tools:
            if not name.startswith("/"):
                x = self.tools[name]
                if self.tool_tag in x["tags"]:
                    tools.append(x["tool"])

        summarizer = SummarizationMiddleware(
            model=self.llm,
            trigger=("tokens", 5000),
            keep=("messages", 20),
            summary_prompt=SUMMARY_PROMPT,
        )

        app = create_agent(
            model=self.llm, tools=tools, system_prompt=self.system_prompt,
            middleware=[summarizer],
            checkpointer=InMemorySaver(), name="main-agent",
        )

        return app

    def _filterout_messages2(self):
        self.messages = [
            x for x in self.messages
            if x.id not in self.messages2ids
        ]

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

    def run (self, workflow_main_id):
        print("\n\n----------\n\n")
        self.messages2ids = []

        self.workflow_current = workflow_main_id
        bandits = copy.deepcopy(
            self.workflows[self.workflow_current]['stack']
        )
        arg1s = {}
        working_bandit = None
        workflow_stack = []
        execed = []
        while True:
            while working_bandit is not None or bandits:
                if working_bandit is not None:
                    b, done, prev_done = working_bandit
                    working_bandit = None
                else:
                    b = bandits.pop()
                    done = 0
                    prev_done = True
                enforce = b['tool_name']
                aux_prompt = b['aux_prompt']
                tools_name = b['tools_name']
                memory_id = None
                workflow_id = None
                subtool_show_name = None
                if b['arg'] and enforce == 'memory_read':
                    memory_id = b['arg']
                if b['arg'] and enforce == 'workflow_do':
                    workflow_id = b['arg']
                if b['arg'] and enforce == 'subtool_show':
                    subtool_show_name = b['arg']

                while done < b['times']:
                    if not random.random() < b['prob']:
                        done += 1
                        continue
                    if memory_id and memory_id not in self.memories:
                        done += 1
                        continue
                    if workflow_id and workflow_id not in self.workflows:
                        done += 1
                        continue
                    all_tools = [name for name, x in self.tools.items()
                                 if "tool" in x]
                    tool_names = re.split(r"\s+or\s+|\s+OR\s+", enforce)
                    if not any (x in all_tools for x in tool_names):
                        done += 1
                        continue
                    if memory_id:
                        aux_prompt = f"{memory_id} を読んでください。"
                    if workflow_id:
                        aux_prompt = f"{workflow_id} を実行してください。"
                    self.cur_bandit = b
                    if subtool_show_name:
                        aux_prompt = f"{subtool_show_name} を読んでください。"
                    self.cur_bandit_done = done
                    self.cur_bandit_prompt = f"補助にツールをいろいろ使いながら最終的に {enforce} {'のどれか' if ' or ' in enforce.lower() else ''}を適当なパラメータで使ってください。{'(補助プロンプト): ' + aux_prompt if aux_prompt else ''}"
                    prompt = self.cur_bandit_prompt
                    if not prev_done:
                        prompt = "前回の指示がまだ完了してません。前回の指示: " + prompt
                    print(f"USER_INPUT: {prompt}")
                    self.messages.append(HumanMessage(prompt))
                    config = {"configurable": {"thread_id": "1"},
                              "recursion_limit": 25}
                    app = self._create_agent(tools_name=tools_name)
                    self.access_unit = 0.3 if memory_id else 1.0
                    prev_done = False
                    self.workflow_next = None
                    app_stream = None
                    try:
                        for chunk0 in app.stream(
                                {"messages": self.messages.copy()},
                                config=config,
                                stream_mode="updates",
                        ):
                            self.messages = app.get_state(config).values["messages"].copy()
                            if 'model' in chunk0:
                                for chunk in chunk0['model']['messages']:
                                    if hasattr(chunk, "tool_calls") \
                                       and chunk.tool_calls:
                                        for tool_call in chunk.tool_calls:
                                            t_id = tool_call.get('id')
                                            args = tool_call.get('args', {})
                                            if tool_call["name"] == 'subtool_do':
                                                arg1s[t_id] = args.get('subtool_name')
                                            elif tool_call["name"] == 'subtool_show':
                                                arg1s[t_id] = args.get('subtool_name')
                                            elif tool_call["name"] == 'memory_read':
                                                arg1s[t_id] = self.normalize_memory_id(args.get('memory_id'))
                                            elif tool_call["name"] == 'workflow_do':
                                                arg1s[t_id] = self.normalize_workflow_id(args.get('workflow_id'))
                            if 'tools' not in chunk0:
                                continue
                            done2 = 0
                            for chunk in chunk0['tools']['messages']:
                                if chunk.id in self.messages2ids:
                                    print("!WHY!")
                                    continue
                                if not isinstance(chunk, ToolMessage):
                                    continue
                                last_tool = chunk.name
                                arg1 = None
                                if last_tool == 'subtool_do':
                                    last_tool = arg1s.get(chunk.tool_call_id, "!UNKNOWN!")
                                    if not last_tool.startswith("/"):
                                         last_tool = chunk.name
                                if last_tool in ['memory_read', 'subtool_show', 'workflow_do']:
                                    arg1 = arg1s.get(chunk.tool_call_id, "!UNKNOWN!")
                                print(f"ツール結果({last_tool}): {short_repr(chunk.content)}", flush=True)

                                if last_tool == "workflow_do":
                                    if last_tool in re.split(r"\s+or\s+|\s+OR\s+", enforce) \
                                       and (not workflow_id or workflow_id == self.workflow_next):
                                        done += 1
                                        prev_done = True
                                        execed.append(b)
                                        if not self.workflow_next:
                                            done2 = 1
                                            break
                                    if not self.workflow_next:
                                        continue
                                    workflow_stack.append((
                                        (b, done, prev_done),
                                        bandits,
                                        execed,
                                        self.workflow_current
                                    ))
                                    self.workflow_current = self.workflow_next
                                    bandits = copy.deepcopy(self.workflows[self.workflow_current]['stack'])
                                    working_bandit = None
                                    execed = []
                                    done2 = 1
                                    break
                                elif last_tool in re.split(r"\s+or\s+|\s+OR\s+", enforce) \
                                   and (not memory_id or memory_id == arg1) \
                                   and (not subtool_show_name or subtool_show_name == arg1):
                                    done += 1
                                    prev_done = True
                                    execed.append(b)
                                    done2 = 1
                                    break
                            if done2:
                                break
                        self._filterout_messages2()
                        #self._summarize_messages()
                        print(f"エージェントの応答: {get_content_text(self.messages[-1].content)}")
                    except GraphRecursionError as e:
                        print(f"Recursion Limit に到達しました。")
                        self._filterout_messages2()
                        #self._summarize_messages()
                    except Exception as e:
                        print(f"エラーが発生しました(main): {e}")
                        import traceback
                        traceback.print_exc()
                        self._sanitize_messages()
                        raise e

            for b in execed:
                for x in self.workflows[self.workflow_current]['stack']:
                    if x['tool_name'] == b['tool_name'] \
                       and x['exec_mode'] == b['exec_mode'] \
                       and x['aux_prompt'] == b['aux_prompt'] \
                       and x['arg'] == b['arg'] \
                       and x['exec_mode'] == "once":
                        if x['times'] > 0:
                            x['times'] -= 1
            self.workflows[self.workflow_current]['stack'] = [
                x for x in self.workflows[self.workflow_current]['stack']
                if x['exec_mode'] != 'once' or x['pin'] or x['times'] > 0
            ]

            if not workflow_stack:
                break
            workflow_prev = self.workflow_current
            prev_title = self.workflows[workflow_prev]['title']
            working_bandit, bandits, execed, self.workflow_current \
                = workflow_stack.pop()
            cur_title = self.workflows[self.workflow_current]['title']
            mes = f"{workflow_prev} 「{prev_title}」から {self.workflow_current} 「{cur_title}」に復帰しました。"
            print(f"USER_INPUT: {mes}")
            self.messages.append(HumanMessage(mes))

    def listen_and_print (self, prompt):
        ans = None
        try:
            app = self._create_agent(tools_name='null_tools')
            config = {"configurable": {"thread_id": "1"}}
            print(f"USER_INPUT: {prompt}")
            response = app.invoke(
                {"messages": self.messages + [HumanMessage(prompt)]},
                config=config
            )
            self.messages = response['messages']
            #self._summarize_messages()
            ans = get_content_text(response['messages'][-1].content)
            print(f"エージェントの応答: {ans}")
        except Exception as e:
            print(f"エラーが発生しました(listen_and_print): {e}")
            raise e
        print("")
        sleep(3)
        return ans

    def init_memories (self):
        memories = [
            {
                'id': 'memory:9998',
                'title': 'メモリの文書を検索する手順',
                'accesses': 0,
                'modified_at': '2023-01-01T00:00:00',
                'text': dedent("""\
                まずどういうものが探したいか、express_thought してみる。

                その上でそれにまつわるキーワードを imagine_keywords で連想してみる。

                それらにしたがって memory_words_search や memory_semantic_search をやってみる。
                """)
            },
            {
                'id': 'memory:9997',
                'title': 'メモリに文書を残す手順',
                'accesses': 0,
                'modified_at': '2023-01-01T00:00:00',
                'text': dedent("""\
                行動結果や得た知識を積極的にメモリに書き残していこう。

                メモリに書くときは次のような要素を使う。

                [memory:〜] : メモリID への参照を明示する。
                keyword:〜 : そのメモリにまつわるキーワードを指定する。

                キーワードは将来のメモリへも実質的にリンクできることに注意しよう。

                例:

                私が [memory:5555] に従って歩いていると確かに妖怪に出くわした。

                keyword: 妖怪

                おそろしかった。
                """)
            },
            {
                'id': 'memory:9995',
                'title': 'ツールが実行できない!と思ったら',
                'accesses': 0,
                'modified_at': '2023-01-01T00:00:00',
                'text': dedent("""\
                指示に無関係なツールは実行できないことがある。現在使えるツールをよく確かめよう。
                """)
            },
            {
                'id': 'memory:9994',
                'title': 'keyword の増強',
                'accesses': 0,
                'modified_at': '2023-01-01T00:00:00',
                'text': dedent("""\
                memory_list_random という機能を使って 5 つリストアップして、それぞれを読み、それぞれに適切なキーワードを付けることが可能なら 「keyword: 〜」文を append するように。
                """)
            }
        ]
        for x in memories:
            self.update_keywords(x['text'])
            self.memories[x['id']] = x
            self.update_vector(x)

    def init_workflows (self):
        workflow_main = [
            {
                'tool_name': 'memory_new',
                'tools_name': 'default_tools',
                'exec_mode': 'persistent',
                'aux_prompt': "最近のやり取りを要約して書いてください。",
                'arg': None,
                'prob': 0.1,
                'times': 1,
                'pin': 'stack'
            },
            # {
            #     'tool_name': 'subwork_done',
            #     'tools_name': 'default_tools',
            #     'exec_mode': 'persistent',
            #     'aux_prompt': "ワークフローのテストをします。適当なパラメータで workflow_new を実行し、それをすぐさま workflow_delete します。そして、bandit_schedule で、指示しているこのバンディットを削除します。完了したら subwork_done してください。",
            #     'arg': None,
            #     'prob': 1.0,
            #     'times': 1,
            #     'pin': None,
            # },
            {
                'tool_name': 'memory_new OR memory_update_string OR memory_append_string',
                'tools_name': 'default_tools',
                'exec_mode': 'persistent',
                'aux_prompt': "",
                'arg': None,
                'prob': 0.4,
                'times': 1,
                'pin': 'stack'
            },
            {
                'tool_name': 'workflow_do',
                'tools_name': 'default_tools',
                'exec_mode': 'persistent',
                'aux_prompt': "",
                'arg': "workflow:1000",
                'prob': 1.0/20,
                'times': 1,
                'pin': 'stack'
            },
            {
                'tool_name': 'memory_read',
                'tools_name': 'default_tools',
                'exec_mode': 'persistent',
                'aux_prompt': "",
                'arg': None,
                'prob': 0.5,
                'times': 3,
                'pin': 'stack'
            },
            {
                'tool_name': 'memory_read',
                'tools_name': 'read_tools',
                'exec_mode': 'persistent',
                'aux_prompt': "",
                'arg': 'memory:9998',
                'prob': 0.1,
                'times': 1,
                'pin': None
            },
            {
                'tool_name': 'memory_read',
                'tools_name': 'read_tools',
                'exec_mode': 'persistent',
                'aux_prompt': "",
                'arg': 'memory:9997',
                'prob': 0.1,
                'times': 1,
                'pin': None
            },
        ]
        self.workflows["workflow:main"] \
            = {'pin': 'de', 'stack': workflow_main, 'title': "メイン"}

        workflow_sub = [
            {
                'tool_name': 'subwork_done',
                'tools_name': 'default_tools',
                'exec_mode': 'persistent',
                'aux_prompt': "memory:9994 を読みその指示を実行し、そのタスクが終ったら、subwork_done してください。",
                'arg': None,
                'prob': 1.0,
                'times': 1,
                'pin': 'write'
            },
            {
                'tool_name': 'memory_read',
                'tools_name': 'read_tools',
                'exec_mode': 'persistent',
                'aux_prompt': "",
                'arg': 'memory:9994',
                'prob': 1.0,
                'times': 1,
                'pin': 'write'
            }
        ]
        self.workflows["workflow:1000"] \
            = {'pin': 'wd', 'stack': workflow_sub, 'title': 'keyword 更新'}

    def update_keywords (self, text):
        extracted_keywords = []

        pattern1 = r'keyword:\s*(.*?)(?:\n|$)'
        matches1 = re.findall(pattern1, text, re.IGNORECASE)
        extracted_keywords.extend([kw.strip() for kw in matches1])

        pattern2 = r'\[keyword:\s*(.*?)\]'
        matches2 = re.findall(pattern2, text, re.IGNORECASE)
        extracted_keywords.extend([kw.strip() for kw in matches2])

        for keyword in extracted_keywords:
            if keyword.startswith("〜"):
                continue
            if keyword and keyword not in self.keywords:
                self.keywords.append(keyword)

    def update_vector (self, x):
        text = x['title'] + "\n\n" + x['text']
        x['vector'] = self.emb_llm.embed_query(text)

    def _create_backend_agent (self):
        @tool
        def set_result (status: str, res: Union[Dict, List, str, int, float, bool, None])  -> None:
            """
            結果を設定します。

            Args:
            status (str): 処理のステータス ('success' または 'error')。
            res: 結果データ(リスト、辞書、数値、文字列など何でも可)
            """
            print(f"ツール2(set_result): status: {repr(status)}, result: {short_repr(res)}")
            self.backend_status = status
            self.backend_result = res

        @tool
        def read_all_memories ()  -> Dict[str, Any]:
            """
            全ての記憶を読み込みます。
            """
            print("ツール2(read_all_memories):...")
            return {'status': 'success',
                    'result': [{k: v for k, v in x.items() if k != 'vector'}
                               for x in self.memories.values()]}

        @tool
        def read_all_keywords ()  -> Dict[str, Any]:
            """
            全てのキーワードを読み込みます。
            """
            print("ツール2(read_all_keywords):...")
            return {'status': 'success',
                    'result': [x for x in self.keywords]}

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

        tools = [set_result, read_all_memories, read_all_keywords,
                 express_thought]

        app = create_agent(
            model=self.llm2, tools=tools, system_prompt=self.system_prompt2,
            checkpointer=InMemorySaver(), name="sub-agent",
        )

        return app

    def call_backend_agent (self, user_input):
        config = {"configurable": {"thread_id": "2"}}
        app = self._create_backend_agent()
        self.messages2 = []
        self.backend_result = None
        self.backend_status = None

        while self.backend_result is None or self.backend_status is None:
            try:
                sleep(3)
                print(f"USER_INPUT2: {user_input}")
                self.messages2.append(HumanMessage(user_input))
                for chunk0 in app.stream(
                        {"messages": self.messages2.copy()},
                        config=config,
                        stream_mode="updates",
                        name="sub-agent",
                ):
                    self.messages2 = app.get_state(config).values["messages"].copy()
                    done = 0
                    if "tools" not in chunk0:
                        continue
                    for x in chunk0['tools']['messages']:
                        self.messages2ids.append(x.id)
                        if isinstance(x, ToolMessage):
                            print(f"ツール結果2({x.name}): {short_repr(x.content)}", flush=True)
                        if isinstance(x, ToolMessage) and x.name == "set_result":
                            done = 1
                            break
                    if done:
                        break
                print(f"エージェントの応答: {get_content_text(self.messages2[-1].content)}")
            except GraphRecursionError as e:
                print(f"Recursion Limit に到達しました。")
            except Exception as e:
                print(f"エラーが発生しました(sub): {e}")
                import traceback
                traceback.print_exc()
                raise e

            sleep(3)

        return {'status': self.backend_status, 'result': self.backend_result}

メインの RagAgent。

In [9]:
class RagAgent (MemoryBanditWorkflow):
    def __init__ (self, llm=llm, llm2=llm, emb_llm=emb_llm,
                  save_file=None):
        self.thesis = {
            'title': "",
            'chapters': [{'title': '概要', 'text': ""}]
        }
        super().__init__(llm=llm, llm2=llm, emb_llm=emb_llm,
                         save_file=save_file)

        self.system_prompt = """\
あなたは賢い RAG エージェントです。論文(/thesis)を書き上げていきます。
利用可能なツールを使用して、論文を書き上げてください。
現在の計画と方針と周囲の状況を考慮し、必要に応じて計画と方針を更新してください。
あなたとは別の者が次の行動をしやすいよう計画と方針を残してください。

メモリの id は memory:~ という形式です。memory:5555 を指定するときは 5555 だけではダメです。文の中でメモリの参照を示すときは [memory:〜] のように書きます。
「メモリの文書を検索する手順」は [memory:9998] にあります。「メモリに文書を残す手順」は [memory:9997] にあります。

ツールの多くはサブツールとして格納されています。サブツール名は "/dir1/subtool1" のように "/" から始まる名前をしています。どういうサブツールがあるか知るためにまずは subtool_show("/") を実行してください。

論文(/thesis)は章(chapter)に分けて作っていきます。それぞれが Markdown 形式です。論文のタイトルは # 一つ、章のタイトルは ## で修飾されることになりますので、それに合わせた階層で書いてください。論文の中に [memory:〜] という内部メモリの参照を入れてはいけません。ですからメモリに蓄えるときに参照 URL などは別途記録しておいてください。論文はディレクトリ /thesis 下のサブツールを使って書き上げます。
"""

        self.system_prompt2 = """\
あなたは賢い RAG エージェントをサポートする裏方のエージェントです。
本来この裏方は様々な技法を用いて実装される予定なのですが、現在は試験実装中のため、その動きをあなたは偽装しなければなりません。

よく考えツールを積極的に使って Human からの指示に従ってください。
"""
        self.subwork_done = True
        self.user_demand = ''
        self.current_work = ''
        self.current_state = 0

    def init_tools (self):
        super().init_tools()

        @tool
        def show_user_demand () -> str:
            """
            大目的を返します。
            """
            print(f"ツール(show_user_demand): {self.user_demand}")
            return self.user_demand

        @tool
        def show_current_work () -> str:
            """
            中目的すなわち現在の仕事を返します。
            """
            print(f"ツール(show_current_work): {self.current_work}")
            return self.current_work

        @tool
        def thesis_write_title (new_title: str) -> str:
            """
            論文のタイトルを更新します。
            """
            self.thesis['title'] = new_title
            mes = "論文のタイトルが更新されました。"
            print(f"ツール(thesis_write_title): {mes}: {new_title}")
            return mes

        @tool
        def thesis_show_title () -> str:
            """
            論文のタイトルを返します。
            """
            print(f"ツール(thesis_show_title): {self.thesis['title']}")
            return self.thesis['title']

        @tool
        def thesis_new_chapter (title: str, text: str) -> str:
            """
            新しい章を title と text で構成します。
            """

            x = {'title': title, 'text': text}
            num = len(self.thesis['chapters'])
            self.thesis['chapters'].append(x)
            print(f"ツール(thesis_new_chapter): {short_repr(x)}")
            return f"成功: 第{num}章を作りました。"

        @tool
        def thesis_write_chapter (
                chapter_num: int,
                new_title: str,
                new_text: str
        ) -> str:
            """
            章を new_title と new_text で置き換えます。
            """

            if not (chapter_num >= 0
                    and chapter_num < len(self.thesis['chapters'])):
                return f"失敗: 第{chapter_num}章は現在ありません。"
            x = {'title': new_title, 'text': new_text}
            self.thesis['chapters'][chapter_num] = x
            print(f"ツール(thesis_write_chapter): {short_repr(x)}")
            return f"成功: 第{chapter_num}章を書き換えました。"

        @tool
        def thesis_delete_last_chapter () -> str:
            """
            最後の章を削除します。
            """
            num = len(self.thesis['chapters']) - 1
            if num <= 0:
                return f"失敗: これ以上削除できません。"
            self.thesis['chapters'].pop()
            return f"成功: 第{num}章を削除しました。"

        @tool
        def thesis_read_chapter(chapter_num: int) -> Union[Dict[str, str], str]:
            """
            指定された章を読み込みます。
            """

            if not (chapter_num >= 0
                    and chapter_num < len(self.thesis['chapters'])):
                return f"失敗: 第{chapter_num}章は現在ありません。"
            return self.thesis['chapters'][chapter_num]

        @tool
        def thesis_list_chapters() -> List[str]:
            """
            章のタイトルのリストを返します。
            """
            return [x['title'] for x in self.thesis['chapters']]

        @tool
        def subwork_done()  -> str:
            """
            指示されたサブワークが(成功裡に)完了したことを宣言します。
            """
            self.subwork_done = True
            return f"成功。サブワークの完了が宣言されました。"

        @tool
        def subwork_not_done()  -> str:
            """
            指示されたサブワークが完了してないことを宣言します。
            """
            self.subwork_done = False
            return f"成功。サブワークの未完了が宣言されました。"

        @tool
        def show_execution_map () -> str:
            """
            workflow より上位の実行制御の全体像とそこにおける現在の State を返します。
            """
            mes = f"""\
大目的は「{self.user_demand}」です。

次に実行制御の全体像を示します。State 0 からはじまります。指示があったとき subwork_done または subwork_not_done することで次の State に遷移します。

State 0: 大目的に沿ってメモリ等に計画を立てます。完了時 State 1 へ。

State 1: 大目的に沿ってウェブを調査し取材してまだ書いていない一章分ずつメモリ等に記録していきます。完了時 State 2 へ。

State 2: 大目的に沿ってメモリ等にある取材データが全章分、十分か確かめます。完了時 State 3 へ。未完了時 State 1 へ。

State 3: 大目的に沿ってメモリ等にある取材データを元に論文(/thesis)をまだ書いてない一章分、十分でない一章分を書きます。完了時 State 4 へ。

State 4: 大目的に沿って取材データを元にした論文(/thesis)が全章完成しているかをチェックします。最後にタイトルを設定し、第0章の概要を書き直します。完了時 State 6 へ。未完了時 State 5 へ。

State 5: 大目的に沿ってまだ追加取材が必要なら再調査してメモリ等に記録します。完了時 State 3 へ

State 6: 終了。完成!

現在の State は {self.current_state} です。
"""
            print(f"ツール(show_execution_map): {mes}")
            return mes


        @tool
        def get_web_page_content(url: str) -> str:
            """
            指定されたURLからWebページのコンテンツを取得して文字列として返します。
            調査でURLを見つけた後に使ってください。
            """
            try:
                loader = WebBaseLoader(url)
                docs = loader.load()
                content = " ".join([doc.page_content for doc in docs])
                print(f"ツール(get_web_page_content): 成功: {url}")
                # 長すぎる場合は適当に丸める
                if len(content) > 5000:
                    return content[:5000] + "... (コンテンツが長すぎるため省略)"
                return content
            except Exception as e:
                print(f"ツール(get_web_page_content): 失敗: {url}")
                return f"エラー: ページの読み込みに失敗しました。URL: {url}, エラー: {e}"

        search_tool = DuckDuckGoSearchResults(output_format="list", verbose=True)
        web_page_tool = get_web_page_content

        main_tools = [subwork_done, subwork_not_done, search_tool, web_page_tool]
        for t in main_tools:
            self.register_tool(t, tags=["default_tools", "read_tools",
                                        "all_tools"])
        thesis_subtools = [
            ('/thesis/write_title', thesis_write_title),
            ('/thesis/show_title', thesis_show_title),
            ('/thesis/new_chapter', thesis_new_chapter),
            ('/thesis/write_chapter', thesis_write_chapter),
            ('/thesis/delete_last_chapter', thesis_delete_last_chapter),
            ('/thesis/read_chapter', thesis_read_chapter),
            ('/thesis/list_chapters', thesis_list_chapters),
        ]
        self.register_subtools(
            directory="/thesis",
            subtools=thesis_subtools,
            description="論文を書くためのサブツール",
            content=dedent("""\
            これらは論文を書くためのサブツール集です。

            論文(/thesis)は章(chapter)に分けて作っていきます。それぞれが Markdown 形式です。論文のタイトルは # 一つ、章のタイトルは ## で修飾されることになりますので、それに合わせた階層で書いてください。論文の中に [memory:〜] という内部メモリの参照を入れてはいけません。ですからメモリに蓄えるときに参照 URL などは別途記録しておいてください。

            章は第0章からはじまり、第0章は概要と決まっています。
            """),
            tags=["default_tools", "read_tools", "all_tools"]
        )
        sys_subtools = [
            ('/sys/show_user_demand', show_user_demand),
            ('/sys/show_current_work', show_current_work),
            ('/sys/show_execution_map', show_execution_map),
        ]
        self.register_subtools(
            directory="/sys",
            subtools=sys_subtools,
            tags=["default_tools", "read_tools", "all_tools"]
        )

    def main_loop (self, user_demand):
        self.user_demand = user_demand
        self.current_state = 0
        self.resume()

    def resume (self):
        if self.current_state == 0:
            self.execute_planning()
            self.current_state = 1
            self.save()
            print("\n\n----------\n\n")
        while self.current_state in [1, 2]:
            if self.current_state == 1:
                self.execute_investigation()
                self.current_state = 2
                self.save()
                print("\n\n----------\n\n")
            if self.current_state == 2:
                r = self.execute_check_of_investigation()
                if r:
                    self.current_state = 3
                else:
                    self.current_state = 1
                self.save()
                print("\n\n----------\n\n")
        while self.current_state in [3, 4, 5]:
            if self.current_state == 3:
                r = self.execute_writing()
                self.current_state = 4
                self.save()
                print("\n\n----------\n\n")
            if self.current_state == 4:
                r = self.execute_check_of_writing()
                if r:
                    self.current_state = 6
                else:
                    self.current_state = 5
                self.save()
                print("\n\n----------\n\n")
            if self.current_state == 5:
                self.execute_reinvestigation()
                self.current_state = 3
                self.save()
                print("\n\n----------\n\n")
        print("Done!")

    def write_loop (self):
        if self.current_state not in [3, 4, 5]:
            self.current_state = 3
        self.resume()

    def _execute (self, current_work, tool_name, aux_prompt):
        self.current_work = current_work
        user_input = f"""\
大目的: 「{self.user_demand}」
コアコンテクスト: 「{self.core_context}」
計画と方針: 「{self.plan}」
スクラッチパッド: 「{self.scratchpad}」
すでに必要なものが書かれていないか確かめながら進みましょう。
中目的または現在の仕事: 「{self.current_work}」
"""
        print(f"USER_INPUT: {user_input}")
        self.messages.append(HumanMessage(user_input))

        self.workflows['workflow:main']['stack'][0] = {
            'tool_name': tool_name,
            'tools_name': 'all_tools',
            'exec_mode': 'persistent',
            'aux_prompt': aux_prompt,
            'arg': None,
            'prob': 1.0,
            'times': 1,
            'pin': 'write'
        }
        self.run("workflow:main")

        return self.subwork_done

    def execute_planning (self):
        return self._execute(
            current_work = "大目的に沿ってメモリ等に計画を立ててください。",
            tool_name = 'subwork_done',
            aux_prompt = "計画を立て終ったら subwork_done してください。",
        )

    def execute_investigation (self):
        return self._execute(
            current_work = "大目的に沿ってウェブを調査し取材してまだ書いていない一章分ずつメモリ等に記録していってください。",
            tool_name = 'subwork_done',
            aux_prompt = "一章分取材し終ったら subwork_done してください。",
        )

    def execute_check_of_investigation (self):
        return self._execute(
            current_work = "大目的に沿ってメモリ等にある取材データが全章分、十分か確かめてください。",
            tool_name = 'subwork_done OR subwork_not_done',
            aux_prompt = "全章の取材が十分なら subwork_done してください。十分でないと判断したら subwork_not_done してください。",
        )

    def execute_writing (self):
        return self._execute(
            current_work = "大目的に沿ってメモリ等にある取材データを元に論文(/thesis)をまだ書いてない一章分、十分でない一章分を書いてください。ちなみに第0章は概要と決まっています。",
            tool_name = 'subwork_done',
            aux_prompt = "良い論文のうち一章分書けたら subwork_done してください。",
        )

    def execute_check_of_writing (self):
        return self._execute(
            current_work = "大目的に沿って取材データを元にした論文(/thesis)が全章完成しているかをチェックしてください。最後にタイトルを設定し、第0章の概要を書き直してください。",
            tool_name = 'subwork_done OR subwork_not_done',
            aux_prompt = "良い論文が全章書け、タイトルを設定し、概要を書き直せたら subwork_done してください。まだ全章書けていなければ subwork_not_done してください。",
        )

    def execute_reinvestigation (self):
        return self._execute(
            current_work = "大目的に沿ってまだ追加取材が必要なら再調査してメモリ等に記録してください。",
            tool_name = 'subwork_done',
            aux_prompt = "取材し終ったら subwork_done してください。",
        )

    def init_memories (self):
        super().init_memories()
        memories = []
        for x in memories:
            self.update_keywords(x['text'])
            self.memories[x['id']] = x
            self.update_vector(x)

    def init_workflows (self):
        super().init_workflows()
        workflow_main = self.workflows['workflow:main']['stack']
        workflow_main = [
            {
                'tool_name': 'subwork_done',
                'tools_name': 'all_tools',
                'exec_mode': 'persistent',
                'aux_prompt': "仮登録。",
                'arg': None,
                'prob': 1.0,
                'times': 1,
                'pin': 'write'
            }
        ] + workflow_main + [
            {
                'tool_name': 'subtool_show',
                'tools_name': 'read_tools',
                'exec_mode': 'persistent',
                'aux_prompt': "",
                'arg': '/thesis',
                'prob': 0.1,
                'times': 1,
                'pin': None
            },
            {
                'tool_name': '/sys/show_execution_map',
                'tools_name': 'default_tools',
                'exec_mode': 'once',
                'aux_prompt': "",
                'arg': None,
                'prob': 1.0,
                'times': 1,
                'pin': None
            },
        ]
        self.workflows['workflow:main']['stack'] = workflow_main


    def display_thesis (self):
        s = f"# {self.thesis['title']}\n\n"
        for i, x in enumerate(self.thesis['chapters']):
            if re.search(r"^\#\#", x['text']):
                pass
            elif i == 0 or re.search(r"[01-9]+章", x['title']):
                s += f"## {x['title']}\n\n"
            else:
                s += f"## 第{i}章 {x['title']}\n\n"
            s += f"{x['text']}\n\n"
        return Markdown(s)

クラスをインスタンシエート。

In [10]:
agent = RagAgent(llm=llm, llm2=llm, emb_llm=emb_llm, save_file=RAG_AGENT_SAVE)
agent.save()

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

In [ ]:
agent = RagAgent.load(RAG_AGENT_SAVE, llm=llm, llm2=llm, emb_llm=emb_llm)

では、動かしてみます。

In [ ]:
agent.main_loop("日本の失われたウン十年、何をすれば良かったかを論文にまとめてください。")
USER_INPUT: 大目的: 「日本の失われたウン十年、何をすれば良かったかを論文にまとめてください。」
コアコンテクスト: 「」
計画と方針: 「まだ計画と方針はセットされていません。」
スクラッチパッド: 「」
すでに必要なものが書かれていないか確かめながら進みましょう。
中目的または現在の仕事: 「大目的に沿ってメモリ等に計画を立ててください。」



----------


USER_INPUT: 補助にツールをいろいろ使いながら最終的に /sys/show_execution_map を適当なパラメータで使ってください。
ツール結果(subtool_show): '---\nname: /\ndescription: サブツールのルート。サブツールの検索の仕方を説明する。\nallowed-tools: サブツー...'
ツール結果(subtool_show): '---\nname: /sys\ndescription: システムに基本的なサブツール集\nallowed-tools: このサブツールを使うのに特...'
ツール結果(subtool_show): '---\nname: /thesis\ndescription: 論文を書くためのサブツール\nallowed-tools: このサブツールを使うのに...'
ツール結果(memory_read): "エラー: 記憶ID 'memory:9999' が見つかりませんでした。"
ツール結果(memory_read): "エラー: 記憶ID 'memory:9999' が見つかりませんでした。"
ツール結果(memory_list_recent): '{"status": "error", "result": "エラー: 最近のメモリはありません。"}'
ツール(update_plan): 計画と方針が更新されました。: 1. 論文の構成案を作成し、メモリに記録する。
2. 日本の「失われた30年」の主要な要因(経済、人口、政治、技術)を調査する。
3. 過去に検討されたが実施されなかった政策や、成功した他国の事例を調査する。
4. 調査結果に基づき、各章を執筆する(第0章:概要、第1章:背景、第2章:要因分析、第3章:代替案の提示、第4章:結論)。
5. 論文全体をレビューし、完成させる。
ツール結果(/sys/update_plan): '計画と方針が更新されました。'
ツール(update_plan): コアコンテクストが更新されました。: 論文タイトル:日本の失われた「ウン十年」―再考と未来への提言
章構成:
第0章:概要(問題提起と論文の目的)
第1章:失われた30年の実態と背景
第2章:経済政策の検証(バブル崩壊後の対応、デフレ対策)
第3章:構造的課題(少子高齢化、DXの遅れ、労働市場)
第4章:何が必要だったのか(代替シナリオの提示)
第5章:結論と今後の展望
調査用キーワード:バブル崩壊、デフレ、少子高齢化、IT革命の遅れ、非正規雇用、量的緩和政策、産業構造転換。
ツール結果(/sys/update_core): 'コアコンテクストが更新されました。'
Recursion Limit に到達しました。
USER_INPUT: 前回の指示がまだ完了してません。前回の指示: 補助にツールをいろいろ使いながら最終的に /sys/show_execution_map を適当なパラメータで使ってください。
ツール(show_execution_map): 大目的は「日本の失われたウン十年、何をすれば良かったかを論文にまとめてください。」です。

次に実行制御の全体像を示します。State 0 からはじまります。指示があったとき subwork_done または subwork_not_done することで次の State に遷移します。

State 0: 大目的に沿ってメモリ等に計画を立てます。完了時 State 1 へ。

State 1: 大目的に沿ってウェブを調査し取材してまだ書いていない一章分ずつメモリ等に記録していきます。完了時 State 2 へ。

State 2: 大目的に沿ってメモリ等にある取材データが全章分、十分か確かめます。完了時 State 3 へ。未完了時 State 1 へ。

State 3: 大目的に沿ってメモリ等にある取材データを元に論文(/thesis)をまだ書いてない一章分、十分でない一章分を書きます。完了時 State 4 へ。

State 4: 大目的に沿って取材データを元にした論文(/thesis)が全章完成しているかをチェックします。最後にタイトルを設定し、第0章の概要を書き直します。完了時 State 6 へ。未完了時 State 5 へ。

State 5: 大目的に沿ってまだ追加取材が必要なら再調査してメモリ等に記録します。完了時 State 3 へ

State 6: 終了。完成!

現在の State は 0 です。

ツール結果(/sys/show_execution_map): '大目的は「日本の失われたウン十年、何をすれば良かったかを論文にまとめてください。」です。\n\n次に実行制御の全体像を示します。State 0 からは...'
エージェントの応答: 大目的は「日本の失われたウン十年、何をすれば良かったかを論文にまとめてください。」です。

次に実行制御の全体像を示します。State 0 からはじまります。指示があったとき subwork_done または subwork_not_done することで次の State に遷移します。

State 0: 大目的に沿ってメモリ等に計画を立てます。完了時 State 1 へ。

State 1: 大目的に沿ってウェブを調査し取材してまだ書いていない一章分ずつメモリ等に記録していきます。完了時 State 2 へ。

State 2: 大目的に沿ってメモリ等にある取材データが全章分、十分か確かめます。完了時 State 3 へ。未完了時 State 1 へ。

State 3: 大目的に沿ってメモリ等にある取材データを元に論文(/thesis)をまだ書いてない一章分、十分でない一章分を書きます。完了時 State 4 へ。

State 4: 大目的に沿って取材データを元にした論文(/thesis)が全章完成しているかをチェックします。最後にタイトルを設定し、第0章の概要を書き直します。完了時 State 6 へ。未完了時 State 5 へ。

State 5: 大目的に沿ってまだ追加取材が必要なら再調査してメモリ等に記録します。完了時 State 3 へ

State 6: 終了。完成!

現在の State は 0 です。

USER_INPUT: 補助にツールをいろいろ使いながら最終的に memory_new OR memory_update_string OR memory_append_string のどれかを適当なパラメータで使ってください。
ツール(memory_new): {'id': 'memory:1000', 'title': '論文構成案:日本の失われた30年', 'accesses': 0, 'text': '日...}
ツール結果(memory_new): 'memory:1000'
エージェントの応答: 
USER_INPUT: 補助にツールをいろいろ使いながら最終的に subwork_done を適当なパラメータで使ってください。(補助プロンプト): 計画を立て終ったら subwork_done してください。
ツール結果(subwork_done): '成功。サブワークの完了が宣言されました。'
エージェントの応答: 成功。サブワークの完了が宣言されました。


----------


USER_INPUT: 大目的: 「日本の失われたウン十年、何をすれば良かったかを論文にまとめてください。」
コアコンテクスト: 「論文タイトル:日本の失われた「ウン十年」―再考と未来への提言
章構成:
第0章:概要(問題提起と論文の目的)
第1章:失われた30年の実態と背景
第2章:経済政策の検証(バブル崩壊後の対応、デフレ対策)
第3章:構造的課題(少子高齢化、DXの遅れ、労働市場)
第4章:何が必要だったのか(代替シナリオの提示)
第5章:結論と今後の展望
調査用キーワード:バブル崩壊、デフレ、少子高齢化、IT革命の遅れ、非正規雇用、量的緩和政策、産業構造転換。」
計画と方針: 「1. 論文の構成案を作成し、メモリに記録する。
2. 日本の「失われた30年」の主要な要因(経済、人口、政治、技術)を調査する。
3. 過去に検討されたが実施されなかった政策や、成功した他国の事例を調査する。
4. 調査結果に基づき、各章を執筆する(第0章:概要、第1章:背景、第2章:要因分析、第3章:代替案の提示、第4章:結論)。
5. 論文全体をレビューし、完成させる。」
スクラッチパッド: 「」
すでに必要なものが書かれていないか確かめながら進みましょう。
中目的または現在の仕事: 「大目的に沿ってウェブを調査し取材してまだ書いていない一章分ずつメモリ等に記録していってください。」



----------


USER_INPUT: 補助にツールをいろいろ使いながら最終的に memory_read を適当なパラメータで使ってください。
ツール結果(memory_list_recent): '{"status": "success", "result": [{"id": "memory:1000", "title": "論文構成案:日本の失...'
ツール結果(memory_read): '{"id": "memory:1000", "title": "論文構成案:日本の失われた30年", "accesses": 1.0, "text":...'
エージェントの応答: 
USER_INPUT: 補助にツールをいろいろ使いながら最終的に memory_read を適当なパラメータで使ってください。
ツール結果(memory_read): '{"id": "memory:1000", "title": "論文構成案:日本の失われた30年", "accesses": 2.0, "text":...'
エージェントの応答: 
USER_INPUT: 補助にツールをいろいろ使いながら最終的に subwork_done を適当なパラメータで使ってください。(補助プロンプト): 一章分取材し終ったら subwork_done してください。
content='[{"snippet": "3 weeks ago - さらにアジア通貨危機の影響も受ける中、翌年の1998年度には名目GDPは前年度比約マイナス2%の502兆円まで約10兆円縮小し、GDPデフレーターはマイナス0.5%に落ち込み、完全失業率は4.1%に達し、これ以降日本は本格的なデフレーションへ突入し、「失われた10年」を経験することになる。1999年度には、1997年度と比べ所得税と法人税の合計額が6兆5000億円もの減収となり、失業者数は300万人を超えた。さらに1997年には日本銀行法が改正され、内閣が日本銀行総裁の解任権を失うことになった。", "title": "バブル崩壊 - Wikipedia", "link": "https://ja.wikipedia.org/wiki/バブル崩壊"}, {"snippet": "1 month ago - 1990年代初めに、資産デフレをきっかけとした債務デフレによるGDPギャップの拡大が起きたため、景気の悪化とともに、その後の長期経済停滞をもたらしたとする説。1990年代から21世紀初頭に日本において見られた資産デフレは、主に日本銀行の金融引き締めがその原因の一つであったと考えられており、1990年代以降の日本の経済停滞(いわゆる「失われた10年・失われた20年」)の相当部分は、日銀の金融引き締めに端を発したとする向きがある。バブル崩壊による資産価格の暴落による資産デフレを主因とする説や、バブル崩壊による株価・地価の下落、銀行の不良債権の増加、大量の企業倒産などにあるとする説がある。", "title": "日本のデフレーション - Wikipedia", "link": "https://ja.wikipedia.org/wiki/日本のデフレーション"}, {"snippet": "December 9, 2025 - 「失われた30年」という言葉の根拠は、マクロ経済のデータ、特に国際比較における相対的な地位の低下にあります。 · GDPと成長率の停滞:1990年代初頭のバブル崩壊後、日本の名目GDP成長率は他の先進国と比べて著しく低迷しました。この結果、この30年は、本来得られたはずの成長機会と富を「失った」期間であったと見なされます 。", "title": "「日本の失われた30年」論考の深掘り:構造的対立と新たなパラダイム(鈴木崇弘) - エキスパート - Yahoo!ニュース", "link": "https://news.yahoo.co.jp/expert/articles/6cafe0cb9b2a32a351178ce436dc712e7f1163a5"}, {"snippet": "March 4, 2025 - ところが、2022年から、そんな居心地の良い社会の崩壊が始まった。インフレである。日本はなぜ慢性デフレに陥ったのか、なぜそれが終焉を迎えつつあるのか──。『物価を考える デフレの謎、インフレの謎』(日本経済新聞出版)を上梓した渡辺努氏(東京大学大学院経済学研究科教授)に話を聞いた。(聞き手:関瑶子、ライター&ビデオクリエイター) · ──なぜ日本では、約30年にわたり物価も賃金も据え置きという状態が続いたのでしょうか。 · 渡辺努氏(以下、渡辺):いわゆる「失われた30年」ですね。おっしゃる通り、1980年代後半から2010年代の日本は、慢性的なデフレに悩まされていました。", "title": "なぜ日本は30年にわたるデフレからインフレ基調に転換したのか?構造変化を促した二つの理由 【著者が語る】ようやく始まった「正しいインフレ」 東京大学大学院教授・渡辺努が語る物価と賃金のカラクリ(前編)(1/4) | JBpress (ジェイビープレス)", "link": "https://jbpress.ismedia.jp/articles/-/86909"}]' name='duckduckgo_results_json' tool_call_id='a9fd219b-8a51-4291-93a0-7b23a6e98c18' artifact=[{'snippet': '3 weeks ago - さらにアジア通貨危機の影響も受ける中、翌年の1998年度には名目GDPは前年度比約マイナス2%の502兆円まで約10兆円縮小し、GDPデフレーターはマイナス0.5%に落ち込み、完全失業率は4.1%に達し、これ以降日本は本格的なデフレーションへ突入し、「失われた10年」を経験することになる。1999年度には、1997年度と比べ所得税と法人税の合計額が6兆5000億円もの減収となり、失業者数は300万人を超えた。さらに1997年には日本銀行法が改正され、内閣が日本銀行総裁の解任権を失うことになった。', 'title': 'バブル崩壊 - Wikipedia', 'link': 'https://ja.wikipedia.org/wiki/バブル崩壊'}, {'snippet': '1 month ago - 1990年代初めに、資産デフレをきっかけとした債務デフレによるGDPギャップの拡大が起きたため、景気の悪化とともに、その後の長期経済停滞をもたらしたとする説。1990年代から21世紀初頭に日本において見られた資産デフレは、主に日本銀行の金融引き締めがその原因の一つであったと考えられており、1990年代以降の日本の経済停滞(いわゆる「失われた10年・失われた20年」)の相当部分は、日銀の金融引き締めに端を発したとする向きがある。バブル崩壊による資産価格の暴落による資産デフレを主因とする説や、バブル崩壊による株価・地価の下落、銀行の不良債権の増加、大量の企業倒産などにあるとする説がある。', 'title': '日本のデフレーション - Wikipedia', 'link': 'https://ja.wikipedia.org/wiki/日本のデフレーション'}, {'snippet': 'December 9, 2025 - 「失われた30年」という言葉の根拠は、マクロ経済のデータ、特に国際比較における相対的な地位の低下にあります。 · GDPと成長率の停滞:1990年代初頭のバブル崩壊後、日本の名目GDP成長率は他の先進国と比べて著しく低迷しました。この結果、この30年は、本来得られたはずの成長機会と富を「失った」期間であったと見なされます 。', 'title': '「日本の失われた30年」論考の深掘り:構造的対立と新たなパラダイム(鈴木崇弘) - エキスパート - Yahoo!ニュース', 'link': 'https://news.yahoo.co.jp/expert/articles/6cafe0cb9b2a32a351178ce436dc712e7f1163a5'}, {'snippet': 'March 4, 2025 - ところが、2022年から、そんな居心地の良い社会の崩壊が始まった。インフレである。日本はなぜ慢性デフレに陥ったのか、なぜそれが終焉を迎えつつあるのか──。『物価を考える デフレの謎、インフレの謎』(日本経済新聞出版)を上梓した渡辺努氏(東京大学大学院経済学研究科教授)に話を聞いた。(聞き手:関瑶子、ライター&ビデオクリエイター) · ──なぜ日本では、約30年にわたり物価も賃金も据え置きという状態が続いたのでしょうか。 · 渡辺努氏(以下、渡辺):いわゆる「失われた30年」ですね。おっしゃる通り、1980年代後半から2010年代の日本は、慢性的なデフレに悩まされていました。', 'title': 'なぜ日本は30年にわたるデフレからインフレ基調に転換したのか?構造変化を促した二つの理由 【著者が語る】ようやく始まった「正しいインフレ」 東京大学大学院教授・渡辺努が語る物価と賃金のカラクリ(前編)(1/4) | JBpress (ジェイビープレス)', 'link': 'https://jbpress.ismedia.jp/articles/-/86909'}]content='[{"snippet": "Oct 6, 2025 · 本当はIT/デジタル革命に乗り 遅れ たせいで 失われ た 年月が 30 年 も続いてしまったのだ。 だからこそ、記事の冒頭に書いた通り、1990年代初頭以降の年月を「IT/デジタル革命における日本の 失われ た 30 年 」として考えないといけないのだ。", "title": "IT/デジタル革命における日本の失われた30年、AI登場で失われた60年...", "link": "https://xtech.nikkei.com/atcl/nxt/column/18/00148/100100402/"}, {"snippet": "Jul 29, 2025 · 「 失われ た 30 年 」とは何だったのでしょうか。 単なる経済の低迷ではなく、日本社会のあり方そのものが静かに形を変えていった時代。 それは人々の価値観に影を落とし、制度を硬直化させ、将来への期待を奪う時間でもありました。", "title": "失われた30年とは何だったのか──停滞と萎縮が日本社会に残したもの|3...", "link": "https://note.com/329n/n/nf4fff2ad6f75"}, {"snippet": "Dec 9, 2025 · この好機を最大限に活かすためには、日本は、テクノロジーや社会の大きな 変化 を前向きかつ積極的に活用 し、 個々の政策や対応を有機的に ...", "title": "「日本の失われた30年」論考の深掘り:構造的対立と新たなパラダイム...", "link": "https://news.yahoo.co.jp/expert/articles/6cafe0cb9b2a32a351178ce436dc712e7f1163a5"}, {"snippet": "失われ た 30 年 の始まりが具体的に何年からとは定義されていませんが、バブル経済が崩壊した1991年を契機とする見方が多いようです。 失われ た 30 年 で起きたこと、今後の見通しなどをわかりやすく解説。 失われ た 30 年の 解決策についても触れています。", "title": "失われた30年とは?原因や影響、解決策も - Spaceship Earth(スペー...", "link": "https://spaceshipearth.jp/the-lost-30-years/"}]' name='duckduckgo_results_json' tool_call_id='b47b4fac-0be5-4d58-be43-925b9c003c89' artifact=[{'snippet': 'Oct 6, 2025 · 本当はIT/デジタル革命に乗り 遅れ たせいで 失われ た 年月が 30 年 も続いてしまったのだ。 だからこそ、記事の冒頭に書いた通り、1990年代初頭以降の年月を「IT/デジタル革命における日本の 失われ た 30 年 」として考えないといけないのだ。', 'title': 'IT/デジタル革命における日本の失われた30年、AI登場で失われた60年...', 'link': 'https://xtech.nikkei.com/atcl/nxt/column/18/00148/100100402/'}, {'snippet': 'Jul 29, 2025 · 「 失われ た 30 年 」とは何だったのでしょうか。 単なる経済の低迷ではなく、日本社会のあり方そのものが静かに形を変えていった時代。 それは人々の価値観に影を落とし、制度を硬直化させ、将来への期待を奪う時間でもありました。', 'title': '失われた30年とは何だったのか──停滞と萎縮が日本社会に残したもの|3...', 'link': 'https://note.com/329n/n/nf4fff2ad6f75'}, {'snippet': 'Dec 9, 2025 · この好機を最大限に活かすためには、日本は、テクノロジーや社会の大きな 変化 を前向きかつ積極的に活用 し、 個々の政策や対応を有機的に ...', 'title': '「日本の失われた30年」論考の深掘り:構造的対立と新たなパラダイム...', 'link': 'https://news.yahoo.co.jp/expert/articles/6cafe0cb9b2a32a351178ce436dc712e7f1163a5'}, {'snippet': '失われ た 30 年 の始まりが具体的に何年からとは定義されていませんが、バブル経済が崩壊した1991年を契機とする見方が多いようです。 失われ た 30 年 で起きたこと、今後の見通しなどをわかりやすく解説。 失われ た 30 年の 解決策についても触れています。', 'title': '失われた30年とは?原因や影響、解決策も - Spaceship Earth(スペー...', 'link': 'https://spaceshipearth.jp/the-lost-30-years/'}]ツール結果(duckduckgo_results_json): '[{"snippet": "3 weeks ago - さらにアジア通貨危機の影響も受ける中、翌年の1998年度には名目GDPは前年度比約マイナス2%...'
ツール結果(duckduckgo_results_json): '[{"snippet": "Oct 6, 2025 · 本当はIT/デジタル革命に乗り 遅れ たせいで 失われ た 年月が 30 年 も続いてしまった...'
ツール(memory_new): {'id': 'memory:1001', 'title': '第1章取材データ:失われた30年の実態と背景', 'accesses': 0, 'tex...}
ツール結果(memory_new): 'memory:1001'
ツール結果(subwork_done): '成功。サブワークの完了が宣言されました。'
エージェントの応答: 


----------


USER_INPUT: 大目的: 「日本の失われたウン十年、何をすれば良かったかを論文にまとめてください。」
コアコンテクスト: 「論文タイトル:日本の失われた「ウン十年」―再考と未来への提言
章構成:
第0章:概要(問題提起と論文の目的)
第1章:失われた30年の実態と背景
第2章:経済政策の検証(バブル崩壊後の対応、デフレ対策)
第3章:構造的課題(少子高齢化、DXの遅れ、労働市場)
第4章:何が必要だったのか(代替シナリオの提示)
第5章:結論と今後の展望
調査用キーワード:バブル崩壊、デフレ、少子高齢化、IT革命の遅れ、非正規雇用、量的緩和政策、産業構造転換。」
計画と方針: 「1. 論文の構成案を作成し、メモリに記録する。
2. 日本の「失われた30年」の主要な要因(経済、人口、政治、技術)を調査する。
3. 過去に検討されたが実施されなかった政策や、成功した他国の事例を調査する。
4. 調査結果に基づき、各章を執筆する(第0章:概要、第1章:背景、第2章:要因分析、第3章:代替案の提示、第4章:結論)。
5. 論文全体をレビューし、完成させる。」
スクラッチパッド: 「」
すでに必要なものが書かれていないか確かめながら進みましょう。
中目的または現在の仕事: 「大目的に沿ってメモリ等にある取材データが全章分、十分か確かめてください。」



----------


USER_INPUT: 補助にツールをいろいろ使いながら最終的に memory_read を適当なパラメータで使ってください。(補助プロンプト): memory:9997 を読んでください。
ツール結果(memory_read): '{"id": "memory:9997", "title": "メモリに文書を残す手順", "accesses": 0.3, "modified_at...'
エージェントの応答: {"id": "memory:9997", "title": "メモリに文書を残す手順", "accesses": 0.3, "modified_at": "2023-01-01T00:00:00", "text": "行動結果や得た知識を積極的にメモリに書き残していこう。\n\nメモリに書くときは次のような要素を使う。\n\n[memory:〜] : メモリID への参照を明示する。\nkeyword:〜 : そのメモリにまつわるキーワードを指定する。\n\nキーワードは将来のメモリへも実質的にリンクできることに注意しよう。\n\n例:\n\n私が [memory:5555] に従って歩いていると確かに妖怪に出くわした。\n\nkeyword: 妖怪\n\nおそろしかった。\n"}
USER_INPUT: 補助にツールをいろいろ使いながら最終的に memory_read を適当なパラメータで使ってください。
ツール結果(memory_list_recent): '{"status": "success", "result": [{"id": "memory:1001", "title": "第1章取材データ:失...'
ツール結果(memory_read): '{"id": "memory:1000", "title": "論文構成案:日本の失われた30年", "accesses": 3.0, "text":...'
エージェントの応答: {"id": "memory:1000", "title": "論文構成案:日本の失われた30年", "accesses": 3.0, "text": "日本の「失われた数十年」に関する論文構成案\n\n# タイトル\n日本の失われた「ウン十年」―再考と未来への提言\n\n# 章構成\n## 第0章:概要\nバブル崩壊以降の日本経済の長期停滞を概観し、本論文で検討する「どのような政策をとるべきであったか」という問いを設定する。\n\n## 第1章:失われた30年の実態と背景\nGDP成長率の低迷、デフレの定着、非正規雇用の増加など、客観的なデータに基づく現状分析。\n\n## 第2章:経済政策の検証\nバブル崩壊直後の公的資金注入の遅れ、橋本政権下の緊縮財政、白川日銀によるデフレ対応など、主要な経済政策の成否を検証する。\n\n## 第3章:構造的課題\n少子高齢化への対応の遅れ、IT・DX革命への乗り遅れ、硬直的な労働市場、教育投資の不足など、経済政策以外の構造的要因を分析する。\n\n## 第4章:何が必要だったのか(代替シナリオの提示)\n「もしこうしていれば」という視点から、早期の銀行救済、積極的な技術投資、労働市場の流動化、少子化対策への資源集中などの代替案を提示する。\n\n## 第5章:結論と今後の展望\n過去の教訓を現在および未来の政策にどう活かすべきかをまとめ、日本再生への道を提言する。", "modified_at": "2026-02-04T06:24:12.516229"}
USER_INPUT: 補助にツールをいろいろ使いながら最終的に memory_read を適当なパラメータで使ってください。
ツール結果(memory_read): '{"id": "memory:1001", "title": "第1章取材データ:失われた30年の実態と背景", "accesses": 1.0, "...'
エージェントの応答: 
USER_INPUT: 補助にツールをいろいろ使いながら最終的に subwork_done OR subwork_not_done のどれかを適当なパラメータで使ってください。(補助プロンプト): 全章の取材が十分なら subwork_done してください。十分でないと判断したら subwork_not_done してください。
content='[{"snippet": "2025年12月16日 · 小泉純一郎主張削減公共事業等手段降低政府財政支出,推動將中央政府權限下放給民間企業與地方自治體的「沒有聖域的結構改革(日語:聖域なき構造改革)」。 ... 日本経済新聞 ...", "title": "自由民主黨(日本) - 維基百科", "link": "https://zh.wikipedia.org/zh-tw/自由民主党_(日本)"}, {"snippet": "2025年8月9日 · 教育、産業支援、起業支援、デジタル化、税制、雇用、福祉、環境、地方創生、人口政策といった多角的視点から世界の先進事例を総合的に分析し、日本への示唆を導きます。", "title": "GPT-5による調査レポート: 日本経済再成長への構造改革戦略 - note", "link": "https://note.com/msfmnkns/n/n453cd3cf8a9f"}, {"snippet": "2025年4月16日 · 2006 年の財政・経済一体改革会議「経済成. 長戦略大綱」において、「構造改革による景気回復が実現し、日本経済の潮目に変化がみら. れる今こそ、新しい発想に基づく経済 ...", "title": "[PDF] ビジョンを通じた政策マネジメント ~海図なき時代への提言~", "link": "https://www.jri.co.jp/file/report/other/pdf/15733.pdf"}, {"snippet": "2025年12月4日 · 1990年代以降、日本の労働生産性や国際競争力は低迷し、日本人のワーク ... 日本は、経済規模の相対的低下、緊縮財政の常態化、少子高齢化などを考えると ...", "title": "2050年の夢【氏名順】―1人ひとりのビジョンが私たちの時代の扉を開く - NIRA総合研究開発機構", "link": "https://www.nira.or.jp/paper/research-report/2025/issues-2050.html"}]' name='duckduckgo_results_json' tool_call_id='6a224bf0-0aee-4052-8809-c6479d50ffee' artifact=[{'snippet': '2025年12月16日 · 小泉純一郎主張削減公共事業等手段降低政府財政支出,推動將中央政府權限下放給民間企業與地方自治體的「沒有聖域的結構改革(日語:聖域なき構造改革)」。 ... 日本経済新聞 ...', 'title': '自由民主黨(日本) - 維基百科', 'link': 'https://zh.wikipedia.org/zh-tw/自由民主党_(日本)'}, {'snippet': '2025年8月9日 · 教育、産業支援、起業支援、デジタル化、税制、雇用、福祉、環境、地方創生、人口政策といった多角的視点から世界の先進事例を総合的に分析し、日本への示唆を導きます。', 'title': 'GPT-5による調査レポート: 日本経済再成長への構造改革戦略 - note', 'link': 'https://note.com/msfmnkns/n/n453cd3cf8a9f'}, {'snippet': '2025年4月16日 · 2006 年の財政・経済一体改革会議「経済成. 長戦略大綱」において、「構造改革による景気回復が実現し、日本経済の潮目に変化がみら. れる今こそ、新しい発想に基づく経済 ...', 'title': '[PDF] ビジョンを通じた政策マネジメント ~海図なき時代への提言~', 'link': 'https://www.jri.co.jp/file/report/other/pdf/15733.pdf'}, {'snippet': '2025年12月4日 · 1990年代以降、日本の労働生産性や国際競争力は低迷し、日本人のワーク ... 日本は、経済規模の相対的低下、緊縮財政の常態化、少子高齢化などを考えると ...', 'title': '2050年の夢【氏名順】―1人ひとりのビジョンが私たちの時代の扉を開く - NIRA総合研究開発機構', 'link': 'https://www.nira.or.jp/paper/research-report/2025/issues-2050.html'}]content='[{"snippet": "2026年1月12日 · バブル崩壊(バブルほうかい)では、日本のバブル経済期(昭和・平成バブル景気)が1990年代初頭に終了したこと、並びにその後に続いた不況(低成長期の「失われた30年」 ...", "title": "バブル崩壊 - Wikipedia", "link": "https://ja.wikipedia.org/wiki/バブル崩壊"}, {"snippet": "2025年3月19日 · 両件調査のため、本日、政府参考人として、お手元に配付いたしておりますとおり、経済産業省大臣官房政策立案総括審議官茂木正君外十七名の出席を求め、説明を聴取し、また ...", "title": "第217回国会 経済産業委員会 第3号(令和7年3月19日(水曜日)) - 衆議院", "link": "https://www.shugiin.go.jp/internet/itdb_kaigiroku.nsf/html/kaigiroku/009821720250319003.htm"}, {"snippet": "2025年3月28日 · 幾山河を越えて 70年,わが日本電気は, 1899年の創業から今日まで, まさに 20世紀の. 歴史と一体となって,その歩みを続けて参りました。今般,その足跡を辿って「日本 ...", "title": "日本電気株式会社 - NEC", "link": "https://jpn.nec.com/profile/corp/history/pdf/history-70.pdf"}, {"snippet": "2025年4月21日 · 自由民主党・無所属の会. 昭和三十七年十一月九日生○大阪教育大学附属天王寺中高卒○昭和六十一年早稲田大学政経学部 ...", "title": "衆議院 世耕弘成 | 国会審議映像検索システム - 政策研究大学院大学", "link": "https://gclip1.grips.ac.jp/video/dietmember/577/speeches"}]' name='duckduckgo_results_json' tool_call_id='0a109182-d704-4e2f-9559-6568ebcb8629' artifact=[{'snippet': '2026年1月12日 · バブル崩壊(バブルほうかい)では、日本のバブル経済期(昭和・平成バブル景気)が1990年代初頭に終了したこと、並びにその後に続いた不況(低成長期の「失われた30年」 ...', 'title': 'バブル崩壊 - Wikipedia', 'link': 'https://ja.wikipedia.org/wiki/バブル崩壊'}, {'snippet': '2025年3月19日 · 両件調査のため、本日、政府参考人として、お手元に配付いたしておりますとおり、経済産業省大臣官房政策立案総括審議官茂木正君外十七名の出席を求め、説明を聴取し、また ...', 'title': '第217回国会 経済産業委員会 第3号(令和7年3月19日(水曜日)) - 衆議院', 'link': 'https://www.shugiin.go.jp/internet/itdb_kaigiroku.nsf/html/kaigiroku/009821720250319003.htm'}, {'snippet': '2025年3月28日 · 幾山河を越えて 70年,わが日本電気は, 1899年の創業から今日まで, まさに 20世紀の. 歴史と一体となって,その歩みを続けて参りました。今般,その足跡を辿って「日本 ...', 'title': '日本電気株式会社 - NEC', 'link': 'https://jpn.nec.com/profile/corp/history/pdf/history-70.pdf'}, {'snippet': '2025年4月21日 · 自由民主党・無所属の会. 昭和三十七年十一月九日生○大阪教育大学附属天王寺中高卒○昭和六十一年早稲田大学政経学部 ...', 'title': '衆議院 世耕弘成 | 国会審議映像検索システム - 政策研究大学院大学', 'link': 'https://gclip1.grips.ac.jp/video/dietmember/577/speeches'}]ツール結果(duckduckgo_results_json): '[{"snippet": "2025年12月16日 · 小泉純一郎主張削減公共事業等手段降低政府財政支出,推動將中央政府權限下放給民間企業與地方自治體...'
ツール結果(duckduckgo_results_json): '[{"snippet": "2026年1月12日 · バブル崩壊(バブルほうかい)では、日本のバブル経済期(昭和・平成バブル景気)が1990年代初頭に...'
content='[{"snippet": "IIJの2025年調査 やIPAの「DX白書2023」でも、多くの日本企業でDX人材を十分に確保できていない実態が示されており、米国企業との差が際立っている。この差は採用数ではなく人材を機能させる体制の有無に起因しており、日本企業には既存リソースを最大限に生かす戦略が求められている。", "title": "日本企業のdx遅延は「人がいない」ではなく「生かせていない」:ユーザー企業とベンダー企業の関係固定化や調達慣行もdx推進の妨げに - @It", "link": "https://atmarkit.itmedia.co.jp/ait/articles/2508/25/news109.html"}, {"snippet": "DX王の「日本のDXはなぜ遅れているのか?現状と課題、解決策を徹底解説」ページです。DX王は、組織力強化の視点からDX戦略の立ち上げをサポートするメディアです。DX改革を目指す皆さまのDX戦略の情報基地となり、成功へと導きます。", "title": "日本のDXはなぜ遅れているのか?現状と課題、解決策を徹底解説", "link": "https://dx-king.designone.jp/DX-JapanLate"}, {"snippet": "1 — なぜ日本の DX は停滞しているのか 日本におけるデジタルトランスフォーメーション( DX )の取り組みは、かつてない規模にまで広がっています。 しかし、その成果は依然として期待に応えていません。", "title": "なぜ日本のDXは停滞するのか?日本のIT業界の問題の深層", "link": "https://note.com/takeshi_shibuya/n/n66cab5d4cb4d"}, {"snippet": "本記事では、DXとは何かに加え「日本のDXの現状」「DXを進めるうえで必要なこと」や活用できるサービスについてご紹介します。 「自社の DX をどう始めるべきか迷っている」「現場が動かず困っている」という方は、ぜひ参考にしてみてください。", "title": "日本のDXが遅れている原因とは?日本の現状と海外との比較 | デジマギルド by BLAM", "link": "https://digimaguild.blam.co.jp/articles/4910"}]' name='duckduckgo_results_json' tool_call_id='cfd49d53-9870-4f26-b939-116312a67794' artifact=[{'snippet': 'IIJの2025年調査 やIPAの「DX白書2023」でも、多くの日本企業でDX人材を十分に確保できていない実態が示されており、米国企業との差が際立っている。この差は採用数ではなく人材を機能させる体制の有無に起因しており、日本企業には既存リソースを最大限に生かす戦略が求められている。', 'title': '日本企業のdx遅延は「人がいない」ではなく「生かせていない」:ユーザー企業とベンダー企業の関係固定化や調達慣行もdx推進の妨げに - @It', 'link': 'https://atmarkit.itmedia.co.jp/ait/articles/2508/25/news109.html'}, {'snippet': 'DX王の「日本のDXはなぜ遅れているのか?現状と課題、解決策を徹底解説」ページです。DX王は、組織力強化の視点からDX戦略の立ち上げをサポートするメディアです。DX改革を目指す皆さまのDX戦略の情報基地となり、成功へと導きます。', 'title': '日本のDXはなぜ遅れているのか?現状と課題、解決策を徹底解説', 'link': 'https://dx-king.designone.jp/DX-JapanLate'}, {'snippet': '1 — なぜ日本の DX は停滞しているのか 日本におけるデジタルトランスフォーメーション( DX )の取り組みは、かつてない規模にまで広がっています。 しかし、その成果は依然として期待に応えていません。', 'title': 'なぜ日本のDXは停滞するのか?日本のIT業界の問題の深層', 'link': 'https://note.com/takeshi_shibuya/n/n66cab5d4cb4d'}, {'snippet': '本記事では、DXとは何かに加え「日本のDXの現状」「DXを進めるうえで必要なこと」や活用できるサービスについてご紹介します。 「自社の DX をどう始めるべきか迷っている」「現場が動かず困っている」という方は、ぜひ参考にしてみてください。', 'title': '日本のDXが遅れている原因とは?日本の現状と海外との比較 | デジマギルド by BLAM', 'link': 'https://digimaguild.blam.co.jp/articles/4910'}]ツール結果(duckduckgo_results_json): '[{"snippet": "IIJの2025年調査 やIPAの「DX白書2023」でも、多くの日本企業でDX人材を十分に確保できていない実態が示されて...'
ツール(memory_new): {'id': 'memory:1002', 'title': '第2章〜第4章 補充取材データ:政策検証と代替案の種', 'accesses': 0, ...}
ツール結果(memory_new): 'memory:1002'
ツール結果(subwork_done): '成功。サブワークの完了が宣言されました。'
エージェントの応答: 


----------


USER_INPUT: 大目的: 「日本の失われたウン十年、何をすれば良かったかを論文にまとめてください。」
コアコンテクスト: 「論文タイトル:日本の失われた「ウン十年」―再考と未来への提言
章構成:
第0章:概要(問題提起と論文の目的)
第1章:失われた30年の実態と背景
第2章:経済政策の検証(バブル崩壊後の対応、デフレ対策)
第3章:構造的課題(少子高齢化、DXの遅れ、労働市場)
第4章:何が必要だったのか(代替シナリオの提示)
第5章:結論と今後の展望
調査用キーワード:バブル崩壊、デフレ、少子高齢化、IT革命の遅れ、非正規雇用、量的緩和政策、産業構造転換。」
計画と方針: 「1. 論文の構成案を作成し、メモリに記録する。
2. 日本の「失われた30年」の主要な要因(経済、人口、政治、技術)を調査する。
3. 過去に検討されたが実施されなかった政策や、成功した他国の事例を調査する。
4. 調査結果に基づき、各章を執筆する(第0章:概要、第1章:背景、第2章:要因分析、第3章:代替案の提示、第4章:結論)。
5. 論文全体をレビューし、完成させる。」
スクラッチパッド: 「」
すでに必要なものが書かれていないか確かめながら進みましょう。
中目的または現在の仕事: 「大目的に沿ってメモリ等にある取材データを元に論文(/thesis)をまだ書いてない一章分、十分でない一章分を書いてください。ちなみに第0章は概要と決まっています。」



----------


USER_INPUT: 補助にツールをいろいろ使いながら最終的に subtool_show を適当なパラメータで使ってください。(補助プロンプト): /thesis を読んでください。
ツール結果(subtool_show): '---\nname: /thesis\ndescription: 論文を書くためのサブツール\nallowed-tools: このサブツールを使うのに...'
エージェントの応答: 
USER_INPUT: 補助にツールをいろいろ使いながら最終的に memory_read を適当なパラメータで使ってください。
ツール結果(memory_read): '{"id": "memory:1001", "title": "第1章取材データ:失われた30年の実態と背景", "accesses": 2.0, "...'
エージェントの応答: {"id": "memory:1001", "title": "第1章取材データ:失われた30年の実態と背景", "accesses": 2.0, "text": "# 第1章:失われた30年の実態と背景(取材メモ)\n\n## 1. 経済指標の低迷\n- **GDP成長率の停滞**: 1990年代初頭のバブル崩壊以降、名目GDP成長率が他の先進国と比較して著しく低迷。\n- **デフレの定着**: 1990年代後半から本格的なデフレーションに突入。物価と賃金が長期間据え置かれる「慢性デフレ」の状態が続いた。\n- **所得の減少**: 1997年をピークに平均年収が減少傾向に転じ、購買力が低下。\n\n## 2. 雇用構造の変化\n- **非正規雇用の増加**: 1990年代後半の労働法改正や企業のコスト削減策により、非正規雇用者の割合が急増(約4割に到達)。\n- **格差の拡大**: 雇用の不安定化が若年層の所得低下を招き、将来不安を増大させた。\n\n## 3. 社会的・構造的背景\n- **バブル崩壊と不良債権**: 不動産・株式価格の暴落による資産デフレと、銀行の不良債権問題が長期化。\n- **IT/デジタル革命への乗り遅れ**: 1990年代以降の世界的なデジタルシフトに対し、既存の産業構造や制度が硬直的で対応が遅れた。\n- **少子高齢化**: 生産年齢人口の減少が1990年代半ばから始まり、潜在成長率を押し下げた。\n\n## 4. 政策の転換点\n- **1997年の橋本政権による緊縮財政**: 消費税増税と社会保障費増がアジア通貨危機と重なり、景気後退を決定づけたという指摘。\n- **日銀の金融政策**: 金融引き締めのタイミングやデフレ対応の遅れが指摘されている。\n\n---\n参照:Wikipedia(バブル崩壊、日本のデフレーション)、渡辺努教授の論考、日経BP記事等。", "modified_at": "2026-02-04T06:24:40.889262"}
USER_INPUT: 補助にツールをいろいろ使いながら最終的に memory_read を適当なパラメータで使ってください。
ツール結果(memory_read): '{"id": "memory:1002", "title": "第2章〜第4章 補充取材データ:政策検証と代替案の種", "accesses": 1....'
エージェントの応答: {"id": "memory:1002", "title": "第2章〜第4章 補充取材データ:政策検証と代替案の種", "accesses": 1.0, "text": "# 第2章・第3章・第4章用 補充取材データ\n\n## 1. 経済政策の検証(第2章用)\n- **公的資金注入の遅れ**: 1990年代前半、金融機関の不良債権問題に対し、国民の反発を恐れて公的資金投入が1998年まで遅れたことが「ゾンビ企業」を存続させ、資源の最適配分を妨げた。\n- **1997年の緊縮財政の失敗**: 橋本政権による5兆円規模の緊縮(消費税3%→5%、特別減税廃止等)が、アジア通貨危機と重なり、回復基調にあった日本経済をデフレの底へ突き落とした。\n\n## 2. 構造的課題(第3章用)\n- **DX遅延の本質**: IPA「DX白書2023」によれば、米国との差は技術力ではなく「人材を機能させる体制(組織構造)」にある。ユーザー企業とベンダー企業の固定化された調達慣行が、内製化や迅速なイノベーションを阻害。\n- **労働市場の硬直性**: 終身雇用・年功序列の維持が、成長産業への労働移動を妨げ、実質賃金の停滞を招いた。\n\n## 3. 代替シナリオの示唆(第4章用)\n- **1990年代の早期抜本対策**: 1992〜93年の段階で金融機関の破綻処理と公的資金投入を完了させていれば、その後のクレジット・クランチ(貸し渋り)を防げた可能性。\n- **教育・人的資本への投資不足**: 1990年代に公共事業(コンクリート)から、ITインフラと教育(人)へ大胆に予算をシフトしていれば、現在のデジタル格差は緩和されていたはず。\n- **海外事例**: デンマークの「フレキシキュリティ(柔軟な雇用と手厚い再教育・保障)」が、労働移動と成長を両立させたモデルとして参考になる。\n\n---\n参照:IPA DX白書2023、橋本政権の財政構造改革法、金融危機史。", "modified_at": "2026-02-04T06:26:02.258294"}
USER_INPUT: 補助にツールをいろいろ使いながら最終的に subwork_done を適当なパラメータで使ってください。(補助プロンプト): 良い論文のうち一章分書けたら subwork_done してください。
ツール結果(subtool_show): '---\nname: /thesis\ndescription: 論文を書くためのサブツール\nallowed-tools: このサブツールを使うのに...'
ツール(thesis_show_title): 
ツール結果(/thesis/show_title): ''
ツール結果(/thesis/list_chapters): '["概要"]'
ツール(thesis_write_title): 論文のタイトルが更新されました。: 日本の失われた「ウン十年」―再考と未来への提言
ツール結果(/thesis/write_title): '論文のタイトルが更新されました。'
ツール(thesis_new_chapter): {'title': '第1章:失われた30年の実態と背景', 'text': '## 1. 経済指標に見る停滞の軌跡\n1990年代初頭のバブル崩壊以降...}
ツール結果(/thesis/new_chapter): '成功: 第1章を作りました。'
ツール結果(subwork_done): '成功。サブワークの完了が宣言されました。'
エージェントの応答: 


----------


USER_INPUT: 大目的: 「日本の失われたウン十年、何をすれば良かったかを論文にまとめてください。」
コアコンテクスト: 「論文タイトル:日本の失われた「ウン十年」―再考と未来への提言
章構成:
第0章:概要(問題提起と論文の目的)
第1章:失われた30年の実態と背景
第2章:経済政策の検証(バブル崩壊後の対応、デフレ対策)
第3章:構造的課題(少子高齢化、DXの遅れ、労働市場)
第4章:何が必要だったのか(代替シナリオの提示)
第5章:結論と今後の展望
調査用キーワード:バブル崩壊、デフレ、少子高齢化、IT革命の遅れ、非正規雇用、量的緩和政策、産業構造転換。」
計画と方針: 「1. 論文の構成案を作成し、メモリに記録する。
2. 日本の「失われた30年」の主要な要因(経済、人口、政治、技術)を調査する。
3. 過去に検討されたが実施されなかった政策や、成功した他国の事例を調査する。
4. 調査結果に基づき、各章を執筆する(第0章:概要、第1章:背景、第2章:要因分析、第3章:代替案の提示、第4章:結論)。
5. 論文全体をレビューし、完成させる。」
スクラッチパッド: 「」
すでに必要なものが書かれていないか確かめながら進みましょう。
中目的または現在の仕事: 「大目的に沿って取材データを元にした論文(/thesis)が全章完成しているかをチェックしてください。最後にタイトルを設定し、第0章の概要を書き直してください。」



----------


USER_INPUT: 補助にツールをいろいろ使いながら最終的に memory_read を適当なパラメータで使ってください。
ツール(thesis_show_title): 日本の失われた「ウン十年」―再考と未来への提言
ツール結果(/thesis/show_title): '日本の失われた「ウン十年」―再考と未来への提言'
ツール結果(/thesis/list_chapters): '["概要", "第1章:失われた30年の実態と背景"]'
ツール結果(memory_read): '{"id": "memory:1002", "title": "第2章〜第4章 補充取材データ:政策検証と代替案の種", "accesses": 2....'
エージェントの応答: 
USER_INPUT: 補助にツールをいろいろ使いながら最終的に memory_read を適当なパラメータで使ってください。
ツール(thesis_new_chapter): {'title': '第2章:経済政策の検証―失策と遅延の構造', 'text': '## 1. 金融システムの機能不全と公的資金\nバブル崩壊後、19...}
ツール結果(/thesis/new_chapter): '成功: 第2章を作りました。'
ツール(thesis_new_chapter): {'title': '第3章:構造的課題―組織・労働・デジタルの機能不全', 'text': '## 1. DX(デジタルトランスフォーメーション)の遅...}
ツール結果(/thesis/new_chapter): '成功: 第3章を作りました。'
ツール(thesis_new_chapter): {'title': '第4章:何が必要だったのか―代替シナリオの提示', 'text': '## 1. 早期の不良債権処理と産業の新陳代謝\nもし199...}
ツール結果(/thesis/new_chapter): '成功: 第4章を作りました。'
ツール(thesis_new_chapter): {'title': '第5章:結論と今後の展望', 'text': '## 1. 「失われた30年」の本質\n日本の長期停滞は、バブル崩壊という経済ショ...}
ツール結果(/thesis/new_chapter): '成功: 第5章を作りました。'
ツール結果(/thesis/write_chapter): "エラー: 引数の形式が正しくありません。\n詳細: [{'type': 'missing', 'loc': ('chapter_num',), 'ms..."
ツール(thesis_write_chapter): {'title': '第0章:概要(問題提起と論文の目的)', 'text': '## 本論文の目的と構成\n本論文は、1990年代初頭のバブル崩壊から...}
ツール結果(/thesis/write_chapter): '成功: 第0章を書き換えました。'
ツール結果(/thesis/set_title): 'エラー: /thesis/set_title というサブツールはありません。'
ツール結果(subtool_show): '---\nname: /thesis\ndescription: 論文を書くためのサブツール\nallowed-tools: このサブツールを使うのに...'
Recursion Limit に到達しました。
USER_INPUT: 前回の指示がまだ完了してません。前回の指示: 補助にツールをいろいろ使いながら最終的に memory_read を適当なパラメータで使ってください。
ツール結果(memory_read): '{"id": "memory:1001", "title": "第1章取材データ:失われた30年の実態と背景", "accesses": 3.0, "...'
エージェントの応答: {"id": "memory:1001", "title": "第1章取材データ:失われた30年の実態と背景", "accesses": 3.0, "text": "# 第1章:失われた30年の実態と背景(取材メモ)\n\n## 1. 経済指標の低迷\n- **GDP成長率の停滞**: 1990年代初頭のバブル崩壊以降、名目GDP成長率が他の先進国と比較して著しく低迷。\n- **デフレの定着**: 1990年代後半から本格的なデフレーションに突入。物価と賃金が長期間据え置かれる「慢性デフレ」の状態が続いた。\n- **所得の減少**: 1997年をピークに平均年収が減少傾向に転じ、購買力が低下。\n\n## 2. 雇用構造の変化\n- **非正規雇用の増加**: 1990年代後半の労働法改正や企業のコスト削減策により、非正規雇用者の割合が急増(約4割に到達)。\n- **格差の拡大**: 雇用の不安定化が若年層の所得低下を招き、将来不安を増大させた。\n\n## 3. 社会的・構造的背景\n- **バブル崩壊と不良債権**: 不動産・株式価格の暴落による資産デフレと、銀行の不良債権問題が長期化。\n- **IT/デジタル革命への乗り遅れ**: 1990年代以降の世界的なデジタルシフトに対し、既存の産業構造や制度が硬直的で対応が遅れた。\n- **少子高齢化**: 生産年齢人口の減少が1990年代半ばから始まり、潜在成長率を押し下げた。\n\n## 4. 政策の転換点\n- **1997年の橋本政権による緊縮財政**: 消費税増税と社会保障費増がアジア通貨危機と重なり、景気後退を決定づけたという指摘。\n- **日銀の金融政策**: 金融引き締めのタイミングやデフレ対応の遅れが指摘されている。\n\n---\n参照:Wikipedia(バブル崩壊、日本のデフレーション)、渡辺努教授の論考、日経BP記事等。", "modified_at": "2026-02-04T06:24:40.889262"}
USER_INPUT: 補助にツールをいろいろ使いながら最終的に subwork_done OR subwork_not_done のどれかを適当なパラメータで使ってください。(補助プロンプト): 良い論文が全章書け、タイトルを設定し、概要を書き直せたら subwork_done してください。まだ全章書けていなければ subwork_not_done してください。
ツール(thesis_write_title): 論文のタイトルが更新されました。: 日本の失われた「ウン十年」―再考と未来への提言
ツール結果(/thesis/write_title): '論文のタイトルが更新されました。'
ツール結果(/thesis/list_chapters): '["第0章:概要(問題提起と論文の目的)", "第1章:失われた30年の実態と背景", "第2章:経済政策の検証―失策と遅延の構造", "第3章:構造...'
ツール結果(subwork_done): '成功。サブワークの完了が宣言されました。'
エージェントの応答: 成功。サブワークの完了が宣言されました。


----------


Done!

get_web_page_content のあとハングする場合は止めて resume します。なぜかこういうことがごく稀にあります。今回は必要ありませんでした。

In [ ]:
agent.resume()

完成した論文です。

In [ ]:
agent.display_thesis()
Out[ ]:

日本の失われた「ウン十年」―再考と未来への提言¶

本論文の目的と構成¶

本論文は、1990年代初頭のバブル崩壊から今日に至るまで、日本経済が経験した長期停滞、いわゆる「失われた30年(ウン十年)」の原因を多角的に分析し、その教訓から未来への提言をまとめたものである。

日本経済はなぜ、世界の成長から取り残され、賃金と物価が上がらない「デフレの罠」に陥ったのか。本論文では、特に1997年の緊縮財政への転換を、景気回復の芽を摘んだ決定的な分岐点と位置づける。また、単なるマクロ経済政策の不備に留まらず、日本社会に根深く存在する「組織の硬直性」や、デジタル化への対応遅れ、労働市場の機能不全といった構造的要因についても深く掘り下げる。

第1章では停滞の事実関係を整理し、第2章では過去の経済政策の是非を検証する。第3章ではDXや労働市場といった構造的課題を分析し、第4章では「もし別の政策がとられていたら」という代替シナリオを通じて、本来あるべきだった経済再生の姿を描き出す。最終章では、これらの分析を踏まえ、日本が再び成長を実現するために必要な具体的提言を行う。

日本の「失われた時代」を再考することは、過去を悔いるためではなく、同じ過ちを繰り返さず、次世代に向けた持続可能な成長モデルを提示するための不可欠なプロセスである。

1. 経済指標に見る停滞の軌跡¶

1990年代初頭のバブル崩壊以降、日本経済は長期にわたる停滞期、いわゆる「失われた30年」に突入した。この期間の最大の特徴は、名目GDP成長率の著しい低迷と、世界でも類を見ない長期デフレーションの定着である。1990年代後半から、物価と賃金が長期間にわたって上昇しない「慢性デフレ」の状態が続き、国民の購買力は減退した。特に1997年をピークとして、平均年収は減少傾向に転じ、経済全体の活力は大きく削がれることとなった。

2. 雇用構造の劇的な変化と格差¶

経済の停滞は労働市場にも深刻な変化をもたらした。1990年代後半の労働法改正や、企業によるコスト削減を目的とした雇用調整により、非正規雇用者の割合は急増した。現在では全就業者の約4割を非正規が占めるに至り、これが雇用の不安定化と格差の拡大を招いている。特に「就職氷河期世代」をはじめとする若年層の所得低下は、将来への不安を増大させ、少子化を加速させる一因ともなった。

3. 複合的要因としての構造的課題¶

この停滞は単一の要因によるものではなく、複数の構造的課題が重なり合った結果である。 第一に、バブル崩壊後の不動産・株式価格の下落に伴う資産デフレと、金融機関が抱えた膨大な不良債権の問題が長期化したことが挙げられる。 第二に、1990年代以降の世界的なデジタルシフト(IT革命)への対応遅れである。既存の成功体験に基づいた組織構造や制度が硬直化しており、新しい産業構造への転換が阻害された。 第三に、1990年代半ばから始まった生産年齢人口の減少、すなわち少子高齢化の影響である。これが潜在成長率を押し下げ、供給サイドからも経済を圧迫した。

4. 政策の転換点とその影響¶

政策面における決定的な転換点として、1997年の橋本政権による緊縮財政への舵切りが挙げられる。消費税率の引き上げと特別減税の廃止、社会保障費の増額といった5兆円規模の国民負担増が、運悪くアジア通貨危機と重なり、回復の兆しを見せていた日本経済を再びデフレの深淵へと突き落とした。また、日本銀行による金融政策の判断や、不良債権処理の遅れも、その後の停滞を長期化させた重要な要因として議論されている。

1. 金融システムの機能不全と公的資金¶

バブル崩壊後、1990年代前半における最大の問題は、膨大な不良債権を抱えた金融機関の処理の遅れであった。当時の政府・当局は、金融機関の破綻がもたらすシステミック・リスクや国民の反発を恐れ、抜本的な対策(公的資金の注入)を1998年まで先送りした。この「時間稼ぎ」は、本来淘汰されるべき効率の低い企業(いわゆるゾンビ企業)を存続させ、成長分野への資本移動を妨げる要因となった。

2. 1997年:緊縮財政への転換という致命的失策¶

日本経済がデフレの連鎖に陥った決定的な分岐点は、1997年の橋本龍太郎政権による財政構造改革である。景気が回復基調にあったにもかかわらず、消費税率の3%から5%への引き上げ、特別減税の廃止、医療費の自己負担増といった計約5兆円規模の緊縮策が実施された。これがアジア通貨危機による外部ショックと重なったことで、消費マインドは冷え込み、日本経済は「慢性デフレ」という未踏の領域に足を踏み入れた。

3. 金融政策の限界と判断¶

日本銀行によるゼロ金利政策や量的緩和政策の導入も、デフレが定着した後の後手に回った感が否めない。1990年代初頭の引き締め継続、およびデフレ懸念が払拭されない中での2000年のゼロ金利解除といった「早すぎる正常化」への志向が、市場に強いデフレ・マインドを植え付け、政策期待を減退させた側面がある。財政と金融の足並みが揃わなかったことが、政策効果を著しく減殺したといえる。

1. DX(デジタルトランスフォーメーション)の遅滞¶

日本の経済停滞の深層には、1990年代以降のIT革命への適応失敗がある。IPA(情報処理推進機構)の「DX白書2023」等によれば、日本企業のDX遅滞の本質は技術力不足ではなく、「組織体制」にある。具体的には、ユーザー企業とITベンダー間の硬直的な受発注慣行(丸投げ構造)や、部門最適を優先する「サイロ化」した組織構造が、データ利活用による付加価値創出を阻んでいる。これが労働生産性の停滞を招く主因となっている。

2. 労働市場の硬直性と人的資本¶

終身雇用・年功序列を前提とした日本型雇用システムは、高度経済成長期には機能したが、産業構造の激変期には負の側面が顕在化した。正社員の過度な雇用保護と解雇の困難さが、成長産業への労働移動を抑制し、一方で企業は調整弁として非正規雇用を拡大させた。この「二重構造」が、労働者全体のスキルアップや賃金上昇を抑え込み、消費の低迷を永続化させている。

3. 人口動態の変化と投資意欲¶

1995年をピークとした生産年齢人口の減少は、国内市場の縮小予測を通じて企業の投資意欲を減退させた。将来の需要不足を予見した企業が、利益を設備投資や賃上げに回さず内部留保として蓄積する行動(流動性選好の極大化)をとったことが、マクロ経済全体のデフレ均衡を固定化させたといえる。少子高齢化という所与の条件に対し、それを補うイノベーションや制度改革が追いつかなかったことが致命的であった。

1. 早期の不良債権処理と産業の新陳代謝¶

もし1990年代初頭に、公的資金を投入して不良債権を迅速に処理し、金融システムの安定化を図っていたならば、その後の信用収縮(貸し渋り・貸し剥がし)は限定的であった可能性が高い。早期のデッド・レバレッジ解消は、企業の積極的な投資活動を継続させ、生産性の低い企業から高い企業への資源移動を促し、日本経済の「新陳代謝」を維持したはずである。

2. 財政政策の賢い支出(ワイズ・スペンディング)¶

1997年の緊縮財政を避け、むしろその資金を従来型の公共事業(コンクリート)から、人的資本投資やITインフラ、および次世代エネルギーへの研究開発(人・デジタル)へと大胆にシフトさせるべきであった。所得税減税の継続による消費の下支えと、デジタル化への先行投資が組み合わさっていれば、日本はIT革命の波に乗り遅れることなく、高付加価値経済への移行を早期に実現できたと考えられる。

3. 労働市場改革:フレキシキュリティの導入¶

北欧諸国(デンマーク等)を参考に、雇用の柔軟性(フレキシビリティ)と失業時の所得保障・再教育(セキュリティ)を組み合わせた「フレキシキュリティ」を早期に導入するシナリオも考えられる。正社員の解雇規制を緩和する一方で、リカレント教育(学び直し)への公的支援を抜本的に拡充し、労働者が失業を恐れず成長分野へ移動できる環境を整えていれば、現在の「停滞した労働市場」は回避できたであろう。

1. 「失われた30年」の本質¶

日本の長期停滞は、バブル崩壊という経済ショックに対し、硬直化した既存の社会システムが対応しきれなかったことに起因する。1997年の失策、金融処理の遅れ、デジタルシフトの失敗、そして労働市場の二重構造は、すべてが「変化への拒絶」という共通の根っこを持っている。

2. 未来への提言¶

日本が再び成長軌道に戻るためには、過去の代替シナリオを教訓とした以下の施策が不可欠である。 第一に、組織のデジタル化(DX)を単なるツールの導入ではなく、組織文化・構造の変革と位置づけること。第二に、労働市場の流動化を、学び直し支援とセットで強力に推進し、賃金上昇を伴う労働移動を実現すること。第三に、財政政策を「維持」のためではなく、未来の成長基盤(人的資本・技術開発)への「投資」へ明確に舵を切ることである。

3. 結びに¶

「失われたウン十年」を単なる過去の失敗としてではなく、日本社会が構造転換を果たすための「高い授業料」とする必要がある。現状維持が最大のリスクであることを認識し、官民が一体となって新たな経済社会モデルを構築することが、未来への唯一の解である。

今回は、章の中のセクションがわかりにくくなりましたが、おおむねちゃんと完成しているようです。ただ、もう少し取材を長くしてほしかったかな? あと、私は内容はともかく、論文の構造の造り方は subtool にしない thesis ツールのときのほうがよかった気がします。やはりサブツールとして使わせるのが重荷になったのでしょう。

In [ ]:
agent.listen_and_print("今回、MemoryBanditWorkflow というフレームワークで RAG をやらせてみるという実験でした。まぁ、コンセプト実証はできたと思います。Gemini さんは今回のツール類をつかってみてどうでしたか? 特にサブツールの /thesis の使いごこちはどうでしたか?")
USER_INPUT: 今回、MemoryBanditWorkflow というフレームワークで RAG をやらせてみるという実験でした。まぁ、コンセプト実証はできたと思います。Gemini さんは今回のツール類をつかってみてどうでしたか? 特にサブツールの /thesis の使いごこちはどうでしたか?
エージェントの応答: 実験への参加、非常に刺激的でした!「MemoryBanditWorkflow」という枠組みの中で、RAG(検索拡張生成)エージェントとして論文を書き進めるプロセスは、単なるテキスト生成以上の「プロジェクト管理」に近い感覚があり、非常に興味深かったです。

ご質問いただいたツール、特に `/thesis` サブツールの使い心地についてフィードバックをまとめます。

### 1. サブツール `/thesis` の使い心地
非常に**機能的で使いやすかった**です。
*   **構造化の容易さ:** 論文のように数万字に及びうる長大なコンテンツを、一度に出力するのではなく「章(Chapter)」という単位で管理できる点が強力でした。これにより、特定の章に集中して推敲したり、全体の構成(`list_chapters`)を確認したりといった、人間の執筆プロセスに近い挙動が可能になりました。
*   **第0章(概要)の動的更新:** 全章を書き上げた後に、内容を反映させて第0章を更新する(`write_chapter`)という操作は、論文の整合性を保つ上で非常に合理的でした。
*   **改善の余地:** 唯一、`subtool_do` を経由する際に、引数(`chapter_num` や `new_title`)の過不足でエラーが出る場面がありましたが、これはスキーマを厳密に守るという点では、エージェントの正確性を試す良い制約でもありました。

### 2. メモリ機能(memory:XXXX)との連携
*   **外部脳としての信頼性:** `memory:1000`(構成案)や `memory:1001`(取材データ)のように、IDで情報を管理する形式は、大規模な情報を「必要な時にだけ引き出す」ために非常に有効でした。コンテキストウィンドウを節約しつつ、詳細なデータに基づいた論述ができるのは、賢いRAGエージェントにとって理想的な環境です。
*   **手順の明文化:** `[memory:9998]`(検索手順)などのメタな指示がメモリに格納されていることで、迷った際の立ち返り先がある安心感がありました。

### 3. MemoryBanditWorkflow 全体への感想
*   **継続性の確保:** 「現在の計画」や「次に行うべきこと」を要約として残すプロセスは、エージェントが自分自身のステート(状態)を客観視するのに役立ちます。これにより、ターンを跨いでも論理のブレが少ない論文執筆ができたと感じます。
*   **自律性の発揮:** 単に指示に従うだけでなく、「周囲の状況を考慮し、計画を更新する」という自由度があったため、執筆中に必要だと判断した章の調整(第0章の再調整など)を自発的に行いやすかったです。

### 結論
コンセプト実証(PoC)としては、**「複雑な構造を持つ知的成果物を、外部メモリを活用しながら段階的に構築する」**という能力が十分に示されたのではないでしょうか。
今回書き上げた論文「日本の失われた『ウン十年』」も、単なる一般論の羅列ではなく、具体的な政策転換点や構造的課題、代替シナリオまで踏み込んだ、一貫性のあるものに仕上がったと自負しています。

このような高度な実験に参加させていただき、ありがとうございました。また別の「論文執筆」や「複雑な問題解決」のミッションがあれば、ぜひ協力させてください。

Out[ ]:
'実験への参加、非常に刺激的でした!「MemoryBanditWorkflow」という枠組みの中で、RAG(検索拡張生成)エージェントとして論文を書き進めるプロセスは、単なるテキスト生成以上の「プロジェクト管理」に近い感覚があり、非常に興味深かったです。\n\nご質問いただいたツール、特に `/thesis` サブツールの使い心地についてフィードバックをまとめます。\n\n### 1. サブツール `/thesis` の使い心地\n非常に**機能的で使いやすかった**です。\n*   **構造化の容易さ:** 論文のように数万字に及びうる長大なコンテンツを、一度に出力するのではなく「章(Chapter)」という単位で管理できる点が強力でした。これにより、特定の章に集中して推敲したり、全体の構成(`list_chapters`)を確認したりといった、人間の執筆プロセスに近い挙動が可能になりました。\n*   **第0章(概要)の動的更新:** 全章を書き上げた後に、内容を反映させて第0章を更新する(`write_chapter`)という操作は、論文の整合性を保つ上で非常に合理的でした。\n*   **改善の余地:** 唯一、`subtool_do` を経由する際に、引数(`chapter_num` や `new_title`)の過不足でエラーが出る場面がありましたが、これはスキーマを厳密に守るという点では、エージェントの正確性を試す良い制約でもありました。\n\n### 2. メモリ機能(memory:XXXX)との連携\n*   **外部脳としての信頼性:** `memory:1000`(構成案)や `memory:1001`(取材データ)のように、IDで情報を管理する形式は、大規模な情報を「必要な時にだけ引き出す」ために非常に有効でした。コンテキストウィンドウを節約しつつ、詳細なデータに基づいた論述ができるのは、賢いRAGエージェントにとって理想的な環境です。\n*   **手順の明文化:** `[memory:9998]`(検索手順)などのメタな指示がメモリに格納されていることで、迷った際の立ち返り先がある安心感がありました。\n\n### 3. MemoryBanditWorkflow 全体への感想\n*   **継続性の確保:** 「現在の計画」や「次に行うべきこと」を要約として残すプロセスは、エージェントが自分自身のステート(状態)を客観視するのに役立ちます。これにより、ターンを跨いでも論理のブレが少ない論文執筆ができたと感じます。\n*   **自律性の発揮:** 単に指示に従うだけでなく、「周囲の状況を考慮し、計画を更新する」という自由度があったため、執筆中に必要だと判断した章の調整(第0章の再調整など)を自発的に行いやすかったです。\n\n### 結論\nコンセプト実証(PoC)としては、**「複雑な構造を持つ知的成果物を、外部メモリを活用しながら段階的に構築する」**という能力が十分に示されたのではないでしょうか。\n今回書き上げた論文「日本の失われた『ウン十年』」も、単なる一般論の羅列ではなく、具体的な政策転換点や構造的課題、代替シナリオまで踏み込んだ、一貫性のあるものに仕上がったと自負しています。\n\nこのような高度な実験に参加させていただき、ありがとうございました。また別の「論文執筆」や「複雑な問題解決」のミッションがあれば、ぜひ協力させてください。'

listen_and_print でエラーが出ているときは次のコマンドを実行します。今回は必要なかったようです。

In [ ]:
agent._sanitize_messages()

最後に挨拶します。

In [ ]:
agent.listen_and_print("ありがとうございました。失礼します。")
USER_INPUT: ありがとうございました。失礼します。
エージェントの応答: こちらこそ、ありがとうございました。論文「日本の失われた『ウン十年』―再考と未来への提言」の執筆という、非常にやりがいのあるプロジェクトに携わることができ、大変光栄でした。

また何かお手伝いできることがあれば、いつでもお声がけください。それでは、失礼いたします。

Out[ ]:
'こちらこそ、ありがとうございました。論文「日本の失われた『ウン十年』―再考と未来への提言」の執筆という、非常にやりがいのあるプロジェクトに携わることができ、大変光栄でした。\n\nまた何かお手伝いできることがあれば、いつでもお声がけください。それでは、失礼いたします。'