Принципы расчета токенов
Что вы сможете сделать после изучения
- Понять стратегию расчета токенов DCP (значения API vs оценочные значения)
- Понимать распределение токенов в выводе
/dcp context - Знать диапазон погрешности оценки токенов и методы оптимизации
- Точно оценить эффект экономии токенов DCP
Ваша текущая проблема
Вы смотрите на вывод /dcp context, там куча цифр и процентов, но вы не знаете, как они рассчитываются:
Session Context Breakdown:
────────────────────────────────────────────────────────
System 15.2% │████████████████▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ 25.1K tokens
User 5.1% │████▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ 8.4K tokens
Assistant 35.8% │██████████████████████████████████████▒▒▒▒▒▒│ 59.2K tokens
Tools (45) 43.9% │████████████████████████████████████████████████│ 72.6K tokens
────────────────────────────────────────────────────────
Summary:
Pruned: 12 tools (~15.2K tokens)
Current context: ~165.3K tokens
Without DCP: ~180.5K tokensОткуда взялись эти цифры? Почему Assistant рассчитывается как "остаточный расчет"? Как вычисляется prunedTokens?
Когда использовать этот метод
- Когда нужно точно оценить эффект экономии токенов DCP
- Когда нужно проанализировать структуру потребления токенов (какая часть занимает больше всего)
- Когда есть подозрение на погрешность расчета токенов и нужно проверить точность
- Когда нужно доложить команде об результатах оптимизации DCP
Основная идея
Расчет токенов DCP использует смешанную стратегию: приоритет точным значениям, возвращаемым API, при необходимости использование оценочных значений.
graph TD
A[Расчет токенов] --> B{Нужно точное вычисление?}
B -->|Да| C[Использовать значения, возвращаемые API]
B -->|Нет| D[Использовать оценку Tokenizer]
C --> E[Total: input + output + reasoning + cache.read + cache.write]
C --> F[System: firstAssistant.input + cache.read - firstUserTokens]
D --> G[функция countTokens]
G --> H{Tokenizer доступен?}
H -->|Да| I[Использовать Anthropic Tokenizer]
H -->|Нет| J[Откат к количеству символов / 4]
I --> K[User: tokenizer всех пользовательских сообщений]
I --> L[Tools: tokenizer ввода-вывода инструментов - prunedTokens]Ключевые принципы:
- Приоритет значений API: Total и System используют точные значения, возвращаемые API
- Дополнение оценочными значениями: User и Tools оцениваются через tokenizer (так как API не предоставляет эти значения)
- Остаточный расчет: Assistant вычисляется через Total - System - User - Tools
Стратегия расчета токенов
Точные значения, возвращаемые API
OpenCode API возвращает точную статистику токенов для каждого ответа AI:
| Поле | Описание | Источник |
|---|---|---|
tokens.input | Количество токенов ввода для текущего запроса | Точное значение API |
tokens.output | Количество токенов вывода, сгенерированных в текущем запросе | Точное значение API |
tokens.reasoning | Количество токенов рассуждения (например, цепочка мыслей Claude 3.5) | Точное значение API (если применимо) |
tokens.cache.read | Количество токенов, прочитанных из кэша | Точное значение API |
tokens.cache.write | Количество токенов, записанных в кэш | Точное значение API |
Эти значения точны, так как они поступают напрямую из системы биллинга поставщика LLM.
Метод расчета оценочных значений
Для значений, которые не предоставляет API (например, пользовательские сообщения, вызовы инструментов), DCP использует Anthropic Tokenizer для оценки.
Код реализации (lib/strategies/utils.ts:36-43):
export function countTokens(text: string): number {
if (!text) return 0
try {
// Приоритет использования Anthropic Tokenizer
return anthropicCountTokens(text)
} catch {
// При неудаче откат к количеству символов / 4
return Math.round(text.length / 4)
}
}Почему количество символов/4 используется как запасной вариант:
- Среднее соотношение токенов для английского текста составляет примерно 4 символа = 1 токен
- Это лишь грубая оценка, возможны погрешности
- Используется только когда Tokenizer недоступен (например, проблемы с сетью, сбой загрузки библиотеки)
Логика расчета токенов по категориям
Total (общее количество токенов)
Формула расчета:
total = input + output + reasoning + cache.read + cache.writeКод реализации (lib/commands/context.ts:103-108):
const apiInput = lastAssistant?.tokens?.input || 0
const apiOutput = lastAssistant?.tokens?.output || 0
const apiReasoning = lastAssistant?.tokens?.reasoning || 0
const apiCacheRead = lastAssistant?.tokens?.cache?.read || 0
const apiCacheWrite = lastAssistant?.tokens?.cache?.write || 0
breakdown.total = apiInput + apiOutput + apiReasoning + apiCacheRead + apiCacheWriteОписание:
- Использует статистику токенов последнего ответа Assistant
- Включает все значения, возвращаемые API
- Совпадает с общим количеством токенов, отображаемым в OpenCode UI
System (токены системного промпта)
Формула расчета:
system = firstAssistant.input + firstAssistant.cache.read - firstUserTokensКод реализации (lib/commands/context.ts:161-165):
if (firstAssistant) {
const firstInput =
(firstAssistant.tokens?.input || 0) + (firstAssistant.tokens?.cache?.read || 0)
breakdown.system = Math.max(0, firstInput - firstUserTokens)
}Принцип:
- input первого ответа Assistant содержит: системный промпт + первое пользовательское сообщение
- Вычитаем количество токенов первого пользовательского сообщения (оценивается через tokenizer), получаем чистое количество токенов системного промпта
- Добавляем cache.read (чтение из кэша также считается системными токенами)
Почему остаточный расчет?
API не предоставляет напрямую количество токенов "системного промпта", можно только вычислить его через "ввод первого ответа - первое пользовательское сообщение". Это стандартный метод оценки.
User (токены пользовательских сообщений)
Формула расчета:
user = tokenizer(все пользовательские сообщения)Код реализации (lib/commands/context.ts:110-157):
const userTextParts: string[] = []
for (const msg of messages) {
if (isMessageCompacted(state, msg)) continue
if (msg.info.role === "user" && isIgnoredUserMessage(msg)) continue
const parts = Array.isArray(msg.parts) ? msg.parts : []
for (const part of parts) {
if (part.type === "text" && msg.info.role === "user") {
const textPart = part as TextPart
const text = textPart.text || ""
userTextParts.push(text)
}
}
}
breakdown.user = countTokens(userTextParts.join("\n"))Описание:
- Использует tokenizer для подсчета токенов всех пользовательских сообщений
- Пропускает уже сжатые сообщения (
isMessageCompacted) - Пропускает ignored сообщения (внутренние системные сообщения)
Tools (токены вызовов инструментов)
Формула расчета:
tools = tokenizer(вводыИнструментов + выводыИнструментов) - prunedTokensКод реализации (lib/commands/context.ts:110-167):
const toolInputParts: string[] = []
const toolOutputParts: string[] = []
for (const msg of messages) {
if (isMessageCompacted(state, msg)) continue
const parts = Array.isArray(msg.parts) ? msg.parts : []
for (const part of parts) {
if (part.type === "tool") {
const toolPart = part as ToolPart
breakdown.toolCount++
// Извлечение ввода инструмента
if (toolPart.state?.input) {
const inputStr =
typeof toolPart.state.input === "string"
? toolPart.state.input
: JSON.stringify(toolPart.state.input)
toolInputParts.push(inputStr)
}
// Извлечение вывода инструмента (только для завершенного состояния)
if (toolPart.state?.status === "completed" && toolPart.state?.output) {
const outputStr =
typeof toolPart.state.output === "string"
? toolPart.state.output
: JSON.stringify(toolPart.state.output)
toolOutputParts.push(outputStr)
}
}
}
}
const toolInputTokens = countTokens(toolInputParts.join("\n"))
const toolOutputTokens = countTokens(toolOutputParts.join("\n"))
breakdown.tools = Math.max(0, toolInputTokens + toolOutputTokens - breakdown.prunedTokens)Описание:
- Вычисляет токены ввода и вывода всех инструментов
- Вычитает токены уже обрезанных инструментов (
prunedTokens) - Учитывает только выводы инструментов со статусом
status === "completed"
Assistant (токены сообщений помощника)
Формула расчета:
assistant = total - system - user - toolsКод реализации (lib/commands/context.ts:168-171):
breakdown.assistant = Math.max(
0,
breakdown.total - breakdown.system - breakdown.user - breakdown.tools,
)Принцип:
- Assistant рассчитывается как остаточный расчет (Residual Calculation)
- Из Total вычитаются System, User, Tools, остаток - это Assistant
Почему Assistant включает токены рассуждения?
Если модель сохраняет токены рассуждения (reasoning) в контексте, они относятся к "содержимому, сгенерированному Assistant", поэтому классифицируются в категорию Assistant. Это разумно, так как рассуждение - это "мышление" помощника.
Расчет экономии токенов
Расчет prunedTokens
Сигнатура функции (lib/strategies/utils.ts:49-94):
export const calculateTokensSaved = (
state: SessionState,
messages: WithParts[],
pruneToolIds: string[],
): numberЛогика расчета:
- Перебор всех сообщений
- Поиск обрезанных инструментов (через сопоставление
pruneToolIdsсpart.callID) - Извлечение содержимого ввода и вывода инструментов
- Использование
countTokensдля подсчета токенов - Суммирование токенов всех обрезанных инструментов
Код реализации:
export const calculateTokensSaved = (
state: SessionState,
messages: WithParts[],
pruneToolIds: string[],
): number => {
try {
const contents: string[] = []
for (const msg of messages) {
if (isMessageCompacted(state, msg)) {
continue
}
const parts = Array.isArray(msg.parts) ? msg.parts : []
for (const part of parts) {
if (part.type !== "tool" || !pruneToolIds.includes(part.callID)) {
continue
}
// Обработка особого случая инструмента question
if (part.tool === "question") {
const questions = part.state.input?.questions
if (questions !== undefined) {
const content =
typeof questions === "string" ? questions : JSON.stringify(questions)
contents.push(content)
}
continue
}
// Извлечение вывода завершенных инструментов
if (part.state.status === "completed") {
const content =
typeof part.state.output === "string"
? part.state.output
: JSON.stringify(part.state.output)
contents.push(content)
} else if (part.state.status === "error") {
// Извлечение информации об ошибках инструментов
const content =
typeof part.state.error === "string"
? part.state.error
: JSON.stringify(part.state.error)
contents.push(content)
}
}
}
const tokenCounts: number[] = estimateTokensBatch(contents)
return tokenCounts.reduce((sum, count) => sum + count, 0)
} catch (error: any) {
return 0
}
}Описание:
- Вычисляет только токены обрезанных инструментов
- Для завершенных инструментов вычисляет их вывод
- Для инструментов с ошибками вычисляет информацию об ошибках
- Если вычисление не удалось (исключение), возвращает 0
Диапазон погрешности расчета токенов
Источники погрешности оценки
| Категория | Точность | Источник погрешности | Влияние |
|---|---|---|---|
| Total | 100% | Точное значение API, без погрешности | Нет |
| System | 95-98% | Оценка токенов первого пользовательского сообщения имеет небольшую погрешность | Очень мало |
| User | 90-95% | Tokenizer может отличаться от фактического токенизатора модели | Среднее |
| Tools | 90-95% | Tokenizer + влияние формата сериализации параметров инструментов | Среднее |
| Assistant | 90-95% | Передача погрешности от User и Tools | Среднее |
Контроль погрешности
DCP использует несколько методов для контроля погрешности:
Использование Anthropic Tokenizer:
- Наиболее близко к фактической логике токенизации Claude
- Для OpenAI, Gemini возможны небольшие различия
Пропуск внутренних системных сообщений:
isIgnoredUserMessageфильтрует внутренние сообщения OpenCode- Избегает расчета нерелевантных токенов
Math.max(0, ...):
- Гарантирует, что результат не будет отрицательным
- Предотвращает аномальные ситуации
Наблюдение фактической погрешности
В тестах разница между оценкой токенов DCP и отображением в OpenCode UI обычно составляет ±5%. Для оценки эффекта экономии токенов эта точность достаточна.
Интерпретация вывода /dcp context
График распределения токенов
System 15.2% │████████████████▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ 25.1K tokens
User 5.1% │████▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ 8.4K tokens
Assistant 35.8% │██████████████████████████████████████▒▒▒▒▒▒│ 59.2K tokens
Tools (45) 43.9% │████████████████████████████████████████████████│ 72.6K tokensЗначения элементов:
| Элемент | Описание | Нормальный диапазон |
|---|---|---|
| System | Токены системного промпта | 10-20% |
| User | Токены всех пользовательских сообщений | 3-8% |
| Assistant | Текстовый вывод помощника + токены рассуждения | 30-40% |
| Tools (n) | Токены вызовов инструментов (n = количество инструментов в контексте) | 40-50% |
Интерпретация Summary
Summary:
Pruned: 12 tools (~15.2K tokens)
Current context: ~165.3K tokens
Without DCP: ~180.5K tokensЗначения элементов:
| Элемент | Способ расчета | Описание |
|---|---|---|
| Pruned | state.prune.toolIds.length количество инструментов + prunedTokens | Количество обрезанных инструментов и сэкономленных токенов |
| Current context | breakdown.total (точное значение API) | Фактическое количество токенов текущего контекста |
| Without DCP | total + prunedTokens | Теоретическое количество токенов без DCP |
Расчет коэффициента экономии:
Коэффициент экономии = prunedTokens / Without DCP
= 15.2K / 180.5K
≈ 8.4%Справка по коэффициенту экономии
- Легкое использование: 5-10% (иногда повторное чтение файлов)
- Среднее использование: 10-20% (частая дедупликация, обрезка перезаписи)
- Интенсивное использование: 20-30% (много вызовов инструментов, активная обрезка AI)
Практика: анализ распределения токенов
Сценарий 1: слишком много вызовов инструментов
Проблема: доля Tools превышает 60%, контекст очень большой.
Tools (120) 65.2% │████████████████████████████████████████████████│ 120.5K tokensВозможные причины:
- В диалоге было вызвано много инструментов (например, многократное чтение файлов, поиск)
- Вывод инструментов очень большой (например, чтение длинных файлов)
Рекомендации по оптимизации:
- Включить стратегию дедупликации (
strategies.deduplication.enabled: true) - Включить стратегию перезаписи (
strategies.supersedeWrites.enabled: true) - Использовать
/dcp sweepдля ручной очистки старых инструментов
Сценарий 2: слишком высокая доля Assistant
Проблема: доля Assistant превышает 50%, возможно, это токены рассуждения.
Assistant 52.3% │██████████████████████████████████████████████▒▒▒▒▒▒│ 95.2K tokensВозможные причины:
- Используется модель с рассуждением (например, Claude 3.5)
- Помощник вывел много текста
Рекомендации по оптимизации:
- Токены рассуждения нельзя обрезать (они относятся к сгенерированному содержимому Assistant)
- Можно сократить вывод помощника (заставить AI быть более кратким)
- Проверить наличие избыточных вызовов инструментов (через обрезку дедупликации)
Сценарий 3: низкий коэффициент экономии
Проблема: Pruned очень мал, коэффициент экономии < 5%.
Summary:
Pruned: 2 tools (~1.2K tokens)
Current context: ~165.3K tokens
Without DCP: ~166.5K tokensВозможные причины:
- В диалоге редко встречаются повторяющиеся вызовы инструментов
- Не включены автоматические стратегии (дедупликация, перезапись, очистка ошибок)
- AI не вызывает инструменты
discard/extractпо собственной инициативе
Рекомендации по оптимизации:
- Включить все автоматические стратегии:jsonc
{ "strategies": { "deduplication": { "enabled": true }, "supersedeWrites": { "enabled": true }, "purgeErrors": { "enabled": true } } } - Включить функцию напоминания (
tools.settings.nudgeEnabled: true), чтобы AI активно обрезал
Предупреждения о типичных ошибках
❌ Ошибка 1: ожидание 100% точности
Проблема: ожидать, что расчет токенов полностью совпадет со счетом API.
Реальность: расчет DCP - это оценочное значение, существует погрешность ±5%.
Решение:
- Использовать статистику DCP как относительную ссылку (например, коэффициент экономии)
- Не использовать для точного аудита биллинга
- Счет API - единственная точная запись потребления
❌ Ошибка 2: игнорирование токенов рассуждения
Проблема: токены рассуждения классифицируются в Assistant, но пользователь думает, что они пропущены.
Реальность: токены рассуждения - часть генерации Assistant, классификация разумна.
Решение:
- Токены рассуждения нельзя обрезать (они не относятся к какому-либо инструменту)
- Если доля токенов рассуждения слишком высока (> 30%), можно попробовать:
- Использовать модель без рассуждения
- Упростить задачу, сократить потребность в рассуждении
❌ Ошибка 3: токены кэша не учитываются в Total
Проблема: думать, что cache.read не учитывается в Total, что приводит к недопониманию.
Реальность: cache.read учитывается в Total, но не создает фактических расходов (так как уже кэширован).
Решение:
Total- это размер контекста, а не фактические расходы- Токены с попаданием в кэш все равно занимают место в контексте
- Если нужно оптимизировать расходы, можно посмотреть накопленную экономию в
/dcp stats
❌ Ошибка 4: после обрезки ввода инструмента токены не уменьшаются
Проблема: DCP обрезал ввод инструмента, но Current context в /dcp context не изменился.
Реальность: Current context показывает токены, отправленные в LLM, обрезанное содержимое не включается.
Решение:
Current context=Without DCP-prunedTokens- Если
prunedTokensувеличивается, значит обрезка работает - Если
Current contextне уменьшается, возможно,prunedTokensслишком мал, или новые сообщения добавили токены
Итоги урока
Расчет токенов DCP использует смешанную стратегию:
- Точные значения API: Total, System используют точные значения, возвращаемые API
- Оценка Tokenizer: User, Tools оцениваются через Anthropic Tokenizer
- Остаточный расчет: Assistant вычисляется через Total - System - User - Tools
Ключевые функции:
countTokens(): вычисляет количество токенов текста (приоритет использования Anthropic Tokenizer)calculateTokensSaved(): вычисляет количество сэкономленных токенов обрезанных инструментовanalyzeTokens(): анализирует распределение токенов контекста
Диапазон погрешности:
- Total: 100% точно (значения API)
- Другие категории: 90-98% (оценочные значения)
- Общая погрешность: ±5%
Сценарии использования:
- Использовать
/dcp contextдля просмотра распределения токенов - Оптимизировать конфигурацию стратегий на основе распределения
- Использовать
/dcp statsдля просмотра накопленного эффекта экономии
О следующем уроке
В следующем уроке мы изучим Справочник API.
Вы узнаете:
- Документацию API плагина DCP
- Полное описание параметров конфигурации
- Интерфейсы управления состоянием
- Точки расширения для разработчиков плагинов
Приложение: ссылки на исходный код
Нажмите, чтобы раскрыть расположение исходного кода
Время обновления: 2026-01-23
| Функция | Путь к файлу | Номер строки |
|---|---|---|
| функция countTokens | lib/strategies/utils.ts | 36-43 |
| calculateTokensSaved | lib/strategies/utils.ts | 49-94 |
| функция analyzeTokens | lib/commands/context.ts | 68-174 |
| комментарии к стратегии расчета токенов | lib/commands/context.ts | 5-38 |
| handleContextCommand | lib/commands/context.ts | 238-247 |
Ключевые функции:
countTokens(text: string): number: вычисляет количество токенов текста, использует Anthropic Tokenizer или откат к количеству символов/4calculateTokensSaved(state, messages, pruneToolIds): number: вычисляет количество сэкономленных токенов обрезанных инструментовanalyzeTokens(state, messages): TokenBreakdown: анализирует распределение токенов контекста
Ключевые интерфейсы:
TokenBreakdown: результат анализа распределения токеновtypescriptinterface TokenBreakdown { system: number user: number assistant: number tools: number toolCount: number prunedTokens: number prunedCount: number total: number }