Как надежно реализовать мультимодальный прокси -сервер Gemini 2.0 в node.js и реагировать?Javascript

Форум по Javascript
Ответить
Anonymous
 Как надежно реализовать мультимодальный прокси -сервер Gemini 2.0 в node.js и реагировать?

Сообщение Anonymous »

Я строю систему голосового чата, используя мультимодальный WebSocket Gemini 2.0 в node.js для бэкэнда и отреагируйте на фронт. Google предоставляет пример репозитория здесь: https://github.com/google-gemini/multim ... eb-console.
Однако проблема в том, что ключ API является выставлен в подключении к WebSocket на фронте, который представляет собой риск безопасности. Чтобы смягчить это, я пытаюсь реализовать прокси -сервер в node.js, чтобы фронт общался с моим бэкэнд, и бэкэнд надежно подключается к API WebSocket Gemini. Попытка построить: < /p>
Frontend: отправляет голосовые данные (через WebSocket) на мой прокси -сервер node.js.
Backend (node.js): подключается к API Websocket Gemini с помощью API Websocket Gemini. Ключ API и реле данных /ответы на /с фронта. > Когда я отправляю голосовые данные, я не получаю никакого ответа от сервера gemini websocket.
Вот упрощенная версия моей бэкэнд -прокси -сокет реализации: < /p>
Бэкэнд -код < /h3>
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const cors = require('cors');
const WebSocket = require('ws');

const app = express();
app.use(cors());
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: '*',
methods: ['GET', 'POST'],
},
});

const GeminiKey = 'Your api key';
const model = `models/gemini-2.0-flash-exp`;
const GEMINI_API_URL =
'wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContent';

const clientGeminiSockets = new Map();
const clientRetryCounts = new Map();

const createGeminiConnection = (socketId) => {
const geminiSocket = new WebSocket(`${GEMINI_API_URL}?key=${GeminiKey}`);

geminiSocket.onopen = () => {
console.log(`Connected to Gemini API for client: ${socketId}`);
const setupMessage = {
setup: {
generation_config: { response_modalities: ['AUDIO'] },
model: model,
},
};
geminiSocket.send(JSON.stringify(setupMessage));
// Reset retry count on successful connection
clientRetryCounts.set(socketId, 0);
};

geminiSocket.onmessage = (event) => {
try {
console.log(
`Received message from Gemini API for client ${socketId}:`,
event.data,
);
// Handle binary data
if (event.data instanceof Buffer) {
console.log(
`Sending Gemini Audio data to client ${socketId}: binary data`,
);
//Send binary data
io.to(socketId).emit('geminiAudio', event.data.toString('base64'));
} else {
const message = JSON.parse(event.data);
io.to(socketId).emit('message', message);
}
} catch (err) {
console.error('Error handling gemini message:', err);
io.to(socketId).emit(
'message',
JSON.stringify({ error: 'Error handling Gemini API message' }),
);
}
};

geminiSocket.onerror = (error) => {
console.error(`Gemini API WebSocket Error for client ${socketId}:`, error);
io.to(socketId).emit(
'message',
JSON.stringify({ error: `Gemini API WebSocket Error: ${error.message}` }),
);
handleReconnection(socketId, geminiSocket);
};

geminiSocket.onclose = (event) => {
console.log(
`Gemini API WebSocket closed for client ${socketId}:`,
event.code,
event.reason,
);
io.to(socketId).emit(
'message',
JSON.stringify({
error: `Gemini API WebSocket Closed ${event.code}, ${event.reason}`,
}),
);
clientGeminiSockets.delete(socketId);
clientRetryCounts.delete(socketId);
};
clientGeminiSockets.set(socketId, geminiSocket);
return geminiSocket;
};

const handleReconnection = (socketId, geminiSocket) => {
const retryCount = clientRetryCounts.get(socketId) || 0;
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000); // Maximum 30 sec
console.log(
`Attempting to reconnect to Gemini for client: ${socketId} in ${delay}ms`,
);
setTimeout(() => {
if (!geminiSocket || geminiSocket.readyState !== WebSocket.OPEN) {
console.log(`Reconnecting Gemini API for client ${socketId}`);
createGeminiConnection(socketId);
}
}, delay);
clientRetryCounts.set(socketId, retryCount + 1);
};

io.on('connection', (socket) => {
console.log(`Client connected: ${socket.id}`);
let geminiSocket = createGeminiConnection(socket.id);
socket.on('message', (formattedMessage) => {
try {
console.log(`Received message from client ${socket.id}:`);
if (geminiSocket && geminiSocket.readyState === WebSocket.OPEN) {
const messageToSend = {
realtimeInput: {
mediaChunks: [
{
mimeType: 'audio/pcm;rate=16000',
data: formattedMessage.mediaChunks[0].data,
},
],
},
};
console.log(`Sending data to gemini API for client ${socket.id}:`);
geminiSocket.send(JSON.stringify(messageToSend));
} else {
console.error(
`Gemini API Websocket is not open for client ${socket.id}`,
);
socket.emit(
'message',
JSON.stringify({ error: 'Gemini API Websocket is not open' }),
);
}
} catch (error) {
console.error(
`Error forwarding message to Gemini API for client ${socket.id}:`,
error,
);
socket.emit(
'message',
JSON.stringify({ error: `Error forwarding message ${error.message}` }),
);
}
});

socket.on('disconnect', () => {
console.log(`Client disconnected: ${socket.id}`);
const geminiSocket = clientGeminiSockets.get(socket.id);
if (geminiSocket) {
geminiSocket.close();
clientGeminiSockets.delete(socket.id);
clientRetryCounts.delete(socket.id);
console.log(`Gemini connection closed for client: ${socket.id}`);
} else {
console.log(`No gemini connection found for client: ${socket.id}`);
}
});
});

