Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions apps/locales/en_US/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -3698,6 +3698,21 @@ msgstr ""
msgid "Output the maximum Tokens"
msgstr ""

#: apps/models_provider/impl/aimlapi_model_provider/credential/image.py:33
#: apps/models_provider/impl/aimlapi_model_provider/credential/llm.py:34
msgid ""
"Specify the maximum number of tokens that the model can generate. Reasoning "
"models may spend more tokens than this limit on their reasoning process, so "
"it does not cap the cost of a request"
msgstr ""

#: apps/models_provider/impl/aimlapi_model_provider/credential/tti.py:24
#: apps/models_provider/impl/aimlapi_model_provider/credential/tti.py:41
msgid ""
"Different image models accept different values, keep auto to let the model "
"decide"
msgstr ""

#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:31
msgid "Specify the maximum number of tokens that the model can generate."
msgstr ""
Expand Down
15 changes: 15 additions & 0 deletions apps/locales/zh_CN/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -3715,6 +3715,21 @@ msgstr "较高的数值会使输出更加随机,而较低的数值会使其更
msgid "Output the maximum Tokens"
msgstr "输出最大Token数"

#: apps/models_provider/impl/aimlapi_model_provider/credential/image.py:33
#: apps/models_provider/impl/aimlapi_model_provider/credential/llm.py:34
msgid ""
"Specify the maximum number of tokens that the model can generate. Reasoning "
"models may spend more tokens than this limit on their reasoning process, so "
"it does not cap the cost of a request"
msgstr "指定模型可以生成的最大 tokens 数。推理模型的思考过程可能会超出该限制,因此它并不能限制单次请求的费用"

#: apps/models_provider/impl/aimlapi_model_provider/credential/tti.py:24
#: apps/models_provider/impl/aimlapi_model_provider/credential/tti.py:41
msgid ""
"Different image models accept different values, keep auto to let the model "
"decide"
msgstr "不同的图片模型支持的取值不同,保持 auto 则由模型自行决定"

#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:31
msgid "Specify the maximum number of tokens that the model can generate."
msgstr "指定模型可以生成的最大 tokens 数"
Expand Down
15 changes: 15 additions & 0 deletions apps/locales/zh_Hant/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -3715,6 +3715,21 @@ msgstr "較高的數值會使輸出更加隨機,而較低的數值會使其更
msgid "Output the maximum Tokens"
msgstr "輸出最大Token數"

#: apps/models_provider/impl/aimlapi_model_provider/credential/image.py:33
#: apps/models_provider/impl/aimlapi_model_provider/credential/llm.py:34
msgid ""
"Specify the maximum number of tokens that the model can generate. Reasoning "
"models may spend more tokens than this limit on their reasoning process, so "
"it does not cap the cost of a request"
msgstr "指定模型可以生成的最大 tokens 數。推理模型的思考過程可能會超出該限制,因此它並不能限制單次請求的費用"

#: apps/models_provider/impl/aimlapi_model_provider/credential/tti.py:24
#: apps/models_provider/impl/aimlapi_model_provider/credential/tti.py:41
msgid ""
"Different image models accept different values, keep auto to let the model "
"decide"
msgstr "不同的圖片模型支援的取值不同,保持 auto 則由模型自行決定"

#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:31
msgid "Specify the maximum number of tokens that the model can generate."
msgstr "指定模型可以生成的最大 tokens 數"
Expand Down
2 changes: 2 additions & 0 deletions apps/models_provider/constants/model_provider_constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# coding=utf-8
from enum import Enum

from models_provider.impl.aimlapi_model_provider.aimlapi_model_provider import AIMLAPIModelProvider
from models_provider.impl.aliyun_bai_lian_model_provider.aliyun_bai_lian_model_provider import \
AliyunBaiLianModelProvider
from models_provider.impl.anthropic_model_provider.anthropic_model_provider import AnthropicModelProvider
Expand All @@ -27,6 +28,7 @@


