深層エージェントの評価:我々の学び
LangChain は Deep Agents の実装経験に基づき、単一ステップ評価から環境設定に至るまでの具体的なテストパターンとベストプラクティスを共有し、開発者の実践的ガイドを提供した。
キーポイント
個別のテストロジックの必要性
Deep Agents は各データポイントごとに独自の成功基準を持つため、汎用的な評価ではなく、ケースごとの特化型テストロジックが必要である。
評価モードの使い分け
意思決定の検証にはトークン効率の良い「単一ステップ」実行を、最終状態の確認には「フルターン」実行を用いるなど、目的に応じた実行モードの使い分けが推奨される。
現実的なシミュレーションと制約
複数ターンによるユーザー対話のシミュレーションは有用だが、エージェントが暴走しないよう厳格な「レール(制約)」を設ける必要がある。
再現可能な環境の重要性
テストの信頼性を担保するためには、クリーンで再現性の高いテスト環境の構築が不可欠であるという実践的教訓が示されている。
影響分析・編集コメントを表示
影響分析
この記事は、Deep Agent という複雑なシステムを実際の製品に落とし込む際に直面する評価課題に対する、LangChain の実務的な知見を体系化しており、開発者が効果的なテスト戦略を構築するための即戦力となるフレームワークを提供します。特に「単一ステップ」と「フルターン」の使い分けや環境設定の重要性は、AI エージェントの開発現場における品質保証(QA)の標準化に寄与する重要な指針です。
編集コメント
Deep Agent の実装において評価が後回しになりがちですが、この記事は「テスト設計」こそが品質を決定づけるという本質的な示唆を与えています。具体的な実行モードの使い分けや環境設定のノウハウは、開発現場ですぐに適用可能な貴重なプラクティスです。
過去1ヶ月間、LangChainではDeep Agentsハーンの上に4つのアプリケーションをリリースしました:
- DeepAgents CLI: コーディングエージェント
- LangSmith Assist: LangSmith内のさまざまなタスクを支援するアプリ内エージェント
- Personal Email Assistant: 各ユーザーとの対話から学習するメールアシスタント
- Agent Builder: メタディープエージェントによって駆動されるノーコードのエージェント構築プラットフォーム
これらのエージェントを構築してリリースすることは、それぞれに評価(evals)を追加することを意味し、その過程で多くのことを学びました!本稿では、ディープエージェントの評価における以下のパターンについて詳しく掘り下げていきます。
- ディープエージェントには、各データポイントごとに個別のテストロジックが必要です。各テストケースには独自の成功基準があります。
- 単一ステップでのディープエージェントの実行は、特定シナリオにおける意思決定を検証するのに優れており(トークンも節約できます!)
- エージェントの完全なターン実行は、エージェントの「最終状態」に関するアサーションをテストするのに適しています。
- 複数のエージェントターンは現実的なユーザーインタラクションをシミュレートしますが、一定の枠組み内で管理する必要があります。
- 環境設定が重要である — ディープエージェントにはクリーンで再現可能なテスト環境が必要です
ギャラリー(用語解説)
詳しく掘り下げる前に、本稿全体で使用されるいくつかの用語を定義します。
エージェントの実行方法:
- シングルステップ:コアエージェントループを 1 ターンのみ実行するように制約し、エージェントが次に取るべき行動を決定します。
- フルターン:単一の入力に対してエージェント全体を実行します。これには複数のツール呼び出しの反復が含まれる場合があります。
- マルチターン:エージェント全体を複数回実行します。通常は、エージェントとユーザーの間で数回のやり取りが行われる「マルチターン」会話をシミュレートするために使用されます。

テスト可能な項目:
- 軌跡(Trajectory):エージェントによって呼び出されるツールのシーケンスと、エージェントが生成する特定のツール引数。
- 最終応答(Final response):ユーザーに対してエージェントから返された最終的な応答。
- その他の状態(Other state):実行中にエージェントが生成したその他の値(例:ファイル、他のアーティファクト)

#1: ディープエージェントでは、各データポイントに対してより個別化されたテストロジック(コード)が必要
従来の LLM 評価は単純明快です。
1) サンプルのデータセットを構築する
2) 評価器(evaluator)を作成する
3) アプリケーションをデータセット上で実行して出力を生成し、その出力を評価器でスコアリングする
すべてのデータポイントは同一に扱われます。同じアプリケーションロジックを通じて実行され、同じ評価器によってスコアリングされます。

