Skip to content

Context Statistics API

Context statistics report an estimated token breakdown of the request a session would send: the system prompt, request-local instructions, tool definitions, skill text, conversation history, and media. They are meant for budgeting and for understanding which part of a request dominates.

These are estimates, not tokenizer counts

Values use the same heuristic as context compaction: roughly four characters per token for text, plus a size-based approximation for base64 media. Cached tokens, provider-specific framing, and attachment tokenization are approximate. Do not treat them as billed usage.

estimate_context_stats is a pure function over already-assembled inputs. The Agent.context_stats(session_id) method assembles the same system prompt, tool schemas, skill catalog, and persisted history the next text request would use, without calling the provider or changing session state. Explicit $skill activation text is request-local and never persisted, so an idle estimate does not include it.

Components

Components always appear in a stable order with stable keys and labels, even when their estimate is zero. Component estimates sum exactly to total_tokens.

Key Label
system_prompt System prompt
instructions Request instructions
tools Tool definitions
skills Skills
history_user Conversation history (user)
history_assistant Conversation history (assistant)
history_tool Conversation history (tool)
history_other Conversation history (other)
media Media and attachments

Loaded skill bodies returned through the skill(name) tool are reported under skills; their media (if any) remains under media.

Python

context_stats.py
from pathlib import Path

from nagents import Agent, Provider, ProviderType, SessionManager

agent = Agent(
    provider=Provider(ProviderType.OPENAI_COMPATIBLE, "sk-...", "gpt-4o"),
    session_manager=SessionManager(Path("sessions.db")),
    system_prompt="You are a helpful assistant.",
)

# ...after some runs...
stats = await agent.context_stats("session-123")
for component in stats.components:
    print(f"{component.label}: {component.tokens}")
print(f"Total: {stats.total_tokens}")
if stats.context_window is not None:
    print(f"Remaining: {stats.remaining_tokens} of {stats.context_window}")
if stats.observed_prompt_tokens is not None:
    print(f"Last provider-reported input: {stats.observed_prompt_tokens}")

The model context window comes from get_model_context_limit; it is None when the window is not known. The window is reported only for reference and never changes model behavior.

nagents.context_stats.ContextStats dataclass

Estimated breakdown of what a request carries.

Attributes:

Name Type Description
components tuple[ContextComponent, ...]

Stable, ordered components that sum to total_tokens.

total_tokens int

Estimated input tokens for the assembled request.

context_window int | None

Provider/model context window when known, else None.

remaining_tokens int | None

context_window - total_tokens when the window is known, else None. It can be negative if the estimate exceeds it.

observed_prompt_tokens int | None

Last provider-reported input tokens, when the agent has seen a real usage event for the session.

observed_completion_tokens int | None

Last provider-reported output tokens.

provider str

Provider identifier the estimate was computed for.

model str

Model identifier the estimate was computed for.

estimate_method str

Human-readable note describing the heuristic.

Source code in src/nagents/context_stats.py
@dataclass(frozen=True)
class ContextStats:
    """Estimated breakdown of what a request carries.

    Attributes:
        components: Stable, ordered components that sum to ``total_tokens``.
        total_tokens: Estimated input tokens for the assembled request.
        context_window: Provider/model context window when known, else ``None``.
        remaining_tokens: ``context_window - total_tokens`` when the window is
            known, else ``None``. It can be negative if the estimate exceeds it.
        observed_prompt_tokens: Last provider-reported input tokens, when the
            agent has seen a real usage event for the session.
        observed_completion_tokens: Last provider-reported output tokens.
        provider: Provider identifier the estimate was computed for.
        model: Model identifier the estimate was computed for.
        estimate_method: Human-readable note describing the heuristic.
    """

    components: tuple[ContextComponent, ...]
    total_tokens: int
    context_window: int | None = None
    remaining_tokens: int | None = None
    observed_prompt_tokens: int | None = None
    observed_completion_tokens: int | None = None
    provider: str = ""
    model: str = ""
    estimate_method: str = ESTIMATE_METHOD

    def component(self, key: str) -> ContextComponent | None:
        """Return the component with ``key``, or ``None`` when it is absent."""
        for component in self.components:
            if component.key == key:
                return component
        return None

    def as_dict(self) -> dict[str, object]:
        """Return a JSON-serializable mapping for HTTP or logging surfaces."""
        return {
            "components": [asdict(component) for component in self.components],
            "total_tokens": self.total_tokens,
            "context_window": self.context_window,
            "remaining_tokens": self.remaining_tokens,
            "observed_prompt_tokens": self.observed_prompt_tokens,
            "observed_completion_tokens": self.observed_completion_tokens,
            "provider": self.provider,
            "model": self.model,
            "estimate_method": self.estimate_method,
        }

as_dict()

Return a JSON-serializable mapping for HTTP or logging surfaces.

