⚠ 该项目已经停止维护 该项目提供的内容已经合并到我仓库的new-api二改项目 本项目不再维护
透明的 LLM API 故障转移代理喵~ 客户端把请求发给它,它按优先级链路转发给上游;上游返回错误、假成功(200 里塞 error)、空流、卡流时,它会静默换下一个候选(换 base_url / api_key / 真实模型名)重发,客户端完全感知不到发生过失败喵。
对客户端来说,它就是一个普通的 OpenAI 兼容端点:把 base_url 指向 autoapi,model 填一个虚拟模型名,剩下的事情不用管喵。
- 协议透传:客户端的请求路径、查询串、请求头、请求体全部原样转发给上游。只有三样东西会被替换 ——
base_url、api_key、请求体顶层的model字段。所以 OpenAI 的/v1/chat/completions和 Anthropic 的/v1/messages都能用,不需要做协议探测喵。 - 静默转移:失败发生在「字节还没出门」的阶段,所以换候选对客户端完全不可见。流式请求会先探测这条流是不是真的健康,确认吐出足够内容之后才把连接交给客户端喵。
- 假成功识别:不少中转站在额度不足或后端报错时照样回 200,把真正的错误塞进 body 的
error字段,或者建了一条 SSE 流却一个字都不吐。这两种都会被判为失败并触发转移喵。
客户端只认虚拟模型这个名字(比如 auto-strong)。每个虚拟模型背后是一条按优先级排好的候选链,链上每个节点是一个真实上游:
虚拟模型 auto-strong
1. 官方直连 https://api.openai.com gpt-4o
2. 中转A https://relay-a.example.com gpt-4o
3. Claude兜底 https://api.anthropic.com claude-sonnet-4-20250514
每条请求永远从链首开始,跳过正在冻结中的节点,往下找第一个可用的。链首就是你最想用的那个,只有它挂了才降级喵。
一次尝试失败之后,规则列表自上而下匹配,第一条命中的决定动作(和防火墙规则一个语义)。四种动作:
| 动作 | 含义 |
|---|---|
retry |
在同一个候选上指数退避重试,次数用尽还失败才换下一个 |
next |
立刻放弃这个候选,换下一个 |
freeze |
全局冻结这个候选一段时间(所有虚拟模型都跳过它),然后换下一个 |
passthrough |
不转移,把上游的响应原样回传给客户端 |
一条规则都没命中时,走最保守的默认动作 next(换下一个候选)喵。
冻结是按 (base_url, api_key, model) 三元组算的,而且是全局的 —— 一个节点被冻上之后,所有引用它的虚拟模型都会跳过它。冻结到期自动失效;节点只要成功一次就立即解冻,因为成功证明它已经恢复了喵。
冻结时长的来源按优先级排:规则正则里捕获到的数字(上游明确说了「几分钟后恢复」时最精准)→ 上游的 Retry-After 响应头 → 规则里写死的 freeze_seconds 兜底值喵。
有些节点会持续返回五花八门的错误,每一种都不足以让规则去冻结它,但它显然已经不能用了。自动避险兜住这种情况:一个节点连续失败达到 auto_hedge_threshold 次,就自动冻结 auto_hedge_minutes 分钟。「连续」的意思是中间只要成功过一次,之前攒的次数就清零重新数喵。
和规则的分工:规则看「这一次失败长什么样」来决定这条请求接下来怎么走;自动避险看「这个节点最近一直在失败」来决定接下来一段时间还要不要用它喵。
这一点要明确说清楚:autoapi 没有全局兜底链。候选链是每个虚拟模型专属的,某个虚拟模型的候选链全挂了(或全在冻结中),代理就直接给客户端返回 502,并在错误体的 attempts 字段里列出每个候选各自的失败原因,不会去借用别的虚拟模型的节点喵。
这是刻意的设计:虚拟模型之间的成本、能力、数据流向都可能完全不同,偷偷把请求转到另一个虚拟模型的节点上,比直接失败更糟糕喵。
⚠ WIndows请使用Powershell启动!! 需要 Python 3.10 及以上(代码里用了
X | None这种类型写法)喵。
# 装依赖喵
pip install -r requirements.txt
# 从模板复制出自己的配置喵
cp config.example config.yaml
# 编辑 config.yaml,把里面的 api_key 换成自己的真 key 喵
# 然后启动喵
python main.py启动之后把客户端的 base_url 指向 http://127.0.0.1:8787,model 填配置里的虚拟模型名就行喵。
GitHub Actions 会在 master 推送、手动触发和版本 tag(v*)时自动构建 Windows EXE 喵~ 普通构建的 ZIP 可以从 Actions 的 artifact 下载,版本 tag 构建还会自动附加到 GitHub Release 喵~
发行包只包含 autoapi.exe 和公开的 config.example 模板,不会内置 config.yaml 或任何真实 API key 喵~ 下载并解压后,在 EXE 同目录用 PowerShell 创建自己的配置文件喵:
Copy-Item config.example config.yaml编辑 config.yaml 填入自己的真实 key 后启动代理喵~ 推荐后台运行时关闭交互式 REPL:
.\\autoapi.exe --no-repl -c config.yaml也可以通过 -c / --config 指向其他位置的配置文件喵~ 配置文件必须由主人自行保管,绝对不要上传到 GitHub 或放进发行包喵~
| 参数 | 说明 |
|---|---|
-c <路径> / --config <路径> |
指定配置文件,默认 config.yaml。相对路径会先在当前工作目录找、再在 main.py 所在目录找,所以从任何目录启动都能找到项目里那份配置喵 |
--no-repl |
不启动交互式命令行,适合用 nohup 之类的方式后台运行喵 |
| 路径 | 说明 |
|---|---|
GET /healthz |
健康检查,返回 JSON 字段 status、virtual_models、frozen_candidates、total_requests、total_exhausted 喵~ |
GET /v1/models |
按 OpenAI 格式列出所有虚拟模型,很多 GUI 客户端启动时会拉这个来填下拉框喵 |
| 其余任意路径 | 通配透传,原样转发给上游喵 |
完整的带注释模板见 config.example,下面按段落讲各字段的含义喵。
| 字段 | 默认值 | 说明 |
|---|---|---|
host |
127.0.0.1 |
监听地址。强烈建议保持回环地址,理由见下面的安全提醒喵 |
port |
8787 |
监听端口。这一项改了要重启代理才生效,因为 socket 在启动时就绑好了喵 |
这三项管的是三件完全不同的事,搞清楚了就不会配错喵:
| 字段 | 默认值 | 衡量的是 | 说明 |
|---|---|---|---|
stall_timeout |
60 |
上游还活着吗 | 允许上游连续静默多少秒。静默 = 一个字节都没发过来。只要收到任何字节就归零重新数 —— 所以上游发心跳、吐思维链期间都不会触发它。触发了说明连接真的挂死了,判为 stalled_stream 喵 |
stream_timeout |
300 |
等太久了吗 | 流式请求的总预算,从发出请求算到「确认这条流健康、可以放行」为止,中途不归零。连接一直健康但正文迟迟不来时靠它兜住,判为 timeout 喵 |
nonstream_timeout |
600 |
同上 | 非流式请求的总预算,从发出请求算到整个响应体读完为止,判为 timeout 喵 |
connect_timeout |
15 |
连得上吗 | 建立连接的握手超时。连不上要快速失败好换下一个候选,判为 network 喵 |
两个要点:
stream_timeout只管「放行之前」那一段。 一旦确认流健康、字节开始流向客户端,代理就不再计时了 —— 模型愿意写多久就写多久,绝不会中途掐断一个正在正常输出的回答喵。nonstream_timeout天生比stream_timeout大。 流式是「一点点吐」,几秒内就能判断健不健康,剩下的交给客户端;非流式是「上游把整篇憋完再一次性返回」,在它返回之前什么都看不到,只能一直等喵。
超时和卡流默认都会原地重发 1 次(config.example 里 status: timeout 和 status: stalled_stream 那两条规则),重发还不行才降级到下一个候选喵。
上面三项超时都可以在单个候选节点上覆盖,只写想改的那几项,没写的继续跟随全局值:
- name: 慢速推理模型
base_url: https://relay-c.example.com
api_key: sk-xxx
model: o3-deep-research
stall_timeout: 240 # 这个模型可能安静想 4 分钟
stream_timeout: 900 # 流式总预算放到 15 分钟
nonstream_timeout: 1800 # 非流式给到 30 分钟为什么需要这个:同一条链上常常混着快慢差很多的模型 —— 链首是会先想很久的推理模型、兜底是秒回的小模型。给它们配同一套超时,要么把推理模型冤枉成卡流,要么让小模型挂死太久才降级喵。
在 REPL 里也能改,填 default 就恢复跟随全局值。配了专属超时的节点会在 vm 命令的输出里标出来:
cand set auto-strong 1 stream_timeout 900
cand set auto-strong 1 stream_timeout default
| 老字段 | 现在改用 |
|---|---|
request_timeout |
拆成了 stream_timeout 和 nonstream_timeout |
first_content_timeout |
由 stall_timeout 和 stream_timeout 接手 |
first_content_timeout 是从「开始读流」那一刻起算的,只要迟迟等不到正文就判失败 —— 哪怕连接一直活着、上游正在发心跳或吐思维链。推理模型先想一两分钟再吐第一个字是很正常的事,于是就会出现「首字还没出就被自动重试」。现在改成了「静默才算卡」,正在干活的上游不会再被冤枉喵。
配置里如果还留着这两个老字段,代理照旧能正常启动,但会在启动时和每次热重载时各打一条警告,提醒它们已经不生效了喵。
| 字段 | 默认值 | 说明 |
|---|---|---|
min_content_chars |
10 |
放行给客户端之前需要先累积够多少个内容字符。不设成 1 是因为「先吐一两个字符然后卡死」的上游会骗过检查 —— 字节一出门就再也换不了候选了。整条流在凑够之前就正常结束(短回答),或者模型只调工具不吐文字,都会照常放行,不会让客户端干等喵 |
auto_hedge_threshold |
5 |
一个节点连续失败多少次就自动避险。设成 0 表示关闭自动避险喵 |
auto_hedge_minutes |
10 |
自动避险触发后冻结多少分钟喵 |
ignored_error_endpoints |
默认忽略 POST /v1/messages/count_tokens |
按 HTTP method + path 精确匹配的接口列表,仅支持代理路由承接的 POST、PUT、PATCH、GET、DELETE,path 不可包含 ? 或 #。命中后候选内部规则仍执行,但不累计自动避险、不进入目标模式、不输出候选 warning;候选全部失败时只记一条 info,客户端仍收到 502。未配置时使用默认条目;显式写 [] 可关闭默认忽略;填写任意非空列表时会整体替换默认条目,如需保留默认接口请一并写入列表喵 |
metrics_window_minutes |
30 |
平均耗时统计窗口,单位:分钟。RPM/TPM/平均缓存命中率固定统计最近 60 秒,可用 set metrics_window_minutes <分钟> 修改喵 |
reload_poll_interval |
2.0 |
配置热重载的轮询间隔,单位:秒。设成 0 表示关闭自动重载喵 |
key 是客户端请求体里要填的 model 名字,value 是按优先级排好的候选链(列表顺序就是优先级,第 1 位最优先)喵。
virtual_models:
auto-strong:
- name: 官方直连 # 可读名字,日志和 REPL 里显示用,可选喵
base_url: https://api.openai.com # 上游根地址,客户端的路径会原样拼在后面喵
api_key: sk-REPLACE-ME-1 # 该上游的真实 api key 喵
model: gpt-4o # 替换进请求体顶层 model 字段的真实模型名喵
auth_style: bearer # 鉴权头风格,可选,默认 bearer 喵候选字段说明:
- 必填:
base_url、api_key、model - 可选:
name(不写会自动生成虚拟模型名#序号)、auth_style auth_style只能是bearer(Authorization: Bearer xxx,OpenAI 系和绝大多数中转站)或x-api-key(Anthropic 系,会自动补anthropic-version头)喵
规则是一个列表,顺序即优先级。每条规则由 match(什么情况)和 action(怎么办)组成喵。
match 里可以写两个条件,都写时必须同时满足:
status:单个整数429、整数列表[500, 502],也可以混合别名[429, "network"]body_regex:对响应体做正则搜索,忽略大小写。上游一律回 200 或 429、真正的原因只写在 body 里时靠它区分喵
除了真实 HTTP 状态码,status 还支持四个特殊别名:
| 别名 | 含义 |
|---|---|
network |
连不上上游 / 握手超时 / 读取时断连 |
bad_stream |
200 但已确定这条流是坏的(明确收到 error 事件,或流结束了却一个字都没有;非流式的「200 里塞 error」也归这里) |
stalled_stream |
上游静默太久,一个字节都不发了 —— 连接像是挂死了(卡流) |
timeout |
连接一直活着、上游也一直在发东西,但超过总预算还没拿到足够内容(太慢了) |
这几个的区别值得记一下,因为它们对应的处置策略不一样喵:
bad_stream是已经确定坏了。重发同一个上游大概率还是坏的,换候选更划算。stalled_stream是连接挂死了。不代表这个上游整体不行,很可能只是这一次调度倒霉,原地重发一次经常就正常了。timeout是连接健康但太慢。也适合重发一次,但因为已经等了很久(默认流式 5 分钟、非流式 10 分钟),再等一整轮的代价很大,所以只重发 1 次就换候选喵。
按动作可用的额外字段:
| 字段 | 适用动作 | 说明 |
|---|---|---|
max_attempts |
retry |
同一候选上总共尝试几次(含首次)。至少为 1 喵 |
backoff_base |
retry |
退避基数,第 n 次重试前等 backoff_base ^ n 秒,单次等待上限 30 秒。至少为 1.0 喵 |
freeze_from_group |
freeze |
从 body_regex 的第几个捕获组取冻结时长数字喵 |
freeze_unit |
freeze |
捕获到的数字的单位,seconds / minutes / hours,默认 minutes 喵 |
freeze_seconds |
freeze |
没能抽取到数字时的兜底冻结秒数,默认 300 喵 |
例子:上游说「额度用尽,6 分钟后恢复」,就照它说的分钟数精确冻结喵。
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 # 万一没抽到数字,退回冻 300 秒喵一条既没写 status 也没写 body_regex 的规则会匹配一切,属于危险配置,加载阶段会直接被拒喵。
成功日志会按请求类型附带性能信息喵:
INFO autoapi.proxy [d3c518] 成功 主力(...)(第 1 次尝试)喵 非流 返回请求耗时=842ms
INFO autoapi.proxy [d3c518] 成功 主力(...)(第 1 次尝试)喵 流 首字=410ms 请求首字=760ms
INFO autoapi.server 流式请求结束 虚拟模型=auto-strong 返回请求耗时=12840ms 正常完成=True usage_tokens=1536 喵
- 非流式请求的
返回请求耗时:从服务端收到客户端请求,到完整上游响应成功取得并准备返回的耗时喵~ - 流式请求的
首字:从本次向上游节点发请求,到节点首次返回任意非空字节的耗时喵~ - 流式请求的
请求首字:从服务端收到客户端请求,到代理确认流健康并开始向客户端转发的耗时喵~ - 流式请求的
返回请求耗时:从服务端收到客户端请求,到上游流自然结束的完整耗时;放行后不会再受stream_timeout截断喵~
REPL 底部状态栏会按虚拟模型显示最近 60 秒 的动态 RPM/TPM,以及配置窗口内的平均耗时和平均缓存命中率喵~ RPM/TPM/平均缓存命中率固定统计最近 60 秒,只有平均耗时使用 metrics_window_minutes 配置窗口,默认是 30 分钟,可以用 set metrics_window_minutes <分钟> 修改喵~ RPM 是 60 秒内成功请求数,TPM 只使用上游响应中的 usage token,不会按字符数或本地 tokenizer 估算喵~ 缓存命中率按上游明确上报的缓存读取 Token / 输入 Token 加权计算;OpenAI 使用 usage.prompt_tokens_details.cached_tokens,Anthropic 使用 usage.cache_read_input_tokens,缺少任一必要字段时显示「未完整上报」而不猜测数值喵~ 没有成功请求的虚拟模型(RPM=0)不会显示,节约状态栏空间喵~ 平均耗时只统计配置窗口内的正常完成请求:非流式响应必须完整成功,流式上游必须自然结束;客户端断开、上游读取异常和其他异常结束的流不会进入平均耗时喵~
常见 usage 字段包括 OpenAI 的 usage.total_tokens,或 prompt_tokens + completion_tokens,以及 Anthropic 的 input_tokens + output_tokens 喵~ 上游不返回这些字段时,代理不会擅自修改请求或猜测 token 数喵。
目标模式是一个仅当前进程有效的临时保活开关,不会写入 config.yaml 喵~
target status
target on
target off
开启后,某个虚拟模型的候选链整轮失败时,代理不会立即把 502 返回给客户端,而是按 server.target_mode_round_interval_seconds 从链首重新尝试,最多持续 server.target_mode_max_wait_seconds 喵~ 超时后由 server.target_mode_timeout_action 决定结果:return_504(默认)返回 504 和错误类型 target_mode_gateway_timeout,return_429 返回 429 和 target_mode_all_unavailable,return_502 返回 502 和 target_mode_all_unavailable,drop_connection 直接断开连接喵~
target on/off/status只改变当前进程内存状态,重启后自动关闭;target_mode_max_wait_seconds、target_mode_round_interval_seconds、target_mode_timeout_action是 YAML 配置,需手动编辑后热重载、执行reload或重启才会更新喵~- 已冻结的节点仍然会跳过,不会为了目标模式反复撞额度限制或绕过自动避险喵~
passthrough仍然立即回传,不会重试客户端自己的明确错误喵~- 目标模式会让单个客户端请求最长占用约
target_mode_max_wait_seconds,请只在确实希望尽量不断线时开启喵~
代理跑起来之后,同一个终端里就是一个 REPL(提示符 autoapi> ),可以随时看状态、改配置、清冻结,改完立即生效,不用重启喵。它跑在独立线程里,敲命令不会影响正在转发的请求喵。
所有改配置的命令都走同一套流程:从磁盘重读原始 YAML → 改动 → 完整校验 → 通过才替换内存配置并写回磁盘。校验不通过就打印错误、什么都不改,磁盘上的文件也完全没被动过喵。
| 命令 | 说明 | 例子 |
|---|---|---|
vm |
列出所有虚拟模型及其候选链,标注每个节点是可用还是冻结中 | vm |
rule ls |
列出所有规则和它们的序号 | rule ls |
freeze ls |
列出当前所有冻结中的候选、剩余时间和冻结原因 | freeze ls |
stats |
打印代理累计计数,以及候选和虚拟模型的健康、资源与缓存统计,详见下文 | stats |
stats 会显示:
- 代理累计请求数
total_requests和累计用尽整条候选链数total_exhausted - 每个候选的成功/失败/被冻结次数、自动避险次数
hedged_times、当前连续失败数和最近错误 - 候选与虚拟模型在所有时间、近 6 小时、近 1 小时、近 30 分钟、近 10 分钟窗口内的成功率、请求数、Token 数、平均耗时和平均缓存命中率
stats 的请求数、Token 数和平均耗时按对应统计窗口展示;缺少 usage 或缓存字段时不会猜测,相关指标显示「未完整上报」喵~
| 命令 | 说明 | 例子 |
|---|---|---|
vm add <名字> <候选JSON> |
新建虚拟模型,同时配上第一个候选(空链是非法配置,所以必须一起给) | vm add my-model {"base_url": "https://y.com", "api_key": "sk-yyy", "model": "gpt-4o-mini"} |
vm rm <名字> |
删掉一整个虚拟模型(连带它的所有候选)。不能删掉最后一个 | vm rm auto-cheap |
| 命令 | 说明 | 例子 |
|---|---|---|
cand add <虚拟模型> <候选JSON> |
给候选链末尾追加一个候选(也就是优先级最低) | cand add auto-strong {"name": "新中转", "base_url": "https://x.com", "api_key": "sk-xxx", "model": "gpt-4o"} |
cand rm <虚拟模型> <序号> |
删掉指定序号的候选。链里只剩一个时不许删,要整个不要就用 vm rm |
cand rm auto-strong 2 |
cand mv <虚拟模型> <原序号> <新序号> |
挪动候选位置来调整优先级,第 1 位最优先 | cand mv auto-strong 3 1 |
cand set <虚拟模型> <序号> <字段> <值> |
改某个候选的单个字段,位置(优先级)不变。字段可选 base_url、api_key、model、name、auth_style、stall_timeout、stream_timeout、nonstream_timeout |
cand set auto-strong 2 api_key sk-new-key-here |
cand set 比「删了重加」好用得多:不用把整个候选重新敲一遍,节点在链里的位置也不会变。可修改 base_url、api_key、model、name、auth_style,以及候选级 stall_timeout、stream_timeout、nonstream_timeout 覆盖。候选级超时填 default、none 或 - 可删除覆盖,恢复跟随 server 全局值;改 api_key 时终端不会回显完整的新旧 key,只显示脱敏结果喵~
| 命令 | 说明 | 例子 |
|---|---|---|
rule add <JSON> |
追加一条规则到列表末尾(优先级最低) | rule add {"match": {"status": 429}, "action": "retry", "max_attempts": 3} |
rule rm <序号> |
删掉指定序号的规则,会把删掉的内容打出来确认 | rule rm 3 |
rule mv <原序号> <新序号> |
挪动规则位置来调整匹配优先级 | rule mv 5 1 |
| 命令 | 说明 | 例子 |
|---|---|---|
freeze add <虚拟模型> <模型名或序号> <秒数> |
主动冻结某个节点,到期自动恢复。上游要维护、或想临时把流量挪走时用 | freeze add auto-strong gpt-4o 600 |
freeze rm <虚拟模型> <模型名或序号> |
解冻指定的那一个节点。节点被自动避险冻上、但你确认它其实已经好了时用 | freeze rm auto-strong 2 |
freeze clear |
一把清空所有冻结记录,所有候选立刻可用 | freeze clear |
节点可以用真实模型名指定(从日志或横幅里直接抄最方便),也可以用 vm 命令里看到的序号(同名节点多时更准)。按模型名匹配到多个时会全部冻上,并额外提示一句喵。
set <字段> <值> 改 server 段的单个数值字段,能改的有 stall_timeout、stream_timeout、nonstream_timeout、connect_timeout、min_content_chars、auto_hedge_threshold、auto_hedge_minutes、reload_poll_interval、port。字段名打错时会把当前允许的完整列表打出来,敲 help 也能看到喵。
除了 port 要重启才生效(socket 启动时就绑好了),其余都立即生效喵。
set auto_hedge_threshold 3
set min_content_chars 20
set stream_timeout 600
敲已经退役的字段名(request_timeout、first_content_timeout)会告诉你现在该改哪一项,不会只回一句「不认识」喵。
| 命令 | 说明 | 例子 |
|---|---|---|
reload |
从磁盘重新加载配置并校验,手改了文件想立刻应用时用 | reload |
save |
校验当前磁盘配置并重新格式化写回,用于确认配置能被正确序列化 | save |
help |
显示完整帮助(也可以敲 ? 或 h) |
help |
quit |
优雅停机,退出整个代理进程(也可以敲 exit 或 q) |
quit |
提示符下方常驻一条横幅,每秒自动刷新,按 虚拟模型/节点model 的格式列出当前所有被冻结的节点和倒计时:
⚠ 下列模型达到了配额限制或异常自动避险!(2 个)
auto-strong/gpt-4o 将在 05 分 42 秒 后再次可用
auto-cheap/gpt-4o-mini 将在 09 分 13 秒 后再次可用
一个节点都没被冻结时显示 ✓ 所有节点可用,所以横幅位置固定、内容不会忽然出现或消失导致上下跳动喵。
横幅由 prompt_toolkit 渲染,刷新时完全不影响正在敲的命令,顺带还有命令历史(上下键)和 Tab 补全。stdin 不是终端时(管道、nohup)会自动退回朴素的 input() 模式,功能照常,只是没有常驻横幅喵。
代理会每隔 reload_poll_interval 秒看一眼 config.yaml 的修改时间,变了就自动重新加载并整体替换内存里的配置 —— 用编辑器改完保存,几秒后就生效,不用重启喵。
几个细节:
- 新配置有语法错或校验不通过时,保留旧配置继续服务,只打一条错误日志。不会因为编辑器保存了一个写坏的配置就让整个代理停摆喵。
- 轮询间隔是每一轮现读的,所以改了
reload_poll_interval当场生效。设成0关掉之后热重载任务并不退出,而是转成每 5 秒瞄一眼这个开关,所以在 REPL 里敲set reload_poll_interval 2还能重新打开,不需要重启喵。 host和port是例外,它们在启动时就绑好了 socket,改了要重启才生效喵。
REPL 里那些改配置的命令(rule add、cand set、set、save 等)写回文件时用的是 PyYAML,而 PyYAML 不保留注释 —— 所以第一次用这类命令之后,config.yaml 里原本那些中文注释就没了喵。
想保住注释的话,改配置时手改文件、然后靠热重载或 reload 命令生效,不要用 REPL 的写回类命令。config.example 是独立的模板文件,不会被 REPL 碰到,注释一直在喵。
⚠ 这个代理不校验客户端身份。 任何能访问到这个端口的人,都能免费用掉你所有上游的额度,而且能通过它把请求发到任意上游喵。
所以默认只绑 127.0.0.1。把 host 改成 0.0.0.0 或任何非回环地址,等于把所有上游额度和 key 的使用权免费送人,除非外层已经有防火墙或反向代理鉴权兜着。代理启动时如果发现绑的不是回环地址,会在终端打一条醒目的警告喵。
另外两点:
config.yaml里存着所有上游的真 api key,它已经在.gitignore里了。别把它从忽略列表里拿出来,也别把真 key 提交上去喵。- 要分享配置的话改
config.example,那份里面的 key 全是sk-REPLACE-ME-x这样的占位符喵。日志和 REPL 里的 key 一律脱敏成sk-abc***1234的形式,可以放心贴出来喵。
# 单元测试喵
pytest
# 端到端冒烟测试喵
python smoke_test.pypytest 跑的是 tests/ 下的单元测试(配置解析、规则匹配、冻结逻辑、流探测这些)喵。
smoke_test.py 不用任何 mock:它起四个真的 HTTP 服务(一个一律返回带恢复时间的 429 的坏上游、一个正常上游、一个吐几个字就挂着不动的卡流上游,以及 autoapi 本体),然后用真的 httpx 客户端打过去,用真 socket 验证故障转移确实发生了、流确实能收到内容喵。
