use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use crate::chat::message::{get_app_data, insert_app_data};
use crate::macros::text_conversions;
use crate::{CacheBreakpoint, ExtraMap, ProviderOptions, RequestOptions, Result};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct SystemPrompt {
pub content: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cache: Option<CacheBreakpoint>,
#[serde(default, skip_serializing_if = "RequestOptions::is_empty")]
pub options: RequestOptions,
#[serde(default, skip_serializing_if = "ExtraMap::is_empty")]
pub app_data: ExtraMap,
}
impl SystemPrompt {
#[must_use]
pub fn new(content: impl Into<String>) -> Self {
Self {
content: content.into(),
cache: None,
options: RequestOptions::new(),
app_data: ExtraMap::new(),
}
}
#[must_use]
pub fn with_cache(mut self, breakpoint: CacheBreakpoint) -> Self {
self.cache = Some(breakpoint);
self
}
#[must_use]
pub fn with_option<T: ProviderOptions + Serialize>(mut self, value: T) -> Self {
self.options.insert(value);
self
}
#[must_use]
pub fn with_app_data(
mut self,
key: impl Into<String>,
value: impl Into<serde_json::Value>,
) -> Self {
self.app_data.insert(key.into(), value.into());
self
}
pub fn app_data_get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>> {
get_app_data(&self.app_data, key)
}
pub fn app_data_insert<T: Serialize + ?Sized>(
&mut self,
key: impl Into<String>,
value: &T,
) -> Result<()> {
insert_app_data(&mut self.app_data, key, value)
}
pub fn resolve_option<T: ProviderOptions + DeserializeOwned + Default>(&self) -> Result<T> {
Ok(self.options.resolve_or_default::<T>()?.into_owned())
}
}
text_conversions!(SystemPrompt, Self::new);
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn round_trip_keeps_cache_and_options() {
let mut prompt = SystemPrompt::new("Be concise").with_cache(CacheBreakpoint::new());
prompt
.options
.insert_json("openai", json!({"role": "developer"}));
let value = serde_json::to_value(&prompt).unwrap();
assert_eq!(value["options"]["openai"]["role"], "developer");
let back: SystemPrompt = serde_json::from_value(value).unwrap();
assert_eq!(back, prompt);
}
#[test]
fn app_data_round_trips() {
let mut prompt = SystemPrompt::new("Be concise").with_app_data("source", json!("persona"));
prompt.app_data_insert("revision", &3_u32).unwrap();
let value = serde_json::to_value(&prompt).unwrap();
assert_eq!(
value,
json!({"content": "Be concise", "app_data": {"source": "persona", "revision": 3}})
);
let back: SystemPrompt = serde_json::from_value(value).unwrap();
assert_eq!(back.app_data_get::<u32>("revision").unwrap(), Some(3));
assert!(back.app_data_get::<u32>("source").is_err());
assert_eq!(back, prompt);
}
#[test]
fn minimal_serialization() {
let value = serde_json::to_value(SystemPrompt::from("x")).unwrap();
assert_eq!(value, json!({"content": "x"}));
}
}