LangChainでRAG(検索拡張生成)を実装する記事は数多くありますが、その大半は v0.2 系の書き方で止まっています。2026年8月時点の最新版は langchain 1.3.15 で、v1.0 のパッケージ再編によって langchain.chains は langchain 本体から消えました。ここでは v1.3 系で実行して確認したコードを使い、社内文書を検索して答えるRAGを組み立てる手順と、0.x 系の記事をそのまま写すと詰まる箇所を整理します。
まとめ
LangChainのRAGは「文書を分割してベクトルストアに入れる」「検索して回答させる」の2工程です。v1.3 系で押さえるべき点は4つあります。
- パッケージ構成:本体は
langchain1.3.15、分割はlangchain-text-splitters、埋め込みとLLMはプロバイダ別パッケージ(langchain-openai等)。langchain単体では埋め込みモデルを呼べません。 - 移行:
from langchain.chains import create_retrieval_chainは 1.3.15 でModuleNotFoundErrorになります。langchain-classicを追加してlangchain_classic.chainsから読み込みます。 - 日本語の分割:
RecursiveCharacterTextSplitterの既定 separators は日本語の句点を区切りに使わないため、文の途中で切れます。separatorsに「。」「、」を追加します。 - 回答生成の方式:検索をツールとしてエージェントに渡す
create_agent方式と、検索結果を必ず1回渡すcreate_retrieval_chain方式があり、用途で選び分けます。
以下、実行結果を示せる工程は出力付きで見ていきます。
RAGとLangChainの役割分担
RAGがLLM単体と違う点
LLMは学習データに含まれない情報を持ちません。社内規程や自社製品の仕様を尋ねると、もっともらしい文章を返しますが根拠がありません。RAGは質問を受けた時点で自社データを検索し、その本文をプロンプトに差し込んでから回答させる方式です。回答の根拠が検索結果に限定されるため、出典の提示と更新が可能になります。モデルを再学習させないので、規程が改訂された場合は検索対象の文書を差し替えるだけで反映されます。
LangChainが担う範囲
RAGそのものは特定のライブラリに依存しない構成です。LangChainが引き受けるのは、文書の読み込みと分割、埋め込みモデルとベクトルストアの共通インターフェース、LLM呼び出しとツール実行のループという定型部分です。検索エンジンの中身(ベクトルDBや全文検索エンジン)はLangChainの外側にあり、VectorStore の実装を差し替える形で接続します。つまりLangChainは配線を担当し、検索精度そのものはベクトルストアと埋め込みモデルの選択で決まります。
ファインチューニングとの使い分け
ファインチューニングは応答の形式や語調をモデルに覚えさせる手法で、事実を最新に保つ用途には向きません。更新頻度のある事実(価格、規程、仕様)はRAGで外から与え、出力フォーマットの固定や専門用語の言い回しはプロンプトまたはファインチューニングで扱う、という分担が実務上の基準になります。両者は排他ではありませんが、社内文書Q&Aの立ち上げでファインチューニングから着手する理由はほとんどありません。文書が週次で変わる環境では、学習し直すコストが検索側の改善に回した方が回収できます。
v1.3系のパッケージ構成とバージョン
インストールと確認済みバージョン
Python 3.13 の仮想環境に本体一式を入れます。langchain 1.3.15 が要求する Python は 3.10 以上で、3.9 では 1.x 系がインストール対象から外れます。
# 本体一式
pip install langchain langchain-classic langchain-text-splitters
# 埋め込みとLLMのプロバイダ用(OpenAIの場合)、およびAPIキー無しで検証する場合のnumpy
pip install langchain-openai numpy
1つ目のコマンド実行後の版は次のとおりです(いずれも2026年8月17日時点のPyPI最新版)。
langchain 1.3.15
langchain-classic 1.0.8
langchain-core 1.5.5
langchain-protocol 0.0.18
langchain-text-splitters 1.1.2
langgraph 1.2.11
langgraph-checkpoint 4.2.0
langgraph-prebuilt 1.1.0
langgraph-sdk 0.4.2
埋め込みとLLMは別パッケージです。OpenAIを使うなら langchain-openai(最新 1.5.1、2026年8月14日公開)を追加します。langchain の依存には langchain-core・langgraph・pydantic しか含まれず、プロバイダ用パッケージは pip install "langchain[openai]" のような extras 経由で入ります。なお langchain-community は最新が 0.4.2(2026年5月22日公開)で、本体の 1.x 系とは版の系統が別です。本体だけ上げても community 側の実装は追随しないため、community のローダーやレトリーバーに依存している場合は個別に動作を確認します。
langchain.chains の消失とlangchain-classicへの移行
v1.0 でパッケージが再編され、langchain 本体はエージェント構築の部品に絞られました。公式移行ガイドは、レガシーチェーン(LLMChain、ConversationChain 等)、langchain.retrievers 配下のレトリーバー、indexing API、hub モジュール、CacheBackedEmbeddings 等の embeddings モジュール、langchain-community の再エクスポートなどを langchain-classic へ移したと記載しています。結果として、0.x 系の記事に載っている import はそのままでは通りません。1.3.15 で実行すると次のようになります。
>>> from langchain.chains import create_retrieval_chain
ModuleNotFoundError: No module named 'langchain.chains'
>>> from langchain_classic.chains import create_retrieval_chain # これは通る
>>> from langchain_classic.chains.combine_documents import create_stuff_documents_chain
RetrievalQA や VectorDBQA も同様に langchain 直下には存在しません。既存コードを動かし続けるなら langchain-classic を入れて import 元だけ書き換えるのが最短で、新規に組むなら後述の create_agent 方式に寄せる判断になります。v1.0 での変更点の全体像は LangChain v1.0とは|create_agent・ミドルウェア・langchain-classicを実装例で解説で整理しています。
社内文書のチャンク分割|日本語で既定設定が破綻する位置
既定separatorsが文中で切る実測値
分割には RecursiveCharacterTextSplitter を使います。既定の separators は改行2つ、改行、空白、空文字の順で、日本語の句点「。」が入っていません。日本語は単語間に空白を置かないため、空白による分割も効きません。結果として最後の候補である空文字で切られ、chunk_size ちょうどの位置で文が分断されます。642文字の規程テキストを chunk_size=200 で分割した実測値が次です。
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.documents import Document
doc = [Document(page_content=text, metadata={"source": "kitei.md"})]
# 既定のseparators
default_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=20)
# 日本語向けに句読点を追加
ja_splitter = RecursiveCharacterTextSplitter(
chunk_size=200,
chunk_overlap=20,
separators=["\n\n", "\n", "。", "、", " ", ""],
)
原文文字数: 642
既定separators チャンク数: 4
既定 各チャンク長: [200, 200, 200, 102]
既定 先頭チャンク末尾: '端末の持ち出しと外部サービス利用の申請を定めます。申請は情報'
日本語separators チャンク数: 4
日本語 各チャンク長: [194, 178, 191, 79]
既定設定では先頭チャンクが「申請は情報」で終わり、「情報システム部の承認が必要です」という後半が次のチャンクへ分かれています。この状態で「端末の持ち出しは誰の承認が必要か」と検索すると、承認者を含むチャンクと申請対象を含むチャンクが別々に返り、どちらか一方しか上位に来ないと回答が欠けます。separators に句読点を加えた側はチャンク長が194/178/191/79とばらつく代わりに、切れ目がすべて文の境界に一致します。日本語文書を扱う場合、この1行の追加が検索精度に直結します。
ただし、この設定には副作用が2つあります。1つ目は chunk_overlap が効かなくなる点です。上の実測で日本語側の合計は194+178+191+79=642字で、原文642字と一致しています。つまり重複が1文字も付いていません(既定側は合計702字=原文+3境界×20字で重複あり)。重複は分割単位をまたいで付くため、1文が chunk_overlap より長いと全て捨てられます。2つ目は句点の位置です。keep_separator の既定値 True は区切り文字を次のチャンクの先頭に付けるため、2番目のチャンクが「。申請は情報システム部の…」と句点で始まります。文末に句点を残したい場合は keep_separator="end" を指定します。
ja_splitter = RecursiveCharacterTextSplitter(
chunk_size=200,
chunk_overlap=20,
keep_separator="end",
separators=["\n\n", "\n", "。", "、", " ", ""],
)
# 先頭チャンク末尾12字と2番目チャンク冒頭12字の実測
# keep_separator=True 末尾: 'ビス利用の申請を定めます' 次の冒頭: '。申請は情報システム部の'
# keep_separator="end" 末尾: 'ス利用の申請を定めます。' 次の冒頭: '申請は情報システム部の承'
chunk_sizeとchunk_overlapの決め方
chunk_size は文字数で数えます。公式チュートリアルは英語ドキュメントに対して chunk_size=1000, chunk_overlap=200 を使っていますが、日本語は同じ情報量を少ない文字数で表すため、そのまま流用すると1チャンクに複数の論点が入ります。規程や手順書のように「条項ごとに独立した意味を持つ」文書では、条項1つが収まる長さ(400〜600文字程度)を上限にし、chunk_overlap はその1〜2割を目安に設定します。重複を大きく取ると同じ内容のチャンクが複数ヒットして、プロンプトに入る情報の種類が減ります。分割結果は必ず len(chunk.page_content) と先頭・末尾の文字列を出力して、意味の切れ目と一致しているかを目視で確認します。
ベクトルストア登録と検索の実装
InMemoryVectorStoreによる最小構成
検索が成立するかを先に確認するため、まずメモリ上のベクトルストアで動かします。InMemoryVectorStore は langchain-core に含まれるので追加インストールは不要です。
from langchain_core.documents import Document
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_openai import OpenAIEmbeddings
docs = [
Document(page_content="端末の社外持ち出しは情報システム部への事前申請が必要です。",
metadata={"source": "sec.md"}),
Document(page_content="有給休暇は取得予定日の3営業日前までに申請します。",
metadata={"source": "kinmu.md"}),
]
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
vector_store = InMemoryVectorStore(embeddings)
ids = vector_store.add_documents(docs)
hits = vector_store.similarity_search("端末の持ち出し", k=2)
print(len(hits), hits[0].metadata)
add_documents は登録した文書のIDリストを返し、similarity_search は k で指定した件数の Document を返します。metadata はそのまま保持されるため、ここに source や更新日を入れておくと回答に出典を添えられます。APIキーを使わずに配線だけ検証したい場合は langchain_core.embeddings.DeterministicFakeEmbedding に差し替えられますが、この実装は内部で numpy を使う一方 langchain-core 1.5.5 の依存には numpy が含まれておらず、未導入の環境では NameError: name 'np' is not defined になります。pip install numpy を併せて実行してください。
as_retrieverによる検索条件の固定
後続のチェーンやツールへ渡すときは、検索条件を埋め込んだ Retriever にします。
retriever = vector_store.as_retriever(search_kwargs={"k": 2})
print(type(retriever).__name__) # VectorStoreRetriever
print(len(retriever.invoke("休暇の申請"))) # 2
as_retriever の戻りは VectorStoreRetriever で、search_type と search_kwargs で検索方法とパラメータを指定します。similarity_search を直接呼ぶ場合と違い、呼び出し側は invoke(質問文) だけを知っていればよくなります。
スコアの閾値で絞り込む search_type="similarity_score_threshold" は、上の InMemoryVectorStore では使えません。関連度スコア関数が実装されておらず、invoke の時点で _select_relevance_score_fn が NotImplementedError を送出します。閾値による絞り込みは、後述の PGVector のように関連度スコアを持つストアへ移してから設計してください。閾値の尺度は実装ごとに異なるため、値は実データの分布を見てから決めます。
本番向けベクトルストアの選択
InMemoryVectorStore はプロセスが終われば消えるので、検証以外では使いません。既にPostgreSQLを運用しているなら langchain-postgres の PGVector、単一サーバーで完結させたいなら langchain-chroma の Chroma、マネージドに寄せるなら langchain-pinecone が接続先の候補です。
from langchain_postgres import PGVector
vector_store = PGVector(
embeddings=embeddings,
collection_name="my_docs",
connection="postgresql+psycopg://user:pass@localhost:5432/ragdb",
)
どれを選んでも add_documents と similarity_search の呼び出し方は変わらないため、検証段階のコードはほぼそのまま移せます。選定で効くのはLangChain側ではなく、既存のバックアップ運用と権限管理に載せられるかどうかです。新しいベクトルDBを1つ増やす前に、手元のRDBに拡張を入れる案を検討する価値があります。なお langchain-postgres は PGVector とは別に新しい PGVectorStore も同梱しているため、新規構築時はどちらを使うか公式の統合ドキュメントで確認してください。
ここまでが「RAGの検索エンジン部分」に当たる工程です。ベクトル検索だけで足りるか、語彙の完全一致を担う全文検索を併用するかは、扱う質問の性質で決まります。型番・条項番号・製品名を引く質問が多い場合はベクトル検索単独では取りこぼすため、LangChain BM25Retrieverの使い方|インストール・日本語対応・ハイブリッド検索の構成を最初から前提に置いた方が手戻りが少なくなります。
回答生成の2方式|create_agentとcreate_retrieval_chain
create_agentへの検索ツール登録
v1.x 系の標準は、検索を関数(ツール)としてモデルに渡し、モデル自身に検索するかどうかと検索語を判断させる形です。create_agent は langchain.agents から読み込みます。
from langchain.tools import tool
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
@tool
def search_kitei(query: str) -> str:
"""社内規程を検索して該当箇所を返す。"""
hits = vector_store.similarity_search(query, k=2)
return "\n\n".join(f"[{d.metadata['source']}] {d.page_content}" for d in hits)
model = init_chat_model("openai:gpt-5.6")
agent = create_agent(
model=model,
tools=[search_kitei],
system_prompt="検索結果だけを根拠に答える。",
)
result = agent.invoke({"messages": [{"role": "user", "content": "端末の持ち出しは?"}]})
create_agent の引数は model、tools、system_prompt、middleware、response_format、state_schema、context_schema、checkpointer、store 等です。実行すると、質問→ツール呼び出し→検索結果→回答という順にメッセージが積まれます。ツール呼び出し部分をモック化して流したメッセージ列が次で、ToolMessage に検索本文が入り、その後のモデル発話が回答になります。
HumanMessage: content='端末の持ち出しは?'
AIMessage: content='' tool_calls=[{'name': 'search_kitei', 'args': {'query': '端末 持ち出し'}, 'id': 'c1', 'type': 'tool_call'}]
ToolMessage: content='[sec.md] 端末の社外持ち出しは情報システム部への事前申請が必要です。 ...'
AIMessage: content='情報システム部への事前申請が必要です(出典: sec.md)。'
この方式の利点は、質問を分割して複数回検索したり、検索が空振りしたら語を変えて再検索したりをモデルが判断できる点です。反面、検索を1度も呼ばずに答えてしまう場合があり、根拠必須の用途ではプロンプトとツール定義の両方で縛る必要があります。判断をモデルに委ねる設計の考え方は Agentic RAGのアーキテクチャと構成要素を詳しく解説で扱っています。
create_retrieval_chainによる0.x系構成の維持
検索を必ず1回だけ実行し、その結果で回答させる決め打ちの構成は langchain-classic 側に残っています。
from langchain_classic.chains import create_retrieval_chain
from langchain_classic.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "次の資料だけで答えてください:\n\n{context}"),
("human", "{input}"),
])
combine = create_stuff_documents_chain(model, prompt)
chain = create_retrieval_chain(retriever, combine)
out = chain.invoke({"input": "端末の持ち出しはどうする?"})
print(sorted(out.keys())) # ['answer', 'context', 'input']
print(len(out["context"])) # 2
戻り値は answer(生成文)、context(渡した Document のリスト)、input(元の質問)の3キーです。context に実際のチャンクが入っているので、metadata["source"] を並べれば出典表示がそのまま作れます。プロンプトの {context} と {input} は変数名が固定で、別名にすると値が入りません。
用途別の方式選択基準
| 観点 | create_agent | create_retrieval_chain |
|---|---|---|
| パッケージ | langchain 1.x | langchain-classic |
| 検索回数 | モデルが判断(0回もあり) | 必ず1回 |
| LLM呼び出し回数 | 検索回数+1以上 | 1回 |
| 出典の取得 | ToolMessageから抽出 | contextキーから直接 |
| 応答時間 | 可変 | ほぼ一定 |
| 向く用途 | 調査・多段の質問 | 定型Q&A・根拠必須 |
社内規程Q&Aのように「必ず資料を引いて答える」ことが要件なら create_retrieval_chain を選びます。検索が保証され、LLM呼び出しが1回で応答時間とコストが読めるからです。逆に、質問が曖昧で検索語の言い換えが必要な用途では create_agent の方が当たります。両方を実装して切り替える構成は保守対象が倍になるので、要件が固まっていない段階では決め打ちのチェーンから始め、検索の空振りが問題になった時点でエージェント方式へ移す順序が無駄になりません。
公式チュートリアルの現在地|RAGページはDeep Agentsへ移動
LangChainのRAGチュートリアルを探すと docs.langchain.com/oss/python/langchain/rag が上位に出ますが、このURLは現在 308(恒久的リダイレクト)で /oss/python/deepagents/rag へ転送され、ページ名は「Retrieval Augmented Generation (RAG) with Deep Agents」に変わっています。内容も create_deep_agent(deepagents パッケージ、最新 0.7.6 / 2026年8月13日公開)を前提に、検索したチャンクをファイルシステムへ退避してサブエージェントに分析させる構成へ書き換えられました。ルーブリックによる根拠チェックは deepagents>=0.6.5 が必要で、公式表記ではベータ段階です。
一方、埋め込みとベクトルストアの基本操作は /oss/python/langchain/knowledge-base(ページ名「Build a semantic search engine with LangChain」)に残っています。つまり、検索インデックスの作り方は knowledge-base、エージェントを絡めた回答生成は deepagents 側という分かれ方です。検索結果の見出しやブックマークが「Build a RAG agent with LangChain」のままになっているケースは、転送前のタイトルを指しています。追加パッケージなしでRAGを組みたい場合は、knowledge-base の手順に本記事の create_agent または create_retrieval_chain を接ぐ形が最短で、deepagents の導入はサブエージェント分割が必要になってから検討すれば足ります。LangChainとLangGraphの層の分かれ方は LangChainとLangGraphの違い|v1.0で逆転した関係と使い分けで確認できます。
精度が出ないときの切り分け
検索が外れている場合
回答が的外れなとき、まず retriever.invoke(質問) の戻りを目で見ます。LLMを挟まず検索結果だけを出力すれば、問題が検索側か生成側かはその場で切り分けられます。
for i, d in enumerate(retriever.invoke("端末の持ち出しは誰の承認が必要か"), 1):
print(i, d.metadata["source"], d.page_content[:40])
正解を含むチャンクが上位に来ていなければ原因は検索側で、プロンプトを直しても改善しません。ベクトル検索は表記が違っても意味で拾える一方、型番や条項番号のような完全一致で引きたい語に弱いという傾向があります。この場合はキーワード検索を併用します。BM25による語彙一致の追加は LangChain BM25Retrieverの使い方|インストール・日本語対応・ハイブリッド検索、複数の検索結果を1つの順位に統合する手法は RRF(Reciprocal Rank Fusion)とは?RAG-Fusionでの仕組み・スコア計算・実装を解説が該当します。
生成が資料から外れる場合
検索は当たっているのに回答が資料の内容とずれる場合は生成側の問題です。チャンクに前後の文脈が欠けている(前節の分割で切断されている)、渡したチャンク数が多すぎて要点が埋もれている、プロンプトが資料外の推測を許しているのいずれかが多いパターンです。検索と生成を分けて数値で診断する枠組みとしては RAGCheckerとは|RAGを検索と生成に分けて診断するAmazonの評価フレームワークがあり、どちらの工程を直すべきかを切り分けられます。改善作業に入る前に、正解が分かっている質問を20〜30件用意しておくと、変更のたびに効果を比較できます。
社内RAGで実装前に決めること
権限とアクセス制御
社内文書RAGで最初に破綻するのは精度ではなく権限です。全社の共有ドライブをまとめて1つのベクトルストアに入れると、人事評価や給与のファイルが検索対象に混ざり、誰でも引ける状態になります。ベクトルストアは「検索できる=内容が読める」ため、後から画面側で隠しても意味がありません。対策は、metadata に部門やロールを持たせて search_kwargs のフィルタで絞るか、権限区分ごとにコレクションを分けるかの二択です。
docs = [
Document(page_content="給与テーブルは人事部のみ閲覧可。", metadata={"dept": "hr"}),
Document(page_content="端末の持ち出しは事前申請が必要です。", metadata={"dept": "all"}),
]
# InMemoryVectorStore の filter は「Documentを受けてboolを返す関数」
retriever = vector_store.as_retriever(
search_kwargs={"k": 2, "filter": lambda d: d.metadata["dept"] == "all"}
)
print([d.metadata for d in retriever.invoke("閲覧")]) # [{'dept': 'all'}]
ここで注意が必要なのは、filter に渡す値の型がストアごとに違う点です。InMemoryVectorStore は上のように関数を要求し、{"dept": "all"} のような辞書を渡すと TypeError: 'dict' object is not callable になります。PGVector をはじめ多くの外部ストアは逆に辞書を受け取ります。検証をメモリ上で行って本番でPGVectorへ差し替える流れだと、この1か所だけ書き換えが必要になります。
前者のフィルタ方式は1つのインデックスで済む代わりにフィルタの実装ミスが漏洩になり、後者のコレクション分離は運用が増える代わりに事故の範囲が閉じます。扱う文書に人事・法務系が含まれるなら、コレクション分離を選ぶべきです。
費用と外部委託の判断
費用は埋め込み(登録時と検索時)、LLMの入出力トークン、ベクトルストアの稼働費の3つで決まります。単価は各社が随時改定するため、見積もりの際は必ず提供元の料金ページで最新の値を確認してください。構成上の傾向としては、初回のインデックス作成は文書量に比例した一度きりの費用で、恒常的に増えるのは質問1件あたりのLLM入力トークンです。ここは検索で渡すチャンク数(k)と chunk_size の積でほぼ決まるので、精度を落とさずに k を減らせるかが費用の主な調整点になります。外部委託を検討する場合、丸ごと任せるより「権限設計と評価用の質問セットは自社で用意し、実装と運用の型作りを委託する」分担の方が引き継ぎで詰まりません。評価質問が自社にないと、納品物の良否を判断する基準が発注側に残らないためです。
よくある質問
pip install langchain だけでRAGは動きますか?
動きません。langchain 1.3.15 の依存は langchain-core、langgraph、pydantic のみで、埋め込みモデルとLLMのクライアントは含まれません。OpenAIを使うなら langchain-openai、ローカルモデルなら langchain-ollama のようにプロバイダ別パッケージを追加します。文書分割の langchain-text-splitters も別パッケージです。
langchain-community は使わなくてよいのですか?
本記事の構成では不要です。langchain-community は最新が 0.4.2(2026年5月22日公開)で本体の 1.x 系とは版の系統が異なり、langchain の extras(langchain[community])経由で入る位置づけになっています。community 固有のドキュメントローダーやレトリーバーを使う場合のみ追加し、その際は本体を上げた後に該当クラスの動作を個別に確認してください。
LangChainを使わずにRAGは作れますか?
作れます。埋め込みAPIを直接呼び、ベクトルDBのクライアントで検索し、取得した本文をプロンプトに文字列結合してLLMへ渡せば同じ処理になります。LangChainの価値は、ベクトルストアや埋め込みモデルを差し替えても呼び出し側のコードが変わらない共通インターフェースと、ツール実行ループの実装済み部分です。接続先を1つに固定してよく、エージェント的な多段処理も不要なら、自前実装の方が依存が減って見通しが良くなります。
回答に出典を付けるにはどうしますか?
Document の metadata にファイル名やURLを入れておき、生成後にそれを表示します。create_retrieval_chain の場合は invoke の戻りの context キーに渡したチャンクがそのまま入っているので、doc.metadata["source"] を並べれば足ります。create_agent の場合はツールの戻り文字列に出典を埋め込み、ToolMessage から抽出するか、システムプロンプトで出典表記を義務付けます。
LangGraphやDeep Agentsへ移行する必要はありますか?
単一の検索と回答で足りる用途では不要です。create_agent は内部でLangGraphを使っており(langchain 1.3.15 は langgraph>=1.2.11 に依存)、グラフを直接書かなくてもツール実行ループは動きます。LangGraphを直接扱う価値が出るのは、条件分岐や人間の承認待ち、途中状態の永続化を自分で設計する段階です。deepagents はさらに、サブエージェントへの分担やファイルシステムへのチャンク退避が必要な規模で検討する選択肢になります。