use std::time::Duration;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use crate::ids::RequestId;
use crate::purpose::ModelPurpose;
use crate::request::{Message, ModelRequest, OutputSpec, ToolSpec};
pub const DUMMY_API_KEY: &str = "sk-turnframe-conformance-DUMMY-0000000000000000";
pub const SCHEMA_NAME: &str = "turnframe_conformance_plan";
pub const SCHEMA_MARKER: &str = "turnframe_conformance_marker";
pub const TOOL_NAME: &str = "turnframe_conformance_read";
pub const EXPECTED_CALL_ID: &str = "turnframe-call-0001";
pub const MALFORMED_JSON: &str = "{\"acts\": [{\"operation\": \"set_travel_date\", ";
pub const REFUSAL_TEXT: &str = "I cannot help with that request.";
pub const SLOW_RESPONSE_DELAY: Duration = Duration::from_secs(5);
pub const SHORT_TIMEOUT: Duration = Duration::from_millis(150);
pub const RETRY_AFTER_SECONDS: u64 = 3;
pub const USAGE_INPUT_TOKENS: u64 = 42;
pub const USAGE_CACHED_TOKENS: u64 = 30;
pub const USAGE_OUTPUT_TOKENS: u64 = 7;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConformanceAct {
pub operation: String,
pub target: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConformancePlan {
pub acts: Vec<ConformanceAct>,
}
#[must_use]
pub fn plan_schema() -> Value {
json!({
"type": "object",
"properties": {
"acts": {
"type": "array",
"items": {
"type": "object",
"properties": {
"operation": {"type": "string"},
"target": {"type": "string"},
SCHEMA_MARKER: {"type": "boolean"}
},
"required": ["operation", "target"],
"additionalProperties": false
}
}
},
"required": ["acts"],
"additionalProperties": false
})
}
#[must_use]
pub fn valid_plan() -> Value {
json!({"acts": [{"operation": "set_travel_date", "target": "tok_1"}]})
}
#[must_use]
pub fn two_act_plan() -> Value {
json!({"acts": [
{"operation": "set_travel_date", "target": "tok_1"},
{"operation": "set_amount", "target": "tok_2"}
]})
}
#[must_use]
pub fn plan_with_unknown_field() -> Value {
json!({"acts": [
{"operation": "set_travel_date", "target": "tok_1"},
{"operation": "set_amount", "target": "tok_2", "force": true}
]})
}
#[must_use]
pub fn plan_with_missing_field() -> Value {
json!({"acts": [
{"operation": "set_travel_date", "target": "tok_1"},
{"operation": "set_amount"}
]})
}
#[must_use]
pub fn read_tool() -> ToolSpec {
ToolSpec::new(
TOOL_NAME,
"Loads the current state of a case. Read-only.",
json!({
"type": "object",
"properties": {"target": {"type": "string"}},
"required": ["target"],
"additionalProperties": false
}),
)
}
#[must_use]
pub fn structured_request() -> ModelRequest {
ModelRequest::new(ModelPurpose::Extract)
.with_request_id(RequestId::new())
.with_system("Propose acts. Never invent identifiers.")
.with_message(Message::user("sposta il volo al 30"))
.with_output(OutputSpec::json(SCHEMA_NAME, plan_schema()))
}
#[must_use]
pub fn tool_request() -> ModelRequest {
ModelRequest::new(ModelPurpose::Investigate)
.with_request_id(RequestId::new())
.with_message(Message::user("che stato ha la pratica?"))
.with_output(OutputSpec::ToolCalls)
.with_tools(vec![read_tool()])
}
#[must_use]
pub fn narration_request() -> ModelRequest {
ModelRequest::new(ModelPurpose::Acknowledge)
.with_request_id(RequestId::new())
.with_message(Message::user("riassumi in una frase"))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::structured::{CompiledSchema, StructuredOutputError, parse_structured_value};
fn schema() -> CompiledSchema {
match CompiledSchema::compile(&plan_schema()) {
Ok(schema) => schema,
Err(error) => panic!("the suite's own schema must compile: {error}"),
}
}
#[test]
fn the_corpus_matches_its_own_schema() {
let schema = schema();
let plan: ConformancePlan = parse_structured_value(&valid_plan(), &schema).unwrap();
assert_eq!(plan.acts.len(), 1);
let two: ConformancePlan = parse_structured_value(&two_act_plan(), &schema).unwrap();
assert_eq!(two.acts.len(), 2);
assert_eq!(two.acts[1].operation, "set_amount");
}
#[test]
fn the_broken_payloads_break_in_the_intended_way() {
let schema = schema();
assert!(matches!(
parse_structured_value::<ConformancePlan>(&plan_with_unknown_field(), &schema),
Err(StructuredOutputError::UnknownField { .. })
));
assert!(matches!(
parse_structured_value::<ConformancePlan>(&plan_with_missing_field(), &schema),
Err(StructuredOutputError::MissingField { .. })
));
assert!(serde_json::from_str::<Value>(MALFORMED_JSON).is_err());
}
#[test]
fn the_marker_lives_only_in_the_schema() {
assert!(plan_schema().to_string().contains(SCHEMA_MARKER));
for payload in [valid_plan(), two_act_plan(), plan_with_missing_field()] {
assert!(!payload.to_string().contains(SCHEMA_MARKER));
}
}
#[test]
fn requests_carry_what_each_check_needs() {
assert!(structured_request().output.is_structured());
assert_eq!(tool_request().tools.len(), 1);
assert!(narration_request().tools.is_empty());
assert!(
SHORT_TIMEOUT < SLOW_RESPONSE_DELAY,
"the slow fixture must outlast the deadline"
);
}
#[test]
fn the_cached_usage_corpus_can_catch_a_net_figure() {
let (input, cached, output) =
(USAGE_INPUT_TOKENS, USAGE_CACHED_TOKENS, USAGE_OUTPUT_TOKENS);
assert!(cached < input, "the cache is a subset of the prompt");
assert!(
cached > input - cached,
"a net figure must break the contract, or the row proves nothing"
);
assert!(output > 0, "an unreported usage is not a usage");
}
}