エージェントのテストが異なる理由
非決定性やLLMのコストを踏まえ、標準的な単体テストでは不十分な理由を学びます。
「エージェントのテストが異なる理由」はCoddyKit上の無料AI Agentsレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Agents学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Agentsコースには全4レッスンが含まれています。
ソフトウェアのテストとエージェントのテスト
従来のソフトウェアは決定論的です。同じ入力を与えると、同じ出力が得られます。単体テストはこの性質を利用して、期待される値と完全に一致するかを検証します。
AIエージェントはこの前提を崩します。同じプロンプトでも実行するたびに異なる出力が生成される可能性があるため、標準的なテスト手法だけでは不十分です。
非決定性:同じ入力でも異なる出力
LLMは本質的に確率的です。temperatureパラメーターがランダム性を制御しますが、temperature=0であっても、モデルのバージョンやインフラストラクチャの変更によって出力が変わることがあります。
つまり、今日成功したエージェントのテストが、コードを変更していないのに明日失敗する可能性があります。
import openai
client = openai.OpenAI(api_key='YOUR_API_KEY')
# Same prompt, potentially different outputs each run
for i in range(3):
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': 'Name a planet.'}],
temperature=0.9 # High randomness
)
print(f'Run {i+1}: {response.choices[0].message.content}')
# Run 1: Mars
# Run 2: Jupiter
# Run 3: Saturnコストの問題:実際のLLM呼び出しには費用がかかる
OpenAIやAnthropicへの実際のAPI呼び出しを行うテストスイートは、1回の実行で数ドルかかることがあります。100個のテストを10回ずつ実行するCIパイプラインでは、月に数百ドルかかる可能性があります。
そのため、エージェントのテストを単体テストと同じ方法で実行するのは現実的ではありません。コストを管理するための戦略が必要です。
# A test that calls the real API costs tokens every run
# 100 tests x 500 tokens each x 10 CI runs/day = 500,000 tokens/day
# At $0.15/1M tokens (gpt-4o-mini): ~$0.075/day = ~$27/year for a tiny suite
# For gpt-4o: 15x more expensive = ~$400/year
# This is why mocking and recording API responses is essential
print('Real API calls in tests = expensive and slow')
print('Solution: Mock or record LLM responses in unit tests')
print('Reserve real calls for scheduled integration tests')レイテンシーの問題
実際のLLM API呼び出しには、通常2~20秒かかります。50個のテストがあるテストスイートでは、実行に100~1,000秒かかることになります。これは開発者の生産性を低下させます。優れたテストでは、高速なフィードバックが重要な価値となります。
LLM呼び出しをモックすると、テストをミリ秒単位で実行できます。
import time
# Simulating what a test suite looks like with real vs mocked calls
num_tests = 50
# Real API calls
real_time = num_tests * 5 # avg 5 seconds per call
print(f'With real API calls: {real_time}s = {real_time/60:.1f} minutes')
# Mocked calls
mock_time = num_tests * 0.001 # <1ms per mock
print(f'With mocked calls: {mock_time:.3f}s = nearly instant')
# Conclusion: mock in unit tests, use real calls in integration testsエージェントテストにおける外部依存関係
エージェントは、検索API、データベース、ファイルシステム、Webスクレイパーなどの外部ツールを呼び出すことがよくあります。テストでは、こうした依存関係によって次の問題が起こる可能性があります。
- 利用できない(ネットワーク障害やAPIの停止)
- 実行するたびに異なるデータを返す
- CIパイプラインを停止させるレート制限がある
単体テストでは、これらを制御するかモックする必要があります。
# An agent might call multiple external services
# Each is a potential test failure point
def agent_pipeline(query: str) -> str:
search_results = search_web(query) # External: Tavily/Serper API
documents = fetch_documents(search_results) # External: HTTP calls
answer = llm_summarize(documents) # External: OpenAI API
saved = database_store(answer) # External: PostgreSQL
return answer
# In unit tests: mock ALL of these
# In integration tests: use sandboxed versions of real services
print('Each external call is a test reliability risk')標準的な単体テストが前提とすること
pytestなどの標準的な単体テストフレームワークは、次のことを前提としています。
- テストが高速である(ミリ秒単位)
- テストが決定論的である
- テストに外部への副作用がない
- テストを任意の順序で実行できる
これらを考慮して明示的に設計しない限り、エージェントテストは4つすべての前提に反します。
# Standard unit test — works perfectly for deterministic code
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5 # Always passes — deterministic
# Agent 'unit test' that calls a real LLM — problematic
# def test_agent_answers_question():
# response = agent.run('What is 2+2?')
# assert response == '4' # Might return 'The answer is 4' or 'Four'
print('Exact string matching fails for LLM outputs')
print('Need structural or semantic assertions instead')エージェントのテストピラミッド
エージェントの実践的なテスト戦略は、ピラミッド構造に従います。
- 単体テスト(多数で高速):LLM呼び出しをモックして、個々のツールや関数をテストする
- 統合テスト(少数で低速):サンドボックス化したサービスを使い、エージェントのパイプラインをエンドツーエンドでテストする
- 評価テスト(まれで高コスト):実際のLLM呼び出しと人間に近い採点によって、出力の品質をテストする
構造的アサーションと意味的アサーション
エージェントのテストでは、文字列全体を完全一致させるのではなく、構造的アサーション(エージェントが正しいツールを呼び出したか)や意味的なチェック(出力に関連する概念が含まれているか)を使用してください。
# Fragile: exact string match
# assert response.content == 'The capital of France is Paris.'
# Better: structural assertion
# assert response.tool_calls[0]['function']['name'] == 'search_web'
# Better: semantic check
def test_capital_in_response(response_text: str) -> bool:
key_words = ['paris', 'france', 'capital']
lower = response_text.lower()
return all(word in lower for word in key_words)
response = 'Paris is the capital city of France.'
print(test_capital_in_response(response)) # True評価ハーネスとLLM-as-Judge
品質評価では、業界でLLM-as-judgeが使われています。2つ目のLLMにエージェントの出力を評価させる方法です。DeepEvalやRAGASなどのフレームワークが、このパターンを自動化します。
これは通常のCIではなく、高コストな評価の実行に限定して使用します。
import openai
client = openai.OpenAI(api_key='YOUR_API_KEY')
def llm_judge(question: str, answer: str) -> dict:
prompt = f'Question: {question}\nAnswer: {answer}\nRate the answer 1-5 for accuracy. Reply with only a number.'
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}]
)
score = int(response.choices[0].message.content.strip())
return {'score': score, 'pass': score >= 4}
# result = llm_judge('What is the capital of France?', 'Paris')
# print(result) # {'score': 5, 'pass': True}エージェントの回帰テスト
プロンプトを更新したりエージェントのロジックを変更したりしたとき、回帰テストによって既存の動作を壊していないことを確認できます。ゴールデン例(入力 → 期待される構造)を記録し、コミットするたびに自動で実行します。
# golden_examples.py
GOLDEN_EXAMPLES = [
{
'input': 'Search for the weather in Paris',
'expected_tool': 'get_weather',
'expected_args': {'city': 'Paris'}
},
{
'input': 'Calculate 15% tip on $45',
'expected_tool': 'calculate',
'expected_args': {'expression': '45 * 0.15'}
}
]
def run_regressions(agent, examples: list) -> int:
failures = 0
for ex in examples:
result = agent.plan(ex['input']) # mocked LLM
if result['tool'] != ex['expected_tool']:
print(f'FAIL: expected {ex["expected_tool"]}, got {result["tool"]}')
failures += 1
return failures
# --- demo: a stub agent whose .plan() mimics an LLM's tool choice ---
class _StubAgent:
def plan(self, text):
if 'weather' in text.lower():
return {'tool': 'get_weather'}
if 'tip' in text.lower() or 'calculate' in text.lower():
return {'tool': 'wrong_tool'} # simulate a regression
return {'tool': 'unknown'}
failures = run_regressions(_StubAgent(), GOLDEN_EXAMPLES)
print(f'{failures} of {len(GOLDEN_EXAMPLES)} golden examples failed')
基本的なエージェントテストファイルを設定する
以下は、エージェント用の最小限のpytestテストファイル構成です。高速な単体テスト(モックを使用)と低速な統合テスト(実際の呼び出しを使用)を分離できるため、必要なテストだけを実行できます。
# tests/test_agent.py
import pytest
# Fast unit tests — run on every commit
class TestAgentTools:
def test_tool_returns_dict(self, mock_llm):
result = my_tool(query='test')
assert isinstance(result, dict)
assert 'data' in result
def test_agent_selects_correct_tool(self, mock_llm):
response = agent.run('Search for Python tutorials')
assert response['tool_used'] == 'web_search'
# Slow integration tests — run nightly or on release
@pytest.mark.integration
class TestAgentIntegration:
def test_full_pipeline_with_real_api(self):
# Uses real OpenAI + sandboxed services
result = agent.run('Summarize the Python docs')
assert len(result['answer']) > 50理解度チェック:エージェントのテストが異なる理由
AIエージェントのテスト特有の課題についての理解度を確認しましょう。
復習:エージェントのテストが異なる理由
AIエージェントのテストには、標準的な単体テストとは異なる考え方が必要です。
- 非決定性:同じ入力でも、異なる有効な出力が生成されることがあります
- コスト:実際のLLM呼び出しには費用がかかるため、単体テストではモックを使用します
- レイテンシ:実際のAPI呼び出しには数秒かかりますが、モックは数ミリ秒で実行されます
- 外部依存関係:テストではツールやAPIを制御する必要があります
- アサーション:完全な文字列一致ではなく、構造的および意味的なチェックを使用します
テストピラミッドを使用します。つまり、モックを使った低コストの単体テストを多数用意し、実際の呼び出しを使う高コストの統合テストは少数にします。
よくある質問
「エージェントのテストが異なる理由」レッスンは無料ですか?
はい。「エージェントのテストが異なる理由」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Agentsコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Agentsコースには全4レッスンが含まれています。
「エージェントのテストが異なる理由」で何を学びますか?
非決定性やLLMのコストを踏まえ、標準的な単体テストでは不十分な理由を学びます。 ブラウザで直接実行するハンズオンコードでAI Agentsを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
AI Agentsを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのAI Agentsは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。
「エージェントのテストが異なる理由」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このAI Agentsレッスンでコードを書いて実行できますか?
はい。すべてのAI Agentsレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- エージェントのテストが異なる理由
- テストにおけるLLM呼び出しのモック
- アサーションベースのエージェントテスト
- エージェントパイプラインの統合テスト