feat(agent/sync): sync non-OpenRouter models from upstream endpoint
When upstream /v1/models returns models not yet in OpenRouter's catalog (e.g. brand-new models like DeepSeek-V4), also upsert them through the same pipeline (provider → model → version → pricing → capabilities → parameter_profile) with inferred defaults, instead of silently dropping them. Previously the direct-sync fallback only triggered when *zero* OpenRouter matches existed.
This commit is contained in:
parent
27cd4ea83c
commit
638dfd7a6e
@ -5,7 +5,10 @@
|
||||
//! 2. Fetch full metadata (pricing, context_length, capabilities) for those
|
||||
//! model IDs from OpenRouter's public `/api/v1/models` endpoint (no auth).
|
||||
//! 3. Upsert provider / model / version / pricing / capability / profile
|
||||
//! records only for models the client can actually call.
|
||||
//! records for models the client can actually call.
|
||||
//! 4. Models accessible from the user's endpoint but NOT in OpenRouter's
|
||||
//! catalog ("stranger" models) are also upserted through the same
|
||||
//! pipeline with inferred defaults (direct sync).
|
||||
//!
|
||||
//! Usage: call `start_sync_task()` to launch a background task that syncs
|
||||
//! immediately and then every 10 minutes. On app startup, run it once
|
||||
@ -704,7 +707,11 @@ impl AppService {
|
||||
/// Steps:
|
||||
/// 1. Call `client.models().list()` to get the set of accessible model IDs.
|
||||
/// 2. Fetch full model list from OpenRouter's public `/api/v1/models` endpoint.
|
||||
/// 3. Keep only models whose ID appears in the accessible set, then upsert.
|
||||
/// 3. Keep only models whose ID appears in the accessible set, then upsert
|
||||
/// with full OpenRouter metadata.
|
||||
/// 4. Models NOT in OpenRouter's catalog are also upserted through the
|
||||
/// same pipeline (provider → model → version → pricing → capabilities
|
||||
/// → parameter_profile) with inferred defaults.
|
||||
pub async fn sync_upstream_models(
|
||||
&self,
|
||||
_ctx: &Session,
|
||||
@ -733,17 +740,21 @@ impl AppService {
|
||||
.filter(|m| m.id != "openrouter/auto")
|
||||
.collect();
|
||||
|
||||
let filtered_count = filtered.len();
|
||||
// Identify "stranger" models: accessible from the user's endpoint but
|
||||
// NOT present in OpenRouter's public catalog. These are also upserted
|
||||
// through the same pipeline (provider → model → version → pricing →
|
||||
// capabilities → parameter_profile) with inferred defaults.
|
||||
let or_matched_ids: std::collections::HashSet<&str> = filtered
|
||||
.iter()
|
||||
.map(|m| m.id.as_str())
|
||||
.collect();
|
||||
let unknown_ids: Vec<&str> = available_ids
|
||||
.iter()
|
||||
.filter(|id| !or_matched_ids.contains(id.as_str()))
|
||||
.map(|s| s.as_str())
|
||||
.collect();
|
||||
|
||||
// Fallback: if no OpenRouter metadata matches, sync models directly from
|
||||
// the user's endpoint (handles Bailian/MiniMax and other non-OpenRouter providers).
|
||||
if filtered_count == 0 && !available_ids.is_empty() {
|
||||
tracing::info!(
|
||||
model_count = available_ids.len(),
|
||||
"sync_upstream_models: no OpenRouter matches, falling back to direct sync"
|
||||
);
|
||||
return Ok(sync_models_direct(&self.db, &available_ids).await);
|
||||
}
|
||||
let filtered_count = filtered.len();
|
||||
|
||||
let mut models_created = 0i64;
|
||||
let mut models_updated = 0i64;
|
||||
@ -752,6 +763,37 @@ impl AppService {
|
||||
let mut capabilities_created = 0i64;
|
||||
let mut profiles_created = 0i64;
|
||||
|
||||
// Sync stranger models (non-OpenRouter) through the direct pipeline.
|
||||
if !unknown_ids.is_empty() {
|
||||
tracing::info!(
|
||||
unknown_count = unknown_ids.len(),
|
||||
"sync_upstream_models: {} models not in OpenRouter catalog, syncing directly",
|
||||
unknown_ids.len()
|
||||
);
|
||||
let unknown_set: std::collections::HashSet<String> =
|
||||
unknown_ids.iter().map(|s| ToString::to_string(s)).collect();
|
||||
let direct_result = sync_models_direct(&self.db, &unknown_set).await;
|
||||
models_created += direct_result.models_created;
|
||||
models_updated += direct_result.models_updated;
|
||||
versions_created += direct_result.versions_created;
|
||||
pricing_created += direct_result.pricing_created;
|
||||
capabilities_created += direct_result.capabilities_created;
|
||||
profiles_created += direct_result.profiles_created;
|
||||
}
|
||||
|
||||
// If no OpenRouter metadata matched at all, the direct sync above
|
||||
// already handled everything — return early.
|
||||
if filtered_count == 0 {
|
||||
return Ok(SyncModelsResponse {
|
||||
models_created,
|
||||
models_updated,
|
||||
versions_created,
|
||||
pricing_created,
|
||||
capabilities_created,
|
||||
profiles_created,
|
||||
});
|
||||
}
|
||||
|
||||
for or_model in filtered {
|
||||
let provider_slug = extract_provider(&or_model.id);
|
||||
let provider = match upsert_provider(&self.db, provider_slug).await {
|
||||
@ -823,8 +865,10 @@ impl AppService {
|
||||
filtered_count,
|
||||
models_created,
|
||||
models_updated,
|
||||
"sync_upstream_models: synced {} accessible models",
|
||||
filtered_count
|
||||
"sync_upstream_models: synced {} accessible models ({}) OpenRouter + ({}) direct",
|
||||
filtered_count + unknown_ids.len(),
|
||||
filtered_count,
|
||||
unknown_ids.len()
|
||||
);
|
||||
|
||||
Ok(SyncModelsResponse {
|
||||
@ -898,19 +942,21 @@ impl AppService {
|
||||
.filter(|m| m.id != "openrouter/auto")
|
||||
.collect();
|
||||
|
||||
// Identify "stranger" models: accessible from the user's endpoint but
|
||||
// NOT present in OpenRouter's public catalog.
|
||||
let or_matched_ids: std::collections::HashSet<&str> = filtered
|
||||
.iter()
|
||||
.map(|m| m.id.as_str())
|
||||
.collect();
|
||||
let unknown_ids: Vec<&str> = available_ids
|
||||
.iter()
|
||||
.filter(|id| !or_matched_ids.contains(id.as_str()))
|
||||
.map(|s| s.as_str())
|
||||
.collect();
|
||||
|
||||
let filtered_count = filtered.len();
|
||||
|
||||
// Fallback: if no OpenRouter metadata matches, sync models directly from
|
||||
// the user's endpoint (handles Bailian/MiniMax and other non-OpenRouter providers).
|
||||
if filtered_count == 0 && !available_ids.is_empty() {
|
||||
tracing::info!(
|
||||
model_count = available_ids.len(),
|
||||
"OpenRouter model sync: no matches, falling back to direct sync"
|
||||
);
|
||||
sync_models_direct(db, &available_ids).await;
|
||||
return;
|
||||
}
|
||||
|
||||
// Sync stranger models (non-OpenRouter) through the direct pipeline.
|
||||
let mut models_created = 0i64;
|
||||
let mut models_updated = 0i64;
|
||||
let mut versions_created = 0i64;
|
||||
@ -918,6 +964,39 @@ impl AppService {
|
||||
let mut capabilities_created = 0i64;
|
||||
let mut profiles_created = 0i64;
|
||||
|
||||
if !unknown_ids.is_empty() {
|
||||
tracing::info!(
|
||||
unknown_count = unknown_ids.len(),
|
||||
"OpenRouter model sync: {} models not in OpenRouter catalog, syncing directly",
|
||||
unknown_ids.len()
|
||||
);
|
||||
let unknown_set: std::collections::HashSet<String> =
|
||||
unknown_ids.iter().map(|s| ToString::to_string(s)).collect();
|
||||
let direct_result = sync_models_direct(db, &unknown_set).await;
|
||||
models_created += direct_result.models_created;
|
||||
models_updated += direct_result.models_updated;
|
||||
versions_created += direct_result.versions_created;
|
||||
pricing_created += direct_result.pricing_created;
|
||||
capabilities_created += direct_result.capabilities_created;
|
||||
profiles_created += direct_result.profiles_created;
|
||||
}
|
||||
|
||||
// If no OpenRouter metadata matched at all, the direct sync above
|
||||
// already handled everything — return early.
|
||||
if filtered_count == 0 {
|
||||
tracing::info!(
|
||||
matched = filtered_count,
|
||||
models_created,
|
||||
models_updated,
|
||||
versions_created,
|
||||
pricing_created,
|
||||
capabilities_created,
|
||||
profiles_created,
|
||||
"OpenRouter model sync complete (direct only)"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
for or_model in filtered {
|
||||
let provider_slug = extract_provider(&or_model.id);
|
||||
let provider = match upsert_provider(db, provider_slug).await {
|
||||
@ -987,13 +1066,17 @@ impl AppService {
|
||||
|
||||
tracing::info!(
|
||||
matched = filtered_count,
|
||||
unknown = unknown_ids.len(),
|
||||
models_created,
|
||||
models_updated,
|
||||
versions_created,
|
||||
pricing_created,
|
||||
capabilities_created,
|
||||
profiles_created,
|
||||
"OpenRouter model sync complete"
|
||||
"OpenRouter model sync complete: {} total ({} OpenRouter + {} direct)",
|
||||
filtered_count + unknown_ids.len(),
|
||||
filtered_count,
|
||||
unknown_ids.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user