feat(tts): switch out retry for key:model rotation
This commit is contained in:
+27
-3
@@ -10,9 +10,17 @@ const { DONATE_WIDGET_KEY, GEMINI_TTS_API_KEY } = process.env as Record<
|
|||||||
string
|
string
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
const MODELS = [
|
||||||
|
"gemini-2.5-flash-preview-tts",
|
||||||
|
"gemini-3.1-flash-preview-tts",
|
||||||
|
] as const;
|
||||||
const API_KEYS = GEMINI_TTS_API_KEY.split(",")
|
const API_KEYS = GEMINI_TTS_API_KEY.split(",")
|
||||||
.map((k) => k.trim())
|
.map((k) => k.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
const POSSIBLE_CHOICES = MODELS.flatMap((m) =>
|
||||||
|
API_KEYS.map((k) => `${k}:${m}` as `${string}:${(typeof MODELS)[number]}`),
|
||||||
|
);
|
||||||
|
const MAX_TRIES = 10;
|
||||||
|
|
||||||
const Schema = z.object({
|
const Schema = z.object({
|
||||||
message: z.string().max(1000),
|
message: z.string().max(1000),
|
||||||
@@ -33,19 +41,33 @@ export async function GET(request: Request) {
|
|||||||
|
|
||||||
const textHash = hash(message).toString(36);
|
const textHash = hash(message).toString(36);
|
||||||
const cached = await redis!.get(`tts:${textHash}`);
|
const cached = await redis!.get(`tts:${textHash}`);
|
||||||
|
console.log(
|
||||||
|
`[TTS] ${textHash}:${cached ? "cached" : "fresh"} "${message.slice(0, 200)}${message.length > 200 ? "..." : ""}"`,
|
||||||
|
);
|
||||||
if (cached)
|
if (cached)
|
||||||
return new Response(pcmToWav(cached), {
|
return new Response(pcmToWav(cached), {
|
||||||
headers: { "Content-Type": "audio/wav" },
|
headers: { "Content-Type": "audio/wav" },
|
||||||
});
|
});
|
||||||
|
|
||||||
let audioData: string | undefined;
|
let audioData: string | undefined;
|
||||||
|
let currentRotation = await redis!
|
||||||
for (const apiKey of API_KEYS) {
|
.get("tts_key_rotation")
|
||||||
|
.then((e) => (e ? Number(e) : 0))
|
||||||
|
.catch(() => 0);
|
||||||
|
for (
|
||||||
|
let i = 0;
|
||||||
|
i < MAX_TRIES;
|
||||||
|
i++, currentRotation = (currentRotation + 1) % POSSIBLE_CHOICES.length
|
||||||
|
) {
|
||||||
|
const [apiKey, model] = POSSIBLE_CHOICES[currentRotation].split(":");
|
||||||
|
console.log(
|
||||||
|
`[TTS] Trying key ${apiKey.slice(0, 8)}:${model} (${currentRotation}/${POSSIBLE_CHOICES} #${i + 1})...`,
|
||||||
|
);
|
||||||
const client = new GoogleGenAI({ apiKey });
|
const client = new GoogleGenAI({ apiKey });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await client.models.generateContent({
|
const response = await client.models.generateContent({
|
||||||
model: "gemini-2.5-flash-preview-tts",
|
model,
|
||||||
contents: [{ parts: [{ text: message }] }],
|
contents: [{ parts: [{ text: message }] }],
|
||||||
config: {
|
config: {
|
||||||
responseModalities: ["AUDIO"],
|
responseModalities: ["AUDIO"],
|
||||||
@@ -65,11 +87,13 @@ export async function GET(request: Request) {
|
|||||||
console.error(`TTS API key failed: ${apiKey.slice(0, 8)}...`, e);
|
console.error(`TTS API key failed: ${apiKey.slice(0, 8)}...`, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log("[TTS] Got response?", audioData);
|
||||||
|
|
||||||
if (!audioData)
|
if (!audioData)
|
||||||
return Response.json({ error: "Response is empty" }, { status: 500 });
|
return Response.json({ error: "Response is empty" }, { status: 500 });
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
redis!.setex(`tts:${textHash}`, 604800, audioData);
|
redis!.setex(`tts:${textHash}`, 604800, audioData);
|
||||||
|
redis!.setex("tts_key_rotation", 604800, `${currentRotation}`);
|
||||||
});
|
});
|
||||||
return new Response(pcmToWav(audioData), {
|
return new Response(pcmToWav(audioData), {
|
||||||
headers: { "Content-Type": "audio/wav" },
|
headers: { "Content-Type": "audio/wav" },
|
||||||
|
|||||||
Reference in New Issue
Block a user