Skip to content

add submodel.ai#1915

Merged
seefs001 merged 71 commits into
QuantumNous:alphafrom
danding5:main
Sep 29, 2025
Merged

add submodel.ai#1915
seefs001 merged 71 commits into
QuantumNous:alphafrom
danding5:main

Conversation

@danding5
Copy link
Copy Markdown
Contributor

@danding5 danding5 commented Sep 29, 2025

Summary by CodeRabbit

  • New Features

    • Added “SubModel” channel with adaptor and endpoint support.
    • Expanded default model list and edit modal options for SubModel.
    • Added pricing/ratio presets for multiple new models to improve cost display and calculations.
    • Enabled selecting “SubModel” in channel options.
  • Style

    • Adjusted pricing description rendering by removing some localization wrappers, showing fixed strings for extra services and input/output descriptions.

danding5 and others added 30 commits September 8, 2025 16:21
# Conflicts:
#	common/api_type.go
#	constant/api_type.go
#	constant/channel.go
#	relay/relay_adaptor.go
#	web/src/constants/channel.constants.js
# Conflicts:
#	relay/relay_adaptor.go
增加并发支持
将连接延迟关闭逻辑调整到协程中执行,防止在完全接收到所有数据前提前关闭
- Création du fichier de traduction `fr.json` en se basant sur `en.json`.
- Mise à jour de la configuration i18n pour inclure la langue française.
- Modification du sélecteur de langue pour afficher l'option "Français" avec le drapeau correspondant.
- Création du fichier `README.fr.md` en se basant sur `README.en.md`.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Sep 29, 2025

Walkthrough

Adds a new Submodel channel/API type (value 53), registers its base URL and API type, implements a relay adaptor for Submodel, extends default model ratio mappings with multiple submodel entries, and updates the web UI to expose channel 53 and related model presets; some UI strings had i18n wrappers removed.

Changes

Cohort / File(s) Summary
API type mapping
common/api_type.go, constant/api_type.go
Adds APITypeSubmodel constant and maps ChannelTypeSubmodel to APITypeSubmodel in the channel→API mapping.
Channel type and base URL
constant/channel.go
Adds ChannelTypeSubmodel = 53 and appends "https://llm.submodel.ai" to ChannelBaseURLs.
Relay submodel adaptor
relay/channel/submodel/adaptor.go, relay/channel/submodel/constants.go, relay/relay_adaptor.go
New Submodel adaptor: request URL/header construction, OpenAI-style request passthrough, streaming/non-stream response handling, unsupported endpoints return errors; exports ModelList and ChannelName; wires adaptor in factory.
Ratio settings
setting/ratio_setting/model_ratio.go
Adds multiple submodel entries to defaultModelRatio with specified ratios; no logic changes beyond mapping additions.
Web UI integration & rendering
web/src/constants/channel.constants.js, web/src/components/table/channels/modals/EditTagModal.jsx, web/src/helpers/render.jsx
Adds CHANNEL_OPTIONS entry for value 53 ("SubModel"); populates localModels for type 53; several pricing/extra-service strings had i18n wrappers removed (now literals).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant UI as Web UI
  participant Relay as Relay Server
  participant Adaptor as Submodel Adaptor
  participant API as Submodel API

  User->>UI: Select channel 53 and send request
  UI->>Relay: HTTP (OpenAI-style payload, stream flag)
  Relay->>Adaptor: GetAdaptor(APITypeSubmodel)
  Adaptor->>Adaptor: GetRequestURL / SetupRequestHeader
  Adaptor->>API: DoApiRequest (Authorization, payload)
  API-->>Adaptor: HTTP response

  alt Stream
    Adaptor->>Relay: stream chunks
    Relay->>UI: forward SSE chunks
  else Non-stream
    Adaptor->>Relay: JSON response
    Relay->>UI: forward response
  end

  note over Adaptor,API: Unsupported endpoints (Claude/audio/image/embedding/rerank) return errors early
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • xyfacai
  • Calcium-Ion
  • seefs001

