channel_monitor_const.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. package service
  2. import (
  3. "time"
  4. infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
  5. )
  6. // ChannelMonitor 全局常量。
  7. // 这些是 MVP 阶段的硬编码值,按需可以提到 config 中。
  8. const (
  9. // monitorRequestTimeout 单次模型请求总超时(含 Body 读取)。
  10. monitorRequestTimeout = 45 * time.Second
  11. // monitorPingTimeout HEAD 请求 endpoint origin 的超时。
  12. monitorPingTimeout = 8 * time.Second
  13. // monitorDegradedThreshold 主请求成功但耗时超过该阈值视为 degraded。
  14. monitorDegradedThreshold = 6 * time.Second
  15. // monitorHistoryRetentionDays 明细历史保留天数。
  16. // 60s 默认间隔 * 30 天 ≈ 43200 行/monitor/model,一般部署总量 <= 2M 行,
  17. // PG 无压力;所以直接保留完整明细一个月,可用率查询可以全走原始行不依赖聚合。
  18. // 聚合表 channel_monitor_daily_rollups 仍然保留,作为长期历史回填/降级查询的兜底。
  19. monitorHistoryRetentionDays = 30
  20. // monitorRollupRetentionDays 日聚合保留天数。
  21. // 日聚合行由 RunDailyMaintenance 在超过该窗口后软删。
  22. monitorRollupRetentionDays = 30
  23. // monitorMaintenanceMaxDaysPerRun 单次维护任务最多聚合的天数。
  24. // 用于限制首次上线回填(30 天)+ 少量余量,避免长事务。
  25. monitorMaintenanceMaxDaysPerRun = 35
  26. // monitorWorkerConcurrency 调度器并发执行的监控数(pond 池容量)。
  27. monitorWorkerConcurrency = 5
  28. // monitorStartupLoadTimeout Start 时一次性加载所有 enabled monitor 的总超时。
  29. monitorStartupLoadTimeout = 10 * time.Second
  30. // monitorMinIntervalSeconds / monitorMaxIntervalSeconds 用户配置的检测间隔上下限。
  31. monitorMinIntervalSeconds = 15
  32. monitorMaxIntervalSeconds = 3600
  33. // monitorMessageMaxBytes message 字段最大字节数(与 schema/migration 一致)。
  34. monitorMessageMaxBytes = 500
  35. // monitorResponseMaxBytes 单次模型响应最大读取字节,防止 OOM。
  36. monitorResponseMaxBytes = 64 * 1024
  37. // monitorErrorBodySnippetMaxBytes 非 2xx 响应时保留上游 body 片段的最大字节数。
  38. // 留 300 字节足够覆盖典型结构化错误(如 `{"error":{"message":"..."}}`),
  39. // 又给 "upstream HTTP <status>: " 前缀留出余量,避免最终被 monitorMessageMaxBytes (500) 截得太狠。
  40. monitorErrorBodySnippetMaxBytes = 300
  41. // monitorChallengeMin / monitorChallengeMax challenge 操作数范围。
  42. monitorChallengeMin = 1
  43. monitorChallengeMax = 50
  44. // providerOpenAIPath OpenAI Chat Completions 路径(Kimi / DeepSeek 同为 OpenAI 兼容)。
  45. providerOpenAIPath = "/v1/chat/completions"
  46. // providerGrokPath Grok OpenAI-compatible Chat Completions 路径。
  47. providerGrokPath = "/v1/chat/completions"
  48. // providerZhipuPath 智谱 OpenAI 兼容 Chat Completions 路径(前缀与官方不同)。
  49. providerZhipuPath = "/api/paas/v4/chat/completions"
  50. // providerOpenAIResponsesPath OpenAI Responses API 路径。
  51. providerOpenAIResponsesPath = "/v1/responses"
  52. // providerAnthropicPath Anthropic Messages 路径。
  53. providerAnthropicPath = "/v1/messages"
  54. // providerGeminiPathTemplate Gemini generateContent 路径模板(含 model 占位)。
  55. providerGeminiPathTemplate = "/v1beta/models/%s:generateContent"
  56. // MonitorProviderOpenAI 等 provider 字符串常量(也是 ent enum 的实际值)。
  57. // 后 4 个 provider(antigravity/kimi/zhipu/deepseek)为配额模式引入:
  58. // antigravity 无探活 adapter(仅配额),其余 3 个复用 OpenAI 兼容探活。
  59. MonitorProviderOpenAI = "openai"
  60. MonitorProviderAnthropic = "anthropic"
  61. MonitorProviderGemini = "gemini"
  62. MonitorProviderGrok = "grok"
  63. MonitorProviderAntigravity = "antigravity"
  64. MonitorProviderKimi = "kimi"
  65. MonitorProviderZhipu = "zhipu"
  66. MonitorProviderDeepseek = "deepseek"
  67. MonitorProviderMiniMax = "minimax"
  68. // MonitorCheckMode 检测模式(channel_monitors.check_mode)。
  69. // probe - LLM 探活(默认,原有行为)
  70. // quota - 仅查关联账号用量/余额,零 LLM 成本
  71. // quota_probe - 探活 + 配额并存(配额快照挂到主模型历史行)
  72. MonitorCheckModeProbe = "probe"
  73. MonitorCheckModeQuota = "quota"
  74. MonitorCheckModeQuotaProbe = "quota_probe"
  75. // MonitorDefaultQuotaModel 是 quota 模式监控未显式指定模型时占位的虚拟模型名
  76. // (primary_model 列 NotEmpty,用 "quota" 让历史行/时间线机制无需特判)。
  77. MonitorDefaultQuotaModel = "quota"
  78. // monitorQuotaFetchCacheTTL 配额快照缓存时长。多个监控可能关联同一账号,
  79. // 而 interval 最小 15s 且国产配额服务无缓存,TTL 防止打爆上游配额端点。
  80. monitorQuotaFetchCacheTTL = 5 * time.Minute
  81. // monitorQuotaErrorCacheTTL 失败快照的负缓存时长:失败也短缓存,避免
  82. // 故障/凭据失效期间每次调度(最小 15s)都带真实凭据打上游;到期自动重试。
  83. monitorQuotaErrorCacheTTL = 60 * time.Second
  84. // monitorQuotaFetchTimeout singleflight 内单次配额抓取的总超时
  85. // (脱离调用方 ctx,防止某个监控的取消波及共享同一账号的其他监控)。
  86. monitorQuotaFetchTimeout = 45 * time.Second
  87. // monitorQuotaDegradedUsedPercent 任一用量窗口使用率超过该阈值时,
  88. // 配额检查状态记为 degraded(对齐账号页展示阈值)。
  89. monitorQuotaDegradedUsedPercent = 90.0
  90. // MonitorDefaultGrokModel 是新增 Grok 监控未显式指定模型时使用的轻量测活模型。
  91. MonitorDefaultGrokModel = "grok-4.5"
  92. // MonitorStatusOperational 等监控状态字符串常量(与 ent enum 一致)。
  93. MonitorStatusOperational = "operational"
  94. MonitorStatusDegraded = "degraded"
  95. MonitorStatusFailed = "failed"
  96. MonitorStatusError = "error"
  97. // monitorAvailability7Days / 15 / 30 用于聚合查询窗口。
  98. monitorAvailability7Days = 7
  99. monitorAvailability15Days = 15
  100. monitorAvailability30Days = 30
  101. // MonitorHistoryDefaultLimit 历史查询默认返回条数(handler 层共享)。
  102. MonitorHistoryDefaultLimit = 100
  103. // MonitorHistoryMaxLimit 历史查询最大返回条数(handler 层共享)。
  104. MonitorHistoryMaxLimit = 1000
  105. // monitorTimelineMaxPoints 用户视图 timeline 每个监控最多返回的历史点数。
  106. monitorTimelineMaxPoints = 60
  107. // monitorEndpointResolveTimeout validateEndpoint 解析 hostname 的最长耗时。
  108. monitorEndpointResolveTimeout = 5 * time.Second
  109. // ---- checker / runner 行为参数(消除 magic 值)----
  110. // monitorAnthropicAPIVersion Anthropic Messages API 版本头。
  111. monitorAnthropicAPIVersion = "2023-06-01"
  112. // monitorChallengeMaxTokens 单次 challenge 请求的 max_tokens(足够回答个位数算术)。
  113. monitorChallengeMaxTokens = 50
  114. // monitorRunOneBuffer runOne 的总超时缓冲(除请求超时与 ping 超时外的额外裕量)。
  115. monitorRunOneBuffer = 10 * time.Second
  116. // monitorIdleConnTimeout HTTP transport 空闲连接关闭超时。
  117. monitorIdleConnTimeout = 30 * time.Second
  118. // monitorTLSHandshakeTimeout HTTP transport TLS 握手超时。
  119. monitorTLSHandshakeTimeout = 10 * time.Second
  120. // monitorResponseHeaderTimeout HTTP transport 等待响应头超时。
  121. monitorResponseHeaderTimeout = 30 * time.Second
  122. // monitorPingDiscardMaxBytes ping 时丢弃响应体的最大字节数。
  123. monitorPingDiscardMaxBytes = 1024
  124. // monitorDialTimeout 自定义 dialer 单次连接超时。
  125. monitorDialTimeout = 10 * time.Second
  126. // monitorDialKeepAlive 自定义 dialer keep-alive 间隔。
  127. monitorDialKeepAlive = 30 * time.Second
  128. )
  129. // 业务错误(统一在此声明,避免散落)。
  130. var (
  131. ErrChannelMonitorNotFound = infraerrors.NotFound(
  132. "CHANNEL_MONITOR_NOT_FOUND", "channel monitor not found",
  133. )
  134. ErrChannelMonitorInvalidProvider = infraerrors.BadRequest(
  135. "CHANNEL_MONITOR_INVALID_PROVIDER", "provider must be one of openai/anthropic/gemini/grok/antigravity/kimi/zhipu/deepseek/minimax",
  136. )
  137. ErrChannelMonitorInvalidCheckMode = infraerrors.BadRequest(
  138. "CHANNEL_MONITOR_INVALID_CHECK_MODE", "check_mode must be one of probe/quota/quota_probe; antigravity only supports quota",
  139. )
  140. ErrChannelMonitorAccountRequired = infraerrors.BadRequest(
  141. "CHANNEL_MONITOR_ACCOUNT_REQUIRED", "account_id is required for quota-based check_mode",
  142. )
  143. ErrChannelMonitorProviderIncompatible = infraerrors.BadRequest(
  144. "CHANNEL_MONITOR_PROVIDER_INCOMPATIBLE", "monitor provider must match the linked account platform",
  145. )
  146. ErrChannelMonitorAccountNotSupportable = infraerrors.BadRequest(
  147. "CHANNEL_MONITOR_ACCOUNT_NOT_SUPPORTABLE", "linked account cannot serve as a quota data source (cn coding plan must be kimi/zhipu/minimax, cn payg must be kimi/deepseek, openai requires an oauth account, anthropic requires oauth or setup-token)",
  148. )
  149. ErrChannelMonitorInvalidAPIMode = infraerrors.BadRequest(
  150. "CHANNEL_MONITOR_INVALID_API_MODE", "api_mode must be chat_completions or responses; responses is only supported for openai",
  151. )
  152. ErrChannelMonitorInvalidRequestBody = infraerrors.BadRequest(
  153. "CHANNEL_MONITOR_INVALID_REQUEST_BODY", "openai-compatible replace-mode body_override must include non-empty messages for chat_completions or non-empty instructions and input for responses",
  154. )
  155. ErrChannelMonitorInvalidInterval = infraerrors.BadRequest(
  156. "CHANNEL_MONITOR_INVALID_INTERVAL", "interval_seconds must be in [15, 3600]",
  157. )
  158. ErrChannelMonitorInvalidJitter = infraerrors.BadRequest(
  159. "CHANNEL_MONITOR_INVALID_JITTER", "jitter_seconds must be >= 0 and interval_seconds - jitter_seconds must be >= 15",
  160. )
  161. ErrChannelMonitorInvalidEndpoint = infraerrors.BadRequest(
  162. "CHANNEL_MONITOR_INVALID_ENDPOINT", "endpoint must be a valid https URL",
  163. )
  164. ErrChannelMonitorEndpointScheme = infraerrors.BadRequest(
  165. "CHANNEL_MONITOR_ENDPOINT_SCHEME", "endpoint must use https scheme",
  166. )
  167. ErrChannelMonitorEndpointPath = infraerrors.BadRequest(
  168. "CHANNEL_MONITOR_ENDPOINT_PATH", "endpoint must be base origin only (no path/query/fragment)",
  169. )
  170. ErrChannelMonitorEndpointPrivate = infraerrors.BadRequest(
  171. "CHANNEL_MONITOR_ENDPOINT_PRIVATE", "endpoint must be a public host",
  172. )
  173. ErrChannelMonitorEndpointUnreachable = infraerrors.BadRequest(
  174. "CHANNEL_MONITOR_ENDPOINT_UNREACHABLE", "endpoint hostname could not be resolved",
  175. )
  176. ErrChannelMonitorMissingAPIKey = infraerrors.BadRequest(
  177. "CHANNEL_MONITOR_MISSING_API_KEY", "api_key is required when creating a monitor",
  178. )
  179. ErrChannelMonitorMissingPrimaryModel = infraerrors.BadRequest(
  180. "CHANNEL_MONITOR_MISSING_PRIMARY_MODEL", "primary_model is required",
  181. )
  182. ErrChannelMonitorAPIKeyDecryptFailed = infraerrors.InternalServer(
  183. "CHANNEL_MONITOR_KEY_DECRYPT_FAILED", "api key decryption failed; please re-edit the monitor with a fresh key",
  184. )
  185. )
  186. var (
  187. ErrChannelMonitorDisabled = infraerrors.Forbidden(
  188. "CHANNEL_MONITOR_DISABLED",
  189. "channel monitor feature is disabled",
  190. )
  191. ErrChannelMonitorActiveProbesRetired = infraerrors.Forbidden(
  192. "CHANNEL_MONITOR_ACTIVE_PROBES_RETIRED",
  193. "channel monitor active probes are retired in v2 mode",
  194. )
  195. ErrChannelMonitorModeMismatch = infraerrors.Forbidden(
  196. "CHANNEL_MONITOR_MODE_MISMATCH",
  197. "channel monitor mode does not allow this operation",
  198. )
  199. )