mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 09:50:21 +08:00
fix(provider): count inherited endpoint formats for model tests
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
use crate::handlers::admin::shared::AdminTypedObjectPatch;
|
use crate::handlers::admin::shared::AdminTypedObjectPatch;
|
||||||
|
use crate::provider_key_auth::provider_key_effective_api_formats;
|
||||||
use aether_admin::provider::endpoints as admin_provider_endpoints_pure;
|
use aether_admin::provider::endpoints as admin_provider_endpoints_pure;
|
||||||
use aether_data_contracts::repository::provider_catalog::{
|
use aether_data_contracts::repository::provider_catalog::{
|
||||||
StoredProviderCatalogEndpoint, StoredProviderCatalogKey,
|
StoredProviderCatalogEndpoint, StoredProviderCatalogKey, StoredProviderCatalogProvider,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
pub(super) fn key_api_formats_without_entry(
|
pub(super) fn key_api_formats_without_entry(
|
||||||
key: &StoredProviderCatalogKey,
|
key: &StoredProviderCatalogKey,
|
||||||
@@ -13,12 +15,50 @@ pub(super) fn key_api_formats_without_entry(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn endpoint_key_counts_by_format(
|
pub(super) fn endpoint_key_counts_by_format(
|
||||||
|
provider: &StoredProviderCatalogProvider,
|
||||||
|
endpoints: &[StoredProviderCatalogEndpoint],
|
||||||
keys: &[StoredProviderCatalogKey],
|
keys: &[StoredProviderCatalogKey],
|
||||||
) -> (
|
) -> (
|
||||||
std::collections::BTreeMap<String, usize>,
|
std::collections::BTreeMap<String, usize>,
|
||||||
std::collections::BTreeMap<String, usize>,
|
std::collections::BTreeMap<String, usize>,
|
||||||
) {
|
) {
|
||||||
admin_provider_endpoints_pure::endpoint_key_counts_by_format(keys)
|
let mut active_endpoint_formats = BTreeSet::new();
|
||||||
|
for endpoint in endpoints.iter().filter(|endpoint| endpoint.is_active) {
|
||||||
|
active_endpoint_formats.insert(endpoint.api_format.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut total_by_format = BTreeMap::<String, BTreeSet<String>>::new();
|
||||||
|
let mut active_by_format = BTreeMap::<String, BTreeSet<String>>::new();
|
||||||
|
for key in keys {
|
||||||
|
for api_format in
|
||||||
|
provider_key_effective_api_formats(key, &provider.provider_type, endpoints)
|
||||||
|
{
|
||||||
|
if !active_endpoint_formats.contains(&api_format) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
total_by_format
|
||||||
|
.entry(api_format.clone())
|
||||||
|
.or_default()
|
||||||
|
.insert(key.id.clone());
|
||||||
|
if key.is_active {
|
||||||
|
active_by_format
|
||||||
|
.entry(api_format)
|
||||||
|
.or_default()
|
||||||
|
.insert(key.id.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
total_by_format
|
||||||
|
.into_iter()
|
||||||
|
.map(|(api_format, keys)| (api_format, keys.len()))
|
||||||
|
.collect(),
|
||||||
|
active_by_format
|
||||||
|
.into_iter()
|
||||||
|
.map(|(api_format, keys)| (api_format, keys.len()))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn build_admin_provider_endpoint_response(
|
pub(super) fn build_admin_provider_endpoint_response(
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ pub(crate) async fn build_admin_provider_endpoints_payload(
|
|||||||
.await
|
.await
|
||||||
.ok()
|
.ok()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let (total_keys_by_format, active_keys_by_format) = endpoint_key_counts_by_format(&keys);
|
let (total_keys_by_format, active_keys_by_format) =
|
||||||
|
endpoint_key_counts_by_format(&provider, &endpoints, &keys);
|
||||||
let now_unix_secs = SystemTime::now()
|
let now_unix_secs = SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
.ok()
|
.ok()
|
||||||
@@ -92,7 +93,8 @@ pub(crate) async fn build_admin_endpoint_payload(
|
|||||||
.await
|
.await
|
||||||
.ok()
|
.ok()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let (total_keys_by_format, active_keys_by_format) = endpoint_key_counts_by_format(&keys);
|
let (total_keys_by_format, active_keys_by_format) =
|
||||||
|
endpoint_key_counts_by_format(&provider, std::slice::from_ref(&endpoint), &keys);
|
||||||
let now_unix_secs = SystemTime::now()
|
let now_unix_secs = SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
.ok()
|
.ok()
|
||||||
|
|||||||
@@ -147,7 +147,8 @@ pub(super) async fn maybe_handle(
|
|||||||
.list_provider_catalog_keys_by_provider_ids(std::slice::from_ref(&provider.id))
|
.list_provider_catalog_keys_by_provider_ids(std::slice::from_ref(&provider.id))
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let (total_keys_by_format, active_keys_by_format) = endpoint_key_counts_by_format(&keys);
|
let (total_keys_by_format, active_keys_by_format) =
|
||||||
|
endpoint_key_counts_by_format(&provider, std::slice::from_ref(&updated), &keys);
|
||||||
|
|
||||||
Ok(Some(
|
Ok(Some(
|
||||||
Json(build_admin_provider_endpoint_response(
|
Json(build_admin_provider_endpoint_response(
|
||||||
|
|||||||
@@ -148,6 +148,120 @@ async fn gateway_handles_admin_provider_endpoints_locally_with_trusted_admin_pri
|
|||||||
upstream_handle.abort();
|
upstream_handle.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn gateway_counts_fixed_provider_oauth_keys_for_inherited_endpoint_formats() {
|
||||||
|
let mut codex_provider = sample_provider("provider-codex", "codex", 10);
|
||||||
|
codex_provider.provider_type = "codex".to_string();
|
||||||
|
let mut chatgpt_web_provider = sample_provider("provider-chatgpt-web", "chatgpt_web", 20);
|
||||||
|
chatgpt_web_provider.provider_type = "chatgpt_web".to_string();
|
||||||
|
|
||||||
|
let mut codex_key = sample_key(
|
||||||
|
"key-codex-oauth",
|
||||||
|
"provider-codex",
|
||||||
|
"openai:responses:compact",
|
||||||
|
"oauth-token",
|
||||||
|
);
|
||||||
|
codex_key.auth_type = "oauth".to_string();
|
||||||
|
codex_key.api_formats = Some(json!(["legacy:mismatch"]));
|
||||||
|
|
||||||
|
let mut chatgpt_web_key = sample_key(
|
||||||
|
"key-chatgpt-web-oauth",
|
||||||
|
"provider-chatgpt-web",
|
||||||
|
"openai:image",
|
||||||
|
"oauth-token",
|
||||||
|
);
|
||||||
|
chatgpt_web_key.auth_type = "oauth".to_string();
|
||||||
|
chatgpt_web_key.api_formats = Some(json!(["legacy:mismatch"]));
|
||||||
|
|
||||||
|
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
|
||||||
|
vec![codex_provider, chatgpt_web_provider],
|
||||||
|
vec![
|
||||||
|
sample_endpoint(
|
||||||
|
"endpoint-codex-compact",
|
||||||
|
"provider-codex",
|
||||||
|
"openai:responses:compact",
|
||||||
|
"https://chatgpt.com/backend-api/codex",
|
||||||
|
),
|
||||||
|
sample_endpoint(
|
||||||
|
"endpoint-codex-image",
|
||||||
|
"provider-codex",
|
||||||
|
"openai:image",
|
||||||
|
"https://chatgpt.com/backend-api/codex",
|
||||||
|
),
|
||||||
|
sample_endpoint(
|
||||||
|
"endpoint-chatgpt-web-image",
|
||||||
|
"provider-chatgpt-web",
|
||||||
|
"openai:image",
|
||||||
|
"https://chatgpt.com",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
vec![codex_key, chatgpt_web_key],
|
||||||
|
));
|
||||||
|
|
||||||
|
let gateway = build_router_with_state(
|
||||||
|
AppState::new()
|
||||||
|
.expect("gateway should build")
|
||||||
|
.with_data_state_for_tests(GatewayDataState::with_provider_catalog_reader_for_tests(
|
||||||
|
provider_catalog_repository,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
let (gateway_url, gateway_handle) = start_server(gateway).await;
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let codex_response = client
|
||||||
|
.get(format!(
|
||||||
|
"{gateway_url}/api/admin/endpoints/providers/provider-codex/endpoints?skip=0&limit=50"
|
||||||
|
))
|
||||||
|
.header(GATEWAY_HEADER, "rust-phase3b")
|
||||||
|
.header(TRUSTED_ADMIN_USER_ID_HEADER, "admin-user-123")
|
||||||
|
.header(TRUSTED_ADMIN_USER_ROLE_HEADER, "admin")
|
||||||
|
.header(TRUSTED_ADMIN_SESSION_ID_HEADER, "session-123")
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("request should succeed");
|
||||||
|
assert_eq!(codex_response.status(), StatusCode::OK);
|
||||||
|
let codex_payload: serde_json::Value = codex_response.json().await.expect("json should parse");
|
||||||
|
let codex_items = codex_payload
|
||||||
|
.as_array()
|
||||||
|
.expect("payload should be an array");
|
||||||
|
for api_format in ["openai:responses:compact", "openai:image"] {
|
||||||
|
let endpoint = codex_items
|
||||||
|
.iter()
|
||||||
|
.find(|item| item["api_format"] == api_format)
|
||||||
|
.expect("endpoint should exist");
|
||||||
|
assert_eq!(endpoint["total_keys"], 1);
|
||||||
|
assert_eq!(endpoint["active_keys"], 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let chatgpt_web_response = client
|
||||||
|
.get(format!(
|
||||||
|
"{gateway_url}/api/admin/endpoints/providers/provider-chatgpt-web/endpoints?skip=0&limit=50"
|
||||||
|
))
|
||||||
|
.header(GATEWAY_HEADER, "rust-phase3b")
|
||||||
|
.header(TRUSTED_ADMIN_USER_ID_HEADER, "admin-user-123")
|
||||||
|
.header(TRUSTED_ADMIN_USER_ROLE_HEADER, "admin")
|
||||||
|
.header(TRUSTED_ADMIN_SESSION_ID_HEADER, "session-123")
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("request should succeed");
|
||||||
|
assert_eq!(chatgpt_web_response.status(), StatusCode::OK);
|
||||||
|
let chatgpt_web_payload: serde_json::Value = chatgpt_web_response
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.expect("json should parse");
|
||||||
|
let chatgpt_web_items = chatgpt_web_payload
|
||||||
|
.as_array()
|
||||||
|
.expect("payload should be an array");
|
||||||
|
let chatgpt_web_image = chatgpt_web_items
|
||||||
|
.iter()
|
||||||
|
.find(|item| item["api_format"] == "openai:image")
|
||||||
|
.expect("image endpoint should exist");
|
||||||
|
assert_eq!(chatgpt_web_image["total_keys"], 1);
|
||||||
|
assert_eq!(chatgpt_web_image["active_keys"], 1);
|
||||||
|
|
||||||
|
gateway_handle.abort();
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn gateway_returns_service_unavailable_for_admin_provider_endpoint_create_when_catalog_writer_unavailable(
|
async fn gateway_returns_service_unavailable_for_admin_provider_endpoint_create_when_catalog_writer_unavailable(
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -441,9 +441,10 @@ const activeEndpoints = computed(() => (props.endpoints ?? [])
|
|||||||
if (typeof endpoint.active_keys === 'number') {
|
if (typeof endpoint.active_keys === 'number') {
|
||||||
return endpoint.is_active !== false
|
return endpoint.is_active !== false
|
||||||
&& isModelTestableApiFormat(endpoint.api_format)
|
&& isModelTestableApiFormat(endpoint.api_format)
|
||||||
&& endpoint.active_keys > 0
|
&& (endpoint.active_keys > 0
|
||||||
|
|| isModelTestableEndpoint(endpoint, providerKeysState.value, props.provider.provider_type))
|
||||||
}
|
}
|
||||||
return isModelTestableEndpoint(endpoint, providerKeysState.value)
|
return isModelTestableEndpoint(endpoint, providerKeysState.value, props.provider.provider_type)
|
||||||
}))
|
}))
|
||||||
const selectableTestEndpoints = computed(() => mappingTestEndpoints.value ?? activeEndpoints.value)
|
const selectableTestEndpoints = computed(() => mappingTestEndpoints.value ?? activeEndpoints.value)
|
||||||
const parsedTestRequestHeaders = computed(() => parseModelTestRequestHeadersDraft(testRequestHeadersDraft.value))
|
const parsedTestRequestHeaders = computed(() => parseModelTestRequestHeadersDraft(testRequestHeadersDraft.value))
|
||||||
|
|||||||
@@ -312,9 +312,10 @@ const activeEndpoints = computed(() => (props.endpoints ?? [])
|
|||||||
if (typeof endpoint.active_keys === 'number') {
|
if (typeof endpoint.active_keys === 'number') {
|
||||||
return endpoint.is_active !== false
|
return endpoint.is_active !== false
|
||||||
&& isModelTestableApiFormat(endpoint.api_format)
|
&& isModelTestableApiFormat(endpoint.api_format)
|
||||||
&& endpoint.active_keys > 0
|
&& (endpoint.active_keys > 0
|
||||||
|
|| isModelTestableEndpoint(endpoint, props.providerKeys ?? [], props.provider.provider_type))
|
||||||
}
|
}
|
||||||
return isModelTestableEndpoint(endpoint, props.providerKeys ?? [])
|
return isModelTestableEndpoint(endpoint, props.providerKeys ?? [], props.provider.provider_type)
|
||||||
}))
|
}))
|
||||||
const parsedTestRequestHeaders = computed(() => parseModelTestRequestHeadersDraft(testRequestHeadersDraft.value))
|
const parsedTestRequestHeaders = computed(() => parseModelTestRequestHeadersDraft(testRequestHeadersDraft.value))
|
||||||
const testRequestHeadersError = computed(() => parsedTestRequestHeaders.value.error)
|
const testRequestHeadersError = computed(() => parsedTestRequestHeaders.value.error)
|
||||||
|
|||||||
@@ -55,6 +55,19 @@ describe('buildDefaultModelTestRequestBody', () => {
|
|||||||
expect(body.input).toBeUndefined()
|
expect(body.input).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('uses image prompt payloads for OpenAI image test requests', () => {
|
||||||
|
const body = JSON.parse(buildDefaultModelTestRequestBody('gpt-image-2', 'openai:image'))
|
||||||
|
|
||||||
|
expect(body).toEqual({
|
||||||
|
model: 'gpt-image-2',
|
||||||
|
prompt: 'Hello! This is a test message.',
|
||||||
|
n: 1,
|
||||||
|
size: '1024x1024',
|
||||||
|
stream: true,
|
||||||
|
})
|
||||||
|
expect(body.messages).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
it('lists endpoint-scoped provider model mappings in test selection order', () => {
|
it('lists endpoint-scoped provider model mappings in test selection order', () => {
|
||||||
const options = listModelTestMappedModelOptions({
|
const options = listModelTestMappedModelOptions({
|
||||||
provider_model_name: 'claude-opus-4-6',
|
provider_model_name: 'claude-opus-4-6',
|
||||||
@@ -202,6 +215,8 @@ describe('isModelTestableApiFormat', () => {
|
|||||||
it.each([
|
it.each([
|
||||||
'openai:chat',
|
'openai:chat',
|
||||||
'openai:responses',
|
'openai:responses',
|
||||||
|
'openai:responses:compact',
|
||||||
|
'openai:image',
|
||||||
'claude:messages',
|
'claude:messages',
|
||||||
'gemini:generate_content',
|
'gemini:generate_content',
|
||||||
'openai:embedding',
|
'openai:embedding',
|
||||||
@@ -242,6 +257,23 @@ describe('isModelTestableEndpoint', () => {
|
|||||||
is_active: true,
|
is_active: true,
|
||||||
}, keys)).toBe(true)
|
}, keys)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('lets fixed provider OAuth keys inherit testable endpoint formats', () => {
|
||||||
|
const keys = [{
|
||||||
|
api_formats: ['legacy:mismatch'],
|
||||||
|
auth_type: 'oauth',
|
||||||
|
is_active: true,
|
||||||
|
}]
|
||||||
|
|
||||||
|
expect(isModelTestableEndpoint({
|
||||||
|
api_format: 'openai:image',
|
||||||
|
is_active: true,
|
||||||
|
}, keys, 'chatgpt_web')).toBe(true)
|
||||||
|
expect(isModelTestableEndpoint({
|
||||||
|
api_format: 'openai:image',
|
||||||
|
is_active: true,
|
||||||
|
}, keys, 'custom')).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('formatModelTestDiagnostic', () => {
|
describe('formatModelTestDiagnostic', () => {
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ type ModelTestEndpointSource = {
|
|||||||
type ModelTestKeySource = {
|
type ModelTestKeySource = {
|
||||||
api_formats?: string[] | null
|
api_formats?: string[] | null
|
||||||
is_active?: boolean | null
|
is_active?: boolean | null
|
||||||
|
auth_type?: string | null
|
||||||
|
credential_kind?: string | null
|
||||||
|
oauth_managed?: boolean | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ModelTestMappedModelOption = {
|
export type ModelTestMappedModelOption = {
|
||||||
@@ -36,6 +39,20 @@ const MODEL_TEST_UNSUPPORTED_API_FORMATS = new Set([
|
|||||||
'gemini:files',
|
'gemini:files',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const MODEL_TEST_OAUTH_INHERITS_PROVIDER_FORMATS = new Set([
|
||||||
|
'claude_code',
|
||||||
|
'codex',
|
||||||
|
'chatgpt_web',
|
||||||
|
'gemini_cli',
|
||||||
|
'vertex_ai',
|
||||||
|
'antigravity',
|
||||||
|
'kiro',
|
||||||
|
])
|
||||||
|
|
||||||
|
const MODEL_TEST_BEARER_INHERITS_PROVIDER_FORMATS = new Set([
|
||||||
|
'chatgpt_web',
|
||||||
|
])
|
||||||
|
|
||||||
const MODEL_TEST_DIAGNOSTIC_LABELS: Record<string, string> = {
|
const MODEL_TEST_DIAGNOSTIC_LABELS: Record<string, string> = {
|
||||||
pool_account_blocked: '账号已失效,需重新授权',
|
pool_account_blocked: '账号已失效,需重新授权',
|
||||||
}
|
}
|
||||||
@@ -50,12 +67,15 @@ export function isModelTestableApiFormat(apiFormat: string | null | undefined):
|
|||||||
export function modelTestKeySupportsEndpoint(
|
export function modelTestKeySupportsEndpoint(
|
||||||
key: ModelTestKeySource,
|
key: ModelTestKeySource,
|
||||||
endpoint: ModelTestEndpointSource,
|
endpoint: ModelTestEndpointSource,
|
||||||
|
providerType?: string | null,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (key.is_active === false) return false
|
if (key.is_active === false) return false
|
||||||
|
|
||||||
const endpointFormat = normalizeApiFormatAlias(endpoint.api_format)
|
const endpointFormat = normalizeApiFormatAlias(endpoint.api_format)
|
||||||
if (!isModelTestableApiFormat(endpointFormat)) return false
|
if (!isModelTestableApiFormat(endpointFormat)) return false
|
||||||
|
|
||||||
|
if (modelTestKeyInheritsProviderFormats(key, providerType)) return true
|
||||||
|
|
||||||
const keyFormats = normalizeStringList(key.api_formats ?? undefined)
|
const keyFormats = normalizeStringList(key.api_formats ?? undefined)
|
||||||
if (keyFormats.length === 0) return true
|
if (keyFormats.length === 0) return true
|
||||||
|
|
||||||
@@ -65,10 +85,11 @@ export function modelTestKeySupportsEndpoint(
|
|||||||
export function isModelTestableEndpoint(
|
export function isModelTestableEndpoint(
|
||||||
endpoint: ModelTestEndpointSource,
|
endpoint: ModelTestEndpointSource,
|
||||||
keys: ModelTestKeySource[],
|
keys: ModelTestKeySource[],
|
||||||
|
providerType?: string | null,
|
||||||
): boolean {
|
): boolean {
|
||||||
return endpoint.is_active !== false
|
return endpoint.is_active !== false
|
||||||
&& isModelTestableApiFormat(endpoint.api_format)
|
&& isModelTestableApiFormat(endpoint.api_format)
|
||||||
&& keys.some(key => modelTestKeySupportsEndpoint(key, endpoint))
|
&& keys.some(key => modelTestKeySupportsEndpoint(key, endpoint, providerType))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatModelTestDiagnostic(value: string | null | undefined): string {
|
export function formatModelTestDiagnostic(value: string | null | undefined): string {
|
||||||
@@ -96,6 +117,27 @@ function normalizeStringList(values: string[] | undefined): string[] {
|
|||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function modelTestKeyInheritsProviderFormats(
|
||||||
|
key: ModelTestKeySource,
|
||||||
|
providerType: string | null | undefined,
|
||||||
|
): boolean {
|
||||||
|
const normalizedProviderType = providerType?.trim().toLowerCase()
|
||||||
|
if (!normalizedProviderType) return false
|
||||||
|
|
||||||
|
const authType = key.auth_type?.trim().toLowerCase()
|
||||||
|
const credentialKind = key.credential_kind?.trim().toLowerCase()
|
||||||
|
const oauthManaged = key.oauth_managed === true
|
||||||
|
|| credentialKind === 'oauth_session'
|
||||||
|
|| authType === 'oauth'
|
||||||
|
|
||||||
|
if (oauthManaged && MODEL_TEST_OAUTH_INHERITS_PROVIDER_FORMATS.has(normalizedProviderType)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return authType === 'bearer'
|
||||||
|
&& MODEL_TEST_BEARER_INHERITS_PROVIDER_FORMATS.has(normalizedProviderType)
|
||||||
|
}
|
||||||
|
|
||||||
function isJsonRecord(value: unknown): value is JsonRecord {
|
function isJsonRecord(value: unknown): value is JsonRecord {
|
||||||
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
||||||
}
|
}
|
||||||
@@ -409,14 +451,16 @@ export function buildExactModelMappingTestRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function buildDefaultModelTestRequestBody(modelName: string, apiFormat?: string | null): string {
|
export function buildDefaultModelTestRequestBody(modelName: string, apiFormat?: string | null): string {
|
||||||
if (apiFormat?.trim().toLowerCase().endsWith(':embedding')) {
|
const normalizedApiFormat = normalizeApiFormatAlias(apiFormat ?? '')
|
||||||
|
|
||||||
|
if (normalizedApiFormat.endsWith(':embedding')) {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
model: modelName,
|
model: modelName,
|
||||||
input: 'This is a test embedding input.',
|
input: 'This is a test embedding input.',
|
||||||
}, null, 2)
|
}, null, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (apiFormat?.trim().toLowerCase().endsWith(':rerank')) {
|
if (normalizedApiFormat.endsWith(':rerank')) {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
model: modelName,
|
model: modelName,
|
||||||
query: 'Apple',
|
query: 'Apple',
|
||||||
@@ -431,6 +475,16 @@ export function buildDefaultModelTestRequestBody(modelName: string, apiFormat?:
|
|||||||
}, null, 2)
|
}, null, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (normalizedApiFormat === 'openai:image') {
|
||||||
|
return JSON.stringify({
|
||||||
|
model: modelName,
|
||||||
|
prompt: DEFAULT_MODEL_TEST_MESSAGE,
|
||||||
|
n: 1,
|
||||||
|
size: '1024x1024',
|
||||||
|
stream: true,
|
||||||
|
}, null, 2)
|
||||||
|
}
|
||||||
|
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
model: modelName,
|
model: modelName,
|
||||||
messages: [
|
messages: [
|
||||||
|
|||||||
Reference in New Issue
Block a user