Poem

I’m a rabbit with a tiny hat,
Channel fifty-three — imagine that!
Headers snug, models in a row,
Ratios set, the packets flow.
Hop, stream, respond — then off we go! 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly identifies the key change—adding support for the Submodel AI integration—by referencing “submodel.ai,” which aligns with the main additions of a new channel type, constants, and adaptor for the submodel provider.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (8)
web/src/constants/channel.constants.js (1)

162-166: Add channel icon support and align naming with rest of UI

The option looks fine. Consider:

  • Add a corresponding case in getChannelIcon for value 53 so the UI shows a brand/icon instead of none.
  • Ensure label spelling is consistent across the app (“SubModel” vs “Submodel”). Minor, but helps searchability.
web/src/components/table/channels/modals/EditTagModal.jsx (2)

121-123: This preset list won’t take effect in this modal (no type input here)

EditTagModal doesn’t expose or set type, so the switch (value) path isn’t reached. The new case 53 is effectively dead here.

  • If the intent is to prefill models when editing channels, move this case to EditChannelModal (where type is present), or wire a type field into this modal.
  • Otherwise, drop the switch (value) block from this modal to avoid confusion.

121-123: Style/readability: break the long preset array into one-id-per-line

Keeps parity with cases 2/5 above and reduces diff churn.

-        case 53:
-          localModels = ['NousResearch/Hermes-4-405B-FP8', 'Qwen/Qwen3-235B-A22B-Thinking-2507', 'Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8','Qwen/Qwen3-235B-A22B-Instruct-2507', 'zai-org/GLM-4.5-FP8', 'openai/gpt-oss-120b', 'deepseek-ai/DeepSeek-R1-0528', 'deepseek-ai/DeepSeek-R1', 'deepseek-ai/DeepSeek-V3-0324', 'deepseek-ai/DeepSeek-V3.1'];
+        case 53:
+          localModels = [
+            'NousResearch/Hermes-4-405B-FP8',
+            'Qwen/Qwen3-235B-A22B-Thinking-2507',
+            'Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8',
+            'Qwen/Qwen3-235B-A22B-Instruct-2507',
+            'zai-org/GLM-4.5-FP8',
+            'openai/gpt-oss-120b',
+            'deepseek-ai/DeepSeek-R1-0528',
+            'deepseek-ai/DeepSeek-R1',
+            'deepseek-ai/DeepSeek-V3-0324',
+            'deepseek-ai/DeepSeek-V3.1',
+          ];
setting/ratio_setting/model_ratio.go (1)

254-265: Ratios added—consider future-proofing versioned IDs

Entries look consistent with the UI presets. To reduce future churn on patch tags (e.g., DeepSeek-V3-0324 vs V3.1), consider adding a normalization in FormatMatchingModelName for these submodel families or introducing wildcard keys similar to existing “gpt--” handling.

web/src/helpers/render.jsx (3)

1203-1221: Consistency: unify “Web搜索” vs “Web 搜索” wording and add icon for channel 53

  • Strings here use “Web搜索”,while renderLogContent uses “Web 搜索” (space). Align to one style for UX consistency.
  • Also, currently Submodel (53) has no icon in getChannelIcon. Add one to avoid a blank badge.
