( modelName: string, chatMessage: ChatMessage, )
| 177 | } |
| 178 | |
| 179 | function countChatMessageTokens( |
| 180 | modelName: string, |
| 181 | chatMessage: ChatMessage, |
| 182 | ): number { |
| 183 | // Doing simpler, safer version of what is here: |
| 184 | // https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb |
| 185 | // every message follows <|im_start|>{role/name}\n{content}<|end|>\n |
| 186 | const BASE_TOKENS = 4; |
| 187 | const TOOL_CALL_EXTRA_TOKENS = 10; |
| 188 | const TOOL_OUTPUT_EXTRA_TOKENS = 10; |
| 189 | let tokens = BASE_TOKENS; |
| 190 | |
| 191 | if (chatMessage.content) { |
| 192 | tokens += countTokens(chatMessage.content, modelName); |
| 193 | } |
| 194 | |
| 195 | if ("toolCalls" in chatMessage && chatMessage.toolCalls) { |
| 196 | for (const call of chatMessage.toolCalls) { |
| 197 | tokens += TOOL_CALL_EXTRA_TOKENS; |
| 198 | tokens += countTokens(JSON.stringify(call), modelName); // TODO hone this |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | if (chatMessage.role === "thinking") { |
| 203 | if (chatMessage.redactedThinking) { |
| 204 | tokens += countTokens(chatMessage.redactedThinking, modelName); |
| 205 | } |
| 206 | if (chatMessage.signature) { |
| 207 | tokens += countTokens(chatMessage.signature, modelName); |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | if (chatMessage.role === "tool") { |
| 212 | tokens += TOOL_OUTPUT_EXTRA_TOKENS; // safety |
| 213 | if (chatMessage.toolCallId) { |
| 214 | tokens += countTokens(chatMessage.toolCallId, modelName); |
| 215 | } |
| 216 | } |
| 217 | return tokens; |
| 218 | } |
| 219 | |
| 220 | /** |
| 221 | * Extracts and validates the tool call sequence from the end of a message array. |
no test coverage detected