mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
feat: add embedding and rerank support
This commit is contained in:
@@ -200,6 +200,7 @@ export interface ModelExport {
|
||||
supports_streaming?: boolean | null
|
||||
supports_extended_thinking?: boolean | null
|
||||
supports_image_generation?: boolean | null
|
||||
supports_embedding?: boolean | null
|
||||
is_active: boolean
|
||||
config?: Record<string, unknown>
|
||||
}
|
||||
|
||||
@@ -15,6 +15,30 @@ describe('api format display helpers', () => {
|
||||
expect(normalizeApiFormatAlias('OPENAI_RESPONSES')).toBe(API_FORMATS.OPENAI_RESPONSES)
|
||||
expect(normalizeApiFormatAlias('OPENAI_RESPONSES_COMPACT')).toBe(API_FORMATS.OPENAI_RESPONSES_COMPACT)
|
||||
expect(normalizeApiFormatAlias('GEMINI_GENERATE_CONTENT')).toBe(API_FORMATS.GEMINI_GENERATE_CONTENT)
|
||||
expect(normalizeApiFormatAlias('OPENAI_EMBEDDING')).toBe(API_FORMATS.OPENAI_EMBEDDING)
|
||||
expect(normalizeApiFormatAlias('OPENAI_RERANK')).toBe(API_FORMATS.OPENAI_RERANK)
|
||||
expect(normalizeApiFormatAlias('GEMINI_EMBEDDING')).toBe(API_FORMATS.GEMINI_EMBEDDING)
|
||||
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)
|
||||
})
|
||||
|
||||
it('formats rerank api format ids distinctly from chat formats', () => {
|
||||
expect(formatApiFormat(API_FORMATS.OPENAI_RERANK)).toBe('OpenAI Rerank')
|
||||
expect(formatApiFormat(API_FORMATS.JINA_RERANK)).toBe('Jina Rerank')
|
||||
expect(formatApiFormatShort(API_FORMATS.OPENAI_RERANK)).toBe('ORR')
|
||||
expect(formatApiFormatShort(API_FORMATS.JINA_RERANK)).toBe('JR')
|
||||
})
|
||||
|
||||
it('formats embedding api format ids distinctly from chat formats', () => {
|
||||
expect(formatApiFormat(API_FORMATS.OPENAI_EMBEDDING)).toBe('OpenAI Embedding')
|
||||
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(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')
|
||||
})
|
||||
|
||||
it('does not remap retired api format ids', () => {
|
||||
@@ -40,15 +64,59 @@ describe('api format display helpers', () => {
|
||||
it('sorts only current canonical formats into known slots', () => {
|
||||
expect(sortApiFormats([
|
||||
'openai:compact',
|
||||
API_FORMATS.DOUBAO_EMBEDDING,
|
||||
API_FORMATS.OPENAI,
|
||||
API_FORMATS.OPENAI_RESPONSES,
|
||||
API_FORMATS.OPENAI_EMBEDDING,
|
||||
API_FORMATS.OPENAI_RERANK,
|
||||
API_FORMATS.GEMINI_EMBEDDING,
|
||||
API_FORMATS.JINA_EMBEDDING,
|
||||
API_FORMATS.JINA_RERANK,
|
||||
])).toEqual([
|
||||
API_FORMATS.OPENAI,
|
||||
API_FORMATS.OPENAI_RESPONSES,
|
||||
API_FORMATS.OPENAI_EMBEDDING,
|
||||
API_FORMATS.OPENAI_RERANK,
|
||||
API_FORMATS.GEMINI_EMBEDDING,
|
||||
API_FORMATS.JINA_EMBEDDING,
|
||||
API_FORMATS.JINA_RERANK,
|
||||
API_FORMATS.DOUBAO_EMBEDDING,
|
||||
'openai:compact',
|
||||
])
|
||||
})
|
||||
|
||||
it('keeps embedding formats after chat/generation formats within each family', () => {
|
||||
expect(sortApiFormats([
|
||||
API_FORMATS.GEMINI_EMBEDDING,
|
||||
API_FORMATS.OPENAI_EMBEDDING,
|
||||
API_FORMATS.OPENAI_RERANK,
|
||||
API_FORMATS.GEMINI_GENERATE_CONTENT,
|
||||
API_FORMATS.OPENAI,
|
||||
])).toEqual([
|
||||
API_FORMATS.OPENAI,
|
||||
API_FORMATS.OPENAI_EMBEDDING,
|
||||
API_FORMATS.OPENAI_RERANK,
|
||||
API_FORMATS.GEMINI_GENERATE_CONTENT,
|
||||
API_FORMATS.GEMINI_EMBEDDING,
|
||||
])
|
||||
})
|
||||
|
||||
it('groups embedding api formats by provider family', () => {
|
||||
expect(groupApiFormats([
|
||||
API_FORMATS.DOUBAO_EMBEDDING,
|
||||
API_FORMATS.JINA_RERANK,
|
||||
API_FORMATS.JINA_EMBEDDING,
|
||||
API_FORMATS.GEMINI_EMBEDDING,
|
||||
API_FORMATS.OPENAI_EMBEDDING,
|
||||
API_FORMATS.OPENAI_RERANK,
|
||||
])).toEqual([
|
||||
{ family: 'openai', label: 'OpenAI', formats: [API_FORMATS.OPENAI_EMBEDDING, API_FORMATS.OPENAI_RERANK] },
|
||||
{ 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] },
|
||||
])
|
||||
})
|
||||
|
||||
it('groups retired enum-style aliases as unknown raw families', () => {
|
||||
expect(groupApiFormats(['OPENAI_CLI'])).toEqual([{
|
||||
family: 'openai_cli',
|
||||
|
||||
@@ -8,10 +8,16 @@ export const API_FORMATS = {
|
||||
OPENAI_RESPONSES_COMPACT: 'openai:responses:compact',
|
||||
OPENAI_IMAGE: 'openai:image',
|
||||
OPENAI_VIDEO: 'openai:video',
|
||||
OPENAI_EMBEDDING: 'openai:embedding',
|
||||
OPENAI_RERANK: 'openai:rerank',
|
||||
GEMINI: 'gemini:generate_content',
|
||||
GEMINI_GENERATE_CONTENT: 'gemini:generate_content',
|
||||
GEMINI_VIDEO: 'gemini:video',
|
||||
GEMINI_FILES: 'gemini:files',
|
||||
GEMINI_EMBEDDING: 'gemini:embedding',
|
||||
JINA_EMBEDDING: 'jina:embedding',
|
||||
JINA_RERANK: 'jina:rerank',
|
||||
DOUBAO_EMBEDDING: 'doubao:embedding',
|
||||
} as const
|
||||
|
||||
export type APIFormat = typeof API_FORMATS[keyof typeof API_FORMATS]
|
||||
@@ -24,9 +30,15 @@ export const API_FORMAT_LABELS: Record<string, string> = {
|
||||
[API_FORMATS.OPENAI_RESPONSES_COMPACT]: 'OpenAI Responses Compact',
|
||||
[API_FORMATS.OPENAI_IMAGE]: 'OpenAI Image',
|
||||
[API_FORMATS.OPENAI_VIDEO]: 'OpenAI Video',
|
||||
[API_FORMATS.OPENAI_EMBEDDING]: 'OpenAI Embedding',
|
||||
[API_FORMATS.OPENAI_RERANK]: 'OpenAI Rerank',
|
||||
[API_FORMATS.GEMINI_GENERATE_CONTENT]: 'Gemini Generate Content',
|
||||
[API_FORMATS.GEMINI_VIDEO]: 'Gemini Video',
|
||||
[API_FORMATS.GEMINI_FILES]: 'Gemini Files',
|
||||
[API_FORMATS.GEMINI_EMBEDDING]: 'Gemini Embedding',
|
||||
[API_FORMATS.JINA_EMBEDDING]: 'Jina Embedding',
|
||||
[API_FORMATS.JINA_RERANK]: 'Jina Rerank',
|
||||
[API_FORMATS.DOUBAO_EMBEDDING]: 'Doubao Embedding',
|
||||
CLAUDE: 'Claude Messages',
|
||||
CLAUDE_MESSAGES: 'Claude Messages',
|
||||
OPENAI: 'OpenAI Chat',
|
||||
@@ -34,10 +46,16 @@ export const API_FORMAT_LABELS: Record<string, string> = {
|
||||
OPENAI_RESPONSES_COMPACT: 'OpenAI Responses Compact',
|
||||
OPENAI_IMAGE: 'OpenAI Image',
|
||||
OPENAI_VIDEO: 'OpenAI Video',
|
||||
OPENAI_EMBEDDING: 'OpenAI Embedding',
|
||||
OPENAI_RERANK: 'OpenAI Rerank',
|
||||
GEMINI: 'Gemini Generate Content',
|
||||
GEMINI_GENERATE_CONTENT: 'Gemini Generate Content',
|
||||
GEMINI_VIDEO: 'Gemini Video',
|
||||
GEMINI_FILES: 'Gemini Files',
|
||||
GEMINI_EMBEDDING: 'Gemini Embedding',
|
||||
JINA_EMBEDDING: 'Jina Embedding',
|
||||
JINA_RERANK: 'Jina Rerank',
|
||||
DOUBAO_EMBEDDING: 'Doubao Embedding',
|
||||
}
|
||||
|
||||
// API 格式缩写映射(用于空间紧凑的显示场景)
|
||||
@@ -47,21 +65,33 @@ export const API_FORMAT_SHORT: Record<string, string> = {
|
||||
[API_FORMATS.OPENAI_RESPONSES_COMPACT]: 'ORC',
|
||||
[API_FORMATS.OPENAI_IMAGE]: 'OI',
|
||||
[API_FORMATS.OPENAI_VIDEO]: 'OV',
|
||||
[API_FORMATS.OPENAI_EMBEDDING]: 'OE',
|
||||
[API_FORMATS.OPENAI_RERANK]: 'ORR',
|
||||
[API_FORMATS.CLAUDE_MESSAGES]: 'CM',
|
||||
[API_FORMATS.GEMINI_GENERATE_CONTENT]: 'G',
|
||||
[API_FORMATS.GEMINI_VIDEO]: 'GV',
|
||||
[API_FORMATS.GEMINI_FILES]: 'GF',
|
||||
[API_FORMATS.GEMINI_EMBEDDING]: 'GE',
|
||||
[API_FORMATS.JINA_EMBEDDING]: 'JE',
|
||||
[API_FORMATS.JINA_RERANK]: 'JR',
|
||||
[API_FORMATS.DOUBAO_EMBEDDING]: 'DE',
|
||||
OPENAI: 'O',
|
||||
OPENAI_RESPONSES: 'OR',
|
||||
OPENAI_RESPONSES_COMPACT: 'ORC',
|
||||
OPENAI_IMAGE: 'OI',
|
||||
OPENAI_VIDEO: 'OV',
|
||||
OPENAI_EMBEDDING: 'OE',
|
||||
OPENAI_RERANK: 'ORR',
|
||||
CLAUDE: 'CM',
|
||||
CLAUDE_MESSAGES: 'CM',
|
||||
GEMINI: 'G',
|
||||
GEMINI_GENERATE_CONTENT: 'G',
|
||||
GEMINI_VIDEO: 'GV',
|
||||
GEMINI_FILES: 'GF',
|
||||
GEMINI_EMBEDDING: 'GE',
|
||||
JINA_EMBEDDING: 'JE',
|
||||
JINA_RERANK: 'JR',
|
||||
DOUBAO_EMBEDDING: 'DE',
|
||||
}
|
||||
|
||||
// API 格式排序顺序(统一的显示顺序)
|
||||
@@ -69,12 +99,18 @@ export const API_FORMAT_ORDER: string[] = [
|
||||
API_FORMATS.OPENAI,
|
||||
API_FORMATS.OPENAI_RESPONSES,
|
||||
API_FORMATS.OPENAI_RESPONSES_COMPACT,
|
||||
API_FORMATS.OPENAI_EMBEDDING,
|
||||
API_FORMATS.OPENAI_RERANK,
|
||||
API_FORMATS.OPENAI_IMAGE,
|
||||
API_FORMATS.OPENAI_VIDEO,
|
||||
API_FORMATS.CLAUDE_MESSAGES,
|
||||
API_FORMATS.GEMINI_GENERATE_CONTENT,
|
||||
API_FORMATS.GEMINI_EMBEDDING,
|
||||
API_FORMATS.GEMINI_VIDEO,
|
||||
API_FORMATS.GEMINI_FILES,
|
||||
API_FORMATS.JINA_EMBEDDING,
|
||||
API_FORMATS.JINA_RERANK,
|
||||
API_FORMATS.DOUBAO_EMBEDDING,
|
||||
]
|
||||
|
||||
// Family 显示名称映射
|
||||
@@ -82,6 +118,8 @@ export const API_FORMAT_FAMILY_LABELS: Record<string, string> = {
|
||||
openai: 'OpenAI',
|
||||
claude: 'Claude',
|
||||
gemini: 'Gemini',
|
||||
jina: 'Jina',
|
||||
doubao: 'Doubao',
|
||||
}
|
||||
|
||||
// Kind 显示名称映射
|
||||
@@ -94,10 +132,12 @@ export const API_FORMAT_KIND_LABELS: Record<string, string> = {
|
||||
image: 'Image',
|
||||
video: 'Video',
|
||||
files: 'Files',
|
||||
embedding: 'Embedding',
|
||||
rerank: 'Rerank',
|
||||
}
|
||||
|
||||
// Family 排序顺序
|
||||
const FAMILY_ORDER = ['openai', 'claude', 'gemini']
|
||||
const FAMILY_ORDER = ['openai', 'claude', 'gemini', 'jina', 'doubao']
|
||||
|
||||
// 工具函数:从 API 格式中提取 family 和 kind
|
||||
export function parseApiFormat(format: string): { family: string; kind: string } {
|
||||
@@ -124,6 +164,10 @@ export function normalizeApiFormatAlias(format: string | null | undefined): stri
|
||||
return API_FORMATS.OPENAI_IMAGE
|
||||
case 'OPENAI_VIDEO':
|
||||
return API_FORMATS.OPENAI_VIDEO
|
||||
case 'OPENAI_EMBEDDING':
|
||||
return API_FORMATS.OPENAI_EMBEDDING
|
||||
case 'OPENAI_RERANK':
|
||||
return API_FORMATS.OPENAI_RERANK
|
||||
case 'GEMINI':
|
||||
case 'GEMINI_GENERATE_CONTENT':
|
||||
return API_FORMATS.GEMINI_GENERATE_CONTENT
|
||||
@@ -131,6 +175,14 @@ export function normalizeApiFormatAlias(format: string | null | undefined): stri
|
||||
return API_FORMATS.GEMINI_VIDEO
|
||||
case 'GEMINI_FILES':
|
||||
return API_FORMATS.GEMINI_FILES
|
||||
case 'GEMINI_EMBEDDING':
|
||||
return API_FORMATS.GEMINI_EMBEDDING
|
||||
case 'JINA_EMBEDDING':
|
||||
return API_FORMATS.JINA_EMBEDDING
|
||||
case 'JINA_RERANK':
|
||||
return API_FORMATS.JINA_RERANK
|
||||
case 'DOUBAO_EMBEDDING':
|
||||
return API_FORMATS.DOUBAO_EMBEDDING
|
||||
default:
|
||||
return raw.toLowerCase()
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ export interface Model {
|
||||
supports_streaming?: boolean | null
|
||||
supports_extended_thinking?: boolean | null
|
||||
supports_image_generation?: boolean | null
|
||||
supports_embedding?: boolean | null
|
||||
// 有效值(合并 Model 和 GlobalModel 默认值后的结果)
|
||||
effective_tiered_pricing?: TieredPricingConfig | null // 有效阶梯计费配置
|
||||
effective_input_price?: number | null
|
||||
@@ -48,6 +49,7 @@ export interface Model {
|
||||
effective_supports_streaming?: boolean | null
|
||||
effective_supports_extended_thinking?: boolean | null
|
||||
effective_supports_image_generation?: boolean | null
|
||||
effective_supports_embedding?: boolean | null
|
||||
is_active: boolean
|
||||
is_available: boolean
|
||||
created_at: string
|
||||
@@ -96,6 +98,7 @@ export interface ModelCapabilities {
|
||||
supports_vision: boolean
|
||||
supports_function_calling: boolean
|
||||
supports_streaming: boolean
|
||||
supports_embedding: boolean
|
||||
[key: string]: boolean
|
||||
}
|
||||
|
||||
@@ -130,6 +133,7 @@ export interface ModelCatalogProviderDetail {
|
||||
supports_vision?: boolean | null
|
||||
supports_function_calling?: boolean | null
|
||||
supports_streaming?: boolean | null
|
||||
supports_embedding?: boolean | null
|
||||
is_active: boolean
|
||||
mapping_id?: string | null
|
||||
}
|
||||
@@ -211,6 +215,7 @@ export interface GlobalModelResponse {
|
||||
default_tiered_pricing: TieredPricingConfig
|
||||
// Key 能力配置 - 模型支持的能力列表
|
||||
supported_capabilities?: string[] | null
|
||||
supports_embedding?: boolean | null
|
||||
// 模型配置(JSON格式)
|
||||
config?: Record<string, unknown> | null
|
||||
// 统计数据
|
||||
|
||||
@@ -340,6 +340,7 @@ export const meApi = {
|
||||
default_price_per_request: number | null
|
||||
default_tiered_pricing: TieredPricingConfig | null
|
||||
supported_capabilities: string[] | null
|
||||
supports_embedding?: boolean | null
|
||||
config: Record<string, unknown> | null
|
||||
usage_count: number
|
||||
}>
|
||||
|
||||
@@ -72,6 +72,7 @@ export interface ModelsDevModelItem {
|
||||
supportsStructuredOutput?: boolean
|
||||
supportsTemperature?: boolean
|
||||
supportsAttachment?: boolean
|
||||
supportsEmbedding?: boolean
|
||||
openWeights?: boolean
|
||||
deprecated?: boolean
|
||||
official?: boolean // 是否来自官方提供商
|
||||
@@ -180,6 +181,9 @@ export async function getModelsDevList(officialOnly: boolean = true): Promise<Mo
|
||||
supportsStructuredOutput: model.structured_output,
|
||||
supportsTemperature: model.temperature,
|
||||
supportsAttachment: model.attachment,
|
||||
supportsEmbedding: model.id.toLowerCase().includes('embedding')
|
||||
|| model.name.toLowerCase().includes('embedding')
|
||||
|| model.family?.toLowerCase().includes('embedding') === true,
|
||||
openWeights: model.open_weights,
|
||||
deprecated: model.deprecated,
|
||||
official: provider.official,
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface PublicGlobalModel {
|
||||
default_price_per_request: number | null // 按次计费价格
|
||||
// Key 能力支持
|
||||
supported_capabilities: string[] | null
|
||||
supports_embedding?: boolean | null
|
||||
// 模型配置(JSON)
|
||||
config: Record<string, unknown> | null
|
||||
// 调用次数
|
||||
|
||||
Reference in New Issue
Block a user