mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
refactor: 将上游模型缓存从前端迁移到后端 Redis
- 后端:定时任务刷新时将上游模型写入 Redis 缓存 - 后端:provider_query 优先从缓存读取,支持 force_refresh 参数 - 后端:auto_fetch_models 开启时改为同步获取,确保前端能立即看到数据 - 前端:移除本地缓存,只保留并发请求去重逻辑 - 前端:KeyAllowedModelsEditDialog 添加刷新上游模型按钮
This commit is contained in:
@@ -219,6 +219,7 @@ export interface ProviderModelsQueryResponse {
|
|||||||
api_format?: string
|
api_format?: string
|
||||||
}>
|
}>
|
||||||
error?: string
|
error?: string
|
||||||
|
from_cache?: boolean
|
||||||
}
|
}
|
||||||
provider: {
|
provider: {
|
||||||
id: string
|
id: string
|
||||||
@@ -478,10 +479,10 @@ export const adminApi = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 查询 Provider 可用模型(从上游 API 获取)
|
// 查询 Provider 可用模型(从上游 API 获取)
|
||||||
async queryProviderModels(providerId: string, apiKeyId?: string): Promise<ProviderModelsQueryResponse> {
|
async queryProviderModels(providerId: string, apiKeyId?: string, forceRefresh = false): Promise<ProviderModelsQueryResponse> {
|
||||||
const response = await apiClient.post<ProviderModelsQueryResponse>(
|
const response = await apiClient.post<ProviderModelsQueryResponse>(
|
||||||
'/api/admin/provider-query/models',
|
'/api/admin/provider-query/models',
|
||||||
{ provider_id: providerId, api_key_id: apiKeyId }
|
{ provider_id: providerId, api_key_id: apiKeyId, force_refresh: forceRefresh }
|
||||||
)
|
)
|
||||||
return response.data
|
return response.data
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ const emit = defineEmits<{
|
|||||||
'changed': []
|
'changed': []
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { fetchModels: fetchCachedModels, clearCache, getCachedModels } = useUpstreamModelsCache()
|
const { fetchModels: fetchCachedModels } = useUpstreamModelsCache()
|
||||||
|
|
||||||
const { error: showError, success } = useToast()
|
const { error: showError, success } = useToast()
|
||||||
const { confirmWarning } = useConfirm()
|
const { confirmWarning } = useConfirm()
|
||||||
@@ -633,24 +633,8 @@ async function loadData() {
|
|||||||
// 同步全局模型选择状态
|
// 同步全局模型选择状态
|
||||||
syncGlobalModelSelection()
|
syncGlobalModelSelection()
|
||||||
|
|
||||||
// 检查缓存
|
// 初始折叠状态
|
||||||
const cachedModels = getCachedModels(props.providerId)
|
collapsedGroups.value = new Set()
|
||||||
if (cachedModels && cachedModels.length > 0) {
|
|
||||||
upstreamModels.value = cachedModels
|
|
||||||
upstreamModelsLoaded.value = true
|
|
||||||
// 同步上游模型选择状态
|
|
||||||
syncUpstreamModelSelection()
|
|
||||||
// 有多个分组时全部折叠
|
|
||||||
const allGroups = new Set(['global'])
|
|
||||||
for (const model of cachedModels) {
|
|
||||||
if (model.api_format) {
|
|
||||||
allGroups.add(model.api_format)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
collapsedGroups.value = allGroups
|
|
||||||
} else {
|
|
||||||
collapsedGroups.value = new Set()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载全局模型列表
|
// 加载全局模型列表
|
||||||
@@ -677,13 +661,9 @@ async function loadExistingModels() {
|
|||||||
|
|
||||||
// 从提供商获取模型
|
// 从提供商获取模型
|
||||||
async function fetchUpstreamModels(forceRefresh = false) {
|
async function fetchUpstreamModels(forceRefresh = false) {
|
||||||
if (forceRefresh) {
|
|
||||||
clearCache(props.providerId)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fetchingUpstreamModels.value = true
|
fetchingUpstreamModels.value = true
|
||||||
const result = await fetchCachedModels(props.providerId, forceRefresh)
|
const result = await fetchCachedModels(props.providerId, undefined, forceRefresh)
|
||||||
if (result) {
|
if (result) {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
showError(result.error, '错误')
|
showError(result.error, '错误')
|
||||||
|
|||||||
@@ -40,10 +40,23 @@
|
|||||||
>
|
>
|
||||||
已选 {{ selectedModels.length }} 个
|
已选 {{ selectedModels.length }} 个
|
||||||
</span>
|
</span>
|
||||||
<Loader2
|
<!-- 刷新上游模型按钮 -->
|
||||||
v-if="fetchingUpstreamModels"
|
<button
|
||||||
class="w-4 h-4 animate-spin text-muted-foreground shrink-0"
|
type="button"
|
||||||
/>
|
class="h-6 w-6 flex items-center justify-center rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors shrink-0"
|
||||||
|
:disabled="fetchingUpstreamModels"
|
||||||
|
title="刷新上游模型"
|
||||||
|
@click="refreshUpstreamModels"
|
||||||
|
>
|
||||||
|
<RefreshCw
|
||||||
|
v-if="!fetchingUpstreamModels"
|
||||||
|
class="w-3.5 h-3.5"
|
||||||
|
/>
|
||||||
|
<Loader2
|
||||||
|
v-else
|
||||||
|
class="w-3.5 h-3.5 animate-spin"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 分组列表 -->
|
<!-- 分组列表 -->
|
||||||
@@ -318,7 +331,8 @@ import {
|
|||||||
Check,
|
Check,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
Lock,
|
Lock,
|
||||||
LockOpen
|
LockOpen,
|
||||||
|
RefreshCw
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import { Dialog, Button, Input } from '@/components/ui'
|
import { Dialog, Button, Input } from '@/components/ui'
|
||||||
import { useToast } from '@/composables/useToast'
|
import { useToast } from '@/composables/useToast'
|
||||||
@@ -627,11 +641,11 @@ async function loadGlobalModels() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 从提供商获取模型(使用缓存)
|
// 从提供商获取模型(使用缓存)
|
||||||
async function fetchUpstreamModels() {
|
async function fetchUpstreamModels(forceRefresh = false) {
|
||||||
if (!props.providerId || !props.apiKey) return
|
if (!props.providerId || !props.apiKey) return
|
||||||
try {
|
try {
|
||||||
fetchingUpstreamModels.value = true
|
fetchingUpstreamModels.value = true
|
||||||
const result = await fetchCachedModels(props.providerId, props.apiKey.id)
|
const result = await fetchCachedModels(props.providerId, props.apiKey.id, forceRefresh)
|
||||||
if (loadingCancelled) return
|
if (loadingCancelled) return
|
||||||
if (result.models.length > 0) {
|
if (result.models.length > 0) {
|
||||||
upstreamModels.value = result.models
|
upstreamModels.value = result.models
|
||||||
@@ -647,6 +661,14 @@ async function fetchUpstreamModels() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 手动刷新上游模型(强制跳过缓存)
|
||||||
|
async function refreshUpstreamModels() {
|
||||||
|
await fetchUpstreamModels(true)
|
||||||
|
if (upstreamModels.value.length > 0) {
|
||||||
|
success('上游模型已刷新')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 解析 allowed_models
|
// 解析 allowed_models
|
||||||
function parseAllowedModels(allowed: AllowedModels): string[] {
|
function parseAllowedModels(allowed: AllowedModels): string[] {
|
||||||
if (allowed === null || allowed === undefined) {
|
if (allowed === null || allowed === undefined) {
|
||||||
|
|||||||
@@ -1,25 +1,16 @@
|
|||||||
/**
|
/**
|
||||||
* 上游模型缓存 - 共享缓存,避免重复请求
|
* 上游模型获取服务
|
||||||
|
*
|
||||||
|
* 缓存已移至后端(Redis),前端只保留并发请求去重,避免同时发多个相同请求。
|
||||||
*/
|
*/
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { adminApi } from '@/api/admin'
|
import { adminApi } from '@/api/admin'
|
||||||
import { parseUpstreamModelError } from '@/utils/errorParser'
|
import { parseUpstreamModelError } from '@/utils/errorParser'
|
||||||
import type { UpstreamModel } from '@/api/endpoints/types'
|
import type { UpstreamModel } from '@/api/endpoints/types'
|
||||||
|
|
||||||
// 扩展类型,包含可能的额外字段
|
|
||||||
export type { UpstreamModel }
|
export type { UpstreamModel }
|
||||||
|
|
||||||
interface CacheEntry {
|
type FetchResult = { models: UpstreamModel[]; error?: string; fromCache?: boolean }
|
||||||
models: UpstreamModel[]
|
|
||||||
timestamp: number
|
|
||||||
}
|
|
||||||
|
|
||||||
type FetchResult = { models: UpstreamModel[]; error?: string }
|
|
||||||
|
|
||||||
// 全局缓存(模块级别,所有组件共享)
|
|
||||||
// 支持两种 key: providerId 或 providerId:apiKeyId
|
|
||||||
const cache = new Map<string, CacheEntry>()
|
|
||||||
const CACHE_TTL = 5 * 60 * 1000 // 5分钟
|
|
||||||
|
|
||||||
// 进行中的请求(用于去重并发请求)
|
// 进行中的请求(用于去重并发请求)
|
||||||
const pendingRequests = new Map<string, Promise<FetchResult>>()
|
const pendingRequests = new Map<string, Promise<FetchResult>>()
|
||||||
@@ -28,9 +19,9 @@ const pendingRequests = new Map<string, Promise<FetchResult>>()
|
|||||||
const loadingMap = ref<Map<string, boolean>>(new Map())
|
const loadingMap = ref<Map<string, boolean>>(new Map())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成缓存 key
|
* 生成请求 key
|
||||||
*/
|
*/
|
||||||
function getCacheKey(providerId: string, apiKeyId?: string): string {
|
function getRequestKey(providerId: string, apiKeyId?: string): string {
|
||||||
return apiKeyId ? `${providerId}:${apiKeyId}` : providerId
|
return apiKeyId ? `${providerId}:${apiKeyId}` : providerId
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,93 +30,59 @@ export function useUpstreamModelsCache() {
|
|||||||
* 获取上游模型列表
|
* 获取上游模型列表
|
||||||
* @param providerId 提供商ID
|
* @param providerId 提供商ID
|
||||||
* @param apiKeyId 可选的 API Key ID(用于获取特定 Key 支持的模型)
|
* @param apiKeyId 可选的 API Key ID(用于获取特定 Key 支持的模型)
|
||||||
* @param forceRefresh 是否强制刷新
|
* @param forceRefresh 是否强制刷新(跳过后端缓存)
|
||||||
* @returns 模型列表或 null(如果请求失败)
|
* @returns 模型列表或错误信息
|
||||||
*/
|
*/
|
||||||
async function fetchModels(
|
async function fetchModels(
|
||||||
providerId: string,
|
providerId: string,
|
||||||
apiKeyId?: string,
|
apiKeyId?: string,
|
||||||
forceRefresh = false
|
forceRefresh = false
|
||||||
): Promise<FetchResult> {
|
): Promise<FetchResult> {
|
||||||
const cacheKey = getCacheKey(providerId, apiKeyId)
|
const requestKey = getRequestKey(providerId, apiKeyId)
|
||||||
|
|
||||||
// 检查缓存
|
// 强制刷新时不复用进行中的请求
|
||||||
if (!forceRefresh) {
|
if (!forceRefresh && pendingRequests.has(requestKey)) {
|
||||||
const cached = cache.get(cacheKey)
|
return pendingRequests.get(requestKey)!
|
||||||
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
||||||
return { models: cached.models }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否有进行中的请求(非强制刷新时复用)
|
|
||||||
if (!forceRefresh && pendingRequests.has(cacheKey)) {
|
|
||||||
return pendingRequests.get(cacheKey)!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建新请求
|
// 创建新请求
|
||||||
const requestPromise = (async (): Promise<FetchResult> => {
|
const requestPromise = (async (): Promise<FetchResult> => {
|
||||||
try {
|
try {
|
||||||
loadingMap.value.set(cacheKey, true)
|
loadingMap.value.set(requestKey, true)
|
||||||
const response = await adminApi.queryProviderModels(providerId, apiKeyId)
|
const response = await adminApi.queryProviderModels(providerId, apiKeyId, forceRefresh)
|
||||||
|
|
||||||
if (response.success && response.data?.models) {
|
if (response.success && response.data?.models) {
|
||||||
// 存入缓存
|
return {
|
||||||
cache.set(cacheKey, {
|
|
||||||
models: response.data.models,
|
models: response.data.models,
|
||||||
timestamp: Date.now()
|
fromCache: response.data.from_cache
|
||||||
})
|
}
|
||||||
return { models: response.data.models }
|
|
||||||
} else {
|
} else {
|
||||||
// 使用友好的错误解析
|
|
||||||
const rawError = response.data?.error || '获取上游模型失败'
|
const rawError = response.data?.error || '获取上游模型失败'
|
||||||
return { models: [], error: parseUpstreamModelError(rawError) }
|
return { models: [], error: parseUpstreamModelError(rawError) }
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// 使用友好的错误解析
|
|
||||||
const rawError = err.response?.data?.detail || err.message || '获取上游模型失败'
|
const rawError = err.response?.data?.detail || err.message || '获取上游模型失败'
|
||||||
return { models: [], error: parseUpstreamModelError(rawError) }
|
return { models: [], error: parseUpstreamModelError(rawError) }
|
||||||
} finally {
|
} finally {
|
||||||
loadingMap.value.set(cacheKey, false)
|
loadingMap.value.set(requestKey, false)
|
||||||
pendingRequests.delete(cacheKey)
|
pendingRequests.delete(requestKey)
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|
||||||
pendingRequests.set(cacheKey, requestPromise)
|
pendingRequests.set(requestKey, requestPromise)
|
||||||
return requestPromise
|
return requestPromise
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取缓存的模型(不发起请求)
|
|
||||||
*/
|
|
||||||
function getCachedModels(providerId: string, apiKeyId?: string): UpstreamModel[] | null {
|
|
||||||
const cacheKey = getCacheKey(providerId, apiKeyId)
|
|
||||||
const cached = cache.get(cacheKey)
|
|
||||||
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
||||||
return cached.models
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除指定提供商/Key的缓存
|
|
||||||
*/
|
|
||||||
function clearCache(providerId: string, apiKeyId?: string) {
|
|
||||||
const cacheKey = getCacheKey(providerId, apiKeyId)
|
|
||||||
cache.delete(cacheKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查是否正在加载
|
* 检查是否正在加载
|
||||||
*/
|
*/
|
||||||
function isLoading(providerId: string, apiKeyId?: string): boolean {
|
function isLoading(providerId: string, apiKeyId?: string): boolean {
|
||||||
const cacheKey = getCacheKey(providerId, apiKeyId)
|
const requestKey = getRequestKey(providerId, apiKeyId)
|
||||||
return loadingMap.value.get(cacheKey) || false
|
return loadingMap.value.get(requestKey) || false
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fetchModels,
|
fetchModels,
|
||||||
getCachedModels,
|
|
||||||
clearCache,
|
|
||||||
isLoading,
|
isLoading,
|
||||||
loadingMap
|
loadingMap
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -256,16 +256,14 @@ class AdminUpdateEndpointKeyAdapter(AdminApiAdapter):
|
|||||||
|
|
||||||
# 处理 auto_fetch_models 的开启和关闭
|
# 处理 auto_fetch_models 的开启和关闭
|
||||||
if not auto_fetch_enabled_before and auto_fetch_enabled_after:
|
if not auto_fetch_enabled_before and auto_fetch_enabled_after:
|
||||||
# 刚刚开启了 auto_fetch_models,立即触发一次模型获取
|
# 刚刚开启了 auto_fetch_models,同步执行模型获取
|
||||||
logger.info("[AUTO_FETCH] Key %s 开启自动获取模型,立即触发模型获取", self.key_id)
|
logger.info("[AUTO_FETCH] Key %s 开启自动获取模型,同步执行模型获取", self.key_id)
|
||||||
try:
|
try:
|
||||||
from src.services.model.fetch_scheduler import get_model_fetch_scheduler
|
from src.services.model.fetch_scheduler import get_model_fetch_scheduler
|
||||||
|
|
||||||
scheduler = get_model_fetch_scheduler()
|
scheduler = get_model_fetch_scheduler()
|
||||||
# 在后台异步执行,不阻塞当前请求
|
# 同步等待模型获取完成,确保前端刷新时能看到最新数据
|
||||||
import asyncio
|
await scheduler._fetch_models_for_key_by_id(self.key_id)
|
||||||
|
|
||||||
asyncio.create_task(scheduler._fetch_models_for_key_by_id(self.key_id))
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"触发模型获取失败: {e}")
|
logger.error(f"触发模型获取失败: {e}")
|
||||||
# 不抛出异常,避免影响 Key 更新操作
|
# 不抛出异常,避免影响 Key 更新操作
|
||||||
@@ -630,17 +628,15 @@ class AdminCreateProviderKeyAdapter(AdminApiAdapter):
|
|||||||
f"Formats={self.key_data.api_formats}, Key=***{self.key_data.api_key[-4:]}, ID={new_key.id}"
|
f"Formats={self.key_data.api_formats}, Key=***{self.key_data.api_key[-4:]}, ID={new_key.id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 如果开启了 auto_fetch_models,立即触发一次模型获取
|
# 如果开启了 auto_fetch_models,同步执行模型获取
|
||||||
if self.key_data.auto_fetch_models:
|
if self.key_data.auto_fetch_models:
|
||||||
logger.info("[AUTO_FETCH] 新 Key %s 开启自动获取模型,立即触发模型获取", new_key.id)
|
logger.info("[AUTO_FETCH] 新 Key %s 开启自动获取模型,同步执行模型获取", new_key.id)
|
||||||
try:
|
try:
|
||||||
from src.services.model.fetch_scheduler import get_model_fetch_scheduler
|
from src.services.model.fetch_scheduler import get_model_fetch_scheduler
|
||||||
|
|
||||||
scheduler = get_model_fetch_scheduler()
|
scheduler = get_model_fetch_scheduler()
|
||||||
# 在后台异步执行,不阻塞当前请求
|
# 同步等待模型获取完成,确保前端刷新时能看到最新数据
|
||||||
import asyncio
|
await scheduler._fetch_models_for_key_by_id(new_key.id)
|
||||||
|
|
||||||
asyncio.create_task(scheduler._fetch_models_for_key_by_id(new_key.id))
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"触发模型获取失败: {e}")
|
logger.error(f"触发模型获取失败: {e}")
|
||||||
# 不抛出异常,避免影响 Key 创建操作
|
# 不抛出异常,避免影响 Key 创建操作
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class ModelsQueryRequest(BaseModel):
|
|||||||
|
|
||||||
provider_id: str
|
provider_id: str
|
||||||
api_key_id: Optional[str] = None
|
api_key_id: Optional[str] = None
|
||||||
|
force_refresh: bool = False # 强制刷新,跳过缓存
|
||||||
|
|
||||||
|
|
||||||
class TestModelRequest(BaseModel):
|
class TestModelRequest(BaseModel):
|
||||||
@@ -73,6 +74,8 @@ async def query_available_models(
|
|||||||
"""
|
"""
|
||||||
查询提供商可用模型
|
查询提供商可用模型
|
||||||
|
|
||||||
|
优先从缓存获取(缓存由定时任务刷新),缓存未命中时实时调用上游 API。
|
||||||
|
|
||||||
遍历所有活跃端点,根据端点的 API 格式选择正确的 Adapter 进行请求:
|
遍历所有活跃端点,根据端点的 API 格式选择正确的 Adapter 进行请求:
|
||||||
- OPENAI/OPENAI_CLI: 使用 OpenAIChatAdapter.fetch_models
|
- OPENAI/OPENAI_CLI: 使用 OpenAIChatAdapter.fetch_models
|
||||||
- CLAUDE/CLAUDE_CLI: 使用 ClaudeChatAdapter.fetch_models
|
- CLAUDE/CLAUDE_CLI: 使用 ClaudeChatAdapter.fetch_models
|
||||||
@@ -84,7 +87,12 @@ async def query_available_models(
|
|||||||
Returns:
|
Returns:
|
||||||
所有端点的模型列表(合并)
|
所有端点的模型列表(合并)
|
||||||
"""
|
"""
|
||||||
# 获取提供商及其端点和 API Keys
|
from src.services.model.fetch_scheduler import (
|
||||||
|
get_upstream_models_from_cache,
|
||||||
|
set_upstream_models_to_cache,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取提供商基本信息
|
||||||
provider = (
|
provider = (
|
||||||
db.query(Provider)
|
db.query(Provider)
|
||||||
.options(
|
.options(
|
||||||
@@ -98,6 +106,26 @@ async def query_available_models(
|
|||||||
if not provider:
|
if not provider:
|
||||||
raise HTTPException(status_code=404, detail="Provider not found")
|
raise HTTPException(status_code=404, detail="Provider not found")
|
||||||
|
|
||||||
|
# 如果指定了 api_key_id 且不是强制刷新,优先从缓存获取
|
||||||
|
# 注:不指定 api_key_id 时(Provider 级别查询)不使用缓存,因为:
|
||||||
|
# 1. Provider 级别查询会遍历多个 Key,结果不稳定
|
||||||
|
# 2. 缓存按 Key 粒度存储,与定时任务的刷新逻辑一致
|
||||||
|
if request.api_key_id and not request.force_refresh:
|
||||||
|
cached_models = await get_upstream_models_from_cache(
|
||||||
|
request.provider_id, request.api_key_id
|
||||||
|
)
|
||||||
|
if cached_models is not None:
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"data": {"models": cached_models, "error": None, "from_cache": True},
|
||||||
|
"provider": {
|
||||||
|
"id": provider.id,
|
||||||
|
"name": provider.name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# 缓存未命中或强制刷新,实时获取
|
||||||
|
|
||||||
# 收集所有活跃端点的配置
|
# 收集所有活跃端点的配置
|
||||||
endpoint_configs: list[dict] = []
|
endpoint_configs: list[dict] = []
|
||||||
|
|
||||||
@@ -235,9 +263,15 @@ async def query_available_models(
|
|||||||
if not unique_models and not error:
|
if not unique_models and not error:
|
||||||
error = "No models returned from any endpoint"
|
error = "No models returned from any endpoint"
|
||||||
|
|
||||||
|
# 如果指定了 api_key_id 且获取成功,写入缓存
|
||||||
|
if request.api_key_id and unique_models:
|
||||||
|
await set_upstream_models_to_cache(
|
||||||
|
request.provider_id, request.api_key_id, unique_models
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": len(unique_models) > 0,
|
"success": len(unique_models) > 0,
|
||||||
"data": {"models": unique_models, "error": error},
|
"data": {"models": unique_models, "error": error, "from_cache": False},
|
||||||
"provider": {
|
"provider": {
|
||||||
"id": provider.id,
|
"id": provider.id,
|
||||||
"name": provider.name,
|
"name": provider.name,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from typing import Optional
|
|||||||
import httpx
|
import httpx
|
||||||
from sqlalchemy.orm import Session, joinedload
|
from sqlalchemy.orm import Session, joinedload
|
||||||
|
|
||||||
|
from src.core.cache_service import CacheService
|
||||||
from src.core.crypto import crypto_service
|
from src.core.crypto import crypto_service
|
||||||
from src.core.headers import get_extra_headers_from_endpoint
|
from src.core.headers import get_extra_headers_from_endpoint
|
||||||
from src.core.logger import logger
|
from src.core.logger import logger
|
||||||
@@ -35,6 +36,35 @@ MAX_CONCURRENT_REQUESTS = 5
|
|||||||
# 单个 Key 处理的超时时间(秒)
|
# 单个 Key 处理的超时时间(秒)
|
||||||
KEY_FETCH_TIMEOUT_SECONDS = 120
|
KEY_FETCH_TIMEOUT_SECONDS = 120
|
||||||
|
|
||||||
|
# 上游模型缓存 TTL(与定时任务间隔保持一致)
|
||||||
|
UPSTREAM_MODELS_CACHE_TTL_SECONDS = MODEL_FETCH_INTERVAL_MINUTES * 60
|
||||||
|
|
||||||
|
|
||||||
|
def _get_upstream_models_cache_key(provider_id: str, api_key_id: str) -> str:
|
||||||
|
"""生成上游模型缓存的 key"""
|
||||||
|
return f"upstream_models:{provider_id}:{api_key_id}"
|
||||||
|
|
||||||
|
|
||||||
|
async def get_upstream_models_from_cache(
|
||||||
|
provider_id: str, api_key_id: str
|
||||||
|
) -> Optional[list[dict]]:
|
||||||
|
"""从缓存获取上游模型列表"""
|
||||||
|
cache_key = _get_upstream_models_cache_key(provider_id, api_key_id)
|
||||||
|
cached = await CacheService.get(cache_key)
|
||||||
|
if cached is not None:
|
||||||
|
logger.debug(f"上游模型缓存命中: {cache_key}")
|
||||||
|
return cached # type: ignore[no-any-return]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def set_upstream_models_to_cache(
|
||||||
|
provider_id: str, api_key_id: str, models: list[dict]
|
||||||
|
) -> None:
|
||||||
|
"""将上游模型列表写入缓存"""
|
||||||
|
cache_key = _get_upstream_models_cache_key(provider_id, api_key_id)
|
||||||
|
await CacheService.set(cache_key, models, UPSTREAM_MODELS_CACHE_TTL_SECONDS)
|
||||||
|
logger.debug(f"上游模型已缓存: {cache_key}, 数量={len(models)}")
|
||||||
|
|
||||||
|
|
||||||
def _get_adapter_for_format(api_format: str) -> Optional[type]:
|
def _get_adapter_for_format(api_format: str) -> Optional[type]:
|
||||||
"""根据 API 格式获取对应的 Adapter 类"""
|
"""根据 API 格式获取对应的 Adapter 类"""
|
||||||
@@ -307,6 +337,22 @@ class ModelFetchScheduler:
|
|||||||
f"Provider {provider.name} Key {key.id} 获取到 {len(fetched_model_ids)} 个唯一模型"
|
f"Provider {provider.name} Key {key.id} 获取到 {len(fetched_model_ids)} 个唯一模型"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 写入上游模型缓存(按 model id + api_format 去重后的完整模型信息)
|
||||||
|
seen_keys: set[str] = set()
|
||||||
|
unique_models: list[dict] = []
|
||||||
|
for model in all_models:
|
||||||
|
model_id = model.get("id")
|
||||||
|
api_format = model.get("api_format", "")
|
||||||
|
unique_key = f"{model_id}:{api_format}"
|
||||||
|
if model_id and unique_key not in seen_keys:
|
||||||
|
seen_keys.add(unique_key)
|
||||||
|
unique_models.append(model)
|
||||||
|
await set_upstream_models_to_cache(
|
||||||
|
provider_id, # type: ignore[arg-type]
|
||||||
|
key.id, # type: ignore[arg-type]
|
||||||
|
unique_models,
|
||||||
|
)
|
||||||
|
|
||||||
# 更新 allowed_models(保留 locked_models)
|
# 更新 allowed_models(保留 locked_models)
|
||||||
has_changed = self._update_key_allowed_models(key, fetched_model_ids)
|
has_changed = self._update_key_allowed_models(key, fetched_model_ids)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user