fix: caching system prompt functionality for OpenRouter Claude (#4872)
Signed| @@ -45,6 +45,7 @@ import { | |||
| 45 | addAssistantPrefix, | 45 | addAssistantPrefix, |
| 46 | embedOpenRouterMedia, | 46 | embedOpenRouterMedia, |
| 47 | addReasoningContentToToolCalls, | 47 | addReasoningContentToToolCalls, |
| 48 | cachingSystemPromptForOpenRouterClaude, | ||
| 48 | } from '../../prompt-converters.js'; | 49 | } from '../../prompt-converters.js'; |
| 49 | 50 | ||
| 50 | import { readSecret, SECRET_KEYS } from '../secrets.js'; | 51 | import { readSecret, SECRET_KEYS } from '../secrets.js'; |
| @@ -2041,15 +2042,23 @@ router.post('/generate', function (request, response) { | |||
| 2041 | }; | 2042 | }; |
| 2042 | } | 2043 | } |
| 2043 | 2044 | ||
| 2045 | const enableSystemPromptCache = getConfigValue('claude.enableSystemPromptCache', false, 'boolean'); | ||
| 2044 | const cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1, 'number'); | 2046 | const cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1, 'number'); |
| 2045 | const isClaude3or4 = /anthropic\/claude-(3|opus-4|sonnet-4|haiku-4)/.test(request.body.model); | 2047 | const isClaude3or4 = /anthropic\/claude-(3|opus-4|sonnet-4|haiku-4)/.test(request.body.model); |
| 2046 | const cacheTTL = getConfigValue('claude.extendedTTL', false, 'boolean') ? '1h' : '5m'; | 2048 | const cacheTTL = getConfigValue('claude.extendedTTL', false, 'boolean') ? '1h' : '5m'; |
| 2047 | if (Array.isArray(request.body.messages)) { | 2049 | if (Array.isArray(request.body.messages)) { |
| 2048 | embedOpenRouterMedia(request.body.messages); | 2050 | embedOpenRouterMedia(request.body.messages); |
| 2049 | if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0 && isClaude3or4) { | 2051 | |
| 2052 | if (isClaude3or4) { | ||
| 2053 | if (enableSystemPromptCache) { | ||
| 2054 | cachingSystemPromptForOpenRouterClaude(request.body.messages, cacheTTL); | ||
| 2055 | } | ||
| 2056 | |||
| 2057 | if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0) { | ||
| 2050 | cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth, cacheTTL); | 2058 | cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth, cacheTTL); |
| 2051 | } | 2059 | } |
| 2052 | } | 2060 | } |
| 2061 | } | ||
| 2053 | 2062 | ||
| 2054 | const isGemini = /google\/gemini/.test(request.body.model); | 2063 | const isGemini = /google\/gemini/.test(request.body.model); |
| 2055 | if (isGemini) { | 2064 | if (isGemini) { |
| @@ -1027,6 +1027,51 @@ export function cachingAtDepthForOpenRouterClaude(messages, cachingAtDepth, ttl) | |||
| 1027 | } | 1027 | } |
| 1028 | 1028 | ||
| 1029 | /** | 1029 | /** |
| 1030 | * Adds cache_control to the system prompt for OpenRouter Claude requests. | ||
| 1031 | * | ||
| 1032 | * @param {object[]} messages Array of messages | ||
| 1033 | * @param {string} ttl TTL value | ||
| 1034 | */ | ||
| 1035 | export function cachingSystemPromptForOpenRouterClaude(messages, ttl) { | ||
| 1036 | if (!Array.isArray(messages) || messages.length === 0) { | ||
| 1037 | return; | ||
| 1038 | } | ||
| 1039 | |||
| 1040 | // Find the first system message | ||
| 1041 | const systemMessage = messages.find(msg => msg.role === 'system'); | ||
| 1042 | if (!systemMessage) { | ||
| 1043 | return; | ||
| 1044 | } | ||
| 1045 | |||
| 1046 | // Check if it already has cache_control (at message level) | ||
| 1047 | if (systemMessage.cache_control) { | ||
| 1048 | return; | ||
| 1049 | } | ||
| 1050 | |||
| 1051 | if (Array.isArray(systemMessage.content)) { | ||
| 1052 | const hasExistingCacheControl = systemMessage.content.some(part => part?.cache_control); | ||
| 1053 | if (hasExistingCacheControl) { | ||
| 1054 | return; | ||
| 1055 | } | ||
| 1056 | |||
| 1057 | for (let i = systemMessage.content.length - 1; i >= 0; i--) { | ||
| 1058 | if (systemMessage.content[i]?.type === 'text') { | ||
| 1059 | systemMessage.content[i].cache_control = { type: 'ephemeral', ttl }; | ||
| 1060 | return; | ||
| 1061 | } | ||
| 1062 | } | ||
| 1063 | } else if (typeof systemMessage.content === 'string') { | ||
| 1064 | systemMessage.content = [ | ||
| 1065 | { | ||
| 1066 | type: 'text', | ||
| 1067 | text: systemMessage.content, | ||
| 1068 | cache_control: { type: 'ephemeral', ttl }, | ||
| 1069 | }, | ||
| 1070 | ]; | ||
| 1071 | } | ||
| 1072 | } | ||
| 1073 | |||
| 1074 | /** | ||
| 1030 | * Calculate the Claude budget tokens for a given reasoning effort. | 1075 | * Calculate the Claude budget tokens for a given reasoning effort. |
| 1031 | * @param {number} maxTokens Maximum tokens | 1076 | * @param {number} maxTokens Maximum tokens |
| 1032 | * @param {string} reasoningEffort Reasoning effort | 1077 | * @param {string} reasoningEffort Reasoning effort |