Deep Agents はこの前提を崩します。最終メッセージだけでなく、より多くの要素を検証する必要があります。「成功基準」もデータポイントごとに特定のものとなり、エージェントの軌跡や状態に対する特定の検証を含む可能性があります。
次の例を考えてみましょう:

ユーザーの好みを記憶できる機能を持つカレンダースケジューリング用の Deep Agent があります。ユーザーはエージェントに「午前 9 時より前に会議をスケジュールしないように覚えておいて」と依頼します。私たちは、この情報を記憶するためにカレンダースケジューリング用エージェントが自身のファイルシステム内のメモリーを更新したことを検証したいと考えています。
これをテストするためには、以下の点を検証するアサーション(assertion)を書く必要があります:
1) エージェントが memories.md ファイルパスに対して edit_file を呼び出したこと
2) エージェントが最終メッセージでメモリの更新内容をユーザーに伝達したこと
3) memories.md ファイル自体に、早朝の会議をスケジュールしないという情報が含まれていること。具体的には:
- 「9am」という言及を探すために正規表現(regex)を使用する
- または、ファイル更新のより包括的な分析のために、特定の成功基準を持つ LLM-as-judge を使用する
LangSmith の Pytest および Vitest 統合は、このような個別のテストをサポートしています。各テストケースに対して、エージェントの軌跡、最終メッセージ、状態について異なるアサーションを行うことができます。
LangSmith テストケースとしてマークする
@pytest.mark.langsmith
def test_remember_no_early_meetings() -> None:
user_input = "9 時 ET より前に会議をスケジュールしたくない"
# エージェントへの入力を LangSmith にログ記録できます
t.log_inputs({"question": user_input})
response = run_agent(user_input)
# エージェントの出力を LangSmith にログ記録できます
t.log_outputs({"outputs": response})
agent_tool_calls = get_agent_tool_calls(response)
# エージェントがメモリの更新のために edit_file ツールを呼び出したことをアサートします
assert any([tc["name"] == "edit_file" and tc["args"]["path"] == "memories.md" for tc in agent_tool_calls])
# 最終メッセージがメモリの更新を確認したという LLM-as-judge(LLM を判事として用いた評価)からのフィードバックをログ記録します
communicated_to_user = llm_as_judge_A(response)
t.log_feedback(key="communicated_to_user", score=communicated_to_user)
# メモリファイルに正しい情報が含まれるようになったという LLM-as-judge(LLM を判事として用いた評価)からのフィードバックをログ記録します
memory_updated = llm_as_judge_B(response)
t.log_feedback(key="memory_updated", score=memory_updated)
Pytest の使用法に関する一般的なコードスニペットについては、こちらのドキュメントをご覧ください:
この LangSmith 統合により、すべてのテストケースが自動的に実験にログ記録されるため、失敗したテストケースのトレース(何が間違っていたかをデバッグするため)を表示したり、時間経過に伴う結果を追跡したりできます。
#2: シングルステップ評価は価値があり効率的である

Deep Agents の評価(evals)を実行する際、テストケースの約半分がシングルステップ評価でした。つまり、特定のメッセージ入力系列の直後に LLM が何を決定したかというものです。
これは、エージェントが特定シナリオにおいて正しいツールを適切な引数で呼び出したことを検証するのに特に有用です。一般的なテストケースには以下が含まれます:
- 会議時間の検索に適切なツールを呼び出したか?
- 正しいディレクトリ contents を確認したか?
- メモリを更新したか?
回帰(regressions)は、完全な実行シーケンス全体ではなく、個々の意思決定ポイントで発生することがよくあります。LangGraph を使用している場合、そのストリーミング機能により、エージェントが単一のツール呼び出しの後に中断され、出力を検査できるようになります。これにより、完全なエージェントシーケンスのオーバーヘッドなしに、問題を早期に検出できます。
以下のコードスニペットでは、ツールノードの手前に手動でブレークポイントを設定し、エージェントをシングルステップで簡単に実行できるようにしています。その後、その単一のステップ後の状態を検査し、アサーションを行うことができます。
@pytest.mark.langsmith
def test_single_step() -> None:
state_before_tool_execution = await agent.ainvoke(
inputs,
# interrupt_before specifies nodes to stop before
# interrupting before the tool node allows us to inspect the tool call args
interrupt_before=["tools"]
)
# We can see the message history of the agent, including the latest tool call
print(state_before_tool_execution["messages"])
#3: Full agent turns give you a complete picture