Source code in src/nagents/context_stats.py
def as_dict(self) -> dict[str, object]:
    """Return a JSON-serializable mapping for HTTP or logging surfaces."""
    return {
        "components": [asdict(component) for component in self.components],
        "total_tokens": self.total_tokens,
        "context_window": self.context_window,
        "remaining_tokens": self.remaining_tokens,
        "observed_prompt_tokens": self.observed_prompt_tokens,
        "observed_completion_tokens": self.observed_completion_tokens,
        "provider": self.provider,
        "model": self.model,
        "estimate_method": self.estimate_method,
    }

component(key)

Return the component with key, or None when it is absent.

Source code in src/nagents/context_stats.py
def component(self, key: str) -> ContextComponent | None:
    """Return the component with ``key``, or ``None`` when it is absent."""
    for component in self.components:
        if component.key == key:
            return component
    return None

nagents.context_stats.ContextComponent dataclass

One labeled part of the estimated request context.

Source code in src/nagents/context_stats.py
@dataclass(frozen=True)
class ContextComponent:
    """One labeled part of the estimated request context."""

    key: str
    label: str
    tokens: int

nagents.estimate_context_stats(*, system_prompt='', instructions='', tools=(), skill_manifest='', skill_text='', messages=(), context_window=None, provider='', model='', observed=None)

Estimate a request breakdown from its assembled inputs.

Callers pass the same values they would send to the provider: the system prompt, request-local instructions, tool schemas, rendered skill text (the catalog manifest and any loaded bodies), and the conversation history. Skill tool results in messages are counted under skills instead of history.

Parameters:

Name Type Description Default
system_prompt str

The agent's base system prompt.

''
instructions str

Request-local instructions separable from the system prompt.

''
tools Sequence[ToolDefinition]

Tool definitions whose JSON schemas are sent to the provider.

()
skill_manifest str

Rendered available-skills catalog text.

''
skill_text str

Loaded skill body text included in the request.

''
messages Sequence[Message]

Conversation history, excluding the base system prompt.

()
context_window int | None

Known model context window, or None when unknown.

None
provider str

Provider identifier for reporting.

''
model str

Model identifier for reporting.

''
observed TokenUsage | None

Last real provider usage for comparison with the estimate.

None
Source code in src/nagents/context_stats.py
def estimate_context_stats(
    *,
    system_prompt: str = "",
    instructions: str = "",
    tools: Sequence[ToolDefinition] = (),
    skill_manifest: str = "",
    skill_text: str = "",
    messages: Sequence[Message] = (),
    context_window: int | None = None,
    provider: str = "",
    model: str = "",
    observed: TokenUsage | None = None,
) -> ContextStats:
    """Estimate a request breakdown from its assembled inputs.

    Callers pass the same values they would send to the provider: the system
    prompt, request-local instructions, tool schemas, rendered skill text (the
    catalog manifest and any loaded bodies), and the conversation history. Skill
    tool results in ``messages`` are counted under ``skills`` instead of history.

    Args:
        system_prompt: The agent's base system prompt.
        instructions: Request-local instructions separable from the system prompt.
        tools: Tool definitions whose JSON schemas are sent to the provider.
        skill_manifest: Rendered available-skills catalog text.
        skill_text: Loaded skill body text included in the request.
        messages: Conversation history, excluding the base system prompt.
        context_window: Known model context window, or ``None`` when unknown.
        provider: Provider identifier for reporting.
        model: Model identifier for reporting.
        observed: Last real provider usage for comparison with the estimate.
    """
    values = {key: 0 for key, _ in COMPONENT_LABELS}
    values[SYSTEM_PROMPT_KEY] = estimate_tokens(system_prompt)
    values[INSTRUCTIONS_KEY] = estimate_tokens(instructions)
    values[TOOLS_KEY] = estimate_tool_tokens(tools)
    values[SKILLS_KEY] = estimate_tokens(skill_manifest) + estimate_tokens(skill_text)
    for key, tokens in _history_tokens(messages).items():
        values[key] += tokens

    components = tuple(ContextComponent(key, label, values[key]) for key, label in COMPONENT_LABELS)
    total = sum(component.tokens for component in components)
    return ContextStats(
        components=components,
        total_tokens=total,
        context_window=context_window,
        remaining_tokens=context_window - total if context_window is not None else None,
        observed_prompt_tokens=observed.prompt_tokens if observed is not None else None,
        observed_completion_tokens=observed.completion_tokens if observed is not None else None,
        provider=provider,
        model=model,
    )

nagents.estimate_tool_tokens(tools)

Estimate tokens for the JSON tool schemas sent to the provider.

The neutral name/description/parameters payload is used for every provider. Provider-specific wrapper keys add a constant, negligible overhead.

Source code in src/nagents/context_stats.py
def estimate_tool_tokens(tools: Sequence[ToolDefinition]) -> int:
    """Estimate tokens for the JSON tool schemas sent to the provider.

    The neutral ``name``/``description``/``parameters`` payload is used for every
    provider. Provider-specific wrapper keys add a constant, negligible overhead.
    """
    total = 0
    for tool in tools:
        schema = json.dumps(
            {"name": tool.name, "description": tool.description, "parameters": tool.parameters},
            ensure_ascii=False,
        )
        total += estimate_tokens(schema)
    return total