Skip to content

Commit 948e3f7

Browse files
authored
feat: Anthropic plugin system prompt caching (#176)
2 parents 937ab51 + c60d5a5 commit 948e3f7

File tree

3 files changed

+71
-10
lines changed

3 files changed

+71
-10
lines changed

plugins/anthropic/src/claude.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,40 @@ describe('toAnthropicRequestBody', () => {
813813
'Only text output format is supported for Claude models currently'
814814
);
815815
});
816+
817+
it('should apply system prompt caching when enabled', () => {
818+
const request: GenerateRequest<typeof AnthropicConfigSchema> = {
819+
messages: [
820+
{ role: 'system', content: [{ text: 'You are a helpful assistant' }] },
821+
{ role: 'user', content: [{ text: 'Hi' }] },
822+
],
823+
output: { format: 'text' },
824+
};
825+
826+
// Test with caching enabled
827+
const outputWithCaching = toAnthropicRequestBody(
828+
'claude-3-haiku',
829+
request,
830+
false,
831+
true
832+
);
833+
expect(outputWithCaching.system).toEqual([
834+
{
835+
type: 'text',
836+
text: 'You are a helpful assistant',
837+
cache_control: { type: 'ephemeral' },
838+
},
839+
]);
840+
841+
// Test with caching disabled
842+
const outputWithoutCaching = toAnthropicRequestBody(
843+
'claude-3-haiku',
844+
request,
845+
false,
846+
false
847+
);
848+
expect(outputWithoutCaching.system).toBe('You are a helpful assistant');
849+
});
816850
});
817851

818852
describe('claudeRunner', () => {

plugins/anthropic/src/claude.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -420,20 +420,31 @@ export function fromAnthropicResponse(response: Message): GenerateResponseData {
420420
* @param modelName The name of the Anthropic model to use.
421421
* @param request The Genkit GenerateRequest to convert.
422422
* @param stream Whether to stream the response.
423+
* @param cacheSystemPrompt Whether to cache the system prompt.
423424
* @returns The converted Anthropic API request body.
424425
* @throws An error if the specified model is not supported or if an unsupported output format is requested.
425426
*/
426427
export function toAnthropicRequestBody(
427428
modelName: string,
428429
request: GenerateRequest<typeof AnthropicConfigSchema>,
429-
stream?: boolean
430+
stream?: boolean,
431+
cacheSystemPrompt?: boolean
430432
): MessageCreateParams {
431433
const model = SUPPORTED_CLAUDE_MODELS[modelName];
432434
if (!model) throw new Error(`Unsupported model: ${modelName}`);
433435
const { system, messages } = toAnthropicMessages(request.messages);
434436
const mappedModelName = request.config?.version ?? model.version ?? modelName;
435437
const body: MessageCreateParams = {
436-
system,
438+
system: cacheSystemPrompt
439+
? [
440+
{
441+
type: 'text',
442+
text: system,
443+
// @ts-expect-error cache_control is in beta
444+
cache_control: { type: 'ephemeral' },
445+
},
446+
]
447+
: system,
437448
messages,
438449
tools: request.tools?.map(toAnthropicTool),
439450
max_tokens: request.config?.maxOutputTokens ?? 4096,
@@ -463,15 +474,25 @@ export function toAnthropicRequestBody(
463474
* Creates the runner used by Genkit to interact with the Claude model.
464475
* @param name The name of the Claude model.
465476
* @param client The Anthropic client instance.
477+
* @param cacheSystemPrompt Whether to cache the system prompt.
466478
* @returns The runner that Genkit will call when the model is invoked.
467479
*/
468-
export function claudeRunner(name: string, client: Anthropic) {
480+
export function claudeRunner(
481+
name: string,
482+
client: Anthropic,
483+
cacheSystemPrompt?: boolean
484+
) {
469485
return async (
470486
request: GenerateRequest<typeof AnthropicConfigSchema>,
471487
streamingCallback?: StreamingCallback<GenerateResponseChunkData>
472488
): Promise<GenerateResponseData> => {
473489
let response: Message;
474-
const body = toAnthropicRequestBody(name, request, !!streamingCallback);
490+
const body = toAnthropicRequestBody(
491+
name,
492+
request,
493+
!!streamingCallback,
494+
cacheSystemPrompt
495+
);
475496
if (streamingCallback) {
476497
const stream = client.messages.stream(body);
477498
for await (const chunk of stream) {
@@ -497,7 +518,8 @@ export function claudeRunner(name: string, client: Anthropic) {
497518
export function claudeModel(
498519
ai: Genkit,
499520
name: string,
500-
client: Anthropic
521+
client: Anthropic,
522+
cacheSystemPrompt?: boolean
501523
): ModelAction<typeof AnthropicConfigSchema> {
502524
const modelId = `anthropic/${name}`;
503525
const model = SUPPORTED_CLAUDE_MODELS[name];
@@ -509,6 +531,6 @@ export function claudeModel(
509531
...model.info,
510532
configSchema: model.configSchema,
511533
},
512-
claudeRunner(name, client)
534+
claudeRunner(name, client, cacheSystemPrompt)
513535
);
514536
}

plugins/anthropic/src/index.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export { claude35Sonnet, claude3Opus, claude3Sonnet, claude3Haiku };
3131

3232
export interface PluginOptions {
3333
apiKey?: string;
34+
cacheSystemPrompt?: boolean;
3435
}
3536

3637
/**
@@ -48,15 +49,15 @@ export interface PluginOptions {
4849
* - anthropic: The main plugin function to interact with the Anthropic AI.
4950
*
5051
* Usage:
51-
* To use the Claude models, initialize the anthropic plugin inside `configureGenkit` and pass the configuration options. If no API key is provided in the options, the environment variable `ANTHROPIC_API_KEY` must be set.
52+
* To use the Claude models, initialize the anthropic plugin inside `configureGenkit` and pass the configuration options. If no API key is provided in the options, the environment variable `ANTHROPIC_API_KEY` must be set. If you want to cache the system prompt, set `cacheSystemPrompt` to `true`. **Note:** Prompt caching is in beta and may change. To learn more, see https://docs.anthropic.com/en/docs/prompt-caching.
5253
*
5354
* Example:
5455
* ```
5556
* import anthropic from 'genkitx-anthropic';
5657
*
5758
* export default configureGenkit({
5859
* plugins: [
59-
* anthropic({ apiKey: 'your-api-key' })
60+
* anthropic({ apiKey: 'your-api-key', cacheSystemPrompt: false })
6061
* ... // other plugins
6162
* ]
6263
* });
@@ -71,10 +72,14 @@ export const anthropic = (options?: PluginOptions) =>
7172
'Please pass in the API key or set the ANTHROPIC_API_KEY environment variable'
7273
);
7374
}
74-
const client = new Anthropic({ apiKey });
75+
let defaultHeaders = {};
76+
if (options?.cacheSystemPrompt == true) {
77+
defaultHeaders['anthropic-beta'] = 'prompt-caching-2024-07-31';
78+
}
79+
const client = new Anthropic({ apiKey, defaultHeaders });
7580

7681
for (const name of Object.keys(SUPPORTED_CLAUDE_MODELS)) {
77-
claudeModel(ai, name, client);
82+
claudeModel(ai, name, client, options?.cacheSystemPrompt);
7883
}
7984
});
8085

0 commit comments

Comments
 (0)