const PORT = 5000;
server.listen(PORT, () => {
console.log(`Proxy server running on port ${PORT}`);
});
< /code>
### код фронта < /p>
import { useEffect, useRef, useState } from 'react';
import io from 'socket.io-client';

export default function Home() {
const [isRecording, setIsRecording] = useState(false);
const [socket, setSocket] = useState(null);
const audioRef = useRef(null);
const [geminiAudio, setGeminiAudio] = useState(null);
const audioContextRef = useRef(null);
const scriptProcessorRef = useRef(null);
const [connectionError, setConnectionError] = useState(null);

const startRecording = async () => {
if (!socket) {
console.error('Socket is not connected.');
return;
}
if (connectionError) {
console.log('There is a connection error, cannot start recording');
return;
}
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
audioContextRef.current = new AudioContext({ sampleRate: 16000 }); // Create audio context with 16kHz
const source = audioContextRef.current.createMediaStreamSource(stream);
scriptProcessorRef.current =
audioContextRef.current.createScriptProcessor(4096, 1, 1); // Create script processor
source.connect(scriptProcessorRef.current);
scriptProcessorRef.current.connect(audioContextRef.current.destination);

scriptProcessorRef.current.onaudioprocess = (event) => {
if (socket && socket.connected) {
const pcmData = event.inputBuffer.getChannelData(0); // Get PCM data
console.log('PCM data', pcmData);
const base64Audio = arrayBufferToBase64(pcmData.buffer);
console.log('base64Audio data', base64Audio);
const formattedMessage = {
mediaChunks: [
{
mimeType: 'audio/pcm;rate=16000',
data: base64Audio,
},
],
};
console.log('Formatted message sent to backend:', formattedMessage);
socket.emit('message', formattedMessage);
} else {
console.error('Socket is not connected');
}
};
setIsRecording(true);
} catch (err) {
console.error('Error recoring audio', err);
}
};

const stopRecording = () => {
if (scriptProcessorRef.current) {
scriptProcessorRef.current.disconnect();
scriptProcessorRef.current.onaudioprocess = null;
audioContextRef.current.close();
setIsRecording(false);
}
};

function arrayBufferToBase64(buffer) {
let binary = '';
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes);
}
return btoa(binary);
}

useEffect(() => {
const newSocket = io('http://localhost:5000'); // Replace with your backend URL

newSocket.on('connect', () => {
console.log('connected to websocket server');
setConnectionError(null);
});
newSocket.on('message', (message) => {
console.log('Received message:', message);
try {
const parsedMessage = JSON.parse(message);
if (parsedMessage.error) {
setConnectionError(parsedMessage.error);
console.log('Setting connection error', parsedMessage.error);
}
} catch (error) {
console.log('Message is not in JSON format');
}
});
newSocket.on('geminiAudio', (audioData) => {
console.log('Received geminiAudio data from backend:', audioData);
setGeminiAudio(audioData);
});
setSocket(newSocket);
return () => {
newSocket.disconnect();
};
}, []);

useEffect(() => {
if (geminiAudio) {
const audio = audioRef.current;
const audioBlob = new Blob(
[Uint8Array.from(atob(geminiAudio), (c) => c.charCodeAt(0))],
{ type: 'audio/webm' },
);
console.log('Audio blob created', audioBlob);
const audioUrl = URL.createObjectURL(audioBlob);
console.log('Audio URL created', audioUrl);
audio.src = audioUrl;
audio.play();
}
}, [geminiAudio]);

return (

Live Voice Conversation
{connectionError && (
{connectionError}
)}


{isRecording ? 'Stop Recording' : 'Start Recording'}


Your browser does not support the audio element.



);
}
< /code>
Что я попробовал: < /h3>
Проверено подключение к WebSocket - WebSocket успешно подключается как к API Frontend и Gemini.
Отправленные отправленные сообщения - Утверждено, что Frontend правильно отправляет голосовые данные, а бэкэнд пересылает его в Gemini.
проверяется на ответы - добавлена ​​console.log для печати любых входящих сообщений из WebSocket Gemini. Сообщения появляются в событии ошибки WebSocket. < /p>
Что я ожидал API.
Gemini должен обработать голосовой ввод и вернуть ответ, который мой бэкэнд отправит обратно на фронт.>

Подробнее здесь: https://stackoverflow.com/questions/793 ... e-js-and-r
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Javascript»