Skip to content

add submodel.ai#1763

Closed
danding5 wants to merge 0 commit into
QuantumNous:mainfrom
danding5:main
Closed

add submodel.ai#1763
danding5 wants to merge 0 commit into
QuantumNous:mainfrom
danding5:main

Conversation

@danding5
Copy link
Copy Markdown
Contributor

@danding5 danding5 commented Sep 8, 2025

Summary by CodeRabbit

  • New Features

    • Added a new "SubModel" channel with selectable models and built-in model list for quick setup.
    • SubModel requests support proper headers and both streaming and non-streaming responses.
    • Default usage ratios added for SubModel models to inform routing and weighting.
  • UI

    • "SubModel" appears as a channel option; associated models auto-populate when editing tags.
    • Channel icon handling updated to include the new type.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Sep 8, 2025

Walkthrough

Adds a new Submodel channel/API: constants (channel & API type, base URL), ChannelType→APIType mapping, relay adaptor registration, a new submodel adaptor + model list, default model ratios, and frontend channel option/behavior for type 52.

Changes

Cohort / File(s) Summary of Changes
Core constants & mappings
common/api_type.go, constant/api_type.go, constant/channel.go, relay/relay_adaptor.go
Added APITypeSubmodel and ChannelTypeSubmodel (52), inserted base URL https://llm.submodel.ai, mapped channel→API type, and registered submodel.Adaptor in GetAdaptor.
Submodel adaptor & metadata
relay/channel/submodel/adaptor.go, relay/channel/submodel/constants.go
New submodel.Adaptor implementation (request conversion stubs, header setup, URL builder, request execution, response handling) and exported ModelList + ChannelName.
Model ratio defaults
setting/ratio_setting/model_ratio.go
Appended ~10 Submodel models to defaultModelRatio with specified ratios; existing logic unchanged.
Web UI: channel option & UI behavior
web/src/constants/channel.constants.js, web/src/helpers/render.js, web/src/pages/Channel/EditTagModal.js
Added CHANNEL option { value: 52, label: 'SubModel' }; getChannelIcon handles case 52 (returns null); EditTagModal sets explicit model list for type 52.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Client
    participant Relay as Relay Server
    participant Adaptor as Submodel Adaptor
    participant HTTP as channel.DoApiRequest
    participant SubAPI as Submodel API
    participant OAI as OpenAI Handlers

    Client->>Relay: OpenAI-compatible request
    Relay->>Adaptor: GetRequestURL(info)
    Adaptor-->>Relay: https://llm.submodel.ai/...
    Relay->>Adaptor: SetupRequestHeader(..., ApiKey)
    Adaptor-->>Relay: Headers w/ Authorization Bearer
    Relay->>Adaptor: DoRequest(body)
    Adaptor->>HTTP: Perform HTTP request
    HTTP->>SubAPI: POST
    SubAPI-->>HTTP: HTTP response
    HTTP-->>Adaptor: *http.Response
    Adaptor->>Adaptor: DoResponse(resp)
    alt streaming
        Adaptor->>OAI: oaiStreamHandler(resp)
        OAI-->>Client: SSE stream
    else non-stream
        Adaptor->>OAI: openaiHandler(resp)
        OAI-->>Client: JSON response
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • xyfacai

Poem

A little rabbit, code in paw,
New Submodel tracks I helped to draw.
Headers tucked, the models bloom,
Streams or JSON — out they zoom!
I twitch, I hop, merge in — hooray! 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 511489d and 23e4249.

📒 Files selected for processing (1)
  • web/src/pages/Channel/EditTagModal.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/pages/Channel/EditTagModal.js
✨ 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: 1

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

138-142: Casing consistency for the new label

Consider "Submodel" (or provider-preferred branding) to keep naming consistent across UI and backend (backend uses "submodel"). Purely cosmetic.

-    label: 'SubModel',
+    label: 'Submodel',
relay/channel/submodel/constants.go (2)

3-14: Avoid FE/BE drift on model lists

This hardcoded list duplicates data also used in the web app (EditTagModal.js). Prefer one source of truth (e.g., expose via API or import a shared manifest) to prevent mismatch.


16-16: Use const for channel name

ChannelName is immutable; declare it as const.

-var ChannelName = "submodel"
+const ChannelName = "submodel"
setting/ratio_setting/model_ratio.go (1)

227-238: defaultModelRatio is aligned with ModelList
Optional: future-proof Thinking variants by adding a wildcard key (e.g. "Qwen/Qwen3-235B-A22B-Thinking-*": 0.6) in defaultModelRatio and normalizing names in GetModelRatio so any new date-suffix reuses the same ratio.

web/src/pages/Channel/EditTagModal.js (2)

101-103: Consistency/readability: use multi-line array as in other cases

Match cases 2/5 formatting for easier diffs/maintenance.

-          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'];
+          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',
+          ];

