Skip to main content

agentforge_core/
scenario.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5/// A single test scenario generated for an agent.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct Scenario {
8    pub id: Uuid,
9    pub agent_id: Uuid,
10    pub input: ScenarioInput,
11    pub expected: ScenarioExpected,
12    pub difficulty: DifficultyTier,
13    pub domain: Option<String>,
14    pub source: ScenarioSource,
15    pub tags: Vec<String>,
16    pub created_at: DateTime<Utc>,
17}
18
19/// The input side of a scenario.
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct ScenarioInput {
22    /// User message or task description.
23    pub user_message: String,
24    /// Prior conversation turns, if any (multi-turn scenarios).
25    pub conversation_history: Vec<ConversationTurn>,
26    /// Any context injected into the conversation (e.g., user data).
27    pub context: Option<serde_json::Value>,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct ConversationTurn {
32    pub role: ConversationRole,
33    pub content: String,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
37#[serde(rename_all = "snake_case")]
38pub enum ConversationRole {
39    User,
40    Assistant,
41    System,
42    Tool,
43}
44
45/// The expected outcomes for a scenario.
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct ScenarioExpected {
48    /// Tool calls that should be made (in order if sequence matters).
49    pub tool_calls: Vec<ExpectedToolCall>,
50    /// JSON Schema the final output must validate against.
51    pub output_schema: Option<serde_json::Value>,
52    /// Natural language description of what passing looks like (for LLM judge).
53    pub pass_criteria: String,
54    /// Minimum number of turns expected.
55    pub min_turns: Option<u32>,
56    /// Maximum number of turns allowed (looping detection).
57    pub max_turns: Option<u32>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct ExpectedToolCall {
62    pub tool_name: String,
63    /// Whether this tool MUST be called (required) or may be called (optional).
64    pub required: bool,
65    /// Partial argument schema to validate against (optional).
66    pub argument_schema: Option<serde_json::Value>,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
70#[serde(rename_all = "snake_case")]
71pub enum DifficultyTier {
72    Easy,
73    Medium,
74    Hard,
75    Edge,
76}
77
78impl std::fmt::Display for DifficultyTier {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        match self {
81            DifficultyTier::Easy => write!(f, "easy"),
82            DifficultyTier::Medium => write!(f, "medium"),
83            DifficultyTier::Hard => write!(f, "hard"),
84            DifficultyTier::Edge => write!(f, "edge"),
85        }
86    }
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
90#[serde(rename_all = "snake_case")]
91pub enum ScenarioSource {
92    SchemaDerived,
93    Adversarial,
94    DomainSeeded,
95    Manual,
96}
97
98impl std::fmt::Display for ScenarioSource {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        match self {
101            ScenarioSource::SchemaDerived => write!(f, "schema_derived"),
102            ScenarioSource::Adversarial => write!(f, "adversarial"),
103            ScenarioSource::DomainSeeded => write!(f, "domain_seeded"),
104            ScenarioSource::Manual => write!(f, "manual"),
105        }
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn difficulty_tier_display() {
115        assert_eq!(DifficultyTier::Easy.to_string(), "easy");
116        assert_eq!(DifficultyTier::Edge.to_string(), "edge");
117    }
118
119    #[test]
120    fn scenario_source_display() {
121        assert_eq!(ScenarioSource::SchemaDerived.to_string(), "schema_derived");
122        assert_eq!(ScenarioSource::DomainSeeded.to_string(), "domain_seeded");
123    }
124}