diff --git a/.changeset/kind-birds-sniff.md b/.changeset/kind-birds-sniff.md new file mode 100644 index 000000000000..c49f7c5bb0d9 --- /dev/null +++ b/.changeset/kind-birds-sniff.md @@ -0,0 +1,5 @@ +--- +'ai': patch +--- + +feat(ai): support SystemModelMessage[] in system and instructions properties diff --git a/content/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx b/content/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx index b73f60807e21..82b7e469aba7 100644 --- a/content/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +++ b/content/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx @@ -40,7 +40,7 @@ To see `generateText` in action, check out [these examples](#examples). }, { name: 'system', - type: 'string | SystemModelMessage', + type: 'string | SystemModelMessage | SystemModelMessage[]', description: 'The system prompt to use that specifies the behavior of the model.', }, @@ -581,7 +581,7 @@ To see `generateText` in action, check out [these examples](#examples). description: 'Change which tools are active for this step.', }, { - name: 'system | SystemModelMessage', + name: 'system | SystemModelMessage | SystemModelMessage[]', type: 'string', isOptional: true, description: 'Change the system prompt for this step.', @@ -622,7 +622,7 @@ To see `generateText` in action, check out [these examples](#examples). parameters: [ { name: 'system', - type: 'string | SystemModelMessage | undefined', + type: 'string | SystemModelMessage | SystemModelMessage[] | undefined', description: 'The system prompt.', }, { diff --git a/content/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx b/content/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx index eae4380e166b..7050a95d263f 100644 --- a/content/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +++ b/content/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx @@ -42,7 +42,7 @@ To see `streamText` in action, check out [these examples](#examples). }, { name: 'system', - type: 'string | SystemModelMessage', + type: 'string | SystemModelMessage | SystemModelMessage[]', description: 'The system prompt to use that specifies the behavior of the model.', }, @@ -633,7 +633,7 @@ To see `streamText` in action, check out [these examples](#examples). }, { name: 'system', - type: 'string | SystemModelMessage', + type: 'string | SystemModelMessage | SystemModelMessage[]', isOptional: true, description: 'Change the system prompt for this step.', }, @@ -673,7 +673,7 @@ To see `streamText` in action, check out [these examples](#examples). parameters: [ { name: 'system', - type: 'string | SystemModelMessage | undefined', + type: 'string | SystemModelMessage | SystemModelMessage[] | undefined', description: 'The system prompt.', }, { diff --git a/content/docs/07-reference/01-ai-sdk-core/03-generate-object.mdx b/content/docs/07-reference/01-ai-sdk-core/03-generate-object.mdx index a6bbc98b13c3..5af5da65c462 100644 --- a/content/docs/07-reference/01-ai-sdk-core/03-generate-object.mdx +++ b/content/docs/07-reference/01-ai-sdk-core/03-generate-object.mdx @@ -142,7 +142,7 @@ To see `generateObject` in action, check out the [additional examples](#more-exa }, { name: 'system', - type: 'string | SystemModelMessage', + type: 'string | SystemModelMessage | SystemModelMessage[]', description: 'The system prompt to use that specifies the behavior of the model.', }, diff --git a/content/docs/07-reference/01-ai-sdk-core/04-stream-object.mdx b/content/docs/07-reference/01-ai-sdk-core/04-stream-object.mdx index c574dd8c883c..780483432c32 100644 --- a/content/docs/07-reference/01-ai-sdk-core/04-stream-object.mdx +++ b/content/docs/07-reference/01-ai-sdk-core/04-stream-object.mdx @@ -147,7 +147,7 @@ To see `streamObject` in action, check out the [additional examples](#more-examp Not available with 'no-schema' or 'enum' output.", }, { - name: 'system | SystemModelMessage', + name: 'system | SystemModelMessage | SystemModelMessage[]', type: 'string', description: 'The system prompt to use that specifies the behavior of the model.', diff --git a/content/docs/07-reference/01-ai-sdk-core/16-tool-loop-agent.mdx b/content/docs/07-reference/01-ai-sdk-core/16-tool-loop-agent.mdx index edc5b4fb282c..46cb8c59e1f8 100644 --- a/content/docs/07-reference/01-ai-sdk-core/16-tool-loop-agent.mdx +++ b/content/docs/07-reference/01-ai-sdk-core/16-tool-loop-agent.mdx @@ -50,7 +50,7 @@ To see `ToolLoopAgent` in action, check out [these examples](#examples). }, { name: 'instructions', - type: 'string | SystemModelMessage', + type: 'string | SystemModelMessage | SystemModelMessage[]', isOptional: true, description: 'Instructions for the agent, usually used for system prompt/context.', diff --git a/content/docs/07-reference/03-ai-sdk-rsc/01-stream-ui.mdx b/content/docs/07-reference/03-ai-sdk-rsc/01-stream-ui.mdx index d95ae6088a25..9f52a199daac 100644 --- a/content/docs/07-reference/03-ai-sdk-rsc/01-stream-ui.mdx +++ b/content/docs/07-reference/03-ai-sdk-rsc/01-stream-ui.mdx @@ -36,7 +36,7 @@ To see `streamUI` in action, check out [these examples](#examples). }, { name: 'system', - type: 'string | SystemModelMessage', + type: 'string | SystemModelMessage | SystemModelMessage[]', description: 'The system prompt to use that specifies the behavior of the model.', }, diff --git a/examples/ai-core/src/agent/anthropic-cache-instruction.ts b/examples/ai-core/src/agent/anthropic-cache-instruction.ts index 787c94e4bb9b..5f8a48227567 100644 --- a/examples/ai-core/src/agent/anthropic-cache-instruction.ts +++ b/examples/ai-core/src/agent/anthropic-cache-instruction.ts @@ -8,15 +8,21 @@ const errorMessage = fs.readFileSync('data/error-message.txt', 'utf8'); const agent = new ToolLoopAgent({ model: anthropic('claude-sonnet-4-5'), - instructions: { - role: 'system', - content: `You are a JavaScript expert that knows everything about the following error message: ${errorMessage}`, - providerOptions: { - anthropic: { - cacheControl: { type: 'ephemeral' }, - } satisfies AnthropicProviderOptions, + instructions: [ + { + role: 'system', + content: `You are a JavaScript expert that knows everything about the following error message: ${errorMessage}`, + providerOptions: { + anthropic: { + cacheControl: { type: 'ephemeral', ttl: '1h' }, + } satisfies AnthropicProviderOptions, + }, }, - }, + { + role: 'system', + content: 'You pay special attention to the error message.', + }, + ], }); run(async () => { @@ -26,4 +32,5 @@ run(async () => { print('Result:', result.content); print('Metadata:', result.providerMetadata?.anthropic); + print('Request:', result.request.body); }); diff --git a/packages/ai/src/agent/tool-loop-agent-settings.ts b/packages/ai/src/agent/tool-loop-agent-settings.ts index 840ae3ffd0ab..717c01fff618 100644 --- a/packages/ai/src/agent/tool-loop-agent-settings.ts +++ b/packages/ai/src/agent/tool-loop-agent-settings.ts @@ -36,7 +36,7 @@ export type ToolLoopAgentSettings< * * It can be a string, or, if you need to pass additional provider options (e.g. for caching), a `SystemModelMessage`. */ - instructions?: string | SystemModelMessage; + instructions?: string | SystemModelMessage | Array; /** The language model to use. diff --git a/packages/ai/src/agent/tool-loop-agent.test.ts b/packages/ai/src/agent/tool-loop-agent.test.ts index 09c3cbbfd759..39fa85da0c0f 100644 --- a/packages/ai/src/agent/tool-loop-agent.test.ts +++ b/packages/ai/src/agent/tool-loop-agent.test.ts @@ -173,6 +173,61 @@ describe('ToolLoopAgent', () => { ] `); }); + + it('should pass array of system message instructions', async () => { + const agent = new ToolLoopAgent({ + model: mockModel, + instructions: [ + { + role: 'system', + content: 'INSTRUCTIONS', + providerOptions: { test: { value: 'test' } }, + }, + { + role: 'system', + content: 'INSTRUCTIONS 2', + providerOptions: { test: { value: 'test 2' } }, + }, + ], + }); + + await agent.generate({ + prompt: 'Hello, world!', + }); + + expect(doGenerateOptions?.prompt).toMatchInlineSnapshot(` + [ + { + "content": "INSTRUCTIONS", + "providerOptions": { + "test": { + "value": "test", + }, + }, + "role": "system", + }, + { + "content": "INSTRUCTIONS 2", + "providerOptions": { + "test": { + "value": "test 2", + }, + }, + "role": "system", + }, + { + "content": [ + { + "text": "Hello, world!", + "type": "text", + }, + ], + "providerOptions": undefined, + "role": "user", + }, + ] + `); + }); }); }); diff --git a/packages/ai/src/generate-text/parse-tool-call.ts b/packages/ai/src/generate-text/parse-tool-call.ts index f0bd624cb06d..3e0b4d7f188f 100644 --- a/packages/ai/src/generate-text/parse-tool-call.ts +++ b/packages/ai/src/generate-text/parse-tool-call.ts @@ -23,7 +23,7 @@ export async function parseToolCall({ toolCall: LanguageModelV3ToolCall; tools: TOOLS | undefined; repairToolCall: ToolCallRepairFunction | undefined; - system: string | SystemModelMessage | undefined; + system: string | SystemModelMessage | Array | undefined; messages: ModelMessage[]; }): Promise> { try { diff --git a/packages/ai/src/generate-text/prepare-step.ts b/packages/ai/src/generate-text/prepare-step.ts index 7cfb84eac697..1a936b881b16 100644 --- a/packages/ai/src/generate-text/prepare-step.ts +++ b/packages/ai/src/generate-text/prepare-step.ts @@ -29,7 +29,7 @@ export type PrepareStepResult< model?: LanguageModel; toolChoice?: ToolChoice>; activeTools?: Array>; - system?: string | SystemModelMessage; + system?: string | SystemModelMessage | Array; messages?: Array; } | undefined; diff --git a/packages/ai/src/generate-text/run-tools-transformation.ts b/packages/ai/src/generate-text/run-tools-transformation.ts index 4811abf23632..6fe39f8b1b3f 100644 --- a/packages/ai/src/generate-text/run-tools-transformation.ts +++ b/packages/ai/src/generate-text/run-tools-transformation.ts @@ -118,7 +118,7 @@ export function runToolsTransformation({ generatorStream: ReadableStream; tracer: Tracer; telemetry: TelemetrySettings | undefined; - system: string | SystemModelMessage | undefined; + system: string | SystemModelMessage | Array | undefined; messages: ModelMessage[]; abortSignal: AbortSignal | undefined; repairToolCall: ToolCallRepairFunction | undefined; diff --git a/packages/ai/src/generate-text/tool-call-repair-function.ts b/packages/ai/src/generate-text/tool-call-repair-function.ts index 3912d0ea64e8..ff63129aa0f4 100644 --- a/packages/ai/src/generate-text/tool-call-repair-function.ts +++ b/packages/ai/src/generate-text/tool-call-repair-function.ts @@ -18,7 +18,7 @@ import { ToolSet } from './tool-set'; * @param options.error - The error that occurred while parsing the tool call. */ export type ToolCallRepairFunction = (options: { - system: string | SystemModelMessage | undefined; + system: string | SystemModelMessage | Array | undefined; messages: ModelMessage[]; toolCall: LanguageModelV3ToolCall; tools: TOOLS; diff --git a/packages/ai/src/prompt/convert-to-language-model-prompt.test.ts b/packages/ai/src/prompt/convert-to-language-model-prompt.test.ts index 7fe336e3c3f9..c854cf14af9d 100644 --- a/packages/ai/src/prompt/convert-to-language-model-prompt.test.ts +++ b/packages/ai/src/prompt/convert-to-language-model-prompt.test.ts @@ -75,6 +75,45 @@ describe('convertToLanguageModelPrompt', () => { ] `); }); + + it('should convert an array of SystemModelMessage system messages', async () => { + const result = await convertToLanguageModelPrompt({ + prompt: { + system: [ + { role: 'system', content: 'INSTRUCTIONS' }, + { role: 'system', content: 'INSTRUCTIONS 2' }, + ], + messages: [{ role: 'user', content: 'Hello, world!' }], + }, + supportedUrls: {}, + download: undefined, + }); + + expect(result).toMatchInlineSnapshot(` + [ + { + "content": "INSTRUCTIONS", + "providerOptions": undefined, + "role": "system", + }, + { + "content": "INSTRUCTIONS 2", + "providerOptions": undefined, + "role": "system", + }, + { + "content": [ + { + "text": "Hello, world!", + "type": "text", + }, + ], + "providerOptions": undefined, + "role": "user", + }, + ] + `); + }); }); describe('user message', () => { diff --git a/packages/ai/src/prompt/convert-to-language-model-prompt.ts b/packages/ai/src/prompt/convert-to-language-model-prompt.ts index 027cd4c0b422..cba63a45e106 100644 --- a/packages/ai/src/prompt/convert-to-language-model-prompt.ts +++ b/packages/ai/src/prompt/convert-to-language-model-prompt.ts @@ -28,6 +28,7 @@ import { import { convertToLanguageModelV3DataContent } from './data-content'; import { InvalidMessageRoleError } from './invalid-message-role-error'; import { StandardizedPrompt } from './standardize-prompt'; +import { asArray } from '../util/as-array'; export async function convertToLanguageModelPrompt({ prompt, @@ -48,13 +49,11 @@ export async function convertToLanguageModelPrompt({ ...(prompt.system != null ? typeof prompt.system === 'string' ? [{ role: 'system' as const, content: prompt.system }] - : [ - { - role: 'system' as const, - content: prompt.system.content, - providerOptions: prompt.system.providerOptions, - }, - ] + : asArray(prompt.system).map(message => ({ + role: 'system' as const, + content: message.content, + providerOptions: message.providerOptions, + })) : []), ...prompt.messages.map(message => convertToLanguageModelMessage({ message, downloadedAssets }), diff --git a/packages/ai/src/prompt/prompt.ts b/packages/ai/src/prompt/prompt.ts index 249916c1759d..3329797acc36 100644 --- a/packages/ai/src/prompt/prompt.ts +++ b/packages/ai/src/prompt/prompt.ts @@ -8,7 +8,7 @@ export type Prompt = { /** System message to include in the prompt. Can be used with `prompt` or `messages`. */ - system?: string | SystemModelMessage; + system?: string | SystemModelMessage | Array; } & ( | { /** diff --git a/packages/ai/src/prompt/standardize-prompt.test.ts b/packages/ai/src/prompt/standardize-prompt.test.ts index 09bd4c78d2d3..646926a7d580 100644 --- a/packages/ai/src/prompt/standardize-prompt.test.ts +++ b/packages/ai/src/prompt/standardize-prompt.test.ts @@ -48,4 +48,35 @@ describe('standardizePrompt', () => { } `); }); + + it('should support array of SystemModelMessage system messages', async () => { + const result = await standardizePrompt({ + system: [ + { role: 'system', content: 'INSTRUCTIONS' }, + { role: 'system', content: 'INSTRUCTIONS 2' }, + ], + prompt: 'Hello, world!', + }); + + expect(result).toMatchInlineSnapshot(` + { + "messages": [ + { + "content": "Hello, world!", + "role": "user", + }, + ], + "system": [ + { + "content": "INSTRUCTIONS", + "role": "system", + }, + { + "content": "INSTRUCTIONS 2", + "role": "system", + }, + ], + } + `); + }); }); diff --git a/packages/ai/src/prompt/standardize-prompt.ts b/packages/ai/src/prompt/standardize-prompt.ts index 89cdf4e7926b..aa1920a0bf8d 100644 --- a/packages/ai/src/prompt/standardize-prompt.ts +++ b/packages/ai/src/prompt/standardize-prompt.ts @@ -7,12 +7,13 @@ import { import { z } from 'zod/v4'; import { modelMessageSchema } from './message'; import { Prompt } from './prompt'; +import { asArray } from '../util/as-array'; export type StandardizedPrompt = { /** * System message. */ - system?: string | SystemModelMessage; + system?: string | SystemModelMessage | Array; /** * Messages. @@ -41,12 +42,18 @@ export async function standardizePrompt( if ( prompt.system != null && typeof prompt.system !== 'string' && - 'role' in prompt.system && - prompt.system.role !== 'system' + !asArray(prompt.system).every( + message => + typeof message === 'object' && + message !== null && + 'role' in message && + message.role === 'system', + ) ) { throw new InvalidPromptError({ prompt, - message: 'system must be a string', + message: + 'system must be a string, SystemModelMessage, or array of SystemModelMessage', }); }