単一ステップの評価を、特定のシナリオでエージェントが期待される行動をとることを保証する「ユニットテスト」だと考えてください。一方、フルエージェントターン(エージェントの完全な一連の応答)も非常に価値があります。これは、エンドツーエンドのエージェントの行動全体を包括的に把握できるからです。
フルエージェントターンを使えば、エージェントの動作を複数の方法でテストできます:
1) 軌跡 (Trajectory): エージェントの完全な軌跡を評価する最も一般的な方法は、特定のツールが行動の過程でいつか呼ばれたことを確認することです。正確にいつ呼ばれたかは問題になりません。カレンダースケジューラーの例では、スケジューラーは全関係者にとって都合の良い時間枠を見つけるために、複数のツール呼び出しが必要になる場合があります。

2) 最終レスポンス: 場合によっては、エージェントがどの経路をたどったかよりも、最終出力の質の方が重要になります。コーディングや調査など、よりオープンエンドなタスクではこれが真実であることがわかりました。

3) その他の状態: 他の状態の評価は、エージェントの最終レスポンスを評価することと非常に似ています。一部のエージェントはチャット形式でユーザーに応答するのではなく、アーティファクトを作成します。LangGraph におけるエージェントの状態を検証することで、これらのアーティファクトの検証やテストは容易です。
- コーディングエージェントの場合 → エージェントが作成したファイルを読み、その後テストする。
- 調査エージェントの場合 → エージェントが適切なリンクやソースを見つけたことを確認する。
完全なエージェントのターン(一連のやり取り)により、エージェントの実行状況全体を把握できます。LangSmith を使用すれば、レイテンシやトークン使用量などの高レベル指標を確認しつつ、各モデル呼び出しやツール呼び出しに至るまで特定のステップを分析できるため、完全なエージェントのターンをトレースとして表示することが非常に容易になります。
#4: 複数のターンにわたってエージェントを実行することで、完全なユーザーインタラクションをシミュレートする

いくつかのシナリオでは、複数の連続するユーザー入力を含む多ターン会話全体にわたってエージェントをテストする必要があります。課題は、単純に入力シーケンスをハードコードした場合、もしエージェントが期待された経路から逸脱すると、その後のハードコードされたユーザー入力が意味をなさなくなってしまう点です。
これに対処するため、Pytest および Vitest のテストに条件付きロジックを追加しました。具体的には以下のように行います。
- 最初のターンを実行し、その後エージェントの出力を確認します。出力が期待通りであれば次のターンを実行します。
- もし期待通りでなければ、テストを早期に失敗させます(これは各ステップ後にチェックを追加できる柔軟性があったため可能でした)。
このアプローチにより、ありとあらゆるエージェントの分岐をモデル化することなく、マルチターン評価を実行できるようになりました。2 番目や 3 番目のターンを単独でテストしたい場合は、その時点から適切な初期状態でテストを設定するだけです。
#5: 適切な評価環境の構築が重要
Deep Agents は状態を保持し、複雑で長時間実行されるタスクに取り組むように設計されています。そのため、評価にはより複雑な環境が必要となることも多いです。
単純な LLM evals(環境が通常はステートレスな数個のツールに限定されているもの)とは異なり、Deep Agents では再現性のある結果を確保するために、各評価実行ごとに新鮮でクリーンな環境が必要です。
コーディングエージェントはこの点を明確に示しています。Harbor は、専用 Docker コンテナまたはサンドボックス内で動作する TerminalBench 用の評価環境を提供します。DeepAgents CLI ではより軽量なアプローチを採用しており、各テストケースごとに一時ディレクトリを作成し、その中でエージェントを実行します。
より広い視点で言えば:Deep Agent の評価では、各テストごとに環境がリセットされる必要があります。そうでなければ、評価結果は不安定(フラキー)になり、再現が困難になります。
ヒント:API リクエストをモックする
LangSmith Assist では、実際の LangSmith API への接続が必要です。ライブサービスに対して評価を実行すると、処理が遅く、コストがかかる可能性があります。代わりに、HTTP リクエストをファイルシステムに記録し、テスト実行時に再生することをお勧めします。Python の場合は vcr がよく機能します。JS の場合、Hono アプリ経由で fetch リクエストをプロキシさせる方法が有効です。
API リクエストのモック化や再生は、特に外部システムの状態に大きく依存するエージェントの場合、Deep Agent の評価を高速化し、デバッグを容易にします。
LangSmith で Deep Agents を評価する
上記のテクニックは、Deep Agent 搭載アプリケーション用のテストスイートを作成する際に私たちがよく目にした一般的なパターンです。特定のアプリケーションでは、上記のパターンの一部のみが必要となる可能性が高いです。したがって、評価フレームワーク が柔軟であることが重要です。Deep Agent の構築を開始し、評価の実装を始める場合は、LangSmith のテスト統合 をぜひご覧ください!
原文を表示
Over the past month at LangChain, we shipped four applications on top of the Deep Agents harness:
- DeepAgents CLI: a coding agent
- LangSmith Assist: an in-app agent to help with various things in LangSmith
- Personal Email Assistant: an email assistant that learns from interactions with each user
- Agent Builder: a no-code agent building platform powered by meta deep agents
Building and shipping these agents meant adding evals for each of them, and we learned a lot along the way! In this post, we’ll be diving deep into the following patterns for evaluating deep agents.
- Deep agents require bespoke test logic for each datapoint — each test case has its own success criteria.
- Running a deep agent for a single-step is great for validating decision-making in specific scenarios (and saves tokens too!)
- Full agent turns are great for testing assertions about the agent’s “end state”.
- Multiple agent turns simulate realistic user interactions but need to be kept on rails.
- Environment setup matters — Deep Agents need clean, reproducible test environments
Glossary
Before diving in, we’ll define a few terms we use throughout this post.
Ways to run an agent:
- Single step: Constrain the core agent loop to run for only one turn, determining the next action the agent will take.
- Full turn: Run the agent in its entirety on a single input, which can consist of multiple tool-calling iterations.
- Multiple turns: Run the agent multiple times in its entirety. Often used to simulate a “multi-turn” conversation between an agent and a user with several back-and-forth interactions.

