From e2afaaf3bbd3e562f28c6edd68b70c673dd61e45 Mon Sep 17 00:00:00 2001
From: Studio <80060894+StudioPlatforms@users.noreply.github.com>
Date: Tue, 20 Oct 2026 10:24:12 +0300
Subject: [PATCH] feat: add AI Stupid Level as a model provider (#21185)
---
conf/llm_factories.json | 8 +
conf/model.md | 1 +
conf/models/aistupidlevel.json | 93 ++++++++++++
internal/entity/models/aistupidlevel.go | 44 ++++++
internal/entity/models/aistupidlevel_test.go | 150 +++++++++++++++++++
internal/entity/models/factory.go | 2 +
web/src/assets/svg/llm/aistupidlevel.svg | 4 +
web/src/components/svg-icon.tsx | 1 +
web/src/constants/llm.ts | 3 +
9 files changed, 306 insertions(+)
create mode 100644 conf/models/aistupidlevel.json
create mode 100644 internal/entity/models/aistupidlevel.go
create mode 100644 internal/entity/models/aistupidlevel_test.go
create mode 100644 web/src/assets/svg/llm/aistupidlevel.svg
diff --git a/conf/llm_factories.json b/conf/llm_factories.json
index a823997a3f..c83a075713 100644
--- a/conf/llm_factories.json
+++ b/conf/llm_factories.json
@@ -3491,6 +3491,14 @@
"llm": [],
"url": "https://api.trustedrouter.com/v1"
},
+ {
+ "name": "AI Stupid Level",
+ "logo": "",
+ "tags": "LLM",
+ "status": "1",
+ "llm": [],
+ "url": "https://aistupidlevel.info/v1"
+ },
{
"name": "Demon Route",
"logo": "",
diff --git a/conf/model.md b/conf/model.md
index b896501b08..7715986566 100644
--- a/conf/model.md
+++ b/conf/model.md
@@ -197,6 +197,7 @@ The following providers are **aggregators** — they host models from multiple u
| **TogetherAI** | Open-source model hosting |
| **TokenHub** | Multi-provider aggregator |
| **TrustedRouter** | OpenAI-compatible router |
+| **AI Stupid Level** | Benchmark-based router (routes each request to the best-scoring model, with your own provider keys) |
| **TokenPony** | Multi-provider aggregator |
| **Volcengine (Doubao)** | ByteDance's cloud (hosts Doubao + third-party) |
diff --git a/conf/models/aistupidlevel.json b/conf/models/aistupidlevel.json
new file mode 100644
index 0000000000..57a3f8ad82
--- /dev/null
+++ b/conf/models/aistupidlevel.json
@@ -0,0 +1,93 @@
+{
+ "name": "AI Stupid Level",
+ "url": {
+ "default": "https://aistupidlevel.info/v1"
+ },
+ "url_suffix": {
+ "chat": "chat/completions",
+ "models": "models"
+ },
+ "class": "aistupidlevel",
+ "models": [
+ {
+ "name": "auto-coding",
+ "context_length": 262144,
+ "model_types": [
+ "chat"
+ ],
+ "tools": {
+ "support": true
+ }
+ },
+ {
+ "name": "auto-reasoning",
+ "context_length": 262144,
+ "model_types": [
+ "chat"
+ ],
+ "tools": {
+ "support": true
+ }
+ },
+ {
+ "name": "auto-tooling",
+ "context_length": 262144,
+ "model_types": [
+ "chat"
+ ],
+ "tools": {
+ "support": true
+ }
+ },
+ {
+ "name": "auto-best",
+ "context_length": 262144,
+ "model_types": [
+ "chat"
+ ],
+ "tools": {
+ "support": true
+ }
+ },
+ {
+ "name": "auto-task",
+ "context_length": 262144,
+ "model_types": [
+ "chat"
+ ],
+ "tools": {
+ "support": true
+ }
+ },
+ {
+ "name": "auto-value",
+ "context_length": 262144,
+ "model_types": [
+ "chat"
+ ],
+ "tools": {
+ "support": true
+ }
+ },
+ {
+ "name": "auto-value-coding",
+ "context_length": 262144,
+ "model_types": [
+ "chat"
+ ],
+ "tools": {
+ "support": true
+ }
+ },
+ {
+ "name": "auto-fastest-quality",
+ "context_length": 262144,
+ "model_types": [
+ "chat"
+ ],
+ "tools": {
+ "support": true
+ }
+ }
+ ]
+}
diff --git a/internal/entity/models/aistupidlevel.go b/internal/entity/models/aistupidlevel.go
new file mode 100644
index 0000000000..29eca12b02
--- /dev/null
+++ b/internal/entity/models/aistupidlevel.go
@@ -0,0 +1,44 @@
+//
+// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package models
+
+// AIStupidLevelModel drives AI Stupid Level's Smart Router, an OpenAI-compatible
+// router (https://aistupidlevel.info/v1) whose model ids are routing modes such as
+// "auto-coding" or "auto-value", or real model ids that pin one model. Each request
+// goes to the model that currently scores best on its continuously re-run benchmarks,
+// called with the user's own provider keys, so the embedded OpenAIAPICompatibleModel
+// covers every call.
+type AIStupidLevelModel struct {
+ *OpenAIAPICompatibleModel
+}
+
+// NewAIStupidLevelModel creates an AI Stupid Level driver.
+func NewAIStupidLevelModel(baseURL map[string]string, urlSuffix URLSuffix) *AIStupidLevelModel {
+ return &AIStupidLevelModel{
+ OpenAIAPICompatibleModel: NewOpenAIAPICompatibleModel(baseURL, urlSuffix),
+ }
+}
+
+// Name returns the driver name.
+func (m *AIStupidLevelModel) Name() string {
+ return "aistupidlevel"
+}
+
+// NewInstance returns a driver bound to the given base URL.
+func (m *AIStupidLevelModel) NewInstance(baseURL map[string]string) ModelDriver {
+ return NewAIStupidLevelModel(baseURL, m.baseModel.URLSuffix)
+}
diff --git a/internal/entity/models/aistupidlevel_test.go b/internal/entity/models/aistupidlevel_test.go
new file mode 100644
index 0000000000..3e8d0013df
--- /dev/null
+++ b/internal/entity/models/aistupidlevel_test.go
@@ -0,0 +1,150 @@
+//
+// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package models
+
+import (
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "ragflow/internal/common"
+ "testing"
+)
+
+// newAIStupidLevelForTest creates an AIStupidLevelModel instance for testing.
+func newAIStupidLevelForTest(baseURL string) *AIStupidLevelModel {
+ return NewAIStupidLevelModel(
+ map[string]string{"default": baseURL},
+ URLSuffix{Chat: "chat/completions", Models: "models"},
+ )
+}
+
+// TestAIStupidLevelName tests the driver name.
+func TestAIStupidLevelName(t *testing.T) {
+ if got := newAIStupidLevelForTest("http://unused").Name(); got != "aistupidlevel" {
+ t.Errorf("Name()=%q", got)
+ }
+}
+
+// TestAIStupidLevelFactory tests creating the driver from the factory by the provider name.
+func TestAIStupidLevelFactory(t *testing.T) {
+ driver, err := NewModelFactory().CreateModelDriver("AI Stupid Level", map[string]string{"default": "http://unused"}, URLSuffix{})
+ if err != nil {
+ t.Fatalf("CreateModelDriver: %v", err)
+ }
+ if _, ok := driver.(*AIStupidLevelModel); !ok {
+ t.Fatalf("driver type=%T, want *AIStupidLevelModel", driver)
+ }
+ if _, ok := driver.NewInstance(map[string]string{"default": "http://other"}).(*AIStupidLevelModel); !ok {
+ t.Fatal("NewInstance did not return *AIStupidLevelModel")
+ }
+}
+
+// The shipped config must resolve to the AI Stupid Level driver: CreateModelDriver
+// dispatches on strings.ToLower(name), so "AI Stupid Level" has to match the
+// "ai stupid level" case; anything else silently falls through to DummyModel.
+func TestAIStupidLevelProviderConfig(t *testing.T) {
+ dir, restore := setupProviderTestDir(t, "aistupidlevel.json")
+ defer restore()
+
+ if err := InitProviderManager(dir); err != nil {
+ t.Fatalf("InitProviderManager: %v", err)
+ }
+
+ provider := GetProviderManager().FindProvider("AI Stupid Level")
+ if provider == nil {
+ t.Fatal("AI Stupid Level provider not found")
+ }
+ if _, ok := provider.ModelDriver.(*AIStupidLevelModel); !ok {
+ t.Fatalf("ModelDriver=%T, want *models.AIStupidLevelModel", provider.ModelDriver)
+ }
+}
+
+// TestAIStupidLevelChatSendsAPIKey tests chat authentication and that the routing
+// mode reaches the API unchanged as the model id.
+func TestAIStupidLevelChatSendsAPIKey(t *testing.T) {
+ withSSRFBypass(t)
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/chat/completions" {
+ t.Errorf("path=%s", r.URL.Path)
+ }
+ if got := r.Header.Get("Authorization"); got != "Bearer sk-gw-test" {
+ t.Errorf("Authorization=%q", got)
+ }
+ var body struct {
+ Model string `json:"model"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
+ t.Errorf("decode request: %v", err)
+ }
+ if body.Model != "auto-coding" {
+ t.Errorf("model=%q", body.Model)
+ }
+ _ = json.NewEncoder(w).Encode(map[string]any{
+ "id": "chat-aistupidlevel",
+ "model": "claude-opus-5-5",
+ "choices": []map[string]any{{"message": map[string]any{"content": "pong"}}},
+ "usage": map[string]any{"prompt_tokens": 3, "completion_tokens": 5, "total_tokens": 8},
+ })
+ }))
+ defer srv.Close()
+
+ usage := &common.ModelUsage{}
+ resp, err := newAIStupidLevelForTest(srv.URL).ChatWithMessages(
+ t.Context(),
+ "auto-coding",
+ []Message{{Role: "user", Content: "ping"}},
+ &APIConfig{ApiKey: &testAPIKey},
+ nil,
+ usage,
+ )
+ if err != nil {
+ t.Fatalf("ChatWithMessages: %v", err)
+ }
+ if *resp.Answer != "pong" {
+ t.Errorf("Answer=%q", *resp.Answer)
+ }
+ assertModelUsage(t, usage, 3, 5, 8)
+}
+
+// TestAIStupidLevelListModelsAndCheckConnection tests model listing and the connectivity check.
+func TestAIStupidLevelListModelsAndCheckConnection(t *testing.T) {
+ withSSRFBypass(t)
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodGet || r.URL.Path != "/models" {
+ t.Errorf("%s %s", r.Method, r.URL.Path)
+ }
+ _ = json.NewEncoder(w).Encode(map[string]any{
+ "data": []map[string]any{
+ {"id": "auto-coding"},
+ {"id": "claude-opus-5-5"},
+ },
+ })
+ }))
+ defer srv.Close()
+
+ model := newAIStupidLevelForTest(srv.URL)
+ list, err := model.ListModels(t.Context(), &APIConfig{ApiKey: &testAPIKey})
+ if err != nil {
+ t.Fatalf("ListModels: %v", err)
+ }
+ if joinModelNames(list, ",") != "auto-coding,claude-opus-5-5" {
+ t.Errorf("models=%v", list)
+ }
+ if err := model.CheckConnection(t.Context(), &APIConfig{ApiKey: &testAPIKey}); err != nil {
+ t.Fatalf("CheckConnection: %v", err)
+ }
+}
diff --git a/internal/entity/models/factory.go b/internal/entity/models/factory.go
index 2166aadc87..064eb9d3ac 100644
--- a/internal/entity/models/factory.go
+++ b/internal/entity/models/factory.go
@@ -123,6 +123,8 @@ func (f *ModelFactory) CreateModelDriver(providerName string, baseURL map[string
return NewDemonRouteModel(baseURL, urlSuffix), nil
case "trustedrouter":
return NewTrustedRouterModel(baseURL, urlSuffix), nil
+ case "ai stupid level":
+ return NewAIStupidLevelModel(baseURL, urlSuffix), nil
case "novitaai":
return NewNovitaModel(baseURL, urlSuffix), nil
case "avian":
diff --git a/web/src/assets/svg/llm/aistupidlevel.svg b/web/src/assets/svg/llm/aistupidlevel.svg
new file mode 100644
index 0000000000..36c2c32472
--- /dev/null
+++ b/web/src/assets/svg/llm/aistupidlevel.svg
@@ -0,0 +1,4 @@
+
diff --git a/web/src/components/svg-icon.tsx b/web/src/components/svg-icon.tsx
index a15e474f7a..7898e1ab17 100644
--- a/web/src/components/svg-icon.tsx
+++ b/web/src/components/svg-icon.tsx
@@ -120,6 +120,7 @@ const svgIcons = [
LLMFactory.Opper,
LLMFactory.TrustedRouter,
LLMFactory.EmpirioLabs,
+ LLMFactory.AIStupidLevel,
];
export const LlmIcon = ({
diff --git a/web/src/constants/llm.ts b/web/src/constants/llm.ts
index e3d32d6a88..7f7418708d 100644
--- a/web/src/constants/llm.ts
+++ b/web/src/constants/llm.ts
@@ -114,6 +114,7 @@ export enum LLMFactory {
DemonRoute = 'Demon Route',
TrustedRouter = 'TrustedRouter',
EmpirioLabs = 'EmpirioLabs AI',
+ AIStupidLevel = 'AI Stupid Level',
}
// Please lowercase the file name
@@ -210,6 +211,7 @@ export const IconMap = {
[LLMFactory.Opper]: 'opper',
[LLMFactory.TrustedRouter]: 'trustedrouter',
[LLMFactory.EmpirioLabs]: 'empiriolabs',
+ [LLMFactory.AIStupidLevel]: 'aistupidlevel',
};
export const ModelTypeToField: Record = {
@@ -242,6 +244,7 @@ export const APIMapUrl = {
[LLMFactory.TrustedRouter]: 'https://trustedrouter.com/keys',
[LLMFactory.EmpirioLabs]:
'https://platform.empiriolabs.ai/dashboard/api-keys',
+ [LLMFactory.AIStupidLevel]: 'https://aistupidlevel.info/router/keys',
[LLMFactory.MWS]:
'https://mws.ru/docs/cloud-platform/gpt/general/inference-text.html',
[LLMFactory.Anthropic]: 'https://console.anthropic.com/settings/keys',