🔧create-atomic-tool
- プラグイン
- atomic-agents
- ソース
- GitHub で見る ↗
説明
`BaseTool[InSchema, OutSchema]` のサブクラスを構築します。 入出力スキーマ、`BaseToolConfig`、`run()`(および任意の `run_async()`)、環境変数ベースのシークレット管理、型付きの失敗出力に対応しています。 次のような場合に使用: ユーザーが「ツールを追加したい」「ツールを作成したい」「APIをツールとしてラップしたい」「`BaseTool` を構築したい」「計算機/検索/天気ツールを作りたい」と依頼した場合、または `/atomic-agents:create-atomic-tool` を実行した場合。
原文を表示
Build a `BaseTool[InSchema, OutSchema]` subclass — input/output schemas, `BaseToolConfig`, `run()` (and optional `run_async()`), env-driven secrets, typed failure outputs. Use when the user asks to "add a tool", "create a tool", "wrap an API as a tool", "build a `BaseTool`", "make a calculator/search/weather tool", or runs `/atomic-agents:create-atomic-tool`.
ユースケース
- ✓ツールを追加・作成したいとき
- ✓APIをツールとしてラップするとき
- ✓計算機/検索/天気ツールを作るとき
- ✓BaseTool をサブクラス構築するとき
本文(日本語訳)
Atomic Agents ツールの作成
ツールとは、agentが呼び出せる決定論的な機能のことです。Atomic Agentsにおいて、すべてのツールは BaseTool[InSchema, OutSchema] のサブクラスとして実装され、型付きの run()(および省略可能な run_async())を持ちます。入出力スキーマは、LLMに対するツールのシグネチャとしての役割と、実行時のPydanticバリデーションとしての役割を兼ねています。
より詳細な情報(MCPとの相互運用、スタンドアロンパッケージとしての配布、高度なエラーパターンなど)については、../framework/references/tools.md を参照してください。
このスキルは実践的なアプローチを取ります: 明確化 → 実装 → 検証。
このスキルと上位の framework スキルの使い分け
- このスキル: ユーザーが特定のツールを作成しようとしている場合 — APIのラッパー、計算機の作成、ページのスクレイピング、DBへのクエリなど。
frameworkスキル: Atomic Agentsに関する一般的な質問、またはツールの作成以外の作業をしている場合。
フェーズ 1 — 明確化
以下の項目をまとめて一つのメッセージで確認します:
- ツールは何をするものか? 一文で説明してください。これがクラスのdocstringになり、LLMのツール説明として使われます。
- 入力と出力。 名前、型、単位。不明な場合は、スキーマのペアを提案して確認します。
- 外部依存関係。 HTTP API? DB? ローカル計算のみ? HTTPの場合、認証方法は?(APIキーの環境変数、OAuth、なし)
- 同期・非同期、またはその両方? プロジェクトの他の部分が非同期の場合、またはI/Oバウンドの処理の場合は、
run()と並行してrun_async()も実装することを検討してください。 - 失敗のパターン。 レート制限、リソース未発見、ネットワークエラー — agentにどう見せるべきか? デフォルト: 例外を投げるのではなく、型付きの失敗出力を使用します。
すでにコンテキストから明らかな質問はスキップしてください。
フェーズ 2 — 計画
ファイルの配置と構造を簡潔なブロックで確認し、実装に進みます:
- ファイル:
<project>/tools/<tool_name>_tool.py(プロジェクト内ツール —../framework/references/project-structure.md参照) - スキーマ:
<ToolName>Input、<ToolName>Output、必要に応じて型付きの失敗シェイプ - Config: ツールにAPIキー、ベースURL、タイムアウト、リトライが必要な場合は
<ToolName>Config(BaseToolConfig)を使用 - 同期・非同期: どちらか一方、または両方を選択
- エラーパターン: 型付き失敗出力(推奨)vs 例外送出(プログラマーエラーのみ)
フェーズ 3 — 実装
スケルトン — ローカル計算、configなし
from pydantic import Field
from atomic_agents import BaseIOSchema, BaseTool
class CalculatorInput(BaseIOSchema):
"""評価する算術式。"""
expression: str = Field(..., description="Pythonスタイルの算術式。例: '2 + 2 * 3'")
class CalculatorOutput(BaseIOSchema):
"""式を評価した結果。"""
result: float = Field(..., description="数値の結果。")
class CalculatorTool(BaseTool[CalculatorInput, CalculatorOutput]):
"""単純な算術式を安全に評価します。"""
def run(self, params: CalculatorInput) -> CalculatorOutput:
import ast, operator as op
ops = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul, ast.Div: op.truediv}
def ev(n):
if isinstance(n, ast.Constant): return n.value
if isinstance(n, ast.BinOp): return ops[type(n.op)](ev(n.left), ev(n.right))
raise ValueError("unsupported")
return CalculatorOutput(result=ev(ast.parse(params.expression, mode="eval").body))
スケルトン — HTTPバックエンド、configと型付き失敗あり
import os
import httpx
from typing import Literal, Optional
from pydantic import Field
from atomic_agents import BaseIOSchema, BaseTool, BaseToolConfig
class WeatherConfig(BaseToolConfig):
api_key: str = Field(
default_factory=lambda: os.environ.get("WEATHER_API_KEY", ""),
description="天気サービスのAPIキー。",
)
base_url: str = Field(
default="https://api.weather.example/v1",
description="天気APIのベースURL。",
)
timeout: float = Field(default=15.0, ge=1.0, le=120.0, description="リクエストタイムアウト(秒)。")
class WeatherInput(BaseIOSchema):
"""現在の気象情報のリクエスト。"""
city: str = Field(..., description="都市名。例: 'Brussels'")
class WeatherOutput(BaseIOSchema):
"""現在の気象情報、または型付きの失敗情報。"""
status: Literal["ok", "error"] = Field(..., description="結果コード。")
temperature_c: Optional[float] = Field(default=None, description="気温(摂氏)。")
summary: Optional[str] = Field(default=None, description="人間が読めるサマリー。")
error: Optional[str] = Field(default=None, description="status='error' の場合の失敗メッセージ。")
class WeatherTool(BaseTool[WeatherInput, WeatherOutput]):
"""天気APIから指定した都市の現在の気象情報を取得します。"""
def __init__(self, config: WeatherConfig | None = None):
super().__init__(config or WeatherConfig())
def run(self, params: WeatherInput) -> WeatherOutput:
cfg: WeatherConfig = self.config
if not cfg.api_key:
return WeatherOutput(status="error", error="WEATHER_API_KEY が設定されていません")
try:
r = httpx.get(
f"{cfg.base_url}/current",
params={"city": params.city},
headers={"Authorization": f"Bearer {cfg.api_key}"},
timeout=cfg.timeout,
)
r.raise_for_status()
except httpx.HTTPError as e:
return WeatherOutput(status="error", error=str(e))
data = r.json()
return WeatherOutput(status="ok", temperature_c=data["temp_c"], summary=data["summary"])
async def run_async(self, params: WeatherInput) -> WeatherOutput:
cfg: WeatherConfig = self.config
if not cfg.api_key:
return WeatherOutput(status="error", error="WEATHER_API_KEY が設定されていません")
async with httpx.AsyncClient(timeout=cfg.timeout) as client:
try:
r = await client.get(
f"{cfg.base_url}/current",
params={"city": params.city},
headers={"Authorization": f"Bearer {cfg.api_key}"},
)
r.raise_for_status()
except httpx.HTTPError as e:
return WeatherOutput(status="error", error=str(e))
data = r.json()
return WeatherOutput(status="ok", temperature_c=data["temp_c"], summary=data["summary"])
厳守ルール
- ジェネリクスのパラメータが実行時の型情報を保持します。
input_schema/output_schemaをクラス属性として別途定義しないでください — フレームワークが管理するプロパティを隠蔽してしまいます。 run()は辞書ではなく、出力スキーマのインスタンスを返してください。- シークレットは環境変数 /
BaseToolConfig経由で管理し、ハードコードは禁止です。 - HTTPツールには必ずタイムアウトを設定してください。ツールはagentのリクエストパス上で実行されます。
- 非同期フックは
async def run_asyncです。arunではありません — フレームワークはrun_asyncを呼び出します。 - レート制限、404、上流APIからのバリデーション拒否などの通常の失敗は、型付き失敗出力としてモデル化してください。
raiseはプログラマーエラーのためだけに使用します。
フェーズ 4 — agentへの組み込み
2つの統合パターンがあります(詳細は ../framework/references/tools.md を参照):
シングルツールagent — agentの出力スキーマがそのままツールの入力スキーマになります:
agent = AtomicAgent[UserQuery, WeatherInput](config=config)
tool = WeatherTool()
call = agent.run(UserQuery(question="weather in Brussels?"))
result = tool.run(call)
ルーターagent — agentがツール呼び出しスキーマの判別共用体(discriminated union)を通じてツールを選択します。
agentが2〜10個のツールを選択する場合に使用してください。
数十個以上の場合は、../framework/references/orchestration.md の検索+実行パターンを参照してください。
フェーズ 5 — 検証
uv run python -c "from <project>.tools.<tool_name>_tool import <ToolName>Tool, <ToolName>Input; t = <ToolName>Tool(); print(t.run(<ToolName>Input(...)))"
- docstringに関するエラーでインポートが失敗する場合は、スキーマにdocstringを追加してください。
self.input_schemaがNoneの場合、ジェネリクスのパラメータが不足しています —class FooTool(BaseTool):ではなくclass FooTool(BaseTool[FooInput, FooOutput]):と記述してください。
フェーズ 6 — 引き渡し
以下をユーザーに伝えます:
- ツールの配置場所とインポート方法。
- agentがそれをどう使うか(シングルツール or ルーターパターン)。
- オプションの次のステップ:
- このツールを呼び出すagentの作成 →
create-atomic-agentスキル - ツールを中心としたマルチagentの配線 →
../framework/references/orchestration.md - MCPとの相互運用やツールの配布パッケージ化 →
../framework/references/tools.md
- このツールを呼び出すagentの作成 →
アンチパターン
class FooTool(BaseTool):にクラス属性としてinput_schema = ...を定義するパターン — ジェネリクスを使用してください:BaseTool[FooInput, FooOutput]run()からOutputSchema(...)ではなく辞書やプリミティブ型を返すパターン- 通常の上流エラーで例外を送出するパターン — 型付き出力としてモデル化してください
- HTTP / DBの呼び出しにタイムアウトを設定しないパターン
MCPTransportType.STREAMABLE_HTTPの使用 — 正しい値はHTTP_STREAMですasync def arun(...)の実装 — フレームワークはrun_asyncを呼び出します
MCPとの相互運用、atomic download 向けのツールパッケージ化、高度なルーターパターンなど、より詳細な情報については ../framework/references/tools.md を参照してください。
原文(English)を表示
Create an Atomic Agents Tool
A tool is a deterministic capability an agent can invoke. In Atomic Agents, every tool is a BaseTool[InSchema, OutSchema] subclass with a typed run() (and optional run_async()). The input/output schemas double as the tool's signature for the LLM and as Pydantic validation at runtime.
For deep material (MCP interop, distributing as a standalone package, advanced error patterns), the authority is ../framework/references/tools.md. This skill is the action-oriented path: clarify → write → verify.
When this fires vs the umbrella framework skill
- This skill: the user is creating a specific tool — wrapping an API, building a calculator, scraping a page, querying a DB.
frameworkskill: questions about Atomic Agents in general, or the user is doing something other than authoring a tool.
Phase 1 — Clarify
Bundle into one message:
- What does the tool do? One sentence. This becomes the class docstring and feeds the LLM's tool description.
- Inputs and outputs. Names, types, units. If unclear, propose a schema pair and confirm.
- External dependencies. HTTP API? DB? Local computation only? If HTTP, what auth (API key env var, OAuth, none)?
- Sync, async, or both? If the rest of the project is async or the call is I/O bound, plan a
run_async()alongsiderun(). - Failure modes. Rate limits, not-found, network errors — how should the agent see them? Default: typed failure output, not raised exceptions.
Skip any question already answered in context.
Phase 2 — Plan
Confirm the location and shape in one short block, then proceed:
- File:
<project>/tools/<tool_name>_tool.py(in-project tool — see../framework/references/project-structure.md). - Schemas:
<ToolName>Input,<ToolName>Output, optionally a typed failure shape. - Config:
<ToolName>Config(BaseToolConfig)if the tool needs API keys, base URLs, timeouts, retries. - Sync vs async: pick one or both.
- Error pattern: typed failure output (preferred) vs raise (only for programmer error).
Phase 3 — Implement
Skeleton — local computation, no config
from pydantic import Field
from atomic_agents import BaseIOSchema, BaseTool
class CalculatorInput(BaseIOSchema):
"""Arithmetic expression to evaluate."""
expression: str = Field(..., description="Python-style arithmetic, e.g. '2 + 2 * 3'.")
class CalculatorOutput(BaseIOSchema):
"""Result of evaluating the expression."""
result: float = Field(..., description="Numeric result.")
class CalculatorTool(BaseTool[CalculatorInput, CalculatorOutput]):
"""Evaluate simple arithmetic expressions safely."""
def run(self, params: CalculatorInput) -> CalculatorOutput:
import ast, operator as op
ops = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul, ast.Div: op.truediv}
def ev(n):
if isinstance(n, ast.Constant): return n.value
if isinstance(n, ast.BinOp): return ops[type(n.op)](ev(n.left), ev(n.right))
raise ValueError("unsupported")
return CalculatorOutput(result=ev(ast.parse(params.expression, mode="eval").body))
Skeleton — HTTP-backed, with config and typed failure
import os
import httpx
from typing import Literal, Optional
from pydantic import Field
from atomic_agents import BaseIOSchema, BaseTool, BaseToolConfig
class WeatherConfig(BaseToolConfig):
api_key: str = Field(
default_factory=lambda: os.environ.get("WEATHER_API_KEY", ""),
description="API key for the weather service.",
)
base_url: str = Field(
default="https://api.weather.example/v1",
description="Base URL for the weather API.",
)
timeout: float = Field(default=15.0, ge=1.0, le=120.0, description="Request timeout (s).")
class WeatherInput(BaseIOSchema):
"""A request for current weather conditions."""
city: str = Field(..., description="City name, e.g. 'Brussels'.")
class WeatherOutput(BaseIOSchema):
"""Current weather conditions, or a typed failure."""
status: Literal["ok", "error"] = Field(..., description="Outcome code.")
temperature_c: Optional[float] = Field(default=None, description="Temperature in Celsius.")
summary: Optional[str] = Field(default=None, description="Human-readable summary.")
error: Optional[str] = Field(default=None, description="Failure message when status='error'.")
class WeatherTool(BaseTool[WeatherInput, WeatherOutput]):
"""Fetch current conditions for a city from the weather API."""
def __init__(self, config: WeatherConfig | None = None):
super().__init__(config or WeatherConfig())
def run(self, params: WeatherInput) -> WeatherOutput:
cfg: WeatherConfig = self.config
if not cfg.api_key:
return WeatherOutput(status="error", error="WEATHER_API_KEY not set")
try:
r = httpx.get(
f"{cfg.base_url}/current",
params={"city": params.city},
headers={"Authorization": f"Bearer {cfg.api_key}"},
timeout=cfg.timeout,
)
r.raise_for_status()
except httpx.HTTPError as e:
return WeatherOutput(status="error", error=str(e))
data = r.json()
return WeatherOutput(status="ok", temperature_c=data["temp_c"], summary=data["summary"])
async def run_async(self, params: WeatherInput) -> WeatherOutput:
cfg: WeatherConfig = self.config
if not cfg.api_key:
return WeatherOutput(status="error", error="WEATHER_API_KEY not set")
async with httpx.AsyncClient(timeout=cfg.timeout) as client:
try:
r = await client.get(
f"{cfg.base_url}/current",
params={"city": params.city},
headers={"Authorization": f"Bearer {cfg.api_key}"},
)
r.raise_for_status()
except httpx.HTTPError as e:
return WeatherOutput(status="error", error=str(e))
data = r.json()
return WeatherOutput(status="ok", temperature_c=data["temp_c"], summary=data["summary"])
Hard rules
- Generic parameters carry the runtime type info. Never also assign
input_schema/output_schemaas class attributes — it shadows the framework-managed property. run()returns the output schema instance, not a dict.- Secrets via env /
BaseToolConfig, never hardcoded. - HTTP tools always set a timeout. Tools run in the agent's request path.
- The async hook is
async def run_async, notarun— the framework callsrun_async. - Convert routine failures (rate limits, 404s, validation rejects from the upstream API) into a typed failure output. Reserve
raisefor programmer error.
Phase 4 — Wire it into an agent
Two integration shapes (see ../framework/references/tools.md for more):
Single-tool agent — agent's output schema is the tool's input schema:
agent = AtomicAgent[UserQuery, WeatherInput](config=config)
tool = WeatherTool()
call = agent.run(UserQuery(question="weather in Brussels?"))
result = tool.run(call)
Router agent — agent picks among tools via a discriminated union of tool-call schemas. Use this when the agent has 2–10 tools to choose from. For dozens, see the search+execute pattern in ../framework/references/orchestration.md.
Phase 5 — Verify
uv run python -c "from <project>.tools.<tool_name>_tool import <ToolName>Tool, <ToolName>Input; t = <ToolName>Tool(); print(t.run(<ToolName>Input(...)))"
If imports fail with the docstring error, add the docstring on the schema. If self.input_schema is None, the generic parameters are missing — write class FooTool(BaseTool[FooInput, FooOutput]):, not class FooTool(BaseTool):.
Phase 6 — Hand off
Tell the user:
- Where the tool lives and what to import.
- How the agent should use it (single-tool or router shape).
- Optional next steps:
- The agent that calls it →
create-atomic-agentskill. - Multi-agent wiring around the tool →
../framework/references/orchestration.md. - MCP interop or packaging the tool for distribution →
../framework/references/tools.md.
- The agent that calls it →
Anti-patterns
class FooTool(BaseTool):withinput_schema = ...class attributes — use generics:BaseTool[FooInput, FooOutput].- Returning a dict or primitive from
run()instead ofOutputSchema(...). - Raising on routine upstream failures — model them as typed output.
- No timeout on HTTP / DB calls.
MCPTransportType.STREAMABLE_HTTP— the correct value isHTTP_STREAM.- Implementing
async def arun(...)— the framework callsrun_async.
For deeper material — MCP interop, packaging a tool for atomic download, advanced router patterns — load ../framework/references/tools.md.
原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。