287 lines
11 KiB
Go
287 lines
11 KiB
Go
package common
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"time"
|
||
)
|
||
|
||
// ErrChannelUnavailable 没有可用渠道
|
||
var ErrChannelUnavailable = errors.New("no available channel")
|
||
|
||
// ErrModelNotFound 模型不存在
|
||
var ErrModelNotFound = errors.New("model not found")
|
||
|
||
// ErrStreamInterrupted 流式响应被客户端中断
|
||
var ErrStreamInterrupted = errors.New("stream interrupted by client disconnect")
|
||
|
||
// ErrTenantModelNotEnabled 租户未启用该模型
|
||
var ErrTenantModelNotEnabled = errors.New("model not enabled for this tenant")
|
||
|
||
// ErrMemberModelNotAllowed 成员无权使用该模型
|
||
var ErrMemberModelNotAllowed = errors.New("model not allowed for this member")
|
||
|
||
// ErrApiKeyModelNotAllowed API Key 无权使用该模型
|
||
var ErrApiKeyModelNotAllowed = errors.New("model not allowed for this API key")
|
||
|
||
// DeprecationInfo 模型弃用信息
|
||
type DeprecationInfo struct {
|
||
Deprecated bool
|
||
SunsetDate string // 格式: "2006-01-02",空表示未设置
|
||
ReplacementModel string
|
||
}
|
||
|
||
// DataProvider 是 relay 层访问业务数据的接口。
|
||
// 实现在 internal/logic/relay/provider.go 中,使用 GoFrame ORM。
|
||
// relay/ 层通过此接口获取数据,不直接依赖 GoFrame。
|
||
//
|
||
// 渠道调度(选号/亲和/健康/容量)已由 relaykit/dispatch 调度引擎统一承担
|
||
// (Route/Next/Report/Finish),本接口只保留数据访问与选择物化。
|
||
type DataProvider interface {
|
||
// ValidateApiKey 验证 API Key 并返回认证信息。
|
||
// rawKey 是完整的 API Key 原值(如 sk-a1b2c3d4e5f6...)。
|
||
ValidateApiKey(ctx context.Context, rawKey string) (*ApiKeyInfo, error)
|
||
|
||
// GetModelMapping 获取模型映射信息。
|
||
// 返回标准模型名和分类(chat/embedding/image 等)。
|
||
GetModelMapping(ctx context.Context, modelName string) (standardName string, category string, err error)
|
||
|
||
// RecordUsage 异步记录 API 调用用量日志。
|
||
RecordUsage(ctx context.Context, record *UsageRecord)
|
||
|
||
// RecordAudit 异步记录请求审计日志。
|
||
RecordAudit(ctx context.Context, record *AuditRecord)
|
||
|
||
// UpdateTaskAudit 更新异步任务的审计记录(任务完成时调用)。
|
||
// 通过 task_id 查找提交阶段写入的审计记录,补充最终结果。
|
||
UpdateTaskAudit(ctx context.Context, record *AuditRecord)
|
||
|
||
// GetAvailableModels 获取指定租户可用的模型列表。
|
||
// apiKeyID > 0 时进一步按 API Key 的模型范围过滤。
|
||
// userID > 0 时进一步按成员的模型范围过滤。
|
||
GetAvailableModels(ctx context.Context, tenantID, apiKeyID, userID int64) ([]ModelInfo, error)
|
||
|
||
// GetModelDetail 获取单个模型的详细信息。
|
||
// tenantID 用于权限校验(检查租户是否有权使用该模型)。
|
||
// apiKeyID / userID 大于 0 时进一步校验 API Key 与成员的模型范围(与 GetAvailableModels 口径一致)。
|
||
GetModelDetail(ctx context.Context, tenantID, apiKeyID, userID int64, modelName string) (*ModelDetail, error)
|
||
|
||
// CheckTenantModelAccess 检查租户是否有权使用指定模型。
|
||
// 返回是否启用和渠道范围(nil/空表示不限渠道)。
|
||
CheckTenantModelAccess(ctx context.Context, tenantID int64, modelName string) (enabled bool, channelScope []int64, err error)
|
||
|
||
// GetModelDeprecationInfo 获取模型弃用信息。
|
||
// 返回 nil 表示模型未弃用。
|
||
GetModelDeprecationInfo(ctx context.Context, modelName string) (*DeprecationInfo, error)
|
||
|
||
// InvalidateModelCache 清除指定模型的缓存。
|
||
InvalidateModelCache(modelName string)
|
||
|
||
// CheckMemberModelAccess 检查成员是否有权使用指定模型。
|
||
// 空 scope(无记录)表示不限制。
|
||
CheckMemberModelAccess(ctx context.Context, tenantID, userID int64, modelName string) (bool, error)
|
||
|
||
// CheckApiKeyModelAccess 检查 API Key 是否有权使用指定模型。
|
||
// 无 scope 记录表示不限制(向后兼容)。
|
||
CheckApiKeyModelAccess(ctx context.Context, apiKeyID int64, modelName string) (bool, error)
|
||
|
||
// InvalidateMemberModelCache 清除指定成员的模型范围缓存。
|
||
InvalidateMemberModelCache(ctx context.Context, tenantID, userID int64)
|
||
|
||
// InvalidateApiKeyModelCache 清除指定 API Key 的模型权限缓存。
|
||
InvalidateApiKeyModelCache(ctx context.Context, apiKeyID int64)
|
||
|
||
// MaterializeSelection 由新调度引擎的决策构造 ChannelSelection:
|
||
// 从目录快照取转发元数据,按 keyID 解密渠道 Key(keyID=0 时取渠道首个 active Key)。
|
||
MaterializeSelection(ctx context.Context, channelID, keyID int64, modelName string) (*ChannelSelection, error)
|
||
}
|
||
|
||
// ApiKeyInfo API Key 验证结果
|
||
type ApiKeyInfo struct {
|
||
ID int64
|
||
TenantID int64
|
||
UserID int64
|
||
ProjectID int64 // 项目密钥关联的项目 ID,个人密钥为 0
|
||
Scope string // full / chat_only / embeddings_only / images_only / read_only / custom
|
||
Status string // active / disabled / expired
|
||
RateLimitQps int // 0 表示使用全局默认
|
||
RateLimitConcurrency int // 0 表示不限制
|
||
IpWhitelist string // 逗号分隔 IP/CIDR 白名单
|
||
TotalQuota float64 // 0 表示不限制
|
||
UsedQuota float64
|
||
}
|
||
|
||
// ChannelSelection 渠道选择结果
|
||
type ChannelSelection struct {
|
||
ChannelID int64
|
||
ChannelType int
|
||
ChannelName string
|
||
BaseURL string
|
||
ApiKey string // 解密后的上游 API Key
|
||
KeyID int64 // 渠道 Key 记录 ID(凭证级归因;0=未知)
|
||
UpstreamModelName string
|
||
IsModelMapped bool
|
||
MaxConcurrency int // 该渠道最大并发(0/负值表示不限),供各转发入口做容量控制
|
||
Settings ChannelSettings
|
||
// 协议能力(chn_abilities 渠道×模型级):
|
||
// SupportsResponses:responses 入站原样直连 /v1/responses;
|
||
// ChatViaResponses:responses-only 上游,chat 入站经桥接发送 /v1/responses。
|
||
SupportsResponses bool
|
||
ChatViaResponses bool
|
||
SelectionReason string // bind / hrw / overflow / probe / cred_rotate
|
||
Priority int
|
||
Weight int
|
||
HealthScore float64
|
||
}
|
||
|
||
// UsageRecord API 调用用量记录
|
||
type UsageRecord struct {
|
||
TenantID int64
|
||
UserID int64
|
||
ApiKeyID int64
|
||
ProjectID int64 // 通过 API Key 关联的项目 ID
|
||
ChannelID int64
|
||
ModelName string
|
||
RelayMode int
|
||
PromptTokens int
|
||
CompletionTokens int
|
||
TotalTokens int
|
||
CachedTokens int // 缓存读取 token 数
|
||
AudioTokens int // 音频 token 数(输入+输出)
|
||
ImageTokens int // 图像 token 数
|
||
ReasoningTokens int // 推理 token 数
|
||
LatencyMs float64
|
||
IsStream bool
|
||
Success bool
|
||
RequestID string
|
||
Status string // success / error / timeout / cancelled
|
||
ErrorMessage string
|
||
|
||
// Cache token 细分
|
||
CacheCreationTokens int // 写入缓存的 token(Claude cache_creation / OpenAI Responses cache_write)
|
||
CacheCreation5mTokens int // Claude 5分钟缓存创建
|
||
CacheCreation1hTokens int // Claude 1小时缓存创建
|
||
CacheReadTokens int // 缓存读取 token
|
||
|
||
// 音频 token 分离
|
||
AudioInputTokens int
|
||
AudioOutputTokens int
|
||
|
||
// 其他 token
|
||
ImageOutputTokens int // 图像输出 token(DALL-E)
|
||
|
||
// 按秒计费任务的视频时长(秒),来自任务提交时的 spec.duration;非时长类任务为 0
|
||
DurationSeconds int
|
||
|
||
// 请求元数据
|
||
RequestedModel string // 用户请求的模型名
|
||
UpstreamModel string // 上游实际模型名
|
||
RequestType int // 1=sync, 2=stream, 3=async, 4=websocket
|
||
UserAgent string
|
||
ClientIP string
|
||
FirstTokenMs int
|
||
ServiceTier string
|
||
ReasoningEffort string
|
||
InboundEndpoint string // 客户端请求路径
|
||
UpstreamEndpoint string // 上游实际请求路径
|
||
|
||
// 上游请求 ID:对话=上游响应头中的请求 ID(ExtractUpstreamRequestID 提取);
|
||
// 任务=上游提交响应体中的任务 ID(tsk_model_tasks.private_data 的 upstream_task_id)。
|
||
// 排障时凭此 ID 在上游日志中定位同一次调用;上游未返回时为空。
|
||
UpstreamRequestID string
|
||
|
||
// 渠道详情
|
||
ChannelName string
|
||
ChannelType int
|
||
|
||
// 计费元数据
|
||
BillingMode string
|
||
BillingSource string
|
||
RateMultiplier float64
|
||
RetryIndex int
|
||
|
||
// 流式追踪
|
||
StreamEndReason string
|
||
|
||
// 图像生成
|
||
ImageCount int
|
||
ImageSize string
|
||
|
||
// 费用数据(由 relay handler 从结算结果填充)
|
||
InputCost float64
|
||
OutputCost float64
|
||
CacheCreationCost float64
|
||
CacheReadCost float64
|
||
TotalCost float64
|
||
ActualCost float64
|
||
Currency string
|
||
PreDeductAmount float64
|
||
RefundAmount float64
|
||
SupplementAmount float64
|
||
|
||
// 计费快照
|
||
BillingSnapshot string // JSON 字符串
|
||
BillingSummary string // 人类可读中文文本
|
||
|
||
// 异步任务关联
|
||
TaskID string // 异步任务公开ID(task_xxxxx),普通请求为空
|
||
|
||
}
|
||
|
||
// AuditRecord 请求审计日志记录
|
||
type AuditRecord struct {
|
||
TenantID int64
|
||
UserID int64
|
||
ApiKeyID int64
|
||
ProjectID int64 // 通过 API Key 关联的项目 ID
|
||
RequestID string
|
||
Method string
|
||
Path string
|
||
QueryParams string
|
||
StatusCode int
|
||
ClientIP string
|
||
UserAgent string
|
||
Model string // 请求使用的模型名称
|
||
RequestBody string
|
||
ResponseBody string
|
||
LatencyMs int
|
||
FirstTokenMs int
|
||
IsStream bool
|
||
RequestHeaders map[string]string // 请求头(仅审计级别为 all 时记录)
|
||
ResponseHeaders map[string]string // 响应头(仅审计级别为 all 时记录)
|
||
ForwardingTrace *ForwardingTrace // 转发路径追踪(仅管理员可见)
|
||
|
||
// 异步任务完成字段(仅 UpdateTaskAudit 时填充)
|
||
TaskID string // 异步任务公开ID,关联 tsk_model_tasks.public_task_id
|
||
TaskStatus string // SUCCESS / FAILURE
|
||
TaskResult string // 上游最终响应体
|
||
TaskUpstreamHeaders map[string]string // 上游最终轮询的响应头
|
||
TaskCompletedAt *time.Time // 任务到达终态的时间
|
||
}
|
||
|
||
// ModelInfo 模型信息(用于 /v1/models 端点)
|
||
type ModelInfo struct {
|
||
ModelId string `json:"model_id"`
|
||
ModelName string `json:"model_name"`
|
||
Category string `json:"category"`
|
||
Status string `json:"status"`
|
||
MaxContextTokens int `json:"max_context_tokens"`
|
||
MaxOutputTokens int `json:"max_output_tokens"`
|
||
Capabilities map[string]bool `json:"capabilities"`
|
||
}
|
||
|
||
// ModelDetail 模型详细信息(用于 /v1/models/{model_id} 端点)
|
||
type ModelDetail struct {
|
||
ID string `json:"id"`
|
||
Object string `json:"object"`
|
||
Created int64 `json:"created"`
|
||
OwnedBy string `json:"owned_by"`
|
||
ModelName string `json:"model_name,omitempty"`
|
||
Category string `json:"category,omitempty"`
|
||
Status string `json:"status,omitempty"`
|
||
MaxContextTokens int `json:"max_context_tokens,omitempty"`
|
||
MaxOutputTokens int `json:"max_output_tokens,omitempty"`
|
||
Description string `json:"description,omitempty"`
|
||
Capabilities map[string]bool `json:"capabilities,omitempty"`
|
||
}
|