use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemModelContent {
text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserModelContent {
text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentModelContent {
author: Option<String>,
text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ModelContentItem {
System(SystemModelContent),
User(UserModelContent),
Agent(AgentModelContent),
MergeDownstream(String),
MergeUpstream(String),
}
impl ModelContentItem {
pub fn user(text: &str) -> Self {
ModelContentItem::User(UserModelContent { text: text.into() })
}
pub fn system(text: &str) -> Self {
ModelContentItem::System(SystemModelContent { text: text.into() })
}
pub fn agent(author: Option<String>, text: &str) -> Self {
ModelContentItem::Agent(AgentModelContent {
author,
text: text.into(),
})
}
pub fn merge_downstream(text: &str) -> Self {
ModelContentItem::MergeDownstream(text.into())
}
pub fn merge_upstream(text: &str) -> Self {
ModelContentItem::MergeUpstream(text.into())
}
pub fn is_empty(&self) -> bool {
self.to_string().trim().is_empty()
}
}
impl ToString for ModelContentItem {
fn to_string(&self) -> String {
match self {
ModelContentItem::System(content) => format!("{}", content.text),
ModelContentItem::User(content) => format!("{}", content.text),
ModelContentItem::Agent(content) => format!("{}", content.text),
ModelContentItem::MergeDownstream(content) => content.clone(),
ModelContentItem::MergeUpstream(content) => content.clone(),
}
}
}
#[derive(Clone, Copy)]
pub enum PromptFormat {
Parts,
}
pub struct PromptConfig {
pub agent: Option<String>,
pub format: PromptFormat,
pub with_agent_names: bool,
pub with_invitation: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelContent(pub Vec<ModelContentItem>);
impl ModelContent {
pub fn new() -> Self {
ModelContent(Vec::new())
}
pub fn from_item(item: ModelContentItem) -> Self {
ModelContent(vec![item])
}
pub fn extend(&mut self, content: ModelContent) {
self.0.extend(content.0.into_iter());
}
pub fn push(&mut self, item: ModelContentItem) {
self.0.push(item);
}
fn embed_in_prompt_as_part(item: &ModelContentItem, config: &PromptConfig) -> String {
match item {
ModelContentItem::System(content) => {
let text = content.text.trim();
if !text.is_empty() {
format!("---\nSystem:\n{}\n", text)
} else {
text.into()
}
}
ModelContentItem::User(content) => {
let text = content.text.trim();
if !text.is_empty() {
format!("---\nUser:\n{}\n", text)
} else {
text.into()
}
}
ModelContentItem::Agent(content) => {
let text = content.text.trim();
if !text.is_empty() {
let name = match config.with_agent_names {
false => None,
true => content
.author
.clone()
.map(|x| super::names::generate_name(&x)),
};
if let Some(name) = name {
format!("Assistant {}:\n{}\n", name, text)
} else {
format!("Assistant:\n{}\n", text)
}
} else {
text.into()
}
}
_ => {
panic!("cannot be embedded, bug!");
}
}
}
fn embed_in_prompt(item: &ModelContentItem, config: &PromptConfig) -> String {
match config.format {
PromptFormat::Parts => Self::embed_in_prompt_as_part(item, config),
}
}
pub fn to_prompt(&self, config: &PromptConfig) -> String {
let agent_name = match config.with_agent_names {
false => None,
true => config
.agent
.clone()
.map(|x| super::names::generate_name(&x)),
};
let identity = agent_name
.clone()
.map(|x| format!("You are {}.\n", x))
.unwrap_or(String::new());
let mut prepend_identity = vec![ModelContentItem::merge_downstream(&identity)];
prepend_identity.extend(self.0.clone());
let non_empty_items = &prepend_identity
.iter()
.filter(|x| !x.is_empty())
.collect::<Vec<&ModelContentItem>>();
let mut merged_items: Vec<ModelContentItem> = Vec::new();
let mut downstream_merges: Vec<String> = Vec::new();
for item in non_empty_items {
match item {
ModelContentItem::System(_) | ModelContentItem::User(_) => {
let mut current_text = item.to_string();
if !downstream_merges.is_empty() {
let prefix = downstream_merges.join("\n");
current_text = format!("{}\n{}", prefix, current_text);
downstream_merges.clear();
}
let new_item = match item {
ModelContentItem::System(_) => ModelContentItem::system(¤t_text),
ModelContentItem::User(_) => ModelContentItem::user(¤t_text),
_ => unreachable!(), };
merged_items.push(new_item);
}
ModelContentItem::Agent(agent_item) => {
let mut current_text = item.to_string();
if !downstream_merges.is_empty() {
let prefix = downstream_merges.join("\n");
current_text = format!("{}\n{}", prefix, current_text);
downstream_merges.clear();
}
let new_item =
ModelContentItem::agent(agent_item.author.clone(), ¤t_text);
merged_items.push(new_item);
}
ModelContentItem::MergeDownstream(s) => {
downstream_merges.push(s.clone());
}
ModelContentItem::MergeUpstream(s) => {
if let Some(last_item) = merged_items.last_mut() {
let text_to_append = s.clone();
match last_item {
ModelContentItem::System(c) => {
c.text = format!("{}\n{}", c.text, text_to_append)
}
ModelContentItem::User(c) => {
c.text = format!("{}\n{}", c.text, text_to_append)
}
ModelContentItem::Agent(c) => {
c.text = format!("{}\n{}", c.text, text_to_append)
}
_ => {} }
}
}
}
}
let mut final_merged_items: Vec<ModelContentItem> = Vec::new();
let mut iter = merged_items.into_iter();
if let Some(mut last) = iter.next() {
for item in iter {
match (&mut last, &item) {
(
ModelContentItem::System(last_content),
ModelContentItem::System(item_content),
) => {
last_content.text.push('\n');
last_content.text.push_str(&item_content.text);
}
(
ModelContentItem::User(last_content),
ModelContentItem::User(item_content),
) => {
last_content.text.push('\n');
last_content.text.push_str(&item_content.text);
}
(
ModelContentItem::Agent(last_content),
ModelContentItem::Agent(item_content),
) => {
last_content.text.push('\n');
last_content.text.push_str(&item_content.text);
}
_ => {
final_merged_items.push(last);
last = item;
}
}
}
final_merged_items.push(last);
}
let mut prompt = final_merged_items
.iter()
.map(|item| Self::embed_in_prompt(item, config))
.collect::<Vec<String>>()
.join("\n");
let invitation = match (config.with_invitation, agent_name) {
(true, Some(agent_name)) => format!("Assistant {}:", agent_name),
(true, None) => format!("Assistant:"),
(false, _) => String::new(),
};
prompt.push_str(&invitation);
prompt
}
}
impl Default for ModelContent {
fn default() -> Self {
Self::new()
}
}
impl ToString for ModelContent {
fn to_string(&self) -> String {
self.0
.iter()
.map(|item| item.to_string())
.collect::<Vec<String>>()
.join("\n")
}
}