Skip to main content

ferrin_core/middleware/builtin/
default_instructions.rs

1//! Default system instructions.
2
3use ferrin_spec::BoxFuture;
4use ferrin_spec::CallOptions;
5use ferrin_spec::error::ProviderError;
6use ferrin_spec::language_model::prompt::PromptMessage;
7
8use crate::middleware::LanguageModelMiddleware;
9use crate::middleware::MiddlewareContext;
10use crate::prompt::Instructions;
11
12/// Middleware created by [`default_instructions`].
13#[derive(Debug, Clone)]
14pub struct DefaultInstructions {
15    instructions: Instructions,
16}
17
18/// Prepends `instructions` as system messages when the prompt has no
19/// system message.
20#[must_use]
21pub fn default_instructions(instructions: impl Into<Instructions>) -> DefaultInstructions {
22    DefaultInstructions {
23        instructions: instructions.into(),
24    }
25}
26
27impl LanguageModelMiddleware for DefaultInstructions {
28    fn transform_params<'a>(
29        &'a self,
30        mut options: CallOptions,
31        _ctx: MiddlewareContext<'a>,
32    ) -> BoxFuture<'a, Result<CallOptions, ProviderError>> {
33        let has_system = options
34            .prompt
35            .iter()
36            .any(|message| matches!(message, PromptMessage::System { .. }));
37        if !has_system {
38            options.prompt.splice(
39                0..0,
40                self.instructions
41                    .as_messages()
42                    .iter()
43                    .map(|message| PromptMessage::System {
44                        content: message.content.clone(),
45                        provider_options: message.provider_options.clone(),
46                    }),
47            );
48        }
49        Box::pin(async move { Ok(options) })
50    }
51}