Simple implementation of caching at depth that should be useful for most use cases
| @@ -168,5 +168,12 @@ claude: | |||
| 168 | # (e.g {{random}} macro or lorebooks not as in-chat injections). | 168 | # (e.g {{random}} macro or lorebooks not as in-chat injections). |
| 169 | # Otherwise, you'll just waste money on cache misses. | 169 | # Otherwise, you'll just waste money on cache misses. |
| 170 | enableSystemPromptCache: false | 170 | enableSystemPromptCache: false |
| 171 | # Enables caching of the message history at depth (if supported). | ||
| 172 | # https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching | ||
| 173 | # -- IMPORTANT! -- | ||
| 174 | # Use with caution. Behavior may be unpredictable and no guarantees can or will be made. | ||
| 175 | # Set to an integer to specify the desired depth. 0 (which does NOT include the prefill) | ||
| 176 | # should be ideal for most use cases. | ||
| 177 | cachingAtDepth: false | ||
| 171 | # -- SERVER PLUGIN CONFIGURATION -- | 178 | # -- SERVER PLUGIN CONFIGURATION -- |
| 172 | enableServerPlugins: false | 179 | enableServerPlugins: false |
| @@ -80,6 +80,7 @@ async function sendClaudeRequest(request, response) { | |||
| 80 | const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.CLAUDE); | 80 | const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.CLAUDE); |
| 81 | const divider = '-'.repeat(process.stdout.columns); | 81 | const divider = '-'.repeat(process.stdout.columns); |
| 82 | const enableSystemPromptCache = getConfigValue('claude.enableSystemPromptCache', false) && request.body.model.startsWith('claude-3'); | 82 | const enableSystemPromptCache = getConfigValue('claude.enableSystemPromptCache', false) && request.body.model.startsWith('claude-3'); |
| 83 | let cachingAtDepth = getConfigValue('claude.cachingAtDepth', false) && request.body.model.startsWith('claude-3'); | ||
| 83 | 84 | ||
| 84 | if (!apiKey) { | 85 | if (!apiKey) { |
| 85 | console.log(color.red(`Claude API key is missing.\n${divider}`)); | 86 | console.log(color.red(`Claude API key is missing.\n${divider}`)); |
| @@ -138,9 +139,25 @@ async function sendClaudeRequest(request, response) { | |||
| 138 | requestBody.tools[requestBody.tools.length - 1]['cache_control'] = { type: 'ephemeral' }; | 139 | requestBody.tools[requestBody.tools.length - 1]['cache_control'] = { type: 'ephemeral' }; |
| 139 | } | 140 | } |
| 140 | } | 141 | } |
| 141 | if (enableSystemPromptCache) { | 142 | |
| 143 | if (cachingAtDepth !== false) { | ||
| 144 | // There are extremely few scenarios in which caching the prefill is a good idea, it mostly just breaks everything | ||
| 145 | const messageCount = convertedPrompt.messages.length; | ||
| 146 | cachingAtDepth += convertedPrompt.messages[messageCount - 1].role === 'assistant' ? 1 : 0; | ||
| 147 | |||
| 148 | if (messageCount - 1 - cachingAtDepth >= 0) { | ||
| 149 | convertedPrompt.messages[messageCount - 1 - cachingAtDepth]['cache_control'] = { type: 'ephemeral' }; | ||
| 150 | } | ||
| 151 | |||
| 152 | if (messageCount - 1 - cachingAtDepth - 2 >= 0) { | ||
| 153 | convertedPrompt.messages[messageCount - 1 - cachingAtDepth - 2]['cache_control'] = { type: 'ephemeral' }; | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | if (enableSystemPromptCache || cachingAtDepth !== false) { | ||
| 142 | additionalHeaders['anthropic-beta'] = 'prompt-caching-2024-07-31'; | 158 | additionalHeaders['anthropic-beta'] = 'prompt-caching-2024-07-31'; |
| 143 | } | 159 | } |
| 160 | |||
| 144 | console.log('Claude request:', requestBody); | 161 | console.log('Claude request:', requestBody); |
| 145 | 162 | ||
| 146 | const generateResponse = await fetch(apiUrl + '/messages', { | 163 | const generateResponse = await fetch(apiUrl + '/messages', { |