返回源码地图

packages/llm/token-meter/src/estimate.ts

main snapshot · da00f7f5358f · 正文引用章节 09;完整原文可核对,不声称全文件人工逐行审计

完整原文供逐行核对;页面收录不代表每行都经过人工语义审核。MIT 许可见 许可证。

1/**
2 * Fixed-density heuristic token pricing shared by the meter service and the
3 * pure context-breakdown projection, so both surfaces price identical content
4 * to identical numbers.
5 *
6 * @module @deepseek-ai/dsh-token-meter/estimate
7 */
8
9import type { ContentBlock, Message } from '@deepseek-ai/dsh-llm'
10import type { EpochHeader } from '@deepseek-ai/dsh-session'
11
12/** Fixed text-density estimate used until exact tokenization is needed. */
13const CHARS_PER_TOKEN = 4
14
15/** Per-block structural overhead for JSON framing and type tags. */
16const BLOCK_OVERHEAD = 4
17
18/** Role-field framing overhead added to every priced message. */
19export const ROLE_OVERHEAD = 4
20
21/**
22 * Structural JSON price of one block outside the typed pricing arms: the
23 * fixed heuristic for merge-extended blocks and for image references, whose
24 * request price is route-owned rather than fixed. Image offload marks do not
25 * change this reference-only heuristic; route pricing owns their placeholders.
26 * @param block - block to price without mutation.
27 * @returns heuristic tokens for the block's JSON structure.
28 */
29export function estimateStructuralBlock(block: ContentBlock): number {
30 if (block.type === 'image') {
31 const { offloaded: _offloaded, ...reference } = block
32 return BLOCK_OVERHEAD + Math.ceil(JSON.stringify(reference).length / CHARS_PER_TOKEN)
33 }
34 return BLOCK_OVERHEAD + Math.ceil(JSON.stringify(block).length / CHARS_PER_TOKEN)
35}
36
37/**
38 * Price content blocks recursively under the fixed density heuristic.
39 * @param blocks - content blocks to price without mutation.
40 * @returns heuristic tokens including per-block structural overhead.
41 */
42export function estimateContent(blocks: readonly ContentBlock[]): number {
43 let tokens = 0
44 for (const block of blocks) {
45 switch (block.type) {
46 case 'text':
47 case 'reasoning':
48 tokens += Math.ceil(block.text.length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD
49 break
50 case 'tool-call':
51 tokens += Math.ceil(block.name.length / CHARS_PER_TOKEN)
52 + Math.ceil(block.arguments.length / CHARS_PER_TOKEN)
53 + BLOCK_OVERHEAD
54 break
55 default:
56 // ContentBlockMap is merge-extensible; unknown blocks (and image
57 // references, whose request price is route-owned) retain a
58 // conservative structural JSON price under the fixed heuristic.
59 tokens += estimateStructuralBlock(block)
60 }
61 }
62 return tokens
63}
64
65/**
66 * Price the rendered system prompt: the `system/message` surface node's text.
67 * Adapters serialize the prompt as a plain string — a system-role message or
68 * the request's system field — not as a typed content block, so the price is
69 * text density plus role framing with no per-block overhead.
70 * @param message - system-role message to price without mutation.
71 * @returns heuristic system-prompt tokens; 0 for empty content ("no system prompt").
72 */
73export function estimateSystemMessage(message: Message): number {
74 if (message.content.length === 0) return 0
75 let characters = 0
76 for (const block of message.content) {
77 characters += block.type === 'text' ? block.text.length : JSON.stringify(block).length
78 }
79 return Math.ceil(characters / CHARS_PER_TOKEN) + ROLE_OVERHEAD
80}
81
82/**
83 * Heuristically price one model-visible message.
84 * @param message - message to price without mutation.
85 * @returns content and role-framing tokens under the fixed heuristic; a
86 * system-role message prices as {@link estimateSystemMessage}.
87 */
88export function estimateMessage(message: Message): number {
89 if (message.role === 'system') return estimateSystemMessage(message)
90 return estimateContent(message.content) + ROLE_OVERHEAD
91}
92
93/**
94 * Price the tool-schema part of a canonical request envelope — the envelope's
95 * only priced field, since the system prompt is a surface node.
96 * @param header - canonical envelope, or undefined before any request.
97 * @returns heuristic tool-schema tokens; 0 when absent or empty.
98 */
99export function estimateToolsTokens(header: EpochHeader | undefined): number {
100 if (header?.tools === undefined || header.tools.length === 0) return 0
101 return Math.ceil(JSON.stringify(header.tools).length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD
102}