* 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):补充手写测试数据集
316 lines
17 KiB
Go
316 lines
17 KiB
Go
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{}
|