This repository was archived by the owner on Sep 4, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathconfig.example
More file actions
211 lines (197 loc) · 12.7 KB
/
Copy pathconfig.example
File metadata and controls
211 lines (197 loc) · 12.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# ===== autoapi 代理配置模板喵 =====
# 用法:复制成 config.yaml 再填自己的真 key 喵(config.yaml 已被 gitignore 保护)
server:
# 只监听本地回环地址,代理内存着所有上游真 key,绝对不要改成 0.0.0.0 喵
host: 127.0.0.1
# 代理对外提供服务的端口喵
port: 8787
# 接口错误忽略列表:按 HTTP method + path 精确匹配喵
# 命中后仍执行候选规则,但不触发自动避险、目标模式和候选 warning;链耗尽仍返回 502 喵
# 未配置时默认忽略 POST /v1/messages/count_tokens;写成 [] 可关闭默认行为喵
# 非空自定义列表会整体替换默认项,需要保留 count_tokens 时请一并写入喵
ignored_error_endpoints:
- method: POST
path: /v1/messages/count_tokens
# ===== 三个超时的分工喵 =====
# 这三项管的是完全不同的三件事,搞清楚了就不会配错喵:
#
# stall_timeout 「上游还活着吗」—— 允许上游连续静默多少秒
# stream_timeout 「等太久了吗」 —— 流式请求放行之前的总预算
# nonstream_timeout 非流式请求的总预算
#
# 允许上游「静默」多少秒,单位:秒。静默 = 一个字节都没发过来(连心跳都没有)。
# 关键点:只要收到任何字节,这个计时器就归零重新数。所以上游在发心跳注释行、
# 发 message_start 元信息、吐思维链的期间,都不会触发它 —— 它衡量的是
# 「这个连接是不是挂死了」,而不是「正文出来了没有」喵。
# 触发之后判定为 stalled_stream(卡流),由下面的规则决定重发还是换候选喵。
# 如果链上有会先安静思考很久的推理模型,可以给那个节点单独放宽(见候选的 stall_timeout)喵。
stall_timeout: 60
# 流式请求的总预算,单位:秒。从发出请求算起,到「确认这条流健康、可以放行给客户端」为止。
# 超过就判定为 timeout,由规则决定重发还是换候选喵。
# 重要:这个预算只管「放行之前」那一段。一旦确认健康、字节开始流向客户端,
# 我们就不再计时了 —— 模型愿意写多久就写多久,绝不会中途掐断一个正常输出的回答喵。
stream_timeout: 300
# 非流式请求的总预算,单位:秒。从发出请求算起,到整个响应体读完为止喵。
# 为什么比流式的大一倍:流式是「一点点吐」,几秒内就能判断健不健康,剩下的交给客户端;
# 非流式是「上游把整篇憋完再一次性返回」,在它返回之前我们什么都看不到,只能一直等。
# 一个长回答加上思考时间,十几分钟都可能,所以默认给 600 秒喵。
nonstream_timeout: 600
# 放行给客户端之前需要先累积够多少个内容字符喵。
# 为什么不是 1:只等 1 个字符的话,「先吐一两个字符然后卡死」的上游会骗过检查,
# 字节一出门就再也换不了候选,客户端只能干等。凑够 10 个字符才放行能挡掉这种喵。
# 注意:如果整条流在凑够 10 个字符之前就正常结束了(正常的短回答),也会照常放行,
# 不会让客户端干等;模型只调工具不吐文字时也会直接放行,所以这两种都不受影响喵。
min_content_chars: 10
# 连接上游的握手超时,单位:秒喵
connect_timeout: 15
# ===== 自动避险喵 =====
# 一个节点在全局范围内「连续」失败多少次,就自动把它冻结一段时间避险喵。
# 这里的失败包括所有类型:非 200 状态码、网络错误、流卡住、200 假成功等等。
# 「连续」的含义:中间只要成功过一次,之前攒的次数就清零重新数喵。
# 和上面那些规则的区别:规则是「看这一次失败长什么样」来决定怎么处置,
# 而自动避险是「看这个节点最近一直在失败」来决定先别用它 —— 有些节点会持续返回
# 各种五花八门的错误,每种都不足以让规则去冻结它,但它显然已经不能用了,
# 自动避险就是兜住这种情况的喵。
# 设成 0 表示关闭自动避险喵。
auto_hedge_threshold: 5
# 自动避险触发后冻结多少分钟喵
auto_hedge_minutes: 10
# 平均耗时的滚动窗口,单位:分钟,默认近 30 分钟,可用 REPL 的 set 修改喵
# RPM/TPM/平均缓存命中率固定统计最近 60 秒;缓存命中率按上游明确上报的缓存读取 Token / 输入 Token 加权计算喵
# 平均耗时只统计正常完成请求的完整请求耗时,不使用流式「首字」或「请求首字」喵
metrics_window_minutes: 30
# 配置文件热重载的轮询间隔,单位:秒。设为 0 表示关闭自动重载喵
reload_poll_interval: 2.0
# ===== 目标模式配置喵 =====
# 目标模式开关只能通过 REPL 的 target on/off 临时改动,重启后自动关闭,不会写入 config.yaml 喵
# 下面三项是 YAML 配置,不能用 set 修改;编辑文件后由热重载、reload 或重启应用喵
# 目标模式可通过 REPL 的 target on/off 临时开启,开启后会让请求在所有候选都失败时
# 持续重试一段时间而不是立即返回 502,适合用来保住正在运行的长时间客户端任务喵。
# 目标模式最长等待时长,单位:秒,默认 300 秒(5 分钟)喵
target_mode_max_wait_seconds: 300
# 目标模式每轮链路失败后的等待间隔,单位:秒,默认 5 秒喵
target_mode_round_interval_seconds: 5
# 目标模式超时后的行为,可选值(不同客户端的重试机制不同,按需选择)喵:
# return_504 - 返回 504 Gateway Timeout(推荐,HTTP 标准网关超时状态码,
# 大多数客户端如 Claude Code、Codex 都会识别并自动重试)
# return_429 - 返回 429 Too Many Requests(表示限流,部分客户端会重试,
# 但语义上不太准确因为实际是超时而非限流)
# return_502 - 返回 502 Bad Gateway(伪装成上游服务故障,部分客户端会重试)
# drop_connection - 直接断开 TCP 连接不返回任何 HTTP 响应(伪造网络超时,
# 客户端会收到连接中断错误,可能触发底层网络重试机制,
# 但调试困难且行为不可预测,不推荐使用)
target_mode_timeout_action: return_504
# ===== 虚拟模型表喵 =====
# key 是客户端请求体里要填的 model 名字,value 是按优先级排好的候选链
# 每条请求永远从链首开始,跳过被冻结的候选,往下找第一个可用的喵
virtual_models:
auto-strong:
- name: 官方直连 # 候选的human可读名字,日志和 REPL 里显示用喵
base_url: https://api.openai.com # 上游根地址,客户端请求的路径会原样拼在后面喵
api_key: sk-REPLACE-ME-1 # 该上游的真实 api key 喵
model: gpt-4o # 替换进请求体顶层 model 字段的真实模型名喵
auth_style: bearer # 鉴权头风格:bearer 表示 Authorization: Bearer xxx 喵
- name: 中转A
base_url: https://relay-a.example.com
api_key: sk-REPLACE-ME-2
model: gpt-4o
auth_style: bearer
- name: Claude兜底
base_url: https://api.anthropic.com
api_key: sk-ant-REPLACE-ME-3
model: claude-sonnet-4-20250514
auth_style: x-api-key # x-api-key 风格会同时自动补 anthropic-version 头喵
# ===== 按节点单独配超时的例子喵(下面三项都是可选的)=====
# 什么时候需要:同一条链上常常混着快慢差很多的模型 —— 比如这个会先安静想两三分钟
# 的推理模型,和上面那些秒回的模型。给它们配同一套超时,要么把推理模型冤枉成卡流,
# 要么让快模型挂死太久才降级。所以给需要区别对待的节点单独开口子喵。
# 不写这三项就完全跟随上面 server 段的全局值,绝大多数节点都不用写喵。
- name: 慢速推理模型
base_url: https://relay-c.example.com
api_key: sk-REPLACE-ME-5
model: o3-deep-research
auth_style: bearer
stall_timeout: 240 # 这个模型可能安静想 4 分钟,放宽静默上限免得被误判成卡流喵
stream_timeout: 900 # 流式总预算放到 15 分钟喵
nonstream_timeout: 1800 # 非流式要憋完整篇,给到 30 分钟喵
auto-cheap:
- name: 廉价中转
base_url: https://relay-b.example.com
api_key: sk-REPLACE-ME-4
model: gpt-4o-mini
auth_style: bearer
# ===== 规则引擎喵 =====
# 自上而下匹配,第一条命中的规则决定动作,四种动作含义:
# retry 同一个候选内指数退避重试,重试用尽还失败才换下一个候选喵
# next 立刻放弃这个候选,换下一个喵
# freeze 全局冻结这个候选一段时间(所有虚拟模型都跳过它),然后换下一个喵
# passthrough 不转移,把上游的响应原样回传给客户端喵
# match 里可写 status(整数或整数列表)和 body_regex(对响应体做正则搜索)
# 四个特殊状态值喵:
# network 连不上上游 / 握手超时 / 读取时断连
# bad_stream 200 但已确定这条流是坏的(明确收到 error 事件,或结束了却一个字都没有)
# stalled_stream 上游静默太久,一个字节都不发了 —— 连接像是挂死了(卡流)
# timeout 连接一直活着、上游也一直在发东西,但超过总预算还没拿到足够内容(太慢了)
rules:
# 额度用尽且上游明确告知几分钟后恢复,就照它说的分钟数精确冻结喵
- match:
status: 429
body_regex: 'refreshes?\s+in\s+(\d+)\s+minutes?'
action: freeze
freeze_from_group: 1 # 用正则第 1 个捕获组的数字当冻结时长喵
freeze_unit: minutes # 捕获到的数字单位是分钟喵
freeze_seconds: 300 # 万一没抽取到数字,退回冻结这么多秒喵
# 其余 429 限流:先原地退避重试几次,通常几秒后就放行了喵
- match:
status: 429
action: retry
max_attempts: 3 # 含首次共尝试 3 次喵
backoff_base: 2.0 # 退避基数,第 n 次等待 backoff_base^n 秒喵
# 上游 5xx 抖动,原地重试比换渠道更划算喵
- match:
status: [500, 502, 503, 504, 520, 524]
action: retry
max_attempts: 3
backoff_base: 1.5
# 网络层连不上或超时,重试两次再换喵
- match:
status: network
action: retry
max_attempts: 2
backoff_base: 1.0
# 流卡住了(上游静默太久,一个字节都不发了):原地重发一次试试喵。
# 为什么是重发而不是直接换候选:卡流是「等不到结论」,不代表这个上游坏了,
# 很可能只是这一次调度倒霉,重发一次经常就正常了。max_attempts=2 表示
# 首次 + 重试 1 次,共两次;两次都卡住才降级到下一个候选喵。
- match:
status: stalled_stream
action: retry
max_attempts: 2 # 含首次共 2 次,也就是最多重试 1 次喵
backoff_base: 1.0 # 卡流不是限流,不用等太久,1 秒后就重发喵
# 超过总预算(stream_timeout 或 nonstream_timeout):也重发一次喵。
# 和卡流的区别是这次连接一直健康、上游一直在发东西,就是太慢了。
# 只重试 1 次是因为已经等了很久(默认流式 5 分钟、非流式 10 分钟),
# 再等一整轮的代价太大;一次不行就换候选更划算喵。
- match:
status: timeout
action: retry
max_attempts: 2 # 含首次共 2 次,也就是最多重发 1 次喵
backoff_base: 1.0 # 已经等很久了,别再叠加等待,1 秒后直接重发喵
# 200 假成功:流已经明确结束或明确报错,但一个字都没吐。直接换下一个候选喵。
# 和上面的卡流不同,这种情况已经确定这次是坏的,重发同一个上游大概率还是坏的喵
- match:
status: bad_stream
action: next
# key 无效、无权限、模型不存在,这个候选没救了,直接换喵
- match:
status: [401, 403, 404]
action: next
# 上下文超限不是客户端字段格式错误,换到上下文窗口更大的候选继续尝试喵
- match:
status: 400
body_regex: 'context_length_exceeded'
action: next
# 400 通常是客户端自己请求体写错了,换渠道也没用,原样回传让客户端自己看喵
- match:
status: 400
action: passthrough