feat(fam-edge): Gemini key 分配改真随机 + 按项目名标注统计

用户明确要求: 每次调用应该是 1/n 概率的真随机抽取,而不是上一版的顺序轮转
(round-robin 在当前单消费者串行处理场景下已经是数学最优均匀分配,但用户想要
"数组里随机取一个,每次都是四分之一概率"这种更直观的随机语义)。_rotated_keys()
去掉持久的轮转下标,改用 random.randrange 每次调用随机选起点,仍保留"起点 key
全部模型失败就级联到下一个 key"的兜底逻辑不变。

同时新增 key_labels 配置:4 个 key 分别对应用户在 Google Cloud 上开的 4 个项目
(智能摄像头-1/2/3/4),model_calls 统计现在按项目名而不是泛化的 key1/key2
展示,ModelStats 页面的 Key 列/拆分逻辑同步改为按最后一个 "·" 分隔(原来写死
按 "·key" 前缀查找,项目名场景下不适用)。

新增/重写 7 个单测覆盖:随机起点级联顺序、randrange 调用范围正确性、大样本
随机分布均匀性、key_labels 默认值/自定义值/key 被丢弃时的对齐。
This commit is contained in:
ericwyuan
2026-08-22 07:37:33 +08:00
parent 22df28f2d3
commit 708b6365f4
4 changed files with 99 additions and 40 deletions

View File

@@ -11,10 +11,11 @@ const calls = ref([])
const loadError = ref('')
const loading = ref(true)
// Gemini 适配器把实际用的 key 编号拼进 model 字段(如 "gemini-flash-latest·key2"
// 这里拆出来单独显示成一个小徽章,而不是让用户在一长串字符串里自己找。
// Gemini 适配器把实际用的 key 标签拼进 model 字段(如 "gemini-flash-latest·智能摄像头-2"
// 未配置项目名时退回 "gemini-flash-latest·key2"这里拆出来单独显示成一个小徽章,
// 而不是让用户在一长串字符串里自己找。模型名本身不含 "·",按最后一个分隔符切开即可。
function splitKey(model) {
const idx = (model || '').lastIndexOf(key')
const idx = (model || '').lastIndexOf('·')
if (idx === -1) return { model, key: '' }
return { model: model.slice(0, idx), key: model.slice(idx + 1) }
}
@@ -126,8 +127,9 @@ onMounted(async () => {
</div>
<p class="text-[11px] text-text-mute">
统计来自甲骨文端每次云端模型请求的记录 30 分钟同步拉取到本地镜像Key 显示配置里第几个
Gemini Key 被用到key1= keykey2/3/4=备用 key不会显示密钥原文
统计来自甲骨文端每次云端模型请求的记录 30 分钟同步拉取到本地镜像Key 列显示该次调用
用的是哪个 Gemini 项目配置了 key_labels 就显示项目名"智能摄像头-2"未配置则显示
key1/key2/...不会显示密钥原文
失败原因取值429_quota配额耗尽/ timeout / 503_overload过载重试/ http_xxx / json_parse_failed
</p>
</template>