OpenAI Agents SDKで問い合わせ振り分けbotを作ってみた|Handoff機能が想像以上に便利だった話
はじめに
「問い合わせ対応、毎回同じような質問に答えるの面倒だな...」
個人開発でちょっとしたWebサービスを運営しているんですが、問い合わせが増えてくると対応が大変になってきました。料金の質問、使い方の質問、バグ報告...内容によって対応が全然違うので、まず「これは何の問い合わせか」を判断するところから始まる。
「AIで自動振り分けできないかな」と思って調べていたら、2025年3月にOpenAIがAgents SDKをリリースしたという記事を見つけました。複数のAIエージェントを連携させて、タスクを分担できるらしい。
これ、まさに今やりたいことじゃん。
というわけで、OpenAI Agents SDKを使って問い合わせ振り分けbotを作ってみました。この記事では、実際に触ってみてわかった「これは便利」「ここでハマった」というリアルな感想を共有します。
なぜ OpenAI Agents SDK を選んだか
エージェント系のフレームワークって、正直たくさんありすぎて迷いました。
- LangChain - 有名だけど、抽象化が多くて学習コストが高そう
- CrewAI - マルチエージェント特化だけど、今回の用途には大げさかも
- Claude Agent SDK - TypeScript派の自分には魅力的だけど、今回はPythonで試したい
そこで出てきたのがOpenAI Agents SDK。調べてみると:
- 最小限の抽象化 - Agents、Handoffs、Guardrailsという3つの概念だけ
- Pythonネイティブ - 関数をそのままツールとして使える
- トレーシング機能付き - デバッグが楽そう
「概念が少ない」というのが決め手でした。LangChainは便利そうだけど、Chains、Agents、Tools、Memory、Callbacks...と覚えることが多くて、最初の一歩が重い。Agents SDKは「まず動かす」までが短そうだったので、試してみることにしました。
実装してみた
セットアップ
まずはインストールから。Python 3.9以上が必要です。
pip install openai-agents
環境変数にAPIキーを設定:
export OPENAI_API_KEY="sk-..."
最初に書いたコード
公式のQuickstartを参考に、まずは単純なエージェントを作ってみました。
from agents import Agent, Runner
agent = Agent(
name="サポートアシスタント",
instructions="あなたは親切なカスタマーサポートです。"
)
result = Runner.run_sync(agent, "料金プランを教えてください")
print(result.final_output)
動いた。シンプル。
でも、これだと普通のChatGPTと変わらない。本題の「振り分け」を実装していきます。
Handoffで振り分けを実装
OpenAI Agents SDKの目玉機能が Handoff(ハンドオフ)です。これは「あるエージェントから別のエージェントにタスクを渡す」機能。
今回作りたいのは:
- 受付エージェント - 問い合わせ内容を判断して振り分け
- 料金担当エージェント - 料金・プランの質問に回答
- 技術担当エージェント - 使い方・バグ報告に対応
from agents import Agent, Runner
import asyncio
# 専門エージェントを定義
billing_agent = Agent(
name="料金担当",
instructions="""
あなたは料金・プラン担当です。
- 無料プラン: 月10回まで利用可能
- Proプラン: 月額980円、無制限
- Teamプラン: 月額2,980円、5人まで
これらの情報をもとに、丁寧に回答してください。
"""
)
tech_agent = Agent(
name="技術担当",
instructions="""
あなたは技術サポート担当です。
使い方の質問やバグ報告に対応します。
具体的な解決策を提示し、必要に応じてドキュメントを案内してください。
"""
)
# 受付エージェント(振り分け役)
triage_agent = Agent(
name="受付",
instructions="""
あなたは問い合わせ受付担当です。
ユーザーの質問内容を判断し、適切な担当に引き継いでください。
- 料金、プラン、支払いに関する質問 → 料金担当
- 使い方、エラー、バグに関する質問 → 技術担当
""",
handoffs=[billing_agent, tech_agent]
)
async def main():
# 料金の質問
result = await Runner.run(triage_agent, "Proプランの料金を教えてください")
print(f"回答: {result.final_output}")
# 技術的な質問
result = await Runner.run(triage_agent, "ログインできないのですが...")
print(f"回答: {result.final_output}")
asyncio.run(main())
辛かったポイント
1. async/awaitが必須
最初、Runner.run_sync()で試していたんですが、Handoffを使うと挙動が不安定になることがありました。公式ドキュメントをよく読むと、async/awaitの理解が前提と書いてある。
Pythonの非同期処理に慣れていないと、ここで「あれ、動かない」ってなるかもしれません。自分もasyncio.run()を毎回書くのが面倒で、最初は戸惑いました。
# これだと動くけど、非同期処理の恩恵が少ない
result = Runner.run_sync(agent, "質問")
# 本来はこう書くべき
async def main():
result = await Runner.run(agent, "質問")
asyncio.run(main())
2. Handoffのデバッグが難しい
これが一番辛かった。Handoffでどのエージェントにタスクが渡されたか、外から見えにくいんです。
「料金の質問をしたのに、なぜか技術担当に行ってしまった」というとき、なぜそうなったのかを追うのが大変。LLMが判断しているので、deterministic(決定的)じゃないんですよね。
解決策としては、トレーシング機能を使います。
from agents.tracing import set_tracing_export_api_key
# OpenAIのダッシュボードでトレースを確認できる
set_tracing_export_api_key("sk-...")
これでOpenAIのダッシュボード上で「どのエージェントがどの順番で呼ばれたか」が可視化されます。正直、これがなかったら詰んでた。
3. instructionsの書き方で挙動が変わりすぎる
受付エージェントのinstructionsを少し変えただけで、振り分けの精度がガラッと変わります。
最初は「適切な担当に引き継いでください」としか書いてなくて、曖昧な質問が全部技術担当に行ってしまっていました。
Before(精度低い):
instructions="問い合わせを適切な担当に引き継いでください"
After(精度改善):
instructions="""
あなたは問い合わせ受付担当です。
ユーザーの質問内容を判断し、適切な担当に引き継いでください。
【振り分けルール】
- 以下のキーワードが含まれる場合は料金担当へ:
料金、プラン、価格、支払い、請求、解約、無料、有料
- 以下のキーワードが含まれる場合は技術担当へ:
使い方、エラー、バグ、動かない、ログイン、設定
迷った場合は、まずユーザーに確認してください。
"""
プロンプトエンジニアリングの重要性を痛感しました。
よかったポイント
1. 関数がそのままツールになる
これ、地味にめちゃくちゃ便利です。Pythonの関数を書くだけで、エージェントが使えるツールになります。
from agents import Agent, Runner, function_tool
@function_tool
def get_user_plan(user_id: str) -> str:
"""ユーザーの契約プランを取得します"""
# 実際はDBから取得
plans = {"user_123": "Pro", "user_456": "Free"}
return plans.get(user_id, "不明")
billing_agent = Agent(
name="料金担当",
instructions="ユーザーの契約プランを確認して回答してください",
tools=[get_user_plan]
)
LangChainだとToolクラスを継承して...とか書かないといけないのに対して、デコレータ一発。Pydanticでバリデーションも自動でやってくれるのも嬉しい。
2. コード量が少ない
上で書いた振り分けbot、コア部分は30行くらいしかありません。これくらいシンプルだと、「とりあえず動くもの」をサクッと作れます。
プロトタイプを作って上司に見せる、みたいなシーンで重宝しそう。
3. OpenAIエコシステムとの親和性
当然ですが、OpenAIのモデルとの相性は抜群です。GPT-4o、GPT-4o-mini、o1など、用途に応じてモデルを切り替えられます。
from agents import Agent, OpenAIChatCompletionsModel
agent = Agent(
name="高精度エージェント",
model=OpenAIChatCompletionsModel(model="gpt-4o"),
instructions="..."
)
完成形のコード
最終的にはこんな感じになりました。
from agents import Agent, Runner, function_tool
import asyncio
# ツール定義
@function_tool
def get_user_info(user_id: str) -> dict:
"""ユーザー情報を取得します"""
users = {
"user_123": {"name": "山田太郎", "plan": "Pro", "joined": "2024-01"},
"user_456": {"name": "佐藤花子", "plan": "Free", "joined": "2025-01"},
}
return users.get(user_id, {"error": "ユーザーが見つかりません"})
@function_tool
def create_ticket(title: str, description: str, priority: str) -> str:
"""サポートチケットを作成します"""
# 実際はDBに保存
return f"チケット作成完了: {title} (優先度: {priority})"
# 専門エージェント
billing_agent = Agent(
name="料金担当",
instructions="""
あなたは料金・プラン担当のサポートスタッフです。
【プラン情報】
- Free: 無料、月10回まで
- Pro: 月額980円、無制限利用
- Team: 月額2,980円、5人までのチーム利用
ユーザーIDがわかる場合は、get_user_infoで契約情報を確認してください。
""",
tools=[get_user_info]
)
tech_agent = Agent(
name="技術担当",
instructions="""
あなたは技術サポート担当です。
【よくある問題と解決策】
- ログインできない → パスワードリセットを案内
- 動作が遅い → キャッシュクリアを提案
- エラーが出る → エラーメッセージを確認し、チケット作成
解決できない場合は、create_ticketでチケットを作成してください。
""",
tools=[create_ticket]
)
# 振り分けエージェント
triage_agent = Agent(
name="受付",
instructions="""
あなたは問い合わせ受付担当です。
ユーザーの質問を分析し、適切な専門担当に引き継いでください。
【振り分けルール】
- 料金、プラン、支払い、請求、解約 → 料金担当
- 使い方、エラー、バグ、設定、ログイン → 技術担当
曖昧な場合は、まずユーザーに「料金についてですか?それとも使い方についてですか?」と確認してください。
""",
handoffs=[billing_agent, tech_agent]
)
async def handle_inquiry(message: str):
"""問い合わせを処理"""
result = await Runner.run(triage_agent, message)
return result.final_output
# 使用例
async def main():
inquiries = [
"Proプランに変更したいのですが",
"ログインできなくなりました。user_123です",
"最近サービスが重いです",
]
for inquiry in inquiries:
print(f"\n📩 問い合わせ: {inquiry}")
response = await handle_inquiry(inquiry)
print(f"💬 回答: {response}")
if __name__ == "__main__":
asyncio.run(main())
学び・Tips
1. instructionsは具体的に書く
「適切に対応して」みたいな曖昧な指示はNG。具体的なルール、キーワード、例を書くと精度が上がります。
2. トレーシングは最初から有効にしておく
デバッグで詰まったときに「あのとき有効にしておけば...」となるので、開発中は常にONにしておくのがおすすめ。
3. Handoffは「専門家チーム」のメタファーで考える
「この質問はAさんが得意だからAさんに聞こう」という人間の判断を、LLMにやらせているイメージ。各エージェントの「専門分野」を明確にしておくと、振り分け精度が上がります。
参考リンク
まとめ
OpenAI Agents SDKを使って問い合わせ振り分けbotを作ってみました。
向いている場面:
- OpenAIモデルを使った開発をしている
- 複数エージェントの連携を試したい
- とにかくシンプルに始めたい
- Pythonで書きたい
向いていない場面:
- OpenAI以外のモデルをメインで使いたい
- 複雑な状態管理が必要(LangGraphの方が向いてる)
- TypeScript/JavaScriptで書きたい(今後対応予定らしい)
こういう人におすすめ:
- 「エージェント開発、まず何から始めればいい?」という人
- LangChainの抽象化に疲れた人
- ChatGPTの次のステップを探している人
Handoff機能、想像以上に便利でした。「まず受付→専門家に振り分け」というパターンは、カスタマーサポート以外にも応用が効きそうです。社内のヘルプデスクbot、技術的な質問を担当別に振り分けるSlackbot...アイデアが広がります。
OpenAI Agents SDK、シンプルに始められるのでぜひ試してみてください。
Discussion