> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/nemo/automodel/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/nemo/automodel/_mcp/server.

# nemo_automodel.components.datasets.llm.agent_chat

Multi-turn agent SFT dataset adapter.

Loads function-calling chat datasets where each example contains tool
definitions and a multi-turn message list that includes tool calls and
tool responses, then renders them through the tokenizer's chat template
with `answer_only_loss_mask=True` so that `user` and `tool` tokens
are excluded from the loss.

Three input schemas are accepted:

1. Swift / chatml `messages` schema (used by
   `AI-ModelScope/function-calling-chatml`)::

   \{
   "tools": "\[\{...openai tool schema...}]",
   "messages": \[
   \{"role": "user",          "content": "..."},
   \{"role": "tool\_call",     "content": "\{"name": ..., "arguments": ...}"},
   \{"role": "tool\_response", "content": "..."},
   \{"role": "assistant",     "content": "..."}
   ]
   }

2. ShareGPT `conversations` schema (used by
   `llamafactory/glaive_toolcall_en` and similar)::

   \{
   "tools": "\[...]",
   "conversations": \[
   \{"from": "human",         "value": "..."},
   \{"from": "function\_call", "value": "..."},
   \{"from": "observation",   "value": "..."},
   \{"from": "gpt",           "value": "..."}
   ]
   }

3. OpenAI `messages` schema (used by `julien-c/synthtraces`)::

   \{
   "tools": \[\{...openai tool schema...}],
   "messages": \[
   \{"role": "user", "content": "..."},
   \{
   "role": "assistant",
   "content": "",
   "reasoning\_content": "...",
   "tool\_calls": \[\{...openai tool call...}]
   },
   \{"role": "tool", "tool\_call\_id": "call\_1", "content": "..."}
   ]
   }

Consecutive `tool_call` entries are merged into a single assistant
message with parallel `tool_calls`; the following `tool_response`
entries are paired with those calls in order.

## Module Contents

### Classes

