g4f 自定义模型路由:config.yaml 按配额与错误率在多个 Provider 间自动切换
g4f 支持通过一个 config.yaml 文件定义自定义模型路由——你可以给客户端暴露一个命名模型(如 my-gpt4),g4f 会按照可用性、配额余量和近期错误次数,透明地把请求转发到一个或多个真实 Provider,并支持条件表达式控制回退顺序。读完本文,你将掌握 config.yaml 的完整文件格式与条件表达式语法、配额缓存与错误计数的底层机制,并能结合 g4f/providers/config_provider.py 的源码理解整个路由决策链路。
一、快速开始
1. 文件放在哪里
config.yaml 需要放在与 .har / .json cookie 文件相同的目录(即"cookies 目录"):
- 默认位置:
~/.config/g4f/cookies/config.yaml - 备选位置:
./har_and_cookies/config.yaml
从源码看,g4f/cookies.py 中的 CookiesConfig.cookies_dir 会优先选择工作目录下的 ./har_and_cookies(存在时),否则回退到用户配置目录下的 g4f/cookies。在 read_cookie_files() 的末尾,g4f 会在同一目录中查找 config.yaml 并加载:
# g4f/cookies.py(节选,L327-L332)
# Load custom model routing config (config.yaml)
try:
from .providers.config_provider import RouterConfig
config_path = os.path.join(dir_path, "config.yaml")
RouterConfig.load(config_path)
except Exception as e:
config_path = os.path.join(dir_path, "config.yaml")
debug.error(
f"config.yaml: Failed to load routing config from {config_path}:", e
)
也就是说,g4f 在读取 cookie 目录时会自动加载该文件——API 服务启动、或任何调用 read_cookie_files() 的场合都会触发,无需额外的启动参数。
2. 从客户端请求自定义模型名
路由定义好后,任何客户端直接使用你在 config.yaml 中声明的模型名即可:
from g4f.client import Client
client = Client()
response = client.chat.completions.create(
model="my-gpt4", # 在 config.yaml 中定义
messages=[{"role": "user", "content": "Hello!"}],
)
print(response.choices[0].message.content)
路由是如何被命中的?g4f/client/service.py 的 get_model_and_provider() 是入口:当调用方没有显式指定 provider 时,它会优先查询 config.yaml 的路由表:
# g4f/client/service.py(节选,L71-L87)
if not provider:
# Check config.yaml custom model routes first
if isinstance(model, str):
try:
from ..providers.config_provider import (
RouterConfig,
ConfigModelProvider,
)
route_config = RouterConfig.get(model)
if route_config is not None:
config_provider = ConfigModelProvider(route_config)
...
debug.log(f"Using config.yaml route for model {model!r}")
return model, config_provider
注意两点:路由检查优先于 ModelUtils.convert 等内置模型解析,因此自定义模型名可以覆盖任何同名内置模型;如果 RouterConfig.get() 抛异常,仅记录日志并继续走内置解析流程,路由故障不会阻断普通请求。
二、config.yaml 文件格式
models:
- name: "<model-name>" # 客户端使用的模型名
providers:
- provider: "<ProviderName>" # g4f 的 Provider 类名
model: "<provider-model>" # 传给该 Provider 的模型名
condition: "<expression>" # 可选——条件表达式
- provider: "..." # 回退 Provider(无条件 = 始终可用)
model: "..."
键说明
| 键 | 必填 | 说明 |
|---|---|---|
name |
✅ | 客户端使用的模型名。 |
providers |
✅ | 有序的 Provider 候选列表。 |
provider |
✅ | Provider 类名(如 "OpenaiAccount"、"PollinationsAI")。 |
model |
转发给 Provider 的模型名,默认继承路由的 name。 |
|
condition |
布尔条件表达式,控制该 Provider 何时可用。 |
源码中的解析规则
RouterConfig.load()(g4f/providers/config_provider.py)用 yaml.safe_load 解析文件,并做了大量容错:
- PyYAML 未安装:记录错误日志后静默跳过,不影响其他功能;
- 文件不存在:直接返回;
- YAML 解析失败:记录日志,不抛出异常;
- 顶层不是 mapping、条目缺
name、Provider 条目缺provider:静默跳过该条目; model缺省时回退为路由name:ProviderRouteConfig(provider=..., model=pentry.get("model", model_name), ...)——这一点由单元测试 test_provider_default_model_uses_route_name 明确验证。
解析后的数据结构是两个 dataclass(g4f/providers/config_provider.py):
@dataclass
class ProviderRouteConfig:
provider: str # Provider 类名
model: str = "" # 缺省继承路由 name
condition: Optional[str] = None # 缺省时始终可用
@dataclass
class ModelRouteConfig:
name: str # 客户端可见的模型名
providers: List[ProviderRouteConfig] = field(default_factory=list)
仓库内附带了一份可直接参考的完整示例:etc/examples/config.yaml。
三、条件表达式(condition)详解
condition 是一个在每次请求前求值的布尔表达式。它可以引用三类变量:
1. quota —— 完整的 Provider 配额字典
实现了 get_quota() 的 Provider 会返回一个各 Provider 自定义结构的字典,g4f 将其缓存后暴露给条件表达式。用点号记法访问任意字段:
| Provider | get_quota() 格式 |
条件示例 |
|---|---|---|
PollinationsAI |
{"balance": float} |
quota.balance > 0 |
Yupp |
{"credits": {"remaining": int, "total": int}} |
quota.credits.remaining > 100 |
PuterJS |
API 返回的原始计量 JSON | quota.total_requests < 1000 |
GeminiCLI |
{"buckets": [...]} |
error_count < 3 |
GithubCopilot |
用量明细字典 | error_count < 5 |
缺失的键会解析为 0.0,不会抛错(见 evaluate_condition 的取值逻辑:逐层 dict.get(part),取到 None 即截断为 0.0)。
2. balance —— 简写别名
balance 是 quota.balance 的便捷简写,为向后兼容保留,最适用于返回 {"balance": float} 的 PollinationsAI。对其他 Provider,建议显式使用 quota.* 形式。源码中还保留了另一个遗留别名:get_quota.balance 会被自动改写为 quota.balance(g4f/providers/config_provider.py),对应测试 test_get_quota_balance_alias。
3. error_count
该 Provider 在最近 1 小时内被记录的错误次数。超过 1 小时的错误会被自动修剪(滑动窗口实现见 ErrorCounter,窗口 window = 3600 秒,increment 和 get_count 都会顺带清理过期时间戳)。
4. 支持的运算符
| 运算符 | 含义 |
|---|---|
> < >= <= |
数值比较 |
== != |
相等 / 不等 |
and or not |
逻辑连接词 |
( ) |
分组 |
5. 条件表达式示例
# PollinationsAI – 使用 balance 简写
condition: "balance > 0"
condition: "balance > 0 or error_count < 3"
# Yupp – Provider 特定的嵌套字段
condition: "quota.credits.remaining > 0"
condition: "quota.credits.remaining > 0 or error_count < 3"
# 任意 Provider – 仅错误数条件通用
condition: "error_count < 3"
condition: "error_count == 0"
条件缺省或求值为 True 时,Provider 可用;求值为 False 时跳过该 Provider,继续尝试列表中的下一个。
底层实现:自研的安全求值器
condition 并不是 Python 表达式——源码明确"不会执行任意 Python"(模块文档)。evaluate_condition() 走的是正则分词 + 递归下降解析:
- 分词器
_TOKEN_RE识别:浮点/整数字面量、比较运算符、and/or/not关键字、标识符(支持点号与连字符,例如quota.models.gemini-3-flash-preview.remaining)、括号; - 解析函数按
or→and→not→比较的优先级逐层下降(_parse_or/_parse_and/_parse_not/_parse_comparison); - 求值时变量环境为三个键:
quota(完整字典)、balance(预取quota["balance"]的浮点值,缺省0.0)、error_count(整数值转浮点)。
这个设计带来的行为边界值得注意(均有单元测试覆盖,见 etc/unittest/config_provider.py):
- 未知变量会抛
ValueError(如unknown_var > 0);而在路由主流程中,条件解析出错时_check_condition会捕获异常、记日志并默认跳过该 Provider(g4f/providers/config_provider.py)——即"写错条件的代价是回退到下一个 Provider",而不是请求报错; - 空条件串返回
True(test_empty_condition_returns_true); quota为None时按空字典处理,balance解析为0.0;- 裸值视为真值判断:无运算符的原子(如单独的
balance)按bool()处理。
四、配额缓存(QuotaCache)
配额值通过 Provider 的 get_quota() 获取后,在内存中缓存 5 分钟(QuotaCache.ttl = 300 秒,可通过该类属性配置):
get()命中且未过期 → 直接返回;过期则删除旧条目并返回None,触发重新拉取;- 当某个 Provider 返回 HTTP 429(Too Many Requests) 时,该 Provider 的缓存条目立即失效,下一次路由决策会先拉取最新配额再做判断。
429 失效的具体触发点在 ConfigModelProvider 的请求循环里(g4f/providers/config_provider.py):
except Exception as e:
# On rate-limit errors invalidate the quota cache
from ..errors import RateLimitError
if isinstance(e, RateLimitError) or "429" in str(e):
debug.log(
f"config.yaml: Rate-limited by {provider_name}, "
"invalidating quota cache"
)
QuotaCache.invalidate(provider_name)
ErrorCounter.increment(provider_name)
单元测试 test_ttl_expiry、test_invalidate 分别验证了 TTL 过期与手动失效两类场景(etc/unittest/config_provider.py)。
五、错误计数(ErrorCounter)
每当路由中的某个 Provider 抛出异常,其错误计数器 +1;错误带时间戳记录,超过 1 小时(window = 3600 秒)的自动修剪,读写两侧都会清理。在条件中引用 error_count,可以避免反复重试已经持续失败的 Provider——这是与 quota 条件最实用的一种组合,例如 "balance > 0 or error_count < 3" 表达"余额充足,或近期失败还不够多,就先试试它"。
六、完整示例
以下配置与 etc/examples/config.yaml 一致,可直接复制后放入 cookies 目录:
# ~/.config/g4f/cookies/config.yaml
models:
# PollinationsAI: 使用 balance 简写
- name: "my-gpt4"
providers:
- provider: "OpenaiAccount"
model: "gpt-4o"
condition: "balance > 0 or error_count < 3"
- provider: "PollinationsAI"
model: "openai-large"
# Yupp: Provider 特定的嵌套配额字段
- name: "yupp-chat"
providers:
- provider: "Yupp"
model: "gpt-4o"
condition: "quota.credits.remaining > 0 or error_count < 3"
- provider: "PollinationsAI"
model: "openai-large"
# 通用: 仅错误数条件适用于任意 Provider
- name: "llama-fast"
providers:
- provider: "Groq"
model: "llama-3.3-70b"
condition: "error_count < 3"
- provider: "DeepInfra"
model: "meta-llama/Llama-3.3-70B-Instruct"
三条路由分别演示了三类典型写法:balance 简写、quota.* 嵌套字段、以及不依赖任何配额接口的 error_count 兜底条件(列表末尾无条件 Provider 充当最终回退)。
七、ConfigModelProvider 的路由执行流程
ConfigModelProvider(g4f/providers/config_provider.py)是对外伪装成一个普通 Provider 的路由器,声明了 working = True、supports_stream = True、supports_message_history = True,因此对流式客户端也透明。它的 create_async_generator 逐 Provider 执行以下步骤:
- 解析 Provider 类:先查
ProviderUtils.convert,再回退到Provider模块的同名属性;都找不到则记录日志并跳过(test_provider_not_found_skipped验证了这一点); - 拉取配额(带缓存):
_get_quota_cached()先查QuotaCache,未命中且 Provider 实现了get_quota()时才发起真实调用; - 条件判断:
_check_condition()取该 Provider 当前的error_count与配额字典求值;不满足则打日志跳过; - 声明上游 Provider 信息:先
yield一个ProviderInfo(含真实 Provider 名、url、label、模型名),让客户端的响应元数据保持透明; - API Key 注入:调用方传入的
api_key字典按 Provider 父类匹配,否则通过AuthManager.load_api_key(provider)从 cookies 目录加载;若AppConfig.disable_custom_api_key为真则跳过; - 执行请求:优先走 Provider 的
create_async_generator,否则回退到同步的create_completion,逐块yield给调用方; - 成功即返回:日志记录后
return; - 失败则继续下一候选:记录错误日志、对 429 失效配额缓存、
ErrorCounter.increment(),然后循环到下一个 Provider。
全部候选耗尽后:若曾有异常,抛出最后一个异常(保留原始错误信息);否则抛出带已尝试列表的 RuntimeError:
raise RuntimeError(
f"config.yaml: No provider succeeded for model {model!r}. "
f"Tried: {tried}"
)
八、Python API
路由机制在 g4f.providers.config_provider 中完整暴露,可用于自定义加载、检查和调试:
from g4f.providers.config_provider import (
RouterConfig, # 加载 / 查询路由
QuotaCache, # 检查 / 失效配额缓存
ErrorCounter, # 检查 / 重置错误计数
evaluate_condition, # 直接求值条件字符串
)
# 从自定义路径重新加载路由
RouterConfig.load("/path/to/config.yaml")
# 检查路由是否存在
route = RouterConfig.get("my-gpt4") # 返回 ModelRouteConfig 或 None
# 手动失效配额缓存(例如检测到 429 后)
QuotaCache.invalidate("OpenaiAccount")
# 查询错误计数
count = ErrorCounter.get_count("OpenaiAccount")
# 用完整的 Provider 特定配额字典直接求值条件(PollinationsAI)
ok = evaluate_condition("balance > 0 or error_count < 3", {"balance": 0.0}, 2)
# True
# Yupp 风格的嵌套配额
ok = evaluate_condition(
"quota.credits.remaining > 0",
{"credits": {"remaining": 500, "total": 5000}},
0,
)
# True
补充几点从源码与测试确认的行为:
RouterConfig.load()是整体替换语义——每次调用后用新文件内容重建routes字典(旧路由全部丢弃),并支持用RouterConfig.clear()清空;QuotaCache/ErrorCounter均为进程级类变量(内存态),进程重启后清零,测试用例通过修改QuotaCache.ttl与ErrorCounter.window为极小值来加速验证过期逻辑;evaluate_condition的三个参数分别对应条件中可用的quota字典、error_count整数值;quota传None等价于空字典。
九、依赖要求与降级行为
路由功能依赖 PyYAML:
pip install pyyaml
它已包含在完整的 requirements.txt 中。源码在模块顶部对 import yaml 做了 try/except 保护(g4f/providers/config_provider.py):PyYAML 缺失时,g4f 仅记录一条警告并跳过 config.yaml 加载,其余 Provider 与内置模型路由完全不受影响;YAML 语法错误同理——记日志、不抛异常。这使得 config.yaml 是一个可以随时添加、出错了随时删除的低风险配置。
十、小结与延伸阅读
config.yaml 路由是 g4f 内置 Provider 体系之上的一层声明式调度:以"命名模型 + 有序候选 + 布尔条件"三要素,把配额余量(5 分钟 TTL 缓存、429 即时失效)与错误率(1 小时滑动窗口)转化为可配置的回退策略。其核心代码集中在 g4f/providers/config_provider.py,入口接线在 g4f/cookies.py(加载)与 g4f/client/service.py(路由命中),可用示例与注释参见 etc/examples/config.yaml,行为回归测试参见 etc/unittest/config_provider.py,原始文档参见 docs/config-yaml-routing.md。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00