mirror of
https://github.com/openai/codex.git
synced 2026-11-08 08:37:30 +00:00
Estimate history tokens from content instead of serialized envelopes (#45094)
## Why Serialized response items include message IDs, metadata, and JSON escaping that inflate token estimates without adding model-visible content. ## What changed - Estimate each response item from its content, retaining JSON syntax for structured tool payloads. - Apply image estimates to all image inputs, including non-base64 URLs, and count audio and encrypted content through their modality-specific estimates. - Exclude plaintext reasoning and bookkeeping-only items from replay accounting. ## Testing Update unit expectations for text, images, audio, and encrypted content. Add a remote compaction regression test showing that equal-length text produces identical token usage estimates despite different message IDs, metadata, and JSON escaping, while preserving the submitted messages. GitOrigin-RevId: 1c43d5abbcc1668b4ab413901a2b4b6511ca5892
This commit is contained in:
1 parent
8d3c6cc13d
commit
b04a2c2645
3 files changed
+241
-196
No files matched your search
@@ -14,6 +14,78 @@ fn compact_response() -> String {
|
||||
])
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn remote_compact_v2_token_estimate_ignores_message_bookkeeping_and_json_escaping()
|
||||
-> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let harness = TestCodexHarness::with_auto_env_builder(
|
||||
test_codex().with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()),
|
||||
)
|
||||
.await?;
|
||||
let escaped_text = "a \"quoted\" line\nand\\path";
|
||||
let plain_text = "x".repeat(escaped_text.len());
|
||||
let mut estimates = Vec::new();
|
||||
for (id, text, metadata) in [
|
||||
(
|
||||
"msg_1",
|
||||
plain_text.as_str(),
|
||||
json!({
|
||||
"turn_id": "turn-1",
|
||||
"create_time": 1,
|
||||
"content_item_kinds": ["unknown"],
|
||||
}),
|
||||
),
|
||||
(
|
||||
"msg_019f0000-0000-7000-8000-000000000001",
|
||||
escaped_text,
|
||||
json!({
|
||||
"turn_id": "019f0000-0000-7000-8000-000000000002",
|
||||
"create_time": 1_700_000_000.123,
|
||||
"content_item_kinds": ["user.text"],
|
||||
}),
|
||||
),
|
||||
] {
|
||||
let codex = harness
|
||||
.test()
|
||||
.thread_manager
|
||||
.start_thread(StartThreadOptions {
|
||||
environments: Some(vec![
|
||||
harness.test().executor_environment().selection().clone(),
|
||||
]),
|
||||
..StartThreadOptions::new(harness.test().config.clone())
|
||||
})
|
||||
.await?
|
||||
.thread;
|
||||
let message = json!({
|
||||
"type": "message",
|
||||
"id": id,
|
||||
"role": "user",
|
||||
"content": [{"type": "input_text", "text": text}],
|
||||
"internal_chat_message_metadata_passthrough": metadata,
|
||||
});
|
||||
codex
|
||||
.inject_response_items(vec![serde_json::from_value(message.clone())?])
|
||||
.await?;
|
||||
let mock = mount_sse_once(harness.server(), compact_response()).await;
|
||||
codex.submit(Op::Compact).await?;
|
||||
wait_for_turn_complete(&codex).await;
|
||||
|
||||
estimates.push(codex.token_usage_info().await.context("token usage")?);
|
||||
assert_eq!(
|
||||
mock.single_request()
|
||||
.input()
|
||||
.into_iter()
|
||||
.find(|item| item["id"] == id),
|
||||
Some(message),
|
||||
);
|
||||
codex.shutdown_and_wait().await?;
|
||||
}
|
||||
|
||||
assert_eq!(estimates[0], estimates[1]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn remote_compact_v2_trims_tool_search_output_to_empty_tools_array() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
Reference in new issue
Block a user