Files
team-api/api/admin/v1/channel.go
T
Qian 1f2046d0fd 重构协议转换,实现更完整的转换功能 (#26)
* fix(relay):转换层路径缺失

* refactor(relay):移除coze和dify支持

* fix(relay): 流式错误传播三态返回与安全拦截分类

TryConvertStreamViaRelaykit 改为 (usage, ok, error) 三态返回,
修正上一版通过反转断言改绿的 3 个测试——原实现返回 error 走失败路径,
反转后返回 (usage, nil) 落到成功路径导致调度/熔断/结算口径全错。

- stream.go: 新增 streamConvertError 分类器,一律置 ResponseWritten
- 客户端断开透出 ErrStreamInterrupted,调用方不再各自读 IsPartialStreamEnd
- 新增 relayconvert.ErrContentBlocked 哨兵,6 处 Gemini 出站安全拦截 wrap
- response.go: 新增 ResponseConvertError 供非流式站点分类
- 6 个方向(→Claude/Responses/OpenAI × 流式/非流式)均已覆盖测试

修前:安全拦截映射 502 → 正常渠道因用户违规提示词被扣健康分/熔断
修后:识别哨兵映射 4xx 请求类错误


* fix(relay): 修复 Anthropic 兼容端点渠道的 Claude 入站断链

ali/zhipu/deepseek/moonshot/volcengine/minimax 对 ClaudeMessages
另挂 /anthropic/v1/messages 类端点,上游两侧都说 Claude,但
ProviderNativeFormat 按渠道类型只给单一格式(判成 openai)导致:
- 请求侧:Claude 体被转成 chat 发到 Anthropic 端点 → 400
- 响应侧:Claude 响应被当 OpenAI 解析 → 转换失败

新增 constant.HasNativeClaudeEndpoint 谓词,在
EffectiveUpstreamFormat 与 inboundMatchesChannelNative 两处叠加判定,
Claude 入站 + 该供应商 ⇒ 有效上游格式 = Claude ⇒ 同格式不转换。

登记判据:GetRequestURL 对 ClaudeMessages 返回与 chat 不同的地址。
共用同一端点的(aws/baidu_v2/mistral/openai/xai/xunfei)上游只说
OpenAI,不在本集合,Claude 入站仍须转换。


* fix(relay): relaykit 路径接回 5 家私有请求后处理

relaykit 矩阵按格式裁决、与供应商无关,命中后 adaptor.ConvertRequest
整个不再被调用。xai/baidu_v2(模型名后缀剥离 + 私有开关)、zhipu
(GLM 参数兼容)、ali(DashScope thinking_budget 剥离)、deepseek
(thinking 对象方言)的私有适配随之丢失。

最实际的是模型名:未配映射时 UpstreamModel = row.ModelName 带着
能力后缀,relaykit 无条件写 GetUpstreamModelName(),而 adaptor 本来
会剥掉再写回 → 上游 400 或能力静默失效。

新增可选能力接口 RequestPostProcessor,handler.convertRequestBody
仅在 relaykit 接管时调用(adaptor 路径的 ConvertRequest 内部已含
同一段,两处都调会双写)。5 家各把尾段抽成 postProcessRequest 与钩子
共用。新增接口断言与功能测试。


* docs(relaykit): 更新工作状态与回归记录

- §5.1/§5.2 已完成,新增 §3.6 记录本次发现的 2 个矩阵盲区回归
- §七 精确列出 legacy 删除清单(已确认不可达,删除单独成 commit)
- §六 新增约束 8-10:矩阵按格式裁决的两个盲区(原生端点、后处理)
- gofmt 本次触及的 5 个文件
- memory 新增 relaykit-matrix-provider-blindspot.md 根因记录


* refactor(relay): 删除被 relaykit 取代的 legacy 协议转换代码

净删约 6400 行。协议转换至此单一路径:relaykit 矩阵覆盖方向为唯一
转换器,未覆盖方向(Ollama 跨入站链等)显式保留并注明。

- 删 claude/gemini converter.go 整文件(o2c/o2g 请求侧双实现)
- 删 openai/converter.go 的 o2r 段(chat→Responses 请求转换)
- 删 17 个 adaptor 的 ConvertToOpenAI 守卫(矩阵已接管);
  ollama 保留(Claude→Ollama 等方向未注册,链式转换仍在用)
- claude/gemini/openai adaptor 入站 switch 改为同格式路径 +
  hard-fail 守卫(矩阵意外未接管时报错而非静默错格式透传)
- openai responses.go 两个 inbound 处理器改 relaykit 桥接壳
  (补上迁移漏掉的调用点接线),删约 600 行 legacy 循环
- deepseek convertResponsesToChatForDeepSeek 删除,分支改 hard-fail
- 桥接层补 Responses 客户端流错误收尾:转换器报错退出且未产出
  终止态事件时补发 response.failed(防 codex 等客户端挂起);
  已自行收尾的(Gemini SAFETY completed 拒答)不追加第二终止事件

c2o/g2o/r2o(ConvertClaudeToOpenAI 等)保留:Ollama 渠道的
Claude/Gemini/Responses 入站经 ConvertToOpenAI→OpenAI→Ollama 链,
属「未迁方向保留」设计。

测试:新增删除项的替代断言(hard-fail 守卫、PostProcess 钩子、
response.failed 收尾);删测已删函数的用例。全仓 build/vet 通过,
relaykit 全绿,relay 侧仅剩 helper 既有失败。

* refactor(relay): 移除模型名能力后缀机制(虚拟模型语法)

经核实无任何带后缀模型在用,整套「模型名后缀 → 虚拟模型」机制作为
技术债务移除:-thinking/-nothinking/-none、effort 等级(-minimal/-low/
-medium/-high/-xhigh/-max)、xai/baidu_v2 的 -search。思考/搜索能力
此后一律由客户端请求参数表达(thinking 对象、reasoning_effort、
reasoning.effort),网关只做协议转换不做能力注入。

宿主侧:
- 删 helper/thinking.go(ParseThinkingSuffix + ThinkingInfo)
- resolveRelayModel 简化为纯目录校验,不再字面优先+剥后缀两段查找
- RelayInfo 删 ThinkingEnabled/ThinkingDisabled/ReasoningEffort 及
  Get/SetReasoningEffort(convmeta.Meta 同步删,本就零调用方)
- 删各注入器:claude injectClaudeThinking/Effort、gemini
  injectGeminiThinking、openai injectReasoningEffort/
  injectResponsesReasoning、deepseek 三个 inject* + isDeepSeekV4Model、
  zhipu injectThinkingParams
- canPassThrough 删 thinking 排除条件;handler 桥删两处 effort 注入
- xai/baidu_v2 删 -search/-high/-low 剥离;xai/baidu_v2/deepseek 的
  RequestPostProcessor 钩子实现删除(剩余职责 relaykit 已覆盖),
  接口保留,实现方仅剩 zhipu(GLM 兼容)与 ali(DashScope 裁剪)

relaykit 侧:
- 删 internal/shared/thinking_adapter.go 与 reasoning/ 包
- convmeta.Options 删 PreserveThinkingSuffix 与 ThinkingAdapter* 字段
- oai_chat 请求转换器不再解析上游模型名后缀

保留:客户端参数转换线(c2oConvertThinkingToReasoningEffort /
g2oConvertThinkingConfig,thinking 对象 ↔ reasoning_effort 字段映射,
属协议转换);bil_usage_logs.reasoning_effort 列(此后恒空);
BaseModelName 字段(responses 路由记录在用)。

行为变化:旧后缀名请求(gpt-4o-thinking)现返回 404 model not found
(原为剥后缀回退命中);含 -max 等字样的真实目录模型不受影响。

全仓 build/vet 通过,relaykit 全绿,relay 侧仅剩 helper 既有失败。

* fix(dispatch): 探测失败不再衰减健康 EWMA,修复 4 处测试失效

processOutcome 之前忽略 Outcome.Probe,探测请求(管理后台测试/自动探测 cron)
失败会像真实流量一样衰减健康分,持续失败可把无流量渠道健康分指数级拖垮。
现探测失败跳过健康 EWMA 写入(探测成功仍照常记录),熔断窗口计数不受影响。

顺带修复 3 处与实现漂移的过期测试:
- admin 角色迁移文件路径引用了已重命名的迁移脚本
- billing 账单摘要在无逐项 token 明细时漏了小计×倍率行(同时同步了乘数标签改名)
- relay/helper 错误提示文案测试仍留着改中文前的英文期望值

* refactor(relay):补充gemini和claude协议的转换

* fix(relay):修复gemini转换问题

* fix(relay):优化工具转换

* feat(test):添加测试用例

* feat(test):添加测试用例

* feat(relay):补充转换矩阵

* feat(relay):补充协议转换测试

* fix(relay):修复vertex缺陷

* feat(relay):补充协议间的工具转换

* feat(relay):添加语料采集功能

* fix(relay):修复claude输出协议转换问题

* fix(relay):修复codex请求claude模型无法使用搜索工具问题

* fix(relay):修复codex请求gemini模型无法使用搜索工具问题

* refactor(relay):修改真实语料处理逻辑,将真实响应数据移出仓库

* fix(relay):补充手写测试数据集
2026-09-12 16:08:05 +08:00

316 lines
17 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package v1
import "github.com/gogf/gf/v2/frame/g"
// ChannelListReq 渠道列表请求
type ChannelListReq struct {
g.Meta `path:"/channels" method:"get" mime:"json" tags:"管理后台-渠道" summary:"渠道列表"`
Page int `json:"page" d:"1" v:"min:1" dc:"页码"`
PageSize int `json:"page_size" d:"20" v:"min:1|max:100" dc:"每页数量"`
Type int `json:"type" dc:"供应商类型筛选"`
Status string `json:"status" dc:"状态筛选:active/disabled/testing"`
Search string `json:"search" dc:"搜索关键词(渠道名称/备注)"`
ID int64 `json:"id" dc:"按渠道 ID 精确筛选"`
Model string `json:"model" dc:"按支持的模型名筛选(模糊匹配平台模型名/上游模型名)"`
BaseURL string `json:"base_url" dc:"按 API 地址筛选(模糊匹配)"`
}
// ChannelListRes 渠道列表响应
type ChannelListRes struct {
List []ChannelItem `json:"list"`
Total int `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
}
// ChannelItem 渠道信息
type ChannelItem struct {
ID int64 `json:"id"`
Name string `json:"name"`
Type int `json:"type"`
TypeName string `json:"type_name"`
BaseURL string `json:"base_url"`
Status string `json:"status"`
Priority int `json:"priority"`
Weight int `json:"weight"`
MaxConcurrency int `json:"max_concurrency"`
Tier string `json:"tier"`
StrictCapacity bool `json:"strict_capacity"`
TestModel string `json:"test_model"`
Remark string `json:"remark"`
IsVIP bool `json:"is_vip"`
UseProxy bool `json:"use_proxy"`
SharingThreshold *float64 `json:"sharing_threshold"`
PreemptionThreshold *float64 `json:"preemption_threshold"`
BorrowingCooldownSeconds *int `json:"borrowing_cooldown_seconds"`
CreatedAt string `json:"created_at"`
HealthScore *float64 `json:"health_score"`
BreakerState int `json:"breaker_state"` // 调度熔断状态:0=正常 1=熔断中 2=半开探活
BreakerModels int `json:"breaker_models"` // 处于熔断/半开的模型数量(渠道×模型级汇总)
WorstModel string `json:"worst_model"` // 健康度最低的模型名(空=渠道不在调度目录)
WorstModelScore float64 `json:"worst_model_score"`
}
// ChannelCreateReq 创建渠道请求
type ChannelCreateReq struct {
g.Meta `path:"/channels" method:"post" mime:"json" tags:"管理后台-渠道" summary:"创建渠道"`
Name string `json:"name" v:"required|length:1,100#请输入渠道名|渠道名长度1-100" dc:"渠道名称"`
Type int `json:"type" v:"required|min:1#请选择供应商类型" dc:"供应商类型"`
BaseURL string `json:"base_url" dc:"API 基础地址(留空使用供应商默认地址)"`
ApiKey string `json:"api_key" v:"required#请输入 API Key" dc:"API Key"`
Priority int `json:"priority" d:"0" dc:"优先级"`
Weight int `json:"weight" d:"100" v:"between:0,100" dc:"权重"`
MaxConcurrency int `json:"max_concurrency" d:"100" v:"min:0" dc:"最大并发请求数(0=按上游 429 水位自动估算)"`
Tier string `json:"tier" d:"primary" v:"in:primary,secondary,reserve" dc:"调度层级:primary=首选 secondary=备用 reserve=保底"`
StrictCapacity bool `json:"strict_capacity" d:"false" dc:"严格容量:Redis 故障时实例级保守限额(fail-closed),用于高成本渠道"`
TestModel string `json:"test_model" dc:"测试模型名"`
Remark string `json:"remark" dc:"备注"`
IsVIP bool `json:"is_vip" d:"false" dc:"是否VIP专属渠道"`
UseProxy bool `json:"use_proxy" d:"false" dc:"启用代理"`
WebSearchToGoogleSearch bool `json:"web_search_to_google_search" d:"false" dc:"web_search→googleSearch 映射(Gemini 渠道,搜索按次另行计价)"`
SharingThreshold float64 `json:"sharing_threshold" d:"0.6" dc:"普通租户借用阈值"`
PreemptionThreshold float64 `json:"preemption_threshold" d:"0.8" dc:"VIP抢占阈值"`
BorrowingCooldownSeconds int `json:"borrowing_cooldown_seconds" d:"30" dc:"被抢占后冷却时间(秒)"`
}
// ChannelCreateRes 创建渠道响应
type ChannelCreateRes struct {
ID int64 `json:"id"`
}
// ChannelUpdateReq 更新渠道请求
type ChannelUpdateReq struct {
g.Meta `path:"/channels/{id}" method:"put" mime:"json" tags:"管理后台-渠道" summary:"更新渠道"`
ID int64 `json:"id" in:"path" v:"required" dc:"渠道ID"`
Name string `json:"name" dc:"渠道名称"`
Type *int `json:"type" v:"min:1#请选择供应商类型" dc:"供应商类型(留空不修改)"`
BaseURL string `json:"base_url" dc:"API 基础地址(留空不更新;切换类型且旧地址为该类型默认值时自动跟随新类型默认地址)"`
ApiKey *string `json:"api_key" dc:"更新 API Key(留空不更新)"`
Priority *int `json:"priority" dc:"优先级(留空不更新)"`
Weight *int `json:"weight" dc:"权重(留空不更新)"`
MaxConcurrency *int `json:"max_concurrency" v:"min:0" dc:"最大并发请求数(0=按上游 429 水位自动估算,留空不更新)"`
TestModel string `json:"test_model" dc:"测试模型名"`
Remark string `json:"remark" dc:"备注"`
Status string `json:"status" v:"in:active,disabled,testing" dc:"状态"`
Tier string `json:"tier" v:"in:primary,secondary,reserve" dc:"调度层级(留空不更新)"`
StrictCapacity *bool `json:"strict_capacity" dc:"严格容量(fail-closed)"`
IsVIP *bool `json:"is_vip" dc:"是否VIP专属渠道"`
UseProxy *bool `json:"use_proxy" dc:"启用代理"`
WebSearchToGoogleSearch *bool `json:"web_search_to_google_search" dc:"web_search→googleSearch 映射(Gemini 渠道,留空不更新)"`
DebugLogEnabled *bool `json:"debug_log_enabled" dc:"启用渠道调试日志(记录四段完整报文,排障用,用完及时关闭)"`
DebugLogTenantID *int64 `json:"debug_log_tenant_id" dc:"调试目标租户ID过滤(0=不限)"`
DebugLogUserID *int64 `json:"debug_log_user_id" dc:"调试目标成员ID过滤(0=不限)"`
DebugLogApiKeyID *int64 `json:"debug_log_api_key_id" dc:"调试目标密钥ID过滤(0=不限)"`
SharingThreshold *float64 `json:"sharing_threshold" dc:"普通租户借用阈值"`
PreemptionThreshold *float64 `json:"preemption_threshold" dc:"VIP抢占阈值"`
BorrowingCooldownSeconds *int `json:"borrowing_cooldown_seconds" dc:"被抢占后冷却时间(秒)"`
}
// ChannelDeleteReq 删除渠道请求
type ChannelDeleteReq struct {
g.Meta `path:"/channels/{id}" method:"delete" mime:"json" tags:"管理后台-渠道" summary:"删除渠道"`
ID int64 `json:"id" in:"path" v:"required" dc:"渠道ID"`
}
// ChannelDetailReq 渠道详情请求
type ChannelDetailReq struct {
g.Meta `path:"/channels/{id}" method:"get" mime:"json" tags:"管理后台-渠道" summary:"渠道详情"`
ID int64 `json:"id" in:"path" v:"required" dc:"渠道ID"`
}
// ChannelDetailRes 渠道详情响应
type ChannelDetailRes struct {
ID int64 `json:"id"`
Name string `json:"name"`
Type int `json:"type"`
TypeName string `json:"type_name"`
BaseURL string `json:"base_url"`
Status string `json:"status"`
Priority int `json:"priority"`
Weight int `json:"weight"`
MaxConcurrency int `json:"max_concurrency"`
Tier string `json:"tier"`
StrictCapacity bool `json:"strict_capacity"`
TestModel string `json:"test_model"`
Remark string `json:"remark"`
IsVIP bool `json:"is_vip"`
UseProxy bool `json:"use_proxy"`
WebSearchToGoogleSearch bool `json:"web_search_to_google_search"`
DebugLogEnabled bool `json:"debug_log_enabled"`
DebugLogTenantID int64 `json:"debug_log_tenant_id"`
DebugLogUserID int64 `json:"debug_log_user_id"`
DebugLogApiKeyID int64 `json:"debug_log_api_key_id"`
SharingThreshold *float64 `json:"sharing_threshold"`
PreemptionThreshold *float64 `json:"preemption_threshold"`
BorrowingCooldownSeconds *int `json:"borrowing_cooldown_seconds"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
HealthScore *float64 `json:"health_score"`
BreakerState int `json:"breaker_state"` // 调度熔断状态:0=正常 1=熔断中 2=半开探活
BreakerModels int `json:"breaker_models"` // 处于熔断/半开的模型数量(渠道×模型级汇总)
WorstModel string `json:"worst_model"` // 健康度最低的模型名(空=渠道不在调度目录)
WorstModelScore float64 `json:"worst_model_score"`
KeyType string `json:"key_type"`
KeyStatus string `json:"key_status"`
KeyName string `json:"key_name"`
// KeyCooldownRemaining 凭证冷却剩余秒数(0=未冷却)。冷却期内该 Key 会被调度器
// 整段跳过且不发起上游请求,渠道全部 Key 冷却即整体不可用——必须可见,否则
// 表现为"健康分正常、探测也正常,但就是无可用渠道"。
KeyCooldownRemaining int `json:"key_cooldown_remaining"`
TokenExpiresAt string `json:"token_expires_at"`
}
// ChannelKeyCreateReq 添加渠道 Key 请求
type ChannelKeyCreateReq struct {
g.Meta `path:"/channels/{channel_id}/keys" method:"post" mime:"json" tags:"管理后台-渠道" summary:"添加渠道 Key"`
ChannelID int64 `json:"channel_id" in:"path" v:"required" dc:"渠道ID"`
Name string `json:"name" dc:"Key 别名"`
ApiKey string `json:"api_key" v:"required" dc:"API Key 原值"`
}
// ChannelKeyDeleteReq 删除渠道 Key 请求
type ChannelKeyDeleteReq struct {
g.Meta `path:"/channels/{channel_id}/keys/{key_id}" method:"delete" mime:"json" tags:"管理后台-渠道" summary:"删除渠道 Key"`
ChannelID int64 `json:"channel_id" in:"path" v:"required" dc:"渠道ID"`
KeyID int64 `json:"key_id" in:"path" v:"required" dc:"Key ID"`
}
// ChannelKeyDeleteRes 删除渠道 Key 响应
type ChannelKeyDeleteRes struct{}
// ChannelKeyCreateRes 添加渠道 Key 响应
type ChannelKeyCreateRes struct {
ID int64 `json:"id"`
}
// ChannelAbilityBatchReq 批量设置渠道模型能力
type ChannelAbilityBatchReq struct {
g.Meta `path:"/channels/{channel_id}/abilities" method:"put" mime:"json" tags:"管理后台-渠道" summary:"设置渠道模型能力"`
ChannelID int64 `json:"channel_id" in:"path" v:"required" dc:"渠道ID"`
Abilities []AbilityItem `json:"abilities" dc:"能力列表"`
}
// AbilityItem 模型能力项
type AbilityItem struct {
ID int64 `json:"id"`
ModelName string `json:"model_name" v:"required" dc:"平台标准模型名"`
UpstreamModel string `json:"upstream_model" dc:"上游实际模型名"`
Enabled bool `json:"enabled" d:"true" dc:"是否启用"`
CostRatio float64 `json:"cost_ratio" d:"1" v:"between:0,100" dc:"成本比例:上游实际价/平台基准价,1.0=等价(参与调度 costFactor)"`
SupportsResponses bool `json:"supports_responses" d:"false" dc:"支持 OpenAI Responses 协议(/v1/responses 原生直连,responses 入站软偏好)"`
ChatViaResponses bool `json:"chat_via_responses" d:"false" dc:"上游仅有 Responses 协议(responses-only),chat 入站经桥接发送 /v1/responses"`
// 模型级健康状态(从 Redis 实时读取)
HealthScore *float64 `json:"health_score" dc:"健康分 0-100,null=无数据"`
SuccEWMA *float64 `json:"succ_ewma" dc:"成功率 EWMA 0-1"`
LatencyEWMA *float64 `json:"latency_ewma" dc:"延迟 EWMA (ms)"`
BreakerState *int `json:"breaker_state" dc:"熔断状态:0=正常 1=熔断 2=半开,null=无熔断器"`
}
// ProviderDefaultURLReq 获取供应商默认 URL
type ProviderDefaultURLReq struct {
g.Meta `path:"/channels/provider-default-urls" method:"get" mime:"json" tags:"管理后台-渠道" summary:"供应商默认地址"`
}
type ProviderDefaultURLRes struct {
URLs map[int]string `json:"urls"`
}
// ChannelUpdateRes 更新渠道响应
type ChannelUpdateRes struct{}
// ChannelDeleteRes 删除渠道响应
type ChannelDeleteRes struct{}
// ChannelAbilityBatchRes 批量设置渠道模型能力响应
type ChannelAbilityBatchRes struct{}
// ChannelKeyListReq 渠道 Key 列表请求
type ChannelKeyListReq struct {
g.Meta `path:"/channels/{channel_id}/keys" method:"get" mime:"json" tags:"管理后台-渠道" summary:"渠道Key列表"`
ChannelID int64 `json:"channel_id" in:"path" v:"required" dc:"渠道ID"`
}
type ChannelKeyListRes struct {
List []ChannelKeyItem `json:"list"`
}
// ChannelKeyItem 渠道 Key 信息
type ChannelKeyItem struct {
ID int64 `json:"id"`
Name string `json:"name"`
ApiKey string `json:"api_key"`
Status string `json:"status"`
KeyType string `json:"key_type"`
TokenExpiresAt string `json:"token_expires_at"`
CreatedAt string `json:"created_at"`
// CooldownRemaining 凭证冷却剩余秒数(0=未冷却),含义见 ChannelDetailRes.KeyCooldownRemaining
CooldownRemaining int `json:"cooldown_remaining"`
}
// ChannelAbilitiesGetReq 获取渠道模型能力请求
type ChannelAbilitiesGetReq struct {
g.Meta `path:"/channels/{channel_id}/abilities" method:"get" mime:"json" tags:"管理后台-渠道" summary:"获取渠道模型能力"`
ChannelID int64 `json:"channel_id" in:"path" v:"required" dc:"渠道ID"`
}
type ChannelAbilitiesGetRes struct {
List []AbilityItem `json:"list"`
}
// ChannelHealthTrendReq 渠道健康趋势请求
type ChannelHealthTrendReq struct {
g.Meta `path:"/channels/{id}/health_trend" method:"get" mime:"json" tags:"管理后台-渠道" summary:"渠道健康趋势"`
ID int64 `json:"id" in:"path" v:"required" dc:"渠道ID"`
Hours int `json:"hours" d:"24" v:"between:1,168" dc:"查询时长(小时)"`
}
// ChannelHealthTrendRes 渠道健康趋势响应
type ChannelHealthTrendRes struct {
Points []HealthTrendPoint `json:"points"`
}
// HealthTrendPoint 健康趋势数据点(健康度、延迟取整展示,不含小数)
type HealthTrendPoint struct {
SnapshotAt string `json:"snapshot_at"`
HealthScore int64 `json:"health_score"`
SuccessRate float64 `json:"success_rate"`
LatencyMs int64 `json:"latency_ms"`
}
// ChannelExportReq 导出渠道列表请求
type ChannelExportReq struct {
g.Meta `path:"/channels/export" method:"get" mime:"json" tags:"管理后台-渠道" summary:"导出渠道列表"`
Format string `json:"format" in:"query" d:"csv" v:"in:csv,xlsx" dc:"导出格式:csv / xlsx"`
Type int `json:"type" in:"query" dc:"供应商类型筛选"`
Status string `json:"status" in:"query" dc:"状态筛选:active/disabled/testing"`
Search string `json:"search" in:"query" dc:"搜索关键词(渠道名称/备注)"`
ID int64 `json:"id" in:"query" dc:"按渠道 ID 精确筛选"`
Model string `json:"model" in:"query" dc:"按支持的模型名筛选(模糊匹配平台模型名/上游模型名)"`
BaseURL string `json:"base_url" in:"query" dc:"按 API 地址筛选(模糊匹配)"`
}
type ChannelExportRes struct{}
// ChannelCloneReq 克隆渠道请求
type ChannelCloneReq struct {
g.Meta `path:"/channels/{id}/clone" method:"post" mime:"json" tags:"管理后台-渠道" summary:"克隆渠道"`
ID int64 `json:"id" in:"path" v:"required" dc:"源渠道ID"`
Name string `json:"name" dc:"新渠道名称(留空使用默认)"`
ApiKey string `json:"api_key" v:"required#请输入新渠道的 API Key" dc:"新渠道的 API Key"`
}
// ChannelCloneRes 克隆渠道响应
type ChannelCloneRes struct {
ID int64 `json:"id"`
}
// ChannelResetHealthReq 重置渠道健康度请求
type ChannelResetHealthReq struct {
g.Meta `path:"/channels/{id}/reset-health" method:"post" mime:"json" tags:"管理后台-渠道" summary:"重置渠道健康度"`
ID int64 `json:"id" in:"path" v:"required" dc:"渠道ID"`
ModelName string `json:"model_name" dc:"可选:指定模型名则只重置该模型的健康度,留空则重置整个渠道(含所有模型)"`
}
// ChannelResetHealthRes 重置渠道健康度响应
type ChannelResetHealthRes struct{}