Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions codex-rs/app-server/tests/common/models_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@ fn preset_to_info(preset: &ModelPreset, priority: i32) -> ModelInfo {
supported_in_api: true,
priority,
upgrade: preset.upgrade.as_ref().map(|u| u.id.clone()),
base_instructions: None,
base_instructions: "base instructions".to_string(),
supports_reasoning_summaries: false,
support_verbosity: false,
default_verbosity: None,
apply_patch_tool_type: None,
truncation_policy: TruncationPolicyConfig::bytes(10_000),
supports_parallel_tool_calls: false,
context_window: None,
context_window: 272_000,
auto_compact_token_limit: None,
effective_context_window_percent: 95,
experimental_supported_tools: Vec::new(),
}
}
Expand Down
4 changes: 2 additions & 2 deletions codex-rs/codex-api/src/endpoint/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,14 @@ mod tests {
"supported_in_api": true,
"priority": 1,
"upgrade": null,
"base_instructions": null,
"base_instructions": "base instructions",
"supports_reasoning_summaries": false,
"support_verbosity": false,
"default_verbosity": null,
"apply_patch_tool_type": null,
"truncation_policy": {"mode": "bytes", "limit": 10_000},
"supports_parallel_tool_calls": false,
"context_window": null,
"context_window": 272_000,
"experimental_supported_tools": [],
}))
.unwrap(),
Expand Down
6 changes: 4 additions & 2 deletions codex-rs/codex-api/tests/models_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,16 @@ async fn models_client_hits_models_endpoint() {
supported_in_api: true,
priority: 1,
upgrade: None,
base_instructions: None,
base_instructions: "base instructions".to_string(),
supports_reasoning_summaries: false,
support_verbosity: false,
default_verbosity: None,
apply_patch_tool_type: None,
truncation_policy: TruncationPolicyConfig::bytes(10_000),
supports_parallel_tool_calls: false,
context_window: None,
context_window: 272_000,
auto_compact_token_limit: None,
effective_context_window_percent: 95,
experimental_supported_tools: Vec::new(),
}],
};
Expand Down
386 changes: 386 additions & 0 deletions codex-rs/core/prompt_with_apply_patch_instructions.md

Large diffs are not rendered by default.

53 changes: 28 additions & 25 deletions codex-rs/core/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use codex_otel::otel_manager::OtelManager;
use codex_protocol::ConversationId;
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
use codex_protocol::models::ResponseItem;
use codex_protocol::openai_models::ModelInfo;
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
use codex_protocol::protocol::SessionSource;
use eventsource_stream::Event;
Expand Down Expand Up @@ -49,15 +50,14 @@ use crate::features::FEATURES;
use crate::flags::CODEX_RS_SSE_FIXTURE;
use crate::model_provider_info::ModelProviderInfo;
use crate::model_provider_info::WireApi;
use crate::models_manager::model_family::ModelFamily;
use crate::tools::spec::create_tools_json_for_chat_completions_api;
use crate::tools::spec::create_tools_json_for_responses_api;

