Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Token Counting & Budget

Synaptic provides token counting and context budget primitives for managing model input limits.

TokenCounter Trait

The TokenCounter trait abstracts token counting for text and messages.

use synaptic::core::TokenCounter;

pub trait TokenCounter: Send + Sync {
    fn count_text(&self, text: &str) -> usize;

    // Default: sum of count_text(content) + 4 per-message overhead
    fn count_messages(&self, messages: &[Message]) -> usize;
}

HeuristicTokenCounter

A built-in implementation that estimates ~4 characters per token.

use synaptic::core::HeuristicTokenCounter;

let counter = HeuristicTokenCounter;
let tokens = counter.count_text("Hello, world!");  // ~3 tokens

This is a fast approximation. For precise counting, implement TokenCounter with a tokenizer such as tiktoken.

ContextBudget

ContextBudget assembles messages from multiple prioritized slots within a token limit.

use synaptic::core::{ContextBudget, ContextSlot, Priority, HeuristicTokenCounter};

let counter = Arc::new(HeuristicTokenCounter);
let budget = ContextBudget::new(4096, counter);

Priority Levels

Slots are processed in priority order. Lower values mean higher priority.

use synaptic::core::Priority;

Priority::CRITICAL  // 0 — always included first
Priority::HIGH      // 64
Priority::NORMAL    // 128
Priority::LOW       // 192 — dropped first when budget is tight

ContextSlot

Each slot carries a name, priority, messages, and an optional reserved token count.

use synaptic::core::ContextSlot;

let system_slot = ContextSlot {
    name: "system".to_string(),
    priority: Priority::CRITICAL,
    messages: vec![Message::system("You are a helpful assistant.")],
    reserved_tokens: 100,  // guaranteed if total reserved fits
};

let history_slot = ContextSlot {
    name: "history".to_string(),
    priority: Priority::NORMAL,
    messages: conversation_history,
    reserved_tokens: 0,  // best-effort
};

let tool_slot = ContextSlot {
    name: "tool_results".to_string(),
    priority: Priority::HIGH,
    messages: tool_messages,
    reserved_tokens: 0,
};

Assembling the Budget

Call assemble() to merge slots into a single message list that fits the budget.

let messages = budget.assemble(vec![system_slot, history_slot, tool_slot]);
// Slots are sorted by priority. Lower-priority slots are dropped if
// the budget is exceeded. The result is a flat Vec<Message>.

Higher-priority slots are included first. If a slot does not fit and has no reserved tokens, it is skipped entirely.