101-103: Remove unreachable ‘type’ branch or add a ‘type’ field
In EditTagModal.js, the switch inside handleInputChange(…, …) for name === 'type' (lines 101–103) never runs—no <Form> input binds to field='type'. Either remove this dead code or introduce a Form.Input/Select with field='type'.

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

51-57: Unimplemented endpoints: consider minimal pass-through for embeddings/responses

If submodel is OpenAI-compatible, you can mirror ConvertOpenAIRequest: validate and pass through Embeddings and Responses to reduce 501s. Otherwise, leave unimplemented.

 func (a *Adaptor) ConvertRerankRequest(c *gin.Context, relayMode int, request dto.RerankRequest) (any, error) {
   return nil, errors.New("not implemented")
 }
 
 func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.EmbeddingRequest) (any, error) {
-  return nil, errors.New("not implemented")
+  if request.Model == "" {
+    return nil, errors.New("model is empty")
+  }
+  return request, nil
 }
 
 func (a *Adaptor) ConvertOpenAIResponsesRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.OpenAIResponsesRequest) (any, error) {
-  return nil, errors.New("not implemented")
+  if request.Model == "" {
+    return nil, errors.New("model is empty")
+  }
+  return request, nil
 }

Also applies to: 59-61


38-42: Add X-API-Key fallback header
Consider also sending X-API-Key: <key> alongside Authorization: Bearer <key>—it’s harmless if unused and covers providers expecting that scheme. For example:

 func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error {
   channel.SetupApiRequestHeader(info, c, req)
   req.Set("Authorization", "Bearer "+info.ApiKey)
+  req.Set("X-API-Key", info.ApiKey) // fallback for providers expecting this header
   return nil
 }

Confirm with submodel.ai documentation for their required auth header.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3064ff0 and 511489d.

📒 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/constants/channel.constants.js (1 hunks)
  • web/src/helpers/render.js (1 hunks)
  • web/src/pages/Channel/EditTagModal.js (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 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
📚 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/pages/Channel/EditTagModal.js
📚 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/pages/Channel/EditTagModal.js
🧬 Code graph analysis (3)
common/api_type.go (2)
constant/channel.go (1)
  • ChannelTypeSubmodel (52-52)
constant/api_type.go (1)
  • APITypeSubmodel (34-34)
relay/relay_adaptor.go (3)
constant/api_type.go (1)
  • APITypeSubmodel (34-34)
relay/channel/submodel/adaptor.go (1)
  • Adaptor (16-17)
relay/channel/adapter.go (1)
  • Adaptor (13-29)
relay/channel/submodel/adaptor.go (11)
relay/common/relay_info.go (1)
  • RelayInfo (62-112)
dto/claude.go (1)
  • ClaudeRequest (183-199)
dto/audio.go (1)
  • AudioRequest (3-9)
relay/common/relay_utils.go (1)
  • GetFullRequestURL (13-25)
relay/channel/api_request.go (2)
  • SetupApiRequestHeader (23-35)
  • DoApiRequest (37-58)
dto/openai_request.go (2)
  • GeneralOpenAIRequest (21-65)
  • OpenAIResponsesRequest (607-629)
dto/rerank.go (1)
  • RerankRequest (3-11)
dto/embedding.go (1)
  • EmbeddingRequest (14-25)
types/error.go (1)
  • NewAPIError (75-81)
relay/channel/openai/relay-openai.go (2)
  • OaiStreamHandler (109-183)
  • OpenaiHandler (185-241)
relay/channel/submodel/constants.go (2)
  • ModelList (3-14)
  • ChannelName (16-16)
🔇 Additional comments (10)
web/src/helpers/render.js (1)

401-402: Explicit no-icon handling is fine

Adding case 52 to return null mirrors 21 and 44. No issues.

relay/relay_adaptor.go (2)

37-37: Import looks correct

Package path and placement are consistent with other channel imports.


100-101: Adaptor registration added

APITypeSubmodel → submodel.Adaptor wiring is correct.

common/api_type.go (1)

68-70: Channel→API mapping added

ChannelTypeSubmodel correctly maps to APITypeSubmodel.

constant/channel.go (2)

52-52: Enum extension OK

New ChannelTypeSubmodel = 52 fits the sequence and sentinel usage.


110-110: Verify provider base URL

Confirm https://llm.submodel.ai is the correct base (path/versioning handled by adaptor, e.g., /v1). If a suffix is required by the provider, add it here or in the adaptor builder.

constant/api_type.go (1)

34-35: API type added in the right place

Inserted before APITypeDummy; no existing values shift except Dummy. Looks good.

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

34-36: URL construction LGTM

Using GetFullRequestURL keeps Cloudflare Gateway compatibility and honors per-channel base URLs.


67-74: Response handling LGTM

Delegating to OpenAI stream/non-stream handlers is appropriate if upstream is OpenAI-compatible.


76-82: Model/channel metadata LGTM

Exposes backend ModelList and ChannelName as expected.

Comment thread web/src/pages/Channel/EditTagModal.js Outdated
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.

1 participant