Add optional Claude system prompt cache.

7322dd1954fa20c08a0d370515407ea002bf3fea

Cohee <18619528+Cohee1207@users.noreply.github.com>

2 files changed, +27 -12Showing whitespace changes
default/config.yaml+14 -5
@@ -41,6 +41,11 @@ enableCorsProxy: false
41enableUserAccounts: false41enableUserAccounts: false
42# Enable discreet login mode: hides user list on the login screen42# Enable discreet login mode: hides user list on the login screen
43enableDiscreetLogin: false43enableDiscreetLogin: 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
48sessionTimeout: 86400
44# Used to sign session cookies. Will be auto-generated if not set49# Used to sign session cookies. Will be auto-generated if not set
45cookieSecret: ''50cookieSecret: ''
46# Disable CSRF protection - NOT RECOMMENDED51# Disable CSRF protection - NOT RECOMMENDED
@@ -123,10 +128,14 @@ ollama:
123 # * 0: Unload the model immediately after the request128 # * 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: -1130 keepAlive: -1
131# -- ANTHROPIC CLAUDE API CONFIGURATION --
132claude:
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 --
127enableServerPlugins: false141enableServerPlugins: 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
132sessionTimeout: 86400
src/endpoints/backends/chat-completions.js+13 -7
@@ -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);
108109
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 sequences124 // 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 }
128129
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 messages146 // 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);
155161
156 const generateResponse = await fetch(apiUrl + '/messages', {162 const generateResponse = await fetch(apiUrl + '/messages', {