use super::enums::{ObjectiveDirection, ObjectiveKey, RewardScope, RewardSource, RewardType};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObjectiveSpec {
pub key: ObjectiveKey,
pub direction: ObjectiveDirection,
#[serde(default)]
pub units: Option<String>,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub target: Option<f64>,
#[serde(default)]
pub min_value: Option<f64>,
#[serde(default)]
pub max_value: Option<f64>,
}
impl ObjectiveSpec {
pub fn new(key: ObjectiveKey, direction: ObjectiveDirection) -> Self {
Self {
key,
direction,
units: None,
description: None,
target: None,
min_value: None,
max_value: None,
}
}
pub fn maximize_reward() -> Self {
Self::new(ObjectiveKey::Reward, ObjectiveDirection::Maximize)
}
pub fn minimize_cost() -> Self {
Self::new(ObjectiveKey::CostUsd, ObjectiveDirection::Minimize).with_units("usd")
}
pub fn minimize_latency() -> Self {
Self::new(ObjectiveKey::LatencyMs, ObjectiveDirection::Minimize).with_units("ms")
}
pub fn with_units(mut self, units: impl Into<String>) -> Self {
self.units = Some(units.into());
self
}
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = Some(desc.into());
self
}
pub fn satisfies_constraints(&self, value: f64) -> bool {
if let Some(min) = self.min_value {
if value < min {
return false;
}
}
if let Some(max) = self.max_value {
if value > max {
return false;
}
}
true
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RewardObservation {
pub value: f64,
#[serde(default)]
pub reward_type: RewardType,
#[serde(default)]
pub scope: RewardScope,
#[serde(default)]
pub source: RewardSource,
#[serde(default)]
pub objective_key: ObjectiveKey,
#[serde(default)]
pub event_id: Option<String>,
#[serde(default)]
pub turn_number: Option<i32>,
#[serde(default)]
pub metadata: HashMap<String, Value>,
}
impl RewardObservation {
pub fn new(value: f64) -> Self {
Self {
value,
reward_type: RewardType::default(),
scope: RewardScope::default(),
source: RewardSource::default(),
objective_key: ObjectiveKey::default(),
event_id: None,
turn_number: None,
metadata: HashMap::new(),
}
}
pub fn outcome(value: f64) -> Self {
Self::new(value).with_scope(RewardScope::Outcome)
}
pub fn event(value: f64, event_id: impl Into<String>) -> Self {
let mut obs = Self::new(value).with_scope(RewardScope::Event);
obs.event_id = Some(event_id.into());
obs
}
pub fn with_type(mut self, reward_type: RewardType) -> Self {
self.reward_type = reward_type;
self
}
pub fn with_scope(mut self, scope: RewardScope) -> Self {
self.scope = scope;
self
}
pub fn with_source(mut self, source: RewardSource) -> Self {
self.source = source;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutcomeObjectiveAssignment {
pub objectives: HashMap<String, f64>,
#[serde(default)]
pub session_id: Option<String>,
#[serde(default)]
pub trace_id: Option<String>,
#[serde(default)]
pub metadata: HashMap<String, Value>,
}
impl OutcomeObjectiveAssignment {
pub fn new() -> Self {
Self {
objectives: HashMap::new(),
session_id: None,
trace_id: None,
metadata: HashMap::new(),
}
}
pub fn with_objective(mut self, key: impl Into<String>, value: f64) -> Self {
self.objectives.insert(key.into(), value);
self
}
pub fn with_session(mut self, session_id: impl Into<String>) -> Self {
self.session_id = Some(session_id.into());
self
}
}
impl Default for OutcomeObjectiveAssignment {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventObjectiveAssignment {
pub event_id: String,
pub objectives: HashMap<String, f64>,
#[serde(default)]
pub turn_number: Option<i32>,
#[serde(default)]
pub metadata: HashMap<String, Value>,
}
impl EventObjectiveAssignment {
pub fn new(event_id: impl Into<String>) -> Self {
Self {
event_id: event_id.into(),
objectives: HashMap::new(),
turn_number: None,
metadata: HashMap::new(),
}
}
pub fn with_objective(mut self, key: impl Into<String>, value: f64) -> Self {
self.objectives.insert(key.into(), value);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InstanceObjectiveAssignment {
pub instance_id: String,
pub objectives: HashMap<String, f64>,
#[serde(default)]
pub split: Option<String>,
#[serde(default)]
pub metadata: HashMap<String, Value>,
}
impl InstanceObjectiveAssignment {
pub fn new(instance_id: impl Into<String>) -> Self {
Self {
instance_id: instance_id.into(),
objectives: HashMap::new(),
split: None,
metadata: HashMap::new(),
}
}
pub fn with_objective(mut self, key: impl Into<String>, value: f64) -> Self {
self.objectives.insert(key.into(), value);
self
}
pub fn with_split(mut self, split: impl Into<String>) -> Self {
self.split = Some(split.into());
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_objective_spec() {
let spec = ObjectiveSpec::maximize_reward();
assert_eq!(spec.key, ObjectiveKey::Reward);
assert_eq!(spec.direction, ObjectiveDirection::Maximize);
}
#[test]
fn test_objective_constraints() {
let mut spec = ObjectiveSpec::minimize_latency();
spec.max_value = Some(1000.0);
assert!(spec.satisfies_constraints(500.0));
assert!(!spec.satisfies_constraints(1500.0));
}
#[test]
fn test_reward_observation() {
let obs = RewardObservation::outcome(0.95)
.with_type(RewardType::Sparse)
.with_source(RewardSource::Verifier);
assert_eq!(obs.value, 0.95);
assert_eq!(obs.scope, RewardScope::Outcome);
assert_eq!(obs.source, RewardSource::Verifier);
}
#[test]
fn test_outcome_assignment() {
let assignment = OutcomeObjectiveAssignment::new()
.with_objective("reward", 0.85)
.with_objective("cost_usd", 0.002)
.with_session("session-123");
assert_eq!(assignment.objectives.get("reward"), Some(&0.85));
assert_eq!(assignment.session_id, Some("session-123".to_string()));
}
#[test]
fn test_serde() {
let obs = RewardObservation::outcome(1.0);
let json = serde_json::to_string(&obs).unwrap();
let parsed: RewardObservation = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.value, 1.0);
}
}