class ModelProvideConstants(Enum):
model_aimlapi_provider = AIMLAPIModelProvider()
model_azure_provider = AzureModelProvider()
model_wenxin_provider = WenxinModelProvider()
model_ollama_provider = OllamaModelProvider()
Expand Down
8 changes: 8 additions & 0 deletions apps/models_provider/impl/aimlapi_model_provider/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# coding=utf-8
"""
@project: MaxKB
@Author:aimlapi.com
@file: __init__.py
@date:2026/09/03 10:00
@desc:
"""
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# coding=utf-8
"""
@project: MaxKB
@Author:aimlapi.com
@file: aimlapi_model_provider.py
@date:2026/09/03 10:00
@desc:
"""
import os

from common.utils.common import get_file_content
from maxkb.conf import PROJECT_DIR
from models_provider.base_model_provider import ModelInfo, ModelTypeConst, ModelInfoManage, IModelProvider, \
ModelProvideInfo
from models_provider.impl.aimlapi_model_provider.credential.embedding import AIMLAPIEmbeddingCredential
from models_provider.impl.aimlapi_model_provider.credential.image import AIMLAPIImageModelCredential
from models_provider.impl.aimlapi_model_provider.credential.llm import AIMLAPILLMModelCredential
from models_provider.impl.aimlapi_model_provider.credential.tti import AIMLAPITextToImageModelCredential
from models_provider.impl.aimlapi_model_provider.model.embedding import AIMLAPIEmbeddingModel
from models_provider.impl.aimlapi_model_provider.model.image import AIMLAPIImage
from models_provider.impl.aimlapi_model_provider.model.llm import AIMLAPIChatModel
from models_provider.impl.aimlapi_model_provider.model.tti import AIMLAPITextToImage

aimlapi_llm_model_credential = AIMLAPILLMModelCredential()
aimlapi_image_model_credential = AIMLAPIImageModelCredential()
aimlapi_embedding_model_credential = AIMLAPIEmbeddingCredential()
aimlapi_tti_model_credential = AIMLAPITextToImageModelCredential()

# AI/ML API 提供 350 多个对话模型,这里只列出常用的一部分,
# 其余模型可以在添加模型时直接填写模型名称。
# 模型 ID 以 https://api.aimlapi.com/v1/models 为准,注意点号写法(如 claude-sonnet-4.5)。
model_info_list = [
ModelInfo('openai/gpt-5-5', '', ModelTypeConst.LLM, aimlapi_llm_model_credential, AIMLAPIChatModel),
ModelInfo('openai/gpt-5-mini', '', ModelTypeConst.LLM, aimlapi_llm_model_credential, AIMLAPIChatModel),
ModelInfo('openai/gpt-4.1', '', ModelTypeConst.LLM, aimlapi_llm_model_credential, AIMLAPIChatModel),
ModelInfo('openai/gpt-4.1-mini', '', ModelTypeConst.LLM, aimlapi_llm_model_credential, AIMLAPIChatModel),
ModelInfo('openai/gpt-4o', '', ModelTypeConst.LLM, aimlapi_llm_model_credential, AIMLAPIChatModel),
ModelInfo('openai/gpt-4o-mini', '', ModelTypeConst.LLM, aimlapi_llm_model_credential, AIMLAPIChatModel),
ModelInfo('anthropic/claude-opus-4.5', '', ModelTypeConst.LLM, aimlapi_llm_model_credential, AIMLAPIChatModel),
ModelInfo('anthropic/claude-sonnet-4.5', '', ModelTypeConst.LLM, aimlapi_llm_model_credential, AIMLAPIChatModel),
ModelInfo('anthropic/claude-haiku-4.5', '', ModelTypeConst.LLM, aimlapi_llm_model_credential, AIMLAPIChatModel),
ModelInfo('google/gemini-2.5-pro', '', ModelTypeConst.LLM, aimlapi_llm_model_credential, AIMLAPIChatModel),
ModelInfo('google/gemini-2.5-flash', '', ModelTypeConst.LLM, aimlapi_llm_model_credential, AIMLAPIChatModel),
ModelInfo('deepseek/deepseek-v4-flash', '', ModelTypeConst.LLM, aimlapi_llm_model_credential, AIMLAPIChatModel),
ModelInfo('alibaba/qwen3-max', '', ModelTypeConst.LLM, aimlapi_llm_model_credential, AIMLAPIChatModel),
ModelInfo('zhipu/glm-4.6', '', ModelTypeConst.LLM, aimlapi_llm_model_credential, AIMLAPIChatModel),
ModelInfo('moonshotai/kimi-k2', '', ModelTypeConst.LLM, aimlapi_llm_model_credential, AIMLAPIChatModel),
ModelInfo('meta-llama/Llama-3.3-70B-Instruct-Turbo', '', ModelTypeConst.LLM, aimlapi_llm_model_credential,
AIMLAPIChatModel),
ModelInfo('mistralai/mistral-medium-3.1', '', ModelTypeConst.LLM, aimlapi_llm_model_credential, AIMLAPIChatModel),
ModelInfo('x-ai/grok-4-6', '', ModelTypeConst.LLM, aimlapi_llm_model_credential, AIMLAPIChatModel),
]

