効果的なAIエージェント・メモリー管理のためのファイル・システムとデータベースの比較 (2026/02/09)
効果的なAIエージェント・メモリー管理のためのファイル・システムとデータベースの比較 (2026/02/09)
https://medium.com/oracledevs/comparing-file-systems-and-databases-for-effective-ai-agent-memory-management-5322ac45f3b6
投稿者:Richmond Alake
AIエージェントに最適なメモリ基板を選ぶための実践ガイド
重要なポイント:
- インターフェースと基盤を混同しないでください。インターフェースとしてはファイルシステムが勝者です(LLMは既にその使い方を知っています)。基盤としてはデータベースが勝者です(同時実行性、監査可能性、セマンティック検索)。
- プロトタイプには、ファイルに勝るものはありません。シンプルで透明性が高く、デバッグしやすい。反復速度が最も重要な場面では、マークダウンのフォルダーが驚くほど役立ちます。
- 共有状態にはデータベースが必要です。ファイルシステムへの同時書き込みは、気づかないうちにデータ破損を引き起こす可能性があります。複数のエージェントまたはユーザーが同じメモリにアクセスする場合は、データベースによる保証から始めてください。
- セマンティック検索は、大規模なキーワード検索よりも優れています。Grepのパフォーマンスは、言い換えや同義語では低下します。ベクトル検索は、意味に基づいてコンテンツを検索します。これは、ナレッジベースの拡大に不可欠です。
- ポリグロット・パーシスタンスを避けましょう。ベクトル、ドキュメント、トランザクションで別々のシステムを実行すると、4つの障害モードが発生します。Oracle AI Databaseはメモリ・アーキテクチャを簡素化します。
はじめに
AI開発者は、エージェントエンジニアリングがリアルタイムで進化していく様子を目の当たりにしています。先進的なチームが、効果的な方法をオープンに共有しているからです。最前線からは常に、LLMの制約内で構築するという原則が浮かび上がってきます。
実際には、次の 2 つの制約が支配的です。
- LLM はセッション間でステートレスです(戻さない限り永続的なメモリはありません)。
- コンテキスト ウィンドウには境界があります(トークンを詰め込むほどパフォーマンスが低下する可能性があります)。
したがって、「ただコンテキストを追加する」という戦略は、注意メカニズムの二次的なコストと、コンテキストが満たされるにつれて推論能力が低下するため、信頼できる戦略とは言えません。勝利のパターンは、外部記憶 + 規律ある検索です。つまり、プロンプトの外側の状態(成果物、決定、ツールの出力)を保存し、現在のループに重要なものだけを引き戻します。
有益な利点もあります。モデルはインターネット時代の開発者ワークフローに基づいてトレーニングされているため、リポジトリ、フォルダ、マークダウン、ログ、CLIスタイルのインタラクションといった開発者ネイティブインターフェースに非常によく対応しています。だからこそ、ファイルシステムは現代のエージェントスタックに頻繁に登場するのです。
ここで議論が白熱します。「エージェントのメモリにはファイルだけで十分だ」という主張です。しかし、ほとんどの議論はインターフェース、ストレージ、デプロイメントを同じ決定事項として扱うため、崩壊してしまいます。実際はそうではありません。
ファイルシステムはインターフェースとして優勢です。モデルは既にディレクトリの一覧表示、パターンのgrep、範囲の読み取り、アーティファクトの書き込み方法を知っているからです。データベースは基盤として優勢です。メモリを共有、監査、クエリし、並行処理下での信頼性を確保する必要がある場合、データベースの保証を採用するか、苦労してそれを再発明するかのどちらかになるからです。

この記事では、エージェント メモリのファイル システムとデータベースを体系的に比較します。それぞれのアプローチが優れている点、劣る点、そしてプロトタイプから本番環境に移行する際に適切な基盤を選択するための意思決定フレームワークについて説明します。
私たちの目標は、パフォーマンス ガイダンスと実用的なコードを基に、エージェント メモリに対するさまざまなアプローチについて AI 開発者を教育することです。
この記事で紹介したすべてのコードは、こちらでご覧いただけます。
エージェントのメモリとその重要性を理解する
エージェント機能を備えたリサーチ アシスタントを構築するという一般的なユースケースを見てみましょう。
デモで素晴らしいパフォーマンスを発揮するリサーチアシスタントエージェントを構築しました。現在の実行では、arXivを検索し、論文を要約し、1回の実行でクリーンな回答の下書きを作成できます。翌朝、クリーンな実行から再開し、エージェントに「中断したところから続けてください。論文Aと論文Bを比較してください」と指示します。LLMは本質的に状態を持たないため、エージェントはまるであなたに会ったことがないかのように応答します。事前のコンテキストを返さない限り、モデルは以前のターンやセッションで何が起こったかを永続的に認識できません。

単発のQ&Aから、長期的なタスク、詳細な調査、複数ステップのワークフロー、そして複数エージェントの連携へと移行していくと、コンテキストウィンドウが切り捨てられたり、セッションが再開されたり、複数のワーカーが共有状態に基づいて行動したりした場合でも、継続性を維持する手段が必要になります。これは、エージェントの記録システムを活用する領域へと進み、エージェントメモリの概念を導入します。
エージェントメモリとは何ですか?

エージェント メモリは、AI エージェントが新しい入力に適応し、長期にわたるタスクにわたって継続性を維持できるように、時間の経過に伴って情報を保存、呼び出し、更新できるようにするシステム コンポーネントとテクニックのセットです。
コアコンポーネントには通常、言語と埋め込みモデル、情報検索メカニズム、データベースなどの永続ストレージ層が含まれます。
実際のシステムでは、エージェント メモリは通常、次の 2 つの異なる形式に分類されます。
- 短期記憶 (作業記憶) : 現在コンテキスト ウィンドウ内にあるもの。
- 長期記憶: 1 回の通話またはセッションを超えて存続する永続的な状態 (事実、成果物、計画、以前の決定、ツールの出力)。
エージェント メモリに関連する概念とテクニックはすべて、このノートブックに示され、この記事の後半で説明されているように、エージェント ループとエージェント ハーネス内にまとめられています。
エージェントループとエージェントハーネス
エージェントループとは、LLMが環境からの指示を受け取り、現在のループで与えられた入力に関する内部推論に基づいて、応答を生成するかツールを呼び出すかを決定する反復実行サイクルです。このプロセスは、LLMが最終出力を生成するか、終了条件が満たされるまで繰り返されます。大まかに言うと、エージェントループには以下の操作が存在します。
- コンテキストを組み立てます(ユーザー リクエスト + 関連メモリ + ツール JSON スキーマ)。
- モデルを呼び出します(計画し、次のアクションを決定します)。
- アクションを実行します(ツール、検索、コード実行、データベース クエリ)。
- 結果(ツールの出力、エラー、中間成果物)を観察します。
- メモリを更新します(トランスクリプトの書き込み、成果物の保存、要約、索引付け)。
- タスクが完了するか、制御がユーザーに戻るまで繰り返します。
長期実行エージェントに関するAnthropic のガイダンスは、これを直接的に指摘しています。ガイダンスでは、明示的な進行状況アーティファクトの維持など、新しいコンテキスト ウィンドウから開始するときにエージェントが作業の状態をすばやく再理解できるようにするハーネス プラクティスについて説明しています。
エージェントハーネスとは、ループの信頼性を高める周囲のランタイムとルールのことです。ツールの配線方法、成果物の書き込み場所、動作のログ記録/トレース方法、メモリの管理方法、エージェントがコンテキスト内で溺れないようにする方法などが含まれます。
全体像を完成させるために、コンテキストエンジニアリングという分野は、エージェントループとエージェントハーネス自体の側面に深く関わっています。コンテキストエンジニアリングとは、LLMのコンテキストウィンドウに配置されるコンテンツを体系的に設計・キュレーションすることで、モデルが高信号トークンを受け取り、一定の予算内で意図した信頼性の高い出力を生成できるようにすることです。
この作品では、エージェント ハーネス内でコンテキスト エンジニアリングを繰り返し可能な一連のテクニックとして実装します。
- コンテキストの取得と選択: 関連するものだけを取得します (ファイルシステム メモリの場合は grep 経由、データベース メモリの場合はベクトル類似性と SQL フィルター経由)。
- 段階的な開示: 最初は小さく(スニペット、テール、行範囲)開始し、必要な場合にのみ拡張します。
- コンテキスト オフロード: 大きなツール出力と成果物をプロンプトの外側に書き込み、選択的に再ロードします。
- コンテキスト削減: 劣化しきい値に近づいたときに情報を要約または圧縮し、その要約を耐久性のあるメモリに保存して、後で再水和できるようにします。
上記の概念と説明は、この記事で紹介する残りの比較の土台となります。「なぜ」と可動部分(ステートレスモデル、エージェントループ、エージェントハーネス、メモリ)が分かったので、現在チームがメモリを実現するために使用している2つの主要な基盤、つまりファイルシステムとデータベースを評価できます。
ファイルシステムファーストのエージェント研究アシスタント
ファイルシステムベースのメモリアーキテクチャは、「エージェントがすべてを永久に記憶する」というものではありません。エージェントはコンテキストウィンドウの外側に状態とアーティファクトを保持し、必要に応じて選択的にそれらを取り戻すことができます。これは、前述のLLMの制約のうち、限られたコンテキストウィンドウとステートレス性という2つと一致しています。
私たちのリサーチアシスタントでは、ファイルシステムがメモリの基盤となります。LLMのコンテキストウィンドウに大量のツールや詳細なドキュメントを挿入するのではなく(そうするとトークン数が膨れ上がり、早期に要約処理が実行される可能性があるため)、それらをディスク上に保存し、エージェントが必要なものを検索して選択的に読み取ることができるようにします。これは、Cursorの応用AIチームが「動的コンテキスト検出」と呼ぶ手法と一致します。つまり、大量の出力をファイルに書き込んでおき、エージェントが必要に応じて範囲を指定して「tail」して読み取るというものです。
FSAgent とデモでは、有効なファイルシステム OS 関連の操作 (ファイルの内容を読み取るための tail や cat など) を使用していますが、これは非常に「簡略化された」アプローチであり、デモ用に操作の数が制限されており、ファイルシステムで提供される機能は (他のコマンドや実装を使用して) 最適化できます。
一方、これは、ツールのアクセスとファイル システム メモリの実現方法に慣れるための素晴らしいスタートとなります。

