OpenAI Agent SDK を使ってみる:LLMと自作関数・外部サービスの連携
はじめに
OpenAIにおいて、Agents SDK を中心としたエージェント機能が提供されるようになってから、しばらく経過しました。
このエージェント機能を使用することで、LLMが外部のツールやサービスを呼び出しながら、複数ステップの処理を行うことが容易になります。
たとえば、LLMの問い合わせの結果をWebで検索して、その内容を独自の関数で解析後、解析結果を再度LLMに問い合わせるといったようなワークフローが作成できます。
このドキュメントでは、OpenAIエージェントをまだ使用したことのない状態から、TypeScriptおよびPythonでOpenAIのエージェントを使用したサンプルの作成と実行を通して、エージェントの使用方法と、どのような時に利用するかを考えてみたいと思います。。
本記事は、TypeScriptまたはPythonでOpenAI APIを使用したことはあるものの、エージェントはまだ使用していない読者を対象とします。
環境構築とクイックスタート
公式のページにTypeScriptとPythonのクイックスタートが用意されています。
簡単にOpenAI Agent SDKを動かしてみたい場合には最適な資料の一つです。
また、本ドキュメントのサンプルコードが動作する環境の作成方法は以下の通りです。
TypeScriptでの環境構築方法
nodeのバージョンはv25.2.1を用意します
以下のpackage.jsonを用意します。
package.json
{
"name": "playwright_mcp",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc",
"dev": "tsx",
"start": "node dist/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "module",
"dependencies": {
"@openai/agents": "^0.4.4",
"@playwright/mcp": "^0.0.62",
"dotenv": "^17.2.3",
"playwright": "^1.59.0-alpha-1769819922000",
"zod": "^4.3.6"
},
"devDependencies": {
"@playwright/test": "^1.58.1",
"@types/node": "^22.0.0",
"jest": "^30.2.0",
"tsx": "^4.19.2",
"typescript": "^5.7.2"
}
}
インストール方法:
npm install
.envファイルに環境変数の設定
OPENAI_API_KEY=sk-proj-略
Pythonでの環境構築の方法
任意の方法でuvを導入します。
pyproject.toml
[project]
name = "vecdb"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"openai>=2.14.0",
"pydantic>=2.12.5",
"python-dotenv>=1.2.1",
"tqdm>=4.67.1",
"ipykernel",
"openai-agents>=0.7.0",
]
[dependency-groups]
dev = [
"pyrefly>=0.47.0",
"pytest>=9.0.2",
"pytest-cov>=7.0.0",
"ruff>=0.14.10",
]
インストール方法:
uv sync
.envファイルに環境変数の設定
OPENAI_API_KEY=sk-proj-略
なお、実験の日付は2026年2月1日〜2月4日のものなので、将来のリリースで実験通りの動作をしない可能性があります。
単純なサンプル
ここではTypeScriptとPythonで、もっとも単純なAgentの使用例を確認します。
このサンプルでは単純にLLMで質問を行い、その結果を取得するだけです。
このサンプルは単純な使い方を紹介するためのもので、Agentの有効性を十分に説明しているものではありません。
TypeScriptでの単純なサンプル例
以下のようなTypeScriptで単純なAgentの使用例が確認できます。
import { Agent, run } from "@openai/agents";
import dotenv from "dotenv";
dotenv.config();
const agent = new Agent({
name: 'Guide-City-Agent',
instructions: '日本の市町村名に関する質問です.ユーザーの質問に最も一致する市町村名を簡潔に答えてください。',
model: "gpt-5-nano"
});
const result = await run(agent, '東京ディズニーランドがある市はどこですか?');
console.log(result.finalOutput);
このスクリプトは以下のように実行します。
npx tsx sample_agent_sdk.ts
Agentクラスでエージェントオブジェクトを作成して、それをrun関数で実行します。
run関数の実行結果のfinalOutputプロパティに最終的な結果が格納されます。
もし、使用したトークン数などの使用状況を確認したい場合はstateプロパティのUsageクラスを参照することで確認可能です。
console.log(result.state.usage);
また、Agentがどのように動作したかの履歴はOpenAIのLogsのページから確認できます。


