Compare commits

..

1 Commits

Author SHA1 Message Date
Soulter
ef1f854231 fix: return model metadata separately from providers 2026-07-06 20:54:36 +08:00
11 changed files with 95 additions and 25 deletions

View File

@@ -1299,14 +1299,16 @@ class ProviderConfigService:
if provider.default_config_tmpl:
provider_default_tmpl[provider.type] = provider.default_config_tmpl
providers = copy.deepcopy(self.config.get("provider", []))
model_metadata = {}
for provider in providers:
model_id = provider.get("model")
if isinstance(model_id, str) and model_id in LLM_METADATAS:
provider["model_metadata"] = LLM_METADATAS[model_id]
model_metadata[model_id] = LLM_METADATAS[model_id]
return {
"config_schema": config_schema,
"providers": providers,
"provider_sources": self.config.get("provider_sources", []),
"model_metadata": model_metadata,
}
def list_provider_sources(self) -> dict:
@@ -1551,6 +1553,7 @@ class ProviderConfigService:
) -> dict:
provider_type = self._resolve_provider_type(capability)
providers = []
model_metadata = {}
source_provider_type = {
source["id"]: source.get("provider_type", "chat_completion")
for source in self.provider_manager.provider_sources_config
@@ -1575,9 +1578,9 @@ class ProviderConfigService:
provider_response = copy.deepcopy(provider)
model_id = provider_response.get("model")
if isinstance(model_id, str) and model_id in LLM_METADATAS:
provider_response["model_metadata"] = LLM_METADATAS[model_id]
model_metadata[model_id] = LLM_METADATAS[model_id]
providers.append(provider_response)
return {"providers": providers}
return {"providers": providers, "model_metadata": model_metadata}
def list_providers_for_dashboard_types(
self, provider_type: str | None
@@ -1607,10 +1610,12 @@ class ProviderConfigService:
)
if provider is None:
raise ValueError(f"Provider {provider_id} not found")
model_id = provider.get("model")
provider_response = copy.deepcopy(provider)
model_id = provider_response.get("model")
model_metadata = {}
if isinstance(model_id, str) and model_id in LLM_METADATAS:
provider["model_metadata"] = LLM_METADATAS[model_id]
return {"provider": provider}
model_metadata[model_id] = LLM_METADATAS[model_id]
return {"provider": provider_response, "model_metadata": model_metadata}
async def create_provider(self, config: dict, source_id: str | None = None) -> None:
config = copy.deepcopy(config)

View File

@@ -78,6 +78,17 @@ export interface ProviderSchemaData {
config_schema?: OpenConfig;
providers?: OpenConfig[];
provider_sources?: OpenConfig[];
model_metadata?: Record<string, unknown>;
}
export interface ProviderListData {
providers?: OpenConfig[];
model_metadata?: Record<string, unknown>;
}
export interface ProviderByIdData {
provider?: OpenConfig;
model_metadata?: Record<string, unknown>;
}
export interface ProviderSourceModelsData {
@@ -496,11 +507,13 @@ export const providerApi = {
);
},
list(params?: ProviderListParams) {
return typed<{ providers: OpenConfig[] }>(
return typed<ProviderListData>(
openApiV1.listProviders({ query: generatedQuery(params) }),
);
},
async listByProviderType(providerType: string): Promise<AxiosResponse<ApiEnvelope<OpenConfig[]>>> {
async listByProviderType(
providerType: string,
): Promise<AxiosResponse<ApiEnvelope<ProviderListData>>> {
const capabilities = providerTypeToCapabilities(providerType);
if (capabilities.length === 0) {
const response = await providerApi.list();
@@ -508,7 +521,10 @@ export const providerApi = {
...response,
data: {
...response.data,
data: response.data.data.providers || [],
data: {
providers: response.data.data.providers || [],
model_metadata: response.data.data.model_metadata || {},
},
},
};
}
@@ -517,11 +533,23 @@ export const providerApi = {
capabilities.map((capability) => providerApi.list({ capability })),
);
const first = responses[0];
const modelMetadata = responses.reduce<Record<string, unknown>>(
(acc, response) => ({
...acc,
...(response.data.data.model_metadata || {}),
}),
{},
);
return {
...first,
data: {
...first.data,
data: responses.flatMap((response) => response.data.data.providers || []),
data: {
providers: responses.flatMap(
(response) => response.data.data.providers || [],
),
model_metadata: modelMetadata,
},
},
};
},
@@ -545,7 +573,7 @@ export const providerApi = {
);
},
get(providerId: string, merged = false) {
return typed<{ provider: OpenConfig }>(
return typed<ProviderByIdData>(
openApiV1.getProviderById({
query: { provider_id: providerId, merged },
}),

View File

@@ -141,6 +141,7 @@ import { useToast } from "@/utils/toast";
import {
formatContextLimit,
providerCapabilityBadges,
providerWithModelMetadata,
type ProviderMetadataSource,
} from "@/utils/providerMetadata";
@@ -230,9 +231,12 @@ async function loadProviderConfigs(force = false) {
try {
const response = await providerApi.listByProviderType("chat_completion");
if (response.data.status === "ok") {
const metadataMap = response.data.data.model_metadata || {};
providerConfigs.value = (
(response.data.data || []) as unknown as ProviderConfig[]
).filter((provider: ProviderConfig) => provider.enable !== false);
(response.data.data.providers || []) as unknown as ProviderConfig[]
)
.map((provider) => providerWithModelMetadata(provider, metadataMap))
.filter((provider: ProviderConfig) => provider.enable !== false);
providersLoaded.value = true;
const selected = selectedProvider.value;
if (selected) {

View File

@@ -137,6 +137,7 @@ import { useModuleI18n } from "@/i18n/composables";
import {
formatContextLimit,
providerCapabilityBadges,
providerWithModelMetadata,
type ProviderMetadataSource,
} from "@/utils/providerMetadata";
@@ -168,9 +169,12 @@ async function loadProviderConfigs(force = false) {
try {
const response = await providerApi.listByProviderType("chat_completion");
if (response.data.status === "ok") {
const metadataMap = response.data.data.model_metadata || {};
providerConfigs.value = (
(response.data.data || []) as unknown as ProviderConfig[]
).filter((provider: ProviderConfig) => provider.enable !== false);
(response.data.data.providers || []) as unknown as ProviderConfig[]
)
.map((provider) => providerWithModelMetadata(provider, metadataMap))
.filter((provider: ProviderConfig) => provider.enable !== false);
providersLoaded.value = true;
}
} catch (error) {

View File

@@ -241,7 +241,11 @@ import { computed, ref, watch } from 'vue'
import { providerApi } from '@/api/v1'
import { useModuleI18n } from '@/i18n/composables'
import { useToast } from '@/utils/toast'
import { formatContextLimit, providerCapabilityBadges } from '@/utils/providerMetadata'
import {
formatContextLimit,
providerCapabilityBadges,
providerWithModelMetadata
} from '@/utils/providerMetadata'
import ProviderChatCompletionPanel from '@/components/provider/ProviderChatCompletionPanel.vue'
import ProviderPage from '@/views/ProviderPage.vue'
@@ -329,7 +333,10 @@ async function loadProviders() {
try {
const response = await providerApi.listByProviderType(props.providerType)
if (response.data.status === 'ok') {
const providers = response.data.data || []
const metadataMap = response.data.data.model_metadata || {}
const providers = (response.data.data.providers || []).map((provider) =>
providerWithModelMetadata(provider, metadataMap)
)
providerList.value = props.providerSubtype
? providers.filter((provider) => matchesProviderSubtype(provider, props.providerSubtype))
: providers

View File

@@ -691,6 +691,7 @@ export function useProviderSources(options: UseProviderSourcesOptions) {
providerTemplates.value = configSchema.value.provider.config_template
}
providerSources.value = response.data.data.provider_sources || []
modelMetadata.value = (response.data.data.model_metadata || {}) as Record<string, any>
providers.value = response.data.data.providers || []
}
} catch (error) {

View File

@@ -6,12 +6,15 @@ export interface ProviderModelMetadata {
}
export interface ProviderMetadataSource {
model?: string
modalities?: string[]
model_metadata?: ProviderModelMetadata | null
max_context_tokens?: number
reasoning?: boolean
}
export type ProviderMetadataMap = Record<string, ProviderModelMetadata | null | undefined>
export interface ProviderCapabilityBadge {
key: string
icon: string
@@ -19,6 +22,21 @@ export interface ProviderCapabilityBadge {
tooltip: string
}
export function providerWithModelMetadata<T extends ProviderMetadataSource>(
provider: T,
metadataMap: Record<string, unknown> | ProviderMetadataMap | null | undefined
): T & ProviderMetadataSource {
const modelId = typeof provider.model === 'string' ? provider.model : ''
const metadata = modelId
? (metadataMap?.[modelId] as ProviderModelMetadata | null | undefined)
: null
if (!metadata && !provider.model_metadata) return provider
return {
...provider,
model_metadata: metadata || provider.model_metadata || null
}
}
export function formatContextLimit(provider: ProviderMetadataSource | null | undefined): string {
const context = provider?.model_metadata?.limit?.context || provider?.max_context_tokens
if (!context || typeof context !== 'number') return ''

View File

@@ -998,9 +998,10 @@ export default {
providerApi.listByProviderType('embedding,rerank,chat_completion')
.then(response => {
if (response.data.status === 'ok') {
this.embeddingProviderConfigs = response.data.data.filter(provider => provider.provider_type === 'embedding');
this.rerankProviderConfigs = response.data.data.filter(provider => provider.provider_type === 'rerank');
this.llmProviderConfigs = response.data.data.filter(provider => provider.provider_type === 'chat_completion');
const providers = response.data.data.providers || [];
this.embeddingProviderConfigs = providers.filter(provider => provider.provider_type === 'embedding');
this.rerankProviderConfigs = providers.filter(provider => provider.provider_type === 'rerank');
this.llmProviderConfigs = providers.filter(provider => provider.provider_type === 'chat_completion');
} else {
this.showSnackbar(response.data.message || this.tm('messages.getEmbeddingModelListFailed'), 'error');
return [];

View File

@@ -366,10 +366,11 @@ const loadProviders = async () => {
try {
const response = await providerApi.listByProviderType('embedding,rerank')
if (response.data.status === 'ok') {
embeddingProviders.value = response.data.data.filter(
const providers = response.data.data.providers || []
embeddingProviders.value = providers.filter(
(p: any) => p.provider_type === 'embedding'
)
rerankProviders.value = response.data.data.filter(
rerankProviders.value = providers.filter(
(p: any) => p.provider_type === 'rerank'
)
}

View File

@@ -775,7 +775,7 @@ const loadLlmProviders = async () => {
try {
const response = await providerApi.listByProviderType('chat_completion')
if (response.data.status === 'ok') {
llmProviders.value = response.data.data
llmProviders.value = response.data.data.providers || []
}
} catch (error) {
console.error('Failed to load LLM providers:', error)

View File

@@ -224,10 +224,11 @@ const loadProviders = async () => {
try {
const response = await providerApi.listByProviderType('embedding,rerank')
if (response.data.status === 'ok') {
embeddingProviders.value = response.data.data.filter(
const providers = response.data.data.providers || []
embeddingProviders.value = providers.filter(
(p: any) => p.provider_type === 'embedding'
)
rerankProviders.value = response.data.data.filter(
rerankProviders.value = providers.filter(
(p: any) => p.provider_type === 'rerank'
)
}