Things we can test:
- Trajectory: The sequence of tools that are called by the agent, and the specific tool arguments the agent generates.
- Final response: The final returned response from the agent to the user.
- Other state: Other values that the agent generated while running (e.g. files, other artifacts)

#1: Deep Agents require more bespoke test logic (code) for each datapoint
Traditional LLM evaluation is straightforward:
1) Build a dataset of examples
2) Write an evaluator
3) Run your application over the dataset to produce outputs, and score those outputs with your evaluator
Every data point is treated identically — run through the same application logic, scored by the same evaluator.

Deep Agents breaks this assumption. You’ll want to test more than just the final message. The “success criteria” may be also more specific to each datapoint, and may involve specific assertions against the agent’s trajectory and state.
Consider this example:

We have a calendar scheduling deep agent that has the ability to remember user preferences. A user asks their agent to "remember to never schedule meetings before 9am". We want to assert that the calendar scheduling agent updates its own memories in its filesystem to remember this information.
In order to test this, we might want to write assertions to verify that:
1) The agent called edit_file on the memories.md file path
2) The agent communicated the memory update to the user in its final message
3) The memories.md file actually contains information about not scheduling early meetings. You could:
- Use regex to look for a mention of “9am”
- Or use an LLM-as-judge with specific success criteria for a more holistic analysis of the file update
LangSmith’s Pytest and Vitest integrations support this type of bespoke testing. You can make different assertions about the agent’s trajectory, final message, and state for each test case.
# Mark as a LangSmith test case
@pytest.mark.langsmith
def test_remember_no_early_meetings() -> None:
user_input = "I don't want any meetings scheduled before 9 AM ET"
# We can log the input to the agent to LangSmith
t.log_inputs({"question": user_input})
response = run_agent(user_input)
# We can log the output of the agent to LangSmith
t.log_outputs({"outputs": response})
agent_tool_calls = get_agent_tool_calls(response)
# We assert that the agent called the edit_file tool to update its memories
assert any([tc["name"] == "edit_file" and tc["args"]["path"] == "memories.md" for tc in agent_tool_calls])
# We log feedback from an llm-as-judge that the final message confirmed the memory update
communicated_to_user = llm_as_judge_A(response)
t.log_feedback(key="communicated_to_user", score=communicated_to_user)
# We log feedback from an llm-as-judge that the memories file now contains the right info
memory_updated = llm_as_judge_B(response)
t.log_feedback(key="memory_updated", score=memory_updated)For a general code snippet of how to use Pytest, check out these docs:
This LangSmith integration automatically logs all test cases to an experiment, so you can view traces for a failed test case (to debug what went wrong) and track results over time.
#2: Single step evals are valuable and efficient

