mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Scale skill metadata budgets with model context windows (#34626)
## Why A fixed character limit does not account for the different context-window sizes supported by models. ## What changed - Budget extension-rendered skill metadata at 2% of the resolved model context window, capped at 4,000 tokens. - Keep the existing 8,000-character fallback when model context metadata is unavailable. - Include the omission marker in the budget and still emit it when no skill entry fits. - Apply the same resolved budget to executor and host skill catalogs assembled for a turn. ## Testing - Cover proportional and capped budgets, multibyte fallback accounting, and omission-marker behavior. - Verify through the production turn path that larger model context windows include more catalog entries without exceeding the computed budget. GitOrigin-RevId: 4667293f1594de4dd605b32b9fa4255d26772c0f
This commit is contained in:
1 parent
bbad09a83b
commit
37eef7bacc
10 files changed
+454
-31
No files matched your search
@@ -1,5 +1,6 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use codex_utils_string::approx_token_count;
|
||||
use codex_utils_string::take_bytes_at_char_boundary;
|
||||
|
||||
use crate::catalog::SkillCatalog;
|
||||
@@ -7,7 +8,9 @@ use crate::catalog::SkillCatalogEntry;
|
||||
use crate::catalog::SkillSourceKind;
|
||||
use crate::fragments::AvailableSkillsInstructions;
|
||||
|
||||
const MAX_AVAILABLE_SKILLS_BYTES: usize = 8_000;
|
||||
const DEFAULT_SKILL_METADATA_CHAR_BUDGET: usize = 8_000;
|
||||
const MAX_SKILL_METADATA_TOKEN_BUDGET: usize = 4_000;
|
||||
const SKILL_METADATA_CONTEXT_WINDOW_PERCENT: usize = 2;
|
||||
const MAX_MAIN_PROMPT_BYTES: usize = 8_000;
|
||||
const MAX_CATALOG_SKILL_DESCRIPTION_CHARS: usize = 1_024;
|
||||
const TRUNCATED_SKILL_DESCRIPTION_SUFFIX: &str = "...";
|
||||
@@ -39,6 +42,37 @@ impl SkillCatalogRenderPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum SkillMetadataBudget {
|
||||
Tokens(usize),
|
||||
Characters(usize),
|
||||
}
|
||||
|
||||
pub(crate) fn capped_skill_metadata_budget(context_window: Option<i64>) -> SkillMetadataBudget {
|
||||
context_window
|
||||
.and_then(|window| usize::try_from(window).ok())
|
||||
.filter(|window| *window > 0)
|
||||
.map(|window| {
|
||||
SkillMetadataBudget::Tokens(
|
||||
window
|
||||
.saturating_mul(SKILL_METADATA_CONTEXT_WINDOW_PERCENT)
|
||||
.saturating_div(100)
|
||||
.clamp(1, MAX_SKILL_METADATA_TOKEN_BUDGET),
|
||||
)
|
||||
})
|
||||
.unwrap_or(SkillMetadataBudget::Characters(
|
||||
DEFAULT_SKILL_METADATA_CHAR_BUDGET,
|
||||
))
|
||||
}
|
||||
|
||||
fn metadata_line_cost(budget: SkillMetadataBudget, line: &str) -> usize {
|
||||
let line = format!("{line}\n");
|
||||
match budget {
|
||||
SkillMetadataBudget::Tokens(_) => approx_token_count(&line),
|
||||
SkillMetadataBudget::Characters(_) => line.chars().count(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(
|
||||
level = "trace",
|
||||
skip_all,
|
||||
@@ -48,8 +82,12 @@ pub(crate) fn available_skills_fragment(
|
||||
catalog: &SkillCatalog,
|
||||
include_skills_usage_instructions: bool,
|
||||
policy: SkillCatalogRenderPolicy,
|
||||
budget: SkillMetadataBudget,
|
||||
) -> Option<AvailableSkillsInstructions> {
|
||||
let mut total_bytes = 0usize;
|
||||
let budget_limit = match budget {
|
||||
SkillMetadataBudget::Tokens(limit) | SkillMetadataBudget::Characters(limit) => limit,
|
||||
};
|
||||
let mut total_cost = 0usize;
|
||||
let mut omitted = 0usize;
|
||||
let mut skill_lines = Vec::new();
|
||||
|
||||
@@ -61,29 +99,39 @@ pub(crate) fn available_skills_fragment(
|
||||
let description = policy.description(entry);
|
||||
let description = truncate_catalog_skill_description(description);
|
||||
let line = render_skill_line(entry, description.as_ref());
|
||||
let next_bytes = total_bytes.saturating_add(line.len());
|
||||
if next_bytes > MAX_AVAILABLE_SKILLS_BYTES {
|
||||
let next_cost = total_cost.saturating_add(metadata_line_cost(budget, &line));
|
||||
if next_cost > budget_limit {
|
||||
omitted = omitted.saturating_add(1);
|
||||
continue;
|
||||
}
|
||||
total_bytes = next_bytes;
|
||||
total_cost = next_cost;
|
||||
skill_lines.push(line);
|
||||
}
|
||||
|
||||
if skill_lines.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if omitted > 0 {
|
||||
let skill_word = if omitted == 1 { "skill" } else { "skills" };
|
||||
skill_lines.push(format!(
|
||||
"- {omitted} additional {skill_word} omitted from this bounded skills list."
|
||||
));
|
||||
loop {
|
||||
let marker = omission_marker(omitted);
|
||||
if total_cost.saturating_add(metadata_line_cost(budget, &marker)) <= budget_limit {
|
||||
skill_lines.push(marker);
|
||||
break;
|
||||
}
|
||||
let line = skill_lines.pop()?;
|
||||
total_cost = total_cost.saturating_sub(metadata_line_cost(budget, &line));
|
||||
omitted = omitted.saturating_add(1);
|
||||
}
|
||||
}
|
||||
|
||||
Some(AvailableSkillsInstructions::from_skill_lines(
|
||||
skill_lines,
|
||||
include_skills_usage_instructions,
|
||||
))
|
||||
(!skill_lines.is_empty()).then(|| {
|
||||
AvailableSkillsInstructions::from_skill_lines(
|
||||
skill_lines,
|
||||
include_skills_usage_instructions,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn omission_marker(omitted: usize) -> String {
|
||||
let skill_word = if omitted == 1 { "skill" } else { "skills" };
|
||||
format!("- {omitted} additional {skill_word} omitted from this bounded skills list.")
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_catalog_skill_description(description: &str) -> Cow<'_, str> {
|
||||
|
||||
Reference in new issue
Block a user