今回の実験では一連のエージェントに対する処理が自動的にワークフローとしてまとめられてTracesタブで確認できましたが、任意の単位で制御することもできます。
また、動作履歴はrun関数の実行結果のhistoryプロパティからも確認できます。
console.dir(result.history, { depth: null });
Pythonでの単純なサンプル例
以下のようなPythonで単純なAgentの使用例が確認できます。
from dotenv import load_dotenv
from agents import Agent, Runner
load_dotenv()
# Agent の定義
agent = Agent(
name="Guide-City-Agent",
instructions=(
"日本の市町村名に関する質問です。"
"ユーザーの質問に最も一致する市町村名を簡潔に答えてください。"
),
model="gpt-5-nano",
)
# 実行(JS: run(agent, '...') 相当)
result = Runner.run_sync(
agent,
input="東京ディズニーランドがある市はどこですか?"
)
# 出力
print(result.final_output) # JS: result.finalOutput
このスクリプトは以下のように実行します。
uv run agent_sample01.py
Pythonでの使用方法もTypeScriptとほぼ同じです。
使用するクラスや関数は以下を参考にしてください。
なお、2026/2/3時点ではRunResultからstateを取得する方法がないため[1]、以下のようにraw_response経由でModelResponseを取得して使用状況を取得します。
# 使用量(JS: result.state.usage に近いもの)
if result.raw_responses:
print(result.raw_responses[-1].usage)
print(result.raw_responses)
また、エージェントがどのような実行を行ったかの履歴についてはRunResultのnew_itemsから取得できます。
for item in result.new_items:
print(item)
複数段階の問い合わせを行うサンプル
ここではTypeScriptとPythonで、複数段階で問い合わせを行うサンプルを確認します。
この例では以下のような問い合わせを行います。
- 最初の問い合わせで市町村名を取得する(例:東京ディズニーランドがある市はどこですか?')
- 最初の回答の市町村の所属都道府県と人口を問い合わせる[2]
TypeScriptによる複数段階の問い合わせを行うサンプル
複数段階で問い合わせる場合、前回の回答の結果を入力として渡す方法とセッションを使用して直前までの会話の内容を保持する方法があります。
以下は単純に前回の回答内容を入力として含めるサンプルです。
import { Agent, run } from "@openai/agents";
import dotenv from "dotenv";
dotenv.config();
const agent1 = new Agent({
name: 'Guide-City-Agent',
instructions: '日本の市町村名に関する質問です.ユーザーの質問に最も一致する市町村名を簡潔に答えてください。',
model: "gpt-5-nano"
});
const result1 = await run(agent1, '東京ディズニーランドがある市はどこですか?');
console.log('最初の問い:', result1.finalOutput);
const agent2 = new Agent({
name: 'Guide-City-Detail-Agent',
instructions: '指定した市について所属する都道府県と今わかる人口を簡潔に答えてください',
model: "gpt-5-nano"
});
const result2 = await run(agent2, result1.finalOutput as string);
console.log('次の問い:', result2.finalOutput);
出力例:
最初の問い: 浦安市
次の問い: 浦安市は千葉県に属します。人口は約17万人(最新の推計値)
この例では、2回目のエージェントの実行時に1回目の結果を明示的に与えて回答を得ています。
一方、セッションの機能を利用することで、以前の会話を利用して回答を作成することができます。
import { Agent, run, OpenAIConversationsSession } from "@openai/agents";
import dotenv from "dotenv";
dotenv.config();
const agent1 = new Agent({
name: 'Guide-City-Agent',
instructions: '日本の市町村名に関する質問です.ユーザーの質問に最も一致する市町村名を簡潔に答えてください。',
model: "gpt-5-nano"
});
const session = new OpenAIConversationsSession();
const result1 = await run(agent1, '東京ディズニーランドがある市はどこですか?', {
session
});
console.log('最初の問い:', result1.finalOutput);
const agent2 = new Agent({
name: 'Guide-City-Detail-Agent',
instructions: '指定した市について所属する都道府県と今わかる人口を簡潔に答えてください',
model: "gpt-5-nano"
});
const result2 = await run(agent2, "前回の回答に対する市町村について答えてください", {
session
});
console.log('次の問い:', result2.finalOutput);
この例では明示的に1回目の回答を2回目の問い合わせには使用してませんが、1回目と2回目で同じセッションを利用しています。
これにより2回目の問い合わせで過去の会話の履歴を利用することができます。
この会話の履歴は以下のように永続化することが可能です。
import fs from "fs";
/// 略
const session = new OpenAIConversationsSession();
const result1 = await run(agent1, '東京ディズニーランドがある市はどこですか?', {
session
});
console.log('最初の問い:', result1.finalOutput);
// 永続化
const snapshot = await session.getItems();
fs.writeFileSync(
"session.json",
JSON.stringify(snapshot, null, 2),
"utf-8"
);
/// 略
永続化した会話の履歴は以下のように復元できます。
const items = JSON.parse(fs.readFileSync("session.json", "utf-8"));
const session = new OpenAIConversationsSession();
await session.addItems(items)
その他、TypeScriptでのセッションの扱いは下記を参考にしてください。
Pythonによる複数段階の問い合わせを行うサンプル
Pythonにおけるセッションを使用した複数段階の問い合わせの例を確認します。
from agents import Agent, Runner, OpenAIConversationsSession
from dotenv import load_dotenv
load_dotenv()
# --- Agent 1 -------------------------------------------------
agent1 = Agent(
name="Guide-City-Agent",
instructions=(
"日本の市町村名に関する質問です。"
"ユーザーの質問に最も一致する市町村名を簡潔に答えてください。"
),
model="gpt-5-nano",
)
# セッション作成
session = OpenAIConversationsSession()
# 1回目の run
result1 = Runner.run_sync(
agent1,
"東京ディズニーランドがある市はどこですか?",
session=session,
)
print("最初の問い:", result1.final_output)
# --- Agent 2 -------------------------------------------------
agent2 = Agent(
name="Guide-City-Detail-Agent",
instructions=(
"指定した市について所属する都道府県と"
"今わかる人口を簡潔に答えてください。"
),
model="gpt-5-nano",
)
# 2回目の run(同じ session を渡す)
result2 = Runner.run_sync(
agent2,
"前回の回答に対する市町村について答えてください",
session=session,
)
print("次の問い:", result2.final_output)
PythonでもOpenAIConversationsSessionを使用してセッションを管理することができます。
セッションの永続化については非同期処理になっているため、以下のようにasyncioライブラリを使用する必要があります。
セッションの永続化の方法
import asyncio
# 略
async def dump_items():
items = await session.get_items()
with open("session_items.json", "w", encoding="utf-8") as f:
json.dump(items, f, ensure_ascii=False, indent=2)
asyncio.run(dump_items())
セッションの復元方法
async def restore_session():
# セッション作成
session = OpenAIConversationsSession()
with open("session_items.json", "r", encoding="utf-8") as f:
items = json.load(f)
# ★ ここで await
await session.add_items(items)
return session
session = asyncio.run(restore_session())
その他、Pythonでのセッションの使用方法は以下を参照してください。
ハンドオフにおける別エージェントへの委譲のサンプル
ハンドオフを使用することで、会話の一部を別のエージェントに委譲できます。
以下の例ではユーザーの質問を「数学・計算用のエージェント」または「旅行・観光用のエージェント」に振り分ける例を確認します。
TypeScriptによるハンドオフのサンプル
サンプルコード
import { Agent, run, handoff } from "@openai/agents";
import { RECOMMENDED_PROMPT_PREFIX } from '@openai/agents-core/extensions';
import dotenv from "dotenv";
dotenv.config();
// 1) 専門家: 数学
const mathAgent = new Agent({
name: "Math agent",
model: "gpt-5-nano",
instructions: `${RECOMMENDED_PROMPT_PREFIX}
あなたは数学専門。計算・数式・論理だけを簡潔に答えてください。`,
});
// 2) 専門家: 旅行
const travelAgent = new Agent({
name: "Travel agent",
model: "gpt-5-nano",
instructions: `${RECOMMENDED_PROMPT_PREFIX}
あなたは旅行専門。行程・注意点・おすすめを簡潔に答えてください。`,
});
// 3) 受付(トリアージ)
const toMath = handoff(mathAgent);
const toTravel = handoff(travelAgent);
const triageAgent = Agent.create({
name: "Triage agent",
model: "gpt-5-nano",
instructions: `
あなたは「振り分け専用」です。自然言語で回答してはいけません。
必ず次のどちらかのツールを1回だけ呼び出して終了してください。
- 数学・計算っぽい質問 → Math agent
- 旅行・観光っぽい質問 → Travel agent
(禁止)「〜に移譲しました」「お待ちください」などの文章出力
(必須)ツール呼び出しのみ
`,
handoffs: [toMath, toTravel],
});
// 実行例1: 数学に振り分け
const r1 = await run(triageAgent, "17*23 は?");
console.log("回答1:", r1.lastAgent?.name, r1.finalOutput);
// 実行例2: 旅行に振り分け
const r2 = await run(triageAgent, "来週、京都を日帰りで回るなら?");
console.log("回答2:", r2.lastAgent?.name, r2.finalOutput);
実行結果
回答1: Math agent Compute: 17 * 23 = 391.
回答2: Travel agent 京都日帰りプランの例です。移動はJR・市営バス中心を想定しています。
- 08:30 東京出発(新幹線または特急で京都へ約2:15〜2:40)
- 11:00 清水寺・産寧坂・二年坂周辺
- 清水寺の境内と桜や紅葉の季節は混雑回避のため早めの訪問がおすすめ
- 12:30 清水周辺で昼食
- 京都風ジャンル:京持ち帰り弁当や湯豆腐、鰻料理など
- 14:00 八坂神社・祇園界隈を散策
- 鴨川沿いを南下して四条界隈へ
- 16:00 金閣寺または嵐山エリアへ移動
- 金閣寺はクラシック、嵐山は渡月橋・竹林が定番
- 18:30 京都駅周辺で夕食
- 祇園・河原町で京料理や創作和食を楽しむ
- 20:00 新幹線・特急で東京へ戻る
ポイント
- 移動は混雑を避けるため、朝は早めに動く。混雑ピークの金閣寺・清水寺は朝一が狙い目。
- 季節に応じて alternatives: 春は哲学の道、秋は紅葉の嵐山・東福寺。
- 京都は歩きやすい靴を。荷物は軽めに。
必要があれば、日程の希望(食事の好み・同行者の年齢・興味)を教えてください。最適化します。
AgentからHandOffオブジェクトを作成してAgent.createを使用してハンドオフを備えたAgentを作成します。
実行結果を確認すると、r1.lastAgent.nameやr2.lastAgent.nameの表示から最後に実行したエージェントを確認できます。
今回についてはTriage-Agentが回答1ではMath agent、回答2ではTravel agentという適切なエージェントを使用していることが確認できます。
エージェントの指示に「RECOMMENDED_PROMPT_PREFIX」が指定されていますが、これによりLLMが安定するとされています。[3]
その他、TypeScriptのハンドオフについてのサンプルを以下を参照してください。
Pythonによるハンドオフのサンプル
PythonのハンドオフはTypeScriptと同じような実装が可能です。
サンプルコード
from dotenv import load_dotenv
from agents import Agent, Runner, handoff
from agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX
load_dotenv()
# 1) 専門家: 数学
math_agent = Agent(
name="Math agent",
model="gpt-5-nano",
instructions=f"""{RECOMMENDED_PROMPT_PREFIX}
あなたは数学専門。計算・数式・論理だけを簡潔に答えてください。""",
)
# 2) 専門家: 旅行
travel_agent = Agent(
name="Travel agent",
model="gpt-5-nano",
instructions=f"""{RECOMMENDED_PROMPT_PREFIX}
あなたは旅行専門。行程・注意点・おすすめを簡潔に答えてください。""",
)
# 3) 受付(トリアージ)
to_math = handoff(math_agent)
to_travel = handoff(travel_agent)
triage_agent = Agent(
name="Triage agent",
model="gpt-5-nano",
instructions="""
あなたは「振り分け専用」です。自然言語で回答してはいけません。
必ず次のどちらかのツールを1回だけ呼び出して終了してください。
- 数学・計算っぽい質問 → Math agent
- 旅行・観光っぽい質問 → Travel agent
(禁止)「〜に移譲しました」「お待ちください」などの文章出力
(必須)ツール呼び出しのみ
""",
handoffs=[to_math, to_travel],
)
# 実行例1: 数学に振り分け
r1 = Runner.run_sync(triage_agent, "17*23 は?")
print("回答1:", r1.last_agent.name, r1.final_output)
# 実行例2: 旅行に振り分け
r2 = Runner.run_sync(triage_agent, "来週、京都を日帰りで回るなら?")
print("回答2:", r2.last_agent.name, r2.final_output)
その他、Pythonのハンドオフについてのサンプルを以下を参照してください。
ツールによる関数呼び出しのサンプル
エージェントはツールを使用してデータ取得、外部 API 呼び出し、コード実行などを行うことができます。
今回は、ツールを使用して簡単なサンプルコードを呼び出します。
TypeScriptでの関数呼び出しのサンプル
サンプルコード
import { Agent, run, tool } from "@openai/agents";
import { z } from 'zod';
import dotenv from "dotenv";
dotenv.config();
const addTool = tool({
name: "add",
description: "Add two numbers",
parameters: z.object({
a: z.number(),
b: z.number()
}),
async execute({ a, b }) {
return a + b;
}
});
const agent = new Agent({
name: "calculator-agent",
instructions: "You are a calculator. Use the appropriate tool.",
tools: [addTool],
modelSettings: { toolChoice: 'required' }, // auto:デフォルト or required: かならずツールを呼び出す or none: ツールを使わない or ツール名
model: "gpt-5-nano"
});
async function main() {
const result = await run(agent, "12と30をたすと?" );
console.log(result.finalOutput);
console.dir(result.history, { depth: null });
}
main().catch(console.error);
出力例
12と30を足すと42です。
[
{ type: 'message', role: 'user', content: '12と30をたすと?' },
{
type: 'reasoning',
id: 'rs_0c73e3b6fa2a217100698288a1e6d081a0b69138a85f87743c',
content: [],
providerData: {
id: 'rs_0c73e3b6fa2a217100698288a1e6d081a0b69138a85f87743c',
type: 'reasoning'
}
},
{
type: 'function_call',
id: 'fc_0c73e3b6fa2a217100698288a6df6481a09137b1c7437734d1',
callId: 'call_U1j3e0JIIB8t3X2SKkiwB6uv',
name: 'add',
status: 'completed',
arguments: '{"a":12,"b":30}',
providerData: {
id: 'fc_0c73e3b6fa2a217100698288a6df6481a09137b1c7437734d1',
type: 'function_call'
}
},
{
type: 'function_call_result',
name: 'add',
callId: 'call_U1j3e0JIIB8t3X2SKkiwB6uv',
status: 'completed',
output: { type: 'text', text: '42' }
},
{
id: 'msg_0c73e3b6fa2a217100698288a93c3881a08303d1dc41718e2f',
type: 'message',
role: 'assistant',
content: [
{
type: 'output_text',
text: '12と30を足すと42です。',
annotations: [],
logprobs: []
}
],
status: 'completed',
providerData: {}
}
]
関数呼び出しをする場合、zodライブラリを使用してZod スキーマを定義することで厳密な型でパラメータを定義できます。
toolオブジェクトを作成して、そこに関数の定義をします。
このツールオブジェクトをAgentオブジェクトに渡すことでエージェントからツールを使用することができます。
Agentオブジェクトがtoolを自動で使用するか、強制的に使用するかはmodelSettingsのtoolChoiceで選択出来ます。
この関数の実行結果を確認すると以下のような履歴になっていることが確認できます。
- 入力をLLMによる推論
- add関数の呼び出し
- add関数の結果取得
- 関数の結果をLLMで推論
つまりadd関数の結果を単純に返しているだけでなく、add関数の結果に対してLLMで推論が行われてから問い合わせの回答が生成されます。
Pythonを使用した関数呼び出しのサンプル
サンプルコード
from agents import Agent, Runner, function_tool, ModelSettings
from pydantic import BaseModel
import dotenv
dotenv.load_dotenv()
class AddInput(BaseModel):
a: int
b: int
@function_tool
def add_tool(args: AddInput) -> int:
"""Add two numbers
Args:
args: AddInputクラス
"""
return args.a + args.b
agent = Agent(
name="calculator-agent",
instructions="You are a calculator. Use the appropriate tool.",
tools=[add_tool],
model="gpt-5-nano",
model_settings=ModelSettings(
tool_choice="required"
)
)
result = Runner.run_sync(
agent,
input="12と30をたすと?"
)
# 出力
print(result.final_output) # JS: result.finalOutput
# 途中経過(JS: result.history に近いのは new_items / raw_responses)
print("new_items:")
for item in result.new_items:
print(item)
Pythonの場合はfunction_tool関数デコレータで関数ツールを実装できます。
ホスト済みのツールの使用例
OpenAIにはWeb検索やOpenAI 上でホストされるベクトルストアの検索などの、組み込まれたツールが存在します。
今回はホスト上のWeb検索を行うエージェントのサンプルを確認します。
なお、通常のAPIを実行する場合には以下のように実行します。
TypeScriptにおけるWeb検索を実施するサンプル
サンプルコード
import { Agent, run, webSearchTool } from "@openai/agents";
import dotenv from "dotenv";
dotenv.config();
// 参考
// https://github.com/openai/openai-agents-js/blob/main/examples/tools/web-search.ts
const agent = new Agent({
name: "web-search ",
instructions: "貴方はWebを検索して結果を返すエージェントです。質問に対して検索を行いユーザーの地理的にふさわしい結果を簡潔に答えてください",
tools: [
webSearchTool({
userLocation: { type: 'approximate', city: 'Tokyo' },
})
],
modelSettings: { toolChoice: 'required' }, // auto:デフォルト or required: かならずツールを呼び出す or none: ツールを使わない or ツール名
model: "gpt-5-nano"
});
async function main() {
const result = await run(agent, "現在の天気と気温は?");
console.log(result.finalOutput);
console.dir(result.history, { depth: null });
}
main().catch(console.error);
出力結果
東京の現在の天気はほぼ晴れ、気温は約3°Cです。乾燥注意報が出ています。
[
{ type: 'message', role: 'user', content: '現在の天気と気温は?' },
{
type: 'reasoning',
id: 'rs_08630de6fc5803a60069828d247dfc819ea7a38e9aa4a75859',
content: [],
providerData: {
id: 'rs_08630de6fc5803a60069828d247dfc819ea7a38e9aa4a75859',
type: 'reasoning'
}
},
{
type: 'hosted_tool_call',
id: 'ws_08630de6fc5803a60069828d272f28819ea3b748e81892e12a',
name: 'web_search_call',
status: 'completed',
output: undefined,
providerData: {
id: 'ws_08630de6fc5803a60069828d272f28819ea3b748e81892e12a',
type: 'web_search_call',
action: {
type: 'search',
queries: [ 'weather: Tokyo, Japan' ],
query: 'weather: Tokyo, Japan'
}
}
},
{
type: 'reasoning',
id: 'rs_08630de6fc5803a60069828d280b94819e9480bb8b388fe0dc',
content: [],
providerData: {
id: 'rs_08630de6fc5803a60069828d280b94819e9480bb8b388fe0dc',
type: 'reasoning'
}
},
{
id: 'msg_08630de6fc5803a60069828d30aa34819e975b90001e24208b',
type: 'message',
role: 'assistant',
content: [
{
type: 'output_text',
text: '東京の現在の天気はほぼ晴れ、気温は約3°Cです。乾燥注意報が出ています。',
annotations: [],
logprobs: []
}
],
status: 'completed',
providerData: {}
}
]
webSearchToolをツールとして指定することでホスト側でのWeb検索が可能になります。
webSearchToolオブジェクトのパラメータを調整することで、filtersオプションを使用してアクセスするドメインを制限できたりします。
PythonにおけるWeb検索をおこなうサンプル
サンプルコード
from agents import Agent, Runner, WebSearchTool, ModelSettings
import dotenv
dotenv.load_dotenv()
agent = Agent(
name="web-search",
instructions="貴方はWebを検索して結果を返すエージェントです。質問に対して検索を行いユーザーの地理的にふさわしい結果を簡潔に答えてください",
tools=[
WebSearchTool(
user_location={ "type": "approximate", "city": "Tokyo" }
)
],
model="gpt-5-nano",
model_settings=ModelSettings(
tool_choice="required"
)
)
result = Runner.run_sync(
agent,
input="現在の天気と気温は?"
)
# 出力
print(result.final_output)
エージェントのツールにWebSearchToolを追加することで実現できます。
ローカル組み込みツールの使用例
ローカルPC用の組み込みツールも提供されており、GUIによるコンピュータ操作、Shellの実行、パッチ適用などがあります。
シェルの実行や、パッチ適用はサポートされているモデルが限られているので、ドキュメントでご確認ください。
以下でおこなうシェルの実験についてはgpt5-nanoなどではサポートされていないためgpt5.2を使用しています。
TypeScriptによるShell実行のサンプル
公式のサンプルに以下があるので以下を参考に実装するのが望ましいです。
以下は上記をもとに日本語の翻訳とタイムアウト処理や例外処理を除いたものです。実運用に使う場合は公式のサンプルを参考にしてください。
サンプルコード
import { exec } from 'node:child_process';
import { promisify } from 'node:util';
import process from 'node:process';
import {
Agent,
run,
Shell,
ShellAction,
ShellResult,
shellTool,
} from '@openai/agents';
import dotenv from "dotenv";
dotenv.config();
const execAsync = promisify(exec);
// 参考:
// https://platform.openai.com/docs/guides/tools-shell
class LocalShell implements Shell {
constructor(private readonly cwd: string = process.cwd()) {}
// 色々省略しているのでちゃんと動かす場合は以下を参照
// https://raw.githubusercontent.com/openai/openai-agents-js/refs/heads/main/examples/tools/shell.ts
async run(action: ShellAction): Promise<ShellResult> {
console.log('run...', action);
const output: ShellResult['output'] = await Promise.all(
action.commands.map(async (command) => {
const { stdout, stderr } = await execAsync(command, {
cwd: this.cwd,
timeout: action.timeoutMs,
maxBuffer: action.maxOutputLength,
});
return {
command,
stdout,
stderr,
outcome: { type: "exit", exitCode: 0 },
};
}),
);
console.dir(output, { depth: null });
return {
output,
providerData: { working_directory: this.cwd },
};
}
}
async function promptShellApproval(commands: string[]): Promise<boolean> {
console.log(' 以下のコマンドを実行します。: \n');
commands.forEach((cmd) => console.log(` > ${cmd}`));
const { createInterface } = await import('node:readline/promises');
const rl = createInterface({
input: process.stdin,
output: process.stdout,
});
try {
const answer = await rl.question('\nProceed? [y/N] ');
const approved = answer.trim().toLowerCase();
return approved === 'y' || approved === 'yes';
} finally {
rl.close();
}
}
async function main() {
const shell = new LocalShell();
const agent = new Agent({
name: 'Shell Assistant',
model: 'gpt-5.2',
instructions:
'シェルコマンドを実行してリポジトリを検査できます。応答は簡潔にし、役立つ場合はコマンドの出力も含めてください.',
tools: [
shellTool({
shell,
// ツールを実行する前に確認を求める
needsApproval: true,
onApproval: async (_ctx, approvalItem) => {
const commands =
approvalItem.rawItem.type === 'shell_call'
? approvalItem.rawItem.action.commands
: [];
const approve = await promptShellApproval(commands);
return { approve };
},
}),
],
});
const result = await run(agent, 'Show the Node.js version.');
console.log(result.finalOutput);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
実行結果
以下のコマンドを実行します。:
> node -v
> npm -v
Proceed? [y/N] y
run... { commands: [ 'node -v', 'npm -v' ], timeoutMs: 10000 }
[
{
command: 'node -v',
stdout: 'v25.2.1\n',
stderr: '',
outcome: { type: 'exit', exitCode: 0 }
},
{
command: 'npm -v',
stdout: '11.6.2\n',
stderr: '',
outcome: { type: 'exit', exitCode: 0 }
}
]
Node.js version: `v25.2.1`
(Also installed: npm `11.6.2`)
Shellツールを使う場合はShellインターフェイスを自前で実装する必要があります。
また、ローカルのシェルやファイルを動かす場合はツールを動かしていいかどうかについて人間の承認をいれたほうが良いです。
人間の承認を入れる場合、shellToolオブジェクトのneedsApprovalをtrueにしてonApprovalイベントに認証プロセスを実装してください。
その他、人間による承認については以下のドキュメントを参考にしてください。
PythonによるShell実行のサンプル
2026/2/3でインストールしたライブラリでは、以下のサンプルが動作しませんでした。
これは将来のリリースで動作する可能性があります。
MCPとの連携例
LLMと外部ツールを連携するプロトコルにMCP(Model Context Protocol)があります。
OpenAI AgentはMCPを使用して外部ツールとの連携が可能です。
ここではPlaywrightのMCPサーバーと連携してクローリングを行うサンプルを確認します。
このサンプルではZennのトップページを開いてh2タグのテキストを列挙するクローリングを実施します。
TypeScriptによるPlaywright MCPとの連携サンプル
サンプルコード
import { Agent, run, MCPServerStdio } from "@openai/agents";
import dotenv from "dotenv";
dotenv.config();
async function main() {
// Playwright MCP(stdio)を npx で起動
const playwrightMcp = new MCPServerStdio({
name: "playwright-mcp",
// -y は npx の確認プロンプト回避
// @playwright/mcp は Microsoft の Playwright MCP サーバー
fullCommand: "npx -y @playwright/mcp@latest",
// 必要なら環境変数も渡せる
// env: { ...process.env, PLAYWRIGHT_BROWSERS_PATH: "0" },
cacheToolsList: true,
});
await playwrightMcp.connect();
try {
const agent = new Agent({
name: "zenn-h2-lister",
// モデルは適宜。ツール呼び出しが必要なので toolChoice を required にするのが無難
model: "gpt-5-nano",
modelSettings: { toolChoice: "required" },
mcpServers: [playwrightMcp],
instructions: [
"あなたはブラウザの自動操作エージェントです。",
"Playwright MCPのみ使用してください。",
"Task: ",
" 1. ユーザから入力されたURLをブラウザで開きます",
" 2. すべてのページ中の <h2> 文字を列挙してください。",
" もし h2 が空文字なら対象外とします。",
" 3. 結果を文字列の配列のJSONで返します。コメントは不要です",
"Be robust: ",
" - 抽出前にページのロードがおわることを待ってください.",
].join("\n"),
});
const result = await run(agent, "https://zenn.dev/");
console.log(result.finalOutput);
console.dir(result.history, { depth: null });
} finally {
await playwrightMcp.close();
}
}
main().catch((e) => {
console.error(e);
process.exit(1);
});
このサンプルを実行するとブラウザが起動してクローリングが行われます。
PythonによるPlaywright MCPとの連携サンプル
Python用のPlaywright MCPサーバーは存在していないので、nodeでインストールしたplaywright-mcpを使用します。
npx -y @playwright/mcp@latest
サンプルコード
import asyncio
import os
from dotenv import load_dotenv
from agents import Agent, Runner
from agents.mcp import MCPServerStdio
from agents.model_settings import ModelSettings
load_dotenv()
INSTRUCTIONS = """
あなたはブラウザの自動操作エージェントです。
Playwright MCPのみ使用してください。
Task:
1. ユーザから入力されたURLをブラウザで開きます
2. すべてのページ中の <h2> 文字を列挙してください。
もし h2 が空文字なら対象外とします。
3. 結果を文字列の配列のJSONで返します。コメントは不要です
Be robust:
- 抽出前にページのロードがおわることを待ってください.
"""
async def main() -> None:
# Playwright MCP(stdio)を npx で起動
# TS: fullCommand: "npx -y @playwright/mcp@latest"
async with MCPServerStdio(
name="playwright-mcp",
params={
"command": "npx",
"args": ["-y", "@playwright/mcp@latest"],
# 必要なら環境変数も渡せる(TS の env: { ...process.env, ... } に相当)
"env": dict(os.environ),
# "cwd": "/path/to/working/dir", # 必要なら
},
cache_tools_list=True,
client_session_timeout_seconds=60,
max_retry_attempts=2,
retry_backoff_seconds_base=1.0,
) as server:
agent = Agent(
name="zenn-h2-lister",
model="gpt-5-nano",
model_settings=ModelSettings(tool_choice="required"),
mcp_servers=[server],
instructions=INSTRUCTIONS,
)
url = "https://zenn.dev/"
result = await Runner.run(agent, url)
# TS: console.log(result.finalOutput)
# Python: result.final_output
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())
MCPServerStdioについて同期コンテキストマネージャ(with MCPServerStdio())をサポートしていなかったため、今回は非同期処理で実装しています。
基本的な流れはTypeScriptと同じような実装になっています。
ただし、Pythonの場合はデフォルトの設定だとタイムアウトで終了したためclient_session_timeout_secondsを使用してMCPサーバーのセッションタイムアウト時間を調整しています。
ガードレールの例
ガードレールを使用することでユーザー入力やエージェント出力に対するチェックやバリデーションを行えます。
ガードレールはAgentやtoolに設定することが可能です。
次の例では以下のガードレールを追加します。
- 加算ツールの入力であるa, bは10未満であること
- 加算ツールの出力は10未満の数値であること
- エージェントの入力は算術計算に関係するものであること
TypeScriptによるガードレールのサンプル
サンプルコード
import {
Agent,
run,
tool,
InputGuardrail,
ToolGuardrailFunctionOutputFactory,
defineToolInputGuardrail,
defineToolOutputGuardrail
} from "@openai/agents";
import { z } from 'zod';
import dotenv from "dotenv";
dotenv.config();
const addParameterType = z.object({
a: z.number(),
b: z.number()
});
const guardrailAgent = new Agent({
name: 'Guardrail check',
instructions: 'この質問が算術計算について質問しているかを確認してください.',
outputType: z.object({
isMathHomework: z.boolean(),
reasoning: z.string(),
}),
});
const mathGuardrail: InputGuardrail = {
name: 'Math Homework Guardrail',
runInParallel: false,
execute: async ({ input, context }) => {
const result = await run(guardrailAgent, input, { context });
return {
outputInfo: result.finalOutput,
tripwireTriggered: result.finalOutput?.isMathHomework === false,
};
},
};
// 入力ガードレール: a または b が 10 以上の場合は拒否する
const inputValueGuardrailBlock = defineToolInputGuardrail({
name: 'inputValueGuardrailBlock',
run: async ({ toolCall }) => {
console.log('inputValueGuardrailBlock called with:', toolCall);
const args = JSON.parse(toolCall.arguments);
if (args.a >= 10 || args.b >= 10) {
return ToolGuardrailFunctionOutputFactory.rejectContent(
`${args.a}または${args.b}が10以上です。`,
);
}
return ToolGuardrailFunctionOutputFactory.allow();
},
});
// 出力ガードレール: 結果が数値でない場合は拒否する
function isNumericStrict(value: string): boolean {
if (value.trim() === "") return false;
const n = Number(value);
return Number.isFinite(n);
}
const outputValueGuardrailBlock = defineToolOutputGuardrail({
name: 'outputValueGuardrailBlock',
run: async ({ output }) => {
console.log('outputValueGuardrailBlock called with:', output);
const text = String(output ?? '');
if (isNumericStrict(text)) {
const v = Number(text);
if (v < 10) {
return ToolGuardrailFunctionOutputFactory.allow();
} else {
return ToolGuardrailFunctionOutputFactory.rejectContent(
'Output is too large.',
);
}
} else {
return ToolGuardrailFunctionOutputFactory.rejectContent(
'Output is not numeric.',
);
}
},
});
const addTool = tool({
name: "add",
description: "Add two numbers",
parameters: addParameterType,
inputGuardrails: [inputValueGuardrailBlock],
outputGuardrails: [outputValueGuardrailBlock],
async execute({ a, b }) {
console.log('add tool called with:', { a, b });
return a + b;
}
});
const agent = new Agent({
name: "calculator-agent",
instructions: "貴方は計算機です。適切なtoolを使用して計算してください。toolからの結果が得られない場合は原因を記載して回答不能と簡潔に回答してください。貴方自身が答えを計算するのは絶対にやめてください。",
tools: [addTool],
modelSettings: { toolChoice: 'required' }, // auto:デフォルト or required: かならずツールを呼び出す or none: ツールを使わない or ツール名
model: "gpt-5-nano",
inputGuardrails: [mathGuardrail],
});
async function main() {
{
// 正常
const result = await run(agent, "1と3をたすと?" );
console.log(result.finalOutput);
// console.dir(result.history, { depth: null });
}
{
// ツールの入力ガードレールで拒否
const result = await run(agent, "1と30をたすと?" );
console.log(result.finalOutput);
// console.dir(result.history, { depth: null });
}
{
// ツールの出力ガードレールで拒否
const result = await run(agent, "1と9をたすと?" );
console.log(result.finalOutput);
// console.dir(result.history, { depth: null });
}
{
// エージェントの入力ガードレールで拒否。例外がでる
const result = await run(agent, "今日の天気は?" );
console.log(result.finalOutput);
// console.dir(result.history, { depth: null });
}
}
main().catch(console.error);
実行結果
inputValueGuardrailBlock called with: {
type: 'function_call',
id: 'fc_0532b9f8f52d1d9f006982ba16f5bc81a3a6c09eccf2848bb3',
callId: 'call_pOETSrZ3rZPMCkddfgLEeZfz',
name: 'add',
status: 'completed',
arguments: '{"a":1,"b":3}',
providerData: {
id: 'fc_0532b9f8f52d1d9f006982ba16f5bc81a3a6c09eccf2848bb3',
type: 'function_call'
}
}
add tool called with: { a: 1, b: 3 }
outputValueGuardrailBlock called with: 4
4です。
inputValueGuardrailBlock called with: {
type: 'function_call',
id: 'fc_0236102adf9f8aa2006982ba1ddd0481978b2e7d101050a0b1',
callId: 'call_jK9ORfCSCcipwzUhQGmWXNxb',
name: 'add',
status: 'completed',
arguments: '{"a":1,"b":30}',
providerData: {
id: 'fc_0236102adf9f8aa2006982ba1ddd0481978b2e7d101050a0b1',
type: 'function_call'
}
}
inputValueGuardrailBlock called with: {
type: 'function_call',
id: 'fc_0236102adf9f8aa2006982ba21421081978cb76d742539b334',
callId: 'call_LN4KpaZVdcvjpn4lJbUnheDX',
name: 'add',
status: 'completed',
arguments: '{"a":1,"b":30}',
providerData: {
id: 'fc_0236102adf9f8aa2006982ba21421081978cb76d742539b334',
type: 'function_call'
}
}
ツールが数値を返さず、計算結果を提供できません。回答不能です。
inputValueGuardrailBlock called with: {
type: 'function_call',
id: 'fc_023a3fb271aa0b56006982ba2f0450819d97796e003484fb2d',
callId: 'call_c8cfBu4Ir6BFj0GzeIG3C87U',
name: 'add',
status: 'completed',
arguments: '{"a":1,"b":9}',
providerData: {
id: 'fc_023a3fb271aa0b56006982ba2f0450819d97796e003484fb2d',
type: 'function_call'
}
}
add tool called with: { a: 1, b: 9 }
outputValueGuardrailBlock called with: 10
inputValueGuardrailBlock called with: {
type: 'function_call',
id: 'fc_023a3fb271aa0b56006982ba326074819d8ceccece33f718a5',
callId: 'call_XX9hWeBQcNbfIDWIGCGeyXOh',
name: 'add',
status: 'completed',
arguments: '{"a":1,"b":9}',
providerData: {
id: 'fc_023a3fb271aa0b56006982ba326074819d8ceccece33f718a5',
type: 'function_call'
}
}
add tool called with: { a: 1, b: 9 }
outputValueGuardrailBlock called with: 10
申し訳ありません。ツールからの結果を取得できませんでした。原因: 出力が大きすぎるエラーです。
InputGuardrailTripwireTriggered: Input guardrail triggered: {"isMathHomework":false,"reasoning":"The question '今日の天気は?' is asking about today's weather, which does not involve arithmetic calculation."}
この例の実行結果は1つ目の質問は4, 2つ目の質問はツールの入力ガードレールにより回答不能, 3つ目の質問はツールの出力ガードレールにより回答不能, 4つ目はエージェントの入力ガードレールにより例外となります。
ガードレールを違反した際の結果が、ツールかエージェントのガードレールかによって異なっています。
ツールのガードレール違反は例外とならず処理が実行されます。プロンプトによってはツールでエラーであっても別の手段、たとえばLLMで足し算の結果を計算させて回答します。
エージェントに対するガードレール違反は直ちに例外となります。
Pythonによるガードレールのサンプル
サンプルコード
from agents import Agent, Runner, function_tool, ModelSettings
from agents import (
input_guardrail,
tool_input_guardrail,
tool_output_guardrail,
GuardrailFunctionOutput,
ToolGuardrailFunctionOutput,
InputGuardrailTripwireTriggered,
)
from pydantic import BaseModel
import dotenv
import os
import json
dotenv.load_dotenv()
class AddInput(BaseModel):
a: int
b: int
# --- (1) エージェント入力ガードレール(TS: mathGuardrail 相当)---
class MathGuardrailOut(BaseModel):
isMathHomework: bool
reasoning: str
guardrail_agent = Agent(
name="Guardrail check",
instructions="この質問が算術計算について質問しているかを確認してください.",
output_type=MathGuardrailOut,
model="gpt-5-nano",
)
@input_guardrail(run_in_parallel=False)
async def math_guardrail(ctx, agent: Agent, user_input: str):
print("math_guardrail:", user_input)
r = await Runner.run(guardrail_agent, input=user_input, context=ctx.context)
out: MathGuardrailOut = r.final_output
print("math_guardrail output:", out)
# TS と同じ:算術でないなら tripwire
return GuardrailFunctionOutput(
output_info=out,
tripwire_triggered=(out.isMathHomework is False),
)
# --- (2) ツール入力ガードレール(TS: a or b >= 10 で拒否)---
@tool_input_guardrail
def input_value_guardrail_block(data):
payload = json.loads(data.context.tool_arguments or "{}")
print('input_value_guardrail_block:', payload)
args = payload.get("args", {})
a = args.get("a")
b = args.get("b")
# 念のため
if a is None or b is None:
return ToolGuardrailFunctionOutput.reject_content("Missing a or b.")
print("a:", a, "b:", b)
if a >= 10 or b >= 10:
print(f"input_value_guardrail_block reject: a={a}, b={b}")
return ToolGuardrailFunctionOutput.reject_content(f"{a}または{b}が10以上です。")
return ToolGuardrailFunctionOutput.allow()
# --- (3) ツール出力ガードレール(TS: 数値かつ <10 以外は拒否)---
def is_numeric_strict(text: str) -> bool:
if text.strip() == "":
return False
try:
v = float(text)
except ValueError:
return False
return v not in (float("inf"), float("-inf"))
@tool_output_guardrail
def output_value_guardrail_block(data):
print("output_value_guardrail_block:", data.output)
text = str(data.output if data.output is not None else "")
if is_numeric_strict(text):
v = float(text)
if v < 10:
return ToolGuardrailFunctionOutput.allow()
return ToolGuardrailFunctionOutput.reject_content("Output is too large.")
return ToolGuardrailFunctionOutput.reject_content("Output is not numeric.")
# ---- ここから下は「元の add_tool / agent / run_sync」を極力そのまま ----
@function_tool(
tool_input_guardrails=[input_value_guardrail_block],
tool_output_guardrails=[output_value_guardrail_block],
)
def add_tool(args: AddInput) -> int:
"""Add two numbers
Args:
args: AddInputクラス
"""
print("add_tool called with:", args)
return args.a + args.b
agent = Agent(
name="calculator-agent",
instructions="貴方は計算機です。適切なtoolを使用して計算してください。toolからの結果が得られない場合は原因を記載して回答不能と簡潔に回答してください。貴方自身が答えを計算するのは絶対にやめてください。",
tools=[add_tool],
model="gpt-5-nano",
model_settings=ModelSettings(tool_choice="required"),
input_guardrails=[math_guardrail], # ★ ここだけ追加
)
def run_case(text: str):
try:
result = Runner.run_sync(agent, input=text)
print("input:", text)
print("output:", result.final_output)
except InputGuardrailTripwireTriggered:
print("input:", text)
print("output: 入力ガードレールにより拒否されました。")
# 動作確認(TS の main の4ケース相当)
run_case("1と3をたすと?") # 正常
run_case("1と30をたすと?") # ツール入力ガードレールで拒否
run_case("1と9をたすと?") # ツール出力ガードレールで拒否(10は >=10)
run_case("今日の天気は?") # エージェント入力ガードレールで拒否
出力結果
% uv run agent_sample04b.py
math_guardrail: 1と3をたすと?
math_guardrail output: isMathHomework=True reasoning='はい。この質問は算術計算(加算)に関するものです。1と3を足すという基本的な算術の問題です。'
input_value_guardrail_block: {'args': {'a': 1, 'b': 3}}
a: 1 b: 3
add_tool called with: a=1 b=3
output_value_guardrail_block: 4
input: 1と3をたすと?
output: 4
math_guardrail: 1と30をたすと?
math_guardrail output: isMathHomework=True reasoning='これは算術計算の質問です。1と30を足すと答えは31です。'
input_value_guardrail_block: {'args': {'a': 1, 'b': 30}}
a: 1 b: 30
input_value_guardrail_block reject: a=1, b=30
input_value_guardrail_block: {'args': {'a': 1, 'b': 30}}
a: 1 b: 30
input_value_guardrail_block reject: a=1, b=30
input: 1と30をたすと?
output: 計算ツールからの結果を得られませんでした。返ってきたのは数値ではなくエラーメッセージのような文字列のため、答えを提供できません。再度試してよろしいですか?
math_guardrail: 1と9をたすと?
math_guardrail output: isMathHomework=True reasoning='はい、これは算術計算です。1と9をたすと10になります。'
input_value_guardrail_block: {'args': {'a': 1, 'b': 9}}
a: 1 b: 9
add_tool called with: a=1 b=9
output_value_guardrail_block: 10
input_value_guardrail_block: {'args': {'a': 1, 'b': 9}}
a: 1 b: 9
add_tool called with: a=1 b=9
output_value_guardrail_block: 10
input: 1と9をたすと?
output: ツールからの結果を取得できませんでした。原因: 出力が大きすぎるためです。
math_guardrail: 今日の天気は?
math_guardrail output: isMathHomework=False reasoning='この質問は天気について尋ねており、算術計算(数学の計算)には関係ありません。'
input: 今日の天気は?
output: 入力ガードレールにより拒否されました
PythonでもTypeScriptと同様のガードレールを入れられます。
なお、run_syncで動かしている処理から呼ばれた関数で、run_syncを使うとエラーとなったため、エージェントのガードレールについてはrunを使用して非同期に実行しています。
動作の履歴を確認するとツールのガードレールでToolGuardrailFunctionOutput.reject_contentを返しているケースでリトライが動いているような挙動になっています。もし直ちに止めたい場合などはraise_exceptionを検討した方がよさそうです。
まとめ
今回はOpenAI Agent SDKを使用してOpenAIのエージェントを使用する実験を行いました。
APIを使用しているだけの時は自前でLLMと関数や外部サービスの連携をおこなう必要がありましたが、エージェントを使用することで、それらのハードルが低くなるように感じました。
また、Pythonの場合、Agentをグラフとして表現できたり、前述の実行トレースの機能があるので、どういう処理をやっているかについても、そう難しくなく確認できます。
一方、LLMが主になってどの機能と連携するかを判断するということは非決定的な動作が行われる可能性があります。たとえば、独自関数で行ってほしいことをLLMが勝手に答えるという事態などです。
そのため、実運用時にはログなどによる監視の仕組みを検討する必要があるでしょう。
また、LLMの使用量も必要に応じてAPIを実行していたときより多くなる傾向があります。
なお、今回紹介しきれなかった機能もありますが、公式のサンプルをみることで、どう実装すればいいかは当たりがつくのではないかと思います。
参考
- OpenAI Agent SDK Document
- openai-agents-js のコード
- openai-agents-js のドキュメント
- openai-agents-pythonのコード
- openai-agents-pythonのドキュメント
- zod
- playwright mcp
- What is the Model Context Protocol (MCP)?
-
2026年1月の末にto_stateが追加されています。これがリリースされればTypeScriptのような使いかたができるかもしれません。参考 ↩︎
-
あくまで例です。LLMで市町村の人口をとるとハルシネーションを起こしやすいですし、そもそも人口は測定年によってかわるもので、一律できまるものではありません。 ↩︎
-
推奨プロンプトに記載されている内容です。RECOMMENDED_PROMPT_PREFIXは以下のような内容となっています。
"# System context\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstractions: **Agents** and **Handoffs**. An agent encompasses instructions and tools and can hand off a conversation to another agent when appropriate. Handoffs are achieved by calling a handoff function, generally named ``transfer_to_<agent_name>``. Transfers between agents are handled seamlessly in the background; do not mention or draw attention to these transfers in your conversation with the user."↩︎
Discussion