refactor!: 移除自定义模型支持
This commit is contained in:
parent
b866ddfec2
commit
a31f60b0a6
1
.gitignore
vendored
1
.gitignore
vendored
@ -49,6 +49,7 @@ saves_dev
|
||||
notebooks
|
||||
graphrag
|
||||
docker/volumes
|
||||
docs/vibe
|
||||
.cursorrules
|
||||
|
||||
/models
|
||||
|
||||
22
AGENTS.md
22
AGENTS.md
@ -3,25 +3,8 @@
|
||||
|
||||
Yuxi-Know 是一个基于知识图谱和向量数据库的智能知识库系统。它通过 FastAPI 提供后端 API,使用 Vue.js 构建前端界面,并利用 Docker Compose 进行整体服务的编排和管理。
|
||||
|
||||
文档中心在 `docs` 文件夹下面。
|
||||
|
||||
```
|
||||
Yuxi-Know/
|
||||
├── docker/ # Docker 配置文件
|
||||
├── scripts/ # 脚本文件,如批量上传等
|
||||
├── server/ # 服务端代码(部分)
|
||||
├── src/ # 主要源代码目录
|
||||
│ ├── agents/ # 智能体应用
|
||||
│ ├── config/ # 配置文件
|
||||
│ ├── knowledge/ # 知识库相关
|
||||
│ ├── models/ # 数据模型
|
||||
│ ├── plugins/ # 插件(存放OCR)
|
||||
│ ├── static/ # 静态资源(配置文件)
|
||||
│ └── utils/ # 工具函数
|
||||
├── web/src/ # 前端代码
|
||||
│ ├── apis/ # 前端的接口定义
|
||||
│ └── views/ # 主要 vue 文件
|
||||
└── docker-compose.yml # Docker Compose 配置
|
||||
```
|
||||
|
||||
# 核心服务与容器 (Core Services & Containers)
|
||||
|
||||
@ -54,5 +37,6 @@ Yuxi-Know/
|
||||
|
||||
其他:
|
||||
|
||||
- 如果需要新建说明文档,则保存在 docs/vibe 文件夹下面
|
||||
- 如果需要新建说明文档(仅开发者可见,非必要不创建),则保存在 `docs/vibe` 文件夹下面
|
||||
- 测试脚本可以放在 test 文件夹下面,可以从 docker 中启动测试
|
||||
- 代码更新后要检查文档部分是否有需要更新的地方,文档的目录定义在 `docs/.vitepress/config.mts` 中
|
||||
|
||||
@ -141,7 +141,7 @@ export HTTPS_PROXY=http://IP:PORT
|
||||
<details>
|
||||
<summary>自定义模型供应商</summary>
|
||||
|
||||
在 [src/config/static/models.yaml](src/config/static/models.yaml) 中添加新的模型配置,然后在 `.env` 文件中添加对应的环境变量,最后重新部署项目即可。
|
||||
在 [src/config/static/models.yaml](src/config/static/models.yaml) 中添加新的模型配置,然后在 `.env` 文件中添加对应的环境变量,最后重新部署项目即可。如需使用自定义配置文件,可设置环境变量 `OVERRIDE_DEFAULT_MODELS_CONFIG_WITH` 指向该文件。
|
||||
|
||||
**示例**:
|
||||
|
||||
@ -150,8 +150,7 @@ custom-provider-name-here:
|
||||
name: custom-provider-name-here
|
||||
default: custom-model-name-here
|
||||
base_url: "https://api.your-provider.com/v1"
|
||||
env:
|
||||
- CUSTOM_API_KEY_ENV_NAME # e.g. OPENAI_API_KEY
|
||||
env: CUSTOM_API_KEY_ENV_NAME # e.g. OPENAI_API_KEY
|
||||
models:
|
||||
- supported-model-name-here
|
||||
```
|
||||
@ -159,7 +158,7 @@ custom-provider-name-here:
|
||||
|
||||
### 自定义嵌入模型和重排序模型
|
||||
|
||||
需要注意的是,从 v0.2 版本开始,项目将模型部署和项目本身做了完全解耦,因此无论是 Embedding 还是 Reranker,如果需要使用本地模型,都需要使用 vllm /ollama 转换为 api 服务后,在 src/config/static/models.yaml 或者 src/config/static/models.private.yaml (优先)中 添加类似下面的信息:
|
||||
需要注意的是,从 v0.2 版本开始,项目将模型部署和项目本身做了完全解耦。因此无论是 Embedding 还是 Reranker,如果需要使用本地模型,都需要使用 vllm /ollama 转换为 API 服务后,在 [src/config/static/models.yaml](src/config/static/models.yaml)(或环境变量 `OVERRIDE_DEFAULT_MODELS_CONFIG_WITH` 指向的文件)中添加类似下面的信息:
|
||||
|
||||
```yaml
|
||||
EMBED_MODEL_INFO:
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
- [x] 添加统计信息
|
||||
- [ ] 支持 MinerU 2.5 的解析方法
|
||||
- [ ] Options 中添加网络搜索和绘制图片的选项,分别是用来调用工具
|
||||
- [ ] 移除自定义模型,同时将模型的 env 修改为单环境变量的形式,移除 models.yaml 的私有共有双配置模式,除非配置了环境变量 `OVERRIDE_DEFAULT_MODELS_CONFIG_WITH` 然后指向一个变量。
|
||||
- [x] 移除自定义模型,同时将模型的 env 修改为单环境变量的形式,移除 models.yaml 的私有共有双配置模式,除非配置了环境变量 `OVERRIDE_DEFAULT_MODELS_CONFIG_WITH` 然后指向一个变量。
|
||||
|
||||
📝 **Base**
|
||||
|
||||
|
||||
@ -42,13 +42,17 @@
|
||||
|
||||
### 1. 编辑模型配置文件
|
||||
|
||||
**方式一:修改公共配置**
|
||||
**方式一:修改默认配置**
|
||||
编辑 `src/config/static/models.yaml` 文件
|
||||
|
||||
**方式二:创建私有配置(推荐)**
|
||||
复制模板并创建私有配置:
|
||||
**方式二:使用覆盖配置**
|
||||
创建自定义配置文件并通过环境变量指定:
|
||||
```bash
|
||||
cp src/config/static/models.yaml src/config/static/models.private.yaml
|
||||
# 创建自定义配置文件
|
||||
cp src/config/static/models.yaml /path/to/your/custom-models.yaml
|
||||
|
||||
# 设置环境变量
|
||||
export OVERRIDE_DEFAULT_MODELS_CONFIG_WITH=/path/to/your/custom-models.yaml
|
||||
```
|
||||
|
||||
### 2. 添加模型配置
|
||||
@ -60,20 +64,17 @@ custom-provider-name:
|
||||
name: custom-provider-name
|
||||
default: custom-model-name
|
||||
base_url: "https://api.your-provider.com/v1"
|
||||
env:
|
||||
- CUSTOM_API_KEY_ENV_NAME
|
||||
env: CUSTOM_API_KEY_ENV_NAME # 注意:现在是单个环境变量
|
||||
models:
|
||||
- supported-model-name
|
||||
- another-model-name
|
||||
|
||||
|
||||
# 本地 Ollama 服务
|
||||
local-ollama:
|
||||
name: Local Ollama
|
||||
base_url: "http://localhost:11434/v1"
|
||||
default: llama3.2
|
||||
env:
|
||||
- NO_API_KEY
|
||||
env: NO_API_KEY # 对于不需要API Key的服务,使用NO_API_KEY
|
||||
models:
|
||||
- llama3.2
|
||||
- qwen2.5
|
||||
@ -83,8 +84,7 @@ local-vllm:
|
||||
name: Local vLLM
|
||||
base_url: "http://localhost:8000/v1"
|
||||
default: Qwen/Qwen2.5-7B-Instruct
|
||||
env:
|
||||
- NO_API_KEY
|
||||
env: NO_API_KEY
|
||||
models:
|
||||
- Qwen/Qwen2.5-7B-Instruct
|
||||
- Qwen/Qwen2.5-14B-Instruct
|
||||
@ -113,7 +113,7 @@ docker compose restart api-dev
|
||||
|
||||
#### 1. 配置模型信息
|
||||
|
||||
在 `src/config/static/models.yaml` 或 `src/config/static/models.private.yaml` 中添加配置:
|
||||
在 `src/config/static/models.yaml` 中或通过覆盖配置文件添加配置:
|
||||
|
||||
```yaml
|
||||
EMBED_MODEL_INFO:
|
||||
|
||||
@ -665,10 +665,7 @@ async def get_all_feedbacks(
|
||||
db.query(MessageFeedback, Message, Conversation, User)
|
||||
.join(Message, MessageFeedback.message_id == Message.id)
|
||||
.join(Conversation, Message.conversation_id == Conversation.id)
|
||||
.outerjoin(User,
|
||||
(MessageFeedback.user_id == User.id) |
|
||||
(MessageFeedback.user_id == User.user_id)
|
||||
)
|
||||
.outerjoin(User, (MessageFeedback.user_id == User.id) | (MessageFeedback.user_id == User.user_id))
|
||||
)
|
||||
|
||||
# Apply filters
|
||||
|
||||
@ -5,7 +5,6 @@ from langchain_core.language_models import BaseChatModel
|
||||
from pydantic import SecretStr
|
||||
|
||||
from src import config
|
||||
from src.models import get_custom_model
|
||||
from src.utils import get_docker_safe_url
|
||||
|
||||
|
||||
@ -15,22 +14,13 @@ def load_chat_model(fully_specified_name: str, **kwargs) -> BaseChatModel:
|
||||
"""
|
||||
provider, model = fully_specified_name.split("/", maxsplit=1)
|
||||
|
||||
if provider == "custom":
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
model_info = get_custom_model(model)
|
||||
api_key = model_info.get("api_key") or "custom_model"
|
||||
base_url = get_docker_safe_url(model_info["api_base"])
|
||||
model_name = model_info.get("name") or "custom_model"
|
||||
return ChatOpenAI(
|
||||
model=model_name,
|
||||
api_key=SecretStr(api_key),
|
||||
base_url=base_url,
|
||||
stream_usage=True,
|
||||
)
|
||||
assert provider != "custom", "[弃用] 自定义模型已移除,请在 src/config/static/models.yaml 中配置"
|
||||
|
||||
model_info = config.model_names.get(provider, {})
|
||||
api_key = os.getenv(model_info["env"][0], model_info["env"][0])
|
||||
env_var = model_info["env"]
|
||||
|
||||
api_key = os.getenv(env_var, env_var)
|
||||
|
||||
base_url = get_docker_safe_url(model_info["base_url"])
|
||||
|
||||
if provider in ["deepseek", "dashscope"]:
|
||||
|
||||
@ -6,8 +6,6 @@ import yaml
|
||||
|
||||
from src.utils.logging_config import logger
|
||||
|
||||
DEFAULT_MOCK_API = "this_is_mock_api_key_in_frontend"
|
||||
|
||||
|
||||
class SimpleConfig(dict):
|
||||
def __key(self, key):
|
||||
@ -44,6 +42,7 @@ class Config(SimpleConfig):
|
||||
self.filename = str(Path(f"{self.save_dir}/config/base.yaml"))
|
||||
os.makedirs(os.path.dirname(self.filename), exist_ok=True)
|
||||
|
||||
self._models_config_path: Path | None = None
|
||||
self._update_models_from_file()
|
||||
|
||||
### >>> 默认配置
|
||||
@ -100,40 +99,42 @@ class Config(SimpleConfig):
|
||||
|
||||
def _update_models_from_file(self):
|
||||
"""
|
||||
从 models.yaml 和 models.private.yaml 中更新 MODEL_NAMES
|
||||
从 models.yaml 或覆盖配置文件中更新 MODEL_NAMES
|
||||
"""
|
||||
# 检查是否设置了覆盖配置文件的环境变量
|
||||
override_config_path = os.getenv("OVERRIDE_DEFAULT_MODELS_CONFIG_WITH")
|
||||
|
||||
with open(Path("src/config/static/models.yaml"), encoding="utf-8") as f:
|
||||
if override_config_path and os.path.exists(override_config_path):
|
||||
config_file = Path(override_config_path)
|
||||
logger.info(f"Using override models config from: {override_config_path}")
|
||||
else:
|
||||
config_file = Path("src/config/static/models.yaml")
|
||||
logger.info("Using default models config")
|
||||
|
||||
self._models_config_path = config_file
|
||||
|
||||
with open(self._models_config_path, encoding="utf-8") as f:
|
||||
_models = yaml.safe_load(f)
|
||||
|
||||
# 尝试打开一个 models.private.yaml 文件,用来覆盖 models.yaml 中的配置
|
||||
# 兼容性测试(历史遗留问题)
|
||||
exist_yml = Path("src/config/static/models.private.yml").exists()
|
||||
exist_yaml = Path("src/config/static/models.private.yaml").exists()
|
||||
if exist_yml and not exist_yaml:
|
||||
os.rename("src/config/static/models.private.yml", "src/config/static/models.private.yaml")
|
||||
|
||||
try:
|
||||
with open(Path("src/config/static/models.private.yaml"), encoding="utf-8") as f:
|
||||
_models_private = yaml.safe_load(f)
|
||||
except FileNotFoundError:
|
||||
_models_private = {}
|
||||
|
||||
# 修改为按照子元素合并
|
||||
# _models = {**_models, **_models_private}
|
||||
|
||||
self.model_names = {**_models["MODEL_NAMES"], **_models_private.get("MODEL_NAMES", {})}
|
||||
self.embed_model_names = {**_models["EMBED_MODEL_INFO"], **_models_private.get("EMBED_MODEL_INFO", {})}
|
||||
self.reranker_names = {**_models["RERANKER_LIST"], **_models_private.get("RERANKER_LIST", {})}
|
||||
self.model_names = _models["MODEL_NAMES"]
|
||||
self.embed_model_names = _models["EMBED_MODEL_INFO"]
|
||||
self.reranker_names = _models["RERANKER_LIST"]
|
||||
|
||||
def _save_models_to_file(self):
|
||||
_models = {
|
||||
"""
|
||||
将当前模型配置写回模型配置文件
|
||||
"""
|
||||
if self._models_config_path is None:
|
||||
self._models_config_path = Path("src/config/static/models.yaml")
|
||||
|
||||
models_payload = {
|
||||
"MODEL_NAMES": self.model_names,
|
||||
"EMBED_MODEL_INFO": self.embed_model_names,
|
||||
"RERANKER_LIST": self.reranker_names,
|
||||
}
|
||||
with open(Path("src/config/static/models.private.yaml"), "w", encoding="utf-8") as f:
|
||||
yaml.dump(_models, f, indent=2, allow_unicode=True)
|
||||
|
||||
with open(self._models_config_path, "w", encoding="utf-8") as f:
|
||||
yaml.safe_dump(models_payload, f, indent=2, allow_unicode=True, sort_keys=False)
|
||||
|
||||
def handle_self(self):
|
||||
"""
|
||||
@ -156,19 +157,21 @@ class Config(SimpleConfig):
|
||||
)
|
||||
|
||||
# 检查模型提供商的环境变量
|
||||
conds = {}
|
||||
self.model_provider_status = {}
|
||||
for provider in self.model_names:
|
||||
conds[provider] = self.model_names[provider]["env"]
|
||||
conds_bool = [bool(os.getenv(_k)) for _k in conds[provider]]
|
||||
self.model_provider_status[provider] = all(conds_bool)
|
||||
env_var = self.model_names[provider]["env"]
|
||||
# 如果环境变量名为 NO_API_KEY,则认为总是可用
|
||||
if env_var == "NO_API_KEY":
|
||||
self.model_provider_status[provider] = True
|
||||
else:
|
||||
self.model_provider_status[provider] = bool(os.getenv(env_var))
|
||||
|
||||
if os.getenv("TAVILY_API_KEY"):
|
||||
self.enable_web_search = True
|
||||
|
||||
self.valuable_model_provider = [k for k, v in self.model_provider_status.items() if v]
|
||||
assert len(self.valuable_model_provider) > 0, (
|
||||
f"No model provider available, please check your `.env` file. API_KEY_LIST: {conds}"
|
||||
"No model provider available, please check your `.env` file."
|
||||
)
|
||||
|
||||
def load(self):
|
||||
@ -218,23 +221,6 @@ class Config(SimpleConfig):
|
||||
def dump_config(self):
|
||||
return json.loads(str(self))
|
||||
|
||||
def compare_custom_models(self, value):
|
||||
"""
|
||||
比较 custom_models 中的 api_key,如果输入的 api_key 与当前的 api_key 相同,则不修改
|
||||
如果输入的 api_key 为 DEFAULT_MOCK_API,则使用当前的 api_key
|
||||
"""
|
||||
current_models_dict = {model["custom_id"]: model.get("api_key") for model in self.get("custom_models", [])}
|
||||
|
||||
for i, model in enumerate(value):
|
||||
input_custom_id = model.get("custom_id")
|
||||
input_api_key = model.get("api_key")
|
||||
|
||||
if input_custom_id in current_models_dict:
|
||||
current_api_key = current_models_dict[input_custom_id]
|
||||
if input_api_key == DEFAULT_MOCK_API or input_api_key == current_api_key:
|
||||
value[i]["api_key"] = current_api_key
|
||||
|
||||
return value
|
||||
|
||||
|
||||
config = Config()
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
####################################################
|
||||
#
|
||||
# 不要直接修改这个里面的文件,可能会有被覆盖的风险,
|
||||
# 建议 复制一份 在 models.private.yaml 中修改,
|
||||
# 会自动加载
|
||||
# 默认模型配置文件。可以直接修改该文件更新模型。
|
||||
# 如需自定义路径,可通过环境变量
|
||||
# OVERRIDE_DEFAULT_MODELS_CONFIG_WITH 指向新的 YAML。
|
||||
#
|
||||
#####################################################
|
||||
|
||||
@ -12,8 +12,7 @@ MODEL_NAMES:
|
||||
url: https://platform.openai.com/docs/models
|
||||
base_url: https://api.openai.com/v1
|
||||
default: gpt-4o-mini
|
||||
env:
|
||||
- OPENAI_API_KEY
|
||||
env: OPENAI_API_KEY
|
||||
models:
|
||||
- gpt-4
|
||||
- gpt-4o
|
||||
@ -24,8 +23,7 @@ MODEL_NAMES:
|
||||
url: https://platform.deepseek.com/api-docs/zh-cn/pricing
|
||||
base_url: https://api.deepseek.com/v1
|
||||
default: deepseek-chat
|
||||
env:
|
||||
- DEEPSEEK_API_KEY
|
||||
env: DEEPSEEK_API_KEY
|
||||
models:
|
||||
- deepseek-chat
|
||||
- deepseek-reasoner
|
||||
@ -35,8 +33,7 @@ MODEL_NAMES:
|
||||
url: https://open.bigmodel.cn/dev/api
|
||||
base_url: https://open.bigmodel.cn/api/paas/v4/
|
||||
default: glm-4.5-flash
|
||||
env:
|
||||
- ZHIPUAI_API_KEY
|
||||
env: ZHIPUAI_API_KEY
|
||||
models:
|
||||
- glm-4.5
|
||||
- glm-4.5-air
|
||||
@ -47,8 +44,7 @@ MODEL_NAMES:
|
||||
url: https://cloud.siliconflow.cn/models
|
||||
base_url: https://api.siliconflow.cn/v1
|
||||
default: zai-org/GLM-4.5
|
||||
env:
|
||||
- SILICONFLOW_API_KEY
|
||||
env: SILICONFLOW_API_KEY
|
||||
models:
|
||||
- Qwen/Qwen3-235B-A22B-Thinking-2507
|
||||
- Qwen/Qwen3-235B-A22B-Instruct-2507
|
||||
@ -60,8 +56,7 @@ MODEL_NAMES:
|
||||
url: https://api.together.ai/models
|
||||
base_url: https://api.together.xyz/v1/
|
||||
default: meta-llama/Llama-3.3-70B-Instruct-Turbo-Free
|
||||
env:
|
||||
- TOGETHER_API_KEY
|
||||
env: TOGETHER_API_KEY
|
||||
models:
|
||||
- meta-llama/Llama-3.3-70B-Instruct-Turbo-Free
|
||||
|
||||
@ -70,8 +65,7 @@ MODEL_NAMES:
|
||||
url: https://bailian.console.aliyun.com/?switchAgent=10226727&productCode=p_efm#/model-market
|
||||
base_url: https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
default: qwen-max-latest
|
||||
env:
|
||||
- DASHSCOPE_API_KEY
|
||||
env: DASHSCOPE_API_KEY
|
||||
models:
|
||||
- qwen-max-latest
|
||||
- qwen-plus-latest
|
||||
@ -84,8 +78,7 @@ MODEL_NAMES:
|
||||
url: https://console.volcengine.com/ark/region:ark+cn-beijing/model
|
||||
base_url: https://ark.cn-beijing.volces.com/api/v3
|
||||
default: doubao-seed-1-6-250615
|
||||
env:
|
||||
- ARK_API_KEY
|
||||
env: ARK_API_KEY
|
||||
models:
|
||||
- doubao-seed-1-6-250615
|
||||
- doubao-seed-1-6-thinking-250715
|
||||
@ -96,8 +89,7 @@ MODEL_NAMES:
|
||||
url: https://openrouter.ai/models
|
||||
base_url: https://openrouter.ai/api/v1
|
||||
default: openai/gpt-4o
|
||||
env:
|
||||
- OPENROUTER_API_KEY
|
||||
env: OPENROUTER_API_KEY
|
||||
models:
|
||||
- openai/gpt-4o
|
||||
- x-ai/grok-4
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from src.models.chat import get_custom_model, select_model
|
||||
from src.models.chat import select_model
|
||||
from src.models.embed import select_embedding_model
|
||||
|
||||
__all__ = ["select_model", "select_embedding_model", "get_custom_model"]
|
||||
__all__ = ["select_model", "select_embedding_model"]
|
||||
|
||||
@ -79,14 +79,6 @@ class OpenModel(OpenAIBase):
|
||||
super().__init__(api_key=api_key, base_url=base_url, model_name=model_name)
|
||||
|
||||
|
||||
class CustomModel(OpenAIBase):
|
||||
def __init__(self, model_info):
|
||||
model_name = model_info["name"]
|
||||
api_key = model_info.get("api_key") or "custom_model"
|
||||
base_url = get_docker_safe_url(model_info["api_base"])
|
||||
logger.info(f"> Custom model: {model_name}, base_url: {base_url}")
|
||||
|
||||
super().__init__(api_key=api_key, base_url=base_url, model_name=model_name)
|
||||
|
||||
|
||||
class GeneralResponse:
|
||||
@ -106,14 +98,10 @@ def select_model(model_provider, model_name=None):
|
||||
if model_provider == "openai":
|
||||
return OpenModel(model_name)
|
||||
|
||||
if model_provider == "custom":
|
||||
model_info = get_custom_model(model_name)
|
||||
return CustomModel(model_info)
|
||||
|
||||
# 其他模型,默认使用OpenAIBase
|
||||
try:
|
||||
model = OpenAIBase(
|
||||
api_key=os.getenv(model_info["env"][0]),
|
||||
api_key=os.getenv(model_info["env"]),
|
||||
base_url=model_info["base_url"],
|
||||
model_name=model_name,
|
||||
)
|
||||
@ -122,13 +110,6 @@ def select_model(model_provider, model_name=None):
|
||||
raise ValueError(f"Model provider {model_provider} load failed, {e} \n {traceback.format_exc()}")
|
||||
|
||||
|
||||
def get_custom_model(model_id):
|
||||
"""return model_info"""
|
||||
assert config.custom_models is not None, "custom_models is not set"
|
||||
modle_info = next((x for x in config.custom_models if x["custom_id"] == model_id), None)
|
||||
if modle_info is None:
|
||||
raise ValueError(f"Model {model_id} not found in custom models")
|
||||
return modle_info
|
||||
|
||||
|
||||
async def test_chat_model_status(provider: str, model_name: str) -> dict:
|
||||
@ -178,18 +159,11 @@ async def test_all_chat_models_status() -> dict:
|
||||
|
||||
# 获取所有可用的模型
|
||||
for provider, provider_info in config.model_names.items():
|
||||
if provider == "custom":
|
||||
# 处理自定义模型
|
||||
for custom_model in config.custom_models:
|
||||
model_id = f"custom/{custom_model['custom_id']}"
|
||||
status = await test_chat_model_status("custom", custom_model["custom_id"])
|
||||
results[model_id] = status
|
||||
else:
|
||||
# 处理普通模型
|
||||
for model_name in provider_info.models:
|
||||
model_id = f"{provider}/{model_name}"
|
||||
status = await test_chat_model_status(provider, model_name)
|
||||
results[model_id] = status
|
||||
# 处理普通模型
|
||||
for model_name in provider_info.models:
|
||||
model_id = f"{provider}/{model_name}"
|
||||
status = await test_chat_model_status(provider, model_name)
|
||||
results[model_id] = status
|
||||
|
||||
available_count = len([m for m in results.values() if m["status"] == "available"])
|
||||
|
||||
|
||||
@ -1,38 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-alert message="自定义模型将在 0.3的稳定版中移除,届时只能通过修改 models.yaml 来添加模型和供应商。" type="warning" />
|
||||
<a-alert message="自定义模型在 0.3.x 的稳定版中移除,只能通过修改 models.yaml 来添加模型和供应商。" type="warning" />
|
||||
<br>
|
||||
<div class="model-provider-card custom-models-card">
|
||||
<div class="card-header">
|
||||
<h3>自定义模型</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="custom-model" v-for="(item, key) in configStore.config.custom_models" :key="item.custom_id">
|
||||
<div class="card-models__header">
|
||||
<div class="name" :title="item.name">{{ item.name }}</div>
|
||||
<div class="action">
|
||||
<a-popconfirm
|
||||
title="确认删除该模型?"
|
||||
@confirm="handleDeleteCustomModel(item.custom_id)"
|
||||
okText="确认删除"
|
||||
cancelText="取消"
|
||||
ok-type="danger"
|
||||
>
|
||||
<a-button type="text" @click.stop><DeleteOutlined /></a-button>
|
||||
</a-popconfirm>
|
||||
<a-button type="text" @click.stop="prepareToEditCustomModel(item)"><EditOutlined /></a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="api_base">{{ item.api_base }}</div>
|
||||
</div>
|
||||
<div class="card-models custom-model add-model" @click="prepareToAddCustomModel">
|
||||
<div class="card-models__header">
|
||||
<div class="name"> + 添加模型</div>
|
||||
</div>
|
||||
<div class="api_base">添加兼容 OpenAI 的模型</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-provider-card configured-provider" v-for="(item, key) in modelKeys" :key="key">
|
||||
<div class="card-header" @click="toggleExpand(item)">
|
||||
<div :class="{'model-icon': true, 'available': modelStatus[item]}">
|
||||
@ -87,42 +56,11 @@
|
||||
<SettingOutlined />
|
||||
</a-button>
|
||||
<div class="missing-keys">
|
||||
需配置<span v-for="(key, idx) in modelNames[item].env" :key="idx">{{ key }}</span>
|
||||
需配置<span>{{ modelNames[item].env }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加和编辑自定义模型的弹窗 -->
|
||||
<a-modal
|
||||
class="custom-model-modal"
|
||||
v-model:open="customModel.visible"
|
||||
:title="customModel.modelTitle"
|
||||
@ok="handleAddOrEditCustomModel"
|
||||
@cancel="handleCancelCustomModel"
|
||||
:okText="'确认'"
|
||||
:cancelText="'取消'"
|
||||
:okButtonProps="{disabled: !customModel.name || !customModel.api_base}"
|
||||
:ok-type="'primary'"
|
||||
>
|
||||
<p>添加的模型是兼容 OpenAI 的模型,比如 vllm,Ollama。</p>
|
||||
<a-form :model="customModel" layout="vertical">
|
||||
<a-form-item label="模型ID" v-if="customModel.edit_type == 'edit'" name="custom_id">
|
||||
<p class="form-item-description">调用的模型的ID</p>
|
||||
<a-input v-model:value="customModel.custom_id" disabled/>
|
||||
</a-form-item>
|
||||
<a-form-item label="模型名称" name="name" :rules="[{ required: true, message: '请输入模型名称' }]">
|
||||
<p class="form-item-description">调用的模型的名称</p>
|
||||
<a-input v-model:value="customModel.name" :disabled="customModel.edit_type == 'edit'"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="API Base" name="api_base" :rules="[{ required: true, message: '请输入API Base' }]">
|
||||
<p class="form-item-description">比如 <code>http://localhost:11434/v1</code></p>
|
||||
<a-input v-model:value="customModel.api_base" />
|
||||
</a-form-item>
|
||||
<a-form-item label="API KEY" name="api_key">
|
||||
<a-input-password v-model:value="customModel.api_key" :visibilityToggle="true" autocomplete="new-password"/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 模型提供商配置弹窗 -->
|
||||
<a-modal
|
||||
@ -180,7 +118,7 @@
|
||||
<div v-if="providerConfig.allModels.length === 0" class="modal-no-models">
|
||||
<a-alert v-if="!modelStatus[providerConfig.provider]" type="warning" message="请在 src/.env 中配置对应的 APIKEY,并重新启动服务" />
|
||||
<div v-else>
|
||||
<a-alert type="warning" message="该提供商暂未适配获取模型列表的方法,如果需要添加模型,请在 src/config/static/models.private.yaml 中添加。(如没有此文件,则需要手动创建一个)" />
|
||||
<a-alert type="warning" message="该提供商暂未适配获取模型列表的方法,如需添加模型,请编辑 src/config/static/models.yaml 或通过环境变量 OVERRIDE_DEFAULT_MODELS_CONFIG_WITH 指向的文件。" />
|
||||
<img src="@/assets/pics/guides/how-to-add-models.png" alt="添加模型指引" style="width: 100%; height: 100%; margin-top: 16px;">
|
||||
</div>
|
||||
</div>
|
||||
@ -194,8 +132,6 @@
|
||||
import { computed, reactive, watch, h } from 'vue'
|
||||
import { message } from 'ant-design-vue';
|
||||
import {
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
InfoCircleOutlined,
|
||||
SettingOutlined,
|
||||
DownCircleOutlined,
|
||||
@ -212,16 +148,6 @@ const configStore = useConfigStore();
|
||||
const modelNames = computed(() => configStore.config?.model_names);
|
||||
const modelStatus = computed(() => configStore.config?.model_provider_status);
|
||||
|
||||
// 自定义模型相关状态
|
||||
const customModel = reactive({
|
||||
modelTitle: '添加自定义模型',
|
||||
visible: false,
|
||||
custom_id: '',
|
||||
name: '',
|
||||
api_key: '',
|
||||
api_base: '',
|
||||
edit_type: 'add',
|
||||
});
|
||||
|
||||
// 提供商配置相关状态
|
||||
const providerConfig = reactive({
|
||||
@ -257,86 +183,6 @@ watch(modelKeys, (newKeys) => {
|
||||
});
|
||||
}, { immediate: true });
|
||||
|
||||
// 生成随机哈希值
|
||||
const generateRandomHash = (length) => {
|
||||
let chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
let hash = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
hash += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return hash;
|
||||
};
|
||||
|
||||
// 处理自定义模型删除
|
||||
const handleDeleteCustomModel = (customId) => {
|
||||
const updatedModels = configStore.config.custom_models.filter(item => item.custom_id !== customId);
|
||||
configStore.setConfigValue('custom_models', updatedModels);
|
||||
};
|
||||
|
||||
// 准备编辑自定义模型
|
||||
const prepareToEditCustomModel = (item) => {
|
||||
customModel.modelTitle = '编辑自定义模型';
|
||||
customModel.custom_id = item.custom_id;
|
||||
customModel.visible = true;
|
||||
customModel.edit_type = 'edit';
|
||||
customModel.name = item.name;
|
||||
customModel.api_key = item.api_key;
|
||||
customModel.api_base = item.api_base;
|
||||
};
|
||||
|
||||
// 准备添加自定义模型
|
||||
const prepareToAddCustomModel = () => {
|
||||
customModel.modelTitle = '添加自定义模型';
|
||||
customModel.edit_type = 'add';
|
||||
customModel.visible = true;
|
||||
clearCustomModel();
|
||||
};
|
||||
|
||||
// 清除自定义模型表单
|
||||
const clearCustomModel = () => {
|
||||
customModel.custom_id = '';
|
||||
customModel.name = '';
|
||||
customModel.api_key = '';
|
||||
customModel.api_base = '';
|
||||
};
|
||||
|
||||
// 取消自定义模型添加/编辑
|
||||
const handleCancelCustomModel = () => {
|
||||
clearCustomModel();
|
||||
customModel.visible = false;
|
||||
};
|
||||
|
||||
// 添加或编辑自定义模型
|
||||
const handleAddOrEditCustomModel = async () => {
|
||||
if (!customModel.name || !customModel.api_base) {
|
||||
message.error('请填写完整的模型名称和API Base信息。');
|
||||
return;
|
||||
}
|
||||
|
||||
let custom_models = configStore.config.custom_models || [];
|
||||
|
||||
const model_info = {
|
||||
custom_id: customModel.custom_id || `${customModel.name}-${generateRandomHash(4)}`,
|
||||
name: customModel.name,
|
||||
api_key: customModel.api_key,
|
||||
api_base: customModel.api_base,
|
||||
};
|
||||
|
||||
if (customModel.edit_type === 'add') {
|
||||
if (custom_models.find(item => item.custom_id === customModel.custom_id)) {
|
||||
message.error('模型ID已存在');
|
||||
return;
|
||||
}
|
||||
custom_models.push(model_info);
|
||||
} else {
|
||||
// 如果 custom_id 相同,则更新
|
||||
custom_models = custom_models.map(item => item.custom_id === customModel.custom_id ? model_info : item);
|
||||
}
|
||||
|
||||
customModel.visible = false;
|
||||
await configStore.setConfigValue('custom_models', custom_models);
|
||||
message.success(customModel.edit_type === 'add' ? '模型添加成功' : '模型修改成功');
|
||||
};
|
||||
|
||||
// 切换展开状态
|
||||
const toggleExpand = (item) => {
|
||||
|
||||
@ -37,11 +37,6 @@
|
||||
{{ model }}
|
||||
</a-menu-item>
|
||||
</a-menu-item-group>
|
||||
<a-menu-item-group v-if="customModels.length > 0" title="自定义模型">
|
||||
<a-menu-item v-for="(model, idx) in customModels" :key="`custom-${idx}`" @click="handleSelectModel('custom', model.custom_id)">
|
||||
custom/{{ model.custom_id }}
|
||||
</a-menu-item>
|
||||
</a-menu-item-group>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
@ -75,7 +70,6 @@ const state = reactive({
|
||||
// 从configStore中获取所需数据
|
||||
const modelNames = computed(() => configStore.config?.model_names)
|
||||
const modelStatus = computed(() => configStore.config?.model_provider_status)
|
||||
const customModels = computed(() => configStore.config?.custom_models || [])
|
||||
|
||||
// 筛选 modelStatus 中为真的key
|
||||
const modelKeys = computed(() => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user