-
Notifications
You must be signed in to change notification settings - Fork 11.6k
Add Submodel channel support #1804
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
511489d
23e4249
42d2975
78b0f89
a12ed57
cab5622
274872b
4c2979b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| package submodel | ||
|
|
||
| import ( | ||
| "errors" | ||
| "io" | ||
| "net/http" | ||
| "one-api/dto" | ||
| "one-api/relay/channel" | ||
| "one-api/relay/channel/openai" | ||
| relaycommon "one-api/relay/common" | ||
| "one-api/types" | ||
|
|
||
| "github.com/gin-gonic/gin" | ||
| ) | ||
|
|
||
| type Adaptor struct { | ||
| } | ||
|
|
||
| func (a *Adaptor) ConvertClaudeRequest(*gin.Context, *relaycommon.RelayInfo, *dto.ClaudeRequest) (any, 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, errors.New("submodel channel: endpoint not supported") | ||
| } | ||
|
|
||
| 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) Init(info *relaycommon.RelayInfo) { | ||
| } | ||
|
|
||
| func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) { | ||
| return relaycommon.GetFullRequestURL(info.BaseUrl, info.RequestURLPath, info.ChannelType), nil | ||
| } | ||
|
|
||
| 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) | ||
| return nil | ||
| } | ||
|
|
||
| func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeneralOpenAIRequest) (any, error) { | ||
| if request == nil { | ||
| return nil, errors.New("request is nil") | ||
| } | ||
| return request, nil | ||
| } | ||
|
|
||
| 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) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.EmbeddingRequest) (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, errors.New("submodel channel: endpoint not supported") | ||
| } | ||
|
|
||
| func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (any, error) { | ||
| return channel.DoApiRequest(a, c, info, requestBody) | ||
| } | ||
|
|
||
| func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (usage any, err *types.NewAPIError) { | ||
| if info.IsStream { | ||
| usage, err = openai.OaiStreamHandler(c, info, resp) | ||
| } else { | ||
| usage, err = openai.OpenaiHandler(c, info, resp) | ||
| } | ||
| return | ||
| } | ||
|
|
||
| func (a *Adaptor) GetModelList() []string { | ||
| return ModelList | ||
| } | ||
|
|
||
| func (a *Adaptor) GetChannelName() string { | ||
| return ChannelName | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package submodel | ||
|
|
||
| var ModelList = []string{ | ||
| "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", | ||
| } | ||
|
|
||
| const ChannelName = "submodel" |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -37,8 +37,12 @@ import ( | |||||||||||||||
| "one-api/relay/channel/zhipu" | ||||||||||||||||
| "one-api/relay/channel/zhipu_4v" | ||||||||||||||||
| "strconv" | ||||||||||||||||
| <<<<<<< HEAD | ||||||||||||||||
| "one-api/relay/channel/submodel" | ||||||||||||||||
| ======= | ||||||||||||||||
|
|
||||||||||||||||
| "github.com/gin-gonic/gin" | ||||||||||||||||
| >>>>>>> 4f760a8d407d321bf7f011331ecffb2744b555fd | ||||||||||||||||
|
Comment on lines
+41
to
+45
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unresolved merge conflict and missing imports — blocks build. Conflict markers present; gin import is dropped; submodel import only on one side. Resolve and include both imports. Apply this fix: import (
"one-api/constant"
"one-api/relay/channel"
"one-api/relay/channel/ali"
@@
"one-api/relay/channel/zhipu_4v"
"strconv"
-<<<<<<< HEAD
- "one-api/relay/channel/submodel"
-=======
-
- "github.com/gin-gonic/gin"
->>>>>>> 4f760a8d407d321bf7f011331ecffb2744b555fd
+ "github.com/gin-gonic/gin"
+ "one-api/relay/channel/submodel"
)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||
| ) | ||||||||||||||||
|
|
||||||||||||||||
| func GetAdaptor(apiType int) channel.Adaptor { | ||||||||||||||||
|
|
@@ -103,6 +107,8 @@ func GetAdaptor(apiType int) channel.Adaptor { | |||||||||||||||
| return &jimeng.Adaptor{} | ||||||||||||||||
| case constant.APITypeMoonshot: | ||||||||||||||||
| return &moonshot.Adaptor{} // Moonshot uses Claude API | ||||||||||||||||
| case constant.APITypeSubmodel: | ||||||||||||||||
| return &submodel.Adaptor{} | ||||||||||||||||
| } | ||||||||||||||||
|
Comment on lines
+110
to
112
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainAdaptor wiring for SubModel — OK once imports are fixed. Case is correct; will work after resolving imports. 🏁 Script executed: #!/bin/bash
# Ensure no other conflict markers remain
rg -n '^(<{7}|={7}|>{7})' -SLength of output: 147 Resolve git merge conflicts and fix imports in relay/relay_adaptor.go Submodel adaptor case (lines 110–112) is correct, but relay/relay_adaptor.go contains unresolved git conflict markers at lines 40, 42, 45 — remove the conflict markers and fix imports so the file compiles. 🤖 Prompt for AI Agents |
||||||||||||||||
| return nil | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -54,6 +54,7 @@ import { | |||||||||||||
| FastGPT, | ||||||||||||||
| Kling, | ||||||||||||||
| Jimeng, | ||||||||||||||
| SubModel, | ||||||||||||||
| } from '@lobehub/icons'; | ||||||||||||||
|
Comment on lines
+57
to
58
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainVerify SubModel icon export exists in @lobehub/icons. Import compiles only if SubModel is exported in your installed version. 🌐 Web query: 💡 Result: Short answer: No.
If you want, I can search a specific package version tarball or list the package's exported names to double-check—which version should I inspect? Sources:
SubModel is not exported by @lobehub/icons — remove or replace import The @lobehub/icons package does not export a component named "SubModel"; the import in web/src/helpers/render.jsx (lines 57–58) will fail to compile.
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| import { | ||||||||||||||
|
|
@@ -342,6 +343,8 @@ export function getChannelIcon(channelType) { | |||||||||||||
| return <FastGPT.Color size={iconSize} />; | ||||||||||||||
| case 21: // 知识库:AI Proxy | ||||||||||||||
| case 44: // 嵌入模型:MokaAI M3E | ||||||||||||||
| case 53: // 嵌入模型:SubModel | ||||||||||||||
| return <SubModel size={iconSize} />; | ||||||||||||||
|
Comment on lines
+346
to
+347
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard SubModel usage with feature-detect; provide fallback icon Avoid hard dependency on a possibly-missing icon component. Use LobeIcons.SubModel if present, otherwise a generic Layers icon. - case 53: // 嵌入模型:SubModel
- return <SubModel size={iconSize} />;
+ case 53: { // 子模型 / SubModel
+ const Icon = LobeIcons.SubModel;
+ return Icon ? <Icon size={iconSize} /> : <Layers size={iconSize} />;
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| default: | ||||||||||||||
| return null; // 未知类型或自定义渠道不显示图标 | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -1200,25 +1203,25 @@ export function renderModelPrice( | |||||||||||||
| const extraServices = [ | ||||||||||||||
| webSearch && webSearchCallCount > 0 | ||||||||||||||
| ? i18next.t( | ||||||||||||||
| ' + Web搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}', | ||||||||||||||
| { | ||||||||||||||
| count: webSearchCallCount, | ||||||||||||||
| price: webSearchPrice, | ||||||||||||||
| ratio: groupRatio, | ||||||||||||||
| ratioType: ratioLabel, | ||||||||||||||
| }, | ||||||||||||||
| ) | ||||||||||||||
| ' + Web搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}', | ||||||||||||||
| { | ||||||||||||||
| count: webSearchCallCount, | ||||||||||||||
| price: webSearchPrice, | ||||||||||||||
| ratio: groupRatio, | ||||||||||||||
| ratioType: ratioLabel, | ||||||||||||||
| }, | ||||||||||||||
| ) | ||||||||||||||
| : '', | ||||||||||||||
| fileSearch && fileSearchCallCount > 0 | ||||||||||||||
| ? i18next.t( | ||||||||||||||
| ' + 文件搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}', | ||||||||||||||
| { | ||||||||||||||
| count: fileSearchCallCount, | ||||||||||||||
| price: fileSearchPrice, | ||||||||||||||
| ratio: groupRatio, | ||||||||||||||
| ratioType: ratioLabel, | ||||||||||||||
| }, | ||||||||||||||
| ) | ||||||||||||||
| ' + 文件搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}', | ||||||||||||||
| { | ||||||||||||||
| count: fileSearchCallCount, | ||||||||||||||
| price: fileSearchPrice, | ||||||||||||||
| ratio: groupRatio, | ||||||||||||||
| ratioType: ratioLabel, | ||||||||||||||
| }, | ||||||||||||||
| ) | ||||||||||||||
| : '', | ||||||||||||||
| imageGenerationCall && imageGenerationCallPrice > 0 | ||||||||||||||
| ? i18next.t( | ||||||||||||||
|
|
@@ -1398,10 +1401,10 @@ export function renderAudioModelPrice( | |||||||||||||
| let audioPrice = | ||||||||||||||
| (audioInputTokens / 1000000) * inputRatioPrice * audioRatio * groupRatio + | ||||||||||||||
| (audioCompletionTokens / 1000000) * | ||||||||||||||
| inputRatioPrice * | ||||||||||||||
| audioRatio * | ||||||||||||||
| audioCompletionRatio * | ||||||||||||||
| groupRatio; | ||||||||||||||
| inputRatioPrice * | ||||||||||||||
| audioRatio * | ||||||||||||||
| audioCompletionRatio * | ||||||||||||||
| groupRatio; | ||||||||||||||
| let price = textPrice + audioPrice; | ||||||||||||||
| return ( | ||||||||||||||
| <> | ||||||||||||||
|
|
@@ -1457,27 +1460,27 @@ export function renderAudioModelPrice( | |||||||||||||
| <p> | ||||||||||||||
| {cacheTokens > 0 | ||||||||||||||
| ? i18next.t( | ||||||||||||||
| '文字提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}', | ||||||||||||||
| { | ||||||||||||||
| nonCacheInput: inputTokens - cacheTokens, | ||||||||||||||
| cacheInput: cacheTokens, | ||||||||||||||
| cachePrice: inputRatioPrice * cacheRatio, | ||||||||||||||
| price: inputRatioPrice, | ||||||||||||||
| completion: completionTokens, | ||||||||||||||
| compPrice: completionRatioPrice, | ||||||||||||||
| total: textPrice.toFixed(6), | ||||||||||||||
| }, | ||||||||||||||
| ) | ||||||||||||||
| '文字提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}', | ||||||||||||||
| { | ||||||||||||||
| nonCacheInput: inputTokens - cacheTokens, | ||||||||||||||
| cacheInput: cacheTokens, | ||||||||||||||
| cachePrice: inputRatioPrice * cacheRatio, | ||||||||||||||
| price: inputRatioPrice, | ||||||||||||||
| completion: completionTokens, | ||||||||||||||
| compPrice: completionRatioPrice, | ||||||||||||||
| total: textPrice.toFixed(6), | ||||||||||||||
| }, | ||||||||||||||
| ) | ||||||||||||||
| : i18next.t( | ||||||||||||||
| '文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}', | ||||||||||||||
| { | ||||||||||||||
| input: inputTokens, | ||||||||||||||
| price: inputRatioPrice, | ||||||||||||||
| completion: completionTokens, | ||||||||||||||
| compPrice: completionRatioPrice, | ||||||||||||||
| total: textPrice.toFixed(6), | ||||||||||||||
| }, | ||||||||||||||
| )} | ||||||||||||||
| '文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}', | ||||||||||||||
| { | ||||||||||||||
| input: inputTokens, | ||||||||||||||
| price: inputRatioPrice, | ||||||||||||||
| completion: completionTokens, | ||||||||||||||
| compPrice: completionRatioPrice, | ||||||||||||||
| total: textPrice.toFixed(6), | ||||||||||||||
| }, | ||||||||||||||
| )} | ||||||||||||||
| </p> | ||||||||||||||
| <p> | ||||||||||||||
| {i18next.t( | ||||||||||||||
|
|
@@ -1617,35 +1620,35 @@ export function renderClaudeModelPrice( | |||||||||||||
| <p> | ||||||||||||||
| {cacheTokens > 0 || cacheCreationTokens > 0 | ||||||||||||||
| ? i18next.t( | ||||||||||||||
| '提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * ${{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}', | ||||||||||||||
| { | ||||||||||||||
| nonCacheInput: nonCachedTokens, | ||||||||||||||
| cacheInput: cacheTokens, | ||||||||||||||
| cacheRatio: cacheRatio, | ||||||||||||||
| cacheCreationInput: cacheCreationTokens, | ||||||||||||||
| cacheCreationRatio: cacheCreationRatio, | ||||||||||||||
| cachePrice: cacheRatioPrice, | ||||||||||||||
| cacheCreationPrice: cacheCreationRatioPrice, | ||||||||||||||
| price: inputRatioPrice, | ||||||||||||||
| completion: completionTokens, | ||||||||||||||
| compPrice: completionRatioPrice, | ||||||||||||||
| ratio: groupRatio, | ||||||||||||||
| ratioType: ratioLabel, | ||||||||||||||
| total: price.toFixed(6), | ||||||||||||||
| }, | ||||||||||||||
| ) | ||||||||||||||
| '提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * ${{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}', | ||||||||||||||
| { | ||||||||||||||
| nonCacheInput: nonCachedTokens, | ||||||||||||||
| cacheInput: cacheTokens, | ||||||||||||||
| cacheRatio: cacheRatio, | ||||||||||||||
| cacheCreationInput: cacheCreationTokens, | ||||||||||||||
| cacheCreationRatio: cacheCreationRatio, | ||||||||||||||
| cachePrice: cacheRatioPrice, | ||||||||||||||
| cacheCreationPrice: cacheCreationRatioPrice, | ||||||||||||||
| price: inputRatioPrice, | ||||||||||||||
| completion: completionTokens, | ||||||||||||||
| compPrice: completionRatioPrice, | ||||||||||||||
| ratio: groupRatio, | ||||||||||||||
| ratioType: ratioLabel, | ||||||||||||||
| total: price.toFixed(6), | ||||||||||||||
| }, | ||||||||||||||
| ) | ||||||||||||||
| : i18next.t( | ||||||||||||||
| '提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}', | ||||||||||||||
| { | ||||||||||||||
| input: inputTokens, | ||||||||||||||
| price: inputRatioPrice, | ||||||||||||||
| completion: completionTokens, | ||||||||||||||
| compPrice: completionRatioPrice, | ||||||||||||||
| ratio: groupRatio, | ||||||||||||||
| ratioType: ratioLabel, | ||||||||||||||
| total: price.toFixed(6), | ||||||||||||||
| }, | ||||||||||||||
| )} | ||||||||||||||
| '提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}', | ||||||||||||||
| { | ||||||||||||||
| input: inputTokens, | ||||||||||||||
| price: inputRatioPrice, | ||||||||||||||
| completion: completionTokens, | ||||||||||||||
| compPrice: completionRatioPrice, | ||||||||||||||
| ratio: groupRatio, | ||||||||||||||
| ratioType: ratioLabel, | ||||||||||||||
| total: price.toFixed(6), | ||||||||||||||
| }, | ||||||||||||||
| )} | ||||||||||||||
| </p> | ||||||||||||||
| <p>{i18next.t('仅供参考,以实际扣费为准')}</p> | ||||||||||||||
| </article> | ||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Do not add after APITypeDummy — move APITypeSubmodel before Dummy.
APITypeDummy is a sentinel (“do not add any channel after this”). Adding Submodel after Dummy breaks that contract and may exclude it from counts/loops.
Apply this reordering:
🏁 Script executed:
Length of output: 317
Move APITypeSubmodel before APITypeDummy (constant/api_type.go:34-36)
APITypeDummy is a sentinel ("do not add any channel after this"); Submodel after it violates that contract and can be excluded from counts/loops.
📝 Committable suggestion
🤖 Prompt for AI Agents