@directus/api
Version:
Directus is a real-time API and App dashboard for managing SQL database content
88 lines (87 loc) • 4.03 kB
JavaScript
//#region src/ai/chat/utils/prompt-caching.ts
function buildCacheAwareSystemPrompt(provider, content) {
if (provider !== "anthropic") return content;
return {
role: "system",
content,
providerOptions: { anthropic: { cacheControl: { type: "ephemeral" } } }
};
}
/**
* For Anthropic, place a cache breakpoint on the last existing message so the conversation
* prefix (tools + system + history) caches as it grows, and append the per-request page
* context as a new user message after the breakpoint so it stays out of the cached prefix.
*
* The breakpoint lands on the last message even when it is a tool result — most tool-search
* steps end in one, and skipping those steps would leave the large history prefix uncached.
*
* Context is only appended after a real user turn. On multi-step continuations the last
* message is an assistant or tool result; appending a user message there would make the
* model respond to the context instead of synthesizing the tool output. The model still
* sees context from the originating user turn earlier in the conversation.
*
* Other providers either auto-cache (Google/OpenAI) or don't support this, so we keep the
* context inside the system prompt for them (handled upstream).
*/
function applyAnthropicConversationCaching(provider, messages, contextBlock) {
if (provider !== "anthropic" || messages.length === 0) return messages;
const lastIndex = messages.length - 1;
const messagesWithCache = messages.map((message, index) => index === lastIndex ? {
...message,
providerOptions: {
...message.providerOptions,
anthropic: {
...message.providerOptions?.["anthropic"],
cacheControl: { type: "ephemeral" }
}
}
} : message);
if (!contextBlock || messages[lastIndex]?.role !== "user") return messagesWithCache;
return [...messagesWithCache, {
role: "user",
content: contextBlock
}];
}
function sortToolsByName(tools) {
return Object.fromEntries(Object.entries(tools).sort(([a], [b]) => {
if (a < b) return -1;
if (a > b) return 1;
return 0;
}));
}
function formatUsageWithCacheTokens(result) {
const usage = result.totalUsage ?? result.usage;
const providerMetadata = result.steps?.map((step) => step.providerMetadata) ?? [result.providerMetadata];
const { inputTokens, outputTokens, totalTokens } = usage;
return {
inputTokens,
outputTokens,
totalTokens,
...getCacheTokenUsage(usage, providerMetadata)
};
}
function getCacheTokenUsage(usage, providerMetadata) {
const cacheReadTokens = usage.inputTokenDetails?.cacheReadTokens ?? usage.cachedInputTokens ?? sumNumbers([...providerMetadata.map((metadata) => getProviderMetadataNumber(metadata, "anthropic", "cacheReadInputTokens")), ...providerMetadata.map(getGoogleCachedContentTokenCount)]);
const cacheCreationTokens = usage.inputTokenDetails?.cacheWriteTokens ?? sumNumbers(providerMetadata.map((metadata) => getProviderMetadataNumber(metadata, "anthropic", "cacheCreationInputTokens")));
return {
...cacheReadTokens !== void 0 ? { cacheReadTokens } : {},
...cacheCreationTokens !== void 0 ? { cacheCreationTokens } : {}
};
}
function getProviderMetadataNumber(providerMetadata, providerName, fieldName) {
const value = providerMetadata?.[providerName]?.[fieldName];
return typeof value === "number" ? value : void 0;
}
function getGoogleCachedContentTokenCount(providerMetadata) {
const usageMetadata = providerMetadata?.["google"]?.["usageMetadata"];
if (!usageMetadata || typeof usageMetadata !== "object" || Array.isArray(usageMetadata)) return;
const cachedContentTokenCount = usageMetadata["cachedContentTokenCount"];
return typeof cachedContentTokenCount === "number" ? cachedContentTokenCount : void 0;
}
function sumNumbers(values) {
const definedValues = values.filter((value) => typeof value === "number");
if (definedValues.length === 0) return;
return definedValues.reduce((sum, value) => sum + value, 0);
}
//#endregion
export { applyAnthropicConversationCaching, buildCacheAwareSystemPrompt, formatUsageWithCacheTokens, sortToolsByName };