Формат траектории
Hermes Agent сохраняет траектории диалогов в JSONL-формате, совместимом с ShareGPT, — их используют как обучающие датасеты, отладочные артефакты и наборы данных для обучения с подкреплением.
Исходные файлы: agent/trajectory.py, agent/session_persistence.py (ищи _save_trajectory), batch_runner.py
Соглашение об именовании файлов
Траектории записываются в файлы в текущем рабочем каталоге:
| Файл | Когда |
|---|---|
trajectory_samples.jsonl | Диалоги, завершившиеся успешно (completed=True) |
failed_trajectories.jsonl | Диалоги, которые завершились с ошибкой или были прерваны (completed=False) |
Пакетный раннер (batch_runner.py) пишет в отдельный выходной файл для каждого батча (например, batch_001_output.jsonl) с дополнительными полями метаданных.
Имя файла можно переопределить через параметр filename в save_trajectory().
Формат записи JSONL
Каждая строка файла — самостоятельный JSON-объект. Вариантов два:
CLI / интерактивный формат (из _save_trajectory)
{
"conversations": [ ... ],
"timestamp": "2026-03-30T14:22:31.456789",
"model": "anthropic/claude-sonnet-4.6",
"completed": true
}
Формат пакетного раннера (из batch_runner.py)
{
"prompt_index": 42,
"conversations": [ ... ],
"metadata": { "prompt_source": "gsm8k", "difficulty": "hard" },
"completed": true,
"partial": false,
"api_calls": 7,
"toolsets_used": ["code_tools", "file_tools"],
"tool_stats": {
"terminal": {"count": 3, "success": 3, "failure": 0},
"read_file": {"count": 2, "success": 2, "failure": 0},
"write_file": {"count": 0, "success": 0, "failure": 0}
},
"tool_error_counts": {
"terminal": 0,
"read_file": 0,
"write_file": 0
}
}
Словари tool_stats и tool_error_counts нормализуются так, чтобы в них попали ВСЕ возможные инструменты (из model_tools.TOOL_TO_TOOLSET_MAP) с нулями по умолчанию. Так схема остаётся одинаковой во всех записях, и датасеты грузятся через HuggingFace без сюрпризов.
Массив conversations (формат ShareGPT)
Массив conversations использует ролевые соглашения ShareGPT:
| Роль API | ShareGPT from |
|---|---|
| system | "system" |
| user | "human" |
| assistant | "gpt" |
| tool | "tool" |
Полный пример
{
"conversations": [
{
"from": "system",
"value": "You are a function calling AI model. You are provided with function signatures within <tools> </tools> XML tags. You may call one or more functions to assist with the user query. If available tools are not relevant in assisting with user query, just respond in natural conversational language. Don't make assumptions about what values to plug into functions. After calling & executing the functions, you will be provided with function results within <tool_response> </tool_response> XML tags. Here are the available tools:\n<tools>\n[{\"name\": \"terminal\", \"description\": \"Execute shell commands\", \"parameters\": {\"type\": \"object\", \"properties\": {\"command\": {\"type\": \"string\"}}}, \"required\": null}]\n</tools>\nFor each function call return a JSON object, with the following pydantic model json schema for each:\n{'title': 'FunctionCall', 'type': 'object', 'properties': {'name': {'title': 'Name', 'type': 'string'}, 'arguments': {'title': 'Arguments', 'type': 'object'}}, 'required': ['name', 'arguments']}\nEach function call should be enclosed within <tool_call> </tool_call> XML tags.\nExample:\n<tool_call>\n{'name': <function-name>,'arguments': <args-dict>}\n</tool_call>"
},
{
"from": "human",
"value": "What Python version is installed?"
},
{
"from": "gpt",
"value": "<think>\nThe user wants to know the Python version. I should run python3 --version.\n</think>\n<tool_call>\n{\"name\": \"terminal\", \"arguments\": {\"command\": \"python3 --version\"}}\n</tool_call>"
},
{
"from": "tool",
"value": "<tool_response>\n{\"tool_call_id\": \"call_abc123\", \"name\": \"terminal\", \"content\": \"Python 3.11.6\"}\n</tool_response>"
},
{
"from": "gpt",
"value": "<think>\nGot the version. I can now answer the user.\n</think>\nPython 3.11.6 is installed on this system."
}
],
"timestamp": "2026-03-30T14:22:31.456789",
"model": "anthropic/claude-sonnet-4.6",
"completed": true
}
Правила нормализации
Разметка рассуждений
Конвертер траекторий сводит ВСЕ рассуждения к тегам <think> — неважно, в каком виде модель выдала их изначально:
-
Нативные токены размышлений (поле
msg["reasoning"]от провайдеров вроде Anthropic, серия OpenAI o): оборачиваются в<think>\n{reasoning}\n</think>\nи вставляются перед содержимым. -
XML-тег REASONING_SCRATCHPAD (когда нативное мышление отключено и модель рассуждает через XML, заданный системным промптом): теги
<REASONING_SCRATCHPAD>преобразуются в<think>черезconvert_scratchpad_to_think(). -
Пустые блоки
<think>: у каждого ходаgptблок<think>есть гарантированно. Когда рассуждений не было, подставляется пустой блок:<think>\n</think>\n— формат обучающих данных от этого не плывёт.
Нормализация вызовов инструментов
Вызовы инструментов из API-формата (с tool_call_id, именем функции и аргументами в виде JSON-строки) превращаются в XML-обёрнутый JSON:
<tool_call>
{"name": "terminal", "arguments": {"command": "ls -la"}}
</tool_call>
- Аргументы парсятся из JSON-строк обратно в объекты (без двойного кодирования)
- Если JSON-парсинг сорвался (чего быть не должно — диалог проходит валидацию), подставляется пустой
{}, а в лог пишется предупреждение - Несколько вызовов инструментов в одном ходе ассистента дают несколько блоков
<tool_call>внутри одного сообщенияgpt
Нормализация ответов инструментов
Все результаты инструментов после сообщения ассистента собираются в один ход tool с XML-обёрнутыми JSON-ответами:
<tool_response>
{"tool_call_id": "call_abc123", "name": "terminal", "content": "output here"}
</tool_response>
- Если содержимое ответа инструмента похоже на JSON (начинается с
{или[), оно парсится, и полеcontentхранит объект или массив, а не строку - Несколько результатов объединяются через символ новой строки в одном сообщении
- Имя инструмента сопоставляется по позиции с массивом
tool_callsродительского сообщения ассистента
Системное сообщение
Системное сообщение генерируется в момент сохранения, а не берётся из диалога. Оно построено по шаблону Hermes для function-calling и включает:
- Вводную часть с описанием протокола вызова функций
- XML-блок
<tools>с JSON-определениями инструментов - Ссылку на схему объектов
FunctionCall - Пример
<tool_call>
В определениях инструментов есть поля name, description, parameters и required (значение null соответствует каноническому формату).
Загрузка траекторий
Траектории хранятся в стандартном JSONL — читай их любым парсером JSON-lines:
import json
def load_trajectories(path: str):
"""Загружает записи траекторий из JSONL-файла."""
entries = []
with open(path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line:
entries.append(json.loads(line))
return entries
# Только успешно завершённые диалоги
successful = [e for e in load_trajectories("trajectory_samples.jsonl")
if e.get("completed")]
# Извлекаем диалоги для обучения
training_data = [e["conversations"] for e in successful]
Загрузка в HuggingFace Datasets
from datasets import load_dataset
ds = load_dataset("json", data_files="trajectory_samples.jsonl")
Нормализованная схема tool_stats держит набор колонок одинаковым во всех записях, так что ошибок несоответствия Arrow-схемы при загрузке датасета не возникает.
Управление сохранением траекторий
Сохранение траекторий — переключатель уровня run_agent.py / библиотеки: CLI hermes не предоставляет для него ни ключа конфига, ни флага:
python run_agent.py --save_trajectories --query='your question here'
Либо программно: AIAgent(..., save_trajectories=True) / initialize_agent(..., save_trajectories=True). Когда это включено, метод _save_trajectory() вызывается по завершении каждого хода разговора.
Пакетный раннер сохраняет траектории всегда — ради этого он и нужен.
Записи, где рассуждений нет ни в одном ходе, пакетный раннер автоматически отбрасывает, чтобы не засорять обучающие данные примерами без цепочки рассуждений.