diff --git a/apps/models_provider/constants/model_provider_constants.py b/apps/models_provider/constants/model_provider_constants.py index 4533e0fca9b..3fc330e3e82 100644 --- a/apps/models_provider/constants/model_provider_constants.py +++ b/apps/models_provider/constants/model_provider_constants.py @@ -13,6 +13,7 @@ from models_provider.impl.local_model_provider.local_model_provider import LocalModelProvider from models_provider.impl.ollama_model_provider.ollama_model_provider import OllamaModelProvider from models_provider.impl.openai_model_provider.openai_model_provider import OpenAIModelProvider +from models_provider.impl.orcarouter_model_provider.orcarouter_model_provider import OrcaRouterModelProvider from models_provider.impl.regolo_model_provider.regolo_model_provider import RegoloModelProvider from models_provider.impl.siliconCloud_model_provider.siliconCloud_model_provider import SiliconCloudModelProvider from models_provider.impl.tencent_cloud_model_provider.tencent_cloud_model_provider import TencentCloudModelProvider @@ -32,6 +33,7 @@ class ModelProvideConstants(Enum): model_wenxin_provider = WenxinModelProvider() model_ollama_provider = OllamaModelProvider() model_openai_provider = OpenAIModelProvider() + model_orcarouter_provider = OrcaRouterModelProvider() model_docker_ai_provider = DockerModelProvider() model_kimi_provider = KimiModelProvider() model_zhipu_provider = ZhiPuModelProvider() diff --git a/apps/models_provider/impl/orcarouter_model_provider/__init__.py b/apps/models_provider/impl/orcarouter_model_provider/__init__.py new file mode 100644 index 00000000000..af6a58cd37e --- /dev/null +++ b/apps/models_provider/impl/orcarouter_model_provider/__init__.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +""" +@Project :MaxKB +@File :__init__.py.py +@Date :2026-08-15 +""" diff --git a/apps/models_provider/impl/orcarouter_model_provider/credential/__init__.py b/apps/models_provider/impl/orcarouter_model_provider/credential/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/models_provider/impl/orcarouter_model_provider/credential/llm.py b/apps/models_provider/impl/orcarouter_model_provider/credential/llm.py new file mode 100644 index 00000000000..3d35cbd4b84 --- /dev/null +++ b/apps/models_provider/impl/orcarouter_model_provider/credential/llm.py @@ -0,0 +1,79 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎 + @file: llm.py + @date:2024/7/11 17:51 + @desc: +""" +from typing import Dict + +from django.utils.translation import gettext_lazy as _, gettext +from langchain_core.messages import HumanMessage + +from common import forms +from common.exception.app_exception import AppApiException +from common.forms import BaseForm, TooltipLabel +from models_provider.base_model_provider import BaseModelCredential, ValidCode +from common.utils.logger import maxkb_logger + + +class OrcaRouterLLMModelParams(BaseForm): + temperature = forms.SliderField(TooltipLabel(_('Temperature'), + _('Higher values make the output more random, while lower values make it more focused and deterministic')), + required=True, default_value=0.7, + _min=0.1, + _max=1.0, + _step=0.01, + precision=2) + + max_tokens = forms.SliderField( + TooltipLabel(_('Output the maximum Tokens'), + _('Specify the maximum number of tokens that the model can generate')), + required=True, default_value=8192, + _min=1, + _max=100000, + _step=1, + precision=0) + + +class OrcaRouterLLMModelCredential(BaseForm, BaseModelCredential): + + def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, + raise_exception=False): + model_type_list = provider.get_model_type_list() + if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): + raise AppApiException(ValidCode.valid_error.value, + gettext('{model_type} Model type is not supported').format(model_type=model_type)) + + for key in ['api_key', 'api_base']: + if key not in model_credential: + if raise_exception: + raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) + else: + return False + try: + model = provider.get_model(model_type, model_name, model_credential, **model_params) + model.invoke([HumanMessage(content=gettext('Hello'))]) + except Exception as e: + maxkb_logger.error(f'Exception: {e}', exc_info=True) + if isinstance(e, AppApiException): + raise e + if raise_exception: + raise AppApiException(ValidCode.valid_error.value, + gettext( + 'Verification failed, please check whether the parameters are correct: {error}').format( + error=str(e))) + else: + return False + return True + + def encryption_dict(self, model: Dict[str, object]): + return {**model, 'api_key': super().encryption(model.get('api_key', ''))} + + api_base = forms.TextInputField(_('API URL'), required=True, + default_value='https://api.orcarouter.ai/v1') + api_key = forms.PasswordInputField('API Key', required=True) + + def get_model_params_setting_form(self, model_name): + return OrcaRouterLLMModelParams() diff --git a/apps/models_provider/impl/orcarouter_model_provider/icon/__init__.py b/apps/models_provider/impl/orcarouter_model_provider/icon/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/models_provider/impl/orcarouter_model_provider/icon/orcarouter_icon_svg b/apps/models_provider/impl/orcarouter_model_provider/icon/orcarouter_icon_svg new file mode 100644 index 00000000000..7adf12f4923 --- /dev/null +++ b/apps/models_provider/impl/orcarouter_model_provider/icon/orcarouter_icon_svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/apps/models_provider/impl/orcarouter_model_provider/model/__init__.py b/apps/models_provider/impl/orcarouter_model_provider/model/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/models_provider/impl/orcarouter_model_provider/model/llm.py b/apps/models_provider/impl/orcarouter_model_provider/model/llm.py new file mode 100644 index 00000000000..a4f4198a035 --- /dev/null +++ b/apps/models_provider/impl/orcarouter_model_provider/model/llm.py @@ -0,0 +1,38 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎 + @file: llm.py + @date:2024/4/18 15:28 + @desc: +""" +from typing import Dict + +from langchain_core.messages import BaseMessage, get_buffer_string +from langchain_openai.chat_models import ChatOpenAI + +from common.config.tokenizer_manage_config import TokenizerManage +from models_provider.base_model_provider import MaxKBBaseModel +from models_provider.impl.base_chat_open_ai import BaseChatOpenAI + + +def custom_get_token_ids(text: str): + tokenizer = TokenizerManage.get_tokenizer() + return tokenizer.encode(text) + + +class OrcaRouterChatModel(MaxKBBaseModel, BaseChatOpenAI): + + @staticmethod + def is_cache_model(): + return False + + @staticmethod + def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): + optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) + return OrcaRouterChatModel( + model=model_name, + openai_api_base=model_credential.get('api_base') or "https://api.orcarouter.ai/v1", + openai_api_key=model_credential.get('api_key'), + **optional_params, + ) diff --git a/apps/models_provider/impl/orcarouter_model_provider/orcarouter_model_provider.py b/apps/models_provider/impl/orcarouter_model_provider/orcarouter_model_provider.py new file mode 100644 index 00000000000..9f16f7de32e --- /dev/null +++ b/apps/models_provider/impl/orcarouter_model_provider/orcarouter_model_provider.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +""" +@Project :MaxKB +@File :orcarouter_model_provider.py +@Date :2026-08-15 +""" + +import os + +from common.utils.common import get_file_content +from models_provider.base_model_provider import ( + IModelProvider, + ModelProvideInfo, + ModelInfo, + ModelTypeConst, + ModelInfoManage, +) +from models_provider.impl.orcarouter_model_provider.credential.llm import OrcaRouterLLMModelCredential +from models_provider.impl.orcarouter_model_provider.model.llm import OrcaRouterChatModel +from maxkb.conf import PROJECT_DIR + +orcarouter_llm_model_credential = OrcaRouterLLMModelCredential() +orcarouter_llm_list = [ + ModelInfo("orcarouter/auto", "", ModelTypeConst.LLM, orcarouter_llm_model_credential, OrcaRouterChatModel), + ModelInfo("openai/gpt-5.5", "", ModelTypeConst.LLM, orcarouter_llm_model_credential, OrcaRouterChatModel), + ModelInfo("google/gemini-3.5-flash", "", ModelTypeConst.LLM, orcarouter_llm_model_credential, + OrcaRouterChatModel), + ModelInfo("anthropic/claude-opus-4.8", "", ModelTypeConst.LLM, orcarouter_llm_model_credential, + OrcaRouterChatModel), + ModelInfo("grok/grok-4.3", "", ModelTypeConst.LLM, orcarouter_llm_model_credential, OrcaRouterChatModel), + ModelInfo("deepseek/deepseek-v4-pro", "", ModelTypeConst.LLM, orcarouter_llm_model_credential, + OrcaRouterChatModel), + ModelInfo("minimax/minimax-m2.7", "", ModelTypeConst.LLM, orcarouter_llm_model_credential, + OrcaRouterChatModel), + ModelInfo("qwen/qwen3.7-max", "", ModelTypeConst.LLM, orcarouter_llm_model_credential, OrcaRouterChatModel), +] + +model_info_manage = ( + ModelInfoManage.builder() + .append_model_info_list(orcarouter_llm_list) + .append_default_model_info(orcarouter_llm_list[1]) + .build() +) + + +class OrcaRouterModelProvider(IModelProvider): + def get_model_info_manage(self): + return model_info_manage + + def get_model_provide_info(self): + return ModelProvideInfo( + provider="model_orcarouter_provider", + name="OrcaRouter", + icon=get_file_content( + os.path.join( + PROJECT_DIR, + "apps", + "models_provider", + "impl", + "orcarouter_model_provider", + "icon", + "orcarouter_icon_svg", + ) + ), + )