add submodel.ai#1915
Conversation
# 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`.
WalkthroughAdds 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
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. Comment |
There was a problem hiding this comment.
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 UIThe 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 (notypeinput here)EditTagModal doesn’t expose or set
type, so theswitch (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
typeis present), or wire atypefield 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-lineKeeps 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 IDsEntries 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 clarityThe multi-line
audioPricemultiplication 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
cacheRatioandcacheCreationRatioare 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
📒 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 stringsThe breakdown matches the computed
textPrice; placeholders align with variables. No functional issues spotted.common/api_type.go (1)
70-72: Mapping added correctlyChannelTypeSubmodel → 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 thatconstant.ChannelBaseURLsincludes 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 confirmedMapping for
constant.ChannelTypeSubmodel→constant.APITypeSubmodelexists incommon/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.
| type Adaptor struct { | ||
| } | ||
|
|
There was a problem hiding this comment.
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>
There was a problem hiding this comment.
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
📒 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.
add submodel.ai
Summary by CodeRabbit
New Features
Style