Files
tkmind_go/crates/goose/src/prompt_template.rs
T
Bradley Axen 1c9a7c0b05 feat: V1.0 (#734)
Co-authored-by: Michael Neale <michael.neale@gmail.com>
Co-authored-by: Wendy Tang <wendytang@squareup.com>
Co-authored-by: Jarrod Sibbison <72240382+jsibbison-square@users.noreply.github.com>
Co-authored-by: Alex Hancock <alex.hancock@example.com>
Co-authored-by: Alex Hancock <alexhancock@block.xyz>
Co-authored-by: Lifei Zhou <lifei@squareup.com>
Co-authored-by: Wes <141185334+wesrblock@users.noreply.github.com>
Co-authored-by: Max Novich <maksymstepanenko1990@gmail.com>
Co-authored-by: Zaki Ali <zaki@squareup.com>
Co-authored-by: Salman Mohammed <smohammed@squareup.com>
Co-authored-by: Kalvin C <kalvinnchau@users.noreply.github.com>
Co-authored-by: Alec Thomas <alec@swapoff.org>
Co-authored-by: lily-de <119957291+lily-de@users.noreply.github.com>
Co-authored-by: kalvinnchau <kalvin@block.xyz>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Rizel Scarlett <rizel@squareup.com>
Co-authored-by: bwrage <bwrage@squareup.com>
Co-authored-by: Kalvin Chau <kalvin@squareup.com>
Co-authored-by: Alice Hau <110418948+ahau-square@users.noreply.github.com>
Co-authored-by: Alistair Gray <ajgray@stripe.com>
Co-authored-by: Nahiyan Khan <nahiyan.khan@gmail.com>
Co-authored-by: Alex Hancock <alexhancock@squareup.com>
Co-authored-by: Nahiyan Khan <nahiyan@squareup.com>
Co-authored-by: marcelle <1852848+laanak08@users.noreply.github.com>
Co-authored-by: Yingjie He <yingjiehe@block.xyz>
Co-authored-by: Yingjie He <yingjiehe@squareup.com>
Co-authored-by: Lily Delalande <ldelalande@block.xyz>
Co-authored-by: Adewale Abati <acekyd01@gmail.com>
Co-authored-by: Ebony Louis <ebony774@gmail.com>
Co-authored-by: Angie Jones <jones.angie@gmail.com>
Co-authored-by: Ebony Louis <55366651+EbonyLouis@users.noreply.github.com>
2025-01-24 13:04:43 -08:00

140 lines
4.8 KiB
Rust

use include_dir::{include_dir, Dir};
use serde::Serialize;
use std::path::PathBuf;
use tera::{Context, Error as TeraError, Tera};
// The prompts directory needs to be embedded in the binary (so it works when distributed)
static PROMPTS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/src/prompts");
pub fn load_prompt<T: Serialize>(template: &str, context_data: &T) -> Result<String, TeraError> {
let mut tera = Tera::default();
tera.add_raw_template("inline_template", template)?;
let context = Context::from_serialize(context_data)?;
let rendered = tera.render("inline_template", &context)?;
Ok(rendered.trim().to_string())
}
pub fn load_prompt_file<T: Serialize>(
template_file: impl Into<PathBuf>,
context_data: &T,
) -> Result<String, TeraError> {
let template_path = template_file.into();
// Get the file content from the embedded directory
let template_content = if let Some(file) = PROMPTS_DIR.get_file(template_path.to_str().unwrap())
{
String::from_utf8_lossy(file.contents()).into_owned()
} else {
return Err(TeraError::chain(
"Failed to find template file",
std::io::Error::new(
std::io::ErrorKind::NotFound,
"Template file not found in embedded directory",
),
));
};
load_prompt(&template_content, context_data)
}
#[cfg(test)]
mod tests {
use super::*;
use mcp_core::tool::Tool;
use serde_json::json;
use std::collections::HashMap;
#[test]
fn test_load_prompt() {
let template = "Hello, {{ name }}! You are {{ age }} years old.";
let mut context = HashMap::new();
context.insert("name".to_string(), "Alice".to_string());
context.insert("age".to_string(), 30.to_string());
let result = load_prompt(template, &context).unwrap();
assert_eq!(result, "Hello, Alice! You are 30 years old.");
}
#[test]
fn test_load_prompt_missing_variable() {
let template = "Hello, {{ name }}! You are {{ age }} years old.";
let mut context = HashMap::new();
context.insert("name".to_string(), "Alice".to_string());
// 'age' is missing from context
let result = load_prompt(template, &context);
assert!(result.is_err());
}
#[test]
fn test_load_prompt_file() {
// since we are embedding the prompts directory, the file path needs to be relative to the prompts directory
let file_path = PathBuf::from("mock.md");
let mut context = HashMap::new();
context.insert("name".to_string(), "Alice".to_string());
context.insert("age".to_string(), 30.to_string());
let result = load_prompt_file(file_path, &context).unwrap();
assert_eq!(
result,
"This prompt is only used for testing.\n\nHello, Alice! You are 30 years old."
);
}
#[test]
fn test_load_prompt_file_missing_file() {
let file_path = PathBuf::from("non_existent_template.txt");
let context: HashMap<String, String> = HashMap::new(); // Add type annotation here
let result = load_prompt_file(file_path, &context);
assert!(result.is_err());
}
#[test]
fn test_load_prompt_with_tools() {
let template = "### Tool Descriptions\n{% for tool in tools %}\n{{tool.name}}: {{tool.description}}{% endfor %}";
let tools = vec![
Tool::new(
"calculator",
"Performs basic math operations",
json!({
"type": "object",
"properties": {
"operation": {"type": "string"},
"numbers": {"type": "array"}
}
}),
),
Tool::new(
"weather",
"Gets weather information",
json!({
"type": "object",
"properties": {
"location": {"type": "string"}
}
}),
),
];
let mut context = HashMap::new();
context.insert("tools".to_string(), tools);
let result = load_prompt(template, &context).unwrap();
let expected = "### Tool Descriptions\n\ncalculator: Performs basic math operations\nweather: Gets weather information";
assert_eq!(result, expected);
}
#[test]
fn test_load_prompt_with_empty_tools() {
let template = "### Tool Descriptions\n{% for tool in tools %}\n{{tool.name}}: {{tool.description}}{% endfor %}";
let tools: Vec<Tool> = vec![];
let mut context = HashMap::new();
context.insert("tools".to_string(), tools);
let result = load_prompt(template, &context).unwrap();
let expected = "### Tool Descriptions";
assert_eq!(result, expected);
}
}