feat: add kimi a7m overlay workflow and remote43 validation
This commit is contained in:
@@ -4,6 +4,9 @@
|
||||
|
||||
它不是宿主原生插件,而是一个可被控制面读取的 `model_pack`,用于描述国产模型 provider 的默认接入模板、默认模型映射、默认套餐和导入约束。
|
||||
|
||||
当前 pack 也可以承载 provider 级 `host_overlays` 元数据。
|
||||
这类 overlay 不是立即在线改宿主代码,而是把“某个 provider 在某个宿主版本上需要额外运行时补丁/兼容层”的工程信息纳入 pack 管理,供 preview/import 阶段直接暴露,并可由 CLI 执行器生成 patched 宿主构建目录。
|
||||
|
||||
当前目录现在同时包含:
|
||||
|
||||
- 真实可校验包:`pack.json`、`providers/deepseek.json`、`checksums.txt`
|
||||
@@ -60,6 +63,18 @@ go run ./cmd/cli import-provider \
|
||||
|
||||
如果你要导入的不是这 10 个模板之一,而是一个全新的官方 provider,那么仍然需要先补一个新的 provider manifest,再做一键导入。
|
||||
|
||||
对已经声明 `host_overlays` 的 provider,也可以直接用最小执行器生成 patched 宿主源码目录。例如对 `kimi-a7m` 命中的 `sub2api v0.1.129` overlay:
|
||||
|
||||
```bash
|
||||
go run ./cmd/cli apply-host-overlay \
|
||||
--pack-dir ./packs/openai-cn-pack \
|
||||
--provider-id kimi-a7m \
|
||||
--host-version 0.1.129 \
|
||||
--source-dir /tmp/sub2api-clean
|
||||
```
|
||||
|
||||
命令会解析 provider manifest 中命中的 overlay,复制 `--source-dir` 到新的输出目录,应用 `patch_path` 指向的补丁,并在输出目录写入 `.sub2api-cn-relay-manager-overlay.json` 元数据文件。
|
||||
|
||||
后续真实交付时,还可以继续扩展更多 provider:
|
||||
|
||||
- `kimi.json`
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
5dcc402daddacce6dcaceb1501020342f1b1121fbffe9097ede4d5aae072f84e providers/minimax.json
|
||||
65e3a1a5e56889ddb0474a3b55294aceb6920fa72dcf6f2c56d3199462daa4cf providers/kimi-k2-thinking-official.json
|
||||
a39de44fa68fcb5ee9c3ef38ed3bd5c30acd23cacd2f618d670de0bf9e7096e3 providers/deepseek-reasoner-official.json
|
||||
e3da0745a14cb76f7275bfef90b40a6f652f4dce2efd95e44c27fe2e81f4eea5 pack.json
|
||||
2db47989a9715464a34b00f7e322ceb1396f96617ddcfa7dee5bd3e7b262c17d providers/kimi-a7m.json
|
||||
584991c1a5a3973bda9701ad15bb1c1b167038baa513cb67985708a65bdf6ca6 overlays/kimi-a7m-sub2api-v0.1.129.md
|
||||
2b2597694ab03409360bf73de43a5cfcea0e26369c4ab18cc8552ff3278729aa overlays/kimi-a7m-sub2api-v0.1.129.patch
|
||||
4d0069e7bb014b886d4b21fa9a2144fcf65e835f158eda1bb98e092efebd93f3 pack.json
|
||||
eda16afc83e12055d3a41b5e37fd0923d3741b66da5af780bcea53ff34fa130e providers/step-3-5-flash-official.json
|
||||
fa486a449407f38de8b180ff301568deccef5177ca0436158b1d5b0e6d9328b2 providers/openai-zhongzhuan.json
|
||||
fdf7fa2e1ff4aa4f5dcd3f3ec2f55db11d6197625a467d6d0afa8a554a6ba6e6 providers/deepseek-chat-official.json
|
||||
|
||||
32
packs/openai-cn-pack/overlays/kimi-a7m-sub2api-v0.1.129.md
Normal file
32
packs/openai-cn-pack/overlays/kimi-a7m-sub2api-v0.1.129.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Kimi A7M / sub2api v0.1.129 Overlay
|
||||
|
||||
`overlay_id`: `sub2api-stock-v0129-kimi-a7m`
|
||||
|
||||
适用范围:
|
||||
|
||||
- 宿主:`sub2api`
|
||||
- 版本:`0.1.129`
|
||||
- provider:`kimi-a7m`
|
||||
|
||||
触发背景:
|
||||
|
||||
- stock `weishaw/sub2api:0.1.129` 面对 `https://kimi.a7m.com.cn/v1` 时,`/v1/models` 可以命中 `kimi-k2.6`
|
||||
- 但运行时 `/v1/chat/completions` 仍会误走到不兼容的 `Responses` 路径,最终返回 `502 upstream_error`
|
||||
- 仅靠 relay-manager 控制面侧方案 C,当前还不能把这条链路收敛到 `ready`
|
||||
|
||||
已验证的宿主补丁落点:
|
||||
|
||||
- `backend/internal/service/openai_apikey_responses_probe.go`
|
||||
- `backend/internal/service/openai_gateway_chat_completions.go`
|
||||
- `backend/internal/service/account_test_service.go`
|
||||
|
||||
参考证据:
|
||||
|
||||
- `artifacts/real-host-acceptance/20260525_local_v0129_kimi_a7m_from_hermes/22-patched-host-validation.json`
|
||||
- `artifacts/real-host-acceptance/20260525_local_v0129_kimi_a7m_from_hermes/23-sub2api-host-patch-notes.md`
|
||||
- `artifacts/real-host-acceptance/20260525_local_v0129_kimi_a7m_scheme_c_stockhost_rerun/21-summary.json`
|
||||
|
||||
当前用途:
|
||||
|
||||
- 由 pack/provider manifest 暴露给控制面,作为“该 provider 在该宿主版本上需要 overlay”的正式插件元数据
|
||||
- 这一步只负责纳管,不直接修改宿主源码
|
||||
356
packs/openai-cn-pack/overlays/kimi-a7m-sub2api-v0.1.129.patch
Normal file
356
packs/openai-cn-pack/overlays/kimi-a7m-sub2api-v0.1.129.patch
Normal file
@@ -0,0 +1,356 @@
|
||||
diff --git a/backend/internal/service/account_test_service.go b/backend/internal/service/account_test_service.go
|
||||
index b9cd698a..3a58e022 100644
|
||||
--- a/backend/internal/service/account_test_service.go
|
||||
+++ b/backend/internal/service/account_test_service.go
|
||||
@@ -555,14 +555,8 @@ func (s *AccountTestService) testOpenAIAccountConnection(c *gin.Context, account
|
||||
if err != nil {
|
||||
return s.sendErrorAndEnd(c, fmt.Sprintf("Invalid base URL: %s", err.Error()))
|
||||
}
|
||||
- // 账号已被探测为不支持 Responses(如 DeepSeek/Kimi 等)时,丢出明确提示。
|
||||
- // 账号本身可用(网关会走 CC 直转),仅测试入口需要补齐 CC SSE 处理逻辑。
|
||||
- // TODO:实现 CC 格式的账号测试路径(需专门的 CC SSE handler)。
|
||||
if !openai_compat.ShouldUseResponsesAPI(account.Extra) {
|
||||
- return s.sendErrorAndEnd(c,
|
||||
- "账号已被探测为不支持 OpenAI Responses API(如 DeepSeek/Kimi 等三方兼容上游),"+
|
||||
- "账号本身可正常使用,但当前测试接口仅支持 Responses API 路径。请直接通过实际 API 调用验证。",
|
||||
- )
|
||||
+ return s.testOpenAIRawChatCompletionsConnection(c, ctx, account, testModelID, prompt, normalizedBaseURL, authToken)
|
||||
}
|
||||
apiURL = buildOpenAIResponsesURL(normalizedBaseURL)
|
||||
} else {
|
||||
@@ -1321,6 +1315,133 @@ func (s *AccountTestService) processOpenAIStream(c *gin.Context, body io.Reader)
|
||||
}
|
||||
}
|
||||
|
||||
+func (s *AccountTestService) testOpenAIRawChatCompletionsConnection(
|
||||
+ c *gin.Context,
|
||||
+ ctx context.Context,
|
||||
+ account *Account,
|
||||
+ modelID string,
|
||||
+ prompt string,
|
||||
+ normalizedBaseURL string,
|
||||
+ authToken string,
|
||||
+) error {
|
||||
+ apiURL := buildOpenAIChatCompletionsURL(normalizedBaseURL)
|
||||
+
|
||||
+ c.Writer.Header().Set("Content-Type", "text/event-stream")
|
||||
+ c.Writer.Header().Set("Cache-Control", "no-cache")
|
||||
+ c.Writer.Header().Set("Connection", "keep-alive")
|
||||
+ c.Writer.Header().Set("X-Accel-Buffering", "no")
|
||||
+ c.Writer.Flush()
|
||||
+
|
||||
+ payloadPrompt := strings.TrimSpace(prompt)
|
||||
+ if payloadPrompt == "" {
|
||||
+ payloadPrompt = "hi"
|
||||
+ }
|
||||
+ payloadBytes, _ := json.Marshal(map[string]any{
|
||||
+ "model": modelID,
|
||||
+ "messages": []map[string]any{
|
||||
+ {
|
||||
+ "role": "user",
|
||||
+ "content": payloadPrompt,
|
||||
+ },
|
||||
+ },
|
||||
+ "stream": true,
|
||||
+ "stream_options": map[string]any{
|
||||
+ "include_usage": true,
|
||||
+ },
|
||||
+ })
|
||||
+
|
||||
+ s.sendEvent(c, TestEvent{Type: "test_start", Model: modelID})
|
||||
+
|
||||
+ req, err := http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewReader(payloadBytes))
|
||||
+ if err != nil {
|
||||
+ return s.sendErrorAndEnd(c, "Failed to create request")
|
||||
+ }
|
||||
+ req.Header.Set("Content-Type", "application/json")
|
||||
+ req.Header.Set("Accept", "text/event-stream")
|
||||
+ req.Header.Set("Authorization", "Bearer "+authToken)
|
||||
+ if customUA := strings.TrimSpace(account.GetOpenAIUserAgent()); customUA != "" {
|
||||
+ req.Header.Set("User-Agent", customUA)
|
||||
+ }
|
||||
+
|
||||
+ proxyURL := ""
|
||||
+ if account.ProxyID != nil && account.Proxy != nil {
|
||||
+ proxyURL = account.Proxy.URL()
|
||||
+ }
|
||||
+
|
||||
+ resp, err := s.httpUpstream.DoWithTLS(req, proxyURL, account.ID, account.Concurrency, s.tlsFPProfileService.ResolveTLSProfile(account))
|
||||
+ if err != nil {
|
||||
+ return s.sendErrorAndEnd(c, fmt.Sprintf("Request failed: %s", err.Error()))
|
||||
+ }
|
||||
+ defer func() { _ = resp.Body.Close() }()
|
||||
+
|
||||
+ if resp.StatusCode != http.StatusOK {
|
||||
+ body, _ := io.ReadAll(resp.Body)
|
||||
+ return s.sendErrorAndEnd(c, fmt.Sprintf("API returned %d: %s", resp.StatusCode, string(body)))
|
||||
+ }
|
||||
+
|
||||
+ return s.processOpenAIChatCompletionsStream(c, resp.Body)
|
||||
+}
|
||||
+
|
||||
+func (s *AccountTestService) processOpenAIChatCompletionsStream(c *gin.Context, body io.Reader) error {
|
||||
+ reader := bufio.NewReader(body)
|
||||
+ seenEvent := false
|
||||
+
|
||||
+ for {
|
||||
+ line, err := reader.ReadString('\n')
|
||||
+ if err != nil {
|
||||
+ if err == io.EOF && seenEvent {
|
||||
+ s.sendEvent(c, TestEvent{Type: "test_complete", Success: true})
|
||||
+ return nil
|
||||
+ }
|
||||
+ if err == io.EOF {
|
||||
+ return s.sendErrorAndEnd(c, "Stream ended before any chat completion event was received")
|
||||
+ }
|
||||
+ return s.sendErrorAndEnd(c, fmt.Sprintf("Stream read error: %s", err.Error()))
|
||||
+ }
|
||||
+
|
||||
+ line = strings.TrimSpace(line)
|
||||
+ if line == "" || !sseDataPrefix.MatchString(line) {
|
||||
+ continue
|
||||
+ }
|
||||
+
|
||||
+ jsonStr := sseDataPrefix.ReplaceAllString(line, "")
|
||||
+ if jsonStr == "[DONE]" {
|
||||
+ s.sendEvent(c, TestEvent{Type: "test_complete", Success: true})
|
||||
+ return nil
|
||||
+ }
|
||||
+
|
||||
+ seenEvent = true
|
||||
+
|
||||
+ var data map[string]any
|
||||
+ if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
|
||||
+ continue
|
||||
+ }
|
||||
+
|
||||
+ if errData, ok := data["error"].(map[string]any); ok {
|
||||
+ if msg, ok := errData["message"].(string); ok && msg != "" {
|
||||
+ return s.sendErrorAndEnd(c, msg)
|
||||
+ }
|
||||
+ return s.sendErrorAndEnd(c, "Unknown error")
|
||||
+ }
|
||||
+
|
||||
+ choices, ok := data["choices"].([]any)
|
||||
+ if !ok || len(choices) == 0 {
|
||||
+ continue
|
||||
+ }
|
||||
+ firstChoice, ok := choices[0].(map[string]any)
|
||||
+ if !ok {
|
||||
+ continue
|
||||
+ }
|
||||
+ delta, ok := firstChoice["delta"].(map[string]any)
|
||||
+ if !ok {
|
||||
+ continue
|
||||
+ }
|
||||
+ if text, ok := delta["content"].(string); ok && text != "" {
|
||||
+ s.sendEvent(c, TestEvent{Type: "content", Text: text})
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
// testOpenAIImageAPIKey tests OpenAI image generation using an API Key account.
|
||||
func (s *AccountTestService) testOpenAIImageAPIKey(c *gin.Context, ctx context.Context, account *Account, modelID, prompt string) error {
|
||||
authToken := account.GetOpenAIApiKey()
|
||||
diff --git a/backend/internal/service/openai_apikey_responses_probe.go b/backend/internal/service/openai_apikey_responses_probe.go
|
||||
index a4eb9252..6935fa6e 100644
|
||||
--- a/backend/internal/service/openai_apikey_responses_probe.go
|
||||
+++ b/backend/internal/service/openai_apikey_responses_probe.go
|
||||
@@ -44,6 +44,28 @@ func openaiResponsesProbePayload(modelID string) []byte {
|
||||
return body
|
||||
}
|
||||
|
||||
+// openaiChatCompletionsProbePayload 是用于交叉验证的最小 Chat Completions 请求体。
|
||||
+//
|
||||
+// 当 /v1/responses 返回 403 这类模糊信号时,我们再探一次
|
||||
+// /v1/chat/completions:若 chat 端点可达,则说明该上游更可能是
|
||||
+// chat-only OpenAI-compatible,而不是完整支持 Responses 的实现。
|
||||
+func openaiChatCompletionsProbePayload(modelID string) []byte {
|
||||
+ if strings.TrimSpace(modelID) == "" {
|
||||
+ modelID = openai.DefaultTestModel
|
||||
+ }
|
||||
+ body, _ := json.Marshal(map[string]any{
|
||||
+ "model": modelID,
|
||||
+ "messages": []map[string]any{
|
||||
+ {
|
||||
+ "role": "user",
|
||||
+ "content": "hi",
|
||||
+ },
|
||||
+ },
|
||||
+ "stream": false,
|
||||
+ })
|
||||
+ return body
|
||||
+}
|
||||
+
|
||||
// ProbeOpenAIAPIKeyResponsesSupport 探测 OpenAI APIKey 账号上游是否支持
|
||||
// /v1/responses 端点,并将结果持久化到 accounts.extra.openai_responses_supported。
|
||||
//
|
||||
@@ -51,6 +73,9 @@ func openaiResponsesProbePayload(modelID string) []byte {
|
||||
//
|
||||
// 探测策略(参见包文档 internal/pkg/openai_compat):
|
||||
// - 上游 404 / 405 → 不支持,写 false
|
||||
+// - 上游 403 → 继续交叉探测 /v1/chat/completions:
|
||||
+// - - chat 端点可达(非 404/405)→ 视为 chat-only upstream,写 false
|
||||
+// - - chat 端点不可确认 / 探测失败 → 保持旧语义,写 true
|
||||
// - 上游 2xx / 其他 4xx(401/422/400 等)/ 5xx → 支持,写 true
|
||||
// - 网络层失败(连接错误、超时)→ 不写标记,保持 unknown
|
||||
// (后续请求仍按"现状即证据"默认走 Responses)
|
||||
@@ -116,6 +141,28 @@ func (s *AccountTestService) ProbeOpenAIAPIKeyResponsesSupport(ctx context.Conte
|
||||
}()
|
||||
|
||||
supported := isResponsesEndpointSupportedByStatus(resp.StatusCode)
|
||||
+ if resp.StatusCode == http.StatusForbidden {
|
||||
+ chatStatus, chatErr := s.probeOpenAIAPIKeyChatCompletionsStatus(ctx, account)
|
||||
+ if chatErr != nil {
|
||||
+ logger.LegacyPrintf(
|
||||
+ "service.openai_probe",
|
||||
+ "probe_chat_crosscheck_failed: account_id=%d base_url=%s err=%v",
|
||||
+ accountID,
|
||||
+ normalizedBaseURL,
|
||||
+ chatErr,
|
||||
+ )
|
||||
+ } else if isChatCompletionsEndpointSupportedByStatus(chatStatus) {
|
||||
+ supported = false
|
||||
+ logger.LegacyPrintf(
|
||||
+ "service.openai_probe",
|
||||
+ "probe_chat_crosscheck_chat_only: account_id=%d base_url=%s responses_status=%d chat_status=%d",
|
||||
+ accountID,
|
||||
+ normalizedBaseURL,
|
||||
+ resp.StatusCode,
|
||||
+ chatStatus,
|
||||
+ )
|
||||
+ }
|
||||
+ }
|
||||
|
||||
if err := s.accountRepo.UpdateExtra(ctx, accountID, map[string]any{
|
||||
openai_compat.ExtraKeyResponsesSupported: supported,
|
||||
@@ -147,3 +194,59 @@ func isResponsesEndpointSupportedByStatus(status int) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
+
|
||||
+func isChatCompletionsEndpointSupportedByStatus(status int) bool {
|
||||
+ switch status {
|
||||
+ case http.StatusNotFound, http.StatusMethodNotAllowed:
|
||||
+ return false
|
||||
+ default:
|
||||
+ return true
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+func (s *AccountTestService) probeOpenAIAPIKeyChatCompletionsStatus(ctx context.Context, account *Account) (int, error) {
|
||||
+ if account == nil {
|
||||
+ return 0, http.ErrNoLocation
|
||||
+ }
|
||||
+
|
||||
+ apiKey := account.GetOpenAIApiKey()
|
||||
+ if apiKey == "" {
|
||||
+ return 0, http.ErrNoCookie
|
||||
+ }
|
||||
+ baseURL := account.GetOpenAIBaseURL()
|
||||
+ if baseURL == "" {
|
||||
+ baseURL = "https://api.openai.com"
|
||||
+ }
|
||||
+ normalizedBaseURL, err := s.validateUpstreamBaseURL(baseURL)
|
||||
+ if err != nil {
|
||||
+ return 0, err
|
||||
+ }
|
||||
+
|
||||
+ probeURL := buildOpenAIChatCompletionsURL(normalizedBaseURL)
|
||||
+ probeCtx, cancel := context.WithTimeout(ctx, openaiResponsesProbeTimeout)
|
||||
+ defer cancel()
|
||||
+
|
||||
+ req, err := http.NewRequestWithContext(probeCtx, http.MethodPost, probeURL, bytes.NewReader(openaiChatCompletionsProbePayload("")))
|
||||
+ if err != nil {
|
||||
+ return 0, err
|
||||
+ }
|
||||
+ req.Header.Set("Content-Type", "application/json")
|
||||
+ req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||
+ req.Header.Set("Accept", "application/json")
|
||||
+
|
||||
+ proxyURL := ""
|
||||
+ if account.ProxyID != nil && account.Proxy != nil {
|
||||
+ proxyURL = account.Proxy.URL()
|
||||
+ }
|
||||
+
|
||||
+ resp, err := s.httpUpstream.DoWithTLS(req, proxyURL, account.ID, account.Concurrency, s.tlsFPProfileService.ResolveTLSProfile(account))
|
||||
+ if err != nil {
|
||||
+ return 0, err
|
||||
+ }
|
||||
+ defer func() {
|
||||
+ _, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 1<<20))
|
||||
+ _ = resp.Body.Close()
|
||||
+ }()
|
||||
+
|
||||
+ return resp.StatusCode, nil
|
||||
+}
|
||||
diff --git a/backend/internal/service/openai_gateway_chat_completions.go b/backend/internal/service/openai_gateway_chat_completions.go
|
||||
index 84d85c74..fe6ff300 100644
|
||||
--- a/backend/internal/service/openai_gateway_chat_completions.go
|
||||
+++ b/backend/internal/service/openai_gateway_chat_completions.go
|
||||
@@ -247,6 +247,17 @@ func (s *OpenAIGatewayService) ForwardAsChatCompletions(
|
||||
|
||||
upstreamMsg := strings.TrimSpace(extractUpstreamErrorMessage(respBody))
|
||||
upstreamMsg = sanitizeUpstreamErrorMessage(upstreamMsg)
|
||||
+ if shouldFallbackResponsesCompatToRawChat(account, resp.StatusCode) {
|
||||
+ s.markOpenAIResponsesUnsupported(ctx, account)
|
||||
+ logger.L().Info("openai chat_completions: fallback responses->raw chat after custom upstream incompatibility signal",
|
||||
+ zap.Int64("account_id", account.ID),
|
||||
+ zap.String("account_name", account.Name),
|
||||
+ zap.String("base_url", account.GetOpenAIBaseURL()),
|
||||
+ zap.Int("responses_status", resp.StatusCode),
|
||||
+ zap.String("upstream_message", upstreamMsg),
|
||||
+ )
|
||||
+ return s.forwardAsRawChatCompletions(ctx, c, account, body, defaultMappedModel)
|
||||
+ }
|
||||
if s.shouldFailoverOpenAIUpstreamResponse(resp.StatusCode, upstreamMsg, respBody) {
|
||||
upstreamDetail := ""
|
||||
if s.cfg != nil && s.cfg.Gateway.LogUpstreamErrorBody {
|
||||
@@ -316,6 +327,47 @@ func normalizeResponsesRequestServiceTier(req *apicompat.ResponsesRequest) {
|
||||
req.ServiceTier = normalizedOpenAIServiceTierValue(req.ServiceTier)
|
||||
}
|
||||
|
||||
+func shouldFallbackResponsesCompatToRawChat(account *Account, status int) bool {
|
||||
+ if account == nil || account.Type != AccountTypeAPIKey {
|
||||
+ return false
|
||||
+ }
|
||||
+ if strings.TrimSpace(account.GetOpenAIBaseURL()) == "" {
|
||||
+ return false
|
||||
+ }
|
||||
+ switch status {
|
||||
+ case http.StatusForbidden, http.StatusNotFound, http.StatusMethodNotAllowed:
|
||||
+ return true
|
||||
+ default:
|
||||
+ return false
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+func (s *OpenAIGatewayService) markOpenAIResponsesUnsupported(ctx context.Context, account *Account) {
|
||||
+ if account == nil || account.Type != AccountTypeAPIKey {
|
||||
+ return
|
||||
+ }
|
||||
+
|
||||
+ if account.Extra == nil {
|
||||
+ account.Extra = map[string]any{}
|
||||
+ }
|
||||
+ if supported, ok := account.Extra[openai_compat.ExtraKeyResponsesSupported].(bool); ok && !supported {
|
||||
+ return
|
||||
+ }
|
||||
+ account.Extra[openai_compat.ExtraKeyResponsesSupported] = false
|
||||
+
|
||||
+ if s == nil || s.accountRepo == nil {
|
||||
+ return
|
||||
+ }
|
||||
+ if err := s.accountRepo.UpdateExtra(ctx, account.ID, map[string]any{
|
||||
+ openai_compat.ExtraKeyResponsesSupported: false,
|
||||
+ }); err != nil {
|
||||
+ logger.L().Warn("openai chat_completions: persist responses unsupported flag failed",
|
||||
+ zap.Int64("account_id", account.ID),
|
||||
+ zap.Error(err),
|
||||
+ )
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
func normalizeResponsesBodyServiceTier(body []byte) ([]byte, string, error) {
|
||||
if len(body) == 0 {
|
||||
return body, "", nil
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"pack_id": "openai-cn-pack",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.3",
|
||||
"vendor": "YourTeam",
|
||||
"target_host": "sub2api",
|
||||
"min_host_version": "0.1.126",
|
||||
|
||||
44
packs/openai-cn-pack/providers/kimi-a7m.json
Normal file
44
packs/openai-cn-pack/providers/kimi-a7m.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"provider_id": "kimi-a7m",
|
||||
"display_name": "Kimi A7M OpenAI Compatible",
|
||||
"base_url": "https://kimi.a7m.com.cn/v1",
|
||||
"platform": "openai",
|
||||
"account_type": "apikey",
|
||||
"force_disable_openai_responses_api": true,
|
||||
"host_overlays": [
|
||||
{
|
||||
"overlay_id": "sub2api-stock-v0129-kimi-a7m",
|
||||
"display_name": "sub2api stock v0.1.129 Kimi A7M overlay",
|
||||
"target_host": "sub2api",
|
||||
"min_host_version": "0.1.129",
|
||||
"max_host_version": "0.1.129",
|
||||
"apply_mode": "patch",
|
||||
"patch_path": "overlays/kimi-a7m-sub2api-v0.1.129.patch",
|
||||
"notes_path": "overlays/kimi-a7m-sub2api-v0.1.129.md",
|
||||
"reason": "stock host still routes Kimi A7M chat traffic into an incompatible Responses path; runtime overlay or shim is still required"
|
||||
}
|
||||
],
|
||||
"default_models": ["kimi-k2.6"],
|
||||
"smoke_test_model": "kimi-k2.6",
|
||||
"group_template": {
|
||||
"name": "Kimi A7M 默认分组",
|
||||
"rate_multiplier": 1.0
|
||||
},
|
||||
"channel_template": {
|
||||
"name": "Kimi A7M 默认渠道",
|
||||
"model_mapping": {
|
||||
"kimi-k2.6": "kimi-k2.6"
|
||||
}
|
||||
},
|
||||
"plan_template": {
|
||||
"name": "Kimi A7M 默认套餐",
|
||||
"price": 19.9,
|
||||
"validity_days": 30,
|
||||
"validity_unit": "day"
|
||||
},
|
||||
"import": {
|
||||
"supports_multi_key": true,
|
||||
"supports_strict": true,
|
||||
"supports_partial": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user