test-crewai-project について
CrewAIのcrewとFlowをテストするためのガイドです。APIキーなしで決定論的なテストを行い、実際の実行を調査したい開発者向けに、スタブLLM、プロンプトやルーティングの検証、ガードレールテスト、`crewai test`の動作を説明します。
主な機能
- 01スタブLLMを使ったcrewのテスト
- 02プロンプト内容とツール実行の検証
- 03ガードレールとFlowルーティングのテスト
- 04`crewai test`の評価動作の解説
- 05イベントとトレースに関する注意点
活用シーン
- 01CIでのオフライン単体テスト
- 02プロンプト入力とタスクコンテキストの確認
- 03ガードレールの合格判定と再試行のテスト
- 04CrewAI評価実行の調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ crewaiinc/ skills --skill test-crewai-projectインストール後、Claude Code で/test-crewai-projectを入力して読み込まれたか確認します。
同梱ファイルreferences/example-tests.mdreferences/stub_llm.py
詳細
CrewAI プロジェクトをテストする
API キーなしでクルーとフローを決定論的にテストする方法と、イベントやトレースを使って実際の実行をデバッグする方法です。
2026-10-01 に crewai 1.15.22 および 1.15.23 で検証済みです。
2026-10-01 に実際の LLM を使ってライブテスト済みです。
まず crewai version を実行してください。メジャーまたはマイナーバージョンが異なる場合は、ask-docs スキルでバージョンに依存する行を再検証してください。
1. テストにスタブ LLM が必要な理由
実際の LLM は実行のたびに異なるテキストを返し、費用がかかるうえ、キーも必要です。明示的な llm が設定されていない Agent はすべて gpt-4.1-mini を解決先とするため、キーが設定されていない場合はキックオフ時に ValueError: OPENAI_API_KEY is required が発生します。そのため、シークレットのない CI 実行は開始することさえできません。予測できない出力をアサートすることはできません。
スタブ LLM を使うと応答が固定されるため、正確なアサーションが可能になります。プロンプトの構築、{placeholder} の補間、タスクコンテキスト、ツールの実行、ガードレールの再試行、pydantic への変換、フローのルーティングといった、実際の crewai のコードパスはすべてそのまま使われます。また、すべての呼び出しが記録されるため、crewai が構築したプロンプトをアサートできます。多くのバグは、入力の欠落、コンテキストの脱落、ツールの未登録など、ここで発生します。
段階的にテストします。
| Tier | 必要なもの | 検証できること |
|---|---|---|
| 単体テスト:スタブ LLM、通常の pytest | なし | 配線、プロンプト、ルーティング、解析、ガードレールのロジック |
| ガードレールの真理値表 | なし | 各ガードレールが正しい出力を受け入れ、拒否すること |
実モデルでの実行(crewai test、スモークキックオフ) | プロバイダーのキー、費用 | プロンプトの品質。@pytest.mark.credentialed を付け、オフラインスイートは pytest -m "not credentialed" で実行します |
2. 1.15 におけるカスタム BaseLLM の要件
from crewai import BaseLLM。抽象メソッドは call だけです。crewai は from_task、from_agent、および(構造化タスクの場合)response_model をキーワード引数として渡すため、call はこれらを受け取れる必要があります。
call のシグネチャ | 結果 | |
|---|---|---|
| 不適切(公開されているカスタム LLM ドキュメントの例) | def call(self, messages, tools=None, callbacks=None, available_functions=None) | キックオフ時に TypeError: MyLLM.call() got an unexpected keyword argument 'from_task' |
| 適切 | def call(self, messages, tools=None, callbacks=None, available_functions=None, from_task=None, from_agent=None, response_model=None, **kwargs) | 実行される |
| 要件 | 省略した場合の動作 |
|---|---|
from_task、from_agent、response_model(または **kwargs)を受け取る | 最初の LLM 呼び出し時に TypeError |
空でない model=(例:MyLLM(model="my-model"))を渡す | 構築時に ValidationError |
with llm_call_context(): 内で self._emit_call_started_event(...) / self._emit_call_completed_event(...)(crewai.llms.base_llm から)を呼び出す | イベントリスナーとトレースに LLM 呼び出しが表示されない(セクション 9 を参照) |
messages をプロバイダー SDK に渡す場合は、role と content だけを転送する | crewai は各メッセージに cache_breakpoint: True を追加します。Anthropic API はこれを 400 ... messages.0.cache_breakpoint: Extra inputs are not permitted で拒否します |
supports_function_calling() が False を返す | crewai はツールに ReAct のテキストループ(Thought / Action / Action Input)を使用します |
response_model が渡された場合、検証済みのモデルインスタンスを返す | ネイティブプロバイダーはこのように動作します。テキストを返しても機能しますが、その場合にガードレールから output.pydantic がどう見えるかについては、セクション 6 を参照してください |
references/stub_llm.py にはこれらすべてが実装されています。独自に書く必要がない限り、これをコピーして使ってください。
3. テストハーネスをセットアップする
references/stub_llm.pyをプロジェクト内のtests/stub_llm.pyにコピーします。uv add --dev pytestを実行し、uv run pytestでテストを実行します。- pytest の意味で
uv run testを実行しないでください。スキャフォールドされた crew プロジェクトでは、testはcrewai test(セクション 8)を実行するプロジェクトスクリプトであり、実際のモデルを呼び出します。 tests/.crewai-test-storage/を.gitignoreに追加します。
# tests/conftest.py
import os
import socket
from pathlib import Path
# Optional, test-only switches. The AGENTS.md that `crewai create` writes leaves
# turning observability off to the user, so ask before uncommenting them.
# Without them the socket guard below does not cover everything:
# - crewai telemetry is still sent to telemetry.crewai.com when the test process
# exits, after the guard has been undone;
# - if tracing is on (CREWAI_TRACING_ENABLED=true or tracing=True) and a CrewAI
# credential exists (CREWAI_USER_PAT, or just a saved `crewai login` on the
# machine), kickoff requests a trace grant, the guard refuses it, and the test
# fails with TraceGrantError.
# If uncommented, they must run before crewai is imported; never put them in the project's .env.
# os.environ["CREWAI_DISABLE_TELEMETRY"] = "true"
# os.environ["CREWAI_TRACING_ENABLED"] = "false"
# os.environ["OTEL_SDK_DISABLED"] = "true"
# Absolute path: crewai's SQLite files and memory store go here instead of your user data dir.
os.environ["CREWAI_STORAGE_DIR"] = str(Path(__file__).parent / ".crewai-test-storage")
import pytest
@pytest.fixture(autouse=True)
def offline(monkeypatch, tmp_path):
def refuse(*args, **kwargs):
raise RuntimeError("test tried to use the network")
monkeypatch.setattr(socket.socket, "connect", refuse)
monkeypatch.chdir(tmp_path) # output_file= and flow file writes land in a temp dir
各行の理由:
import crewaiだけで<platform user data dir>/<cwd name>/が作成されます。macOS では~/Library/Application Support/<cwd name>/です。Kickoff によってそこに SQLite ファイルが書き込まれます。インポート前に絶対パスのCREWAI_STORAGE_DIRを設定すると、すべての保存先が変更されます。- ソケットガードにより、誤って実際の LLM を呼び出すと、ハングしたり料金が発生したりする代わりに、即座に失敗します。
- crewai の内部処理は、kickoff のたびに
DeprecationWarningを出力します。これを抑制するには:
# pyproject.toml
[tool.pytest.ini_options]
filterwarnings = ["ignore::DeprecationWarning:crewai.*"]
4. スタブを使った実行
StubLLM は、次の 3 つのソースからこの順番で各応答を選びます。
| ソース | 用途 |
|---|---|
responses=[...] | LLM 呼び出しごとに、呼び出し順に 1 項目ずつ使用します。項目は str、dict/list(JSON として送信)、pydantic モデル、または f(messages, response_model) です |
responder=f(messages, response_model) | キューが空になった後に使用します。呼び出し順ではなく、プロンプトの内容やスキーマに基づいて判断できます |
default_response="..." | 上記の両方を使用した後に使われます。これを設定せず、crewai が response_model を要求した場合、スタブはそのモデルのスケルトン JSON を返します |
ツール呼び出しは ReAct ループを経由します。tool_call(name, args) は Thought/Action/Action Input 形式のテキストを作成します。実際のツールの _run が実行され、その結果は Observation: ... として次の LLM 呼び出しに返されます。
# tests/test_structured_and_tools.py
from pydantic import BaseModel
from crewai import Agent, Crew, Task
from crewai.tools import tool
from stub_llm import StubLLM, prompt_text, tool_call
class Brief(BaseModel):
title: str
bullets: list[str]
tool_inputs = []
@tool("Word Counter")
def word_counter(text: str) -> int:
"""Count the words in a piece of text."""
tool_inputs.append(text)
return len(text.split())
def test_tool_call_then_structured_answer():
llm = StubLLM(responses=[
tool_call("Word Counter", {"text": "bees make honey"}), # researcher: use the tool
"Final Answer: 3 words", # researcher: answer
{"title": "Bees", "bullets": ["honey"]}, # writer: JSON for Brief
])
researcher = Agent(role="Researcher", goal="Count words", backstory="Careful.",
tools=[word_counter], llm=llm)
writer = Agent(role="Writer", goal="Write briefs", backstory="Concise.", llm=llm)
count = Task(description="Count the words", expected_output="A count", agent=researcher)
brief = Task(description="Write a brief", expected_output="Title and bullets",
agent=writer, output_pydantic=Brief)
out = Crew(agents=[researcher, writer], tasks=[count, brief]).kickoff()
assert tool_inputs == ["bees make honey"] # the real tool ran
assert "Observation: 3" in prompt_text(llm.calls[1]) # its result went back to the LLM
assert llm.calls[0]["response_model"] is None # agents WITH tools get no response_model
assert llm.calls[2]["response_model"] is Brief
assert out.pydantic == Brief(title="Bees", bullets=["honey"])
スタブテストを安定させるためのルール:
| ルール | 理由 |
|---|---|
output_pydantic / output_json タスクごとに、有効な JSON(dict)をキューに追加します | 解析できない回答があると、crewai はコンバーターによる LLM 呼び出しを追加で行います。その呼び出しが後続のキュー項目を消費し、最終的に ConverterError が発生します |
out.pydantic に値を設定するのは output_pydantic です | Task(response_model=X) だけではスキーマが LLM に送られるだけで、out.pydantic は None のままです |
呼び出し回数は out.token_usage ではなく len(llm.calls) で数えます | Crew の使用量は、各 LLM オブジェクトの存続期間にわたり、エージェントごとに合算されます。2 つのエージェントで 1 つのスタブを共有すると 2 回カウントされ、2 つ目の crew でスタブを再利用すると、1 つ目の crew のトークンも引き継がれます |
呼び出し順の予測が難しい場合は、responder を使い、response_model またはプロンプトのテキストに基づいて判断します | キューは、正確な呼び出し順がわかっている場合にのみ機能します |
kickoff_for_each では、新しい crew を作成します | すでに kickoff した crew では、各項目で前回の kickoff の入力が暗黙的に再利用されます |
5. crew をテストし、プロンプトを検証する
LLM を注入できるようにします。ファクトリー関数では llm=None を受け取り、すべての Agent に渡します(None の場合、本番環境では crewai のデフォルトモデルが使われます)。スキャフォールドされた @CrewBase クラスでは、.crew() の後に LLM を差し替えます。
# tests/test_crew.py
from pathlib import Path
from acme_research.crew import AcmeResearch
from stub_llm import StubLLM, prompt_text
def crew_with(llm):
crew = AcmeResearch().crew()
for agent in crew.agents:
agent.llm = llm
return crew
def test_research_crew_output_and_prompts():
llm = StubLLM(responses=["- bees pollinate crops", "# Bees report"])
out = crew_with(llm).kickoff(inputs={"topic": "bees", "current_year": "2026"})
assert out.raw == "# Bees report"
assert [t.raw for t in out.tasks_output] == ["- bees pollinate crops", "# Bees report"]
assert len(llm.calls) == 2
first = prompt_text(llm.calls[0])
assert "bees Senior Data Researcher" in first # {topic} filled in agents.yaml
assert "the current year is 2026" in first # {current_year} in tasks.yaml
assert "- bees pollinate crops" in prompt_text(llm.calls[1]) # task 1 output is task 2 context
assert Path("report.md").read_text() == "# Bees report" # output_file, in tmp cwd
llm.calls[i] には、messages(crewai が送信した system メッセージと user メッセージのスナップショット)、response_model、tools、response が格納されています。prompt_text(call) はメッセージの内容を結合します。次のような項目を検証すると便利です。
| 検証項目 | 検出できる問題 |
|---|---|
| 入力値がプロンプトに含まれている | inputs を指定しない kickoff(文字列の {topic} がプロンプトに残ります。キーが 1 つでも不足している dict を渡した場合は、代わりに ValueError が発生します) |
| 前のタスクの出力が次のプロンプトに含まれている | context= の不具合 |
Tool Name: <snake_case name> が含まれている | ツールが紐付けられていないこと。@tool("Word Counter") は word_counter として出力されますが、Action: Word Counter でも引き続き解決されます |
| 再試行プロンプトにガードレールのエラーが含まれている | フィードバックがモデルに届かないガードレール |
分岐で llm.calls == [] である | モデルを呼び出すべきではないフロー分岐で、モデルが呼び出されていること |
6. ガードレールのテスト
関数型ガードレールは、受け入れる場合は (True, value) を、拒否する場合は (False, "error for the LLM") を返します。2 通りの方法でテストします。単純な関数として出力の真理値表を使ってテストする方法と、クルー内でリトライを確認する方法です。ガードレールが失敗し続けると、guardrail_max_retries 回のリトライ後(デフォルトは 3 回)に Task failed guardrail validation after N retries が発生します。真理値表と諦める場合のテストは、example-tests.md のセクション 1 にあります。
# tests/test_guardrails.py
from pydantic import BaseModel
from crewai import Agent, Crew, Task
from crewai.tasks.task_output import TaskOutput
from stub_llm import StubLLM, prompt_text
class Brief(BaseModel):
title: str
bullets: list[str]
def max_three_bullets(output: TaskOutput):
try: # output.pydantic can be None here, so fall back to parsing raw
brief = output.pydantic or Brief.model_validate_json(output.raw)
except ValueError:
return (False, "Return JSON matching the Brief schema.")
if len(brief.bullets) > 3:
return (False, f"Use at most 3 bullets, you used {len(brief.bullets)}.")
return (True, output.raw) # a string: crewai re-runs the pydantic conversion
def brief_crew(llm):
writer = Agent(role="Writer", goal="Write briefs", backstory="Concise.", llm=llm)
task = Task(description="Write a brief", expected_output="Title and up to 3 bullets",
agent=writer, output_pydantic=Brief,
guardrail=max_three_bullets, guardrail_max_retries=2)
return Crew(agents=[writer], tasks=[task])
def test_retry_feeds_the_error_back():
llm = StubLLM(responses=[{"title": "T", "bullets": ["a", "b", "c", "d"]},
{"title": "T", "bullets": ["a"]}])
out = brief_crew(llm).kickoff()
assert out.pydantic == Brief(title="T", bullets=["a"])
assert "Use at most 3 bullets, you used 4." in prompt_text(llm.calls[1])
注意すべき点が 2 つあります。
- ガードレール内では
output.pydanticがNoneの場合があります。 タスクにガードレールがあり、LLM がテキストを返した場合、crewai は最初のガードレール呼び出し前に pydantic 変換をスキップします。エージェントにツールがある場合や、文字列を返すカスタム LLM の場合、LLM はテキストを返します。output.rawを自分で解析してください。 return (True, output)ではout.pydanticがひそかに失われる場合があります。output.pydanticがNoneだった場合、crewai はその TaskOutput をそのまま保持します。(True, output.raw)を返すと、crewai が文字列を変換します。その後、境界でout.pydantic is not Noneをアサートしてください。
7. フローのテスト
フローメソッドは通常のメソッドなので、1 つのステップをテストするには直接呼び出します。ルーティングをエンドツーエンドでテストするには、kickoff(inputs=...) を使用します。kickoff は最後に実行されたメソッドの戻り値を返し、inputs のキーは @start の実行前に state フィールドを上書きします。
# tests/test_flow_routing.py
from pydantic import BaseModel
from crewai.flow import Flow, listen, or_, router, start
class State(BaseModel):
topic: str = ""
log: list[str] = []
class TopicFlow(Flow[State]):
@start()
def normalize(self):
self.state.topic = self.state.topic.strip().lower()
@router(normalize)
def choose(self):
return "has_topic" if self.state.topic else "no_topic" # labels differ from method names
@listen("has_topic")
def research(self):
self.state.log.append("research")
@listen("no_topic")
def skip(self):
self.state.log.append("skip")
@listen(or_(research, skip))
def finish(self):
return f"done:{self.state.topic or '-'}"
def test_step_directly():
flow = TopicFlow()
flow.state.topic = " Bees "
flow.normalize()
assert flow.state.topic == "bees"
assert flow.choose() == "has_topic"
def test_both_branches_via_kickoff():
flow = TopicFlow()
assert flow.kickoff(inputs={"topic": " Bees "}) == "done:bees"
assert flow.state.log == ["research"]
flow = TopicFlow()
assert flow.kickoff(inputs={"topic": " "}) == "done:-"
assert flow.state.log == ["skip"]
crewai create flow のスキャフォールドが ContentCrew().crew() で行うように、フローのステップがそれ自体でクルーを構築する場合、スタブを渡すパラメーターはありません。monkeypatch.setattr でクルークラスの crew メソッドをラップする関数に置き換え、元のメソッドを呼び出した後、すべてのエージェントで agent.llm = stub を設定します。完全なテストは example-tests.md のセクション 2 にあります。
Flow は pydantic モデルです。フローコードを管理している場合、フロークラスに llm: Any = None を宣言し、MyFlow(llm=stub) を渡す方が、パッチを当てるよりすっきりした接続点になります。x という名前のメソッドに @listen("x") を付けると、フローのインスタンス化時にエラーが発生するため、フローを構築するだけのテストでも検出できます。
8. crewai test が実際に行うこと
crewai test はモデル評価による評価であり、ユニットテストランナーではありません。
| 事実 | 詳細 |
|---|---|
| 実行内容 | uv run test <n> <model>。つまり、プロジェクトの main.test() を実行し、これは Crew.test(n_iterations, eval_llm=model, inputs=...) を呼び出します |
| デフォルト | -n 3、-m gpt-5.4-mini(古いドキュメントでは 2 と gpt-4o-mini) |
| 評価モデル | --help では OpenAI モデルのみと記載されていますが、-m は任意のプロバイダー文字列を指定できます。プロジェクトにプロバイダーの追加パッケージがある場合(uv add "crewai[anthropic]")、crewai test -m anthropic/claude-haiku-4-5 が動作します |
| 各イテレーション | 独自の LLM を使ったクルーの完全な実際の kickoff と、タスクごとに 1 回の評価呼び出し |
| スコアリング | -m モデルを使用する評価エージェントが各タスクを 1 から 10 で採点し、Tasks/Crew/Agents テーブルを表示します。テーブルにはタスクごとに 1 行、Crew 行、Execution Time (s)、実行ごとに 1 列、Avg. Total が含まれます |
| 必要なもの | クルーのモデルと評価モデルの両方のキーが必要です。評価モデルは最初のタスクの後に実行されるため、評価モデルのキーが不足していると判明する時点では、そのタスクはすでに実行され、課金されています。エラーは An error occurred while testing the crew: OPENAI_API_KEY is required、または無効なキーの場合は Error code: 401 です |
| 終了コード | 実行に失敗しても 0 なので、CI ではこれを信頼できません |
| 副作用 | 実行するクルーのコピー上で各タスクの callback を置き換えます。タスクのスコアごとに Sync handler error in on_crew_test_result をログに記録しますが、実行は失敗しません |
| フロープロジェクト | フローのスキャフォールドには test スクリプトがないため、uv run test はシェルの test 組み込みコマンドに到達し、test: 1: unexpected operator が発生します |
実際のモデルを使い、プロンプト品質を定期的にチェックする用途には使用してください。費用がかかり、スコアも決定的ではないため、CI には使用しないでください。Crew.test は eval_llm として BaseLLM を受け取るため、クルーと評価モデルの両方にスタブを使えば、オフラインで実行できます。これは配線のみをチェックします(example-tests.md のセクション 4)。
9. デバッグ用イベントリスナー
実行のたびに、crew、task、agent、LLM 呼び出し、tool、flow、memory の型付きイベントが crewai_event_bus に送信されます。これらは crewai.events からインポートしてください。crewai.utilities.events は存在しなくなりました。
BaseEventListener をサブクラス化し、setup_listeners(self, bus) を実装して、その中で @bus.on(EventClass) を使ってハンドラーを登録します。各ハンドラーのシグネチャは handler(source, event) です。リスナーをインスタンス化すると、ハンドラーが登録されます。example-tests.md のセクション 3 には、デバッグ用リスナー(ToolUsageFinishedEvent、LLMCallCompletedEvent、TaskFailedEvent)とレコーダーのテストがあります。
テストでは、with crewai_event_bus.scoped_handlers(): の中でリスナーを登録し、処理後にリスナーが解除されるようにしてください。ハンドラーはワーカースレッドで実行されるため、アサーションの前に crewai_event_bus.flush() を呼び出してください。
- 実際の実行をデバッグするには、リスナーを専用のモジュールに記述し、
main.pyからインポートして一度だけインスタンス化してください。その他に役立つイベントは、LLMCallFailedEvent、MemorySaveFailedEvent、CrewKickoffStartedEvent、FlowFinishedEventです。 LLMCallCompletedEvent.responseはテキストです。ネイティブのツール呼び出しの場合は、プロバイダーのツール使用ブロックのリストです。event.modelとevent.usageには、モデルとトークン数が含まれます。- 独自の
BaseLLMは、LLM 呼び出しイベントを自ら送信する必要があります。 組み込みプロバイダーはLLMCallStartedEventとLLMCallCompletedEventを送信します。テキストを返すだけのサブクラスは何も送信しないため、トレースリスナーも同じイベントを購読していることから、リスナーもトレースもすべての LLM 呼び出しを検出できません。
10. トレーシングとテレメトリーの切り替え
これらは別々のシステムです。テレメトリーは、CrewAI に送信される匿名の使用状況統計です。トレーシングは、CrewAI AMP で確認する実行トレース(プロンプト、入力、出力、ツール呼び出し)です。
| 変数 | 使用できる値(1.15.22-1.15.23) | 効果 |
|---|---|---|
CREWAI_DISABLE_TELEMETRY | true、1、yes、on(大文字・小文字を問わず、前後の空白を除去)は無効化します。false、0、no、off、空の場合は有効のままです。それ以外は警告を出して無視されます | CrewAI のテレメトリーを無効にします |
CREWAI_DISABLE_TRACKING | 上記と同じ | CrewAI のテレメトリーを無効にします |
OTEL_SDK_DISABLED | 同じ値で CrewAI のテレメトリーを無効化します。true(大文字・小文字を問わず)のみ、実行トレーシングもブロックし、OpenTelemetry SDK 自体を無効にします | 1 は完全な停止スイッチではありません |
CREWAI_TRACING_ENABLED | true / 1 はトレーシングを強制的に有効にします。false / 0 は強制的に無効にします。yes、on、no、off は無視され、保存された同意設定に従います | プロセスごとのトレーシングゲート |
Crew(...) または Flow の tracing=True/False | bool | 環境変数より優先されます |
トレーシングの優先順位は、tracing= 引数、CREWAI_TRACING_ENABLED、crewai traces enable|disable で保存された同意設定の順です。保存された同意設定はプロジェクトディレクトリ名ごと(または CREWAI_STORAGE_DIR ごと)に保存されるため、チェックアウトの名前を変更するとリセットされます。crewai traces status には、ログイン済みかどうかではなく、環境変数(未設定の場合は false)と保存された同意設定が表示されます。
ルール:
- オブザーバビリティを無効にするのは、テスト時かつユーザーが選択した場合に限ってください。 切り替え設定は
tests/conftest.pyまたは CI テストジョブの環境に記述してください。本番環境のデバッグができなくなるため、プロジェクトの.env、main.py、デプロイ環境の環境変数には絶対に記述しないでください。 - 値は
true/falseと記述してください。すべての無効化スイッチで完全に認識される表記はtrueのみです。また、CREWAI_TRACING_ENABLEDはyes/no/on/offを無視します。 - 1 回の実行をデバッグするには、
CREWAI_TRACING_ENABLED=true crewai runまたはtracing=Trueを使用してください。トレースの送信先は、CrewAI の認証情報(CREWAI_USER_PATまたは保存済みのcrewai login。PAT は不要)があるかどうかによって異なります。- 認証情報がある場合: kickoff は最初にトレース許可を要求し、その後、ターミナルの有無にかかわらず、そのログインアカウントで CrewAI AMP にトレースをアップロードします。1.15.23 では
View traces:リンクが表示されますが、1.15.22 では表示されません。許可の要求に失敗すると、クルーの実行前に kickoff がTraceGrantErrorを発生させます。 - 認証情報がなく、1.15.23 で対話型ターミナルを使用する場合: トレーシングを有効にすると、同意したものとみなされます。トレースはプロンプトなしでエフェメラルトレースとしてアップロードされ、
Ephemeral Execution Tracesリンクが表示されます。同意設定は保存されません。 - 認証情報がなく、1.15.22 で対話型ターミナルを使用する場合: 実行後に crewai が
Share this execution trace with CrewAI? [y/N]と尋ねます。 - 認証情報がなく、ターミナルを使用しない場合(CI、pytest、
< /dev/null): どちらのリリースでもトレースは破棄されます。 - スイッチを設定せずに初めて実行する場合: crewai はローカルで自動的に収集し、ターミナルで
Share this execution trace with CrewAI? [y/N] (20s timeout)と尋ね、回答を同意設定として保存します。
- 認証情報がある場合: kickoff は最初にトレース許可を要求し、その後、ターミナルの有無にかかわらず、そのログインアカウントで CrewAI AMP にトレースをアップロードします。1.15.23 では
- 1.15.23 以降、crewai はすべての
BaseLLMサブクラスのcallをレート制限リトライでラップします。「rate limit」または 429 に言及するエラーを発生させるスタブは、エラーが表面化するまでに約 3 秒かけて 3 回呼び出されます。独自のレート制限処理をテストするには、別のエラー型を発生させるか、リトライ後の呼び出し回数をアサートしてください。 - トレースにはタスクのテキスト、入力、出力が含まれます。データがマシンの外部に送信されても問題ない場合を除き、トレース付き実行では合成データを使用してください。
よくある間違い
| 症状 | 原因 | 対処 |
|---|---|---|
TypeError: ... call() got an unexpected keyword argument 'from_task' | ドキュメントの例のシグネチャをそのまま使ってカスタム LLM を実装している | from_task、from_agent、response_model、**kwargs を受け取るようにします |
CI で ValueError: OPENAI_API_KEY is required が発生する | エージェントがデフォルトの gpt-4.1-mini を使い続けている | すべてのエージェントにスタブを注入します(セクション 5) |
| テストが停止する、またはアカウントに料金が発生する | 実際の LLM 呼び出しが紛れ込んでいる | conftest.py のソケットガードを使います |
uv run test が OpenAI を呼び出す | test はスキャフォールドの crewai test スクリプトです | uv run pytest を使います |
| キューの回答が別のタスクに割り当てられる | 追加のコンバーター呼び出しやガードレールの再試行がキューの項目を消費した | 有効な JSON をキューに入れ、response_model をキーにした responder を使います |
out.pydantic is None なのに実行が「成功」する | output_pydantic= を指定せずに response_model= を使っている、またはガードレールが (True, output) を返した | output_pydantic を使い、ガードレールは (True, output.raw) を返すようにし、None でないことをアサートします |
| ガードレールが有効な JSON を初回で拒否する | output.pydantic を読み取っていたが、これは None だった | output.raw を解析します |
| リスナーまたはトレースに LLM 呼び出しが表示されない | カスタム LLM がイベントを出力しない | llm_call_context() 内でイベントを出力します(スタブは出力します) |
| テスト間でリスナーのカウントが増える | ハンドラーがグローバルに登録されている | crewai_event_bus.scoped_handlers() と flush() を使います |
token_usage が呼び出し回数の 2 倍になる、またはテストごとに増える | 1 つの LLM インスタンスを複数のエージェントで共有している、または複数の crew で再利用している | crew ごとに新しいスタブを用意し、len(llm.calls) をアサートします |
| テスト後にユーザーデータディレクトリ内に新しいディレクトリができる | インポート時に CREWAI_STORAGE_DIR が設定されていない | conftest.py の先頭で絶対パスに設定します |
pytest で TraceGrantError: AMP trace grant request failed (RuntimeError) が発生する | トレーシングが有効で、保存済みの crewai login または CREWAI_USER_PAT があり、ソケットガードも有効になっている | conftest.py で CREWAI_TRACING_ENABLED=false を設定します(テストのみ) |
CI で crewai test が「成功」したのに、スコアがない | 実行が失敗しても終了コード 0 を返す | 出力にスコア表があるか確認します |
OTEL_SDK_DISABLED=1 でもトレーシングが実行される | 1 はテレメトリーのみを無効にし、トレーシングもブロックするのは true のみ | CREWAI_TRACING_ENABLED=false を設定します(テストのみ) |
チェックリスト
-
tests/stub_llm.pyをreferences/stub_llm.pyからコピーし、pytest を開発用依存関係に追加する -
conftest.pyで crewai をインポートする前に絶対パスのCREWAI_STORAGE_DIRを設定してネットワークをブロックする。可観測性の切り替え設定のコメントを外すのは、ユーザーが選択した場合のみ - すべてのエージェントの LLM を注入できるようにする(ファクトリーパラメーター、
.crew()の後に差し替え、または crew クラスへのパッチ) - 各構造化タスクでスタブから有効な JSON を受け取り、テストで
out.pydantic is not Noneをアサートする - 少なくとも 1 つのテストでプロンプトテキスト(入力、コンテキスト、ツール一覧)をアサートする
- 各ガードレールに真理値表のテストと再試行テストを用意する
- 各フロールーターの分岐にキックオフテストを用意し、主要なステップも直接呼び出す
- 実際のモデルを使うテストに
credentialedマークを付け、デフォルトの CI から除外する - 可観測性の切り替え設定はテスト設定にのみ記述し、
.envやデプロイ環境変数には記述しない
リファレンス
- stub_llm.py — オフラインで決定的な動作をする
StubLLMと、tool_call()およびprompt_text()ヘルパーです。tests/にコピーしてください。 - テストの例 — ガードレールの真理値表とリトライ上限到達、ステップ内でクルーを構築するスキャフォールド付きフロー、イベントリスナーのテスト、オフラインでの
Crew.testの接続確認です。 - 公開ドキュメント:https://docs.crewai.com/en/concepts/event-listener、https://docs.crewai.com/en/observability/tracing、https://docs.crewai.com/en/telemetry
関連スキル:
- check-crewai-api — 現行の Agent/Task/Crew API、構造化出力フィールド、ガードレールの仕様
- build-flow — フローの状態、ルーター、
or_/and_、永続化 - connect-tools-and-mcp — スタブが操作するツールの構築
- design-task — テストする価値のあるガードレールと expected_output の記述
- ask-docs — バージョンによって変わる可能性のある項目を、最新のドキュメントで確認