fix(fam-edge): Gemini 多 key 从未真正轮转 - 流量全压在 key1

analyze_video/_generate_text 原来每次都从 api_keys[0] 开始遍历,只有当
key 的全部模型都失败才换下一个 key;由于 flash-lite 兜底基本总能在
key1 下成功,key2/3/4 实际零流量,配置的多 key 配额分散形同虚设。

新增 _rotated_keys():每次调用前记住上次轮转到的起点,按起点滚动排序
返回 key 列表并把起点推进到下一个,多次调用后流量均匀摊到全部 key。
model_calls 统计的 model 字段现在带上 ·key{n} 后缀,可以看出具体是哪个
key 在跑(ModelStats 页面对应拆出 Key 列展示)。

新增 4 个单测覆盖起点归零/逐次推进/回绕/单 key 情形。
This commit is contained in:
ericwyuan
2026-08-22 07:19:23 +08:00
parent 6b149a0dd0
commit 1ad6af3d5d
2 changed files with 95 additions and 37 deletions

View File

@@ -38,3 +38,33 @@ def test_no_keys_at_all():
a = GeminiAdapter(_cfg(api_key=""))
assert a.api_keys == []
assert a.api_key == ""
def test_rotated_keys_starts_at_zero_first_call():
a = GeminiAdapter(_cfg(extra_api_keys=["key-2", "key-3", "key-4"]))
order = a._rotated_keys()
assert [k for _, k in order] == ["key-primary", "key-2", "key-3", "key-4"]
assert [i for i, _ in order] == [0, 1, 2, 3]
def test_rotated_keys_advances_each_call_evenly():
"""核心诉求: 连续调用应该轮流从不同 key 起手,而不是每次都从 0 号开始
(旧实现的 bug0 号 key 的 fallback 模型通常最终能成功,导致其余 key 常年闲置)。"""
a = GeminiAdapter(_cfg(extra_api_keys=["key-2", "key-3", "key-4"]))
starts = [a._rotated_keys()[0][1] for _ in range(4)]
assert starts == ["key-primary", "key-2", "key-3", "key-4"]
# 转满一圈后应该回到起点
assert a._rotated_keys()[0][1] == "key-primary"
def test_rotated_keys_wraps_around_correctly():
a = GeminiAdapter(_cfg(extra_api_keys=["key-2", "key-3", "key-4"]))
a._key_rotation_idx = 3 # 手动模拟"上次从 4 号 key 起手"
order = a._rotated_keys()
assert [k for _, k in order] == ["key-4", "key-primary", "key-2", "key-3"]
def test_rotated_keys_single_key_never_errors():
a = GeminiAdapter(_cfg())
for _ in range(3):
assert a._rotated_keys() == [(0, "key-primary")]