- return i18next.t('Web搜索价格:${{price}} / 1K 次', {
+ return i18next.t('Web 搜索价格:${{price}} / 1K 次', {
   price: webSearchPrice,
 })

Additional change outside this hunk (same file) to show an icon for channel 53:

@@ export function getChannelIcon(channelType) {
   const iconSize = 14;
@@
     case 51: // 即梦 Jimeng
       return <Jimeng.Color size={iconSize} />;
+    case 53: // Submodel
+      // Placeholder: use a neutral aggregator icon; replace if a Submodel icon becomes available
+      return <OpenRouter size={iconSize} />;

1401-1404: Minor: collapse broken arithmetic line for clarity

The multi-line audioPrice multiplication is harder to scan; one-line improves readability.

-      (audioCompletionTokens / 1000000) *
-      inputRatioPrice *
-      audioRatio *
-      audioCompletionRatio *
-      groupRatio;
+      (audioCompletionTokens / 1000000) * inputRatioPrice * audioRatio * audioCompletionRatio * groupRatio;

1620-1648: Remove unused placeholders in template params

cacheRatio and cacheCreationRatio are passed to i18n but not used in this final summary string. Drop them to avoid confusion (or include them explicitly if desired).

-                  cacheRatio: cacheRatio,
                   cacheCreationInput: cacheCreationTokens,
-                  cacheCreationRatio: cacheCreationRatio,
                   cachePrice: cacheRatioPrice,
                   cacheCreationPrice: cacheCreationRatioPrice,
relay/channel/submodel/adaptor.go (1)

19-30: Deduplicate “endpoint not supported” errors.
Optional: define a package-level var to reuse and keep messages consistent.

+var errNotSupported = errors.New("submodel channel: endpoint not supported")
 
-func (a *Adaptor) ConvertClaudeRequest(*gin.Context, *relaycommon.RelayInfo, *dto.ClaudeRequest) (any, error) {
-	return nil, errors.New("submodel channel: endpoint not supported")
-}
+func (a *Adaptor) ConvertClaudeRequest(*gin.Context, *relaycommon.RelayInfo, *dto.ClaudeRequest) (any, error) {
+	return nil, errNotSupported
+}
 
-func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.AudioRequest) (io.Reader, error) {
-	return nil, errors.New("submodel channel: endpoint not supported")
-}
+func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.AudioRequest) (io.Reader, error) {
+	return nil, errNotSupported
+}
 
-func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.ImageRequest) (any, error) {
-	return nil, errors.New("submodel channel: endpoint not supported")
-}
+func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.ImageRequest) (any, error) {
+	return nil, errNotSupported
+}
 
-func (a *Adaptor) ConvertRerankRequest(c *gin.Context, relayMode int, request dto.RerankRequest) (any, error) {
-	return nil, errors.New("submodel channel: endpoint not supported")
-}
+func (a *Adaptor) ConvertRerankRequest(c *gin.Context, relayMode int, request dto.RerankRequest) (any, error) {
+	return nil, errNotSupported
+}
 
-func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.EmbeddingRequest) (any, error) {
-	return nil, errors.New("submodel channel: endpoint not supported")
-}
+func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.EmbeddingRequest) (any, error) {
+	return nil, errNotSupported
+}
 
-func (a *Adaptor) ConvertOpenAIResponsesRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.OpenAIResponsesRequest) (any, error) {
-	return nil, errors.New("submodel channel: endpoint not supported")
-}
+func (a *Adaptor) ConvertOpenAIResponsesRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.OpenAIResponsesRequest) (any, error) {
+	return nil, errNotSupported
+}

Also applies to: 51-61

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f989fc and 8c5b665.

