Documentation
¶
Overview ¶
Package server 把 relay、channel 与 plugin 装配成一个可监听的 HTTP 服务, 对外暴露四种方言的端点。
装配规则只有一条:client 方言与渠道方言相同时按字节透传,不同时经 relay 转换。 插件在四个时机介入,插件失败不影响转发可用性。
Index ¶
Constants ¶
const SessionHeaderName = "X-TokenMP-Session"
SessionHeaderName 是客户端声明会话标识的请求头。
同一段会话的连续请求带上同一个值,core 会把它们粘到同一个上游渠道上,让上游 的提示缓存有机会命中。这个头只在本进程内使用,不会转发给上游。
Variables ¶
This section is empty.
Functions ¶
func BuildPlugins ¶
BuildPlugins 按配置加载插件目录并构造插件集合。
插件目录不存在且没有启用任何插件时视为没有插件;启用了插件却找不到目录则报错, 否则「插件没生效」只能靠读日志发现。
func WithUpstreamHeader ¶ added in v0.5.0
WithUpstreamHeader 把一条上游请求头挂到 ctx 上,转发这个请求时随上游请求带出去。
宿主在 Options.Middleware 里调用:中间件装在内核外侧,这里的 ctx 会一路传到 转发路径与 OnRequestDone。同一个名字重复设置时后一次生效,等价于 http.Header.Set。
客户端自带的请求头默认不转发,只有名字出现在渠道 pass_headers 名单里时才会带上, 因此这里明确设置的头总是会上游。
与渠道配置里的同名头按请求覆盖:请求级比渠道级具体,按请求覆盖某个渠道头正是 这个入口的用途。它也比名字出现在渠道 pass_headers 名单里时从客户端转发的头更 具体,同名时宿主赢。覆盖是显式的,渠道配置自己不受影响,宿主没设的同名头照旧 上去,被覆盖的那个只发一条。覆盖是按名字整个替换:宿主看不到渠道配置里的原值, 两个值都想要得自己拼。渠道的鉴权头是例外,它在最后按 Auth 写入,宿主设的同名头 不生效,凭据始终由渠道决定。
一个名字只带一个值:ctx 里存的是名字到取值的映射,重复字段的头(比如两条同名 Cookie)表达不了。
名字不是合法的 HTTP 头名(RFC 7230 的 token),或取值含控制字符时,这条头在转发 时被丢弃并记一行 WARN:让一个写错的头名把整条请求带成 502 代价太大。水平制表符 在取值里是合法的,其他控制字符不是。
Types ¶
type Attempt ¶ added in v0.3.0
type Attempt struct {
Channel string
Dialect dialect.Name
Upstream string
// Status 是上游返回的状态码。0 表示没拿到上游响应,原因看 Failure。
Status int
// Failure 是没拿到上游响应的原因,Status 非 0 时恒为 FailureNone。
Failure Failure
// Retries 是这条候选内部的渠道内重试次数,0 表示没有重试。它回答「上游实际
// 多收了几次请求」,与路由级的 attempts 不是一回事。
Retries int
// Duration 是这次尝试的耗时,从选定候选算到拿到响应头或失败。含这条候选内部
// 的全部退避等待。
Duration time.Duration
}
Attempt 是一次候选尝试。
type Failure ¶ added in v0.3.0
type Failure string
Failure 是一次尝试没拿到上游响应的原因。
取值封闭:不把上游的错误文字带进来。那是客户端与上游可控的字符串,长度没有上限, 放进公开面就得跟着定一套截断口径。原因文字在 WARN 日志里。
type Option ¶ added in v0.3.0
type Option func(*Options)
Option 覆盖 FromConfig 的装配参数。
func WithMiddleware ¶ added in v0.4.0
WithMiddleware 设置包住内核处理器的宿主中间件,见 Options.Middleware。
func WithRequestDone ¶ added in v0.3.0
WithRequestDone 设置每次请求结束时调用的回调,见 Options.OnRequestDone。
func WithoutAccessLog ¶ added in v0.4.0
func WithoutAccessLog() Option
WithoutAccessLog 关掉内核自己那两行访问日志,见 Options.DisableAccessLog。
type Options ¶
type Options struct {
// Config 是配置,必填。
Config *config.Config
// Channels 是渠道客户端集合,必填。
Channels *channel.Set
// Plugins 是插件集合,为 nil 时视为没有插件。
Plugins *plugin.Store
// Engine 是转换内核,为 nil 时用零值内核。
//
// 零值内核不带任何行为开关。按配置构造内核是 FromConfigWith 的事:它把
// defaults 段的默认值翻成 relay.Options 再注入这里。这个字段留给直接调 New
// 的宿主自己注入内核,New 不读配置。
Engine *relay.Engine
// Logger 是日志入口,为 nil 时丢弃日志。
Logger *slog.Logger
// Now 返回当前时间,为 nil 时用 time.Now。
Now func() time.Time
// MaxBodyBytes 是单个请求体的上限,为 0 时用 config.DefaultMaxBodyBytes。
// FromConfigWith 会先把它填成配置里 defaults.max_body_bytes 的生效值。
MaxBodyBytes int64
// OnRequestDone 在每次请求结束时同步调用,交出这次请求的原始量。
//
// ctx 是本次请求一直沿用下来的那个:宿主在 Options.Middleware 里加进 context 的
// 值在这里能读到,用来把请求级的记账状态跟结束时这份原始量对上。
//
// 为 nil 时什么也不做,也不构造 Outcome。
OnRequestDone func(context.Context, Outcome)
// Middleware 包住内核的整个处理器,包括访问日志。宿主在这里记自己的日志、
// 测首字节、加响应头、接 tracing,也可以换掉请求体(内核从 request.Body 读,
// 换过之后参与路由的就是新内容)或根本不调用 next。
//
// 装在外侧:宿主加进 context 的值会一路传下去,OnRequestDone 的 ctx 里有它。
//
// 改请求头默认不会传到上游:只有渠道 pass_headers 名单里的头会跟随,其余请求头
// 与 Auth 都由内核按渠道配置决定。要按请求加上游头,在中间件里调
// WithUpstreamHeader 往 ctx 上挂。
//
// 包装 http.ResponseWriter 时必须把 Flush 转发下去,否则流式响应会静默退化成
// 不刷新。
//
// 为 nil 时什么都不装。
Middleware func(http.Handler) http.Handler
// DisableAccessLog 关掉内核自己那两行访问日志。
//
// 关掉的是「请求进入」与「请求完成」,包括失败请求的 WARN 完成行;request_id 仍然
// 生成、其余 WARN 行照旧带着它,OnRequestDone 也不受影响。
// 进入行里的 client 与 user_agent 会一起没有,需要的话从 Outcome 的同名字段自己记。
// 默认为 false,即照常记录。
DisableAccessLog bool
}
Options 是服务的装配参数。
type Outcome ¶ added in v0.3.0
type Outcome struct {
// RequestID 是这次请求的 id,与访问日志里的 request_id 相同。
RequestID string
Method string
Path string
// Client 是去掉端口之后的客户端地址,UserAgent 是请求头里的 User-Agent。
Client string
UserAgent string
// Status 是写回客户端的状态码。处理器没有显式写响应头时按 200 记,与访问日志一致。
Status int
// Duration 是从请求进入中间件到处理器返回的时长,含渠道内重试的退避、换候选与
// 等待上游的全部时间。不拆成几段:宿主算服务时长要的就是客户端实际等了多久,
// 拆开之后还得自己加回去,反而多一处会算错的地方。
Duration time.Duration
// FirstByte 是从请求进入中间件到首次写出响应体的时长,也就是 core 能测到的 TTFT。
// 整个请求没有写出过响应体时为 0。
//
// 只算响应体,不算响应头:流式路径拿到上游响应头就写响应头,而第一个分片可能过很久
// 才到,按响应头记会让 TTFT 系统性偏小。非流式响应是一次写完的,这个值因此约等于
// Duration,看不出 TTFT——该不该算由宿主按 Streaming 自己判断。
FirstByte time.Duration
// BytesWritten 是写回客户端的响应体字节数,不含响应头。
BytesWritten int64
// Model 是插件改写之后参与匹配的模型名。Channel 是实际服务的那条渠道,降级换过
// 渠道时是真正服务的那个;没有一条候选服务成功时为空串。
Model string
Channel string
// Streaming 报告客户端是否要求流式,取自请求内容,不是上游实际按流式回的。
Streaming bool
// Attempts 按顺序记下每条候选尝试的耗时与状态,长度与访问日志的 attempts 相同。
Attempts []Attempt
// Usage 是上游给出的用量,上游没给时为 nil。
Usage *Usage
}
Outcome 是一次请求结束时交给宿主的原始量。
只交原始量,不算指标。指标口径随宿主变:TTFT 是从请求进入算还是从上游首字节算, TPS 的分母取总时长还是生成时长,core 算出来的一定不合宿主的意。宿主拿这些量自己算。
字段是每次请求都会填的,没有「有时有有时没有」的项:上游没给用量时 Usage 为 nil, 这是唯一的可选项。
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server 是一个转发服务。
func FromConfigWith ¶ added in v0.3.0
FromConfigWith 与 FromConfig 相同,另外可以用 opts 覆盖装配参数。
做成单独一个函数而不是给 FromConfig 加可变参数:可变参数会让它不再能赋给 func(*config.Config, *slog.Logger) (*Server, error) 类型的变量,按仓库的兼容 承诺算破坏性变更。要完全掌控装配就直接用 New。
type Usage ¶ added in v0.3.0
type Usage struct {
PromptTokens int
CompletionTokens int
TotalTokens int
// CachedTokens 是命中提示缓存的那部分提示词 token,上游没报时为 0。
CachedTokens int
// CacheWriteTokens 是写进提示缓存的那部分提示词 token,上游没报时为 0。
// Claude 与 OpenAI 各用一种字段名报这个数,这里交出转换层归一之后的一个值,
// 宿主不必再去分辨上游用的是哪一种写法。
CacheWriteTokens int
}
Usage 是上游给出的用量。字段与访问日志一致,够算 TPS 与账目。