use crate::monitor::llm::error::{LLMError, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
pub type PromptId = String;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptVersion {
pub id: PromptId,
pub template: String,
pub variables: Vec<String>,
pub version: u32,
pub created_at: DateTime<Utc>,
pub sha256: String,
pub description: Option<String>,
pub tags: HashMap<String, String>,
}
impl PromptVersion {
pub fn new(template: &str, variables: Vec<String>) -> Self {
let sha256 = Self::compute_hash(template);
let id = sha256[..16].to_string();
Self {
id,
template: template.to_string(),
variables,
version: 1,
created_at: Utc::now(),
sha256,
description: None,
tags: HashMap::new(),
}
}
pub fn with_version(mut self, version: u32) -> Self {
self.version = version;
self
}
pub fn with_description(mut self, desc: &str) -> Self {
self.description = Some(desc.to_string());
self
}
pub fn with_tag(mut self, key: &str, value: &str) -> Self {
self.tags.insert(key.to_string(), value.to_string());
self
}
fn compute_hash(template: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(template.as_bytes());
let result = hasher.finalize();
hex::encode(result)
}
pub fn render(&self, vars: &HashMap<String, String>) -> Result<String> {
let mut result = self.template.clone();
for var in &self.variables {
let placeholder = format!("{{{var}}}");
if let Some(value) = vars.get(var) {
result = result.replace(&placeholder, value);
} else {
return Err(LLMError::EvaluationFailed(format!("Missing variable: {var}")));
}
}
Ok(result)
}
pub fn extract_variables(template: &str) -> Vec<String> {
let mut vars = Vec::new();
let mut in_var = false;
let mut current = String::new();
for c in template.chars() {
match c {
'{' => {
in_var = true;
current.clear();
}
'}' if in_var => {
if !current.is_empty() && !vars.contains(¤t) {
vars.push(current.clone());
}
in_var = false;
}
_ if in_var => {
current.push(c);
}
_non_brace => {}
}
}
vars
}
}