Lambda Inference
Status: Verified | Idempotent: Yes | Coverage: 95%+
Run Command
cargo run --example serverless_lambda_inference
Code
//! # Recipe: Lambda Inference Function
//!
//! Contract: contracts/recipe-iiur-v1.yaml
//! **Category**: Serverless/Lambda
//! **Isolation Level**: Full
//! **Idempotency**: Guaranteed
//! **Dependencies**: None (default features)
//!
//! ## QA Checklist
//! 1. [x] `cargo run` succeeds (Exit Code 0)
//! 2. [x] `cargo test` passes
//! 3. [x] Deterministic output (Verified)
//! 4. [x] No temp files leaked
//! 5. [x] Memory usage stable
//! 6. [x] WASM compatible (N/A)
//! 7. [x] Clippy clean
//! 8. [x] Rustfmt standard
//! 9. [x] No `unwrap()` in logic
//! 10. [x] Proptests pass (100+ cases)
//!
//! ## Learning Objective
//! Deploy model inference as AWS Lambda function (simulated).
//!
//! ## Run Command
//! ```bash
//! cargo run --example serverless_lambda_inference
//! ```
//!
//!
//! ## Format Variants
//! ```bash
//! apr run model.apr # APR native format
//! apr run model.gguf # GGUF (llama.cpp compatible)
//! apr run model.safetensors # SafeTensors (HuggingFace)
//! ```
//! ## References
//! - Schleier-Smith, J. et al. (2021). *What Serverless Computing Is and Should Become*. CACM. DOI: 10.1145/3406011
use apr_cookbook::prelude::*;
use serde::{Deserialize, Serialize};
fn main() -> Result<()> {
let mut ctx = RecipeContext::new("serverless_lambda_inference")?;
println!("=== Recipe: {} ===", ctx.name());
println!("Lambda inference function simulation");
println!();
// Create Lambda runtime context
let lambda_ctx = LambdaContext {
function_name: "fraud-detector-lambda".to_string(),
function_version: "$LATEST".to_string(),
memory_limit_mb: 512,
timeout_seconds: 30,
request_id: "req-abc123".to_string(),
};
println!("Lambda Context:");
println!(" Function: {}", lambda_ctx.function_name);
println!(" Version: {}", lambda_ctx.function_version);
println!(" Memory: {}MB", lambda_ctx.memory_limit_mb);
println!(" Timeout: {}s", lambda_ctx.timeout_seconds);
println!();
// Simulate Lambda invocation
let event = LambdaEvent {
body: InferenceRequest {
inputs: vec![0.5, 0.3, 0.8, 0.1],
},
request_context: RequestContext {
stage: "prod".to_string(),
path: "/infer".to_string(),
},
};
ctx.record_metric("input_size", event.body.inputs.len() as i64);
println!("Event:");
println!(" Inputs: {:?}", event.body.inputs);
println!(" Stage: {}", event.request_context.stage);
println!();
// Handler execution
let response = handler(&lambda_ctx, &event)?;
ctx.record_metric("status_code", i64::from(response.status_code));
ctx.record_float_metric("billed_duration_ms", f64::from(response.billed_duration_ms));
println!("Response:");
println!(" Status: {}", response.status_code);
println!(" Body: {}", response.body);
println!(" Billed duration: {}ms", response.billed_duration_ms);
// Save Lambda metrics
let metrics_path = ctx.path("lambda_metrics.json");
save_metrics(&metrics_path, &lambda_ctx, &response)?;
println!();
println!("Metrics saved to: {:?}", metrics_path);
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct LambdaContext {
function_name: String,
function_version: String,
memory_limit_mb: u32,
timeout_seconds: u32,
request_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct LambdaEvent {
body: InferenceRequest,
request_context: RequestContext,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct InferenceRequest {
inputs: Vec<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct RequestContext {
stage: String,
path: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct LambdaResponse {
status_code: u16,
body: String,
billed_duration_ms: u32,
memory_used_mb: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct InferenceOutput {
predictions: Vec<f32>,
model_version: String,
}
fn handler(ctx: &LambdaContext, event: &LambdaEvent) -> Result<LambdaResponse> {
// Simulate model inference
let predictions: Vec<f32> = event.body.inputs.iter().map(|x| (x * 2.0).tanh()).collect();
let output = InferenceOutput {
predictions,
model_version: "1.0.0".to_string(),
};
let body =
serde_json::to_string(&output).map_err(|e| CookbookError::Serialization(e.to_string()))?;
// Deterministic billing calculation
let billed_duration = 10 + event.body.inputs.len() as u32 * 5;
let memory_used = ctx.memory_limit_mb / 2;
Ok(LambdaResponse {
status_code: 200,
body,
billed_duration_ms: billed_duration,
memory_used_mb: memory_used,
})
}
fn save_metrics(
path: &std::path::Path,
ctx: &LambdaContext,
response: &LambdaResponse,
) -> Result<()> {
#[derive(Serialize)]
struct Metrics<'a> {
function: &'a str,
request_id: &'a str,
status_code: u16,
billed_duration_ms: u32,
memory_used_mb: u32,
memory_limit_mb: u32,
}
let metrics = Metrics {
function: &ctx.function_name,
request_id: &ctx.request_id,
status_code: response.status_code,
billed_duration_ms: response.billed_duration_ms,
memory_used_mb: response.memory_used_mb,
memory_limit_mb: ctx.memory_limit_mb,
};
let json = serde_json::to_string_pretty(&metrics)
.map_err(|e| CookbookError::Serialization(e.to_string()))?;
std::fs::write(path, json)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_handler_success() {
let ctx = LambdaContext {
function_name: "test".to_string(),
function_version: "1".to_string(),
memory_limit_mb: 256,
timeout_seconds: 10,
request_id: "req-1".to_string(),
};
let event = LambdaEvent {
body: InferenceRequest {
inputs: vec![0.5, 0.5],
},
request_context: RequestContext {
stage: "test".to_string(),
path: "/".to_string(),
},
};
let response = handler(&ctx, &event).unwrap();
assert_eq!(response.status_code, 200);
assert!(response.body.contains("predictions"));
}
#[test]
fn test_deterministic_billing() {
let ctx = LambdaContext {
function_name: "test".to_string(),
function_version: "1".to_string(),
memory_limit_mb: 256,
timeout_seconds: 10,
request_id: "req-1".to_string(),
};
let event = LambdaEvent {
body: InferenceRequest {
inputs: vec![1.0, 2.0, 3.0],
},
request_context: RequestContext {
stage: "test".to_string(),
path: "/".to_string(),
},
};
let r1 = handler(&ctx, &event).unwrap();
let r2 = handler(&ctx, &event).unwrap();
assert_eq!(r1.billed_duration_ms, r2.billed_duration_ms);
}
#[test]
fn test_memory_usage() {
let ctx = LambdaContext {
function_name: "test".to_string(),
function_version: "1".to_string(),
memory_limit_mb: 512,
timeout_seconds: 10,
request_id: "req-1".to_string(),
};
let event = LambdaEvent {
body: InferenceRequest { inputs: vec![1.0] },
request_context: RequestContext {
stage: "test".to_string(),
path: "/".to_string(),
},
};
let response = handler(&ctx, &event).unwrap();
assert!(response.memory_used_mb <= ctx.memory_limit_mb);
}
#[test]
fn test_save_metrics() {
let recipe_ctx = RecipeContext::new("test_lambda_metrics").unwrap();
let path = recipe_ctx.path("metrics.json");
let ctx = LambdaContext {
function_name: "test".to_string(),
function_version: "1".to_string(),
memory_limit_mb: 256,
timeout_seconds: 10,
request_id: "req-1".to_string(),
};
let response = LambdaResponse {
status_code: 200,
body: "{}".to_string(),
billed_duration_ms: 10,
memory_used_mb: 128,
};
save_metrics(&path, &ctx, &response).unwrap();
assert!(path.exists());
}
}
#[cfg(test)]
mod proptests {
use super::*;
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn prop_always_returns_200(inputs in proptest::collection::vec(-1.0f32..1.0, 1..20)) {
let ctx = LambdaContext {
function_name: "test".to_string(),
function_version: "1".to_string(),
memory_limit_mb: 256,
timeout_seconds: 10,
request_id: "req-1".to_string(),
};
let event = LambdaEvent {
body: InferenceRequest { inputs },
request_context: RequestContext {
stage: "test".to_string(),
path: "/".to_string(),
},
};
let response = handler(&ctx, &event).unwrap();
prop_assert_eq!(response.status_code, 200);
}
#[test]
fn prop_billing_increases_with_inputs(n in 1usize..50) {
let ctx = LambdaContext {
function_name: "test".to_string(),
function_version: "1".to_string(),
memory_limit_mb: 256,
timeout_seconds: 10,
request_id: "req-1".to_string(),
};
let event = LambdaEvent {
body: InferenceRequest { inputs: vec![1.0; n] },
request_context: RequestContext {
stage: "test".to_string(),
path: "/".to_string(),
},
};
let response = handler(&ctx, &event).unwrap();
let expected = 10 + n as u32 * 5;
prop_assert_eq!(response.billed_duration_ms, expected);
}
}
}