23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
이 글은 Devocean 에 올라온 Go로 만드는 실시간 음성 챗봇: OpenAI Realtime API를 가장 쉽게 쓰는 법 (Go routine + Go channel) 글을 읽다가 영감을 받아
Node.js의 Stream으로 만들어도 간단한 예제를 작성할 수 있을 것 같아서 글을 적어봅니다.
Node.js의 Stream 도 Go언어의 Channel 처럼 간단히 연결할 수 있으며, Node.js의 내장 Eventloop는 이미 강력한 비동기 처리를 지원하기 때문입니다.
이 글에서는 API 형식의 연결이 아니라 맥북에 있는 마이크를 통해 음성을 입력받아서 OpenAI Realtime Websocket API에 Stream으로 보내고,
API응답으로 오는 Websocket 데이터를 맥북의 스피커로 출력하는 예제를 작성해보도록 하겠습니다.
윈도우나 리눅스 환경에서는 테스트해보지 않았음을 미리 알려드립니다.
또한 맥북에 내장된 마이크와 스피커를 사용하는 경우 스피커로 나오는 출력이 마이크로 다시 입력되어 무한히 반복되는 loopback 문제가 있으므로 헤드폰 사용을 권장합니다.
또한 마이크와 스피커 사용을 위해서는 마이크와 스피커 권한이 필요합니다.
맥북 or PC
마이크가 내장된 이어폰 or 헤드폰 (권장)
Node.js 설치
OpenAI Realtime Websocket API를 사용하기 위한 환경
OpenAI 계정 생성
OpenAI API 키 발급
OpenAI Realtime Websocket API 활성화
먼저 node.js 환경을 설정해야 합니다. node.js 설치는 node.js 공식 홈페이지 에서 다운로드 받아 설치하면 됩니다.
여러 버전의 node.js를 설치하고 싶다면 nvm 을 사용하면 됩니다.
마이크가 내장된 헤드폰이 있다면 블루투스로 PC에 연결해주세요. 이걸로 준비는 끝났습니다.
OpenAI Realtime API 는 Websocket 프로토콜을 지원하므로,
여기서 우리는 mic의 출력을 OpenAI Realtime API의 입력 stream으로 연결하고, 응답으로 오는 websocket stream을 스피커로 그대로 연결해보겠습니다.
먼저 필요한 라이브러리를 설치합니다.
npm install dotenv ws mic @mastra/node-speaker그리고 필요한 라이브러리를 불러옵시다.
require("dotenv").config();
const WebSocket = require("ws");
const mic = require("mic");
const Speaker = require("@mastra/node-speaker");먼저 OpenAI Realtime API Websocket 에 연결하고 필요한 Handler를 등록합니다.
const ws = new WebSocket(
"wss://api.openai.com/v1/realtime?model=gpt-4o-mini-realtime-preview",
{
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
"OpenAI-Beta": "realtime=v1",
},
}
);
ws.on("open", () => {
console.log(
"mic 입력 stream을 열고, 마이크의 출력을 OpenAI Realtime API의 입력 stream으로 연결합니다."
);
});
ws.on("message", (message) => {
console.log("OpenAI Realtime API의 응답을 스피커로 출력합니다.");
});
ws.on("close", () => console.log("Connection closed"));
ws.on("error", (err) => console.error("WebSocket error:", err));이제 마이크의 출력을 OpenAI Realtime API의 Websocket 입력 stream으로 연결해봅시다.
const micInstance = mic({ rate: "16000", channels: "1", debug: false });
const micStream = micInstance.getAudioStream();
ws.on("open", () => {
console.log("Connected to OpenAI Realtime API");
micInstance.start();
micStream.on("data", (data) => {
ws.readyState === WebSocket.OPEN &&
ws.send(
JSON.stringify({
type: "input_audio_buffer.append",
audio: data.toString("base64"),
})
);
});
});이제 벌써 마지막입니다. 이제 마이크의 출력을 OpenAI Realtime API의 입력 stream으로 연결했으니, 이제 응답으로 오는 websocket stream을 스피커로 출력해봅시다.
const speaker = new Speaker({ channels: 2, bitDepth: 16, sampleRate: 16000 });
ws.on("message", (message) => {
try {
const { type, delta } = JSON.parse(message);
if (type === "response.audio.delta") {
const audioData = Buffer.from(delta, "base64");
audioData.length && speaker.write(audioData);
}
} catch (e) {
console.error("Message parsing error:", e);
}
});자 이제 모든 준비가 끝났습니다. 위에 적은 코드를 합쳐서 동작 가능한 코드를 완성해봅시다.
require("dotenv").config();
const WebSocket = require("ws");
const mic = require("mic");
const Speaker = require("@mastra/node-speaker");
const speaker = new Speaker({ channels: 2, bitDepth: 16, sampleRate: 16000 });
const micInstance = mic({ rate: "16000", channels: "1", debug: false });
const micStream = micInstance.getAudioStream();
const ws = new WebSocket(
"wss://api.openai.com/v1/realtime?model=gpt-4o-mini-realtime-preview",
{
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
"OpenAI-Beta": "realtime=v1",
},
}
);
ws.on("open", () => {
console.log("Connected to OpenAI Realtime API");
micInstance.start();
micStream.on("data", (data) => {
ws.readyState === WebSocket.OPEN &&
ws.send(
JSON.stringify({
type: "input_audio_buffer.append",
audio: data.toString("base64"),
})
);
});
});
ws.on("message", (message) => {
try {
const { type, delta } = JSON.parse(message);
if (type === "response.audio.delta") {
const audioData = Buffer.from(delta, "base64");
audioData.length && speaker.write(audioData);
}
} catch (e) {
console.error("Message parsing error:", e);
}
});
ws.on("close", () => console.log("Connection closed"));
ws.on("error", (err) => console.error("WebSocket error:", err));
process.on("SIGINT", () => {
console.log("Shutting down...");
ws.close();
micInstance.stop();
speaker.end();
process.exit(0);
});Go channel 대신 Node.js stream을 이용해서 Voice Chat을 만들어 봤습니다.
이렇게 간단하게 만들 수 있는 이유는 Node.js의 내장 Eventloop가 이미 강력한 비동기 처리를 지원하기 때문입니다.
Text 위주의 LLM 사용예가 많지만 Voice, Image, Video 등의 멀티미디어 처리가 앞으로 다양하게 사용될 것이라고 생각하여 이번 예제를 작성해봤습니다.
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.