Files

112 lines
4.2 KiB
JavaScript
Raw Permalink Normal View History

2026-02-16 00:30:59 +08:00
// ═══════════════════════════════════════════════════════════════════════════
// siliconflow.js - OpenAI-compatible Embedding + 多 Key 轮询
2026-02-17 15:24:39 +08:00
//
// 在 API Key 输入框中用逗号、分号、竖线或换行分隔多个 Key例如
// sk-aaa,sk-bbb,sk-ccc
// 每次调用自动轮询到下一个 Key并发请求会均匀分布到所有 Key 上。
2026-02-16 00:30:59 +08:00
// ═══════════════════════════════════════════════════════════════════════════
import { getVectorConfig } from '../../data/config.js';
2026-02-16 00:30:59 +08:00
const BASE_URL = 'https://api.siliconflow.cn';
const EMBEDDING_MODEL = 'BAAI/bge-m3';
2026-02-17 15:24:39 +08:00
// ★ 多 Key 轮询状态
let _keyIndex = 0;
function getEmbeddingApiConfig() {
const cfg = getVectorConfig() || {};
return cfg.embeddingApi || {
provider: 'siliconflow',
url: `${BASE_URL}/v1`,
key: '',
model: EMBEDDING_MODEL,
};
}
2026-02-17 15:24:39 +08:00
/**
* localStorage 解析所有 Key支持逗号分号竖线换行分隔
*/
function parseKeys(rawKey) {
2026-02-16 00:30:59 +08:00
try {
const keyStr = String(rawKey || '');
return keyStr
.split(/[,;|\n]+/)
.map(k => k.trim())
.filter(k => k.length > 0);
2026-02-16 00:30:59 +08:00
} catch { }
2026-02-17 15:24:39 +08:00
return [];
}
/**
* 获取下一个可用的 API Key轮询
* 每次调用返回不同的 Key自动循环
*/
export function getApiKey(rawKey = null) {
const keys = parseKeys(rawKey ?? getEmbeddingApiConfig().key);
2026-02-17 15:24:39 +08:00
if (!keys.length) return null;
if (keys.length === 1) return keys[0];
const idx = _keyIndex % keys.length;
const key = keys[idx];
_keyIndex = (_keyIndex + 1) % keys.length;
const masked = key.length > 10 ? key.slice(0, 6) + '***' + key.slice(-4) : '***';
console.log(`[SiliconFlow] 使用 Key ${idx + 1}/${keys.length}: ${masked}`);
return key;
2026-02-16 00:30:59 +08:00
}
2026-02-17 15:24:39 +08:00
/**
* 获取当前配置的 Key 数量供外部模块动态调整并发用
*/
export function getKeyCount() {
return Math.max(1, parseKeys(getEmbeddingApiConfig().key).length);
2026-02-17 15:24:39 +08:00
}
// ═══════════════════════════════════════════════════════════════════════════
// Embedding
// ═══════════════════════════════════════════════════════════════════════════
2026-02-16 00:30:59 +08:00
export async function embed(texts, options = {}) {
if (!texts?.length) return [];
const apiCfg = options.apiConfig || getEmbeddingApiConfig();
const key = getApiKey(apiCfg.key);
if (!key) throw new Error('未配置 Embedding API Key');
2026-02-16 00:30:59 +08:00
const { timeout = 30000, signal } = options;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const baseUrl = String(apiCfg.url || `${BASE_URL}/v1`).replace(/\/+$/, '');
const response = await fetch(`${baseUrl}/embeddings`, {
2026-02-16 00:30:59 +08:00
method: 'POST',
headers: {
'Authorization': `Bearer ${key}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: String(apiCfg.model || EMBEDDING_MODEL),
2026-02-16 00:30:59 +08:00
input: texts,
}),
signal: signal || controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
const errorText = await response.text().catch(() => '');
throw new Error(`Embedding ${response.status}: ${errorText.slice(0, 200)}`);
}
const data = await response.json();
return (data.data || [])
.sort((a, b) => a.index - b.index)
.map(item => Array.isArray(item.embedding) ? item.embedding : Array.from(item.embedding));
} finally {
clearTimeout(timeoutId);
}
}
export { EMBEDDING_MODEL as MODELS };