Add optional Claude system prompt cache.
| @@ -41,6 +41,11 @@ enableCorsProxy: false | |||
| 41 | enableUserAccounts: false | 41 | enableUserAccounts: false |
| 42 | # Enable discreet login mode: hides user list on the login screen | 42 | # Enable discreet login mode: hides user list on the login screen |
| 43 | enableDiscreetLogin: false | 43 | enableDiscreetLogin: false |
| 44 | # User session timeout *in seconds* (defaults to 24 hours). | ||
| 45 | ## Set to a positive number to expire session after a certain time of inactivity | ||
| 46 | ## Set to 0 to expire session when the browser is closed | ||
| 47 | ## Set to a negative number to disable session expiration | ||
| 48 | sessionTimeout: 86400 | ||
| 44 | # Used to sign session cookies. Will be auto-generated if not set | 49 | # Used to sign session cookies. Will be auto-generated if not set |
| 45 | cookieSecret: '' | 50 | cookieSecret: '' |
| 46 | # Disable CSRF protection - NOT RECOMMENDED | 51 | # Disable CSRF protection - NOT RECOMMENDED |
| @@ -123,10 +128,14 @@ ollama: | |||
| 123 | # * 0: Unload the model immediately after the request | 128 | # * 0: Unload the model immediately after the request |
| 124 | # * 5m: Keep the model loaded for 5 minutes after the request. Accepts duration strings (e.g. 5h30m40s) | 129 | # * 5m: Keep the model loaded for 5 minutes after the request. Accepts duration strings (e.g. 5h30m40s) |
| 125 | keepAlive: -1 | 130 | keepAlive: -1 |
| 131 | # -- ANTHROPIC CLAUDE API CONFIGURATION -- | ||
| 132 | claude: | ||
| 133 | # Enables caching of the system prompt (if supported). | ||
| 134 | # https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching | ||
| 135 | # -- IMPORTANT! -- | ||
| 136 | # Use only when the prompt before the chat history is static and doesn't change between requests | ||
| 137 | # (e.g {{random}} macro or lorebooks not as in-chat injections). | ||
| 138 | # Otherwise, you'll just waste money on cache misses. | ||
| 139 | enableSystemPromptCache: false | ||
| 126 | # -- SERVER PLUGIN CONFIGURATION -- | 140 | # -- SERVER PLUGIN CONFIGURATION -- |
| 127 | enableServerPlugins: false | 141 | enableServerPlugins: false |
| 128 | # User session timeout *in seconds* (defaults to 24 hours). | ||
| 129 | ## Set to a positive number to expire session after a certain time of inactivity | ||
| 130 | ## Set to 0 to expire session when the browser is closed | ||
| 131 | ## Set to a negative number to disable session expiration | ||
| 132 | sessionTimeout: 86400 | ||
| @@ -105,6 +105,7 @@ async function sendClaudeRequest(request, response) { | |||
| 105 | const apiUrl = new URL(request.body.reverse_proxy || API_CLAUDE).toString(); | 105 | const apiUrl = new URL(request.body.reverse_proxy || API_CLAUDE).toString(); |
| 106 | const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.CLAUDE); | 106 | const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.CLAUDE); |
| 107 | const divider = '-'.repeat(process.stdout.columns); | 107 | const divider = '-'.repeat(process.stdout.columns); |
| 108 | const enableSystemPromptCache = getConfigValue('claude.enableSystemPromptCache', false); | ||
| 108 | 109 | ||
| 109 | if (!apiKey) { | 110 | if (!apiKey) { |
| 110 | console.log(color.red(`Claude API key is missing.\n${divider}`)); | 111 | console.log(color.red(`Claude API key is missing.\n${divider}`)); |
| @@ -118,8 +119,8 @@ async function sendClaudeRequest(request, response) { | |||
| 118 | controller.abort(); | 119 | controller.abort(); |
| 119 | }); | 120 | }); |
| 120 | const additionalHeaders = {}; | 121 | const additionalHeaders = {}; |
| 121 | let use_system_prompt = (request.body.model.startsWith('claude-2') || request.body.model.startsWith('claude-3')) && request.body.claude_use_sysprompt; | 122 | const useSystemPrompt = (request.body.model.startsWith('claude-2') || request.body.model.startsWith('claude-3')) && request.body.claude_use_sysprompt; |
| 122 | let converted_prompt = convertClaudeMessages(request.body.messages, request.body.assistant_prefill, use_system_prompt, request.body.human_sysprompt_message, request.body.char_name, request.body.user_name); | 123 | const convertedPrompt = convertClaudeMessages(request.body.messages, request.body.assistant_prefill, useSystemPrompt, request.body.human_sysprompt_message, request.body.char_name, request.body.user_name); |
| 123 | // Add custom stop sequences | 124 | // Add custom stop sequences |
| 124 | const stopSequences = []; | 125 | const stopSequences = []; |
| 125 | if (Array.isArray(request.body.stop)) { | 126 | if (Array.isArray(request.body.stop)) { |
| @@ -127,7 +128,7 @@ async function sendClaudeRequest(request, response) { | |||
| 127 | } | 128 | } |
| 128 | 129 | ||
| 129 | const requestBody = { | 130 | const requestBody = { |
| 130 | messages: converted_prompt.messages, | 131 | messages: convertedPrompt.messages, |
| 131 | model: request.body.model, | 132 | model: request.body.model, |
| 132 | max_tokens: request.body.max_tokens, | 133 | max_tokens: request.body.max_tokens, |
| 133 | stop_sequences: stopSequences, | 134 | stop_sequences: stopSequences, |
| @@ -136,13 +137,15 @@ async function sendClaudeRequest(request, response) { | |||
| 136 | top_k: request.body.top_k, | 137 | top_k: request.body.top_k, |
| 137 | stream: request.body.stream, | 138 | stream: request.body.stream, |
| 138 | }; | 139 | }; |
| 139 | if (use_system_prompt) { | 140 | if (useSystemPrompt) { |
| 140 | requestBody.system = converted_prompt.systemPrompt; | 141 | requestBody.system = enableSystemPromptCache |
| 142 | ? [{ type: 'text', text: convertedPrompt.systemPrompt, cache_control: { type: 'ephemeral' } }] | ||
| 143 | : convertedPrompt.systemPrompt; | ||
| 141 | } | 144 | } |
| 142 | if (Array.isArray(request.body.tools) && request.body.tools.length > 0) { | 145 | if (Array.isArray(request.body.tools) && request.body.tools.length > 0) { |
| 143 | // Claude doesn't do prefills on function calls, and doesn't allow empty messages | 146 | // Claude doesn't do prefills on function calls, and doesn't allow empty messages |
| 144 | if (converted_prompt.messages.length && converted_prompt.messages[converted_prompt.messages.length - 1].role === 'assistant') { | 147 | if (convertedPrompt.messages.length && convertedPrompt.messages[convertedPrompt.messages.length - 1].role === 'assistant') { |
| 145 | converted_prompt.messages.push({ role: 'user', content: '.' }); | 148 | convertedPrompt.messages.push({ role: 'user', content: '.' }); |
| 146 | } | 149 | } |
| 147 | additionalHeaders['anthropic-beta'] = 'tools-2024-05-16'; | 150 | additionalHeaders['anthropic-beta'] = 'tools-2024-05-16'; |
| 148 | requestBody.tool_choice = { type: request.body.tool_choice === 'required' ? 'any' : 'auto' }; | 151 | requestBody.tool_choice = { type: request.body.tool_choice === 'required' ? 'any' : 'auto' }; |
| @@ -151,6 +154,9 @@ async function sendClaudeRequest(request, response) { | |||
| 151 | .map(tool => tool.function) | 154 | .map(tool => tool.function) |
| 152 | .map(fn => ({ name: fn.name, description: fn.description, input_schema: fn.parameters })); | 155 | .map(fn => ({ name: fn.name, description: fn.description, input_schema: fn.parameters })); |
| 153 | } | 156 | } |
| 157 | if (enableSystemPromptCache) { | ||
| 158 | additionalHeaders['anthropic-beta'] = 'prompt-caching-2024-07-31'; | ||
| 159 | } | ||
| 154 | console.log('Claude request:', requestBody); | 160 | console.log('Claude request:', requestBody); |
| 155 | 161 | ||
| 156 | const generateResponse = await fetch(apiUrl + '/messages', { | 162 | const generateResponse = await fetch(apiUrl + '/messages', { |