ferrin_core/prompt/
standardize.rs1use ferrin_message::Message;
4use ferrin_message::Role;
5use ferrin_message::SystemMessage;
6use ferrin_spec::ProviderOptions;
7
8use crate::error::Error;
9
10#[derive(Debug, Clone, PartialEq, Eq, Default)]
12pub struct Instructions {
13 pub content: String,
15 pub provider_options: Option<ProviderOptions>,
17}
18
19impl Instructions {
20 #[must_use]
22 pub fn new(content: impl Into<String>) -> Self {
23 Self {
24 content: content.into(),
25 provider_options: None,
26 }
27 }
28
29 #[must_use]
31 pub fn with_provider_options(mut self, options: ProviderOptions) -> Self {
32 self.provider_options = Some(options);
33 self
34 }
35
36 #[must_use]
38 pub fn into_message(self) -> SystemMessage {
39 SystemMessage {
40 content: self.content,
41 provider_options: self.provider_options,
42 }
43 }
44}
45
46impl From<&str> for Instructions {
47 fn from(content: &str) -> Self {
48 Self::new(content)
49 }
50}
51
52impl From<String> for Instructions {
53 fn from(content: String) -> Self {
54 Self::new(content)
55 }
56}
57
58impl From<SystemMessage> for Instructions {
59 fn from(message: SystemMessage) -> Self {
60 Self {
61 content: message.content,
62 provider_options: message.provider_options,
63 }
64 }
65}
66
67#[derive(Debug, Clone, PartialEq)]
70pub(crate) struct StandardizedPrompt {
71 pub(crate) system: Option<Instructions>,
72 pub(crate) messages: Vec<Message>,
73}
74
75pub(crate) fn standardize(
81 system: Option<Instructions>,
82 prompt: Option<String>,
83 messages: Option<Vec<Message>>,
84 allow_system_in_messages: bool,
85) -> Result<StandardizedPrompt, Error> {
86 let messages = match (prompt, messages) {
87 (Some(_), Some(_)) => {
88 return Err(Error::invalid_prompt(
89 "prompt and messages cannot be set at the same time",
90 ));
91 }
92 (None, None) => {
93 return Err(Error::invalid_prompt(
94 "either prompt or messages must be set",
95 ));
96 }
97 (Some(text), None) => vec![Message::user(text)],
98 (None, Some(messages)) => messages,
99 };
100 if messages.is_empty() {
101 return Err(Error::invalid_prompt("messages must not be empty"));
102 }
103 if !allow_system_in_messages
104 && let Some(index) = messages
105 .iter()
106 .position(|message| message.role() == Role::System)
107 {
108 return Err(Error::invalid_prompt(format!(
109 "messages must not contain system messages (found at index {index}); \
110 use `system` or enable `allow_system_in_messages`"
111 )));
112 }
113 Ok(StandardizedPrompt { system, messages })
114}