When running our evals for Deep Agents, about half of our test cases looked like single step evals, i.e. what did the LLM decide to do immediately after a specific series of input messages?
This is especially useful for validating that the agent called the correct tool with the correct arguments in a specific scenario. Common test cases include:
- Did it call the right tool to search for meeting times?
- Did it inspect the right directory contents?
- Did it update its memories?
Regressions often occur at individual decision points rather than across full execution sequences. If using LangGraph, its streaming capabilities allow you to interrupt the agent after a single tool call to inspect the output — so you can catch issues early without the overhead of a complete agent sequence.
In the code snippet below, we manually introduce a break point before the tools node, allowing us to easily run the agent for a single step. We can then inspect and make assertions about the state after that single step.
@pytest.mark.langsmith
def test_single_step() -> None:
state_before_tool_execution = await agent.ainvoke(
inputs,
# interrupt_before specifies nodes to stop before
# interrupting before the tool node allows us to inspect the tool call args
interrupt_before=["tools"]
)
# We can see the message history of the agent, including the latest tool call
print(state_before_tool_execution["messages"])#3: Full agent turns give you a complete picture

Think of single-step evals as your “unit tests” that ensure the agent takes the expected action in a specific scenario. Meanwhile, full agent turns are also valuable — they show you a complete picture of the end-to-end actions that your agent takes.
Full agent turns let you test agent behavior in multiple ways:
1) Trajectory: A very common way to evaluate a full trajectory is to ensure that a particular tool was called at some point during action, but it doesn’t matter exactly when. In our calendar scheduler example, the scheduler might need multiple tool calls to find a suitable time slot that works for all parties.

2) Final Response: In some cases, the quality of the final output matters more than the specific path taken by the agent. We found this to be true for more open-ended tasks like coding and research.

3) Other State: Evaluating other state is very similar to evaluating an agent’s final response. Some agents will create artifacts instead of responding to the user in a chat format. Examining and testing these artifacts is easy by examining an agent’s state in LangGraph.
- For a coding agent → read and then test the files that the agent wrote.
- For a research agent → assert the agent found the right links or sources.
Full agent turns give you a complete picture of your agent execution. LangSmith makes it really easy to view your full agent turns as traces, where you can see high level metrics like latency and token use, while also analyzing specific steps down to each model call or tool invocation.
#4: Running an agent across multiple turns simulates full user interactions

Some scenarios require testing agents across multi-turn conversations that have multiple sequential user inputs. The challenge is that if you naively hardcode a sequence of inputs and the agent deviates from the expected path, the subsequent hardcoded user input may not make sense.
We addressed this by adding conditional logic in our Pytest and Vitest tests. For example, we would:
- Run the first turn, and then check the agent output.If the output was expected, run the next turn.
- If it was not expected, fail the test early. (This was possible because we had the flexibility to add checks after each step.)
This approach let us run multi-turn evals without having to model every possible agent branch. If we wanted to test the second or third turn in isolation, we simply set up a test starting from that point with appropriate initial state.
#5: Setting up the right eval environment is important
Deep Agents are stateful and designed to tackle complex, long-running tasks — often requiring more complex environments to evaluate in.
Unlike simpler LLM evals where the environment is limited to a few usually stateless tools, Deep Agents need a fresh, clean environment for each eval run in order to ensure reproducible results.
Coding agents illustrate this clearly. Harbor provides an evaluation environment for TerminalBench that runs inside a dedicated Docker container or sandbox. For DeepAgents CLI, we use a more lightweight approach: we create a temporary directory and run the agent inside it for each test case.
The broader point: Deep Agent evals require environments that resets per test -- otherwise your evals become flaky and difficult to reproduce.
Tip: Mock out your API requests
LangSmith Assist requires connecting to real LangSmith APIs. Running evals against live services can be slow and expensive. Instead, record HTTP requests into a filesystem and replay them during test execution. For Python, vcr works well; for JS, we proxy fetch requests through a Hono app works.
Mocking or replaying API requests makes Deep Agent evals faster and easier to debug, especially when the agent depends heavily on external system state.
Evaluate Deep Agents with LangSmith
The above techniques are common patterns we saw when writing our own test suites for deep agents powered applications. You likely only need a subset of the above patterns for your specific application — and as such, it’s important for your evaluation frameworkto be flexible. If you’re building a deep agent and getting started with evals, check out LangSmith’s testing integrations!
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み