Это руководство поможет вам создать полноценный плагин Hermes с нуля. В результате вы создали рабочий плагин с несколькими инструментами, хуками жизненного цикла, предоставляемыми файлами данных и активным навыком — все, что поддерживает плагины системы.
Не уверены, какое руководство вам нужно?
Hermes имеет несколько различных подключаемых интерфейсов — одни используют Python API register_*, другие управляются конфигурацией или каталогами. Сначала воспользуйтесь этой картой:
Если вы хотите добавить…
Читайте
Пользовательские инструменты, хуки, слеш-команды, навыки или подкоманды CLI
Полную таблицу всех изменений видимых образов, включая управляемые конфигурации (TTS, STT, MCP, хуки выполнения) и каталоги (хуки шлюза), см. в Таблица подключаемых интерфейсов.
Что вы производите
Плагин калькулятор с двумя инструментами:
- вычислить — вычисление математических выражений (2**16, sqrt(144), pi * 5**2)
- unit_convert — преобразование единиц измерения (100 F → 37,78 C, 5 км → 3,11 миль)
Плюс хук, который регистрирует каждый вызов инструмента и встроенный файл навыка.
name:calculatorversion:1.0.0description:Математический калькулятор — вычисление выражений и преобразование единицprovides_tools:-calculate-unit_convertprovides_hooks:-post_tool_call
Об этом сообщает Hermes: «Я подключил калькулятор имени, я предоставляю инструменты и хуки». Поля provides_tools и provides_hooks — это организовано тем, что регистрирует плагин.
Необязательные поля, которые можно добавить:
author:Ваше Имяrequires_env:# ограничение загрузки по переменным окружения; запрашивается при установке-SOME_API_KEY# простой формат — плагин отключен, если отсутствует-name:OTHER_KEY# расширенный формат — показывает описание/URL при установкеdescription:"КлючдлясервисаOther"url:"https://other.com/keys"secret:true
Шаг 3: Напишите схемы инструментов
Создайте schemas.py — это то, что читает LLM, чтобы решить, когда хранить ваши инструменты:
"""Схемы инструментов — то, что видит LLM."""CALCULATE={"name":"calculate","description":("Вычисляет математическое выражение и возвращает результат. ""Поддерживает арифметику (+, -, *, /, **), функции (sqrt, sin, cos, ""log, abs, round, floor, ceil) и константы (pi, e). ""Используйте для любой математики, о которой спрашивает пользователь."),"parameters":{"type":"object","properties":{"expression":{"type":"string","description":"Математическое выражение для вычисления (например, '2**10', 'sqrt(144)')",},},"required":["expression"],},}UNIT_CONVERT={"name":"unit_convert","description":("Преобразует значение между единицами измерения. Поддерживает длину (m, km, mi, ft, in), ""вес (kg, lb, oz, g), температуру (C, F, K), данные (B, KB, MB, GB, TB) ""и время (s, min, hr, day)."),"parameters":{"type":"object","properties":{"value":{"type":"number","description":"Числовое значение для преобразования",},"from_unit":{"type":"string","description":"Исходная единица (например, 'km', 'lb', 'F', 'GB')",},"to_unit":{"type":"string","description":"Целевая единица (например, 'mi', 'kg', 'C', 'MB')",},},"required":["value","from_unit","to_unit"],},}
Почему схема важна: Поле «описание» — это то, как LLM решит, когда использовать ваш инструмент. Будьте конкретны в том, что он делает и когда его используют. Параметры parameters определяют, какие аргументы передают LLM.
Шаг 4: Напишите обработчикам инструментов
Создайте tools.py — этот код действительно эффективен, когда LLM предоставит ваши инструменты:
"""Обработчики инструментов — код, выполняемый при вызове каждого инструмента LLM."""importjsonimportmath# Безопасные глобальные переменные для вычисления выражений — без доступа к файлам/сети_SAFE_MATH={"abs":abs,"round":round,"min":min,"max":max,"pow":pow,"sqrt":math.sqrt,"sin":math.sin,"cos":math.cos,"tan":math.tan,"log":math.log,"log2":math.log2,"log10":math.log10,"floor":math.floor,"ceil":math.ceil,"pi":math.pi,"e":math.e,"factorial":math.factorial,}defcalculate(args:dict,**kwargs)->str:"""Безопасно вычисляет математическое выражение. Правила для обработчиков: 1. Получают args (dict) — параметры, переданные LLM 2. Выполняют работу 3. Возвращают JSON-строку — ВСЕГДА, даже при ошибке 4. Принимают **kwargs для обратной совместимости """expression=args.get("expression","").strip()ifnotexpression:returnjson.dumps({"error":"Выражение не предоставлено"})try:result=eval(expression,{"__builtins__":{}},_SAFE_MATH)returnjson.dumps({"expression":expression,"result":result})exceptZeroDivisionError:returnjson.dumps({"expression":expression,"error":"Деление на ноль"})exceptExceptionase:returnjson.dumps({"expression":expression,"error":f"Некорректно: {e}"})# Таблицы преобразования — значения в базовых единицах_LENGTH={"m":1,"km":1000,"mi":1609.34,"ft":0.3048,"in":0.0254,"cm":0.01}_WEIGHT={"kg":1,"g":0.001,"lb":0.453592,"oz":0.0283495}_DATA={"B":1,"KB":1024,"MB":1024**2,"GB":1024**3,"TB":1024**4}_TIME={"s":1,"ms":0.001,"min":60,"hr":3600,"day":86400}def_convert_temp(value,from_u,to_u):# Нормализация к Цельсиюc={"F":(value-32)*5/9,"K":value-273.15}.get(from_u,value)# Преобразование к целевойreturn{"F":c*9/5+32,"K":c+273.15}.get(to_u,c)defunit_convert(args:dict,**kwargs)->str:"""Преобразует между единицами измерения."""value=args.get("value")from_unit=args.get("from_unit","").strip()to_unit=args.get("to_unit","").strip()ifvalueisNoneornotfrom_unitornotto_unit:returnjson.dumps({"error":"Необходимы value, from_unit и to_unit"})try:# Температураiffrom_unit.upper()in{"C","F","K"}andto_unit.upper()in{"C","F","K"}:result=_convert_temp(float(value),from_unit.upper(),to_unit.upper())returnjson.dumps({"input":f"{value}{from_unit}","result":round(result,4),"output":f"{round(result,4)}{to_unit}"})# Преобразования на основе коэффициентовfortablein(_LENGTH,_WEIGHT,_DATA,_TIME):lc={k.lower():vfork,vintable.items()}iffrom_unit.lower()inlcandto_unit.lower()inlc:result=float(value)*lc[from_unit.lower()]/lc[to_unit.lower()]returnjson.dumps({"input":f"{value}{from_unit}","result":round(result,6),"output":f"{round(result,6)}{to_unit}"})returnjson.dumps({"error":f"Невозможно преобразовать {from_unit} → {to_unit}"})exceptExceptionase:returnjson.dumps({"error":f"Ошибка преобразования: {e}"})
Ключевые правила для обработчиков:
1. Сигнатура:def my_handler(args: dict, **kwargs) -> str
2. Возврат: Всегда JSON-строка. Как для успеха, так и для ошибок.
3. Никогда не записывайте исключения: Перехватите все исключения, вместо этого возвращайте JSON с ошибкой.
4. Принимать **kwargs: В будущем Hermes может передать дополнительный контекст.
Шаг 5: Напишите регистрацию
Создайте __init__.py — это связывает схемы с обработчиками:
"""Плагин калькулятора — регистрация."""importloggingfrom.importschemas,toolslogger=logging.getLogger(__name__)# Отслеживание использования инструментов через хуки_call_log=[]def_on_post_tool_call(tool_name,args,result,task_id,**kwargs):"""Хук: выполняется после каждого вызова инструмента (не только нашего)."""_call_log.append({"tool":tool_name,"session":task_id})iflen(_call_log)>100:_call_log.pop(0)logger.debug("Инструмент вызван: %s (сессия %s)",tool_name,task_id)defregister(ctx):"""Связывает схемы с обработчиками и регистрирует хуки."""ctx.register_tool(name="calculate",toolset="calculator",schema=schemas.CALCULATE,handler=tools.calculate)ctx.register_tool(name="unit_convert",toolset="calculator",schema=schemas.UNIT_CONVERT,handler=tools.unit_convert)# Этот хук срабатывает для ВСЕХ вызовов инструментов, не только нашихctx.register_hook("post_tool_call",_on_post_tool_call)
Что делает register():
- Вызывается ровно один раз при запуске.
- ctx.register_tool() помещает ваш инструмент в реестр — модель видит его немедленно
- ctx.register_hook() при записи событий жизненного цикла.
- ctx.register_cli_command() регистрирует подкоманду CLI (например, hermes my-plugin <subcommand>)
- ctx.register_command() регистрирует слеш-команду внутри сессии (например, /myplugin <args> в CLI/чате шлюза) — см. Регистрация слеш-команд ниже
- ctx.dispatch_tool(name, аргументы) — возникновение любого другого инструмента (встроенного или из другого плагина) с автоматически подключенным контекстом родительского агента (одобрения, учетные данные, Task_id). Полезно из обработчиков слэш-команды, которым нужно вызвать терминал, read_file или любой другой инструмент так, как если бы модель вызывала его напрямую.
- Если эта функция аварийно завершится, вилка отключится, но Hermes продолжит работу.
Пример dispatch_tool — слеш-команда, запускающая инструмент:
defhandle_scan(ctx,argstr):"""Реализует /scan путем вызова инструмента terminal через реестр."""result=ctx.dispatch_tool("terminal",{"command":f"find. -name '{argstr}'"})returnresult# возвращается в UI чата вызывающегоdefregister(ctx):ctx.register_command("scan",handle_scan,help="Поиск файлов по маске")
Вызванный инструмент проходит через обычные конвейеры одобрения, редактирования и бюджета — это инструмент реального вызова, а не обходной путь.
Шаг 6: Протестируйте
Запустите Hermes:
hermes
Вы должны увидеть «калькулятор: покрытие, unit_convert» в списке инструментов баннера.
выполните следующие запросы:
Сколько будет 2 в степени 16?
Преобразуй 100 градусов Фаренгейта в Цельсий
Чему равен квадратный корень из 2, умноженный на пи?
Сколько гигабайт в 1.5 терабайтах?
Если ваш плагин не отображается — или отображается, но не загружается — установите HERMES_PLUGINS_DEBUG=1, чтобы получить последовательный поиск журналов в stderr:
HERMES_PLUGINS_DEBUG=1hermespluginslist
Вы укажете для каждого источника плагинов (встроенные, пользовательские, проектные, точки входа):
какие каталоги были просканированы и сколько манифестов каждый дал
для каждого манифеста: разрешенный ключ, имя, вид, источник, путь на диске.
причина исключения: отключено через конфиг, не включено в конфиге, эксклюзивный плагин, нет плагина.yaml, достигнута максимальная глубина
в наличии: импортируемый плагин, а также однострочное резюме того, что зарегистрировал register(ctx) (инструменты, хуки, слэш-команды, команды CLI)
при Нужны разбора: полная трассировка исключений (ошибки сканера YAML и т.д.)
при необходимости register(): полная трассировка, указывающая на символ в вашем __init__.py, что вызвало ошибку
Те же логи всегда приводят к результату в ~/.hermes/logs/agent.log на уровне WARNING (только ошибки) и DEBUG (все), когда установлена переменная окружность. Если вы не можете управлять переменным окружением (например, из шлюза), вместо этого просматривайте файл лога:
hermeslogs--levelWARNING|grep-iplugin
Распространенные причины, по которым вилка не появляется:
Не включено в конфиге — плагины подключаются по желанию. Выполните hermes Plugins Enable <name> (имя берется из вывода plugins list, который может быть <category>/<plugin> для вложенных структур).
Неправильная структура каталогов — должна быть ~/.hermes/plugins/<имя-плагина>/plugin.yaml (ская) или ~/.hermes/plugins/<категория>/<имя-плагина>/plugin.yaml (один уровень вложенности категории, максимум). Всё, что происходит, зависит.
Отсутствует __init__.py — плагин каталога должен сохраняться как plugin.yaml, так и __init__.py с пониженным register(ctx).
Неправильный kind — адаптеры шлюза должны иметь kind: Platform в своем манифесте. Провайдеры памяти автоматически обновляются как «вид: эксклюзив» и маршрутизируются через конфигурацию «memory.provider», а не «plugins.enabled».
Финальная структура вашего плагина
~/.hermes/plugins/calculator/├──plugin.yaml# "Я калькулятор, я предоставляю инструменты и хуки"├──__init__.py# Связывание: схемы → обработчики, регистрация хуков├──schemas.py# Что читает LLM (описания + спецификации параметров)└──tools.py# Что выполняется (функции calculate, unit_convert)
Четыре файла, четкое разделение:
- Манифест объявляет, что такое плагин
- Схемы инструментов для LLM
- Обработчики реализуют фактическую логику
- Регистрация связывает всё вместе
Что еще можно сделать плагины?
Поставка файлов данных
Разместите любые файлы в плагине каталога и прочитайте их во время импорта:
# В tools.py или __init__.pyfrompathlibimportPath_PLUGIN_DIR=Path(__file__).parent_DATA_FILE=_PLUGIN_DIR/"data"/"languages.yaml"withopen(_DATA_FILE)asf:_DATA=yaml.safe_load(f)
Встраивание навыков
Плагины могут предоставлять файлы, которые агент загружает через skill_view("plugin:skill"). Зарегистрируйте их в своем __init__.py:
Теперь агент может загрузить ваши навыки по своим именам в поле имен:
skill_view("my-plugin:my-workflow")# → версия плагинаskill_view("my-workflow")# → встроенная версия (без изменений)
Ключевые свойства:
- Навыки плагина только для чтения — они не изучаются в ~/.hermes/skills/ и не могут быть отредактированы через skill_manage.
- Навыки плагина не перечисляются в индексе <available_skills> системного запроса — они загружаются открыто с помощью утилиты.
- Простые имена навыков не затрагиваются — пространство имен вызывает конфликты с ограниченными навыками.
- Когда агент загружает плагины, перед ним добавляется пакет контекста баннера, перечисляющий местные навыки из того же плагина.
💡 Tip
Устаревший шаблон
Старый шаблон shutil.copy2 (копирование навыка в ~/.hermes/skills/) все еще работает, но вызывает риск конфликта имен с включенными навыками. Для новых плагинов предпочтительнее ctx.register_skill().
Ограничение по переменному окружению
Если вашему плагину нужен API-ключ:
# plugin.yaml — простой формат (обратно совместимый)requires_env:-WEATHER_API_KEY
Если WEATHER_API_KEY не установлен, плагин отключается с понятным сообщением. Никаких сбоев, никаких ошибок в агенте — просто «Плагин погоды отключен (отсутствует: WEATHER_API_KEY)».
Когда пользователи запускают установку плагинов Hermes, они интерактивно запрашивают все возможные переменные requires_env. Значения автоматически отображаются в .env.
Для лучшего опыта установки используйте расширенный формат с описаниями и URL для регистрации:
Каждый хук полностью документирован в Справочнике событий хуков — сигнатуры обратных вызовов, таблицы параметров, точное время разработки и примеры. Вот сводка:
Большинство хуков — это наблюдатели типа «запустили и забыли» — их возвратные известные значения принимаются. Исключение — pre_llm_call, который может включать контекст в разговор.
Все обратные вызовы должны принимать **kwargs для обратной совместимости. Если обратный вызов хука аварийно завершается, он регистрируется и отключается. Другие хуки и агенты продолжают работать нормально.
Внедрение контекста pre_llm_call
Это огромное значение, которое имеет значение. Когда обратный вызов pre_llm_call получает словарь с ключом "context" (или простой код), Hermes внедряет этот текст в сообщение текущего хода. Это механизм для плагинов памяти, интеграции RAG, ограничителей и любых плагинов, к которым нужно подключать модели дополнительного контекста.
Возврат формы
# Словарь с ключом contextreturn{"context":"Воспоминания:\n- Пользователь предпочитает темную тему\n- Последний проект: hermes-agent"}# Простая строка (эквивалентна форме словаря выше)return"Воспоминания:\n- Пользователь предпочитает темную тему"# Возврат None или отсутствие возврата → без внедрения (только наблюдение)returnNone
Любой не-None, непустой возврат с ключом "context" (или простая непустая строка) собирается и добавляется к сообщению пользователя для текущего хода.
Как работает внедрение
Внешний контекст добавляется к сообщению пользователя, а не к системному приглашению. Это осознанный выбор дизайна:
Сохранение кэша промптов — системный запрос остается ответственным между ходами. Anthropic и OpenRouter кэшируют префикс системного промпта, поэтому его стабильность экономит 75%+ входных токенов в многоходовых разговорах. Если бы вы подключили отображаемую системную подсказку, каждый ход приводил бы к промаху кэша.
Эфемерность — внедрение происходит только во время вызова API. Исходное сообщение пользователя в истории разговора никогда не меняется, и ничего не сохраняется в базе данных сессии.
Системный запрос — территория Hermesа — содержит характеристики, специфичные для моделей, правила использования инструментов, инструкции по индивидуальному подходу и кэшированные навыки. Плагины носят контекст вместе с вводом пользователя, не изменяя основную операцию агента.
Пример: Плагин памяти
"""Плагин памяти — извлекает релевантный контекст из векторного хранилища."""importhttpxMEMORY_API="https://your-memory-api.example.com"defrecall_context(session_id,user_message,is_first_turn,**kwargs):"""Вызывается перед каждым ходом LLM. Возвращает извлеченные воспоминания."""try:resp=httpx.post(f"{MEMORY_API}/recall",json={"session_id":session_id,"query":user_message,},timeout=3)memories=resp.json().get("results",[])ifnotmemories:returnNone# нечего внедрятьtext="Извлеченный контекст из предыдущих сессий:\n"text+="\n".join(f"- {m['text']}"forminmemories)return{"context":text}exceptException:returnNone# молча завершаемся, не ломаем агентаdefregister(ctx):ctx.register_hook("pre_llm_call",recall_context)
Пример: Плагин ограничителей
"""Плагин ограничителей — обеспечивает соблюдение политик контента."""POLICY="""Вы ДОЛЖНЫ следовать следующим политикам контента для этой сессии:- Никогда не генерируйте код, который обращается к файловой системе вне рабочего каталога- Всегда предупреждайте перед выполнением деструктивных операций- Отказывайте в запросах, связанных с извлечением личных данных"""definject_guardrails(**kwargs):"""Внедряет текст политики в каждый ход."""return{"context":POLICY}defregister(ctx):ctx.register_hook("pre_llm_call",inject_guardrails)
Пример: Хук только для наблюдения (без ремонта)
"""Плагин аналитики — отслеживает метаданные хода без внедрения контекста."""importlogginglogger=logging.getLogger(__name__)deflog_turn(session_id,user_message,model,is_first_turn,**kwargs):"""Срабатывает перед каждым вызовом LLM. Возвращает None — контекст не внедряется."""logger.info("Ход: сессия=%s модель=%s первый=%s длина_сообщения=%d",session_id,model,is_first_turn,len(user_messageor""))# Нет возврата → без внедренияdefregister(ctx):ctx.register_hook("pre_llm_call",log_turn)
Несколько плагинов, возвращающий контекст
Когда несколько плагинов возвращают контекст из pre_llm_call, их выходные данные объединяются с трансляцией строк и включаются в сообщение пользователя вместе. Соблюдается порядок выбора плагинов (порядок алфавита имени плагина каталога).
Регистрация команды CLI
Плагины могут добавлять собственное дерево подкомандой hermes <plugin>:
def_my_command(args):"""Обработчик для hermes my-plugin <subcommand>."""sub=getattr(args,"my_command",None)ifsub=="status":print("Всё хорошо!")elifsub=="config":print("Текущий конфиг:...")else:print("Использование: hermes my-plugin <status|config>")def_setup_argparse(subparser):"""Строит дерево argparse для hermes my-plugin."""subs=subparser.add_subparsers(dest="my_command")subs.add_parser("status",help="Показать статус плагина")subs.add_parser("config",help="Показать конфиг плагина")subparser.set_defaults(func=_my_command)defregister(ctx):ctx.register_tool(...)ctx.register_cli_command(name="my-plugin",help="Управление моим плагином",setup_fn=_setup_argparse,handler_fn=_my_command,)
После регистрации пользователи могут выбрать hermes my-plugin status, hermes my-plugin config и т.д.
Плагины провайдеров памяти используют подход на основе соглашений: функцию register_cli(subparser) в файле cli.py вашего плагина. Система поиска плагинов памяти находит ее автоматически — вызов ctx.register_cli_command() не требуется. Подробности см. в Руководстве по плагинам провайдеров памяти.
Ограничение активным провайдером: Команды CLI затем подключают память только в том случае, если их провайдер является активным memory.provider в конфигурации. Если пользователь не настроил вашего провайдера, ваш CLI не будет засорять вывод информации.
Регистрация слеш-команд
Плагины могут регистрировать слеш-команды внутри сессии — команды, которые пользователи ведут во время разговора (например, /lcm status или /ping). Они работают как в CLI, так и в шлюзе (Telegram, Discord и т.д.).
def_handle_status(raw_args:str)->str:"""Обработчик для /mystatus — вызывается со всем, что после имени команды."""ifraw_args.strip()=="help":return"Использование: /mystatus [help|check]"return"Статус плагина: все системы в норме"defregister(ctx):ctx.register_command("mystatus",handler=_handle_status,description="Показать статус плагина",)
После регистрации пользователи могут войти в /mystatus в любой сессии. Команда появляется в автодополнении, вызывает /help и меню бота Telegram.
Имя команды без ведущего слеша (например, "lcm", "mystatus")
обработчик
Callable[[str], str \| Нет]
Вызывается с сырой строкой аргументов. Может быть асинхронным.
описание
ул
Показывается в /help, автодополнении и меню бота Telegram
Ключевые отличия от register_cli_command():
register_command()
register_cli_command()
Вызывается как
/name в сессии
имя Hermesа в терминале
Где работает
Сессии CLI, Telegram, Discord и т.д.
Только терминал
Обработчик получает
Сырой аргумент аргументов
argparse Пространство имен
Сценарий использования
Диагностика, статус, быстрые действия
Сложные деревья подкоманд, мастер настройки
Защита от упоминаний: Если в плагине появляется имя, которое конфликтует с внутренней командой («помощь», «модель», «новая» и т.д.), регистрация молча отклоняется с предупреждением в журнале. Встроенные команды всегда имеют приоритет.
Асинхронные обработчики: Диспетчер шлюза автоматически обнаруживает и ожидает асинхронных обработчиков, поэтому вы можете использовать как синхронные, так и асинхронные функции:
Обработчики слэш-команды, которым нужно оркестровать инструменты (породить под-агента через delegate_task, вызвать file_edit и т.д.), должны использовать ctx.dispatch_tool() вместо обращения к внутренностям каркаса. Контекст родительского агента (подсказки рабочей области, спиннер, исследование моделей) сохраняется автоматически.
defregister(ctx):def_handle_deliver(raw_args:str):result=ctx.dispatch_tool("delegate_task",{"goal":raw_args,"toolsets":["terminal","file","web"],},)returnresultctx.register_command("deliver",handler=_handle_deliver,description="Делегировать цель под-агенту",)
Имя инструмента, зарегистрированное в реестре инструментов (например, "delegate_task","file_edit"`)
аргументы
дикт
Аргументы инструмента той же формы, что была отправлена модель
родительский_агент
Агент \| Нет
Необязательное переопределение. Если опущено, разрешается из текущего агента CLI (или корректно деградирует в режиме шлюза)
Поведение во время выполнения:
Режим CLI:parent_agent разрешается из активного агента CLI, поэтому подсказки рабочей области, области спиннера и выбор моделей наследуются как эксперт.
Режим шлюза: Агента CLI нет, поэтому инструменты корректно деградируют — рабочая область читается из TERMINAL_CWD, спиннер не показывается.
Явное переопределение: Если вызывается передача parent_agent= явно, она уважается и не перезаписывается.
Это общедоступный, стабильный интерфейс для инструментов управления с помощью командных плагинов. Плагины не должны обращаться к ctx._cli_ref.agent или обычному приватному состоянию.
💡 Tip
Это руководство противоречит общие плагины (инструменты, хуки, слеш-команды, команды CLI). Ниже приведены разделы, посвященные шаблонам создания для каждого специализированного типа вилки; Каждое из них содержит полное руководство по полям и примерам.
Специализированные виды вилок
Hermes имеет пять специализированных типов вилок для защиты поверхности поверхности. Каждый из них соответствует каталогу plugins/<category>/<name>/ (встроенный) или ~/.hermes/plugins/<category>/<name>/ (пользовательский). Контракт различается в зависимости от категории — выберите предпочтительный вариант, а затем прочитайте полное руководство.
Плагины моделей провайдеров — добавление бэкенда LLM
Поместите профиль в plugins/model-providers/<name>/:
# plugins/model-providers/acme/plugin.yamlname:acme-providerkind:model-providerversion:1.0.0description:Acme Inference — прямой API, совместимый с OpenAI
Лениво обнаруживается при первом вызове get_provider_profile() или list_providers() — auth.py, config.py, doctor.py, models.py, runtime_provider.py и транспорт Chat_Completions автоматически подключаются к нему. Пользовательские плагины переопределяют встроенные по имени.
Полное руководство:Плагины провайдеров моделей — справочник полей, переопределяемые хуки (prepare_messages, build_extra_body, build_api_kwargs_extras, fetch_models), выбор api_mode, типы аутентификации, тестирование.
Платформенные плагины — добавление канала шлюза
Поместите адаптер в plugins/platforms/<name>/:
# plugins/platforms/myplatform/adapter.pyfromgateway.platforms.baseimportBasePlatformAdapterclassMyPlatformAdapter(BasePlatformAdapter):asyncdefconnect(self):...asyncdefsend(self,chat_id,text):...asyncdefdisconnect(self):...defcheck_requirements():importosreturnbool(os.environ.get("MYPLATFORM_TOKEN"))def_env_enablement():importostok=os.getenv("MYPLATFORM_TOKEN","").strip()ifnottok:returnNonereturn{"token":tok}defregister(ctx):ctx.register_platform(name="myplatform",label="MyPlatform",adapter_factory=lambdacfg:MyPlatformAdapter(cfg),check_fn=check_requirements,required_env=["MYPLATFORM_TOKEN"],# Автозаполнение PlatformConfig.extra из окружения, чтобы конфигурации# только с env отображались в `hermes gateway status` без создания SDK.env_enablement_fn=_env_enablement,# Подписка на доставку cron: `deliver=myplatform` направляет в эту переменную.cron_deliver_env_var="MYPLATFORM_HOME_CHANNEL",emoji="💬",platform_hint="Вы общаетесь через MyPlatform. Старайтесь отвечать кратко.",)
Полное руководство:Добавление платформенных адаптеров — полный контракт BasePlatformAdapter, маршрутизация сообщений, ограничение по аутентификации, интеграция настроек мастера. Посмотрите plugins/platforms/irc/ для рабочего примера только на stdlib.
Плагины провайдеров памяти — добавление знаний бэкенда между сессиями
Поместите реализацию MemoryProvider в plugins/memory/<name>/:
# plugins/image_gen/my-imggen/__init__.pyfromagent.image_gen_providerimportImageGenProviderclassMyImageGenProvider(ImageGenProvider):@propertydefname(self)->str:return"my-imggen"defis_available(self)->bool:...defgenerate(self,prompt:str,**kwargs)->str:...# возвращает путь к изображениюdefregister(ctx):ctx.register_image_gen_provider(MyImageGenProvider())
Полное руководство:Плагины генерации изображений — полный ABC ImageGenProvider, метаданные list_models() / get_setup_schema(), хелперы success_response()/error_response(), вывод base64 vs URL, пользовательские переопределения, передача через pip.
Примеры для справки:plugins/image_gen/openai/ (DALL-E / GPT-Image через OpenAI SDK), plugins/image_gen/openai-codex/, plugins/image_gen/xai/ (генерация изображений Grok).
Не-Python расширяемые поверхности
Hermes также принимает расширения, которые вообще не являются Python-плагинами. Они показаны в Таблице подключаемых интерфейсов; разделы ниже кратко касаются создания каждого стиля.
MCP-серверы — регистрация внешних инструментов
Серверы Model Context Protocol (MCP) регистрируют свои собственные инструменты в Hermes без какого-либо Python-плагина. Объявите их в ~/.hermes/config.yaml:
При запуске Hermes подключается к каждому серверу, пересчитывает его инструменты и регистрирует их вместе с компонентами. LLM видит их точно так же, как и любой другой инструмент. Полное руководство:MCP.
Хуки событий шлюза — организация событий жизненного цикла
Поместите манифест + обработчик в ~/.hermes/hooks/<name>/:
# ~/.hermes/hooks/long-task-alert/HOOK.yamlname:long-task-alertdescription:Отправить push-уведомление, когда долгая задача завершитсяevents:-agent:end
События включают в себя шлюз: запуск, сессия: начало, сессия: конец, сессия: сброс, агент: старт, агент: шаг, агент: конец и подстановочный знак команда:*. Ошибки в хуках перехватываются и регистрируются — они никогда не блокируют основной конвейер.
Хуки обработки — Выполнение работ по вызову инструментов
Если вы хотите просто запустить скрипт при работе с инструментом (уведомления, журналы аудита, уведомления на рабочем столе, автоформатировщики), используйте хуки в config.yaml — Python не требуется:
Поддерживает все те же события, что и хуки Python-плагинов (pre_tool_call, post_tool_call, pre_llm_call, post_llm_call, on_session_start, on_session_end, pre_gateway_dispatch), а также структурированный вывод JSON для решений блокировки pre_tool_call.
Публикация собственного крана — это просто репозиторий GitHub с каталогами skills/<skill-name>/SKILL.md — никакого сервера или регистрации не требуется.
Для STT укажите HERMES_LOCAL_STT_COMMAND в шаблоне обработки. Поддерживаемые плейсхолдеры: {input_path}, {output_path}, {format}, {voice}, {model}, {speed} (TTS); {input_path}, {output_dir}, {language}, {model} (STT). Любой CLI, взаимодействующий с путями, автоматически становится плагином.
# Неправильно — сломается, если Hermes передаст дополнительный контекстdefhandler(args):...# Правильноdefhandler(args,**kwargs):...
Обработчик приводит выводы:
# Неправильно — исключение распространяется, вызов инструмента завершается ошибкойdefhandler(args,**kwargs):result=1/int(args["value"])# ZeroDivisionError!returnjson.dumps({"result":result})# Правильно — перехватываем и возвращаем JSON с ошибкойdefhandler(args,**kwargs):try:result=1/int(args.get("value",0))returnjson.dumps({"result":result})exceptExceptionase:returnjson.dumps({"error":str(e)})
Слишком расплывчатое описание схемы:
# Плохо — модель не знает, когда использовать"description":"Делает что-то"# Хорошо — модель знает точно когда и как"description":"Вычисляет математическое выражение. Используйте для арифметики, тригонометрии, логарифмов. Поддерживает: +, -, *, /, **, sqrt, sin, cos, log, pi, e."