> This page is for version 0.6.0.
> For other versions, use one of these documentation indexes:
> - Main (default): https://docs.nvidia.com/nemo/gym/main/llms.txt
> - 0.7.0: https://docs.nvidia.com/nemo/gym/v0.7.0/llms.txt
> - 0.6.0: https://docs.nvidia.com/nemo/gym/v0.6.0/llms.txt
> - 0.5.1: https://docs.nvidia.com/nemo/gym/v0.5.1/llms.txt
> - 0.5.0: https://docs.nvidia.com/nemo/gym/v0.5.0/llms.txt
> - 0.4.0: https://docs.nvidia.com/nemo/gym/v0.4.0/llms.txt
> - 0.3.0: https://docs.nvidia.com/nemo/gym/v0.3.0/llms.txt
> - 0.2.1: https://docs.nvidia.com/nemo/gym/v0.2.1/llms.txt

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

# nemo_gym.trajectory.model_call_record

One model call as a rollout records it: token counts, cache use, tool calls, and totals across calls.

## Module Contents

### Classes

| Name                                                                                | Description                                                           |
| ----------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| [`ModelCallRecord`](#nemo_gym-trajectory-model_call_record-ModelCallRecord)         | Observability record derived from one captured model-server exchange. |
| [`ToolRequestEvidence`](#nemo_gym-trajectory-model_call_record-ToolRequestEvidence) | Whether captured response evidence can establish a tool request.      |

### Functions

| Name                                                                                                  | Description                                                                                   |
| ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| [`_as_arguments`](#nemo_gym-trajectory-model_call_record-_as_arguments)                               | -                                                                                             |
| [`_cache_signal`](#nemo_gym-trajectory-model_call_record-_cache_signal)                               | Cache hit/miss + cached-token count, from usage cache fields (OpenAI / Anthropic).            |
| [`_prompt_size`](#nemo_gym-trajectory-model_call_record-_prompt_size)                                 | Return the whole prompt size, with cached tokens counted exactly once.                        |
| [`_token_count`](#nemo_gym-trajectory-model_call_record-_token_count)                                 | -                                                                                             |
| [`_tool_calls_and_reasoning`](#nemo_gym-trajectory-model_call_record-_tool_calls_and_reasoning)       | Structured tool calls (name, arguments, call_id) and reasoning text, across all three shapes. |
| [`_usage_detail_token`](#nemo_gym-trajectory-model_call_record-_usage_detail_token)                   | Return the first valid token count across equivalent provider detail shapes.                  |
| [`aggregate_model_call_records`](#nemo_gym-trajectory-model_call_record-aggregate_model_call_records) | Aggregate token and latency values from model-call records.                                   |
| [`build_model_call_record`](#nemo_gym-trajectory-model_call_record-build_model_call_record)           | Map one captured exchange and its transport metadata into an observability record.            |
| [`extract_token_stats`](#nemo_gym-trajectory-model_call_record-extract_token_stats)                   | Normalize token totals across Responses, Chat Completions, and Anthropic Messages usage.      |
| [`tool_request_evidence`](#nemo_gym-trajectory-model_call_record-tool_request_evidence)               | Classify request evidence consistently for normalization and canonical decoding.              |

### API

```python
class nemo_gym.trajectory.model_call_record.ModelCallRecord()
```

**Bases:** `BaseModel`

Observability record derived from one captured model-server exchange.

**`cache_creation_tokens`** `Optional[int] = None`

---

**`cache_hit`** `Optional[bool] = None`

---

**`cached_tokens`** `Optional[int] = None`

---

**`call_index`** `int`

---

**`client_assistant_message_id`** `Optional[str]`

---

**`client_session_id`** `Optional[str]`

---

**`completed_at`** `Optional[float] = None`

---

**`dialect`** `Optional[str] = None`

---

**`error_category`** `Optional[str] = None`

---

**`failure_report`** `FailureReport | None = None`

---

**`finish_reason`** `Optional[str] = None`

---

**`latency_total_ms`** `Optional[float] = None`

---

**`latency_ttft_ms`** `Optional[float] = None`

---

**`local_response_reason`** `Optional[str] = None`

---

**`model`** `Optional[str] = None`

---

**`model_call_id`** `Optional[str] = None`

---

**`model_operations`** `list[ModelOperationObservation] | None = None`

---

**`model_ref`** `Optional[ModelServerRef] = None`

---

**`reasoning_content`** `Optional[str] = None`

---

**`request`** `Optional[dict[str, Any]] = None`

---

**`request_raw`** `Optional[str] = None`

---

**`response`** `Optional[dict[str, Any]] = None`

---

**`response_id`** `Optional[str] = None`

---

**`response_raw`** `Optional[str] = None`

---

**`response_source`** `Optional[Literal['upstream', 'local']] = None`

---

**`response_status`** `Optional[str] = None`

---

**`started_at`** `Optional[float] = None`

---

**`status_code`** `Optional[int] = None`

---

**`tokens_in`** `Optional[int] = None`

---

**`tokens_out`** `Optional[int] = None`

---

**`tokens_reasoning`** `Optional[int] = None`

---

**`tokens_total`** `Optional[int] = None`

---

**`tool_calls`** `list[dict[str, Any]] = Field(default_factory=list)`

---

**`upstream_attempted`** `Optional[bool] = None`

---

**`upstream_status_code`** `Optional[int] = None`

---

```python
class nemo_gym.trajectory.model_call_record.ToolRequestEvidence()
```

**Bases:** `NamedTuple`

Whether captured response evidence can establish a tool request.

**`call_ids`** `frozenset[str] = frozenset()`

---

**`kind`** `Literal['available', 'omitted', 'malformed', 'unsuccessful']`

---

```python
nemo_gym.trajectory.model_call_record.ToolRequestEvidence.permits_link(
    native_call_id: str
) -> bool
```

```python
nemo_gym.trajectory.model_call_record._as_arguments(
    arguments: typing.Any
) -> dict[str, typing.Any]
```

```python
nemo_gym.trajectory.model_call_record._cache_signal(
    usage: typing.Any
) -> tuple[typing.Optional[bool], typing.Optional[int]]
```

Cache hit/miss + cached-token count, from usage cache fields (OpenAI / Anthropic).

```python
nemo_gym.trajectory.model_call_record._prompt_size(
    usage: typing.Mapping[str, typing.Any]
) -> typing.Optional[int]
```

Return the whole prompt size, with cached tokens counted exactly once.

Usage formats disagree on whether the prompt count includes the cache:

* `prompt_tokens` (Chat Completions) includes it. It wins whenever present, also when a gateway
  adds Anthropic-style `cache_*` fields beside it.
* `input_tokens` with top-level `cache_read_input_tokens` or `cache_creation_input_tokens`
  (Anthropic Messages) excludes it, so the cache counts are added.
* `input_tokens` alone (Responses) includes it.

The OpenAI `*_tokens_details` cache read and write counts are subsets of the prompt count and are never added.

```python
nemo_gym.trajectory.model_call_record._token_count(
    value: typing.Any
) -> typing.Optional[int]
```

```python
nemo_gym.trajectory.model_call_record._tool_calls_and_reasoning(
    response: dict[str, typing.Any]
) -> tuple[list[dict[str, typing.Any]], typing.Optional[str]]
```

Structured tool calls (name, arguments, call_id) and reasoning text, across all three shapes.

```python
nemo_gym.trajectory.model_call_record._usage_detail_token(
    usage: typing.Mapping[str, typing.Any],
    detail_groups: tuple[str, ...],
    field_names: tuple[str, ...]
) -> typing.Optional[int]
```

Return the first valid token count across equivalent provider detail shapes.

```python
nemo_gym.trajectory.model_call_record.aggregate_model_call_records(
    calls: list[nemo_gym.trajectory.model_call_record.ModelCallRecord]
) -> dict[str, typing.Any]
```

Aggregate token and latency values from model-call records.

```python
nemo_gym.trajectory.model_call_record.build_model_call_record(
    exchange: dict[str, typing.Any],
    call_index: int
) -> nemo_gym.trajectory.model_call_record.ModelCallRecord
```

Map one captured exchange and its transport metadata into an observability record.

```python
nemo_gym.trajectory.model_call_record.extract_token_stats(
    usage: typing.Any
) -> dict[str, typing.Optional[int]]
```

Normalize token totals across Responses, Chat Completions, and Anthropic Messages usage.

`tokens_in` is the whole prompt size (see `_prompt_size`); cache creation is also reported
separately as `cache_creation_tokens`. A count the provider did not report stays `None`, never 0.

`tokens_in` is a prompt-*size* metric, not a cost proxy: providers price cache-read (\~0.1x) and
cache-creation (\~1.25x) differently from base input, so cost-accurate consumers should weight
`cached_tokens` and `cache_creation_tokens` separately rather than summing `tokens_in`.

```python
nemo_gym.trajectory.model_call_record.tool_request_evidence(
    response: typing.Any,
    status_code: int | None = None,
    response_status: str | None = None
) -> nemo_gym.trajectory.model_call_record.ToolRequestEvidence
```

Classify request evidence consistently for normalization and canonical decoding.

Omitted payloads may retain an independently resolved association with a verification gap.
Malformed payloads and explicit failed responses cannot establish a requesting call.