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.
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
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
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();
}