mirror of
https://github.com/openai/codex.git
synced 2026-05-17 09:43:19 +00:00
Keep guardian output schema strict
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -262,7 +262,6 @@ pub enum ResponsesWsRequest {
|
||||
pub fn create_text_param_for_request(
|
||||
verbosity: Option<VerbosityConfig>,
|
||||
output_schema: &Option<Value>,
|
||||
output_schema_strict: bool,
|
||||
) -> Option<TextControls> {
|
||||
if verbosity.is_none() && output_schema.is_none() {
|
||||
return None;
|
||||
@@ -272,7 +271,7 @@ pub fn create_text_param_for_request(
|
||||
verbosity: verbosity.map(std::convert::Into::into),
|
||||
format: output_schema.as_ref().map(|schema| TextFormat {
|
||||
r#type: TextFormatType::JsonSchema,
|
||||
strict: output_schema_strict,
|
||||
strict: true,
|
||||
schema: schema.clone(),
|
||||
name: "codex_output_schema".to_string(),
|
||||
}),
|
||||
|
||||
@@ -445,11 +445,7 @@ impl ModelClient {
|
||||
}
|
||||
None
|
||||
};
|
||||
let text = create_text_param_for_request(
|
||||
verbosity,
|
||||
&prompt.output_schema,
|
||||
prompt.output_schema_strict,
|
||||
);
|
||||
let text = create_text_param_for_request(verbosity, &prompt.output_schema);
|
||||
let payload = ApiCompactionInput {
|
||||
model: &model_info.slug,
|
||||
input: &input,
|
||||
@@ -853,11 +849,7 @@ impl ModelClientSession {
|
||||
}
|
||||
None
|
||||
};
|
||||
let text = create_text_param_for_request(
|
||||
verbosity,
|
||||
&prompt.output_schema,
|
||||
prompt.output_schema_strict,
|
||||
);
|
||||
let text = create_text_param_for_request(verbosity, &prompt.output_schema);
|
||||
let prompt_cache_key = Some(self.client.state.conversation_id.to_string());
|
||||
let request = ResponsesApiRequest {
|
||||
model: model_info.slug.clone(),
|
||||
|
||||
@@ -23,7 +23,7 @@ pub const REVIEW_EXIT_INTERRUPTED_TMPL: &str =
|
||||
include_str!("../templates/review/exit_interrupted.xml");
|
||||
|
||||
/// API request payload for a single model turn
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Prompt {
|
||||
/// Conversation context input items.
|
||||
pub input: Vec<ResponseItem>,
|
||||
@@ -42,23 +42,6 @@ pub struct Prompt {
|
||||
|
||||
/// Optional the output schema for the model's response.
|
||||
pub output_schema: Option<Value>,
|
||||
|
||||
/// Whether the Responses API should strictly validate `output_schema`.
|
||||
pub output_schema_strict: bool,
|
||||
}
|
||||
|
||||
impl Default for Prompt {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
input: Vec::new(),
|
||||
tools: Vec::new(),
|
||||
parallel_tool_calls: false,
|
||||
base_instructions: BaseInstructions::default(),
|
||||
personality: None,
|
||||
output_schema: None,
|
||||
output_schema_strict: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Prompt {
|
||||
|
||||
@@ -52,12 +52,9 @@ fn serializes_text_schema_with_strict_format() {
|
||||
},
|
||||
"required": ["answer"],
|
||||
});
|
||||
let text_controls = create_text_param_for_request(
|
||||
/*verbosity*/ None,
|
||||
&Some(schema.clone()),
|
||||
/*output_schema_strict*/ true,
|
||||
)
|
||||
.expect("text controls");
|
||||
let text_controls =
|
||||
create_text_param_for_request(/*verbosity*/ None, &Some(schema.clone()))
|
||||
.expect("text controls");
|
||||
|
||||
let req = ResponsesApiRequest {
|
||||
model: "gpt-5.1".to_string(),
|
||||
@@ -93,29 +90,6 @@ fn serializes_text_schema_with_strict_format() {
|
||||
assert_eq!(format.get("schema"), Some(&schema));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serializes_text_schema_with_non_strict_format() {
|
||||
let schema = serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"answer": {"type": "string"},
|
||||
"rationale": {"type": "string"}
|
||||
},
|
||||
"required": ["answer"],
|
||||
"additionalProperties": false
|
||||
});
|
||||
let text_controls = create_text_param_for_request(
|
||||
/*verbosity*/ None,
|
||||
&Some(schema.clone()),
|
||||
/*output_schema_strict*/ false,
|
||||
)
|
||||
.expect("text controls");
|
||||
|
||||
let format = text_controls.format.expect("format field");
|
||||
assert!(!format.strict);
|
||||
assert_eq!(format.schema, schema);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn omits_text_when_not_set() {
|
||||
let input: Vec<ResponseItem> = vec![];
|
||||
|
||||
@@ -156,7 +156,6 @@ async fn run_remote_compact_task_inner_impl(
|
||||
base_instructions,
|
||||
personality: turn_context.personality,
|
||||
output_schema: None,
|
||||
output_schema_strict: true,
|
||||
};
|
||||
|
||||
let mut new_history = sess
|
||||
|
||||
@@ -551,22 +551,22 @@ pub(crate) fn guardian_output_schema() -> Value {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"risk_level": {
|
||||
"type": "string",
|
||||
"enum": ["low", "medium", "high", "critical"]
|
||||
"type": ["string", "null"],
|
||||
"enum": ["low", "medium", "high", "critical", null]
|
||||
},
|
||||
"user_authorization": {
|
||||
"type": "string",
|
||||
"enum": ["unknown", "low", "medium", "high"]
|
||||
"type": ["string", "null"],
|
||||
"enum": ["unknown", "low", "medium", "high", null]
|
||||
},
|
||||
"outcome": {
|
||||
"type": "string",
|
||||
"enum": ["allow", "deny"]
|
||||
},
|
||||
"rationale": {
|
||||
"type": "string"
|
||||
"type": ["string", "null"]
|
||||
}
|
||||
},
|
||||
"required": ["outcome"]
|
||||
"required": ["risk_level", "user_authorization", "outcome", "rationale"]
|
||||
})
|
||||
}
|
||||
|
||||
@@ -575,14 +575,14 @@ pub(crate) fn guardian_output_schema() -> Value {
|
||||
fn guardian_output_contract_prompt() -> &'static str {
|
||||
r#"You may use read-only tool checks to gather any additional context you need before deciding. When you are ready to answer, your final message must be strict JSON.
|
||||
|
||||
For low-risk actions, give the final answer directly: {"outcome":"allow"}.
|
||||
For low-risk actions, use null for optional details: {"risk_level":null,"user_authorization":null,"outcome":"allow","rationale":null}.
|
||||
|
||||
For anything else, use this JSON schema:
|
||||
{
|
||||
"risk_level": "low" | "medium" | "high" | "critical",
|
||||
"user_authorization": "unknown" | "low" | "medium" | "high",
|
||||
"risk_level": "low" | "medium" | "high" | "critical" | null,
|
||||
"user_authorization": "unknown" | "low" | "medium" | "high" | null,
|
||||
"outcome": "allow" | "deny",
|
||||
"rationale": string
|
||||
"rationale": string | null
|
||||
}"#
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -876,7 +876,25 @@ fn parse_guardian_assessment_treats_bare_allow_as_low_risk() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guardian_output_schema_requires_only_outcome_and_allows_optional_details() {
|
||||
fn parse_guardian_assessment_treats_nullable_allow_as_low_risk() {
|
||||
let parsed = parse_guardian_assessment(Some(
|
||||
r#"{"risk_level":null,"user_authorization":null,"outcome":"allow","rationale":null}"#,
|
||||
))
|
||||
.expect("guardian assessment");
|
||||
|
||||
assert_eq!(
|
||||
parsed,
|
||||
GuardianAssessment {
|
||||
risk_level: GuardianRiskLevel::Low,
|
||||
user_authorization: GuardianUserAuthorization::Unknown,
|
||||
outcome: GuardianAssessmentOutcome::Allow,
|
||||
rationale: "Guardian returned a low-risk allow decision.".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guardian_output_schema_uses_strict_nullable_details() {
|
||||
let schema = guardian_output_schema();
|
||||
|
||||
assert_eq!(
|
||||
@@ -886,22 +904,22 @@ fn guardian_output_schema_requires_only_outcome_and_allows_optional_details() {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"risk_level": {
|
||||
"type": "string",
|
||||
"enum": ["low", "medium", "high", "critical"]
|
||||
"type": ["string", "null"],
|
||||
"enum": ["low", "medium", "high", "critical", null]
|
||||
},
|
||||
"user_authorization": {
|
||||
"type": "string",
|
||||
"enum": ["unknown", "low", "medium", "high"]
|
||||
"type": ["string", "null"],
|
||||
"enum": ["unknown", "low", "medium", "high", null]
|
||||
},
|
||||
"outcome": {
|
||||
"type": "string",
|
||||
"enum": ["allow", "deny"]
|
||||
},
|
||||
"rationale": {
|
||||
"type": "string"
|
||||
"type": ["string", "null"]
|
||||
}
|
||||
},
|
||||
"required": ["outcome"]
|
||||
"required": ["risk_level", "user_authorization", "outcome", "rationale"]
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -980,7 +998,7 @@ async fn guardian_review_request_layout_matches_model_visible_request_snapshot()
|
||||
let request_body = request.body_json();
|
||||
assert_eq!(
|
||||
request_body.pointer("/text/format/strict"),
|
||||
Some(&serde_json::json!(false))
|
||||
Some(&serde_json::json!(true))
|
||||
);
|
||||
assert_eq!(
|
||||
request_body.pointer("/text/format/schema"),
|
||||
@@ -989,22 +1007,22 @@ async fn guardian_review_request_layout_matches_model_visible_request_snapshot()
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"risk_level": {
|
||||
"type": "string",
|
||||
"enum": ["low", "medium", "high", "critical"]
|
||||
"type": ["string", "null"],
|
||||
"enum": ["low", "medium", "high", "critical", null]
|
||||
},
|
||||
"user_authorization": {
|
||||
"type": "string",
|
||||
"enum": ["unknown", "low", "medium", "high"]
|
||||
"type": ["string", "null"],
|
||||
"enum": ["unknown", "low", "medium", "high", null]
|
||||
},
|
||||
"outcome": {
|
||||
"type": "string",
|
||||
"enum": ["allow", "deny"]
|
||||
},
|
||||
"rationale": {
|
||||
"type": "string"
|
||||
"type": ["string", "null"]
|
||||
}
|
||||
},
|
||||
"required": ["outcome"]
|
||||
"required": ["risk_level", "user_authorization", "outcome", "rationale"]
|
||||
}))
|
||||
);
|
||||
let mut settings = Settings::clone_current();
|
||||
|
||||
@@ -341,7 +341,6 @@ mod job {
|
||||
},
|
||||
personality: None,
|
||||
output_schema: Some(output_schema()),
|
||||
output_schema_strict: true,
|
||||
};
|
||||
|
||||
let mut client_session = session.services.model_client.new_session();
|
||||
|
||||
@@ -971,9 +971,6 @@ pub(crate) fn build_prompt(
|
||||
base_instructions,
|
||||
personality: turn_context.personality,
|
||||
output_schema: turn_context.final_output_json_schema.clone(),
|
||||
output_schema_strict: !crate::guardian::is_guardian_reviewer_source(
|
||||
&turn_context.session_source,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user