feat(model): 引入 is_v2_spec_format 函数以优化模型规格判断逻辑
This commit is contained in:
parent
2e0b4a8358
commit
a8eb5ab6fe
@ -5,6 +5,7 @@ from langchain.chat_models import BaseChatModel, init_chat_model
|
|||||||
from pydantic import SecretStr
|
from pydantic import SecretStr
|
||||||
|
|
||||||
from yuxi import config
|
from yuxi import config
|
||||||
|
from yuxi.services.model_cache import is_v2_spec_format
|
||||||
from yuxi.utils import get_docker_safe_url
|
from yuxi.utils import get_docker_safe_url
|
||||||
from yuxi.utils.logging_config import logger
|
from yuxi.utils.logging_config import logger
|
||||||
|
|
||||||
@ -64,8 +65,8 @@ def load_chat_model(fully_specified_name: str, **kwargs) -> BaseChatModel:
|
|||||||
"""
|
"""
|
||||||
Load a chat model from a fully specified name.
|
Load a chat model from a fully specified name.
|
||||||
"""
|
"""
|
||||||
# v2 判断:如果 spec 包含冒号,尝试走 v2 路径
|
# v2 判断:第一个特殊字符为冒号则走 v2 路径
|
||||||
if ":" in fully_specified_name:
|
if is_v2_spec_format(fully_specified_name):
|
||||||
from yuxi.services.model_cache import model_cache
|
from yuxi.services.model_cache import model_cache
|
||||||
|
|
||||||
info = model_cache.get_model_info(fully_specified_name)
|
info = model_cache.get_model_info(fully_specified_name)
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from openai import AsyncOpenAI
|
|||||||
from tenacity import before_sleep_log, retry, retry_if_exception_type, stop_after_attempt, wait_exponential
|
from tenacity import before_sleep_log, retry, retry_if_exception_type, stop_after_attempt, wait_exponential
|
||||||
|
|
||||||
from yuxi import config
|
from yuxi import config
|
||||||
|
from yuxi.services.model_cache import is_v2_spec_format
|
||||||
from yuxi.utils import logger
|
from yuxi.utils import logger
|
||||||
|
|
||||||
|
|
||||||
@ -126,13 +127,16 @@ def select_model_v2(spec: str) -> OpenAIBase:
|
|||||||
|
|
||||||
def select_model(model_provider=None, model_name=None, model_spec=None):
|
def select_model(model_provider=None, model_name=None, model_spec=None):
|
||||||
"""根据模型提供者选择模型"""
|
"""根据模型提供者选择模型"""
|
||||||
# v2 判断:如果 spec 包含冒号且在缓存中存在,走 v2 路径
|
if model_spec and is_v2_spec_format(model_spec):
|
||||||
if model_spec and ":" in model_spec:
|
|
||||||
from yuxi.services.model_cache import model_cache
|
from yuxi.services.model_cache import model_cache
|
||||||
|
|
||||||
if model_cache.is_v2_spec(model_spec):
|
if model_cache.is_v2_spec(model_spec):
|
||||||
return select_model_v2(model_spec)
|
return select_model_v2(model_spec)
|
||||||
|
|
||||||
|
available = model_cache.get_all_specs("chat")
|
||||||
|
available_ids = [s.spec for s in available[:10]]
|
||||||
|
raise ValueError(f"未找到 V2 模型: '{model_spec}'。可用聊天模型 ({len(available)}): {available_ids}")
|
||||||
|
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"旧版本的模型选择逻辑已废弃,建议尽快迁移至新的模型配置;"
|
f"旧版本的模型选择逻辑已废弃,建议尽快迁移至新的模型配置;"
|
||||||
f"当前模型选择参数: provider={model_provider}, model_name={model_name}, spec={model_spec}"
|
f"当前模型选择参数: provider={model_provider}, model_name={model_name}, spec={model_spec}"
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import httpx
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from yuxi import config
|
from yuxi import config
|
||||||
|
from yuxi.services.model_cache import is_v2_spec_format
|
||||||
from yuxi.utils import get_docker_safe_url, hashstr, logger
|
from yuxi.utils import get_docker_safe_url, hashstr, logger
|
||||||
|
|
||||||
|
|
||||||
@ -288,7 +289,7 @@ def get_embedding_model_info_by_id(model_id: str) -> dict:
|
|||||||
api_key 已从环境变量解析为实际值。
|
api_key 已从环境变量解析为实际值。
|
||||||
"""
|
"""
|
||||||
# V2 spec 检测
|
# V2 spec 检测
|
||||||
if isinstance(model_id, str) and ":" in model_id:
|
if isinstance(model_id, str) and is_v2_spec_format(model_id):
|
||||||
from yuxi.services.model_cache import model_cache
|
from yuxi.services.model_cache import model_cache
|
||||||
|
|
||||||
info = model_cache.get_model_info(model_id)
|
info = model_cache.get_model_info(model_id)
|
||||||
@ -321,8 +322,8 @@ def select_embedding_model(model_id):
|
|||||||
V1 格式: provider/model_name(斜杠分隔)
|
V1 格式: provider/model_name(斜杠分隔)
|
||||||
V2 格式: provider_id:model_id(冒号分隔)
|
V2 格式: provider_id:model_id(冒号分隔)
|
||||||
"""
|
"""
|
||||||
# V2 spec 检测:包含 ":" 且存在于缓存中
|
# V2 spec 检测:第一个特殊字符为冒号且存在于缓存中
|
||||||
if isinstance(model_id, str) and ":" in model_id:
|
if isinstance(model_id, str) and is_v2_spec_format(model_id):
|
||||||
from yuxi.services.model_cache import model_cache
|
from yuxi.services.model_cache import model_cache
|
||||||
|
|
||||||
if model_cache.is_v2_spec(model_id):
|
if model_cache.is_v2_spec(model_id):
|
||||||
@ -385,7 +386,7 @@ async def test_embedding_model_status_by_spec(spec: str) -> dict:
|
|||||||
V2 spec 格式: provider_id:model_id(冒号分隔)
|
V2 spec 格式: provider_id:model_id(冒号分隔)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if ":" in spec:
|
if is_v2_spec_format(spec):
|
||||||
from yuxi.services.model_cache import model_cache
|
from yuxi.services.model_cache import model_cache
|
||||||
|
|
||||||
if model_cache.is_v2_spec(spec):
|
if model_cache.is_v2_spec(spec):
|
||||||
|
|||||||
@ -22,6 +22,22 @@ _DEFAULT_REDIS_URL = "redis://redis:6379/0"
|
|||||||
_CACHE_TTL_SECONDS = 5
|
_CACHE_TTL_SECONDS = 5
|
||||||
|
|
||||||
|
|
||||||
|
def is_v2_spec_format(spec: str) -> bool:
|
||||||
|
"""判断 spec 是否为 V2 格式(基于格式特征而非缓存查找)。
|
||||||
|
|
||||||
|
V2 格式的特征是第一个特殊字符为冒号(provider_id:model_id),
|
||||||
|
V1 格式的特征是第一个特殊字符为斜杠(provider/model_name)。
|
||||||
|
Provider ID 中不包含斜杠,因此第一个特殊字符决定了格式类型。
|
||||||
|
"""
|
||||||
|
colon_pos = spec.find(":")
|
||||||
|
slash_pos = spec.find("/")
|
||||||
|
if colon_pos == -1:
|
||||||
|
return False
|
||||||
|
if slash_pos == -1:
|
||||||
|
return True
|
||||||
|
return colon_pos < slash_pos
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class ModelInfo:
|
class ModelInfo:
|
||||||
"""不可变的模型信息,供运行时使用。"""
|
"""不可变的模型信息,供运行时使用。"""
|
||||||
@ -245,8 +261,8 @@ def resolve_model_spec(spec: str) -> ModelInfo:
|
|||||||
if not spec:
|
if not spec:
|
||||||
raise ValueError("spec 不能为空")
|
raise ValueError("spec 不能为空")
|
||||||
|
|
||||||
# V2: 优先查找缓存
|
# V2: 第一个特殊字符为冒号则走 v2 路径
|
||||||
if ":" in spec:
|
if is_v2_spec_format(spec):
|
||||||
info = model_cache.get_model_info(spec)
|
info = model_cache.get_model_info(spec)
|
||||||
if info:
|
if info:
|
||||||
return info
|
return info
|
||||||
|
|||||||
@ -15,6 +15,7 @@ from yuxi.repositories.model_provider_repository import (
|
|||||||
update_model_provider,
|
update_model_provider,
|
||||||
)
|
)
|
||||||
from yuxi.config.builtin_providers import BUILTIN_PROVIDERS
|
from yuxi.config.builtin_providers import BUILTIN_PROVIDERS
|
||||||
|
from yuxi.services.model_cache import is_v2_spec_format
|
||||||
from yuxi.storage.postgres.models_business import ModelProvider
|
from yuxi.storage.postgres.models_business import ModelProvider
|
||||||
|
|
||||||
VALID_MODEL_TYPES = {"chat", "embedding", "rerank"}
|
VALID_MODEL_TYPES = {"chat", "embedding", "rerank"}
|
||||||
@ -375,7 +376,7 @@ async def test_model_status_by_spec(spec: str) -> dict:
|
|||||||
from yuxi.services.model_cache import model_cache
|
from yuxi.services.model_cache import model_cache
|
||||||
|
|
||||||
# V2: 从缓存识别模型类型并分派
|
# V2: 从缓存识别模型类型并分派
|
||||||
if ":" in spec:
|
if is_v2_spec_format(spec):
|
||||||
info = model_cache.get_model_info(spec)
|
info = model_cache.get_model_info(spec)
|
||||||
if info:
|
if info:
|
||||||
if info.model_type == "embedding":
|
if info.model_type == "embedding":
|
||||||
|
|||||||
@ -18,6 +18,7 @@ from yuxi.plugins.parser import Parser, SUPPORTED_FILE_EXTENSIONS, is_supported_
|
|||||||
from yuxi.knowledge.utils import calculate_content_hash
|
from yuxi.knowledge.utils import calculate_content_hash
|
||||||
from yuxi.knowledge.utils.kb_utils import parse_minio_url
|
from yuxi.knowledge.utils.kb_utils import parse_minio_url
|
||||||
from yuxi.models.embed import test_all_embedding_models_status, test_embedding_model_status
|
from yuxi.models.embed import test_all_embedding_models_status, test_embedding_model_status
|
||||||
|
from yuxi.services.model_cache import is_v2_spec_format
|
||||||
from yuxi.storage.postgres.models_business import User
|
from yuxi.storage.postgres.models_business import User
|
||||||
from yuxi.storage.minio.client import MinIOClient, StorageError, aupload_file_to_minio, get_minio_client
|
from yuxi.storage.minio.client import MinIOClient, StorageError, aupload_file_to_minio, get_minio_client
|
||||||
from yuxi.utils import logger
|
from yuxi.utils import logger
|
||||||
@ -149,8 +150,8 @@ async def create_database(
|
|||||||
if not embed_model_name:
|
if not embed_model_name:
|
||||||
raise HTTPException(status_code=400, detail="embed_model_name 不能为空")
|
raise HTTPException(status_code=400, detail="embed_model_name 不能为空")
|
||||||
|
|
||||||
# V2 embedding model (spec 格式: provider_id:model_id,使用冒号分隔)
|
# V2 embedding model (spec 格式: provider_id:model_id,第一个特殊字符为冒号)
|
||||||
if ":" in embed_model_name:
|
if is_v2_spec_format(embed_model_name):
|
||||||
from yuxi.services.model_cache import model_cache
|
from yuxi.services.model_cache import model_cache
|
||||||
|
|
||||||
info = model_cache.get_model_info(embed_model_name)
|
info = model_cache.get_model_info(embed_model_name)
|
||||||
@ -443,7 +444,9 @@ async def add_documents(
|
|||||||
db_id, file_id, indexing_params, operator_id=current_user.user_id
|
db_id, file_id, indexing_params, operator_id=current_user.user_id
|
||||||
)
|
)
|
||||||
# 2. 执行入库(传入 indexing_params 确保使用的参数与用户设置一致)
|
# 2. 执行入库(传入 indexing_params 确保使用的参数与用户设置一致)
|
||||||
result = await knowledge_base.index_file(db_id, file_id, operator_id=current_user.user_id, params=indexing_params)
|
result = await knowledge_base.index_file(
|
||||||
|
db_id, file_id, operator_id=current_user.user_id, params=indexing_params
|
||||||
|
)
|
||||||
processed_items.append(result)
|
processed_items.append(result)
|
||||||
except Exception as index_error:
|
except Exception as index_error:
|
||||||
logger.error(f"自动入库失败 {item} (file_id={file_id}): {index_error}")
|
logger.error(f"自动入库失败 {item} (file_id={file_id}): {index_error}")
|
||||||
|
|||||||
@ -1063,7 +1063,7 @@ const confirmDeleteConfig = async () => {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
&.open {
|
&.open {
|
||||||
width: 400px;
|
width: 360px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-header {
|
.sidebar-header {
|
||||||
@ -1074,7 +1074,7 @@ const confirmDeleteConfig = async () => {
|
|||||||
border-bottom: 1px solid var(--gray-150);
|
border-bottom: 1px solid var(--gray-150);
|
||||||
background: var(--gray-0);
|
background: var(--gray-0);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
min-width: 400px;
|
min-width: 360px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|
||||||
.header-top-row {
|
.header-top-row {
|
||||||
@ -1150,7 +1150,7 @@ const confirmDeleteConfig = async () => {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 10px 12px 8px;
|
padding: 10px 12px 8px;
|
||||||
min-width: 400px;
|
min-width: 360px;
|
||||||
|
|
||||||
.agent-info {
|
.agent-info {
|
||||||
.agent-basic-info {
|
.agent-basic-info {
|
||||||
|
|||||||
@ -293,7 +293,7 @@ const renameChat = async (chatId) => {
|
|||||||
.conversation-actions {
|
.conversation-actions {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
right: 8px;
|
right: 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
BarChart3,
|
BarChart3,
|
||||||
ClipboardList,
|
ClipboardList,
|
||||||
Blocks,
|
Blocks,
|
||||||
Cpu,
|
Box,
|
||||||
PanelLeftClose,
|
PanelLeftClose,
|
||||||
PanelLeftOpen,
|
PanelLeftOpen,
|
||||||
MessageCirclePlus
|
MessageCirclePlus
|
||||||
@ -154,8 +154,8 @@ const mainList = computed(() => {
|
|||||||
items.push({
|
items.push({
|
||||||
name: '模型配置',
|
name: '模型配置',
|
||||||
path: '/model-config',
|
path: '/model-config',
|
||||||
icon: Cpu,
|
icon: Box,
|
||||||
activeIcon: Cpu
|
activeIcon: Box
|
||||||
})
|
})
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
|
|||||||
@ -661,14 +661,6 @@ onMounted(loadProviders)
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Empty State -->
|
|
||||||
<a-empty v-if="!filteredProviders.length" :image="false" description="暂无供应商">
|
|
||||||
<a-button type="primary" class="lucide-icon-btn" @click="openCreateProviderModal">
|
|
||||||
<Plus :size="14" />
|
|
||||||
新增供应商
|
|
||||||
</a-button>
|
|
||||||
</a-empty>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Provider Edit Modal -->
|
<!-- Provider Edit Modal -->
|
||||||
@ -1182,7 +1174,7 @@ onMounted(loadProviders)
|
|||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: var(--main-50);
|
background: #e1f6fb; // 固定使用浅色背景,确保图标清晰可见
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user