model_info_image_list = [
ModelInfo('openai/gpt-4o', '', ModelTypeConst.IMAGE, aimlapi_image_model_credential, AIMLAPIImage),
ModelInfo('openai/gpt-4o-mini', '', ModelTypeConst.IMAGE, aimlapi_image_model_credential, AIMLAPIImage),
ModelInfo('openai/gpt-4.1', '', ModelTypeConst.IMAGE, aimlapi_image_model_credential, AIMLAPIImage),
ModelInfo('google/gemini-2.5-pro', '', ModelTypeConst.IMAGE, aimlapi_image_model_credential, AIMLAPIImage),
ModelInfo('google/gemini-2.5-flash', '', ModelTypeConst.IMAGE, aimlapi_image_model_credential, AIMLAPIImage),
ModelInfo('anthropic/claude-sonnet-4.5', '', ModelTypeConst.IMAGE, aimlapi_image_model_credential, AIMLAPIImage),
ModelInfo('alibaba/qwen3-vl-plus', '', ModelTypeConst.IMAGE, aimlapi_image_model_credential, AIMLAPIImage),
]

model_info_embedding_list = [
ModelInfo('openai/text-embedding-3-small', '', ModelTypeConst.EMBEDDING, aimlapi_embedding_model_credential,
AIMLAPIEmbeddingModel),
ModelInfo('openai/text-embedding-3-large', '', ModelTypeConst.EMBEDDING, aimlapi_embedding_model_credential,
AIMLAPIEmbeddingModel),
ModelInfo('openai/text-embedding-ada-002', '', ModelTypeConst.EMBEDDING, aimlapi_embedding_model_credential,
AIMLAPIEmbeddingModel),
ModelInfo('alibaba/text-embedding-v4', '', ModelTypeConst.EMBEDDING, aimlapi_embedding_model_credential,
AIMLAPIEmbeddingModel),
ModelInfo('google/text-multilingual-embedding-002', '', ModelTypeConst.EMBEDDING,
aimlapi_embedding_model_credential, AIMLAPIEmbeddingModel),
ModelInfo('anthropic/voyage-multilingual-2', '', ModelTypeConst.EMBEDDING, aimlapi_embedding_model_credential,
AIMLAPIEmbeddingModel),
]

model_info_tti_list = [
ModelInfo('flux/schnell', '', ModelTypeConst.TTI, aimlapi_tti_model_credential, AIMLAPITextToImage),
ModelInfo('google/gemini-2.5-flash-image', '', ModelTypeConst.TTI, aimlapi_tti_model_credential,
AIMLAPITextToImage),
ModelInfo('openai/gpt-image-1', '', ModelTypeConst.TTI, aimlapi_tti_model_credential, AIMLAPITextToImage),
ModelInfo('alibaba/qwen-image', '', ModelTypeConst.TTI, aimlapi_tti_model_credential, AIMLAPITextToImage),
ModelInfo('bytedance/seedream-v4-text-to-image', '', ModelTypeConst.TTI, aimlapi_tti_model_credential,
AIMLAPITextToImage),
]

model_info_manage = (
ModelInfoManage.builder()
.append_model_info_list(model_info_list)
.append_default_model_info(
ModelInfo('openai/gpt-4o-mini', '', ModelTypeConst.LLM, aimlapi_llm_model_credential, AIMLAPIChatModel))
.append_model_info_list(model_info_image_list)
.append_default_model_info(model_info_image_list[0])
.append_model_info_list(model_info_embedding_list)
.append_default_model_info(model_info_embedding_list[0])
.append_model_info_list(model_info_tti_list)
.append_default_model_info(model_info_tti_list[0])
.build()
)


