mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat(core) update Personality on turn (#9644)
## Summary Support updating Personality mid-Thread via UserTurn/OverwriteTurn. This is explicitly unused by the clients so far, to simplify PRs - app-server and tui implementations will be follow-ups. ## Testing - [x] added integration tests
This commit is contained in:
@@ -166,6 +166,7 @@ use crate::util::backoff;
|
||||
use codex_async_utils::OrCancelExt;
|
||||
use codex_otel::OtelManager;
|
||||
use codex_protocol::config_types::CollaborationMode;
|
||||
use codex_protocol::config_types::Personality;
|
||||
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::DeveloperInstructions;
|
||||
@@ -301,6 +302,7 @@ impl Codex {
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions,
|
||||
personality: config.model_personality,
|
||||
base_instructions,
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.approval_policy.clone(),
|
||||
@@ -415,6 +417,7 @@ pub(crate) struct TurnContext {
|
||||
pub(crate) developer_instructions: Option<String>,
|
||||
pub(crate) compact_prompt: Option<String>,
|
||||
pub(crate) user_instructions: Option<String>,
|
||||
pub(crate) personality: Option<Personality>,
|
||||
pub(crate) approval_policy: AskForApproval,
|
||||
pub(crate) sandbox_policy: SandboxPolicy,
|
||||
pub(crate) shell_environment_policy: ShellEnvironmentPolicy,
|
||||
@@ -454,6 +457,9 @@ pub(crate) struct SessionConfiguration {
|
||||
/// Model instructions that are appended to the base instructions.
|
||||
user_instructions: Option<String>,
|
||||
|
||||
/// Personality preference for the model.
|
||||
personality: Option<Personality>,
|
||||
|
||||
/// Base instructions for the session.
|
||||
base_instructions: String,
|
||||
|
||||
@@ -489,6 +495,9 @@ impl SessionConfiguration {
|
||||
if let Some(summary) = updates.reasoning_summary {
|
||||
next_configuration.model_reasoning_summary = summary;
|
||||
}
|
||||
if let Some(personality) = updates.personality {
|
||||
next_configuration.personality = Some(personality);
|
||||
}
|
||||
if let Some(approval_policy) = updates.approval_policy {
|
||||
next_configuration.approval_policy.set(approval_policy)?;
|
||||
}
|
||||
@@ -510,6 +519,7 @@ pub(crate) struct SessionSettingsUpdate {
|
||||
pub(crate) collaboration_mode: Option<CollaborationMode>,
|
||||
pub(crate) reasoning_summary: Option<ReasoningSummaryConfig>,
|
||||
pub(crate) final_output_json_schema: Option<Option<Value>>,
|
||||
pub(crate) personality: Option<Personality>,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
@@ -521,6 +531,7 @@ impl Session {
|
||||
per_turn_config.model_reasoning_effort =
|
||||
session_configuration.collaboration_mode.reasoning_effort();
|
||||
per_turn_config.model_reasoning_summary = session_configuration.model_reasoning_summary;
|
||||
per_turn_config.model_personality = session_configuration.personality;
|
||||
per_turn_config.features = config.features.clone();
|
||||
per_turn_config
|
||||
}
|
||||
@@ -566,6 +577,7 @@ impl Session {
|
||||
developer_instructions: session_configuration.developer_instructions.clone(),
|
||||
compact_prompt: session_configuration.compact_prompt.clone(),
|
||||
user_instructions: session_configuration.user_instructions.clone(),
|
||||
personality: session_configuration.personality,
|
||||
approval_policy: session_configuration.approval_policy.value(),
|
||||
sandbox_policy: session_configuration.sandbox_policy.get().clone(),
|
||||
shell_environment_policy: per_turn_config.shell_environment_policy.clone(),
|
||||
@@ -1109,6 +1121,34 @@ impl Session {
|
||||
)
|
||||
}
|
||||
|
||||
fn build_personality_update_item(
|
||||
&self,
|
||||
previous: Option<&Arc<TurnContext>>,
|
||||
next: &TurnContext,
|
||||
) -> Option<ResponseItem> {
|
||||
let personality = next.personality?;
|
||||
if let Some(prev) = previous
|
||||
&& prev.personality == Some(personality)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let model_info = next.client.get_model_info();
|
||||
let personality_message = Self::personality_message_for(&model_info, personality);
|
||||
|
||||
personality_message.map(|personality_message| {
|
||||
DeveloperInstructions::personality_spec_message(personality_message).into()
|
||||
})
|
||||
}
|
||||
|
||||
fn personality_message_for(model_info: &ModelInfo, personality: Personality) -> Option<String> {
|
||||
model_info
|
||||
.model_instructions_template
|
||||
.as_ref()
|
||||
.and_then(|template| template.personality_messages.as_ref())
|
||||
.and_then(|messages| messages.0.get(&personality))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn build_collaboration_mode_update_item(
|
||||
&self,
|
||||
previous_collaboration_mode: &CollaborationMode,
|
||||
@@ -1150,6 +1190,11 @@ impl Session {
|
||||
) {
|
||||
update_items.push(collaboration_mode_item);
|
||||
}
|
||||
if let Some(personality_item) =
|
||||
self.build_personality_update_item(previous_context, current_context)
|
||||
{
|
||||
update_items.push(personality_item);
|
||||
}
|
||||
update_items
|
||||
}
|
||||
|
||||
@@ -2014,6 +2059,7 @@ async fn submission_loop(sess: Arc<Session>, config: Arc<Config>, rx_sub: Receiv
|
||||
effort,
|
||||
summary,
|
||||
collaboration_mode,
|
||||
personality,
|
||||
} => {
|
||||
let collaboration_mode = if let Some(collab_mode) = collaboration_mode {
|
||||
collab_mode
|
||||
@@ -2034,6 +2080,7 @@ async fn submission_loop(sess: Arc<Session>, config: Arc<Config>, rx_sub: Receiv
|
||||
sandbox_policy,
|
||||
collaboration_mode: Some(collaboration_mode),
|
||||
reasoning_summary: summary,
|
||||
personality,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
@@ -2218,6 +2265,7 @@ mod handlers {
|
||||
final_output_json_schema,
|
||||
items,
|
||||
collaboration_mode,
|
||||
personality,
|
||||
} => {
|
||||
let collaboration_mode = collaboration_mode.or_else(|| {
|
||||
Some(CollaborationMode::Custom(Settings {
|
||||
@@ -2235,6 +2283,7 @@ mod handlers {
|
||||
collaboration_mode,
|
||||
reasoning_summary: Some(summary),
|
||||
final_output_json_schema: Some(final_output_json_schema),
|
||||
personality,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -2708,6 +2757,7 @@ async fn spawn_review_thread(
|
||||
developer_instructions: None,
|
||||
user_instructions: None,
|
||||
compact_prompt: parent_turn_context.compact_prompt.clone(),
|
||||
personality: parent_turn_context.personality,
|
||||
approval_policy: parent_turn_context.approval_policy,
|
||||
sandbox_policy: parent_turn_context.sandbox_policy.clone(),
|
||||
shell_environment_policy: parent_turn_context.shell_environment_policy.clone(),
|
||||
@@ -2997,7 +3047,7 @@ async fn run_sampling_request(
|
||||
tools: router.specs(),
|
||||
parallel_tool_calls: model_supports_parallel,
|
||||
base_instructions,
|
||||
personality: None,
|
||||
personality: turn_context.personality,
|
||||
output_schema: turn_context.final_output_json_schema.clone(),
|
||||
};
|
||||
|
||||
@@ -3116,6 +3166,7 @@ async fn try_run_sampling_request(
|
||||
approval_policy: turn_context.approval_policy,
|
||||
sandbox_policy: turn_context.sandbox_policy.clone(),
|
||||
model: turn_context.client.get_model(),
|
||||
personality: turn_context.personality,
|
||||
collaboration_mode: Some(collaboration_mode),
|
||||
effort: turn_context.client.get_reasoning_effort(),
|
||||
summary: turn_context.client.get_reasoning_summary(),
|
||||
@@ -3748,6 +3799,7 @@ mod tests {
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions: config.user_instructions.clone(),
|
||||
personality: config.model_personality,
|
||||
base_instructions: config
|
||||
.base_instructions
|
||||
.clone()
|
||||
@@ -3823,6 +3875,7 @@ mod tests {
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions: config.user_instructions.clone(),
|
||||
personality: config.model_personality,
|
||||
base_instructions: config
|
||||
.base_instructions
|
||||
.clone()
|
||||
@@ -4082,6 +4135,7 @@ mod tests {
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions: config.user_instructions.clone(),
|
||||
personality: config.model_personality,
|
||||
base_instructions: config
|
||||
.base_instructions
|
||||
.clone()
|
||||
@@ -4186,6 +4240,7 @@ mod tests {
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions: config.user_instructions.clone(),
|
||||
personality: config.model_personality,
|
||||
base_instructions: config
|
||||
.base_instructions
|
||||
.clone()
|
||||
|
||||
@@ -94,6 +94,7 @@ async fn run_compact_task_inner(
|
||||
approval_policy: turn_context.approval_policy,
|
||||
sandbox_policy: turn_context.sandbox_policy.clone(),
|
||||
model: turn_context.client.get_model(),
|
||||
personality: turn_context.personality,
|
||||
collaboration_mode: Some(collaboration_mode),
|
||||
effort: turn_context.client.get_reasoning_effort(),
|
||||
summary: turn_context.client.get_reasoning_summary(),
|
||||
@@ -111,6 +112,7 @@ async fn run_compact_task_inner(
|
||||
let prompt = Prompt {
|
||||
input: turn_input,
|
||||
base_instructions: sess.get_base_instructions().await,
|
||||
personality: turn_context.personality,
|
||||
..Default::default()
|
||||
};
|
||||
let attempt_result = drain_to_completed(&sess, turn_context.as_ref(), &prompt).await;
|
||||
|
||||
@@ -55,7 +55,7 @@ async fn run_remote_compact_task_inner_impl(
|
||||
tools: vec![],
|
||||
parallel_tool_calls: false,
|
||||
base_instructions: sess.get_base_instructions().await,
|
||||
personality: None,
|
||||
personality: turn_context.personality,
|
||||
output_schema: None,
|
||||
};
|
||||
|
||||
|
||||
@@ -86,7 +86,9 @@ impl ContextManager {
|
||||
// This is a coarse lower bound, not a tokenizer-accurate count.
|
||||
pub(crate) fn estimate_token_count(&self, turn_context: &TurnContext) -> Option<i64> {
|
||||
let model_info = turn_context.client.get_model_info();
|
||||
let personality = turn_context.client.config().model_personality;
|
||||
let personality = turn_context
|
||||
.personality
|
||||
.or(turn_context.client.config().model_personality);
|
||||
let base_instructions = model_info.get_model_instructions(personality);
|
||||
let base_tokens = i64::try_from(approx_token_count(&base_instructions)).unwrap_or(i64::MAX);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user