RRust By Example

LLM Rust Maintainability

Building maintainable LLM applications in Rust: prompt versioning, model configuration management, A/B testing infrastructure, and keeping LLM code testable and readable.

Topic: Llm Rust

Search intent: High-intent search: "rust llm code maintainability prompts"

LLM Rust Maintainability

Prompt versioning

Prompts are code. Version them like code.

rust
use std::collections::HashMap;

/// Versioned prompt template
#[derive(Debug, Clone)]
struct PromptVersion {
    version: String,
    template: String,
    variables: Vec<String>,
    notes: String,
}

impl PromptVersion {
    fn render(&self, vars: &HashMap<&str, &str>) -> Result<String, String> {
        let mut result = self.template.clone();
        for required in &self.variables {
            let value = vars.get(required.as_str())
                .ok_or_else(|| format!("Missing variable: {}", required))?;
            result = result.replace(&format!("{{{{{}}}}}", required), value);
        }
        Ok(result)
    }
}

/// Prompt registry with version management
struct PromptRegistry {
    prompts: HashMap<String, Vec<PromptVersion>>,
}

impl PromptRegistry {
    fn new() -> Self { Self { prompts: HashMap::new() } }

    fn register(&mut self, name: &str, version: PromptVersion) {
        self.prompts.entry(name.to_string()).or_default().push(version);
    }

    fn get_latest(&self, name: &str) -> Option<&PromptVersion> {
        self.prompts.get(name)?.last()
    }

    fn get_version(&self, name: &str, version: &str) -> Option<&PromptVersion> {
        self.prompts.get(name)?
            .iter()
            .find(|v| v.version == version)
    }
}

fn main() {
    let mut registry = PromptRegistry::new();

    registry.register("code_review", PromptVersion {
        version: "1.0.0".to_string(),
        template: "Review this {{language}} code for {{focus}}:\n\n{{code}}".to_string(),
        variables: vec!["language".to_string(), "focus".to_string(), "code".to_string()],
        notes: "Initial version".to_string(),
    });

    registry.register("code_review", PromptVersion {
        version: "1.1.0".to_string(),
        template: "You are a {{language}} expert. Review for {{focus}}. \
            Be concise. Format as JSON with keys: issues, score, summary.\n\nCode:\n{{code}}".to_string(),
        variables: vec!["language".to_string(), "focus".to_string(), "code".to_string()],
        notes: "Added JSON output instruction, improved persona".to_string(),
    });

    let mut vars = HashMap::new();
    vars.insert("language", "Rust");
    vars.insert("focus", "correctness and safety");
    vars.insert("code", "fn main() { let x = vec![1,2,3]; println!(\"{}\", x[10]); }");

    if let Some(prompt) = registry.get_latest("code_review") {
        println!("Version: {}", prompt.version);
        println!("Notes: {}", prompt.notes);
        println!("\nRendered:\n{}", prompt.render(&vars).unwrap());
    }
}

Model configuration management

rust
use serde::{Deserialize, Serialize};

/// All model parameters in one place — no magic constants scattered in code
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ModelConfig {
    model_id: String,
    max_tokens: u32,
    temperature: f32,
    top_p: f32,
    frequency_penalty: f32,
    presence_penalty: f32,
    timeout_secs: u64,
}

impl ModelConfig {
    /// Deterministic config (temperature=0) for classification/extraction
    fn deterministic(model: &str) -> Self {
        Self {
            model_id: model.to_string(),
            max_tokens: 256,
            temperature: 0.0,
            top_p: 1.0,
            frequency_penalty: 0.0,
            presence_penalty: 0.0,
            timeout_secs: 15,
        }
    }

    /// Creative config for generation tasks
    fn creative(model: &str) -> Self {
        Self {
            model_id: model.to_string(),
            max_tokens: 1024,
            temperature: 0.8,
            top_p: 0.95,
            frequency_penalty: 0.3,
            presence_penalty: 0.3,
            timeout_secs: 60,
        }
    }

    /// Long context config for document processing
    fn long_context(model: &str) -> Self {
        Self {
            model_id: model.to_string(),
            max_tokens: 4096,
            temperature: 0.3,
            top_p: 1.0,
            frequency_penalty: 0.0,
            presence_penalty: 0.0,
            timeout_secs: 120,
        }
    }
}

fn main() {
    let configs = vec![
        ("Classification", ModelConfig::deterministic("gpt-4o-mini")),
        ("Creative writing", ModelConfig::creative("gpt-4o")),
        ("Document summary", ModelConfig::long_context("gpt-4o")),
    ];

    for (name, config) in &configs {
        println!(
            "{}: model={} temp={} max_tokens={}",
            name, config.model_id, config.temperature, config.max_tokens
        );
    }
}

A/B testing prompt variants

rust
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;

#[derive(Debug, Clone)]
struct PromptVariant {
    id: String,
    content: String,
    traffic_weight: u32,
}

struct PromptABTest {
    variants: Vec<PromptVariant>,
    results: Arc<Mutex<HashMap<String, Vec<f32>>>>, // variant_id → quality scores
}

impl PromptABTest {
    fn new(variants: Vec<PromptVariant>) -> Self {
        Self {
            variants,
            results: Arc::new(Mutex::new(HashMap::new())),
        }
    }

    /// Deterministically assign user to variant (same user always gets same variant)
    fn assign_variant(&self, user_id: &str) -> &PromptVariant {
        let mut h = DefaultHasher::new();
        user_id.hash(&mut h);
        let hash = h.finish();

        let total_weight: u32 = self.variants.iter().map(|v| v.traffic_weight).sum();
        let mut pick = (hash % total_weight as u64) as u32 + 1;

        for variant in &self.variants {
            if pick <= variant.traffic_weight { return variant; }
            pick -= variant.traffic_weight;
        }
        &self.variants[0]
    }

    fn record_score(&self, variant_id: &str, score: f32) {
        self.results.lock().unwrap()
            .entry(variant_id.to_string())
            .or_default()
            .push(score);
    }

    fn report(&self) {
        let results = self.results.lock().unwrap();
        for variant in &self.variants {
            if let Some(scores) = results.get(&variant.id) {
                let avg = scores.iter().sum::<f32>() / scores.len() as f32;
                println!("Variant '{}': n={} avg_score={:.2}", variant.id, scores.len(), avg);
            }
        }
    }
}

fn main() {
    let test = PromptABTest::new(vec![
        PromptVariant { id: "control".to_string(), content: "Answer the question: {{q}}".to_string(), traffic_weight: 50 },
        PromptVariant { id: "verbose".to_string(), content: "Please provide a detailed answer to: {{q}}".to_string(), traffic_weight: 25 },
        PromptVariant { id: "concise".to_string(), content: "One sentence: {{q}}".to_string(), traffic_weight: 25 },
    ]);

    // Simulate 100 users
    for i in 0..100 {
        let user = format!("user-{}", i);
        let variant = test.assign_variant(&user);
        // Simulate scoring (in production: human eval or automated metrics)
        let score = 0.7 + (i as f32 % 3.0) * 0.1;
        test.record_score(&variant.id, score);
    }

    test.report();
}

Related reading

Related Guides

Continue in This Topic

More Rust Guides