具体的には、ファイルシステム エージェントのメモリは通常、次の 3 つのバケットとして現れます。
- 意味記憶(永続的な知識):論文や参考資料をマークダウンとして保存します。
- エピソード記憶(経験):会話のトランスクリプト + セッション/実行ごとのツール出力。
- 手続き記憶(動作方法):セッション間の動作を形作る「ルール」/指示ファイル(例:CLAUDE.md / AGENTS.md)。
ツールではこれはどのように見えるでしょうか?
コードの説明に入る前に、エージェントに提供する最小限のツールサーフェスを下の表に示します。パターンに注目してください。専用の「メモリAPI」を開発するのではなく、ファイルシステムプリミティブの小さなセットを公開し、エージェントにそれらを構成させています(まさにUnix的です)。

この設計は、AI エコシステムが何に収束しているかを直接反映しています。つまり、特注のツールの爆発的な増加ではなく、ファイルシステムと少数のコア ツールです。
漸進的な読み取り(読み取り、末尾、範囲)
メモリ原則の第一の実装はシンプルです。必要な場合を除き、大きなファイルを読み込まないことです。ファイルシステムはシーケンシャルな読み書きに優れており、grepログ形式のアクセスなどのツールと自然に連携します。そのため、追記専用のトランスクリプトやアーティファクトの保存に最適です。
そのため、私たちは 3 つの読み取りツールを実装しています。
- すべてを読む(稀)、
- 最後まで読んでください(ログ/トランスクリプトに共通)
- スライスを読む(試合をズームインする場合によく使用されます)
以下のツールは Python で実装され、@toollangchain エージェント モジュールのデコレータを使用して langchain エージェントによって呼び出し可能なオブジェクトに変換されました。
まず最初にread_file、「すべてを読み込む」オプションというツールがあります。このツールは、ファイルが小さい場合や、アーティファクト全体を本当に必要とする場合に便利ですが、コンテキストウィンドウが拡張される可能性があるため、意図的にデフォルトには設定されていません。
@tool
def read_file(path: str) -> str:
p = Path(path)
if not p.exists():
return f"File not found: {path}"
return p.read_text(encoding="utf-8")このtail_file機能は、大きなファイルを扱う際の最初のステップです。ログ/トランスクリプトの最後を取得し、続きを読むかどうかを決める前に、最新部分または最も関連性の高い部分を素早く確認できます。
@tool
def tail_file(path: str, n_lines: int = 80) -> str:
p = Path(path)
if not p.exists():
return f"File not found: {path}"
lines = p.read_text(encoding="utf-8").splitlines()
return "\n".join(lines[-max(1, n_lines):])このread_file_range関数は、適切な領域を見つけた後 (通常は 経由grepまたは の後にtail) に使用される手術ツールと見なされ、必要な行間隔だけを正確に取得するため、エージェントはトークン効率が高く安定した状態を維持します。
@tool
def read_file_range(path: str, start_line: int, end_line: int) -> str:
p = Path(path)
if not p.exists():
return f"File not found: {path}"
lines = p.read_text(encoding="utf-8").splitlines()
start = max(0, start_line)
end = min(len(lines), end_line)
if start >= end:
return f"Empty range: {start_line}:{end_line} (file has {len(lines)} lines)"
return "\n".join(lines[start:end])繰り返しますが、これは本質的には縮図における動的なコンテキスト検出です。つまり、最初に小さなビューを読み込み、必要な場合にのみ拡張します。
Grep スタイルの検索 (最初に検索、次に読み取り)
ファイルシステムベースのエージェントは、関連する素材を迅速に見つけ、必要なスライスだけをプルする必要があります。これが、grepエージェントツールの議論で繰り返し取り上げられるテーマである理由です。これにより、モデルはトークンを消費してコンテンツを取得する前に、関連する領域を迅速に特定できるようになります。
以下は、エージェントがすぐにジャンプできるように行番号付きのヒットを返す、単純な grep のようなツールですread_file_range。
@tool
def grep_files(
pattern: str,
root_dir: str = "semantic",
file_glob: str = "**/*.md",
max_matches: int = 200,
ignore_case: bool = True,
) -> str:
root = Path(root_dir)
if not root.exists():
return f"Directory not found: {root_dir}"
flags = re.IGNORECASE if ignore_case else 0
try:
rx = re.compile(pattern, flags)
except re.error as e:
return f"Invalid regex pattern: {e}"
matches = []
for fp in root.glob(file_glob):
if not fp.is_file():
continue
try:
with open(fp, "r", encoding="utf-8", errors="ignore") as f:
for i, line in enumerate(f, start=1):
if rx.search(line):
matches.append(f"{fp.as_posix()}:{i}: {line.strip()}")
if len(matches) >= max_matches:
return "\n".join(matches) + "\n\n[TRUNCATED: max_matches reached]"
except Exception:
continue
if not matches:
return "No matches found."
return "\n".join(matches)grep_files の実装において、さりげなくも重要な点がファイルの読み込み方法です。 read_text().splitlines()を使ってファイル全体をメモリに読み込むのではなく、 for line in open(fp) を使って遅延反復処理を行います。これにより、一度に1行ずつストリームが読み込まれ、ファイルサイズに関係なくメモリ使用量が一定に保たれます。
これは「まず検索、次に読み込み」という哲学に合致しています。つまり、すべてを事前に読み込むことなく、必要なものを見つけ出すということです。最高のパフォーマンスを求める読者のために、ノートブック全体版には、メモリマップドI/OやSIMD命令といったOSレベルの最適化を活用し、ripgrepまたはgrepを呼び出すgrep_files_os_basedバリアントも含まれています。実際には、このパターン(「まず検索し、次に範囲を読み込みます」)こそが、ファイルシステムエージェントが焦点を絞ったコーパスにおいて驚くほど強力に感じられる理由の一つです。エージェントは、単発の検索クエリに頼るのではなく、反復的にコンテキストを絞り込みます。
ツールはファイルとして出力します: 大きな JSON をプロンプトから除外します
コンテキストウィンドウを爆発させる最も早い方法の一つは、ツールから大きなJSONペイロードを返すことです。Cursorのアプローチは、これらの結果をファイルに書き出し、エージェントが必要に応じて(多くの場合、tailから開始して)それらを検査できるようにすることです。
これが、フォルダー構造に tool_outputs/<session_id>/ ディレクトリが含まれている理由です。このディレクトリは、エージェントが実行したすべての内容の「証拠ロッカー」のような役割を果たしますが、それらのペイロードを現在のコンテキストに強制的に適用することはありません。
{
"ts_utc" : "2026-01-27T12:41:12.135396+00:00" ,
"tool" : "arxiv_search_candidates" ,
"input" : "{'query': 'memgpt'}" ,
"output" : "content='[\\n {\\n \"arxiv_id\": \"2310.08560v2\",\\n \"entry_id\": \"http://arxiv.org/abs/2310.08560v2\",\\n \"title\": \"MemGPT: LLMをオペレーティングシステムとして活用する\",\\n \"authors\": \"Charles Packer, Sarah Wooders, Kevin Lin, Vivian Fang, Shishir G. Patil, Ion Stoica, Joseph E. Gonzalez\",\\n \"published\": \"2024-02-12\",\\n \"abstract\": ...msPnaMxOl8Pa'"
}まとめ:エージェントツールセット
エージェントを作成する前に、ツールを小さく構成可能なツールボックスにまとめます。これは、ツールサーフェスが小さく、選択肢の麻痺(コンテキストの混乱)が少なく、ツールスキーマの奇妙さや重複が少なく、実績のあるファイルシステムワークフローへの依存度が高いほど、エージェントのパフォーマンスが向上するという、より広範なトレンドと一致しています。
FS_TOOLS = [
arxiv_search_candidates, # arXiv で関連する研究論文を検索
fetch_and_save_paper, # 論文テキスト (PDF->text) を取得し、semantic/knowledge_base/<id>.md に保存
read_file, # ファイル全体を読み込む (控えめに使用)
tail_file, # 最初にファイルの末尾を読み込む
read_file_range, # 特定の行範囲を読み込む
conversation_to_file, # エピソード記憶に会話エントリを追加する
summarise_conversation_to_file, # トランスクリプトとコンパクトな要約を保存する
monitor_context_window, # トークンの使用量を推定する
list_papers, # 保存した論文を一覧表示
する grep_files # grep のようなファイル検索
]</ id >「ファイルシステムファースト」のシステムプロンプト:ポリシーが賢さに勝る
ファイルシステムツールだけでは不十分です。エージェントのトークン使用を効率的かつ安定的に保つための読み取りポリシーも必要です。これは、CLAUDE.md / AGENTS.md/ SKILLS.md、そして「ルールファイル」が重要である理由と同じです。これらは、セッション間で一貫して適用される手続き型メモリです。
以下にエンコードする主要なポリシーは次のとおりです。
- 大きな成果物(論文、ツールの出力、トランスクリプト)をディスクに保存します。
- 完全な読み取りよりも grep + 範囲の読み取りを優先します。
- 大きなファイルやログの場合は tail first を使用します。
- 実際に読んだ内容を明確にします(根拠を示す)。
以下は、langchain フレームワークを使用したエージェントの実装です。
fs_agent = create_agent(
model=f"openai:{os.getenv('OPENAI_MODEL', 'gpt-4o-mini')}",
tools=FS_TOOLS,
system_prompt=(
"You are a conversational research ingestion agent.\n\n"
"Core behavior:\n"
"- When asked to find a paper: use arxiv_search_candidates, pick the best arxiv_id, "
"then call fetch_and_save_paper to store the full text in semantic/knowledge_base/.\n"
"- Papers/knowledge base live in semantic/knowledge_base/.\n"
"- Conversations (transcripts) live in episodic/conversations/ (one file per run).\n"
"- Summaries live in episodic/summaries/.\n"
"- Conversation may be summarised externally; respect summary + transcript references.\n"
),
)ディスク上のメモリフットプリントの様子
エージェントを実行すると、エージェントの「メモリ」を具体的に確認できるディレクトリレイアウトが作成されます。この例では、エージェントは以下を生成します。
episodic/conversations/fsagent_session_0010.md— セッションの記録(エピソード記憶)episodic/tool_outputs/fsagent_session_0010/*.json— ツールの結果はファイルとして保存されます(証拠 + 再生)semantic/knowledge_base/*.md— 保存した書類(意味記憶)
これがまさにファイルシステムファーストメモリのポイントです。モデルは状態を魔法のように保持することで「記憶」するのではなく、以前の成果物を再度開き、検索し、選択的に読み取ることができるため「記憶」します。
これは、多くのチームが同じパターンを再発見し続ける理由でもあります。つまり、ファイルは単純な抽象化であり、エージェントは驚くほどそれを使いこなすことができるのです。
AIエージェントにおけるファイルシステムの利点
前のセクションでは、ファイルシステムファーストのメモリハーネスが実際にはどのように見えるかを示しました。エージェントは耐久性のあるアーティファクト(論文、ツールの出力、トランスクリプト)をディスクに書き込み、必要な部分だけを検索して選択的に読み取ることで「記憶」します。
このアプローチが機能するのは、LLMの2つの主要な制約、すなわち限られたコンテキストウィンドウと固有のステートレス性に直接対処しているためです。これらの制約に対処すると、初期のエージェントシステムにおいてファイルシステムがデフォルトのインターフェースとなることがよくある理由が明らかになります。
- 事前トレーニングのネイティブ インターフェース: LLM には大量のリポジトリ、ドキュメント、ログ、README 駆動型のワークフローが取り込まれているため、フォルダーとファイルは使い慣れた操作面になっています。
- シンプルなプリミティブ、強力な構成: 小さなアクション セット (リスト/読み取り/書き込み/検索) が、スキーマ、移行、またはクエリ プランニングを必要とせずに、洗練された動作を構成します。
- 段階的な開示によるトークンの効率: 検索で取得し、ドキュメント全体をプロンプトにダンプするのではなく、小さなスライス (スニペット、行範囲) を読み込みます。
- アーティファクトと証拠の自然な保存場所: トランスクリプト、中間結果、キャッシュされたドキュメント、ツールの出力はファイルとしてきれいに収まり、人間が検査できる状態のままです。
- デフォルトでデバッグ可能: ディレクトリを開いて、エージェントが何を保存したか、どのツールが返したか、エージェントが何を参照したかを正確に確認できます。
- 移植性: フォルダーは簡単にコピー、圧縮、比較、バージョン管理、他の場所での再生が可能で、デモ、再現性、ハンドオフに最適です。
- 運用オーバーヘッドが低い: PoC や MVP では、追加のインフラストラクチャをプロビジョニングせずに永続性と構造を実現できます。
実際には、ファイルシステムメモリは、ワークロードにアーティファクト(研究ノート、論文、トランスクリプトなど)が多い場合、明確な監査証跡が必要な場合、そして高度な検索よりも反復処理の速度が重要になる場合に最も効果的です。また、エージェントの衛生管理(出力を書き留め、出典を引用し、必要なものだけを読み込む)にも役立ちます。
AIエージェントにおけるファイルシステムの欠点
しかし残念ながら、それだけではありません。ファイルの魅力であるシンプルさ、比較的低コスト、そして迅速な実装といった強みは、これらのシステムを本番環境に導入すると、共有された信頼性の高いメモリプラットフォームのように動作することが期待されるため、たちまちボトルネックになる可能性があります。
エージェントがシングルユーザーのプロトタイプを超えて、同時読み取りと書き込みが標準で、負荷時の堅牢性が譲れない現実世界のシナリオに移行するとすぐに、ファイルシステムは限界を示し始めます。
- デフォルトでは弱い同時実行性保証:ロックを正しく実装しないと、複数のプロセスが書き込みを上書きしたり、インターリーブしたりする可能性があります。ロックを正しく実装した場合でも、ロックのセマンティクスはプラットフォームやネットワークファイルシステムによって異なります。
- ACIDトランザクションなし:アトミックなマルチステップ更新、書き込み間の分離、永続コミットセマンティクスは、これらを構築しなければ実現できません。部分的な書き込みや操作中の失敗により、メモリが不整合な状態になる可能性があります。
- 検索品質は通常脆弱です。キーワード/grep 形式の検索では、意味、同義語、言い換えが失われます。
- スケーリングは「1000 個のファイルによる死」になります。ディレクトリの肥大化、断片化されたアーティファクト、およびコストのかかるスキャンにより、特にフォルダー全体の検索を繰り返し行う場合は、メモリが増加するにつれてパフォーマンスが低下します。
- インデックス作成は DIY です。高速検索、重複排除、ランキング、最新性の重み付けが必要な場合は、独自のインデックスとメタデータ ストア (正直に言うと、基本的にはデータベースです) を管理することになります。
- メタデータとスキーマのドリフト:エージェントは必然的に余分なフィールド(ソースURL、タイムスタンプ、埋め込み、タグ)を蓄積します。これらのフィールドをファイル間で一貫性を保つことは、テーブルに制約を適用するよりも困難です。
- マルチユーザー/マルチエージェント間の連携が不十分:エージェント間でメモリを共有するということは、状態を共有することを意味します。中央コーディネータがなければ、競合状態、ビューの不整合、そして「真実の源」が不明確になります。
- 大規模でより厳しい監査: ファイルは人間が判読できますが、構造化されたログ、タイムスタンプ、クエリ可能な履歴がないと、多数の実行やスレッドにわたって「何が起こったか」を再構築するのは面倒になります。
- セキュリティとアクセス制御が粗雑です。権限はファイルシステムレベルであり、行レベルではありません。データの複製や認証レイヤーの追加なしに、「エージェントAはXを読み込めるが、Yは読み込めない」という設定を強制することは困難です。
コアパターンは、同時実行、セマンティック検索、または構造化保証の下での正確性が必要になるまでは、ファイルシステムメモリが魅力的であり続けるというものです。その時点で、制限を受け入れる(エージェントをシングルユーザー/シングルプロセスのままにする)か、データベースを導入するかのいずれかになります。
エージェントメモリ用データベース
ここまでくれば、ほとんどのAI開発者は、ファイルシステムファーストのエージェント実装がなぜ注目を集めているのか理解できるでしょう。使い慣れたインターフェースでプロトタイプ作成も容易であり、エージェントはアーティファクトをディスクに書き込み、後から検索と選択的な読み取りによって再読み込みすることで「記憶」することができます。ノートパソコンで作業する開発者1人であれば、これで十分な場合が多いでしょう。しかし、「自分のノートパソコンでは動作する」というレベルを超え、数千人、数百万人のユーザーに製品を提供する開発者をサポートし始めると、メモリは単なる便利なファイルのフォルダではなく、負荷がかかった状態でも予測可能な動作をしなければならない共有システムへと変化します。
データベースは、あまりにも多くの人やプロセスが同じデータにアクセスするようになり、「ファイルの山」では物足りなくなるまさにその瞬間のために作られました。データベースの起源として最もよく語られる話の一つは、アポロ計画時代に遡ります。IBMはパートナー企業と共同で、後にIMSとなるシステムを構築し、このプログラムの複雑な運用データを管理しました。初期バージョンは1968年にロックウェル宇宙部門に導入され、NASAを支援しました。重要なのは単なるストレージではありませんでした。多くの活動が同時に行われている中でも、調整、正確性、そして共有データの信頼性が重要だったのです。
同じ運用上の現実が、今日エージェント メモリをデータベースへと推進している理由です。
エージェント メモリが同時読み取りと書き込みを処理し、発生した事象の監査可能な履歴を保持し、多数のセッションにわたる高速取得をサポートし、一貫した更新を適用する必要がある場合、ベスト エフォートのファイル規則ではなく、データベースの保証が必要になります。
Oracleは、1979年に最初の商用SQLデータベースを出荷して以来、まさにこれらの問題を解決してきました。当時の目標は現在と変わらず、共有状態の信頼性、移植性、そして負荷下でも信頼性の高いものにすることです。
それでは、これが実際にどのように機能するかをお見せしましょう。
データベースファースト研究アシスタント
ファイルシステムの最初のセクションでは、リサーチアシスタントはアーティファクトをディスクに書き込んで「記憶」し、後で安価な検索と選択的な読み取りを用いて再読み込みすることで、そのアーティファクトを記憶していました。これは素晴らしい出発点です。しかし、共有され、クエリ可能で、同時使用下でも信頼性の高いメモリが必要な場合は、別の基盤が必要です。
このエージェントのイテレーションでは、ユーザーエクスペリエンスと高レベルのジョブはこれまでと同じままです。arXivの検索、論文の取り込み、フォローアップの質問への回答、そしてセッション間の継続性を維持します。違いは、メモリがOracle AI Databaseに保存されるようになったことです。これにより、メモリは耐久性、インデックス化、フィルタリングが可能になり、同時読み取り・書き込みに対して安全なものになります。また、SQLテーブル内の構造化履歴とベクトル検索によるセマンティックリコールという2つのメモリサーフェスを明確に分離しています。
その結果、MemAgentと呼ばれるエージェントが誕生しました。これは、メモリがアーティファクトのフォルダではなく、クエリ可能なシステムであるエージェントです。マルチスレッドセッションのサポート、完全な会話履歴の保存、デバッグと監査のためのツールログの保存、そしてキーワードではなく意味で検索できるセマンティック知識ベースの保存が可能です。
MemAgentで利用可能なツール
エージェントループを接続する前に、MemAgent が知識の推論、取得、そして永続化に使用できるツールサーフェスを定義する必要があります。ここでの設計目標はファイルシステムファーストのアプローチと同様です。ツールセットを小さく構成可能な状態に保ちながら、メモリ基盤をファイルからデータベースへと移行します。MemAgent は、フォルダを grep して行範囲を読み取る代わりに、ベクトル類似度検索を用いて意味的に関連するコンテキストを取得し、学習内容をセッション間でクエリ可能かつ信頼性の高い方法で永続化します。
実際には、それは 2 つのことを意味します。
- まず、取り込みツールはコンテンツを単に「取得」するだけでなく、後で検索できるようにコンテンツをチャンク化して埋め込みます。
- 2 番目に、検索ツールはキーワードベースではなく意味ベースであるため、ユーザーが言い換えたり、同義語を使用したり、より高レベルの概念的な質問をしたりする場合でも、エージェントは関連する文章を見つけることができます。
以下の表は、MemAgent に公開する最小限のツールセットと、各ツールが出力を保存する場所をまとめたものです。

FSAgentとMemAgentは、どちらも論文を取り込み、質問に答え、継続性を維持できるため、外見上は似ているように見えるかもしれません。違いは、その継続性を支える仕組みと、システムが成長したときに検索がどのように機能するかにあります。
FSAgentはメモリサーフェスとしてオペレーティングシステムに依存しており、これは反復処理の速度と人間による検査可能性に優れていますが、通常はキーワード形式の検出とファイルトラバーサルに依存しています。MemAgentはメモリをデータベースの問題として扱うため、セットアップのオーバーヘッドは増加しますが、インデックスによる検索、同時実行におけるより強力な保証、エージェントが学習した情報のクエリとフィルタリングのためのより豊富な方法を実現します。

LangChainとOracle AI Databaseを使用したデータストアの作成
テーブルとベクトルストアの定義を始める前に、使用するスタックとその理由を明確にしておくことが重要です。この実装では、特注のエージェントフレームワークをゼロから構築するわけではありません。
LangChain を LLM フレームワークとして使用し、エージェント ループ、ツールの呼び出し、およびメッセージ処理を抽象化します。次に、推論と生成のためのモデル プロバイダーと組み合わせ、構造化された履歴とセマンティック埋め込みの両方を保存する統合メモリ コアとして Oracle AI Database と組み合わせます。
この分離は、本番環境のエージェントシステムの一般的な構築方法を反映しているため重要です。エージェントロジックは急速に進化し、モデルは交換可能であり、メモリ層は信頼性とクエリ可能性を維持する必要があります。
これをエージェントスタックと考えてください。各レイヤーには明確な役割があり、それらが組み合わさって、構築が容易で、かつ拡張性に優れたエージェントが構築されます。
- モデルプロバイダー (OpenAI) : 推論、応答、ツールの決定を生成します。
- LLM フレームワーク (LangChain) : エージェントの抽象化、ツールの配線、およびランタイム オーケストレーションを提供します。
- 統合メモリ コア (Oracle AI データベース) : 永続的な会話メモリを SQL に格納し、セマンティック メモリをベクトル インデックスに格納します。
このスタックが準備できたら、最初のステップはOracleデータベースに接続し、埋め込みモデルを初期化するだけです。データベース接続はすべてのメモリ操作の基盤として機能し、埋め込みモデルによってベクターストア層を通じて知識をセマンティックに保存および取得できるようになります。
def connect_oracle(user, password, dsn="127.0.0.1:1521/FREEPDB1", program="langchain_oracledb_demo"):
return oracledb.connect(user=user, password=password, dsn=dsn, program=program)
database_connection = connect_oracle(
user="VECTOR",
password="VectorPwd_2025",
dsn="127.0.0.1:1521/FREEPDB1",
program="devrel.content.filesystem_vs_dbs",
)
print("Using user:", database_connection.username)
embedding_model = HuggingFaceEmbeddings(
model_name="sentence-transformers/paraphrase-mpnet-base-v2"
)次に、エージェントのメモリを保存するためのデータベーススキーマを定義し、デモ用のクリーンな状態を準備します。メモリを個別のテーブルに分割することで、各タイプを適切に管理、インデックス付け、クエリできるようになります。
LangChainエコシステムへのOracle Database統合のインストールは簡単です。pipコマンド1つで環境に追加できます。
pip install -U langchain-oracledb
会話履歴とログは当然表形式で保存されますが、セマンティックメモリとサマリーメモリはOracleVSを介してベクトルベースのテーブルに保存されます。再現性を確保するため、以前の実行から既存のテーブルを削除することでノートブックを決定論的なものにし、ウォークスルーを再実行した際に混乱を招く結果を回避することができます。
from langchain_oracledb.vectorstores import OracleVS
from langchain_oracledb.vectorstores.oraclevs import create_index
from langchain_community.vectorstores.utils import DistanceStrategy
CONVERSATIONAL_TABLE = "CONVERSATIONAL_MEMORY"
KNOWLEDGE_BASE_TABLE = "SEMANTIC_MEMORY"
LOGS_TABLE = "LOGS_MEMORY"
SUMMARY_TABLE = "SUMMARY_MEMORY"
ALL_TABLES = [
CONVERSATIONAL_TABLE,
KNOWLEDGE_BASE_TABLE,
LOGS_TABLE,
SUMMARY_TABLE
]
for table in ALL_TABLES:
try:
with database_connection.cursor() as cur:
cur.execute(f"DROP TABLE {table} PURGE")
except Exception as e:
if "ORA-00942" in str(e):
print(f" - {table} (not exists)")
else:
print(f" [FAIL] {table}: {e}")
database_connection.commit()
ベクトルストアとHNSWインデックスを作成する
このセクションでは、エージェントの文脈における「ベクトルストア」が実際には何なのかを説明する価値があります。ベクトルストアとは、メタデータと共に埋め込み情報を永続化し、類似検索をサポートするストレージシステムです。これにより、エージェントはキーワードではなく意味に基づいてアイテムを検索できます。
エージェントは、「どのファイルにこの正確なフレーズが含まれているか」を尋ねる代わりに、「どのチャンクが私の質問に意味的に最も近いか」を尋ね、最も一致するものを取得します。
実際には、これは通常、近似最近傍インデックスを意味します。これは、知識ベースが拡大するにつれて、すべてのベクトルをスキャンすることは非常にコストがかかるためです。HNSWは、このスタイルの検索における最も一般的なインデックス作成手法の1つです。
以下のコードでは、langchain_oracledb モジュール OracleVS を使用して 2 つのベクトル ストアを作成します。1 つはナレッジ ベース用、もう 1 つはサマリー用で、どちらもコサイン距離を使用します。
2 番目に、メモリが増えても類似性検索が高速に維持されるように HNSW インデックスを構築します。これは、リサーチ アシスタントが多数の論文を取り込み、長期間存続するスレッドを実行し始めたときにまさに必要なことです。
knowledge_base_vs = OracleVS(
client=database_connection,
embedding_function=embedding_model,
table_name=KNOWLEDGE_BASE_TABLE,
distance_strategy=DistanceStrategy.COSINE,
)
summary_vs = OracleVS(
client=database_connection,
embedding_function=embedding_model,
table_name=SUMMARY_TABLE,
distance_strategy=DistanceStrategy.COSINE,
)
def safe_create_index(conn, vs, idx_name):
try:
create_index(
client=conn,
vector_store=vs,
params={"idx_name": idx_name, "idx_type": "HNSW"}
)
print(f" Created index: {idx_name}")
except Exception as e:
if "ORA-00955" in str(e):
print(f" [SKIP] Index already exists: {idx_name}")
else:
raise
print("Creating vector indexes...")
safe_create_index(database_connection, knowledge_base_vs, "kb_hnsw_cosine_idx")
safe_create_index(database_connection, summary_vs, "summary_hnsw_cosine_idx")
print("All indexes created!")メモリマネージャー
以下のコードでは、カスタムメモリマネージャを作成します。メモリマネージャは、生のデータベース操作を「エージェントのメモリ動作」に変換する抽象化レイヤーです。この部分によって、データベースファーストエージェントの理解が容易になります。
- SQLメソッドは会話履歴を保存および読み込みます
thread_id - ベクトル法は類似性検索によって意味記憶を保存および取得する
- 要約メソッドは圧縮されたコンテキストを保存し、コンテキストの制限に近づいたときにワーキングセットを回転させる。
from langchain.tools import tool
from typing import List, Dict
class MemoryManager:
"""
A simplified memory manager for AI agents using Oracle AI Database.
"""
def __init__(self, conn, conversation_table: str, knowledge_base_vs, summary_vs, tool_log_table):
self.conn = conn
self.conversation_table = conversation_table
self.knowledge_base_vs = knowledge_base_vs
self.summary_vs = summary_vs
self.tool_log_table = tool_log_table
def write_conversational_memory(self, content: str, role: str, thread_id: str) -> str:
thread_id = str(thread_id)
with self.conn.cursor() as cur:
id_var = cur.var(str)
cur.execute(f"""
INSERT INTO {self.conversation_table} (thread_id, role, content, metadata, timestamp)
VALUES (:thread_id, :role, :content, :metadata, CURRENT_TIMESTAMP)
RETURNING id INTO :id
""", {"thread_id": thread_id, "role": role, "content": content, "metadata": "{}", "id": id_var})
record_id = id_var.getvalue()[0] if id_var.getvalue() else None
self.conn.commit()
return record_id
def load_conversational_history(self, thread_id: str, limit: int = 50) -> List[Dict[str, str]]:
thread_id = str(thread_id)
with self.conn.cursor() as cur:
cur.execute(f"""
SELECT role, content FROM {self.conversation_table}
WHERE thread_id = :thread_id AND summary_id IS NULL
ORDER BY timestamp ASC
FETCH FIRST :limit ROWS ONLY
""", {"thread_id": thread_id, "limit": limit})
results = cur.fetchall()
return [{"role": str(role), "content": content.read() if hasattr(content, 'read') else str(content)} for role, content in results]
def mark_as_summarized(self, thread_id: str, summary_id: str):
thread_id = str(thread_id)
with self.conn.cursor() as cur:
cur.execute(f"""
UPDATE {self.conversation_table}
SET summary_id = :summary_id
WHERE thread_id = :thread_id AND summary_id IS NULL
""", {"summary_id": summary_id, "thread_id": thread_id})
self.conn.commit()
print(f" Marked messages as summarized (summary_id: {summary_id})")
def write_knowledge_base(self, text: str, metadata_json: str = "{}"):
metadata = json.loads(metadata_json)
self.knowledge_base_vs.add_texts([text], [metadata])
def read_knowledge_base(self, query: str, k: int = 5) -> str:
results = self.knowledge_base_vs.similarity_search(query, k=k)
content = "\n".join([doc.page_content for doc in results])
return f"""## Knowledge Base Memory: This are general information that is relevant to the question
### How to use: Use the knowledge base as background information that can help answer the question
{content}"""
def write_summary(self, summary_id: str, full_content: str, summary: str, description: str):
self.summary_vs.add_texts(
[f"{summary_id}: {description}"],
[{"id": summary_id, "full_content": full_content, "summary": summary, "description": description}]
)
return summary_id
def read_summary_memory(self, summary_id: str) -> str:
results = self.summary_vs.similarity_search(
summary_id,
k=5,
filter={"id": summary_id}
)
if not results:
return f"Summary {summary_id} not found."
doc = results[0]
return doc.metadata.get('summary', 'No summary content.')
def read_summary_context(self, query: str = "", k: int = 5) -> str:
results = self.summary_vs.similarity_search(query or "summary", k=k)
if not results:
return "## Summary Memory\nNo summaries available."
lines = ["## Summary Memory", "Use expand_summary(id) to get full content:"]
for doc in results:
sid = doc.metadata.get('id', '?')
desc = doc.metadata.get('description', 'No description')
lines.append(f" - [ID: {sid}] {desc}")
return "\n".join(lines)次にそれをインスタンス化します。
memory_manager = MemoryManager(
conn=database_connection,
conversation_table=CONVERSATION_HISTORY_TABLE,
knowledge_base_vs=knowledge_base_vs,
tool_log_table=TOOL_LOG_TABLE,
summary_vs=summary_vs
)ツールとエージェントの作成
データベースファースト エージェントは、シンプルで本番環境に適したパターンに従います。
- スレッドまたは実行 ID とタイムスタンプを含むユーザー メッセージとアシスタント メッセージを含む、すべての会話ターンを構造化された行として保持するため、セッションは回復可能で、追跡可能であり、再起動後も一貫性が保たれます。
- ドキュメントをチャンク化し、埋め込みを生成し、メタデータとともに保存することで、ベクトル対応ストアに長期的な知識を保存します。これにより、コーパスが拡大しても、検索はセマンティックかつランク付けされ、高速になります。
- ツール名、入力、出力、ステータス、エラー、および主要なメタデータをキャプチャするファーストクラスのレコードとしてツールアクティビティを保持するため、エージェントの動作を検査、再現、および監査できます。
さらに、エージェントはコンテキストを積極的に管理します。つまり、トークンの使用状況を追跡し、古いダイアログと中間状態を定期的に永続的な要約(および/または「メモリ」テーブル)にまとめます。そのため、作業プロンプトは小さいまま、完全な履歴をオンデマンドで利用できるようになります。
論文を知識ベースベクターストアに取り込む
これは「論文を取得して保存する」のデータベースファースト版です。マークダウンファイルを書く代わりに、以下の3つのステップを実行します。
- arXivから論文テキストを読み込む
- 埋め込みモデルの制限を尊重するためにチャンク化する
- メタデータを含むチャンクをベクトルストアに保存することで、後で高速なセマンティック検索が可能になります。
from datetime import datetime, timezone
from langchain_core.tools import tool
from langchain_community.document_loaders import ArxivLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
@tool
def fetch_and_save_paper_to_kb_db(
arxiv_id: str,
chunk_size: int = 1500,
chunk_overlap: int = 200,
) -> str:
loader = ArxivLoader(
query=arxiv_id,
load_max_docs=1,
doc_content_chars_max=None,
)
docs = loader.load()
if not docs:
return f"No documents found for arXiv id: {arxiv_id}"
doc = docs[0]
title = (
doc.metadata.get("Title")
or doc.metadata.get("title")
or f"arXiv {arxiv_id}"
)
entry_id = doc.metadata.get("Entry ID") or doc.metadata.get("entry_id") or ""
published = doc.metadata.get("Published") or doc.metadata.get("published") or ""
authors = doc.metadata.get("Authors") or doc.metadata.get("authors") or ""
full_text = doc.page_content or ""
if not full_text.strip():
return f"Loaded arXiv {arxiv_id} but extracted empty text (PDF parsing issue)."
splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
)
chunks = splitter.split_text(full_text)
ts_utc = datetime.now(timezone.utc).isoformat()
metadatas = []
for i in range(len(chunks)):
metadatas.append(
{
"source": "arxiv",
"arxiv_id": arxiv_id,
"title": title,
"entry_id": entry_id,
"published": str(published),
"authors": str(authors),
"chunk_id": i,
"num_chunks": len(chunks),
"ingested_ts_utc": ts_utc,
}
)
knowledge_base_vs.add_texts(chunks, metadatas)
return (
f"Saved arXiv {arxiv_id} to {KNOWLEDGE_BASE_TABLE}: "
f"{len(chunks)} chunks (title: {title})."
)以下の 2 つのツールをさらに作成します。
- search_knowledge_base(query, k=5) : データベースに裏付けられた知識ベースに対して意味的類似性検索を実行し、最も関連性の高い上位k個のチャンクを返します。これにより、エージェントは正確なキーワードではなく、意味によってコンテキストを取得できます。
- store_to_knowledge_base(text, metadata_json=”{}”) : 新しいテキストをナレッジベースに保存し、メタデータ (JSON として) を添付します。メタデータは埋め込まれ、インデックス化されるため、将来のクエリで検索可能になります。
import os
from langchain.tools import tool
@tool
def search_knowledge_base(query: str, k: int = 5) -> str:
return memory_manager.read_knowledge_base(query, k)
@tool
def store_to_knowledge_base(text: str, metadata_json: str = "{}") -> str:
memory_manager.write_knowledge_base(text, metadata_json)
return "Successfully stored text to knowledge base."ここで、データベース ファースト ツールを使用して LangChain エージェントを構築します。
from langchain.agents import create_agent
MEM_AGENT = create_agent(
model=f"openai:{os.getenv('OPENAI_MODEL', 'gpt-4o-mini')}",
tools=[search_knowledge_base, store_to_knowledge_base, arxiv_search_candidates, fetch_and_save_paper_to_kb_db],
)結果比較: FSAgent vs MemAgent: エンドツーエンドベンチマーク (レイテンシ + 品質)
ここまでくれば、ファイルシステムエージェントとデータベースベースのエージェントの違いは、哲学的な議論というより、むしろエンジニアリング上のトレードオフのように感じられるはずです。どちらのアプローチも、状態を永続化し、コンテキストを取得し、追加の質問に答えることができるという意味で「記憶」できます。真の試練は、整理されたラップトップのデモを終え、実運用環境、つまりより大規模なコーパス、より曖昧なクエリ、そして同時実行ワークロードに直面したときに何が起こるかです。
これを具体的にするために、エンドツーエンドのベンチマークを実行し、クエリごとのエージェント ループ全体 (取得、コンテキストのアセンブリ、ツールの呼び出し、モデルの呼び出し、最終的な回答) を 3 つのシナリオにわたって測定しました。
- 小規模コーパス検索:最小限のコンテキストでベースライン検索と回答合成を検証するための、タイトでキーワードに適したデータセット。
- 大規模コーパス検索: 大規模な検索品質とコンテキスト効率を重視した、より言い換えの多様性に富んだ大規模なデータセット。
- 同時書き込み整合性:同時読み取り/書き込み時の正確性 (整合性、競合状態、スループット) を評価するマルチワーカー ストレス テスト。
FSAgent vs MemAgent: エンドツーエンドベンチマーク(レイテンシ + 品質)

上の画像に示されている結果から、2つの結論がすぐに浮かび上がります。1つ目は、レイテンシと回答の質です。
私たちの実行では、MemAgent は FSAgent よりもエンドツーエンドで概ね高速に終了しました。「データベースはオーバーヘッド」と考えると、直感に反するように聞こえるかもしれませんが、実際そうである場合もあります。
しかし、エージェントループは生のストレージプリミティブによって支配されているわけではありません。重要なのは、適切な情報をいかに速く見つけられるか、そしてモデルに不要なコンテキストをいかに少なく押し込めるか、つまりコンテキストエンジニアリングです。セマンティック検索は、より関連性の高いチャンクを少数返す傾向があります(これは検索パイプラインのチューニングに依存します)。つまり、スキャン回数、ファイルのページング回数、そして無関係なテキストに費やされるトークンの量が減ります。
この特定の実行では、両エージェントは同程度の質の回答を生成しました。これは驚くべきことではありません。質問が検索しやすいもので、コーパスが十分に小さい場合、どちらのアプローチも適切な文章を見つけることができます。FSAgentはキーワード検索と注意深い読解によって目的を達成します。MemAgentは埋め込まれたチャンクの類似性検索によって目的を達成します。道は違えど、目的地は似ています。
ここで、あるニュアンスに焦点を当てる価値があると思います。走査する情報が文字数的に最小限で、クエリがキーワードフレンドリーである場合、両方のエージェントの検索品質は収束する傾向があります。
その規模では「検索」はほとんど問題にならないため、検索基盤ではなく、モデルの読み取りと統合能力が支配的な要因となります。このギャップは、コーパスが大きくなり、表現が曖昧になり、ノイズ、言い換え、同時実行といった現実世界の制約下でシステムが確実に検索を行う必要が生じたときに初めて広がり始めます。そして最終的には、システムは確実に検索を実行します。
「LLM-裁判官」指標について
また、LLMを審査員として用いるプロンプトを用いて回答を採点しました。これは、ラベル付けされたグラウンドトゥルースがない場合に方向性を示すフィードバックを得るための実用的な方法ですが、万能薬ではありません。審査員はプロンプトの言い回しに敏感になり、流暢さを過度に評価し、微妙な基礎づけの失敗を見逃してしまう可能性があります。
これを本番環境向けに構築する場合は、LLM の審査をゴールではなくスタートラインとして捉えてください。より信頼性の高いアプローチは、以下の要素を組み合わせることです。
- ルーブリック評価、完全一致、F1 スタイルのスコアリングなど、グラウンド トゥルースがある場合の参照ベースの評価。
- コンテキスト精度と再現率、回答の忠実性、根拠性など、コンテキストが重要となる状況において、検索を考慮した評価を行います。
トレースと評価ツールを組み合わせることで、失敗を、その原因となった特定の検索、ツール呼び出し、コンテキストアセンブリの決定に結び付けることができます。
軽量な判定でも、方向性のストーリーは一貫しています。検索が困難になり、システムが混雑するにつれて、データベースに裏付けられたメモリのパフォーマンスが向上する傾向があります。
大規模コーパスベンチマーク:データの増加とともにギャップが広がる理由
大規模コーパステストは、キーワード優先記憶の弱点を的確に捉えるように設計されています。コーパスを拡大し、クエリの「完全一致」を少なくすることで、検索問題を意図的に困難にしました。
連結されたコーパスを持つFSAgent
多数の論文を大きなマークダウンファイルに統合すると、FSAgentはgrepスタイルの検索と、適切なセクションをコンテキストウィンドウにページングする処理に依存するようになります。この方法でも問題なく動作しますが、コーパスが大きくなるにつれて不安定になります。
- ユーザーが言い換えたり同義語を使用したりすると、正確なキーワード一致が失敗する可能性があります。
- キーワードが一般的すぎると、ヒット数が多すぎて、エージェントが手動でふるいにかける必要が生じます。
- 不確実な場合、エージェントは「念のため」より大きなスライスをロードすることが多く、これによりトークン数、レイテンシ、コンテキスト希釈のリスクが増加します。
チャンク化された埋め込みメモリを備えた MemAgent
チャンク化と埋め込みにより、検索がより寛容になり、より安定します。
- ユーザーはソースのフレーズと正確に一致する必要はありません。
- エージェントは、コンテキストを厳密に保ちながら、類似度の高いチャンクの小さなセットを取得できます。
- ファイルを繰り返しスキャンするのではなく、インデックス検索はメモリが増加しても予測可能なままです。
物語の要点はシンプルです。コーパスが小さく、クエリがキーワードフレンドリーな場合には、ファイルシステムは最適です。コーパスが大きくなり、質問が曖昧になるにつれて、セマンティック検索が差別化要因となり、データベースに裏付けられたメモリがより信頼性の高いデフォルトになります。
品質の差は規模とともに拡大します。少数の文書であれば、grep は総当たり方式で合理的な答えを導き出すことができます。エージェントは一致するキーワードを見つけ、周囲の文脈を引き出し、応答します。
しかし、同じ情報を数百のファイルに散在させると、キーワード検索では全体像を見失い、ユーザーのフレーズが原文と逐語的に一致しない場合、浅いヒットが多すぎたり、ヒットが全く返されなかったりします。一方、セマンティック検索は、語彙が異なっていても概念的に関連性の高いチャンクを表示します。その結果、検索速度が速くなるだけでなく、幻覚的なギャップが少なく、より一貫性のある回答が得られます。これは、大規模コーパスベンチマークにおけるLLMジャッジの評価からも明らかです。FSAgentは29.7%のスコアを獲得したのに対し、MemAgentは87.1%のスコアを獲得しました。

同時実行テスト: 本番環境ですぐにわかること
ファイルシステムメモリの真の限界点は、ほとんどの場合、取得ではなく同時実行にあることが分かりました。
同時書き込みで同じワークロードの 3 つのバージョンを実行しました。
- ロックのないファイルシステム。複数のワーカーが同じファイルに追加します。
- ロック機能を備えたファイルシステム。書き込みはファイル ロックによって保護されます。
- トランザクションを備えた Oracle AI データベース。複数のワーカーが ACID 保証の下で行を書き込みます。
次に、次の 2 つの点を測定しました。
- 整合性、つまり破損のないエントリの予想数を取得したかどうか。
- 実行時間。バッチがエンドツーエンドでかかった時間を意味します。

私たちが観察したことは、多くのチームが苦労して発見したことと一致しています。
単純なファイルシステムへの書き込みは高速であっても、誤りが生じる可能性があります。
ロックがなければ、同時書き込みは最終的に互いに競合します。スループットは良好でも、メモリエントリが失われる可能性があります。エージェントの「メモリ」が下流の推論に使用されている場合、サイレントロスはパフォーマンスの問題ではなく、正確性の問題です。
ロックは整合性を保てますが、今度は正確性を確保するのがあなたの仕事です。
明示的なロックを使用することで、ファイルシステムへの書き込みを安全に行うことができます。しかし、複雑さは引き継がれます。ロックのスコープ、ロックの競合、プラットフォームの違い、ネットワークファイルシステムの挙動、そして障害からの回復など、すべてがエージェントエンジニアリングの作業の一部となります。
データベースは正確性をデフォルトとしています。
トランザクションと分離性はまさにデータベースが設計された目的です。確かにオーバーヘッドはあります。しかし、重要な違いは、本番環境でインシデントが発生した後に正確性を後から追加するのではなく、共有状態を保護することを目的としたシステムから始めるということです。
もちろん、ファイルロックのアプローチを採用したり、アトミック書き込みを追加したり、先行書き込みログを構築したり、再試行と回復のロジックを導入したり、高速検索のためにインデックスを維持したり、メタデータを標準化して確実にクエリを実行したりすることもできます。
しかし、最終的には、データベースをまったく「回避」していないことに気付くでしょう。
保証が少なくなり、エッジケースが増えただけで、再構築が完了しました。
結論: AI開発者にとって最適な選択肢はあるか
これは「ファイル」と「データベース」の間の宗教戦争ではありません。何を最適化するのか、そしてどのような障害モードを許容するのかという問題です。
シングルユーザーまたはシングルライターのプロトタイプを構築する場合、ファイルシステムメモリは最適なデフォルトです。シンプルで透明性が高く、反復処理も高速です。フォルダを開いてエージェントが保存した内容を正確に確認し、差分やバージョン管理を行い、テキストエディタだけで再生できます。
マルチユーザー エージェント、バックグラウンド ワーカー、または大規模に出荷する予定のものを構築する場合、そのシナリオでは、データベースでバックアップされたメモリ ストアがより安全な基盤となります。
この段階では、単純さよりも、同時実行性、整合性、ガバナンス、アクセス制御、監査可能性が重要になります。現実的な妥協案としては、ハイブリッド設計が挙げられます。つまり、成果物と開発者ワークフローについてはファイルのようなエルゴノミクスを維持しながら、正確性を保証できるデータベースに耐久性のあるメモリを保存するというものです。
本番環境でファイルシステム専用メモリの使用を強く求めるなら、ロック、アトミック書き込み、リカバリ、インデックス作成、メタデータ管理を最上級のエンジニアリング作業として扱うべきです。なぜなら、これらを真剣に取り組んだ瞬間から、もはや「ただファイルを使っている」のではなく、データベースを再構築していることになるからです。
最後に指摘しておく価値のある罠は、多言語での持続性です。
多くのAIスタックは、アンチパターンに陥ります。埋め込みにはベクターDB、JSONにはNoSQL DB、リレーションシップにはグラフDB、トランザクションにはリレーショナルDBといった具合です。それぞれの製品は「それぞれの得意分野に特化している」ように見えますが、実際には4つのデータベース、4つのセキュリティモデル、4つのバックアップ戦略、4つのスケーリングプロファイル、そして4つの連鎖的な障害ポイントを運用していることに気づきます。
調整は負担になります。エージェントにとってシステムが統一されていると感じられるようにするためだけに、グルーコードや同期パイプラインを構築することになります。だからこそ、エージェントシステムでは統合アプローチが重要なのです。プロダクションメモリはベクトルを保存するだけでなく、運用履歴、アーティファクト、メタデータ、そしてセマンティクスを一貫した保証の下で保存するのです。
AI開発者にとって、アプリケーションは複数のストレージエンジンの統合レイヤーとして機能します。これらのストレージエンジンはそれぞれアクセスパターンと操作セマンティクスが異なります。エージェントにとってシステムが統一されていると感じられるようにするためだけに、グルーコード、同期パイプライン、そして調整ロジックを構築することになります。
もちろん、本番環境のデータは本質的に異種混合です。構造化テキスト、半構造化テキスト、非構造化テキスト、埋め込みデータ、JSONドキュメント、そして関係性を重視したデータなど、様々なデータを扱うことになります。
重要なのは、「1つのモデルが勝つ」ということではありません。
重要なのは、データ管理、信頼性、インデックス作成、ガバナンス、クエリ可能性の基礎を理解すると、AI インフラストラクチャを緩く調整されたサブシステムの集合に変えることなく、これらのフォームを保存および取得できるプラットフォームが必要になるということです。
これが、Oracleの統合データベース・アプローチの根底にある哲学です。このアプローチは、単一のエンジン内で複数のデータ型とワークロードをネイティブにサポートするように設計されています。エージェントの世界では、これは実用的な利点となります。なぜなら、Oracleをオペレーショナル・メモリ(履歴とログ用のSQLテーブル)とセマンティック・メモリ(取得用のベクトル検索)の両方の統合メモリ・コアとして使用できるからです。
よくある質問
- AIエージェントメモリとは?
AIエージェントメモリとは、AIエージェントが時間の経過とともに情報を保存、呼び出し、更新できるようにするシステムコンポーネントと技術のセットです。LLMは本質的にステートレスであるため(以前のセッションを記憶する機能が組み込まれていないため)、エージェントメモリは、エージェントが会話全体にわたって継続性を維持し、過去のインタラクションから学習し、ユーザーの好みに適応することを可能にする永続性レイヤーを提供します。 - AIエージェントのメモリにはファイルシステムとデータベースのどちらを使うべきでしょうか?
ユースケースによって異なります。ファイルシステムは、シングルユーザーのプロトタイプ、アーティファクトを多用するワークフロー、迅速な反復処理に優れています。シンプルで透明性が高く、LLMの自然な動作と整合しています。一方、同時アクセス、ACIDトランザクション、セマンティック検索、複数のエージェントやユーザー間での状態共有が必要な場合は、データベースが不可欠になります。多くの実稼働システムでは、エージェントとのインタラクションにはファイルのようなインターフェースを使用し、その下でデータベースによる保証を提供するというハイブリッドなアプローチが採用されています。 - 長期記憶を持つAIエージェントをどのように構築すればよいでしょうか?
まず、記憶の種類を作業記憶(現在のコンテキスト)、意味記憶(知識ベース)、エピソード記憶(インタラクション履歴)、手続き記憶(行動ルール)に分類することから始めます。ストレージを実装します。プロトタイプ用のファイルシステムと本番環境用のデータベースです。エージェントが呼び出せる検索ツールを追加します。過去のコンテキストを圧縮するための要約機能を構築します。エージェントが以前の会話から情報を思い出す必要があるマルチセッションシナリオでテストします。 - AIエージェントにおける意味記憶、エピソード記憶、手続き記憶とは何でしょうか?認知科学から借用されたこれらの用語は、エージェントの記憶の異なるタイプを表します。意味記憶は、永続的な知識や事実(保存された文書や参考資料など)を保存します。エピソード記憶は、経験やインタラクション履歴(会話の記録、ツールの出力など)を記録します。手続き記憶は、エージェントがどのように行動すべきかをエンコードします。つまり、指示、ルール、CLAUDE.mdのようなファイル、そしてセッション全体にわたって行動を形成する学習されたワークフローです。
- AIアプリケーションに最適なデータベースとは?
最適なデータベースは要件によって異なります。特にAIエージェントのメモリには、セマンティック検索のためのベクトル検索機能、履歴とメタデータのためのSQLまたは構造化クエリ、複数のエージェントが状態を共有する場合のACIDトランザクション、そしてメモリコーパスの増大に応じた拡張性が必要です。これらの機能を組み合わせた統合データベース(Oracle AI Databaseなど)は、個別の専用システムを実行する場合と比較して、運用の複雑さを軽減します。
コメント
コメントを投稿