use std::fmt;
pub mod emitter;
pub mod hash;
pub mod ir;
pub mod validator;
#[derive(Debug, thiserror::Error)]
pub enum PromptError {
#[error("SPARQL error: {0}")]
Sparql(String),
#[error("Template error: {0}")]
Template(String),
#[error("Validation error: {0}")]
Validation(String),
#[error("Hash error: {0}")]
Hash(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Invalid prompt: {0}")]
Invalid(String),
}
pub type Result<T> = std::result::Result<T, PromptError>;
pub struct PromptCompiler {
emitter: emitter::PromptEmitter,
validator: validator::PromptValidator,
}
impl PromptCompiler {
pub fn new() -> Result<Self> {
Ok(Self {
emitter: emitter::PromptEmitter::new()?,
validator: validator::PromptValidator::new(),
})
}
pub fn compile_from_construct(&self, construct_query: &str) -> Result<CompiledPrompt> {
let prompt_ir = ir::PromptIR::from_construct(construct_query)?;
self.validator.validate(&prompt_ir)?;
let content = self.emitter.emit(&prompt_ir)?;
let hash = hash::compute_prompt_hash(&content)?;
Ok(CompiledPrompt {
content,
hash,
ir: prompt_ir,
})
}
pub fn compile_from_ir(&self, prompt_ir: ir::PromptIR) -> Result<CompiledPrompt> {
self.validator.validate(&prompt_ir)?;
let content = self.emitter.emit(&prompt_ir)?;
let hash = hash::compute_prompt_hash(&content)?;
Ok(CompiledPrompt {
content,
hash,
ir: prompt_ir,
})
}
pub fn compile_from_store(
&self, store: &oxigraph::store::Store, construct_query: &str,
) -> Result<CompiledPrompt> {
let prompt_ir = ir::PromptIR::from_store(store, construct_query)?;
self.validator.validate(&prompt_ir)?;
let content = self.emitter.emit(&prompt_ir)?;
let hash = hash::compute_prompt_hash(&content)?;
Ok(CompiledPrompt {
content,
hash,
ir: prompt_ir,
})
}
}
impl Default for PromptCompiler {
#[allow(clippy::panic)] fn default() -> Self {
Self::new().unwrap_or_else(|e| {
panic!("invariant violated: embedded Tera templates are malformed: {e}")
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompiledPrompt {
pub content: String,
pub hash: String,
pub ir: ir::PromptIR,
}
impl CompiledPrompt {
pub fn content(&self) -> &str {
&self.content
}
pub fn hash(&self) -> &str {
&self.hash
}
pub fn ir(&self) -> &ir::PromptIR {
&self.ir
}
}
impl fmt::Display for CompiledPrompt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.content)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compiler_initialization() {
let compiler = PromptCompiler::new();
assert!(compiler.is_ok());
}
#[test]
fn test_default_compiler() {
let _compiler = PromptCompiler::default();
}
#[test]
fn test_compile_from_ir() {
use std::collections::BTreeMap;
let compiler = PromptCompiler::new().expect("compiler init");
let mut sections = BTreeMap::new();
sections.insert(
"system".to_string(),
ir::Section {
section_type: ir::SectionType::System,
blocks: vec![ir::ContentBlock {
block_type: ir::BlockType::Instruction,
content: "You are a helpful assistant.".to_string(),
metadata: BTreeMap::new(),
}],
priority: 0,
},
);
let prompt_ir = ir::PromptIR {
sections,
metadata: ir::PromptMetadata {
id: "manual".to_string(),
version: "0.1.0".to_string(),
schema_version: "1.0.0".to_string(),
source_ontology: "manual".to_string(),
construct_query: "n/a".to_string(),
},
variables: BTreeMap::new(),
};
let result = compiler.compile_from_ir(prompt_ir);
assert!(result.is_ok());
let compiled = result.expect("compile_from_ir should succeed");
assert!(!compiled.content().is_empty());
}
}