1
1
import { Anthropic } from "@anthropic-ai/sdk"
2
- import { countTokens } from "@anthropic-ai/tokenizer"
3
- import { Buffer } from "buffer"
4
- import sizeOf from "image-size"
5
-
6
- export function isWithinContextWindow (
7
- contextWindow : number ,
8
- systemPrompt : string ,
9
- tools : Anthropic . Messages . Tool [ ] ,
10
- messages : Anthropic . Messages . MessageParam [ ]
11
- ) : boolean {
12
- const adjustedContextWindow = contextWindow * 0.75 // Buffer to account for tokenizer differences
13
- // counting tokens is expensive, so we first try to estimate before doing a more accurate calculation
14
- const estimatedTotalMessageTokens = countTokens ( systemPrompt + JSON . stringify ( tools ) + JSON . stringify ( messages ) )
15
- if ( estimatedTotalMessageTokens <= adjustedContextWindow ) {
16
- return true
17
- }
18
- const systemPromptTokens = countTokens ( systemPrompt )
19
- const toolsTokens = countTokens ( JSON . stringify ( tools ) )
20
- let availableTokens = adjustedContextWindow - systemPromptTokens - toolsTokens
21
- let accurateTotalMessageTokens = messages . reduce ( ( sum , message ) => sum + countMessageTokens ( message ) , 0 )
22
- return accurateTotalMessageTokens <= availableTokens
23
- }
24
2
25
3
/*
26
4
We can't implement a dynamically updating sliding window as it would break prompt cache
@@ -46,54 +24,3 @@ export function truncateHalfConversation(
46
24
47
25
return truncatedMessages
48
26
}
49
-
50
- function countMessageTokens ( message : Anthropic . Messages . MessageParam ) : number {
51
- if ( typeof message . content === "string" ) {
52
- return countTokens ( message . content )
53
- } else if ( Array . isArray ( message . content ) ) {
54
- return message . content . reduce ( ( sum , item ) => {
55
- if ( typeof item === "string" ) {
56
- return sum + countTokens ( item )
57
- } else if ( item . type === "text" ) {
58
- return sum + countTokens ( item . text )
59
- } else if ( item . type === "image" ) {
60
- return sum + estimateImageTokens ( item . source . data )
61
- } else if ( item . type === "tool_use" ) {
62
- return sum + countTokens ( JSON . stringify ( item . input ) )
63
- } else if ( item . type === "tool_result" ) {
64
- if ( Array . isArray ( item . content ) ) {
65
- return (
66
- sum +
67
- item . content . reduce ( ( contentSum , contentItem ) => {
68
- if ( contentItem . type === "text" ) {
69
- return contentSum + countTokens ( contentItem . text )
70
- } else if ( contentItem . type === "image" ) {
71
- return contentSum + estimateImageTokens ( contentItem . source . data )
72
- }
73
- return contentSum + countTokens ( JSON . stringify ( contentItem ) )
74
- } , 0 )
75
- )
76
- } else {
77
- return sum + countTokens ( item . content || "" )
78
- }
79
- } else {
80
- return sum + countTokens ( JSON . stringify ( item ) )
81
- }
82
- } , 0 )
83
- } else {
84
- return countTokens ( JSON . stringify ( message . content ) )
85
- }
86
- }
87
-
88
- function estimateImageTokens ( base64 : string ) : number {
89
- const base64Data = base64 . split ( ";base64," ) . pop ( )
90
- if ( base64Data ) {
91
- const buffer = Buffer . from ( base64Data , "base64" )
92
- const dimensions = sizeOf ( buffer )
93
- if ( dimensions . width && dimensions . height ) {
94
- // "you can estimate the number of tokens used through this algorithm: tokens = (width px * height px)/750"
95
- return Math . ceil ( ( dimensions . width * dimensions . height ) / 750 )
96
- }
97
- }
98
- return countTokens ( base64 )
99
- }
0 commit comments