mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-10 05:00:19 +08:00
Merge remote-tracking branch 'origin/pr/593'
This commit is contained in:
@@ -21,6 +21,8 @@ describe('api format display helpers', () => {
|
||||
expect(normalizeApiFormatAlias('JINA_EMBEDDING')).toBe(API_FORMATS.JINA_EMBEDDING)
|
||||
expect(normalizeApiFormatAlias('JINA_RERANK')).toBe(API_FORMATS.JINA_RERANK)
|
||||
expect(normalizeApiFormatAlias('DOUBAO_EMBEDDING')).toBe(API_FORMATS.DOUBAO_EMBEDDING)
|
||||
expect(normalizeApiFormatAlias('ALIYUN_MULTIMODAL_EMBEDDING')).toBe(API_FORMATS.ALIYUN_MULTIMODAL_EMBEDDING)
|
||||
expect(normalizeApiFormatAlias('dashscope:multimodal_embedding')).toBe(API_FORMATS.ALIYUN_MULTIMODAL_EMBEDDING)
|
||||
})
|
||||
|
||||
it('formats rerank api format ids distinctly from chat formats', () => {
|
||||
@@ -35,10 +37,12 @@ describe('api format display helpers', () => {
|
||||
expect(formatApiFormat(API_FORMATS.GEMINI_EMBEDDING)).toBe('Gemini Embedding')
|
||||
expect(formatApiFormat(API_FORMATS.JINA_EMBEDDING)).toBe('Jina Embedding')
|
||||
expect(formatApiFormat(API_FORMATS.DOUBAO_EMBEDDING)).toBe('Doubao Embedding')
|
||||
expect(formatApiFormat(API_FORMATS.ALIYUN_MULTIMODAL_EMBEDDING)).toBe('Aliyun Multimodal Embedding')
|
||||
expect(formatApiFormatShort(API_FORMATS.OPENAI_EMBEDDING)).toBe('OE')
|
||||
expect(formatApiFormatShort(API_FORMATS.GEMINI_EMBEDDING)).toBe('GE')
|
||||
expect(formatApiFormatShort(API_FORMATS.JINA_EMBEDDING)).toBe('JE')
|
||||
expect(formatApiFormatShort(API_FORMATS.DOUBAO_EMBEDDING)).toBe('DE')
|
||||
expect(formatApiFormatShort(API_FORMATS.ALIYUN_MULTIMODAL_EMBEDDING)).toBe('AE')
|
||||
})
|
||||
|
||||
it('does not remap retired api format ids', () => {
|
||||
@@ -72,6 +76,7 @@ describe('api format display helpers', () => {
|
||||
API_FORMATS.GEMINI_EMBEDDING,
|
||||
API_FORMATS.JINA_EMBEDDING,
|
||||
API_FORMATS.JINA_RERANK,
|
||||
API_FORMATS.ALIYUN_MULTIMODAL_EMBEDDING,
|
||||
])).toEqual([
|
||||
API_FORMATS.OPENAI,
|
||||
API_FORMATS.OPENAI_RESPONSES,
|
||||
@@ -81,6 +86,7 @@ describe('api format display helpers', () => {
|
||||
API_FORMATS.JINA_EMBEDDING,
|
||||
API_FORMATS.JINA_RERANK,
|
||||
API_FORMATS.DOUBAO_EMBEDDING,
|
||||
API_FORMATS.ALIYUN_MULTIMODAL_EMBEDDING,
|
||||
'openai:compact',
|
||||
])
|
||||
})
|
||||
@@ -104,6 +110,7 @@ describe('api format display helpers', () => {
|
||||
it('groups embedding api formats by provider family', () => {
|
||||
expect(groupApiFormats([
|
||||
API_FORMATS.DOUBAO_EMBEDDING,
|
||||
API_FORMATS.ALIYUN_MULTIMODAL_EMBEDDING,
|
||||
API_FORMATS.JINA_RERANK,
|
||||
API_FORMATS.JINA_EMBEDDING,
|
||||
API_FORMATS.GEMINI_EMBEDDING,
|
||||
@@ -114,6 +121,7 @@ describe('api format display helpers', () => {
|
||||
{ family: 'gemini', label: 'Gemini', formats: [API_FORMATS.GEMINI_EMBEDDING] },
|
||||
{ family: 'jina', label: 'Jina', formats: [API_FORMATS.JINA_EMBEDDING, API_FORMATS.JINA_RERANK] },
|
||||
{ family: 'doubao', label: 'Doubao', formats: [API_FORMATS.DOUBAO_EMBEDDING] },
|
||||
{ family: 'aliyun', label: 'Aliyun', formats: [API_FORMATS.ALIYUN_MULTIMODAL_EMBEDDING] },
|
||||
])
|
||||
})
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ export const API_FORMATS = {
|
||||
JINA_EMBEDDING: 'jina:embedding',
|
||||
JINA_RERANK: 'jina:rerank',
|
||||
DOUBAO_EMBEDDING: 'doubao:embedding',
|
||||
ALIYUN_MULTIMODAL_EMBEDDING: 'aliyun:multimodal_embedding',
|
||||
} as const
|
||||
|
||||
export type APIFormat = typeof API_FORMATS[keyof typeof API_FORMATS]
|
||||
@@ -39,6 +40,7 @@ export const API_FORMAT_LABELS: Record<string, string> = {
|
||||
[API_FORMATS.JINA_EMBEDDING]: 'Jina Embedding',
|
||||
[API_FORMATS.JINA_RERANK]: 'Jina Rerank',
|
||||
[API_FORMATS.DOUBAO_EMBEDDING]: 'Doubao Embedding',
|
||||
[API_FORMATS.ALIYUN_MULTIMODAL_EMBEDDING]: 'Aliyun Multimodal Embedding',
|
||||
CLAUDE: 'Claude Messages',
|
||||
CLAUDE_MESSAGES: 'Claude Messages',
|
||||
OPENAI: 'OpenAI Chat',
|
||||
@@ -56,6 +58,7 @@ export const API_FORMAT_LABELS: Record<string, string> = {
|
||||
JINA_EMBEDDING: 'Jina Embedding',
|
||||
JINA_RERANK: 'Jina Rerank',
|
||||
DOUBAO_EMBEDDING: 'Doubao Embedding',
|
||||
ALIYUN_MULTIMODAL_EMBEDDING: 'Aliyun Multimodal Embedding',
|
||||
}
|
||||
|
||||
// API 格式缩写映射(用于空间紧凑的显示场景)
|
||||
@@ -75,6 +78,7 @@ export const API_FORMAT_SHORT: Record<string, string> = {
|
||||
[API_FORMATS.JINA_EMBEDDING]: 'JE',
|
||||
[API_FORMATS.JINA_RERANK]: 'JR',
|
||||
[API_FORMATS.DOUBAO_EMBEDDING]: 'DE',
|
||||
[API_FORMATS.ALIYUN_MULTIMODAL_EMBEDDING]: 'AE',
|
||||
OPENAI: 'O',
|
||||
OPENAI_RESPONSES: 'OR',
|
||||
OPENAI_RESPONSES_COMPACT: 'ORC',
|
||||
@@ -92,6 +96,7 @@ export const API_FORMAT_SHORT: Record<string, string> = {
|
||||
JINA_EMBEDDING: 'JE',
|
||||
JINA_RERANK: 'JR',
|
||||
DOUBAO_EMBEDDING: 'DE',
|
||||
ALIYUN_MULTIMODAL_EMBEDDING: 'AE',
|
||||
}
|
||||
|
||||
// API 格式排序顺序(统一的显示顺序)
|
||||
@@ -111,6 +116,7 @@ export const API_FORMAT_ORDER: string[] = [
|
||||
API_FORMATS.JINA_EMBEDDING,
|
||||
API_FORMATS.JINA_RERANK,
|
||||
API_FORMATS.DOUBAO_EMBEDDING,
|
||||
API_FORMATS.ALIYUN_MULTIMODAL_EMBEDDING,
|
||||
]
|
||||
|
||||
// Family 显示名称映射
|
||||
@@ -120,6 +126,7 @@ export const API_FORMAT_FAMILY_LABELS: Record<string, string> = {
|
||||
gemini: 'Gemini',
|
||||
jina: 'Jina',
|
||||
doubao: 'Doubao',
|
||||
aliyun: 'Aliyun',
|
||||
}
|
||||
|
||||
// Kind 显示名称映射
|
||||
@@ -137,7 +144,7 @@ export const API_FORMAT_KIND_LABELS: Record<string, string> = {
|
||||
}
|
||||
|
||||
// Family 排序顺序
|
||||
const FAMILY_ORDER = ['openai', 'claude', 'gemini', 'jina', 'doubao']
|
||||
const FAMILY_ORDER = ['openai', 'claude', 'gemini', 'jina', 'doubao', 'aliyun']
|
||||
|
||||
// 工具函数:从 API 格式中提取 family 和 kind
|
||||
export function parseApiFormat(format: string): { family: string; kind: string } {
|
||||
@@ -183,8 +190,20 @@ export function normalizeApiFormatAlias(format: string | null | undefined): stri
|
||||
return API_FORMATS.JINA_RERANK
|
||||
case 'DOUBAO_EMBEDDING':
|
||||
return API_FORMATS.DOUBAO_EMBEDDING
|
||||
case 'ALIYUN_MULTIMODAL_EMBEDDING':
|
||||
case 'ALIYUN_EMBEDDING':
|
||||
case 'DASHSCOPE_MULTIMODAL_EMBEDDING':
|
||||
case 'DASHSCOPE_EMBEDDING':
|
||||
return API_FORMATS.ALIYUN_MULTIMODAL_EMBEDDING
|
||||
default:
|
||||
return raw.toLowerCase()
|
||||
switch (raw.toLowerCase()) {
|
||||
case 'dashscope:multimodal_embedding':
|
||||
case 'aliyun_multimodal_embedding':
|
||||
case 'dashscope_multimodal_embedding':
|
||||
return API_FORMATS.ALIYUN_MULTIMODAL_EMBEDDING
|
||||
default:
|
||||
return raw.toLowerCase()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,13 @@ describe('global model form embedding payload helpers', () => {
|
||||
streaming: false,
|
||||
embedding: true,
|
||||
model_type: 'embedding',
|
||||
api_formats: ['openai:embedding', 'gemini:embedding', 'jina:embedding', 'doubao:embedding'],
|
||||
api_formats: [
|
||||
'openai:embedding',
|
||||
'gemini:embedding',
|
||||
'jina:embedding',
|
||||
'doubao:embedding',
|
||||
'aliyun:multimodal_embedding',
|
||||
],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@ export const EMBEDDING_API_FORMATS = [
|
||||
'gemini:embedding',
|
||||
'jina:embedding',
|
||||
'doubao:embedding',
|
||||
'aliyun:multimodal_embedding',
|
||||
] as const
|
||||
|
||||
export const RERANK_API_FORMATS = [
|
||||
|
||||
+1
@@ -16,6 +16,7 @@ describe('provider model form embedding helpers', () => {
|
||||
{ supported_capabilities: null, config: { embedding: true } },
|
||||
{ supported_capabilities: null, config: { model_type: 'embedding' } },
|
||||
{ supported_capabilities: null, config: { api_formats: ['doubao:embedding'] } },
|
||||
{ supported_capabilities: null, config: { api_formats: ['aliyun:multimodal_embedding'] } },
|
||||
{ supports_embedding: true, effective_supports_embedding: null, config: {} },
|
||||
{ supports_embedding: null, effective_supports_embedding: true, config: {} },
|
||||
])('detects embedding metadata from %o', (model) => {
|
||||
|
||||
@@ -7,6 +7,11 @@ interface EmbeddingMetadataCarrier {
|
||||
config?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
function isEmbeddingApiFormat(format: unknown): boolean {
|
||||
const value = String(format).trim().toLowerCase()
|
||||
return value.endsWith(':embedding') || value === 'aliyun:multimodal_embedding'
|
||||
}
|
||||
|
||||
export interface ProviderModelCreatePayloadInput {
|
||||
globalModelId: string
|
||||
providerModelName: string
|
||||
@@ -45,7 +50,7 @@ export function modelSupportsEmbedding(model: EmbeddingMetadataCarrier | null |
|
||||
return supportedCapabilities?.includes('embedding') === true
|
||||
|| config.embedding === true
|
||||
|| config.model_type === 'embedding'
|
||||
|| (Array.isArray(config.api_formats) && config.api_formats.some((format) => String(format).endsWith(':embedding')))
|
||||
|| (Array.isArray(config.api_formats) && config.api_formats.some(isEmbeddingApiFormat))
|
||||
}
|
||||
|
||||
export function buildProviderModelCreatePayload(input: ProviderModelCreatePayloadInput): ModelCreate {
|
||||
|
||||
+15
@@ -34,6 +34,21 @@ describe('buildDefaultModelTestRequestBody', () => {
|
||||
expect(body.stream).toBeUndefined()
|
||||
})
|
||||
|
||||
it('uses multimodal input and qwen3-vl defaults for Aliyun embedding tests', () => {
|
||||
const body = JSON.parse(buildDefaultModelTestRequestBody('qwen3-vl-embedding', 'aliyun:multimodal_embedding'))
|
||||
|
||||
expect(body).toEqual({
|
||||
model: 'qwen3-vl-embedding',
|
||||
input: [
|
||||
{ text: 'White running shoes, lightweight and breathable.' },
|
||||
{ image: 'https://dashscope.oss-cn-beijing.aliyuncs.com/images/256_1.png' },
|
||||
],
|
||||
parameters: {
|
||||
enable_fusion: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it.each([
|
||||
'openai:rerank',
|
||||
'jina:rerank',
|
||||
|
||||
@@ -151,6 +151,19 @@ export function buildDefaultModelTestRequestBody(
|
||||
): string {
|
||||
const normalizedApiFormat = normalizeApiFormatAlias(apiFormat ?? '')
|
||||
|
||||
if (normalizedApiFormat === 'aliyun:multimodal_embedding') {
|
||||
return JSON.stringify({
|
||||
model: modelName,
|
||||
input: [
|
||||
{ text: 'White running shoes, lightweight and breathable.' },
|
||||
{ image: 'https://dashscope.oss-cn-beijing.aliyuncs.com/images/256_1.png' },
|
||||
],
|
||||
parameters: {
|
||||
enable_fusion: true,
|
||||
},
|
||||
}, null, 2)
|
||||
}
|
||||
|
||||
if (normalizedApiFormat.endsWith(':embedding')) {
|
||||
return JSON.stringify({
|
||||
model: modelName,
|
||||
@@ -273,4 +286,4 @@ export function parseModelTestRequestHeadersDraft(
|
||||
emptyError: null,
|
||||
invalidTypeError: '测试请求头必须是 JSON 对象',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ const ENDPOINT_SORT_ORDER = [
|
||||
'jina:embedding',
|
||||
'jina:rerank',
|
||||
'doubao:embedding',
|
||||
'aliyun:multimodal_embedding',
|
||||
]
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,6 +34,7 @@ export function useProviderFilters(
|
||||
{ value: 'jina:embedding', label: 'Jina Embedding' },
|
||||
{ value: 'jina:rerank', label: 'Jina Rerank' },
|
||||
{ value: 'doubao:embedding', label: 'Doubao Embedding' },
|
||||
{ value: 'aliyun:multimodal_embedding', label: 'Aliyun Multimodal Embedding' },
|
||||
]
|
||||
|
||||
const modelFilters = computed<FilterOption[]>(() => {
|
||||
|
||||
@@ -20,7 +20,7 @@ describe('embedding mock metadata', () => {
|
||||
|
||||
it('includes all embedding API formats as distinct catalog formats', () => {
|
||||
const embeddingFormats = MOCK_API_FORMATS.formats
|
||||
.filter(format => format.value.endsWith(':embedding'))
|
||||
.filter(format => format.value.endsWith(':embedding') || format.value.endsWith('_embedding'))
|
||||
.map(format => [format.value, format.label])
|
||||
|
||||
expect(embeddingFormats).toEqual([
|
||||
@@ -28,6 +28,7 @@ describe('embedding mock metadata', () => {
|
||||
['gemini:embedding', 'Gemini Embedding'],
|
||||
['jina:embedding', 'Jina Embedding'],
|
||||
['doubao:embedding', 'Doubao Embedding'],
|
||||
['aliyun:multimodal_embedding', 'Aliyun Multimodal Embedding'],
|
||||
])
|
||||
})
|
||||
|
||||
|
||||
@@ -1196,6 +1196,12 @@ export const MOCK_API_FORMATS = {
|
||||
{ value: 'gemini:video', label: 'Gemini Video', default_path: '/v1beta/models/{model}:predictLongRunning', aliases: [] },
|
||||
{ value: 'jina:embedding', label: 'Jina Embedding', default_path: '/v1/embeddings', aliases: [] },
|
||||
{ value: 'jina:rerank', label: 'Jina Rerank', default_path: '/v1/rerank', aliases: [] },
|
||||
{ value: 'doubao:embedding', label: 'Doubao Embedding', default_path: '/embeddings/multimodal', aliases: [] }
|
||||
{ value: 'doubao:embedding', label: 'Doubao Embedding', default_path: '/embeddings/multimodal', aliases: [] },
|
||||
{
|
||||
value: 'aliyun:multimodal_embedding',
|
||||
label: 'Aliyun Multimodal Embedding',
|
||||
default_path: '/api/v1/services/embeddings/multimodal-embedding/multimodal-embedding',
|
||||
aliases: ['dashscope:multimodal_embedding'],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1827,7 +1827,7 @@ function generateMockModelsForProvider(providerId: string) {
|
||||
const hasClaude = provider.api_formats.some(f => f.includes('claude'))
|
||||
const hasOpenAI = provider.api_formats.some(f => f.includes('openai'))
|
||||
const hasGemini = provider.api_formats.some(f => f.includes('gemini'))
|
||||
const hasEmbedding = provider.api_formats.some(f => f.endsWith(':embedding'))
|
||||
const hasEmbedding = provider.api_formats.some(f => f.endsWith(':embedding') || f === 'aliyun:multimodal_embedding')
|
||||
const hasRerank = provider.api_formats.some(f => f.endsWith(':rerank'))
|
||||
|
||||
const models: Record<string, unknown>[] = []
|
||||
|
||||
@@ -35,6 +35,7 @@ describe('model catalog embedding helpers', () => {
|
||||
expect(supportsEmbedding(model({ config: { embedding: true } }))).toBe(true)
|
||||
expect(supportsEmbedding(model({ config: { model_type: 'embedding' } }))).toBe(true)
|
||||
expect(supportsEmbedding(model({ config: { api_formats: ['jina:embedding'] } }))).toBe(true)
|
||||
expect(supportsEmbedding(model({ config: { api_formats: ['aliyun:multimodal_embedding'] } }))).toBe(true)
|
||||
expect(supportsEmbedding(model({ config: { api_formats: ['openai:chat'] } }))).toBe(false)
|
||||
})
|
||||
|
||||
|
||||
@@ -395,7 +395,10 @@ function supportsEmbedding(model: PublicGlobalModel): boolean {
|
||||
|| model.supported_capabilities?.includes('embedding') === true
|
||||
|| model.config?.embedding === true
|
||||
|| model.config?.model_type === 'embedding'
|
||||
|| (Array.isArray(model.config?.api_formats) && model.config.api_formats.some((format) => String(format).endsWith(':embedding')))
|
||||
|| (Array.isArray(model.config?.api_formats) && model.config.api_formats.some((format) => {
|
||||
const value = String(format).trim().toLowerCase()
|
||||
return value.endsWith(':embedding') || value === 'aliyun:multimodal_embedding'
|
||||
}))
|
||||
}
|
||||
|
||||
// 添加 ESC 键监听
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import type { PublicGlobalModel } from '@/api/public-models'
|
||||
|
||||
function isEmbeddingApiFormat(format: unknown): boolean {
|
||||
const value = String(format).trim().toLowerCase()
|
||||
return value.endsWith(':embedding') || value === 'aliyun:multimodal_embedding'
|
||||
}
|
||||
|
||||
export function supportsEmbedding(model: PublicGlobalModel): boolean {
|
||||
return model.supports_embedding === true
|
||||
|| model.supported_capabilities?.includes('embedding') === true
|
||||
|| model.config?.embedding === true
|
||||
|| model.config?.model_type === 'embedding'
|
||||
|| (Array.isArray(model.config?.api_formats) && model.config.api_formats.some((format) => String(format).endsWith(':embedding')))
|
||||
|| (Array.isArray(model.config?.api_formats) && model.config.api_formats.some(isEmbeddingApiFormat))
|
||||
}
|
||||
|
||||
export function supportsRerank(model: PublicGlobalModel): boolean {
|
||||
|
||||
Reference in New Issue
Block a user