#[derive(Debug, Clone)]
pub struct ModelClient {
config: Arc<Config>,
auth_manager: Option<Arc<AuthManager>>,
model_family: ModelFamily,
model_info: ModelInfo,
otel_manager: OtelManager,
provider: ModelProviderInfo,
conversation_id: ConversationId,
Expand All @@ -71,7 +71,7 @@ impl ModelClient {
pub fn new(
config: Arc<Config>,
auth_manager: Option<Arc<AuthManager>>,
model_family: ModelFamily,
model_info: ModelInfo,
otel_manager: OtelManager,
provider: ModelProviderInfo,
effort: Option<ReasoningEffortConfig>,
Expand All @@ -82,7 +82,7 @@ impl ModelClient {
Self {
config,
auth_manager,
model_family,
model_info,
otel_manager,
provider,
conversation_id,
Expand All @@ -93,11 +93,14 @@ impl ModelClient {
}

pub fn get_model_context_window(&self) -> Option<i64> {
let model_family = self.get_model_family();
let effective_context_window_percent = model_family.effective_context_window_percent;
model_family
.context_window
.map(|w| w.saturating_mul(effective_context_window_percent) / 100)
let model_info = self.get_model_info();
let effective_context_window_percent = model_info.effective_context_window_percent;
Some(
model_info
.context_window
.saturating_mul(effective_context_window_percent)
/ 100,
)
}

pub fn config(&self) -> Arc<Config> {
Expand Down Expand Up @@ -146,8 +149,8 @@ impl ModelClient {
}

let auth_manager = self.auth_manager.clone();
let model_family = self.get_model_family();
let instructions = prompt.get_full_instructions(&model_family).into_owned();
let model_info = self.get_model_info();
let instructions = prompt.get_full_instructions(&model_info).into_owned();
let tools_json = create_tools_json_for_chat_completions_api(&prompt.tools)?;
let api_prompt = build_api_prompt(prompt, instructions, tools_json);
let conversation_id = self.conversation_id.to_string();
Expand Down Expand Up @@ -200,13 +203,16 @@ impl ModelClient {
}

let auth_manager = self.auth_manager.clone();
let model_family = self.get_model_family();
let instructions = prompt.get_full_instructions(&model_family).into_owned();
let model_info = self.get_model_info();
let instructions = prompt.get_full_instructions(&model_info).into_owned();
let tools_json: Vec<Value> = create_tools_json_for_responses_api(&prompt.tools)?;

let reasoning = if model_family.supports_reasoning_summaries {
let default_reasoning_effort = (!model_info.supported_reasoning_levels.is_empty()
&& !model_info.slug.contains("codex"))
.then_some(model_info.default_reasoning_level);
let reasoning = if model_info.supports_reasoning_summaries {
Some(Reasoning {
effort: self.effort.or(model_family.default_reasoning_effort),
effort: self.effort.or(default_reasoning_effort),
summary: if self.summary == ReasoningSummaryConfig::None {
None
} else {
Expand All @@ -223,15 +229,13 @@ impl ModelClient {
vec![]
};

let verbosity = if model_family.support_verbosity {
self.config
.model_verbosity
.or(model_family.default_verbosity)
let verbosity = if model_info.support_verbosity {
self.config.model_verbosity.or(model_info.default_verbosity)
} else {
if self.config.model_verbosity.is_some() {
warn!(
"model_verbosity is set but ignored as the model does not support verbosity: {}",
model_family.family
model_info.slug
);
}
None
Expand Down Expand Up @@ -298,12 +302,11 @@ impl ModelClient {

/// Returns the currently configured model slug.
pub fn get_model(&self) -> String {
self.get_model_family().get_model_slug().to_string()
self.model_info.slug.clone()
}

/// Returns the currently configured model family.
pub fn get_model_family(&self) -> ModelFamily {
self.model_family.clone()
pub fn get_model_info(&self) -> ModelInfo {
self.model_info.clone()
}

/// Returns the current reasoning effort setting.
Expand Down Expand Up @@ -340,7 +343,7 @@ impl ModelClient {
.with_telemetry(Some(request_telemetry));

let instructions = prompt
.get_full_instructions(&self.get_model_family())
.get_full_instructions(&self.get_model_info())
.into_owned();
let payload = ApiCompactionInput {
model: &self.get_model(),
Expand Down
57 changes: 19 additions & 38 deletions codex-rs/core/src/client_common.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
use crate::client_common::tools::ToolSpec;
use crate::error::Result;
use crate::models_manager::model_family::ModelFamily;
pub use codex_api::common::ResponseEvent;
use codex_apply_patch::APPLY_PATCH_TOOL_INSTRUCTIONS;
use codex_protocol::models::ResponseItem;
use codex_protocol::openai_models::ModelInfo;
use futures::Stream;
use serde::Deserialize;
use serde_json::Value;
use std::borrow::Cow;
use std::collections::HashSet;
use std::ops::Deref;
use std::pin::Pin;
use std::task::Context;
use std::task::Poll;
Expand Down Expand Up @@ -44,28 +42,12 @@ pub struct Prompt {
}

impl Prompt {
pub(crate) fn get_full_instructions<'a>(&'a self, model: &'a ModelFamily) -> Cow<'a, str> {
let base = self
.base_instructions_override
.as_deref()
.unwrap_or(model.base_instructions.deref());
// When there are no custom instructions, add apply_patch_tool_instructions if:
// - the model needs special instructions (4.1)
// AND
// - there is no apply_patch tool present
let is_apply_patch_tool_present = self.tools.iter().any(|tool| match tool {
ToolSpec::Function(f) => f.name == "apply_patch",
ToolSpec::Freeform(f) => f.name == "apply_patch",
_ => false,
});
if self.base_instructions_override.is_none()
&& model.needs_special_apply_patch_instructions
&& !is_apply_patch_tool_present
{
Cow::Owned(format!("{base}\n{APPLY_PATCH_TOOL_INSTRUCTIONS}"))
} else {
Cow::Borrowed(base)
}
pub(crate) fn get_full_instructions<'a>(&'a self, model: &'a ModelInfo) -> Cow<'a, str> {
Cow::Borrowed(
self.base_instructions_override
.as_deref()
.unwrap_or(model.base_instructions.as_str()),
)
}

pub(crate) fn get_formatted_input(&self) -> Vec<ResponseItem> {
Expand Down Expand Up @@ -272,6 +254,8 @@ mod tests {
let prompt = Prompt {
..Default::default()
};
let prompt_with_apply_patch_instructions =
include_str!("../prompt_with_apply_patch_instructions.md");
let test_cases = vec![
InstructionsTestCase {
slug: "gpt-3.5",
Expand Down Expand Up @@ -312,19 +296,16 @@ mod tests {
];
for test_case in test_cases {
let config = test_config();
let model_family =
ModelsManager::construct_model_family_offline(test_case.slug, &config);
let expected = if test_case.expects_apply_patch_instructions {
format!(
"{}\n{}",
model_family.clone().base_instructions,
APPLY_PATCH_TOOL_INSTRUCTIONS
)
} else {
model_family.clone().base_instructions
};

let full = prompt.get_full_instructions(&model_family);
let model_info = ModelsManager::construct_model_info_offline(test_case.slug, &config);
if test_case.expects_apply_patch_instructions {
assert_eq!(
model_info.base_instructions.as_str(),
prompt_with_apply_patch_instructions
);
}

let expected = model_info.base_instructions.as_str();
let full = prompt.get_full_instructions(&model_info);
assert_eq!(full, expected);
}
}
Expand Down
Loading
Loading