これは Hubble Advent Calendar 2025の21日目の記事です。
こんにちは。株式会社 Hubble の AI Squad でリードエンジニアをしている Kyohei です。
アドカレ21日目となる本記事では、今年7月頃に公開され話題になった Deepagents を紹介したいと思います。
Deepagents とは
Deepagents は、 Claude Code や Codex CLI に触発されて作られたエージェントライブラリです。
エージェント機能を手軽に実装するための仕組みとして、 LangGraph の create_agent があります。 create_agent がシンプルで低レイヤーな道具立てを提供するのに対して、 Deepagents はより複雑なタスクをマルチターンの推論を要する実行プランに落とし込み遂行するための枠組みを備えています。
本記事では、「シンプルに見えながら潜在的にマルチターンの推論が必要なタスク」を題材に、素の LangChain, LangGraph の create_agent, そして Deepagents を使って実装した場合のコードと性能をふわっと比較してみたいと思います。
また、現在の Deepagents は API だけではなく Claude Code 風の CLI も同梱しているので、開発したエージェントを CLI からさくっと使える方法も紹介します。
題材となる法令QAタスクについて
「シンプルに見えながら潜在的にマルチターンの推論が必要なタスク」として、ここでは法令QAタスクを題材として取り上げたいと思います。
法令QAタスクとは、特定の法令に関する設問を与え、利用可能な情報をもとに適切な回答を出力するタスクです。 LLM による法令QAタスクのデータセットのひとつとして、デジタル庁による『日本の法令に関する多岐選択式QAデータセット』(以下「法令 QA データセット」)があります。
このデータセットは日本のいくつかの法令に関する多肢選択型の設問集となっており、設問と正解だけでなく、正答に必要と思われる条文や政令・ガイドラインの抜粋などの情報を専門家がキュレーションしたテキスト(同データセット中「コンテキスト用データ」と呼ばれるため、以下、本文でも同様に呼びます)も含まれています。
データ形式は CSV と JSON で提供され、 JSON では以下のような内容です:
{
"samples": [
{
"ファイル名": "金商法_第2章_選択式_関連法令_問題番号57",
"コンテキスト": "## 金融商品取引法\n### 第5条\n#### 第6項\n第一項の規定により届出書を提出しなければならない外国会社(以下「届出書提出外国会社」という。)は、公益又は投資者保護に欠けることがないものとして内閣府令で定める場合には、同項の届出書に代えて、内閣府令で定めるところにより、次に掲げる書類を提出することができる。\n##### 第2号\n外国において開示が行われている参照書類又は第一項の届出書に類する書類であつて英語で記載されているもの",
"問題文": "金融商品取引法第5条第6項により、公益又は投資者保護に欠けることがないものとして内閣府令で定める場合に該当することを理由として、届出書提出外国会社が金融商品取引法第5条第1項の届出書に代えて提出ことができる書類を教えてください。",
"指示": "<following_context>以下の問題文に対する回答を、選択肢a、b、c、dの中から1つ選んでください。",
"選択肢": "a 第5条第1項の届出書に類する書類であって、届出書提出外国会社の本店所在地の母国語であるフランス語で記載されているもの\nb 外国において開示予定の参照書類であって、英語で記載されているもの\nc 第5条第1項の届出書に類する書類であって、英語で記載されているもの\nd 外国において開示が行われている参照書類であって、日本語で記載されているもの",
"output": "c",
"references": [
"https://laws.e-gov.go.jp/law/323AC0000000025"
]
},
...
}
(キーに日本語と英語が混在しているのが気になる……)
コンテキスト用データを使わなければ LLM 自身が学習を通じて獲得した性能を評価することができ、使う場合は、適切なコンテキストを与えられた場合の LLM の演繹能力のようなものを評価することができます。
コンテキスト用データを使わない場合、 LLM は設問を一見して応えることもできますが、最初に法令の全体像と他の情報へのポインタを与えられれば、必要に応じて個別の条文・関連法令・政令などの情報を複数回にわたって取得することで、より高い精度で回答することができます(この検証の詳細は筆者ブログの過去記事で紹介しています)。
そこで、今回は敢えて「コンテキスト用データ」を使用せず、情報のポインタだけを与えられたエージェント自身がどれだけ適切な情報を収集し正答に辿り着くことができるかを見ることで、 LangChain や create_agent によるシンプルなエージェントと、 deepagents によるエージェントの long-horizon タスクでの性能の違いを評価してみたいと思います。
各フレームワークでの実装
それでは以下に各手法での実装例を紹介します。ここではエージェント機能の実装に関する箇所だけ抜粋しており、実際にはデータセットの読み込み、並列化、エラー処理などが必要なのでもう少し行数が必要です。
素の LangChain
LangChain の ChatOpenAI を使用して、 Anthropic などが agentic tool-calling loop と呼ぶものを while 文で素朴に実装しています。依存関係が少ないのが嬉しいですね。実際、 ChatOpenAI の部分を requests による GET リクエストに置き換えても、他のプロバイダへ切り替えが少々手間になる以外は大差ないと思います。
import requests
from concurrent.futures import ThreadPoolExecutor, as_completed
from langchain_openai import ChatOpenAI
from langchain.tools import tool
@tool
def fetch_url_content(url: str) -> str:
"""Fetch content from a URL via GET request.
Args:
url: The URL to fetch content from
"""
response = requests.get(url, timeout=10)
response.raise_for_status()
return response.text
def invoke_tool_calling_loop(prompt):
llm = ChatOpenAI(
model="gpt-5.2",
api_key=OPENAI_API_KEY,
max_tokens=MAX_TOKENS,
reasoning_effort="high"
)
llm = llm.bind_tools([fetch_url_content])
messages = [("system", ""), ("human", prompt)]
while True:
response = llm.invoke(messages)
if not response.tool_calls:
return response.content
messages.append(response)
for tool_call in response.tool_calls:
tool_name = tool_call["name"]
tool_args = tool_call["args"]
if tool_name == "fetch_url_content":
result = fetch_url_content.invoke(tool_args)
messages.append(
{"role": "tool", "content": result, "tool_call_id": tool_call["id"]}
)
create_agent
create_agent が agentic tool-calling loop に相当する部分を実装しているため、 while 文を書く必要がなくなって見通しが良くなっています(といっても、実際ストリーミング処理したかったりツール呼び出しの詳細を UI に引き回したかったりすると、 .invoke の代わりに .astream などを呼び出して lambda を渡す必要が出てきて、結果的に似たようなコードになってしまいます)。
import requests
from langchain_openai import ChatOpenAI
from langchain.tools import tool
from langchain.agents import create_agent
@tool
def fetch_url_content(url: str) -> str:
"""Fetch content from a URL via GET request.
Args:
url: The URL to fetch content from
"""
response = requests.get(url, timeout=10)
response.raise_for_status()
return response.text
def invoke_agent(prompt):
llm = ChatOpenAI(
model="gpt-5.2",
reasoning_effort="high",
api_key=OPENAI_API_KEY,
max_tokens=MAX_TOKENS,
)
agent = create_agent(
model=llm,
tools=[fetch_url_content],
system_prompt="",
)
messages = [{"role": "user", "content": prompt}]
result = agent.invoke({"messages": messages})
final_message = result["messages"][-1]
return final_message.content
Deepagents
Deepagents の実装は、一見すると create_agent を使用した場合と大きく変わりません。実際、 create_deep_agent は内部で create_agent を呼び出し、 LangGraph のグラフを返します。そのため、一度構築してしまえば LangGraph と同じように取り回すことができます(便利)。
create_agent に比べて優れているのは、複雑なタスクを分解し実行計画を立てるためのシステムプロンプト・効果的なコンテキスト管理を可能にする組み込みツール群(ファイルシステムアクセスや TODO 管理など)・サブエージェント管理を仕組み・長期記憶管理機能が、デフォルトで組み込まれている点です。これにより、一見しただけでは不正答に陥りがちな問題であっても、問題を詳細に分析し、必要に応じて情報(=コンテキスト)を集め、正しい回答を導き出そうとしています。同様の仕組みを create_agent を使って実装するのであれば、 Deepagents の仕組みに乗っかるのもアリだと思います。
import requests
from langchain_openai import ChatOpenAI
from langchain.tools import tool
from deepagents import create_deep_agent
@tool
def fetch_url_content(url: str) -> str:
"""Fetch content from a URL via GET request.
Args:
url: The URL to fetch content from
"""
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
return response.text
except Exception as e:
return f"Error fetching URL: {str(e)}"
def invoke_agent(prompt):
llm = ChatOpenAI(
model="gpt-5.2",
reasoning_effort="high",
api_key=OPENAI_API_KEY,
max_tokens=MAX_TOKENS
)
agent = create_deep_agent(
model=llm,
tools=[fetch_url_content],
system_prompt="",
)
messages = [{"role": "user", "content": prompt}]
result = agent.invoke({"messages": messages})
final_message = result["messages"][-1]
return final_message.content
実行結果の比較表
さて、上記の方法で実装した3種類のエージェントには性能の点で違いがあるのでしょうか。前述した法令QAデータセットのうち金融商品取引法に関する問題320問を抜粋し、各エージェントに回答させ、正答率を以下の表にまとめました。モデルとしては先日リリースされた GPT-5.1 (reasoning effort: high) と GPT-4.1 をそれぞれ使用しました。先述の筆者の過去記事で、 LangChain 版とほぼ同じ実装で GPT-5.1 (reasoning effort: none, low, and high) を使って検証しいてるので、その際の結果も表に含めて比較してみたいと思います。
| LangChain | LangGraph create_agent
|
Deepagents | |
|---|---|---|---|
| GPT-5.2 high | 90.5 % | 91.2% | 90.6% |
| GPT-4.1 | 59.7 % | 58.1% | 60.6% |
| GPT-5.1 (none) | 59.621% | - | - |
| GPT-5.1 (low) | 80.573 % | - | - |
| GPT-5.1 (high) | 91.1671 % | - | - |
モデル間の違いに比べると、実装手法の違いはほとんどないと言ってよさそうです。強いていえば create_agent と Deepagents の比較では、 GPT-4.1 では Deepagents がやや性能が高く、 GPT-5.2 (high) では create_agent のほうがやや良い結果となりました。最終的にモデルに渡るプロンプトという観点でみると、 create_agent では法令QAのための指示しか入っていないのに対し、 Deepagents では long-horizon タスクで一般に必要になりそうな指針や組み込みツールの情報が含まれています。今回検証したような比較的規模の小さなタスクでは、この情報がノイズとなり微妙に性能の劣化を招いたと考えることができそうです。 Long-horizon タスクのための精密なプロンプトは効果を発揮する場面もありますが、使いどころによっては負の影響を及ぼし得ることは認識しておくと良いかもしれません。
検証に使用したコードはこちらに公開しています。
Deepagents CLI
さて、ここからは趣向を変えて、最近追加された Deepagents のコマンドラインインターフェイスを紹介したいと思います。私もしばらくウォッチしていない間に langchain-ai/deepagents リポジトリに libs/deepagents-cli が追加され、 Deepagents を使用したコーディングアシスタントが公開されています。 Claude Code に影響を受けて開発された Deepagents ですが、それ自体はライブラリで、ユーザと直接対話するインターフェイスはライブラリ利用者が開発する必要がありました。このデフォルトの実装として CLI が追加されたのではないかと想像しています(特に議論は追っていないので完全な想像ですが)。
名前を見たとき、なんとなく「今まで Deepagents で開発したエージェント(= LangGraph グラフ)を Claude Code like な CLI で触れる」的なものかと想像していたのですが、エージェントのカスタマイズは Skills と Memory の範囲に留まるようでした。
今回は独自ツールを追加するくらいはやってみたいので、 deepagents-cli に手を入れて上記の fetch_url_content ツールを追加し、それを使って URL を使った調べものをお願いしてみたいと思います。
……と思っていたら、普通に Deepagent CLI には fetch_url ツールが組み込みで存在するようでした。ほぼ書き終わってから気づいたため今さら本稿の構成を変更することもできず、しかたないので任意の複素平面上の点がマンデルブロ集合に含まれるか判定するツールでも作って呼び出してもらうことにします。
まずは Deepagents のリポジトリをクローンします。
$ git clone https://github.com/langchain-ai/deepagents.git
Deepagent CLI でエージェントを実装しているのはlibs/deepagents-cli/deepagents_cli/agent.py なので、このファイルで create_deep_agent() を呼び出している箇所の付近でツールを実装し、それを同関数呼び出しの tools に付け加えれば良いだけです。
diff --git a/libs/deepagents-cli/deepagents_cli/agent.py b/libs/deepagents-cli/deepagents_cli/agent.py
index 14dea59..480336e 100644
--- a/libs/deepagents-cli/deepagents_cli/agent.py
+++ b/libs/deepagents-cli/deepagents_cli/agent.py
@@ -322,6 +322,26 @@ def _add_interrupt_on() -> dict[str, InterruptOnConfig]:
"task": task_interrupt_config,
}
+from langchain.tools import tool
+@tool
+def mandel(c:complex) -> float:
+ """Compute the escape time for a point in the Mandelbrot set.
+
+ Args:
+ c: A complex number to test for membership in the Mandelbrot set.
+ e.g. mandel(0.5 + 0.2j), mandel(0.76 - 0.112j)
+
+ Returns:
+ The number of iterations before |z| >= 2, or 100 if it doesn't escape.
+ """
+ M = 100
+ k = 0
+ z = 0
+ while k < M and abs(z) < 2:
+ z = z ** 2 + c
+ k += 1
+ return k
def create_cli_agent(
model: str | BaseChatModel,
@@ -457,7 +477,7 @@ def create_cli_agent(
agent = create_deep_agent(
model=model,
system_prompt=system_prompt,
- tools=tools,
+ tools=tools + [mandel],
backend=composite_backend,
middleware=agent_middleware,
interrupt_on=interrupt_on,
変更したら uv でビルド・実行します。起動したら、簡単なプロンプトで、リサーチ能力と先ほど試したツールをきちんと使用できるかを試してみます。
$ uv run deepagents
白文字がプロンプト、緑の文字が Deepagent からの返答です。どちらのタスクも無事実行できています。 GEMA v OpenAI で注目を集めた判示もよく説明されています。マンデルブロ集合のツールもきちんと呼び出せているようです。
使用感は Claude Code や Codex CLI にかなり寄せて作られています。さすがに資本の力もあり細部の完成度では劣りますが、 MIT ライセンスなので自由にカスタマイズして再配布もできるのは Deepagents CLI ならではの魅力です。
まとめ
Deepagents で複数ステップに渡るタスクを扱う方法を LangChain, LangGraph の場合と比較し、開発したエージェントを CLI で動作させる方法を紹介しました。
LLM エージェントは一見複雑な仕組みのように思われますが、実際には単純なループでしかないことがよく知られるようになった2025年でした。単純なループとプロンプトでしかないものが、複雑なタスクを小さな問題に分解して確実に遂行するためには、適切なコンテキスト情報を必要なときに取得できる基盤の存在が欠かせません。
契約書管理を扱う Hubble では、 AI 時代が到来するずっと以前から、契約書の文言だけではなくその成立の交渉過程や契約当事者間の関係性などの契約の背景情報をデータ化することに取り組んできました。私が所属する AI Squad では、これらの基盤を活用してプロダクトの様々な場面に LLM エージェントを組み込み、 AI エージェントネイティブなプロダクトに進化させる取り組みを進めています。
エンタープライズレベルのアーキテクチャを備えた Rails アプリケーションにリーガル LLM エージェントを組み込む仕事に興味がある方はぜひカジュアル面談へ!
明日22日は @KOHETs さんの記事です。お楽しみに!


