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)]
15#[non_exhaustive]
16pub enum Instructions {
17 System(SystemMessage),
19 Messages(Vec<SystemMessage>),
21}
22
23impl Default for Instructions {
24 fn default() -> Self {
25 Self::new("")
26 }
27}
28
29impl Instructions {
30 #[must_use]
32 pub fn new(content: impl Into<String>) -> Self {
33 Self::System(SystemMessage {
34 content: content.into(),
35 provider_options: None,
36 })
37 }
38
39 #[must_use]
53 pub fn messages(messages: impl IntoIterator<Item = SystemMessage>) -> Self {
54 Self::Messages(messages.into_iter().collect())
55 }
56
57 #[must_use]
59 pub fn with_provider_options(mut self, options: ProviderOptions) -> Self {
60 match &mut self {
61 Self::System(message) => message.provider_options = Some(options),
62 Self::Messages(messages) => {
63 for message in messages {
64 message.provider_options = Some(options.clone());
65 }
66 }
67 }
68 self
69 }
70
71 #[must_use]
73 pub fn as_messages(&self) -> &[SystemMessage] {
74 match self {
75 Self::System(message) => std::slice::from_ref(message),
76 Self::Messages(messages) => messages,
77 }
78 }
79
80 #[must_use]
82 pub fn into_messages(self) -> Vec<SystemMessage> {
83 match self {
84 Self::System(message) => vec![message],
85 Self::Messages(messages) => messages,
86 }
87 }
88}
89
90impl From<&str> for Instructions {
91 fn from(content: &str) -> Self {
92 Self::new(content)
93 }
94}
95
96impl From<String> for Instructions {
97 fn from(content: String) -> Self {
98 Self::new(content)
99 }
100}
101
102impl From<SystemMessage> for Instructions {
103 fn from(message: SystemMessage) -> Self {
104 Self::System(message)
105 }
106}
107
108impl From<Vec<SystemMessage>> for Instructions {
109 fn from(messages: Vec<SystemMessage>) -> Self {
110 Self::Messages(messages)
111 }
112}
113
114#[derive(Debug, Clone, PartialEq)]
117pub(crate) struct StandardizedPrompt {
118 pub(crate) system: Option<Instructions>,
119 pub(crate) messages: Vec<Message>,
120}
121
122pub(crate) fn standardize(
128 system: Option<Instructions>,
129 prompt: Option<String>,
130 messages: Option<Vec<Message>>,
131 allow_system_in_messages: bool,
132) -> Result<StandardizedPrompt, Error> {
133 let messages = match (prompt, messages) {
134 (Some(_), Some(_)) => {
135 return Err(Error::invalid_prompt(
136 "prompt and messages cannot be set at the same time",
137 ));
138 }
139 (None, None) => {
140 return Err(Error::invalid_prompt(
141 "either prompt or messages must be set",
142 ));
143 }
144 (Some(text), None) => vec![Message::user(text)],
145 (None, Some(messages)) => messages,
146 };
147 if messages.is_empty() {
148 return Err(Error::invalid_prompt("messages must not be empty"));
149 }
150 if !allow_system_in_messages
151 && let Some(index) = messages
152 .iter()
153 .position(|message| message.role() == Role::System)
154 {
155 return Err(Error::invalid_prompt(format!(
156 "messages must not contain system messages (found at index {index}); \
157 use `system` or enable `allow_system_in_messages`"
158 )));
159 }
160 Ok(StandardizedPrompt { system, messages })
161}