CrewAI 入門実践ガイド
# CrewAI入門:実践ガイド
私は3時間かけて、APIエンドポイントを幻覚し続けるCrewAIエージェントのデバッグを行い、そこで重要なことを学びました。**CrewAIの強力さは、同時に最大の落とし穴でもある**ということです。LangChainから移行してきた方、あるいはマルチエージェントシステムを始めたばかりの方なら、複数のAIエージェントを調整することが、単にプロンプトをつなげるだけではないこと、つまり依存関係、コンテキスト、障害モードの管理が重要であることにすぐに気づくでしょう。このガイドでは、自動ブログコンテンツ生成のための実際のCrewAIシステムを構築する中で学んだこと、具体的には壊れたコードとその修正方法について説明します。
## 課題:なぜ単一エージェントではダメなのか?
おそらく、単一のLLM呼び出しでブログ記事を生成しようとしたことがあるでしょう。それはうまくいきます。ただし、ファクトチェック、SEO最適化、フォーマットが必要になるまでは。単一エージェントはコンテキストを忘れたり、自己矛盾を起こしたり、無意味な内容を生成したりします。CrewAIは、人間のチームのように、**専門化されたエージェント**を定義してタスクを相互に渡すことで、この問題を解決します。しかし、ここに落とし穴があります。CrewAIのシンプルさは複雑さを隠しています。エージェントの役割とタスクを慎重に設計しないと、循環依存、無限ループ、あるいは作業の引き継ぎを拒否するエージェントが発生します。
## ステップ1:CrewAIのインストール(そして落とし穴)
```bash
pip install crewai
```
これで必要なものがすべてインストールされると思っていました。**間違いでした。** CrewAIは`langchain`と`openai`に依存していますが、最新バージョンではありません。Python 3.12を使用している場合、`pydantic`の競合が発生します。以下が私が使用した正確な修正方法です。
```bash
pip install crewai langchain==0.1.0 openai==1.6.1 pydantic==2.5.0
```
これらのバージョンを固定しないと、`ImportError: cannot import name 'BaseModel' from 'pydantic'`というエラーが発生します。これで30分無駄にしました。
## ステップ2:エージェントを定義する(具体的にしないと苦労する)
CrewAIのエージェントは、`role`、`goal`、`backstory`を持つPythonクラスとして定義されます。backstoryはオプションですが、エージェントのトーンと動作を制御するため重要です。以下が私が最初に使用したものです。
```python
from crewai import Agent
class Researcher(Agent):
role = "リサーチャー"
goal = "医療におけるAIに関する最近のニュースを見つける"
backstory = "あなたは情報源を検証する几帳面な研究者です。"
```
これは動作しますが、漠然としすぎています。エージェントは一般的な応答を生成します。テスト後、**制約**を追加することを学びました。
```python
class Researcher(Agent):
role = "シニアヘルスケアAIリサーチャー"
goal = "腫瘍学におけるAIに関する最近(2024年)の査読付き論文を3件見つける"
backstory = """あなたは医療AI分野で10年の経験があります。
常に具体的なPMIDまたはDOIリンクを引用します。
情報源を捏造することは決してありません。"""
```
**情報源を引用する明示的な指示**と**年の制約**に注目してください。これらがないと、私のエージェントは偽の論文を作り出しました。CrewAIは事実を検証しません。LLMを信頼するのです。
## ステップ3:正しく連鎖するタスクを作成する
タスクは、ほとんどの人が失敗する部分です。タスクには`description`、`expected_output`、`agent`があります。ポイントは、**タスクを以前の出力に依存させること**です。私は2つのエージェントからなるパイプラインを構築しました。
```python
from crewai import Task
research_task = Task(
description="AIヘルスケアに関する最近の論文を3件見つける。タイトルとリンクのリストを出力する。",
expected_output="タイトル、年、URLを含む3件の論文の箇条書きリスト",
agent=researcher
)
writing_task = Task(
description="""リサーチの出力に基づいて、調査結果を要約した500語のブログ記事を書く。
提供された論文への引用を含めること。""",
expected_output="見出しと引用元を含むマークダウン形式のブログ記事",
agent=writer
)
```
ここにバグがあります。`writing_task`は`research_task`の出力を明示的に参照していません。CrewAIはエージェントのメモリを通じて**暗黙的に**コンテキストを渡しますが、これは信頼性が低いです。私は**タスク依存関係**を使用して修正しました。
```python
writing_task = Task(
description="""前のタスクのリサーチ出力に基づいて、調査結果を要約した500語のブログ記事を書く。
リサーチ出力は次の通りです:{research_output}""",
expected_output="見出しと引用元を含むマークダウン形式のブログ記事",
agent=writer,
context=[research_task] # 明示的な依存関係
)
```
`context`パラメータは、出力が説明に注入されるタスクのリストです。これがないと、私のライターエージェントは独自のリサーチを幻覚していました。
## ステップ4:Crewを実行する(そして障害に対処する)
次に`Crew`を作成して実行します。
```python
from crewai import Crew
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, writing_task],
verbose=True # デバッグに不可欠
)
result = crew.kickoff()
print(result)
```
初めて実行したときは動作しましたが、45秒かかり、API呼び出しで0.12ドルの費用がかかりました。詳細出力を見ると、リサーチャーエージェントが論文を見つけるために3回のAPI呼び出しを行い、その後ライターエージェントが記事を書くためにさらに2回の呼び出しを行っていることがわかりました。CrewAIのデフォルトは**逐次実行**で、各タスクは前のタスクが完了するのを待ちます。
**欠点:** 最初のタスクが失敗した場合(例:APIがエラーを返す)、Crew全体がクラッシュします。CrewAIには組み込みのリトライロジックがありません。私は簡単なリトライラッパーを追加しました。
```python
import time
def safe_kickoff(crew, max_retries=3):
for attempt in range(max_retries):
try:
return crew.kickoff()
except Exception as e:
print(f"試行 {attempt+1} が失敗しました:{e}")
time.sleep(2 ** attempt) # 指数バックオフ
raise Exception("3回試行後もCrewは失敗しました")
```
## ステップ5:実践的な最適化のヒント
1週間のテストの後、実際に信頼性を向上させたものを紹介します。
1. **エージェントのメモリを制限する**:デフォルトでは、エージェントは会話全体を記憶します。長いタスクでは、コンテキストウィンドウが大きくなりすぎます。履歴が必要ないエージェントには`memory=False`を設定します。
```python
researcher = Researcher(memory=False)
```
2. **`allow_delegation=False`を使用する**:デフォルトでは、エージェントは他のエージェントにタスクを委任できます。これによりループが発生します。複雑な階層を構築していない限り、無効にします。
```python
researcher = Researcher(allow_delegation=False)
```
3. **結果をキャッシュする**:CrewAIはデフォルトでLLM呼び出しをキャッシュしますが、エージェントごとです。同じCrewを2回実行すると、応答を再利用します。これはデバッグには便利ですが、本番環境では危険です。古いデータを提供する可能性があります。キャッシュを無効にするには:
```python
crew = Crew(agents=[...], tasks=[...], cache=False)
```
4. **トークン使用量を監視する**:CrewAIはトークン数を公開しません。私は簡単なコールバックを追加しました。
```python
from langchain.callbacks import get_openai_callback
with get_openai_callback() as cb:
result = crew.kickoff()
print(f"総トークン数:{cb.total_tokens}、費用:${cb.total_cost}")
```
## CrewAIが教えてくれない最大の制限
コンテンツ生成のための5エージェントシステムを構築した後、壁にぶつかりました。**CrewAIにはエージェント障害に対する組み込みのエラー回復機能がありません。** リサーチャーエージェントが無意味な出力を返した場合でも、ライターエージェントはそれを使用しようとします。唯一の修正方法は、タスクの説明で出力を検証することです。
```python
research_task = Task(
description="""3件の論文を見つける。3件見つけられない場合は、'NO_RESULTS'を出力し、
理由を説明する。論文を捏造しないこと。""",
...
)
```
その後、ライティングタスクでこのセンチネル値をチェックします。ハッキーな方法ですが、機能します。
## 次のステップ:実際のプロジェクトを構築する
理論から始めないでください。私の壊れたサンプルを[github.com/your-repo/crewai-blog-generator](https://github.com/your-repo/crewai-blog-generator)からクローンし、意図的なバグを修正してください。`README`には、私が意図的に壊した3つのことが書かれています。
1. ライタータスクの`context`パラメータの欠落
2. API障害に対するリトライロジックの欠如
3. `allow_delegation=True`によるエージェントの無限ループ
これらを修正したら、ライターの引用を検証する`FactChecker`エージェントを追加してシステムを拡張してください。ドキュメントを1時間読むよりも、30分のデバッグでより多くを学べます。そして、何かを壊してしまったときは、詳細出力が最良の友であることを忘れないでください。`True`に設定して、エージェントのすべての決定を確認しましょう。