| Name                                                                                    | Description                                                                            |
| --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| [`AgentChatConfig`](#nemo_automodel-components-datasets-llm-agent_chat-AgentChatConfig) | Construction-time configuration for the agent chat dataset (tokenizer is a build arg). |

### Functions

| Name                                                                                                                          | Description                                                                             |
| ----------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| [`_chat_fits_sequence_length`](#nemo_automodel-components-datasets-llm-agent_chat-_chat_fits_sequence_length)                 | Check a rendered length without materializing tokens beyond the budget.                 |
| [`_convert_messages`](#nemo_automodel-components-datasets-llm-agent_chat-_convert_messages)                                   | Convert chatml-style agent messages to OpenAI chat-completions format.                  |
| [`_extract_eval_samples_from_example`](#nemo_automodel-components-datasets-llm-agent_chat-_extract_eval_samples_from_example) | Extract one eval sample per assistant tool-call position.                               |
| [`_format_example`](#nemo_automodel-components-datasets-llm-agent_chat-_format_example)                                       | Render one agent example into tokenized `input_ids` / `labels`.                         |
| [`_format_example_impl`](#nemo_automodel-components-datasets-llm-agent_chat-_format_example_impl)                             | Render one agent example into tokenized `input_ids` / `labels`.                         |
| [`_has_assistant_message`](#nemo_automodel-components-datasets-llm-agent_chat-_has_assistant_message)                         | Return whether an example contains at least one assistant message.                      |
| [`_json_load_if_str`](#nemo_automodel-components-datasets-llm-agent_chat-_json_load_if_str)                                   | Parse `value` as JSON if it is a string, otherwise return as-is.                        |
| [`_reasoning_content`](#nemo_automodel-components-datasets-llm-agent_chat-_reasoning_content)                                 | Return a turn's reasoning/thinking trace as a string, or `None` if absent.              |
| [`_sharegpt_to_chatml`](#nemo_automodel-components-datasets-llm-agent_chat-_sharegpt_to_chatml)                               | Convert ShareGPT `&#123;from, value&#125;` turns to chatml `&#123;role, content&#125;`. |
| [`_trim_after_last_assistant`](#nemo_automodel-components-datasets-llm-agent_chat-_trim_after_last_assistant)                 | Remove an incomplete trailing exchange after the final assistant message.               |
| [`_truncate_messages_to_fit`](#nemo_automodel-components-datasets-llm-agent_chat-_truncate_messages_to_fit)                   | Drop the oldest conversation exchanges until the dialogue fits `seq_length`.            |
| [`make_agent_chat_dataset`](#nemo_automodel-components-datasets-llm-agent_chat-make_agent_chat_dataset)                       | Load a multi-turn function-calling SFT dataset.                                         |
| [`make_agent_chat_eval_samples`](#nemo_automodel-components-datasets-llm-agent_chat-make_agent_chat_eval_samples)             | Build a flat list of tool-call eval samples from an agent SFT dataset.                  |

### Data

[`_SHAREGPT_ROLE_MAP`](#nemo_automodel-components-datasets-llm-agent_chat-_SHAREGPT_ROLE_MAP)

[`_VALID_MESSAGE_ROLES`](#nemo_automodel-components-datasets-llm-agent_chat-_VALID_MESSAGE_ROLES)

[`logger`](#nemo_automodel-components-datasets-llm-agent_chat-logger)

### API

```python
class nemo_automodel.components.datasets.llm.agent_chat.AgentChatConfig(
    dataset_name: str | None = None,
    path: str | list[str] | None = None,
    split: str = 'train',
    revision: str | None = None,
    filter_no_assistant: bool = False,
    filter_overlong: bool = False,
    trim_incomplete_last_turn: bool = False,
    chat_template: str | None = None,
    seq_length: int | None = None,
    limit_dataset_samples: int | None = None,
    padding: str | bool = False,
    truncation: str | bool = False,
    mask_reasoning_content: bool = False,
    train_on_last_turn_only: bool = False,
    drop_history_reasoning_content: bool = False,
    truncate_history: bool = False
)
```

Dataclass

Construction-time configuration for the agent chat dataset (tokenizer is a build arg).

Exactly one of `dataset_name` or `path` must be set when calling :func:`build`.

**`accepts_tokenizer`** `bool = True`

---

**`chat_template`** `str | None = None`

Optional Jinja template string or file path overriding the tokenizer template.

---

**`dataset_name`** `str | None = None`

HF Hub dataset id, e.g. `llamafactory/glaive_toolcall_en`.

---

**`drop_history_reasoning_content`** `bool = False`

If True, strip `reasoning_content` from all but the final assistant turn.

---

**`filter_no_assistant`** `bool = False`

If True, discard examples without an assistant message.

---

**`filter_overlong`** `bool = False`

If True, discard examples that remain longer than `seq_length`.

---

**`limit_dataset_samples`** `int | None = None`

If set, keep only the first N examples.

---

**`mask_reasoning_content`** `bool = False`

If True, exclude assistant `reasoning_content` tokens from the loss.

---

**`padding`** `str | bool = False`

Padding strategy forwarded to the tokenizer.

---

**`path`** `str | list[str] | None = None`

Local JSON/JSONL file path or list of paths.

---

**`revision`** `str | None = None`

Optional Hugging Face dataset revision.

---

**`seq_length`** `int | None = None`

Optional max sequence length for the tokenizer.

---

**`split`** `str = 'train'`

Dataset split (only used with `dataset_name`).

---

**`train_on_last_turn_only`** `bool = False`

If True, supervise only the final assistant turn of each dialogue.

---

**`trim_incomplete_last_turn`** `bool = False`

If True, discard an unfinished exchange after the final assistant message.

---

**`truncate_history`** `bool = False`

If True, drop oldest exchanges until the dialogue fits `seq_length`.

---

**`truncation`** `str | bool = False`

Truncation strategy forwarded to the tokenizer.

---

```python
nemo_automodel.components.datasets.llm.agent_chat.AgentChatConfig.build(
    tokenizer: 'PreTrainedTokenizerBase | None'
) -> nemo_automodel.components.datasets.lazy_mapped_dataset.LazyMappedDataset
```

Build the agent chat :class:`LazyMappedDataset` from this :class:`AgentChatConfig` and a runtime tokenizer.

```python
nemo_automodel.components.datasets.llm.agent_chat._chat_fits_sequence_length(
    tokenizer,
    messages: typing.List[typing.Dict[str, typing.Any]],
    tools: typing.List[typing.Dict[str, typing.Any]] | None,
    seq_length: int
) -> bool
```

Check a rendered length without materializing tokens beyond the budget.

```python
nemo_automodel.components.datasets.llm.agent_chat._convert_messages(
    messages: typing.List[typing.Dict[str, typing.Any]],
    example_id: typing.Union[int, str] | None = None,
    drop_history_reasoning_content: bool = False
) -> typing.List[typing.Dict[str, typing.Any]]
```

Convert chatml-style agent messages to OpenAI chat-completions format.

Native OpenAI assistant `tool_calls` and tool `tool_call_id` fields
are preserved. Consecutive `tool_call` entries collapse into one
assistant message with parallel `tool_calls`. When the preceding emitted turn is an
assistant message without tool\_calls (e.g. an assistant `content`
that reasons before calling tools), the tool\_calls attach to that
message instead of creating a second consecutive assistant turn —
this preserves the natural single-turn shape the model produces at
inference. `tool_response` (or `tool`) entries that follow are
paired with those tool\_call ids in order.

**Parameters:**

**`messages`** `List[Dict[str, Any]]`

chatml-style turns with roles in `_VALID_MESSAGE_ROLES`.

---

**`example_id`** `Union[int, str] | None` — default: None

Optional identifier used to derive unique tool\_call ids.

---

**`drop_history_reasoning_content`** `bool` — default: False

If True, strip `reasoning_content`
from every assistant turn except the final one, so historical
thinking traces are not rendered into the prompt. This matches
inference, where the model never sees its own prior-turn thinking.

---

**Returns:** `List[Dict[str, Any]]`

A list of OpenAI-format messages suitable for `apply_chat_template`.

```python
nemo_automodel.components.datasets.llm.agent_chat._extract_eval_samples_from_example(
    example: typing.Dict[str, typing.Any]
) -> typing.List[typing.Dict[str, typing.Any]]
```

Extract one eval sample per assistant tool-call position.

For each assistant turn in `example` that issues tool\_calls, emit
a sample whose `prompt_messages` are all messages strictly before
that turn and whose `gt_tool_calls` are the tool\_calls from that
turn. This lets the evaluator measure tool-call accuracy at every
position the model is expected to act, not just the first one.

Tool-call arguments are normalized from JSON-encoded strings (as
produced by :func:`_convert_messages`) back to dicts so callers can
compare against parser output directly.

**Parameters:**

**`example`** `Dict[str, Any]`

a raw row from the agent SFT dataset (chatml `messages`
or ShareGPT `conversations` schema, with a `tools` field).

---

**Returns:** `List[Dict[str, Any]]`

A list of eval samples, each with keys `prompt_messages`,

```python
nemo_automodel.components.datasets.llm.agent_chat._format_example(
    example: typing.Dict[str, typing.Any],
    tokenizer,
    eos_token_id: int,
    pad_token_id: int,
    seq_length: int | None = None,
    padding: typing.Union[str, bool] = False,
    truncation: typing.Union[str, bool] = False,
    mask_reasoning_content: bool = False,
    train_on_last_turn_only: bool = False,
    drop_history_reasoning_content: bool = False,
    truncate_history: bool = False,
    trim_incomplete_last_turn: bool = False
) -> typing.Dict[str, typing.List[int]]
```

Render one agent example into tokenized `input_ids` / `labels`.

Thin wrapper that re-raises any parsing/rendering failure as a `ValueError`
tagged with the example id. Rows are rendered lazily inside the dataloader,
so without this a single malformed row surfaces as an opaque
`JSONDecodeError`/`AssertionError` deep in the stack — with no hint as to
which row caused it — and aborts the whole training run.

```python
nemo_automodel.components.datasets.llm.agent_chat._format_example_impl(
    example: typing.Dict[str, typing.Any],
    tokenizer,
    eos_token_id: int,
    pad_token_id: int,
    seq_length: int | None = None,
    padding: typing.Union[str, bool] = False,
    truncation: typing.Union[str, bool] = False,
    mask_reasoning_content: bool = False,
    train_on_last_turn_only: bool = False,
    drop_history_reasoning_content: bool = False,
    truncate_history: bool = False,
    trim_incomplete_last_turn: bool = False
) -> typing.Dict[str, typing.List[int]]
```

Render one agent example into tokenized `input_ids` / `labels`.

```python
nemo_automodel.components.datasets.llm.agent_chat._has_assistant_message(
    example: typing.Dict[str, typing.Any]
) -> bool
```

Return whether an example contains at least one assistant message.

```python
nemo_automodel.components.datasets.llm.agent_chat._json_load_if_str(
    value: typing.Any
) -> typing.Any
```

Parse `value` as JSON if it is a string, otherwise return as-is.

```python
nemo_automodel.components.datasets.llm.agent_chat._reasoning_content(
    turn: typing.Dict[str, typing.Any]
) -> str | None
```

Return a turn's reasoning/thinking trace as a string, or `None` if absent.

Shared by every conversion path so the `reasoning_content` field is read
and coerced identically (a falsy/empty trace is treated as absent).

```python
nemo_automodel.components.datasets.llm.agent_chat._sharegpt_to_chatml(
    conversations: typing.List[typing.Dict[str, typing.Any]]
) -> typing.List[typing.Dict[str, typing.Any]]
```

Convert ShareGPT `&#123;from, value&#125;` turns to chatml `&#123;role, content&#125;`.

```python
nemo_automodel.components.datasets.llm.agent_chat._trim_after_last_assistant(
    messages: typing.List[typing.Dict[str, typing.Any]]
) -> typing.List[typing.Dict[str, typing.Any]]
```

Remove an incomplete trailing exchange after the final assistant message.

```python
nemo_automodel.components.datasets.llm.agent_chat._truncate_messages_to_fit(
    tokenizer,
    messages: typing.List[typing.Dict[str, typing.Any]],
    tools: typing.List[typing.Dict[str, typing.Any]] | None,
    seq_length: int
) -> typing.List[typing.Dict[str, typing.Any]]
```

Drop the oldest conversation exchanges until the dialogue fits `seq_length`.

Unlike token-level `truncation` (which clips from the tokenizer's
`truncation_side` and so typically drops the final assistant answer — the
supervised target), this drops whole oldest *exchanges* while keeping any
leading `system` message, the tool definitions, and the final exchange
(the user turn that produces the last assistant answer). Tool-call/response
pairing among the survivors is preserved because exchanges are cut only at
`user` boundaries.

**Parameters:**

**`tokenizer`**

tokenizer with a chat template.

---

**`messages`** `List[Dict[str, Any]]`

OpenAI-format messages from :func:`_convert_messages`.

---

**`tools`** `List[Dict[str, Any]] | None`

tool definitions rendered alongside the messages.

---

**`seq_length`** `int`

the token budget the rendered dialogue must fit.

---

**Returns:** `List[Dict[str, Any]]`

`messages` unchanged when it already fits or has no `user` boundary

```python
nemo_automodel.components.datasets.llm.agent_chat.make_agent_chat_dataset(
    tokenizer,
    dataset_name: str | None = None,
    path: typing.Union[str, typing.List[str]] | None = None,
    split: str = 'train',
    revision: str | None = None,
    filter_no_assistant: bool = False,
    filter_overlong: bool = False,
    trim_incomplete_last_turn: bool = False,
    chat_template: str | None = None,
    seq_length: int | None = None,
    limit_dataset_samples: int | None = None,
    padding: typing.Union[str, bool] = False,
    truncation: typing.Union[str, bool] = False,
    mask_reasoning_content: bool = False,
    train_on_last_turn_only: bool = False,
    drop_history_reasoning_content: bool = False,
    truncate_history: bool = False
) -> nemo_automodel.components.datasets.lazy_mapped_dataset.LazyMappedDataset
```

Load a multi-turn function-calling SFT dataset.

Exactly one of `dataset_name` (HuggingFace Hub id) or `path` (local
JSON/JSONL file or list of files) must be provided. The loaded examples
are lazily rendered through the tokenizer's chat template; tool/user
tokens are excluded from the loss via `answer_only_loss_mask=True`.

**Parameters:**

**`tokenizer`**

HuggingFace tokenizer with a chat template.

---

**`dataset_name`** `str | None` — default: None

HF Hub dataset id, e.g. `llamafactory/glaive_toolcall_en`.

---

**`path`** `Union[str, List[str]] | None` — default: None

Local JSON/JSONL file path or list of paths.

---

**`split`** `str` — default: 'train'

Dataset split (only used with `dataset_name`).

---

**`revision`** `str | None` — default: None

Optional Hugging Face dataset revision. Agent trace repositories
may expose training-ready rows under `refs/convert/parquet`.

---

**`filter_no_assistant`** `bool` — default: False

If True, discard incomplete traces that contain no
assistant message and therefore have no supervised target.

---

**`filter_overlong`** `bool` — default: False

If True, discard traces that still exceed `seq_length`
after whole-exchange history truncation. This avoids token-level
truncation changing chat-template prefixes while answer-only loss
masks are constructed.

---

**`trim_incomplete_last_turn`** `bool` — default: False

If True, discard messages after the final
assistant message so an unfinished user turn cannot replace the
supervised target during history truncation.

---

**`chat_template`** `str | None` — default: None

Optional Jinja template string or file path overriding
`tokenizer.chat_template`.

---

**`seq_length`** `int | None` — default: None

Optional max sequence length for the tokenizer.

---

**`limit_dataset_samples`** `int | None` — default: None

If set, keep only the first N examples.

---

**`padding`** `Union[str, bool]` — default: False

Padding strategy forwarded to the tokenizer.

---

**`truncation`** `Union[str, bool]` — default: False

Truncation strategy forwarded to the tokenizer.

---

**`mask_reasoning_content`** `bool` — default: False

If True, exclude assistant `reasoning_content`
(thinking) tokens from the loss while still rendering them into the
prompt. Requires a chat template that emits `reasoning_content`.
Defaults to False, which trains on reasoning tokens like any other
assistant content.

---

**`train_on_last_turn_only`** `bool` — default: False

If True, supervise only the final assistant
turn of each dialogue (`mask_history`); all earlier assistant
turns are excluded from the loss. Defaults to False, which
supervises every assistant turn.

---

**`drop_history_reasoning_content`** `bool` — default: False

If True, strip `reasoning_content`
from all but the final assistant turn so historical thinking is not
rendered into the prompt (matching inference, where prior-turn
thinking is not visible). Orthogonal to `mask_reasoning_content`:
this controls whether history thinking appears in the prompt at all,
the latter controls whether rendered thinking contributes to loss.
Defaults to False, which keeps every turn's reasoning\_content.

---

**`truncate_history`** `bool` — default: False

If True and `seq_length` is set, drop the oldest
conversation exchanges (keeping any leading system message, the tool
definitions, and the final exchange) until the dialogue fits
`seq_length`. Unlike token-level `truncation`, which clips from
the tokenizer side and usually drops the final assistant answer,
this preserves the supervised target. Defaults to False.

---

**Returns:** `LazyMappedDataset`

A `LazyMappedDataset` yielding dicts with `input_ids`, `labels`

```python
nemo_automodel.components.datasets.llm.agent_chat.make_agent_chat_eval_samples(
    dataset_name: str | None = None,
    path: typing.Union[str, typing.List[str]] | None = None,
    split: str = 'train',
    revision: str | None = None,
    limit_dataset_samples: int | None = None,
    max_eval_samples: int | None = None
) -> typing.List[typing.Dict[str, typing.Any]]
```

Build a flat list of tool-call eval samples from an agent SFT dataset.

Each dialogue is expanded into one sample per assistant tool-call
position via :func:`_extract_eval_samples_from_example`. The result
is a plain list (not a HuggingFace `Dataset`) because evaluation
iterates linearly and needs the raw structured fields, not tokenized
tensors.

Exactly one of `dataset_name` or `path` must be provided.

**Parameters:**

**`dataset_name`** `str | None` — default: None

HF Hub dataset id, e.g. `llamafactory/glaive_toolcall_en`.

---

**`path`** `Union[str, List[str]] | None` — default: None

Local JSON/JSONL file path or list of paths.

---

**`split`** `str` — default: 'train'

Dataset split (only used with `dataset_name`).

---

**`revision`** `str | None` — default: None

Optional Hugging Face dataset revision.

---

**`limit_dataset_samples`** `int | None` — default: None

If set, read only the first N dialogues
before expansion. Useful to bound evaluation cost.

---

**`max_eval_samples`** `int | None` — default: None

If set, cap the total expanded sample count.

---

**Returns:** `List[Dict[str, Any]]`

A list of dicts with keys `prompt_messages`, `tools`,

```python
nemo_automodel.components.datasets.llm.agent_chat._SHAREGPT_ROLE_MAP = {'system': 'system', 'human': 'user', 'user': 'user', 'gpt': 'assistant', 'assis...
```

```python
nemo_automodel.components.datasets.llm.agent_chat._VALID_MESSAGE_ROLES = {'system', 'user', 'assistant', 'tool_call', 'tool_response', 'tool'}
```

```python
nemo_automodel.components.datasets.llm.agent_chat.logger = logging.getLogger(__name__)
```