ツールの追加
目次
ツールを書き始める前に、いちど考えてみてください。それは スキル で足りませんか?
指示とシェルコマンドと既存のツールの組み合わせで表せるなら、スキル にしてください(arXiv の検索、git の作業手順、Docker の管理、PDF の処理など)。
API キーを使った一連の連携、独自の処理、バイナリデータの扱い、逐次配信が必要なら、ツール にしてください(ブラウザの自動操作、音声合成、画像の解析など)。
全体像
ツールの追加で触るのは 2 つのファイル です。
tools/your_tool.py— ハンドラ、スキーマ、利用可否の判定関数、registry.register()の呼び出しtoolsets.py— ツール名を_HERMES_CORE_TOOLS(または特定のツールセット)に追加
tools/*.py のうち、トップレベルで registry.register() を呼んでいるファイルは起動時に自動で見つかります。import の一覧を手で管理する必要はありません。
手順 1: 組み込みツールのファイルを作る
ツールのファイルは、どれも同じ構成です。
# tools/weather_tool.py
"""Weather Tool -- look up current weather for a location."""
logger = logging.getLogger(__name__)
# --- Availability check ---
def check_weather_requirements() -> bool:
"""Return True if the tool's dependencies are available."""
return bool(os.getenv("WEATHER_API_KEY"))
# --- Handler ---
def weather_tool(location: str, units: str = "metric") -> str:
"""Fetch weather for a location. Returns JSON string."""
api_key = os.getenv("WEATHER_API_KEY")
if not api_key:
return json.dumps({"error": "WEATHER_API_KEY not configured"})
try:
# ... call weather API ...
return json.dumps({"location": location, "temp": 22, "units": units})
except Exception as e:
return json.dumps({"error": str(e)})
# --- Schema ---
WEATHER_SCHEMA = {
"name": "weather",
"description": "Get current weather for a location.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name or coordinates (e.g. 'London' or '51.5,-0.1')"
},
"units": {
"type": "string",
"enum": ["metric", "imperial"],
"description": "Temperature units (default: metric)",
"default": "metric"
}
},
"required": ["location"]
}
}
# --- Registration ---
from tools.registry import registry
registry.register(
name="weather",
toolset="weather",
schema=WEATHER_SCHEMA,
handler=lambda args, **kw: weather_tool(
location=args.get("location", ""),
units=args.get("units", "metric")),
check_fn=check_weather_requirements,
requires_env=["WEATHER_API_KEY"],
)守るべき決まり
手順 2: 組み込みツールをツールセットに追加する
toolsets.py にツール名を追加します。
# If it should be available on all platforms (CLI + messaging):
_HERMES_CORE_TOOLS = [
...
"weather", # <-- add here
]
# Or create a new standalone toolset:
"weather": {
"description": "Weather lookup tools",
"tools": ["weather"],
"includes": []
},~~手順 3: 検出用の import を追加する~~(不要になりました)
トップレベルで registry.register() を呼んでいるツールのモジュールは、tools/registry.py の discover_builtin_tools() が自動で見つけます。import の一覧を保守する必要はありません。tools/ にファイルを作れば、起動時に拾われます。
非同期のハンドラ
ハンドラで非同期の処理が必要なら、is_async=True を指定します。
async def weather_tool_async(location: str) -> str:
async with aiohttp.ClientSession() as session:
...
return json.dumps(result)
registry.register(
name="weather",
toolset="weather",
schema=WEATHER_SCHEMA,
handler=lambda args, **kw: weather_tool_async(args.get("location", "")),
check_fn=check_weather_requirements,
is_async=True, # registry calls _run_async() automatically
)非同期の橋渡しはレジストリが引き受けます。自分で asyncio.run() を呼ぶことはありません。
task_id が必要なハンドラ
セッションごとの状態を扱うツールは、**kwargs 経由で task_id を受け取ります。
def _handle_weather(args, **kw):
task_id = kw.get("task_id")
return weather_tool(args.get("location", ""), task_id=task_id)
registry.register(
name="weather",
...
handler=_handle_weather,
)エージェントループが横取りするツール
一部のツール(todo、memory、session_search、delegate_task)は、セッションごとのエージェントの状態にアクセスする必要があります。これらはレジストリに届く前に run_agent.py が横取りします。スキーマ自体はレジストリが持っていますが、横取りを通らなかった場合は dispatch() が予備のエラーを返します。
任意: セットアップウィザードへの組み込み
ツールが API キーを必要とするなら、hermes_cli/config.py に追加します。
OPTIONAL_ENV_VARS = {
...
"WEATHER_API_KEY": {
"description": "Weather API key for weather lookup",
"prompt": "Weather API key",
"url": "https://weatherapi.com/",
"tools": ["weather"],
"password": True,
},
}チェックリスト
- [ ] ハンドラ、スキーマ、利用可否の判定関数、登録を含むツールのファイルを作った
- [ ]
toolsets.pyの適切なツールセットに追加した - [ ] プラグインではなく、本当に組み込みのコアツールにすべきか確認した
- [ ] ハンドラが JSON 文字列を返し、エラーは
{"error": "..."}で返している - [ ] 任意:
hermes_cli/config.pyのOPTIONAL_ENV_VARSに API キーを追加した - [ ] 任意: バッチ処理のために
toolset_distributions.pyに追加した - [ ]
hermes chat -q "Use the weather tool for London"で動作を確認した