#![allow(clippy::module_name_repetitions)]
use artificial::openai::OpenAiAdapterBuilder;
use artificial::prompt::{builder::PromptBuilder, chain::PromptChain};
use artificial::types::{
fragments::{CurrentDateFragment, StaticFragment},
outputs::result::ThinkResult,
};
use artificial::{
ArtificialClient,
generic::{GenericMessage, GenericRole},
model::{Model, OpenAiModel},
provider::PromptExecutionProvider as _,
template::{IntoPrompt, PromptTemplate},
};
use schemars::{
JsonSchema, SchemaGenerator,
schema::{InstanceType, Metadata, SchemaObject, SingleOrVec},
};
use serde::{Deserialize, Serialize};
#[derive(Clone)]
struct Message {
pub from: String,
pub text: String,
}
const BASE_SYSTEM_ROLE: &str = include_str!("data/role/base_system.md");
const MEMORY_ARCHITECT_ROLE: &str = include_str!("data/role/memory_architect.md");
struct CaptureMemory<'a> {
system_base_fragment: StaticFragment<'a>,
memory_architect_role_fragment: StaticFragment<'a>,
agent_fragment: AgentProfileFragment<'a>,
team_fragment: TeamProfileFragment<'a>,
history_fragment: MessageHistoryFragment<'a>,
}
impl<'a> CaptureMemory<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new(
member: &'a MemberProfile<'a>,
history: &'a [Message],
team_profile: &'a TeamProfile<'a>,
) -> Self {
Self {
system_base_fragment: BASE_SYSTEM_ROLE.into(),
memory_architect_role_fragment: MEMORY_ARCHITECT_ROLE.into(),
agent_fragment: AgentProfileFragment::new(member, team_profile.team_name),
team_fragment: TeamProfileFragment::new(team_profile),
history_fragment: MessageHistoryFragment::new(history),
}
}
}
impl<'a> IntoPrompt for CaptureMemory<'a> {
type Message = GenericMessage;
fn into_prompt(self) -> Vec<Self::Message> {
let final_instruction = StaticFragment::new(
"Extract any important memory worth remembering from this conversation.",
GenericRole::User,
);
PromptChain::new()
.with(self.system_base_fragment)
.with(CurrentDateFragment::new()) .with(self.agent_fragment)
.with(self.team_fragment)
.with(self.memory_architect_role_fragment)
.with(self.history_fragment)
.with(final_instruction)
.build()
}
}
impl<'a> PromptTemplate for CaptureMemory<'a> {
type Output = ThinkResult<MemoryExtraction>;
const MODEL: Model = Model::OpenAi(OpenAiModel::Gpt4oMini);
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let member_r2d2 = MemberProfile {
name: "R2-D2",
biography: "Resourceful astromech droid and hobby scream-beeper.",
};
let member_luke = MemberProfile {
name: "Luke Skywalker",
biography: "Moisture-farmer-turned-Jedi. Good at bullseyeing womp rats.",
};
let member_chewie = MemberProfile {
name: "Chewbacca",
biography: "Walking carpet with a heart of gold. Fluent in Shyriiwook.",
};
let members = vec![&member_r2d2, &member_luke, &member_chewie];
let team_profile = TeamProfile {
team_name: "Rebel Alliance",
members: &members,
};
let history = vec![
Message {
from: "Chewbacca".into(),
text: "⏩ *static* Rrrgh! (Translation: “Is everyone strapped in? I’m about \
to punch it.”)"
.into(),
},
Message {
from: "Luke Skywalker".into(),
text: "Copy that, Chewie. R2, make sure the deflector shields are up. We \
don't want to become space confetti."
.into(),
},
Message {
from: "R2-D2".into(),
text: "Beep-boop-bweep! (Translation: “Shields at 120%. I overclocked them. \
Please don't tell the warranty droid.”)"
.into(),
},
Message {
from: "Chewbacca".into(),
text: "Raaawrr! (Translation: “Good, because I see three TIE fighters \
who think we're today's buffet special.”)"
.into(),
},
Message {
from: "Luke Skywalker".into(),
text: "Stay on target, team. Remember: evasive roll first, philosophical \
quotes later."
.into(),
},
];
let backend = OpenAiAdapterBuilder::new_from_env().build()?;
let client = ArtificialClient::new(backend);
let prompt = CaptureMemory::new(&member_r2d2, &history, &team_profile);
let result = client.prompt_execute(prompt).await?;
println!("🤖 LLM remembered:\n{result:#?}");
Ok(())
}
pub struct TeamProfileFragment<'a> {
team_spec: &'a TeamProfile<'a>,
}
impl<'a> TeamProfileFragment<'a> {
fn new(team_spec: &'a TeamProfile<'a>) -> Self {
Self { team_spec }
}
}
impl IntoPrompt for TeamProfileFragment<'_> {
type Message = GenericMessage;
fn into_prompt(self) -> Vec<Self::Message> {
let profile = serde_yaml::to_string(&self.team_spec)
.unwrap_or_else(|_| "<serialization error>".into());
let builder = PromptBuilder::new()
.add_section_h2("Team Profile")
.add_line("You are part of the following strike team:")
.add_text_yaml(profile);
vec![GenericMessage::new(builder.finalize(), GenericRole::System)]
}
}
pub struct AgentProfileFragment<'a> {
member: &'a MemberProfile<'a>,
team_name: &'a str,
}
impl<'a> AgentProfileFragment<'a> {
fn new(member: &'a MemberProfile<'a>, team_name: &'a str) -> Self {
Self { member, team_name }
}
}
impl IntoPrompt for AgentProfileFragment<'_> {
type Message = GenericMessage;
fn into_prompt(self) -> Vec<Self::Message> {
let profile =
serde_yaml::to_string(&self.member).unwrap_or_else(|_| "<serialization error>".into());
let builder = PromptBuilder::new()
.add_section_h2("Your Profile")
.add_key_value("Name", self.member.name)
.add_key_value("Biography", self.member.biography)
.add_key_value("Affiliation", self.team_name)
.add_text_yaml(profile);
vec![GenericMessage::new(builder.finalize(), GenericRole::System)]
}
}
pub struct MessageHistoryFragment<'a> {
history: &'a [Message],
}
impl<'a> MessageHistoryFragment<'a> {
fn new(history: &'a [Message]) -> Self {
Self { history }
}
}
impl IntoPrompt for MessageHistoryFragment<'_> {
type Message = GenericMessage;
fn into_prompt(self) -> Vec<Self::Message> {
if self.history.is_empty() {
return vec![];
}
let mut messages = Vec::with_capacity(self.history.len());
for message in self.history {
messages.extend(MessageFragment::new(&message.from, &message.text).into_prompt());
}
messages
}
}
pub struct MessageFragment<'a> {
name: &'a str,
message: &'a str,
}
impl<'a> MessageFragment<'a> {
pub fn new(name: &'a str, message: &'a str) -> Self {
Self { name, message }
}
}
impl IntoPrompt for MessageFragment<'_> {
type Message = GenericMessage;
fn into_prompt(self) -> Vec<Self::Message> {
let builder = PromptBuilder::new()
.add_section_h2(format!("Message from {}", self.name))
.add_key_value("Body", "")
.add_text_markdown(self.message)
.add_blank_line();
vec![GenericMessage::new(builder.finalize(), GenericRole::System)]
}
}
#[derive(Serialize)]
struct TeamProfile<'a> {
team_name: &'a str,
members: &'a [&'a MemberProfile<'a>],
}
#[derive(Serialize, Clone, Copy)]
struct MemberProfile<'a> {
pub name: &'a str,
pub biography: &'static str,
}
#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MemoryExtraction {
pub items: Vec<MemoryExtractionItem>,
}
#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MemoryExtractionItem {
pub summary: String,
#[schemars(required)]
pub origin: Option<String>,
pub relevance_score: f32,
pub classification: MemoryClassification,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum MemoryClassification {
#[default]
Reflective,
Directive,
Strategic,
}
impl JsonSchema for MemoryClassification {
fn schema_name() -> String {
"MemoryClassification".into()
}
fn json_schema(_generator: &mut SchemaGenerator) -> schemars::schema::Schema {
schemars::schema::Schema::Object(SchemaObject {
metadata: Some(Box::new(Metadata {
description: Some(
"Classification of the memory information. \
Possible values: reflective, directive, strategic."
.into(),
),
..Default::default()
})),
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
enum_values: Some(vec![
serde_json::Value::String("reflective".into()),
serde_json::Value::String("directive".into()),
serde_json::Value::String("strategic".into()),
]),
..Default::default()
})
}
}