📒 Files selected for processing (10)
  • common/api_type.go (1 hunks)
  • constant/api_type.go (1 hunks)
  • constant/channel.go (2 hunks)
  • relay/channel/submodel/adaptor.go (1 hunks)
  • relay/channel/submodel/constants.go (1 hunks)
  • relay/relay_adaptor.go (2 hunks)
  • setting/ratio_setting/model_ratio.go (1 hunks)
  • web/src/components/table/channels/modals/EditTagModal.jsx (1 hunks)
  • web/src/constants/channel.constants.js (1 hunks)
  • web/src/helpers/render.jsx (4 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-27T02:15:25.448Z
Learnt from: AAEE86
PR: QuantumNous/new-api#1658
File: web/src/components/table/channels/modals/EditChannelModal.jsx:555-569
Timestamp: 2025-08-27T02:15:25.448Z
Learning: In EditChannelModal.jsx, the applyModelMapping function transforms the models list by replacing original model names (mapping values) with display names (mapping keys). The database stores this transformed list containing mapped keys. On channel load, data.models contains these mapped display names, making the initialization filter if (data.models.includes(key)) correct.

Applied to files:

  • web/src/components/table/channels/modals/EditTagModal.jsx
📚 Learning: 2025-08-27T02:15:25.448Z
Learnt from: AAEE86
PR: QuantumNous/new-api#1658
File: web/src/components/table/channels/modals/EditChannelModal.jsx:555-569
Timestamp: 2025-08-27T02:15:25.448Z
Learning: In EditChannelModal.jsx, the database stores mapped keys (display names) in the models field after applying model mapping transformations. When loading a channel, data.models contains the mapped keys, not the original model names. The filtering logic if (data.models.includes(key)) in the initialization is correct.

Applied to files:

  • web/src/components/table/channels/modals/EditTagModal.jsx
📚 Learning: 2025-08-05T17:14:17.246Z
Learnt from: neotf
PR: QuantumNous/new-api#1511
File: setting/ratio_setting/model_ratio.go:118-123
Timestamp: 2025-08-05T17:14:17.246Z
Learning: Claude models handle "-thinking" variants differently from Gemini models. For Claude models, only the base model (without "-thinking") gets an entry in defaultModelRatio map. The "-thinking" variants rely on the Claude relay handler stripping the suffix using strings.TrimSuffix(textRequest.Model, "-thinking") before looking up the ratio, so they automatically use the base model's ratio.

Applied to files:

  • setting/ratio_setting/model_ratio.go
🧬 Code graph analysis (4)
relay/relay_adaptor.go (3)
constant/api_type.go (1)
  • APITypeSubmodel (36-36)
relay/channel/submodel/adaptor.go (1)
  • Adaptor (16-17)
relay/channel/adapter.go (1)
  • Adaptor (13-30)
common/api_type.go (2)
constant/channel.go (1)
  • ChannelTypeSubmodel (53-53)
constant/api_type.go (1)
  • APITypeSubmodel (36-36)
web/src/helpers/render.jsx (1)
web/src/hooks/model-pricing/useModelPricingData.jsx (1)
  • groupRatio (50-50)
relay/channel/submodel/adaptor.go (11)
relay/common/relay_info.go (1)
  • RelayInfo (74-120)
dto/claude.go (1)
  • ClaudeRequest (187-203)
dto/audio.go (1)
  • AudioRequest (9-15)
relay/common/relay_utils.go (1)
  • GetFullRequestURL (22-34)
relay/channel/api_request.go (2)
  • SetupApiRequestHeader (25-37)
  • DoApiRequest (39-72)
dto/openai_request.go (1)
  • GeneralOpenAIRequest (25-78)
dto/rerank.go (1)
  • RerankRequest (10-18)
dto/embedding.go (1)
  • EmbeddingRequest (21-32)
types/error.go (1)
  • NewAPIError (82-90)
relay/channel/openai/relay-openai.go (2)
  • OaiStreamHandler (112-176)
  • OpenaiHandler (178-274)
relay/channel/submodel/constants.go (2)
  • ModelList (3-14)
  • ChannelName (16-16)
🔇 Additional comments (11)
web/src/helpers/render.jsx (1)

1460-1480: LGTM on the explanatory strings

The breakdown matches the computed textPrice; placeholders align with variables. No functional issues spotted.

common/api_type.go (1)

70-72: Mapping added correctly

ChannelTypeSubmodel → APITypeSubmodel wiring looks correct and matches the rest of the switch.

If not already covered, ensure relay_adaptor returns the submodel adaptor for APITypeSubmodel, and that constant.ChannelBaseURLs includes the base URL for type 53.

relay/relay_adaptor.go (2)

40-40: Import wiring looks good.
No issues with the added submodel import.


106-108: Adaptor factory wiring confirmed

Mapping for constant.ChannelTypeSubmodelconstant.APITypeSubmodel exists in common/api_type.go, so the Submodel adaptor case is reachable.

constant/channel.go (2)

53-53: ChannelTypeSubmodel addition looks consistent.
No issues with the new enum value.


113-113: Confirm Submodel base URL.
Please verify that https://llm.submodel.ai is the correct production base for chat endpoints.

If you have docs, double‑check required path (e.g., /v1/chat/completions vs /v1/responses) and auth scheme for Submodel.

relay/channel/submodel/constants.go (2)

16-16: ChannelName is fine.
Matches wiring expectations.


3-14: All models have corresponding ratios; no action needed.
Verified that every entry in relay/channel/submodel/constants.go exists in setting/ratio_setting/model_ratio.go.

relay/channel/submodel/adaptor.go (3)

38-42: Header setup: confirm auth scheme.
Setting Authorization: Bearer is reasonable; please confirm Submodel doesn’t require a different header (e.g., X-API-Key) or additional headers.


44-49: Pass-through OpenAI request LGTM.
No transformation needed if Submodel is OpenAI‑compatible for chat.

Double‑check whether Submodel prefers /v1/responses; if so, wire ConvertOpenAIResponsesRequest instead of returning “not supported.”


67-74: Response handling via OpenAI helpers LGTM.
Stream/non‑stream coverage is correct assuming Submodel uses OpenAI SSE/event shapes.

Please run an end‑to‑end streaming call in staging to validate SSE parsing with OaiStreamHandler.

Comment thread constant/api_type.go Outdated
Comment on lines +16 to +18
type Adaptor struct {
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Adaptor misses ConvertGeminiRequest — interface not fully implemented.
This won’t satisfy channel.Adaptor and will fail to compile. Add a stub returning “endpoint not supported.”

 type Adaptor struct {
 }
 
+// ConvertGeminiRequest is required by channel.Adaptor
+func (a *Adaptor) ConvertGeminiRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeminiChatRequest) (any, error) {
+	return nil, errors.New("submodel channel: endpoint not supported")
+}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In relay/channel/submodel/adaptor.go around lines 16–18, the Adaptor struct is
missing the ConvertGeminiRequest method required by the channel.Adaptor
interface which causes a compile error; add a stub method with the exact
signature from channel.Adaptor (using the correct context and channel types)
that returns a nil response and an error with the message "endpoint not
supported." Ensure the method uses the same package types, returns the correct
types, and add any needed import (errors or fmt) for creating the error.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
constant/api_type.go (1)

34-36: Add an explicit count alias to prevent future regressions.

Define a clear alias so callers don’t accidentally use a non‑sentinel as the bound.

 package constant

 const (
@@
-     APITypeSubmodel
-     APITypeDummy    // this one is only for count, do not add any channel after this
+     APITypeSubmodel
+     APITypeDummy    // this one is only for count, do not add any channel after this
 )
+
+// Use APITypeCount as the canonical upper bound in loops/arrays.
+const APITypeCount = APITypeDummy
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c5b665 and fc2d992.

📒 Files selected for processing (1)
  • constant/api_type.go (1 hunks)
🔇 Additional comments (2)
constant/api_type.go (2)

34-36: Correct sentinel ordering — LGTM.

Submodel is placed before Dummy, and Dummy remains the terminal “count” sentinel. This resolves the earlier blocker about not appending after Dummy and keeps prior iota values stable.


34-36: No remaining sentinel-bound usages of APITypeMoonshot
Ripgrep confirms no loops, array lengths, or casts use APITypeMoonshot as an upper bound; existing case clauses are valid.

@seefs001 seefs001 self-assigned this Sep 29, 2025
@seefs001 seefs001 self-requested a review September 29, 2025 13:51
@seefs001 seefs001 changed the base branch from main to alpha September 29, 2025 13:53
@seefs001 seefs001 merged commit 83b2b07 into QuantumNous:alpha Sep 29, 2025
1 check passed
@coderabbitai coderabbitai Bot mentioned this pull request Jan 14, 2026
x22x22 pushed a commit to x22x22/new-api that referenced this pull request Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants