merge
This commit is contained in:
@@ -109,6 +109,8 @@ pub async fn run_migrations(pool: &DbPool) -> Result<()> {
|
||||
enabled BOOLEAN DEFAULT TRUE,
|
||||
prompt_cost_per_m REAL,
|
||||
completion_cost_per_m REAL,
|
||||
cache_read_cost_per_m REAL,
|
||||
cache_write_cost_per_m REAL,
|
||||
mapping TEXT,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (provider_id) REFERENCES provider_configs(id) ON DELETE CASCADE
|
||||
@@ -180,6 +182,14 @@ pub async fn run_migrations(pool: &DbPool) -> Result<()> {
|
||||
.execute(pool)
|
||||
.await;
|
||||
|
||||
// Add manual cache cost columns to model_configs if they don't exist
|
||||
let _ = sqlx::query("ALTER TABLE model_configs ADD COLUMN cache_read_cost_per_m REAL")
|
||||
.execute(pool)
|
||||
.await;
|
||||
let _ = sqlx::query("ALTER TABLE model_configs ADD COLUMN cache_write_cost_per_m REAL")
|
||||
.execute(pool)
|
||||
.await;
|
||||
|
||||
// Insert default admin user if none exists (default password: admin)
|
||||
let user_count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM users").fetch_one(pool).await?;
|
||||
|
||||
|
||||
@@ -152,8 +152,11 @@ impl super::Provider for DeepSeekProvider {
|
||||
// Sanitize and fix for deepseek-reasoner (R1)
|
||||
if request.model == "deepseek-reasoner" {
|
||||
if let Some(obj) = body.as_object_mut() {
|
||||
obj.remove("stream_options");
|
||||
// Keep stream_options if present (DeepSeek supports include_usage)
|
||||
|
||||
// Remove unsupported parameters
|
||||
obj.remove("temperature");
|
||||
|
||||
obj.remove("top_p");
|
||||
obj.remove("presence_penalty");
|
||||
obj.remove("frequency_penalty");
|
||||
@@ -177,11 +180,6 @@ impl super::Provider for DeepSeekProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For standard deepseek-chat, keep it clean
|
||||
if let Some(obj) = body.as_object_mut() {
|
||||
obj.remove("stream_options");
|
||||
}
|
||||
}
|
||||
|
||||
let url = format!("{}/chat/completions", self.config.base_url);
|
||||
|
||||
@@ -317,8 +317,7 @@ impl super::Provider for OpenAIProvider {
|
||||
|
||||
// Standard OpenAI cleanup
|
||||
if let Some(obj) = body.as_object_mut() {
|
||||
obj.remove("stream_options");
|
||||
|
||||
// stream_options.include_usage is supported by OpenAI for token usage in streaming
|
||||
// Transition: Newer OpenAI models (o1, o3, gpt-5) require max_completion_tokens
|
||||
if request.model.starts_with("o1-") || request.model.starts_with("o3-") || request.model.contains("gpt-5") {
|
||||
if let Some(max_tokens) = obj.remove("max_tokens") {
|
||||
|
||||
+17
-2
@@ -137,8 +137,23 @@ async fn get_model_cost(
|
||||
// Check in-memory cache for cost overrides (no SQLite hit)
|
||||
if let Some(cached) = state.model_config_cache.get(model).await {
|
||||
if let (Some(p), Some(c)) = (cached.prompt_cost_per_m, cached.completion_cost_per_m) {
|
||||
// Manual overrides don't have cache-specific rates, so use simple formula
|
||||
return (prompt_tokens as f64 * p / 1_000_000.0) + (completion_tokens as f64 * c / 1_000_000.0);
|
||||
// Manual overrides logic: if cache rates are provided, use cache-aware formula.
|
||||
// Formula: (non_cached_prompt * input_rate) + (cache_read * read_rate) + (cache_write * write_rate) + (completion * output_rate)
|
||||
let non_cached_prompt = prompt_tokens.saturating_sub(cache_read_tokens);
|
||||
let mut total = (non_cached_prompt as f64 * p / 1_000_000.0) + (completion_tokens as f64 * c / 1_000_000.0);
|
||||
|
||||
if let Some(cr) = cached.cache_read_cost_per_m {
|
||||
total += cache_read_tokens as f64 * cr / 1_000_000.0;
|
||||
} else {
|
||||
// No manual cache_read rate — charge cached tokens at full input rate (backwards compatibility)
|
||||
total += cache_read_tokens as f64 * p / 1_000_000.0;
|
||||
}
|
||||
|
||||
if let Some(cw) = cached.cache_write_cost_per_m {
|
||||
total += cache_write_tokens as f64 * cw / 1_000_000.0;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-3
@@ -15,6 +15,8 @@ pub struct CachedModelConfig {
|
||||
pub mapping: Option<String>,
|
||||
pub prompt_cost_per_m: Option<f64>,
|
||||
pub completion_cost_per_m: Option<f64>,
|
||||
pub cache_read_cost_per_m: Option<f64>,
|
||||
pub cache_write_cost_per_m: Option<f64>,
|
||||
}
|
||||
|
||||
/// In-memory cache for model_configs table.
|
||||
@@ -35,15 +37,15 @@ impl ModelConfigCache {
|
||||
|
||||
/// Load all model configs from the database into cache
|
||||
pub async fn refresh(&self) {
|
||||
match sqlx::query_as::<_, (String, bool, Option<String>, Option<f64>, Option<f64>)>(
|
||||
"SELECT id, enabled, mapping, prompt_cost_per_m, completion_cost_per_m FROM model_configs",
|
||||
match sqlx::query_as::<_, (String, bool, Option<String>, Option<f64>, Option<f64>, Option<f64>, Option<f64>)>(
|
||||
"SELECT id, enabled, mapping, prompt_cost_per_m, completion_cost_per_m, cache_read_cost_per_m, cache_write_cost_per_m FROM model_configs",
|
||||
)
|
||||
.fetch_all(&self.db_pool)
|
||||
.await
|
||||
{
|
||||
Ok(rows) => {
|
||||
let mut map = HashMap::with_capacity(rows.len());
|
||||
for (id, enabled, mapping, prompt_cost, completion_cost) in rows {
|
||||
for (id, enabled, mapping, prompt_cost, completion_cost, cache_read_cost, cache_write_cost) in rows {
|
||||
map.insert(
|
||||
id,
|
||||
CachedModelConfig {
|
||||
@@ -51,6 +53,8 @@ impl ModelConfigCache {
|
||||
mapping,
|
||||
prompt_cost_per_m: prompt_cost,
|
||||
completion_cost_per_m: completion_cost,
|
||||
cache_read_cost_per_m: cache_read_cost,
|
||||
cache_write_cost_per_m: cache_write_cost,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
+16
-2
@@ -118,8 +118,22 @@ where
|
||||
// Check in-memory cache for cost overrides (no SQLite hit)
|
||||
let cost = if let Some(cached) = config_cache.get(&model).await {
|
||||
if let (Some(p), Some(c)) = (cached.prompt_cost_per_m, cached.completion_cost_per_m) {
|
||||
// Cost override doesn't have cache-aware pricing, use simple formula
|
||||
(prompt_tokens as f64 * p / 1_000_000.0) + (completion_tokens as f64 * c / 1_000_000.0)
|
||||
// Manual overrides logic: if cache rates are provided, use cache-aware formula.
|
||||
let non_cached_prompt = prompt_tokens.saturating_sub(cache_read_tokens);
|
||||
let mut total = (non_cached_prompt as f64 * p / 1_000_000.0) + (completion_tokens as f64 * c / 1_000_000.0);
|
||||
|
||||
if let Some(cr) = cached.cache_read_cost_per_m {
|
||||
total += cache_read_tokens as f64 * cr / 1_000_000.0;
|
||||
} else {
|
||||
// Charge cached tokens at full input rate if no specific rate provided
|
||||
total += cache_read_tokens as f64 * p / 1_000_000.0;
|
||||
}
|
||||
|
||||
if let Some(cw) = cached.cache_write_cost_per_m {
|
||||
total += cache_write_tokens as f64 * cw / 1_000_000.0;
|
||||
}
|
||||
|
||||
total
|
||||
} else {
|
||||
provider.calculate_cost(
|
||||
&model,
|
||||
|
||||
Reference in New Issue
Block a user