Появление моделей с управляемым временем размышления (Claude 3.7 Sonnet, OpenAI o-серии) привело к возникновению нового вектора архитектурной оптимизации: Reasoning Token Budgeting. Если в традиционных генеративных моделях стоимость и задержка зависели строго от размера входного контекста и требуемого объема ответа, то в reasoning-системах до 90% вычислительных ресурсов и финансового бюджета расходуется на скрытые промежуточные токены рассуждений (Thinking Tokens).
Ключевой инженерный вызов заключается в строгой нелинейности соотношения между глубиной размышлений, задержкой первого полезного токена (Time To First Token, TTFT) и качеством финального ответа.
Качество решения
▲
100% │ .--- Plateau (Перенасыщение)
│ .-'
│ .-'
50% │ .-'
│ .-'
│ .-'
0% └────────────────┴───────────┴───────────► Бюджет токенов (Tokens)
0 (Disabled) 1024 8192 32768
◄── 150ms ──► ◄─ 1.8s ─► ◄── 9.5s ──► ◄── 35s+ (TTFT Latency)Выделение 8192 токенов на рассуждение для тривиальной задачи (например, валидация DTO или форматирование JSON) не приносит прироста точности, но увеличивает TTFT с 200 миллисекунд до 10 секунд и повышает себестоимость вызова в 15–20 раз. Напротив, ограничение бюджета 512 токенами при анализе гонки потоков в распределенном лок-менеджере приводит к преждевременному прерыванию цепочки верификации и генерации некорректного кода.
Современные API предоставляют различные интерфейсы управления глубиной размышлений:
thinking: { type: "enabled", budget_tokens: N } с минимальным порогом 1024 токена и максимумом до 128k токенов (либо type: "disabled" для мгновенного ответа).reasoning_effort: "low" | "medium" | "high", транслируемый внутренней инференс-системой в скрытые квоты генерации.<end_of_thought>.В архитектуре производственного AI-шлюза бюджетирование reasoning-токенов строится по принципу адаптивного каскада. Запрос анализируется статическим анализатором сложности перед отправкой в инференс-провайдер.
[ Incoming Request (Code / Architecture Task) ]
│
▼
[ Phase 1: Complexity Analyzer (AST / Heuristics / RegEx) ]
├─ Cyclomatic Complexity
├─ Concurrency Primitives (Atomics, Mutex, CAS, Channels)
└─ Distributed Scope (2PC, Saga, Raft, Sharding)
│
▼
┌────────────┴───────────────────────────┐
│ │
Score < 0.30 0.30 <= Score < 0.75 Score >= 0.75
│ │ │
▼ ▼ ▼
[ BUDGET: 0 TOKENS ] [ BUDGET: 1024-2048 TOKENS ] [ BUDGET: 8192-16384 TOKENS ]
- CRUD операции - Сложные React 19 хуки - Распределенный консенсус
- Валидация типов - Оптимизация SQL джойнов - Анализ утечек памяти / GC
- Документация - Преобразование AST - Формальный аудит безопасности
- TTFT: ~200ms - TTFT: ~1.5s - 3.0s - TTFT: ~10s - 25s
│ │ │
└────────────────────────────┬───────────┘ │
│ │
▼ ▼
[ Streaming Inference Engine ] ◄─────────────────────────┘
│
▼
[ Real-time Token Burn Tracker ]
(Прерывание при превышении лимита)budget_tokens: 0) для сохранения задержки менее 150 мс.Ниже представлена реализация производственного диспетчера бюджетов reasoning-токенов AdaptiveReasoningDispatcher на TypeScript. Диспетчер анализирует синтаксические маркеры задачи, вычисляет индекс сложности, динамически формирует конфигурацию запроса для Claude 3.7 Sonnet и осуществляет потоковую передачу с раздельным учетом метрик TTFT и расхода бюджета.
import { z } from "zod";
export const TaskComplexitySchema = z.object({
score: z.number().min(0).max(1),
recommendedBudget: z.number().int().min(0),
reasoningEffort: z.enum(["disabled", "low", "medium", "high"]),
detectedKeywords: z.array(z.string()),
explanation: z.string(),
});
export type TaskComplexity = z.infer<typeof TaskComplexitySchema>;
export interface DispatcherMetrics {
ttftMs: number;
thinkingDurationMs: number;
completionDurationMs: number;
totalDurationMs: number;
thinkingTokensUsed: number;
completionTokensUsed: number;
totalCostUsd: number;
}
export interface StreamEventCallbacks {
onThinkingChunk?: (chunk: string) => void;
onContentChunk?: (chunk: string) => void;
onMetricsCollected?: (metrics: DispatcherMetrics) => void;
}
/**
* Прейскурант Claude 3.7 Sonnet (по состоянию на 2026 год):
* Входные токены: $3.00 / 1M
* Выходные токены (включая thinking tokens): $15.00 / 1M
*/
const PRICING = {
inputCostPerToken: 3.00 / 1_000_000,
outputCostPerToken: 15.00 / 1_000_000,
};
export class AdaptiveReasoningDispatcher {
private readonly apiKey: string;
constructor(apiKey?: string) {
this.apiKey = apiKey || process.env.ANTHROPIC_API_KEY || "";
if (!this.apiKey) {
throw new Error("ANTHROPIC_API_KEY is required for AdaptiveReasoningDispatcher");
}
}
/**
* Эвристический анализатор алгоритмической и архитектурной сложности
*/
public analyzeComplexity(codeContext: string, userInstruction: string): TaskComplexity {
const combinedText = `${codeContext}\n${userInstruction}`.toLowerCase();
// Маркеры высокой сложности (высокий параллелизм, распределенные транзакции, безопасность)
const highComplexityMarkers = [
"deadlock", "race condition", "mutex", "distributed transaction",
"two-phase commit", "2pc", "saga pattern", "raft", "paxos",
"memory leak", "atomic", "cas", "reentrancy", "zero-downtime",
"linearizability", "eventual consistency"
];
// Маркеры средней сложности (бизнес-логика, оптимизация запросов, стейт-менеджмент)
const mediumComplexityMarkers = [
"refactor", "optimization", "sql query", "join", "index",
"state management", "reducer", "usememo", "concurrency",
"data pipeline", "zod schema", "validation", "websocket"
];
const detectedHigh = highComplexityMarkers.filter(m => combinedText.includes(m));
const detectedMedium = mediumComplexityMarkers.filter(m => combinedText.includes(m));
let score = 0.1; // Базовый уровень для тривиальных задач
score += detectedHigh.length * 0.3;
score += detectedMedium.length * 0.15;
score = Math.min(score, 1.0);
let recommendedBudget = 0;
let reasoningEffort: "disabled" | "low" | "medium" | "high" = "disabled";
if (score >= 0.75) {
// Высокая сложность: глубокий анализ race conditions / распределенных систем
recommendedBudget = 8192;
reasoningEffort = "high";
} else if (score >= 0.4) {
// Средняя сложность: комплексная логика, сложные UI-компоненты
recommendedBudget = 2048;
reasoningEffort = "medium";
} else if (score >= 0.25) {
// Умеренная сложность: базовая валидация DTO с нетривиальными правилами
recommendedBudget = 1024;
reasoningEffort = "low";
} else {
// Тривиальная задача: CRUD, базовое форматирование, документация
recommendedBudget = 0;
reasoningEffort = "disabled";
}
return {
score,
recommendedBudget,
reasoningEffort,
detectedKeywords: [...detectedHigh, ...detectedMedium],
explanation: `Complexity score ${score.toFixed(2)} based on markers: ${[...detectedHigh, ...detectedMedium].join(", ") || "none"}`,
};
}
/**
* Потоковый запуск задачи с динамической конфигурацией бюджета
*/
public async executeStreaming(
prompt: string,
systemPrompt: string,
forcedBudget?: number,
callbacks?: StreamEventCallbacks
): Promise<string> {
const complexity = this.analyzeComplexity("", prompt);
const budgetTokens = forcedBudget !== undefined ? forcedBudget : complexity.recommendedBudget;
const isThinkingEnabled = budgetTokens > 0;
// max_tokens должен обязательно превышать budget_tokens
const maxTokens = isThinkingEnabled ? budgetTokens + 4096 : 4096;
const requestBody: Record<string, unknown> = {
model: "claude-3-7-sonnet-20250219",
max_tokens: maxTokens,
messages: [{ role: "user", content: prompt }],
system: systemPrompt,
stream: true,
};
if (isThinkingEnabled) {
requestBody.thinking = {
type: "enabled",
budget_tokens: budgetTokens,
};
}
const startTime = performance.now();
let ttftMs = 0;
let firstTokenReceived = false;
let thinkingDurationMs = 0;
let firstContentTokenTime = 0;
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": this.apiKey,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
},
body: JSON.stringify(requestBody),
});
if (!response.ok || !response.body) {
const errorText = await response.text();
throw new Error(`Anthropic Stream Failure [${response.status}]: ${errorText}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let fullContentText = "";
let thinkingTokensCount = 0;
let completionTokensCount = 0;
let inputTokensCount = 0;
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() || "";
for (const line of lines) {
if (!line.startsWith("data: ")) continue;
const jsonStr = line.slice(6).trim();
if (jsonStr === "[DONE]") continue;
try {
const event = JSON.parse(jsonStr);
// Фиксация времени первого события от API
if (!firstTokenReceived) {
ttftMs = performance.now() - startTime;
firstTokenReceived = true;
}
if (event.type === "content_block_delta") {
const delta = event.delta;
if (delta.type === "thinking_delta") {
callbacks?.onThinkingChunk?.(delta.thinking);
} else if (delta.type === "text_delta") {
if (firstContentTokenTime === 0) {
firstContentTokenTime = performance.now();
thinkingDurationMs = firstContentTokenTime - startTime;
}
fullContentText += delta.text;
callbacks?.onContentChunk?.(delta.text);
}
}
if (event.type === "message_delta" && event.usage) {
completionTokensCount = event.usage.output_tokens || 0;
}
if (event.type === "message_start" && event.message?.usage) {
inputTokensCount = event.message.usage.input_tokens || 0;
}
} catch {
// Игнорирование неполных чанков JSON в стриме
}
}
}
const totalDurationMs = performance.now() - startTime;
const completionDurationMs = firstContentTokenTime > 0 ? totalDurationMs - thinkingDurationMs : totalDurationMs;
const totalCostUsd =
inputTokensCount * PRICING.inputCostPerToken +
completionTokensCount * PRICING.outputCostPerToken;
callbacks?.onMetricsCollected?.({
ttftMs,
thinkingDurationMs,
completionDurationMs,
totalDurationMs,
thinkingTokensUsed: thinkingTokensCount,
completionTokensUsed: completionTokensCount,
totalCostUsd,
});
return fullContentText;
}
}Использование одинакового budget_tokens: 4096 для всех запросов в приложении. На простых сценариях (автодополнение кода, генерация геттеров/сеттеров) пользователи получают неприемлемую задержку (TTFT > 3 секунд), а облачный бюджет на инференс расходуется вхолостую. Бюджет должен динамически адаптироваться под контекст задачи.
Отсутствие стриминга промежуточных событий рассуждения на клиент. Если модель генерирует 8000 токенов рассуждений, HTTP-соединение простаивает без передачи данных 10–20 секунд. Пользователь считает систему зависшей, а промежуточные балансировщики (Cloudflare, AWS ALB) могут сбросить соединение по таймауту (HTTP 504 Gateway Timeout). Необходимо передавать SSE-события или keep-alive пинги.
Передача значений вроде budget_tokens: 256 в Claude 3.7 Sonnet. API возвращает ошибку 400 Bad Request: минимальный допустимый бюджет при включенном thinking составляет 1024 токена. Если требуется меньший объем рассуждений, параметр thinking необходимо полностью отключать ({ type: "disabled" }).
Параметр max_tokens в API ограничивает совокупную сумму: thinking_tokens + completion_tokens <= max_tokens. Если задать budget_tokens: 8192 и max_tokens: 8192, модель потребит весь пул на внутренний CoT и вернет пустой ответ со статусом stop_reason: "max_tokens". Правило: max_tokens = budget_tokens + expected_completion_tokens.
Сканирование AST кода и промпта на наличие примитивов параллелизма и распределенности
Определение категории сложности: Disabled (0), Low (1024), Medium (2048) или High (8192+)
Конфигурация параметров thinking.budget_tokens и max_tokens с запасом под ответ
Трансляция reasoning-чанков через SSE для поддержания сетевого соединения и снижения perceived TTFT
Учет фактически сожженных токенов и калибровка весов эвристического классификатора