ツールを追加する
目次
ツールを書き始める前に、一度立ち止まって考えてみてください。それはスキルで足りるものではありませんか?
指示とシェルコマンドと既存のツールの組み合わせで表現できる機能なら、スキルにします(arXiv 検索、git の作業手順、Docker の管理、PDF の処理など)。
API キーを使った端から端までの連携、独自の処理ロジック、バイナリデータの取り扱い、ストリーミングが必要なら、ツールにします(ブラウザ操作、音声合成、画像の解析など)。
全体像
ツールを 1 つ追加すると、触るファイルは2 つです。
tools/your_tool.py— ハンドラー、スキーマ、利用可否のチェック関数、registry.register()の呼び出しtoolsets.py— ツール名を_HERMES_CORE_TOOLS(または個別のツールセット)に追加する
tools/*.py のうち、トップレベルで registry.register() を呼んでいるファイルは起動時に自動で見つかります。読み込むファイルの一覧を手で管理する必要はありません。
ステップ 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 が必要なハンドラー
セッションごとの状態を扱うツールには、task_id が **kwargs 経由で渡ってきます。
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_list、memory、session_search、delegate_task)は、セッションごとのエージェントの状態にアクセスする必要があります。これらはレジストリに届く前に、エージェントループ(agent/tool_executor.py。agent/conversation_loop.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"で動かして確かめた