fix(presets): use dated Doubao model id and price 6-digit dated models

Volcengine Ark rejects the bare model name doubao-seed-2-1-pro with a 404
("model does not exist or you do not have access to it") even after the
model is activated; the API requires the full dated id
doubao-seed-2-1-pro-260628. Update the Doubao preset model id across all
apps (config default, generated catalog, and OpenClaw namespaced refs).

Pricing lookup only stripped 8-digit (YYYYMMDD) and ISO date suffixes, not
the 6-digit YYMMDD format Volcengine uses (-260628, -250615), so real
Doubao usage never matched the bare-name pricing seed and showed $0 cost.
Extend strip_model_date_suffix to also strip 6-digit YYMMDD (with
month/day validation to avoid eating non-date version suffixes), keeping
the bare-name pricing seed as the canonical identity. This also fixes
pricing for every other Volcengine Doubao model. Add unit and end-to-end
regression tests.
This commit is contained in:
Jason
2026-06-28 23:37:50 +08:00
parent 562480870d
commit 7f83feb222
9 changed files with 87 additions and 19 deletions
+1 -1
View File
@@ -2655,7 +2655,7 @@ web_search = "disabled"
("gpt-5.5", "https://www.packyapi.com/v1"),
("gpt-5-codex", "https://aihubmix.com/v1"),
(
"doubao-seed-2-1-pro",
"doubao-seed-2-1-pro-260628",
"https://ark.cn-beijing.volces.com/api/v3",
),
("Pro/moonshotai/Kimi-K2.6", "https://api.siliconflow.cn/v1"),
+67 -2
View File
@@ -2197,8 +2197,24 @@ fn strip_model_date_suffix(model_id: &str) -> Option<String> {
}
let (base, suffix) = model_id.rsplit_once('-')?;
(!base.is_empty() && suffix.len() == 8 && suffix.chars().all(|c| c.is_ascii_digit()))
.then(|| base.to_string())
if base.is_empty() || !suffix.chars().all(|c| c.is_ascii_digit()) {
return None;
}
// 8 位 YYYYMMDD(如 -20250615OpenAI / Claude / 通义千问等)。
if suffix.len() == 8 {
return Some(base.to_string());
}
// 6 位 YYMMDD(如 -260628;火山方舟 doubao-seed-*、部分国产厂商)。
// 6 位比 8 位更易误伤非日期尾巴(如 -123456 的版本号),故额外校验
// 月 01-12、日 01-31 才剥离;剥不动时退回 None 由上层精确匹配兜底。
if suffix.len() == 6 {
let month: u32 = suffix[2..4].parse().unwrap_or(0);
let day: u32 = suffix[4..6].parse().unwrap_or(0);
if (1..=12).contains(&month) && (1..=31).contains(&day) {
return Some(base.to_string());
}
}
None
}
fn strip_reasoning_effort_suffix(model_id: &str) -> Option<String> {
@@ -3859,6 +3875,55 @@ mod tests {
assert_eq!(strip_model_date_suffix("abc🚀12345678"), None);
}
#[test]
fn test_strip_model_date_suffix_handles_six_digit_yymmdd() {
// 火山方舟 6 位 YYMMDD 后缀应被剥离(doubao 全系都用这种格式)。
assert_eq!(
strip_model_date_suffix("doubao-seed-2-1-pro-260628").as_deref(),
Some("doubao-seed-2-1-pro")
);
assert_eq!(
strip_model_date_suffix("doubao-seed-1-6-250615").as_deref(),
Some("doubao-seed-1-6")
);
// 8 位 YYYYMMDD 仍照旧剥离。
assert_eq!(
strip_model_date_suffix("claude-3-5-sonnet-20241022").as_deref(),
Some("claude-3-5-sonnet")
);
// 月/日非法的 6 位尾巴(版本号等)不剥离,避免误伤。
assert_eq!(strip_model_date_suffix("foo-bar-123456"), None); // 月=34
assert_eq!(strip_model_date_suffix("widget-209900"), None); // 月=99
assert_eq!(strip_model_date_suffix("gizmo-251200"), None); // 日=00
}
#[test]
fn test_pricing_resolves_volcengine_dated_model_to_bare_seed_row() -> Result<(), AppError> {
// 回归:火山真实用量带 6 位日期后缀(doubao-seed-2-1-pro-260628),
// 必须能归一化命中定价表里的裸名 seed 行(doubao-seed-2-1-pro),否则成本显示 $0。
let db = Database::memory()?;
let conn = lock_conn!(db.conn);
conn.execute(
"INSERT OR REPLACE INTO model_pricing (
model_id, display_name, input_cost_per_million, output_cost_per_million,
cache_read_cost_per_million, cache_creation_cost_per_million
) VALUES ('doubao-seed-2-1-pro', 'Doubao Seed 2.1 Pro', '0.84', '4.2', '0.17', '0')",
[],
)?;
let row = find_model_pricing_row(&conn, "doubao-seed-2-1-pro-260628")?;
assert!(
row.is_some(),
"带日期的火山模型应通过 6 位日期剥离命中裸名定价行"
);
let (input, output, ..) = row.unwrap();
assert_eq!(input, "0.84");
assert_eq!(output, "4.2");
Ok(())
}
#[test]
fn test_prefix_pricing_does_not_match_short_base_model_to_variant() -> Result<(), AppError> {
let db = Database::memory()?;