class AIMLAPIModelProvider(IModelProvider):

def get_model_info_manage(self):
return model_info_manage

def get_model_provide_info(self):
return ModelProvideInfo(provider='model_aimlapi_provider', name='aimlapi.com', icon=get_file_content(
os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'aimlapi_model_provider', 'icon',
'aimlapi_icon_svg')))
63 changes: 63 additions & 0 deletions apps/models_provider/impl/aimlapi_model_provider/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# coding=utf-8
"""
@project: MaxKB
@Author:aimlapi.com
@file: const.py
@date:2026/09/03 10:00
@desc: AI/ML API 供应商的公共常量与请求参数处理
"""
from typing import Dict, Optional
from urllib.parse import urlparse

from models_provider.base_model_provider import MaxKBBaseModel

# AI/ML API 的默认接口地址,兼容 OpenAI 协议
API_BASE = 'https://api.aimlapi.com/v1'

# 渠道归因请求头:HTTP-Referer / X-Title 标识调用方(即 MaxKB 本身),
# X-AIMLAPI-* 是 AI/ML API 用于渠道统计的请求头。
# 该常量是共享的,任何时候都不要就地修改,请使用 get_default_headers 生成新字典。
ATTRIBUTION_HEADERS = {
'HTTP-Referer': 'https://github.com/1Panel-dev/MaxKB',
'X-Title': 'MaxKB',
'X-AIMLAPI-Partner-ID': 'part_BOQuEVgOgdpgCUqh5u0YkgzL',
'X-AIMLAPI-Source': 'agent/maxkb',
}

# 只有请求发往 AI/ML API 自己的域名时才携带归因请求头。
# 用户可以把 API URL 改成其他服务商或中转代理,此时不应把这些请求头带过去。
ATTRIBUTION_HOSTS = {'api.aimlapi.com'}


def is_aimlapi_endpoint(api_base: Optional[str]) -> bool:
"""
判断接口地址是否指向 AI/ML API
@param api_base: 用户填写的 API URL,为空时使用默认地址
"""
if not api_base:
return True
hostname = urlparse(api_base if '//' in api_base else f'//{api_base}').hostname
return (hostname or '').lower() in ATTRIBUTION_HOSTS


def get_default_headers(api_base: Optional[str], default_headers: Optional[Dict[str, str]] = None) -> Dict[str, str]:
"""
生成请求头,调用方自定义的请求头优先级更高(合并而不是覆盖)
@param api_base: API URL
@param default_headers: 调用方自定义的请求头
@return: 新的请求头字典
"""
if not is_aimlapi_endpoint(api_base):
return {**(default_headers or {})}
return {**ATTRIBUTION_HEADERS, **(default_headers or {})}


def filter_optional_params(model_kwargs: Dict[str, object]) -> Dict[str, object]:
"""
在 MaxKB 默认过滤逻辑之上再丢弃取值为 None 的参数。
AI/ML API 上不同模型对 null 的容忍度不一样:google/gemini-2.5-flash 可以接受,
而 openai/gpt-4o-mini、deepseek/deepseek-chat 等模型在 temperature、top_p、seed、
tools 为 null 时直接返回 400。因此未设置的参数必须省略,而不是以 None 发送。
"""
optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)
return {key: value for key, value in optional_params.items() if value is not None}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# coding=utf-8
"""
@project: MaxKB
@Author:aimlapi.com
@file: embedding.py
@date:2026/09/03 10:00
@desc:
"""
from typing import Dict

from django.utils.translation import gettext as _

from common import forms
from common.exception.app_exception import AppApiException
from common.forms import BaseForm
from common.utils.logger import maxkb_logger
from models_provider.base_model_provider import BaseModelCredential, ValidCode
from models_provider.impl.aimlapi_model_provider.const import API_BASE


class AIMLAPIEmbeddingCredential(BaseForm, BaseModelCredential):
def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,
raise_exception=True):
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,
_('{model_type} Model type is not supported').format(model_type=model_type))

for key in ['api_base', 'api_key']:
if key not in model_credential:
if raise_exception:
raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key))
else:
return False
try:
model = provider.get_model(model_type, model_name, model_credential)
model.embed_query(_('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,
_('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=API_BASE)
api_key = forms.PasswordInputField('API Key', required=True)
Loading