首页
/ g4f 自定义模型路由:config.yaml 按配额与错误率在多个 Provider 间自动切换

g4f 自定义模型路由:config.yaml 按配额与错误率在多个 Provider 间自动切换

2026-09-03 16:53:18作者:柯茵沙

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.pyget_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 缺省时回退为路由 nameProviderRouteConfig(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 —— 简写别名

balancequota.balance 的便捷简写,为向后兼容保留,最适用于返回 {"balance": float}PollinationsAI。对其他 Provider,建议显式使用 quota.* 形式。源码中还保留了另一个遗留别名:get_quota.balance 会被自动改写为 quota.balanceg4f/providers/config_provider.py),对应测试 test_get_quota_balance_alias

3. error_count

该 Provider 在最近 1 小时内被记录的错误次数。超过 1 小时的错误会被自动修剪(滑动窗口实现见 ErrorCounter,窗口 window = 3600 秒,incrementget_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() 走的是正则分词 + 递归下降解析:

  1. 分词器 _TOKEN_RE 识别:浮点/整数字面量、比较运算符、and/or/not 关键字、标识符(支持点号与连字符,例如 quota.models.gemini-3-flash-preview.remaining)、括号;
  2. 解析函数按 orandnot比较 的优先级逐层下降(_parse_or / _parse_and / _parse_not / _parse_comparison);
  3. 求值时变量环境为三个键:quota(完整字典)、balance(预取 quota["balance"] 的浮点值,缺省 0.0)、error_count(整数值转浮点)。

这个设计带来的行为边界值得注意(均有单元测试覆盖,见 etc/unittest/config_provider.py):

  • 未知变量会抛 ValueError(如 unknown_var > 0);而在路由主流程中,条件解析出错时 _check_condition 会捕获异常、记日志并默认跳过该 Providerg4f/providers/config_provider.py)——即"写错条件的代价是回退到下一个 Provider",而不是请求报错;
  • 空条件串返回 Truetest_empty_condition_returns_true);
  • quotaNone 时按空字典处理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_expirytest_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 的路由执行流程

ConfigModelProviderg4f/providers/config_provider.py)是对外伪装成一个普通 Provider 的路由器,声明了 working = Truesupports_stream = Truesupports_message_history = True,因此对流式客户端也透明。它的 create_async_generator 逐 Provider 执行以下步骤:

  1. 解析 Provider 类:先查 ProviderUtils.convert,再回退到 Provider 模块的同名属性;都找不到则记录日志并跳过(test_provider_not_found_skipped 验证了这一点);
  2. 拉取配额(带缓存)_get_quota_cached() 先查 QuotaCache,未命中且 Provider 实现了 get_quota() 时才发起真实调用;
  3. 条件判断_check_condition() 取该 Provider 当前的 error_count 与配额字典求值;不满足则打日志跳过;
  4. 声明上游 Provider 信息:先 yield 一个 ProviderInfo(含真实 Provider 名、url、label、模型名),让客户端的响应元数据保持透明;
  5. API Key 注入:调用方传入的 api_key 字典按 Provider 父类匹配,否则通过 AuthManager.load_api_key(provider) 从 cookies 目录加载;若 AppConfig.disable_custom_api_key 为真则跳过;
  6. 执行请求:优先走 Provider 的 create_async_generator,否则回退到同步的 create_completion,逐块 yield 给调用方;
  7. 成功即返回:日志记录后 return
  8. 失败则继续下一候选:记录错误日志、对 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.ttlErrorCounter.window 为极小值来加速验证过期逻辑;
  • evaluate_condition 的三个参数分别对应条件中可用的 quota 字典、error_count 整数值;quotaNone 等价于空字典。

九、依赖要求与降级行为

路由功能依赖 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

登录后查看全文
热门项目推荐
相关项目推荐