build-flow について
CrewAI Flowプロジェクトで、ステップの接続、型付き状態、永続化、crewやagentの呼び出しを実装するための実践ガイドです。現行APIを対象とし、CrewAI 1.15.22および1.15.23で検証したバージョン依存の注意点も扱います。
主な機能
- 01Flowプロジェクトの作成と実行
- 02型付き状態と辞書形式の状態の解説
- 03start、listen、routerの接続方法
- 04永続化と状態復元の説明
- 05Flow内からcrewやagentを呼び出す方法
活用シーン
- 01CrewAI CLIを使ったFlowの作成
- 02状態検証やルーティング問題の調査
- 03Flow実行の永続化と再開
- 04同期または非同期ステップからのcrew呼び出し
MCP 対応
非同期FlowメソッドでMCPサーバーに接続するには、`crewai.mcp`の`MCPServerHTTP`を使うよう説明しています。この状況では、`mcps`の文字列参照からツールが解決されない場合があります。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ crewaiinc/ skills --skill build-flowインストール後、Claude Code で/build-flowを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/conversational-flows.mdpersistence-and-checkpoints.mdrouting-patterns.md詳細
CrewAI Flow を構築する
正しく接続し、型付きの状態を保持し、永続化して再開でき、クルーやエージェントを呼び出せる Flow を、現行 API で記述する方法です。
2026-10-01 に crewai 1.15.22 および 1.15.23 で検証済みです。
2026-10-01 に CrewAI AMP と実際の LLM を使ってライブテスト済みです。
まず crewai version を実行してください。メジャーまたはマイナーバージョンが 1.15 と異なる場合は、バージョン依存の項目を信頼する前に、ask-docs スキルで再検証してください。
1. Flow プロジェクトをスキャフォールドして実行する
crewai create flow lead-router # the package is still lead_router
cd lead_router
crewai install # uv sync; writes uv.lock
crewai run # runs the `kickoff` script in pyproject.toml
crewai flow plot # runs the `plot` script
スキャフォールドで生成される内容:
| ファイル | 内容 |
|---|---|
pyproject.toml | [project.scripts] の kickoff、run_crew(どちらも <pkg>.main:kickoff)、plot、run_with_trigger;[tool.crewai] type = "flow" |
src/<pkg>/main.py | Flow[ContentState] のサブクラスと、kickoff()、plot() 関数;from crewai.flow import Flow, listen, start |
src/<pkg>/crews/content_crew/ | config/agents.yaml と config/tasks.yaml を持つ従来型の @CrewBase クルー。エージェントには llm が設定されていないため、デフォルトの OpenAI モデルが使われます |
.env | OPENAI_API_KEY=YOUR_API_KEY — プレースホルダーです。この値で実行すると 401 ... Incorrect API key provided エラーになります。シェルでエクスポートした OPENAI_API_KEY は .env より優先されます。有効なキーをエクスポートすると、変更していないスキャフォールドの crewai run は gpt-4.1-mini で 3 つのエージェントを実行し、output/post.md に書き込みました |
AGENTS.md、CLAUDE.md、GEMINI.md(1.15.23 以降は CURSOR.md も)、README.md、tests/ | コーディングアシスタント向けの指示と、空のテストフォルダー |
.git/ | crewai create は git init を実行します(コミットなし、リモートなし) |
別のプロバイダーを使うには、agents.yaml の各エージェントに llm: anthropic/claude-haiku-4-5(例)を設定し、uv add "crewai[anthropic]" を実行して、.env に ANTHROPIC_API_KEY=<your-key> を記述します。シェルで何もエクスポートしていない状態でも、crewai run は .env からキーを読み取りました。
スキャフォールドから読み取れるルール:
[tool.crewai] type = "flow"はそのままにしてください。CLI はこれを使って、crewai runがuv run kickoffを意味するかどうかを判断します。- テンプレートの
plan_content()は、トリガーペイロードがない場合、self.state.topicを"AI Agents"で上書きします。そのため、kickoff(inputs={"topic": ...})(デプロイされた kickoff のinputsも同様)は無視されます。入力に依存する前に、この代入を削除してください。 - テンプレートの
plot()はデフォルトのshow=Trueでplot()を呼び出すため、crewai flow plotはブラウザーを開きます。ヘッドレス環境で使うには、plot("<name>.html", show=False)に変更してください。 - テンプレートと同様に、絶対パッケージパスでインポートしてください(
from lead_router.crews.content_crew.content_crew import ContentCrew)。 main.pyのkickoff()はNoneを返す必要があります。生成されたコンソールスクリプトはsys.exit(kickoff())を実行するため、Flow の結果(たとえば dict)を返すと、プロセスはステータス 1 で終了し、値を stderr に出力します。また、crewai runは「An error occurred while running the flow」と出力します。ただし、crewai run自体は終了コード 0 のままなので、終了コードだけを確認する CI ステップは成功してしまいます。- Python Flow では
crewai run --inputs '{...}'は拒否されます。表示されるエラーはError: --inputs requires a declarative flow definition ([tool.crewai].definition) or --definitionです。コード内で入力を渡す(flow.kickoff(inputs=...))か、kickoff()でsys.argvから読み取り、uv run kickoff '<json>'を呼び出してください。 crewai flow kickoffは引き続き動作しますが、「deprecated. Use 'crewai run' instead.」と表示されます。- プレースホルダーまたは無効なキーを使うと Flow は失敗しますが、
crewai runは終了コード 0 のままです(An error occurred while running the flow: Command '['uv', 'run', 'kickoff']' returned non-zero exit status 1.と出力されます)。終了コードではなく、この行があるかを確認してください。
2. 状態:構造化または辞書
| 構造化(推奨) | 非構造化 | |
|---|---|---|
| 宣言 | class MyFlow(Flow[MyState]) と MyState(BaseModel) | class MyFlow(Flow) |
| アクセス | self.state.topic | self.state["topic"] |
id | 自動的に追加されます(self.state.id、UUID文字列) | self.state["id"] |
| 検証 | kickoff(inputs=...) の値は型チェックされます | なし |
from pydantic import BaseModel
from crewai.flow import Flow, listen, start
class ReportState(BaseModel):
topic: str = "" # give every field a default
draft: str = ""
notes: list[str] = []
class ReportFlow(Flow[ReportState]):
@start()
def plan(self):
self.state.notes.append(f"planning {self.state.topic}")
return self.state.topic.upper()
@listen(plan)
def write(self, plan_output): # receives plan()'s return value
self.state.draft = f"Draft about {plan_output}"
return self.state.draft
flow = ReportFlow()
result = flow.kickoff(inputs={"topic": "bees"}) # inputs set state BEFORE @start runs
print(result) # "Draft about BEES" - kickoff returns the last method's output
print(flow.state.id) # UUID string, generated per run
状態のルール(それぞれ、実際に起こりうる障害を防ぎます):
| ルール | 違反した場合に起こること |
|---|---|
モデルに id を宣言しないでください。Flow[MyState] が追加します | 宣言された id のデフォルト値は、生成されたUUIDで暗黙に置き換えられます |
initial_state = MyState ではなく、Flow[MyState] を使います | 通常の BaseModel を使った initial_state:フローの状態モデルに「id」フィールドが必要です、という ValidationError ... Flow state model must have an 'id' field |
| すべてのフィールドにデフォルト値を指定します | 必須フィールドがある場合:kickoff の前、フローのインスタンス化時に ValidationError が発生します |
| 構造化状態では属性アクセスを使います | self.state["topic"]:TypeError: 'StateWithId' object is not subscriptable |
inputs のキーはモデルのフィールド名と完全に一致させます | 構造化状態では、不明なキーは暗黙に破棄されます |
正しい型の inputs を渡します | inputs={"topic": 5}:ValueError: Invalid inputs for structured state |
self.state.id を手動で設定しないでください | これは永続化キーです。restore_from_state_id を使ってください(セクション6) |
デプロイ済みフロー(2026-10-01にCrewAI AMPでテスト済み)では、GET /inputs は状態モデルで宣言されたフィールドを一覧表示します(生成された id は一覧に含まれません。モデル自体が id を宣言している場合は、それも一覧に含まれます)。また、POST /kickoff の inputs オブジェクトは、kickoff(inputs=...) とまったく同じように状態に適用されます。inputs ラッパーのない本文({"text": "..."})も受け付けられ(200 と kickoff_id が返され)、デフォルトの状態で実行は成功しましたが、その値はフローに渡りませんでした。実行時の state.id は kickoff_id と同じでした。呼び出しパターンについては、call-deployed-crew スキルを参照してください。
3. 配線:@start、@listen、@router
from typing import Literal
from pydantic import BaseModel
from crewai.flow import Flow, listen, router, start
class TicketState(BaseModel):
text: str = ""
queue: str = ""
class TicketFlow(Flow[TicketState]):
@start()
def receive(self):
return self.state.text
@router(receive)
def triage(self) -> Literal["urgent", "normal"]: # labels, not method names
return "urgent" if "outage" in self.state.text else "normal"
@listen("urgent")
def page_oncall(self):
self.state.queue = "oncall"
@listen("normal")
def file_ticket(self):
self.state.queue = "backlog"
flow = TicketFlow()
flow.kickoff(inputs={"text": "outage in eu-west"})
print(flow.state.queue) # oncall
| ルール | 理由 |
|---|---|
| ルーターのラベルを、それをリッスンするメソッドの名前と同じにしてはいけません | @listen("go") def go はインスタンス化時に次のエラーになります:Invalid flow definition for ...: methods.go.listen listen condition 'go' references the handler name 'go'. A listener triggered by its own completion creates an infinite loop. ラベルとメソッド名は同じ名前空間を共有します |
| どのメソッド名とも一致しないラベルを選びます | ラベルはイベント名であり、すべてのメソッド名も同様です。ルーターが自身をトリガーするメソッド名を返すと、その分岐が再実行され、RecursionError: Method 'r' has been called 100 times in this flow execution が発生します |
| ルーターが返す可能性のあるすべてのラベルにリスナーがあることを確認します | 一致するラベルがないと、フローは何も通知せずに終了し、kickoff() はラベル文字列そのものを返します |
Literal[...] の戻り値型または @router(x, emit=[...]) でラベルを宣言します | plot() のエッジと静的チェックにのみ使われます。emit= は実行時には強制されません(emit に含まれないラベルを返すルーターも実行されました) |
@listen("method_name") よりも @listen(method_ref) のほうが安全です | 文字列のタイプミスは、どのイベントからも発行されないイベントになるだけです |
4. or_ と and_
from crewai.flow import Flow, and_, listen, or_, start
calls = []
class FanIn(Flow):
@start()
def fetch_a(self):
return "A"
@start()
def fetch_b(self):
return "B"
@listen(or_(fetch_a, fetch_b))
def first_result(self, value):
calls.append(("or", value))
@listen(and_(fetch_a, fetch_b))
def all_results(self, value):
calls.append(("and", value))
FanIn().kickoff()
print(calls) # e.g. [('or', 'A'), ('and', 'B')] - the start methods run concurrently, so the values (and order) can vary
or_はキックオフごとに一度だけ、最初に完了した条件で実行されます。2つ目の条件が完了しても再実行されないため、「処理済み」フラグは必要ありません。- ルーターがいずれかのラベルを再び出力すると、
or_は再度有効になります。これにより、改訂ループが機能します(@listen(or_(write_draft, "needs_revision"))は最初のドラフトで実行され、その後はneeds_revisionのたびに実行されます。routing-patterns を参照してください)。 - レビューステップと同じラベルを、別の改訂ステップでリッスンしないでください。2つの
@listen("needs_revision")メソッドは、そのラベルで並行実行されるため、レビューでは改訂前のドラフトが再確認されます(1.15.22 および 1.15.23 で検証済み。3回のレビューすべてでv1が確認されました)。改訂ステップを新しいラベルを返すルーターにし(@router("needs_revision") def revise(...): ...; return "revised")、レビューではor_(write_draft, "revised")をリッスンするようにしてください。 and_はすべての条件が完了した後に一度だけ実行され、最後に完了した条件の出力を受け取ります。それ以外の出力はself.stateから取得してください。- ルーターの後の一般的な合流処理では、
@listen(or_(page_oncall, file_ticket))が、どちらの分岐が実行された場合でも一度だけ実行されます。
5. フローメソッドからクルーやエージェントを呼び出す
from crewai import Agent, Crew, Task
from crewai.flow import Flow, listen, start
class ResearchFlow(Flow):
@start()
def research(self):
researcher = Agent(role="Researcher", goal="Research {topic}",
backstory="You find facts.", llm="openai/gpt-4o-mini")
task = Task(description="List three facts about {topic}.",
expected_output="Three bullets.", agent=researcher)
out = Crew(agents=[researcher], tasks=[task]).kickoff(inputs={"topic": "bees"})
return out.raw # CrewOutput: .raw .pydantic .json_dict
@listen(research)
def summarize(self, facts):
writer = Agent(role="Writer", goal="Summarize", backstory="Concise.",
llm="openai/gpt-4o-mini")
return writer.kickoff(f"One sentence summary: {facts}").raw # LiteAgentOutput
| メソッドの種類 | 呼び出し方 | 誤った形式 |
|---|---|---|
def | crew.kickoff(inputs=...)、agent.kickoff(...) | - |
async def | await crew.akickoff(inputs=...) | async def 内の crew.kickoff():RuntimeError: Agent execution was invoked synchronously from within a running event loop |
await crew.akickoff()の場合、エージェントのmcps=["https://..."]文字列参照は、エラーなしでツールが0個に解決されます(独自のイベントループを開始する必要があるためです)。非同期メソッドではcrewai.mcpのMCPServerHTTP(url=...)を使用してください。詳細は connect-tools-and-mcp スキルを参照してください。- クルーの
{placeholders}は、フローの状態ではなく、クルー自身のkickoff(inputs=...)から値が設定されます。入力なしでキックオフしたクルーは、リテラルの{topic}を LLM に送信します。必要な状態の値は明示的に渡してください。 - クルーはメソッド内で構築するか(または
@CrewBaseクラスから毎回MyCrew().crew()を呼び出し)、実行ごとに新しいオブジェクトが使われるようにしてください。 - フローメソッドは通常のメソッドです。ユニットテストでは
ReportFlow().plan()を直接呼び出せます。
6. @persist による永続化
from pydantic import BaseModel
from crewai.flow import Flow, persist, start # or: from crewai.flow.persistence import persist
class CounterState(BaseModel):
count: int = 0
@persist() # class level: state saved after every method
class CounterFlow(Flow[CounterState]):
@start()
def bump(self):
self.state.count += 1
return self.state.count
first = CounterFlow()
first.kickoff() # count == 1
second = CounterFlow()
second.kickoff(restore_from_state_id=first.state.id) # count == 2
print(second.state.id != first.state.id) # True: a fork with a new id
| 目的 | 実行すること | 避けること |
|---|---|---|
| インポート | from crewai.flow import persist または from crewai.flow.persistence import persist | from crewai.flow.flow import persist(ImportError) |
| デコレート | @persist() - 必ず括弧を付けます | クラスに括弧なしの @persist を付けること:クラスが関数に置き換わり、インスタンス化すると TypeError: persist.<locals>.decorator() missing 1 required positional argument: 'target' が発生します。メソッドに括弧なしの @persist を付けること:エラーはまったく発生しません - メソッド本体は実行されず、kickoff() はフローオブジェクトを返し、何も保存されません |
| 保存済み状態から新しい実行を開始する | kickoff(restore_from_state_id=sid) - 状態を復元してから、新しい state.id で書き込みます | kickoff(inputs={"id": sid}) - 非推奨です。同じ ID で再開し、警告も表示されません |
| チェックポイントとして適切なステップだけで保存する | そのメソッドに @persist() を付けます(@listen の上に重ねます) | 最終状態だけが必要なのに、クラスレベルで指定すること |
| 復元した実行のフィールドを変更する | restore_from_state_id とともに inputs に渡します。入力は状態の復元後に適用されます | SQLite ファイルを編集すること |
状態の保存先と、何も通知されないケース:
- デフォルトのバックエンド:
SQLiteFlowPersistence。ファイルはappdirs.user_data_dir(<CREWAI_STORAGE_DIR or current dir name>, "CrewAI")内のflow_states.dbです。macOS では~/Library/Application Support/<name>/、Linux では~/.local/share/<name>/になります。CREWAI_STORAGE_DIRに絶対パスを設定すると、ファイルはそのディレクトリ直下に置かれます。または、パスを渡します:@persist(SQLiteFlowPersistence("/abs/path/flows.db"))。 - 存在しない
restore_from_state_idを指定してもエラーにはなりません。「No flow state found ... proceeding without hydration」とログに記録され、デフォルト値から実行されます。 @persistのないフローに対するrestore_from_state_idは、通知なく無視されます。- ローカルでは、
@persistは SQLite ファイルに保存します。自分で管理する永続的な状態が必要な場合は、独自のデータベースを使ってFlowPersistenceを実装してください(persistence-and-checkpoints)。
CrewAI AMP(ZIP アップロードでデプロイしたクラスレベルの @persist() フローを 2026-10-01 にテスト)では、POST /kickoff の inputs と並ぶトップレベルの restoreFromStateId として ID を送信します:
| Kickoff のボディ | 確認された動作 |
|---|---|
{"inputs": {...}, "restoreFromStateId": "<state_id>"} | 状態が復元されます(カウンターとリストは引き継がれ、inputs に含まれないフィールドも保持されます)。その上に inputs が適用され、新しい state.id(= 新しい kickoff_id)になります |
同じ内容で、crewai deploy push による再デプロイを 2 回行った後 | 引き続き復元されます - 保存された状態は再デプロイ後も保持されました |
何にも一致しない restoreFromStateId | エラーは発生せず、デフォルト値から実行が開始されます |
{"inputs": {"id": "<state_id>", ...}}(非推奨) | 状態は復元されますが、古い state.id が保持され、その後 kickoff_id と異なる値になります |
呼び出し元が復元に使う ID を取得できるよう、最後のメソッドから self.state.id を返すか、kickoff_id を読み取ってください。
7. チェックポイント(クラッシュ後の再開)
チェックポイントは@persistとは別の仕組みです。実行(状態、完了したメソッド、出力)をファイルにスナップショットとして保存するため、失敗した実行を続行できます。
from crewai import CheckpointConfig
from crewai.flow import Flow, listen, start
class EtlFlow(Flow):
@start()
def extract(self):
return "rows"
@listen(extract)
def load(self, rows):
return f"loaded {rows}"
flow = EtlFlow(checkpoint=CheckpointConfig(
location="./.checkpoints",
on_events=["method_execution_finished"], # required for flows, see below
))
flow.kickoff()
再開するには、EtlFlow().kickoff(from_checkpoint=CheckpointConfig(restore_from="./.checkpoints/<branch>/<file>.json"))を使います。完了済みのメソッドはスキップされます。続行前に状態を変更する場合(たとえば、失敗の原因となったフラグをクリアする場合)は、flow = EtlFlow.from_checkpoint(CheckpointConfig(restore_from=path))を使い、flow.stateを編集してからflow.kickoff()を呼び出します。
- Flowでは、
on_events=["method_execution_finished"]を設定してください。 デフォルトの["task_completed"](およびcheckpoint=True)では、メソッドがCrewを実行した場合でも、Flowのチェックポイントは一切書き込まれませんでした。 - ファイルは
location/<branch>/の下に、完了したメソッドごとに1つずつ、<timestamp>_<id>_p-<parent>.jsonという名前で書き込まれます。タイムスタンプは秒単位で、IDはランダムです。そのため、複数のメソッドが同じ秒に完了した場合、ファイル名をソートしても最新のファイルは見つかりません(ソートしたリストでは、最後から2番目のファイルが選ばれました)。max(files, key=os.path.getmtime)を使うか、crewai checkpoint list <location>を実行してください(新しい順に表示されます)。 - 再開してもルーターの先へは続行されません。
@router(またはemitを持つ@human_feedbackメソッド)の完了後に取得したチェックポイントから再開すると、何も実行されず、ルーターのラベル(またはNone)がエラーなしで返されます。そのラベルのリスナーは実行されません。そのため、ルーターの下流でクラッシュした場合、最新のチェックポイントからは再開できません。ルーターの完了前に取得したチェックポイントからのみ、ルーターを通過して続行できます。1.15.22と1.15.23で確認済みです。クラッシュしやすい処理はルーターより前に置くか、下流のステップを冪等にしてFlowを再実行してください。 kickoff(from_checkpoint=..., restore_from_state_id=...)はValueError: Cannot combine ...を発生させます。いずれか一方のシステムを選んでください。
8. 人間によるフィードバック(@human_feedback)
from crewai.flow import Flow, HumanFeedbackResult, human_feedback, listen, start
class ReviewFlow(Flow):
@start()
@human_feedback(
message="Approve this draft?",
emit=["needs_revision", "approved"],
llm="openai/gpt-4o-mini", # maps free-text feedback to one label
default_outcome="needs_revision", # used when feedback is empty
)
def draft(self):
return "Draft v1"
@listen("approved")
def publish(self, result: HumanFeedbackResult):
return f"published: {result.output} ({result.feedback})"
@listen("needs_revision")
def revise(self, result: HumanFeedbackResult):
return f"revise: {result.feedback}"
HumanFeedbackResultのフィールド:output、feedback、outcome、timestamp、method_name、metadata。テキストは**result.feedback**です(feedback_textはAttributeErrorを発生させます)。Flow上では、self.last_human_feedbackとself.human_feedback_historyも利用できます。- フィードバックが空の場合は、
default_outcomeが使われます。未設定の場合はemit[0]が使われます。安全な結果を先頭に置き、default_outcomeにも設定してください。そうすれば、Enterキーを押しても承認されません。 - 空ではないフィードバックをLLMがラベルに対応付けられない場合は、
HumanFeedbackCollapseErrorが発生し、フォールバックは行われません。 - 空ではないフィードバックは
llmによって対応付けられます。anthropic/claude-haiku-4-5では、「Looks good to me, ship it.」はapprovedに、「No, the tone is wrong - please rewrite it more politely.」はneeds_revisionになりました。元のテキストはresult.feedbackに保持されます。 emitを指定せずにdefault_outcomeを指定すると、ValueError: default_outcome requires emit to be specified.が発生します。emitがない場合、次のリスナーにはHumanFeedbackResultが渡され、ルーティングは行われません。- デフォルトのプロバイダーはコンソールから入力を読み取ります。テストやUIで使う場合は、
provider=(request_feedback(context, flow) -> strを持つオブジェクト)を渡してください。プロバイダーからHumanFeedbackPendingを発生させて一時停止し、後でMyFlow.from_pending(flow_id, persistence).resume(feedback)を呼び出します(persistence-and-checkpoints)。stdinがない場合(CI、< /dev/null、バックグラウンドジョブ)、コンソールプロバイダーはEOFError: EOF when reading a lineを発生させ、Flowは失敗します。デプロイ済みのFlowではinput()を決して使わないでください。
9. plot()
path = TicketFlow().plot("ticket_flow.html", show=False)
print(path) # /tmp/.../crewai_flow_xxxx/ticket_flow.html
plot(filename="crewai_flow.html", show=True)は、インタラクティブな HTML(および .js と .css)を新しい一時ディレクトリに書き込み、絶対パスを返します。現在のディレクトリには何も書き込まれず、PNG もありません。保存する必要がある場合は、ファイルをコピーしてください。- ファイル名は指定どおりに使用されます。
plot("my_flow")は.html拡張子のないmy_flowという名前のファイルを書き込みます。 show=Trueはブラウザーを開きます。CI やヘッドレス環境で実行する場合はshow=Falseを使用してください。Literal[...]の戻り値型もemit=[...]もないルーターは、「Router events for 'r' are dynamic or not statically inferable」とログに記録し、グラフからエッジが省略されることがあります。グラフのデータは.htmlではなく.jsファイルにあります。
10. 会話フロー
複数ターンのチャットでは、crewai.flow(ConversationConfig、ConversationState、RouterConfig)からインポートしてください。crewai.experimental.conversational は同じモジュールの非推奨エイリアスです。ターンは kickoff(user_message=...)(TypeError)ではなく、flow.handle_turn(message, session_id=...) で進めてください。ChatSession クラスはありません。詳細とテスト済みの例については、conversational-flows を参照してください。
11. よくある間違い
| 症状 | 原因 | 解決方法 |
|---|---|---|
ImportError: cannot import name 'persist' | from crewai.flow.flow import persist | from crewai.flow import persist |
TypeError: persist.<locals>.decorator() missing 1 required positional argument: 'target'、またはメソッドが実行されない | 括弧なしの @persist | @persist() |
listen condition 'x' references the handler name 'x' | ルーターのラベルがリスナーのメソッド名と同じ | ラベルまたはメソッド名を変更する |
RecursionError: Method 'r' has been called 100 times | ルーターがメソッド名と同じラベル(多くの場合、自身のトリガー)を返した | メソッド名ではないラベルを使用する |
| ルーターの後にフローが停止し、エラーもない | ルーターが @listen のないラベルを返した | すべてのラベルをリッスンし、ルーターの型を -> Literal[...] にする |
| 上流の両方が完了したのに、リスナーが一度しか実行されない | それは or_ です | すべてを待つには and_ を使用する |
| 入力値が無視される | キーのスペルミス。構造化状態は未知のキーを破棄する | フィールド名を完全に一致させる |
'StateWithId' object is not subscriptable | 構造化状態に対する辞書アクセス | self.state.field |
Flow state model must have an 'id' field | initial_state = MyModel | class F(Flow[MyModel]) |
crewai run でフローがエラーになったと表示されるが、出力は正しい | main.py の kickoff() が値を返した | None を返す |
--inputs requires a declarative flow definition | Python フローに対して crewai run --inputs を実行した | コード内で入力を渡すか、uv run kickoff '<json>' を使用する |
invoked synchronously from within a running event loop | async def 内で crew.kickoff() を呼び出した | await crew.akickoff() を使用するか、メソッドを同期型にする |
| 再開した実行がデフォルト値から始まる | ID が不明、またはクラスに @persist がない | ID とデコレーターを確認する。どちらの場合もエラーは表示されない |
| 実行 ID が複数の実行で再利用される | inputs={"id": ...} | restore_from_state_id= |
| フローのチェックポイントファイルがない | デフォルトの on_events=["task_completed"] | on_events=["method_execution_finished"] |
チェックポイントからの再開でラベル(または None)が返され、何も実行されない | ルーターの完了後にチェックポイントが取得された | ルーターより前のチェックポイントから再開するか、再実行する |
@human_feedback で EOFError: EOF when reading a line | 標準入力がないコンソールプロバイダー | provider= を渡す |
雛形から生成されたフローが topic 入力を無視する | テンプレートの plan_content() が "AI Agents" をハードコードしている | 代入を削除する |
AttributeError: ... 'feedback_text' | 古いフィールド名 | result.feedback |
HumanFeedbackCollapseError | フィードバックがどの emit ラベルにも一致しなかった | ラベルをクリアし、対応可能な llm を使い、エラーを捕捉する |
plot() のファイルがプロジェクト内にない | 一時ディレクトリに書き込まれる | 返されたパスを使用する |
12. チェックリスト
- 状態は
Flow[MyState]で、すべてのフィールドにデフォルト値が設定されていること。idを手動で設定しないこと - すべてのルーターラベルがすべてのメソッド名と異なり、リスナーを持つこと
- ルーターの戻り値の型が
Literal[...](またはemit=[...])であり、plot()にエッジが表示されること -
or_(最初に一致したものを一度だけ実行)またはand_(すべて実行)を意図的に使って結合すること -
async defメソッドではcrew.akickoff()をawaitし、同期メソッドではkickoff()を呼び出すこと -
persistをcrewai.flowからインポートし、@persist()として記述すること。再開にはrestore_from_state_id(HTTP ではrestoreFromStateId)を使用すること - 状態を再び見つける必要がある場所に、絶対パスを
CREWAI_STORAGE_DIRに設定すること - チェックポイントを使用するフローでは
on_events=["method_execution_finished"]を使用すること。最新のファイルは mtime で選択され、クラッシュしやすいステップはルーターの前に配置すること -
@human_feedbackでは安全な結果を最初に指定し、default_outcomeにも設定すること。コードでは.feedbackを読み取ること -
main.kickoff()がNoneを返し、[tool.crewai] type = "flow"が存在すること -
crewai flow plot(またはplot(show=False))を実行し、グラフを確認したこと
参考資料
- ルーティングパターン -
or_+ ルーターによる修正ループ、ネストされた条件、複数のルーター、ルーター/or_/persist を使った完全なサンプルフロー - 永続化とチェックポイント - ストレージパス、フォークと再開の違い、カスタムの
FlowPersistence、チェックポイントからの再開と編集、一時停止された人間によるフィードバック - 会話型フロー -
handle_turn、ConversationConfig、ターンのルーティング
関連スキル:
- getting-started - クルーとフローのどちらを選ぶか、プロジェクトの構成
- check-crewai-api - 現在の Agent/Task/Crew/LLM API と、記憶にある 0.x 形式との比較
- design-task - フローが呼び出すクルーのタスク説明、構造化出力、ガードレール
- test-crewai-project - スタブ LLM を使ってフローをオフラインで実行し、状態を検証する
- deploy-to-amp -
type = "flow"に対してホスト型ビルドが想定するプロジェクト構成とエントリーポイント - call-deployed-crew - HTTP 経由でデプロイ済みフローをキックオフする
- ask-docs - ここに記載された内容が使用中のバージョンと一致しない場合に、最新のドキュメントを検索する