Files
sentinel-home-ai/fam-edge/tests/test_gemini_adapter.py
ericwyuan 1ad6af3d5d 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 情形。
2026-08-22 07:19:23 +08:00

71 lines
2.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from fam_edge.model_adapters.gemini_adapter import GeminiAdapter
def _cfg(**overrides):
base = {
"provider": "gemini",
"model_name": "gemini-flash-latest",
"api_key": "key-primary",
"circuit_breaker": {"enabled": False},
}
base.update(overrides)
return base
def test_single_key_backward_compat():
a = GeminiAdapter(_cfg())
assert a.api_keys == ["key-primary"]
assert a.api_key == "key-primary"
def test_extra_keys_appended_in_order():
a = GeminiAdapter(_cfg(extra_api_keys=["key-2", "key-3", "key-4"]))
assert a.api_keys == ["key-primary", "key-2", "key-3", "key-4"]
def test_extra_keys_dedup_against_primary():
a = GeminiAdapter(_cfg(extra_api_keys=["key-primary", "key-2"]))
assert a.api_keys == ["key-primary", "key-2"]
def test_missing_env_var_keys_are_dropped():
"""extra_api_keys 里未设置的 ${ENV_VAR} 解析为空字符串,不应该混进 api_keys 列表。"""
a = GeminiAdapter(_cfg(extra_api_keys=["${SOME_UNSET_GEMINI_KEY_VAR}", "key-2"]))
assert a.api_keys == ["key-primary", "key-2"]
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")]