Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
226
backend/examples/realtime-openai-go/main_test.go
Normal file
226
backend/examples/realtime-openai-go/main_test.go
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
func TestRunSendsAndReceivesSpeechAudio(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
inputPath := filepath.Join(tmpDir, "input.wav")
|
||||
outputPath := filepath.Join(tmpDir, "response.wav")
|
||||
inputPCM := make([]byte, 9602)
|
||||
for index := range inputPCM {
|
||||
inputPCM[index] = byte(index % 251)
|
||||
}
|
||||
if errWrite := writePCM16WAV(inputPath, inputPCM); errWrite != nil {
|
||||
t.Fatalf("write input WAV: %v", errWrite)
|
||||
}
|
||||
responsePCM := []byte{10, 20, 30, 40, 50, 60, 70, 80}
|
||||
|
||||
websocketEvents := make(chan []string, 1)
|
||||
capturedInput := make(chan []byte, 1)
|
||||
upgrader := websocket.Upgrader{CheckOrigin: func(*http.Request) bool { return true }}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
switch request.URL.Path {
|
||||
case "/v1/realtime/client_secrets":
|
||||
if request.Method != http.MethodPost || request.Header.Get("Authorization") != "Bearer proxy-key" {
|
||||
http.Error(writer, "invalid client secret request", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
var body map[string]any
|
||||
if errDecode := json.NewDecoder(request.Body).Decode(&body); errDecode != nil || !validAudioSession(body) {
|
||||
http.Error(writer, "invalid audio session", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json")
|
||||
_, _ = writer.Write([]byte(`{
|
||||
"value":"ek_test",
|
||||
"expires_at":4102444800,
|
||||
"session":{"id":"sess_test","object":"realtime.session","type":"realtime","model":"gpt-realtime"}
|
||||
}`))
|
||||
case "/v1/realtime":
|
||||
if request.Header.Get("Authorization") != "Bearer ek_test" || request.URL.Query().Get("model") != defaultModel {
|
||||
http.Error(writer, "invalid websocket request", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
connection, errUpgrade := upgrader.Upgrade(writer, request, nil)
|
||||
if errUpgrade != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if errClose := connection.Close(); errClose != nil {
|
||||
t.Logf("close test websocket: %v", errClose)
|
||||
}
|
||||
}()
|
||||
|
||||
types := make([]string, 0, 4)
|
||||
var receivedPCM bytes.Buffer
|
||||
for {
|
||||
_, payload, errRead := connection.ReadMessage()
|
||||
if errRead != nil {
|
||||
return
|
||||
}
|
||||
var event struct {
|
||||
Type string `json:"type"`
|
||||
Audio string `json:"audio"`
|
||||
}
|
||||
if errUnmarshal := json.Unmarshal(payload, &event); errUnmarshal != nil {
|
||||
return
|
||||
}
|
||||
types = append(types, event.Type)
|
||||
if event.Type == "input_audio_buffer.append" {
|
||||
audio, errDecode := base64.StdEncoding.DecodeString(event.Audio)
|
||||
if errDecode != nil {
|
||||
return
|
||||
}
|
||||
_, _ = receivedPCM.Write(audio)
|
||||
}
|
||||
if event.Type == "response.create" {
|
||||
break
|
||||
}
|
||||
}
|
||||
websocketEvents <- types
|
||||
capturedInput <- append([]byte(nil), receivedPCM.Bytes()...)
|
||||
midpoint := len(responsePCM) / 2
|
||||
for _, audio := range [][]byte{responsePCM[:midpoint], responsePCM[midpoint:]} {
|
||||
if errWrite := connection.WriteJSON(map[string]any{
|
||||
"type": "response.output_audio.delta",
|
||||
"delta": base64.StdEncoding.EncodeToString(audio),
|
||||
}); errWrite != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if errWrite := connection.WriteJSON(map[string]any{"type": "response.output_audio_transcript.delta", "delta": "Voice response"}); errWrite != nil {
|
||||
return
|
||||
}
|
||||
_ = connection.WriteJSON(map[string]any{"type": "response.done", "response": map[string]any{"status": "completed"}})
|
||||
default:
|
||||
http.NotFound(writer, request)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
baseURL, errBaseURL := normalizeBaseURL(server.URL + "/v1/")
|
||||
if errBaseURL != nil {
|
||||
t.Fatalf("normalizeBaseURL() error = %v", errBaseURL)
|
||||
}
|
||||
var output bytes.Buffer
|
||||
errRun := run(context.Background(), appConfig{
|
||||
baseURL: baseURL,
|
||||
apiKey: "proxy-key",
|
||||
model: defaultModel,
|
||||
inputWAV: inputPath,
|
||||
outputWAV: outputPath,
|
||||
instructions: defaultInstructions,
|
||||
voice: defaultVoice,
|
||||
}, &output)
|
||||
if errRun != nil {
|
||||
t.Fatalf("run() error = %v", errRun)
|
||||
}
|
||||
if !strings.Contains(output.String(), "Sent") || !strings.Contains(output.String(), "Assistant transcript: Voice response") || !strings.Contains(output.String(), "Saved spoken response") {
|
||||
t.Fatalf("output = %q", output.String())
|
||||
}
|
||||
select {
|
||||
case events := <-websocketEvents:
|
||||
want := []string{"input_audio_buffer.append", "input_audio_buffer.append", "input_audio_buffer.commit", "response.create"}
|
||||
if strings.Join(events, ",") != strings.Join(want, ",") {
|
||||
t.Fatalf("client events = %v, want %v", events, want)
|
||||
}
|
||||
default:
|
||||
t.Fatal("websocket events were not captured")
|
||||
}
|
||||
select {
|
||||
case audio := <-capturedInput:
|
||||
if !bytes.Equal(audio, inputPCM) {
|
||||
t.Fatalf("input PCM mismatch: got %d bytes, want %d", len(audio), len(inputPCM))
|
||||
}
|
||||
default:
|
||||
t.Fatal("input audio was not captured")
|
||||
}
|
||||
actualResponsePCM, errRead := readPCM16WAV(outputPath)
|
||||
if errRead != nil {
|
||||
t.Fatalf("read output WAV: %v", errRead)
|
||||
}
|
||||
if !bytes.Equal(actualResponsePCM, responsePCM) {
|
||||
t.Fatalf("response PCM = %v, want %v", actualResponsePCM, responsePCM)
|
||||
}
|
||||
}
|
||||
|
||||
func validAudioSession(body map[string]any) bool {
|
||||
session, ok := body["session"].(map[string]any)
|
||||
if !ok || session["type"] != "realtime" || session["model"] != defaultModel {
|
||||
return false
|
||||
}
|
||||
modalities, ok := session["output_modalities"].([]any)
|
||||
if !ok || len(modalities) != 1 || modalities[0] != "audio" {
|
||||
return false
|
||||
}
|
||||
audio, ok := session["audio"].(map[string]any)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
input, inputOK := audio["input"].(map[string]any)
|
||||
output, outputOK := audio["output"].(map[string]any)
|
||||
if !inputOK || !outputOK {
|
||||
return false
|
||||
}
|
||||
inputFormat, inputFormatOK := input["format"].(map[string]any)
|
||||
outputFormat, outputFormatOK := output["format"].(map[string]any)
|
||||
if !inputFormatOK || !outputFormatOK {
|
||||
return false
|
||||
}
|
||||
_, turnDetectionPresent := input["turn_detection"]
|
||||
return inputFormat["type"] == "audio/pcm" && inputFormat["rate"] == float64(audioSampleRate) &&
|
||||
outputFormat["type"] == "audio/pcm" && outputFormat["rate"] == float64(audioSampleRate) &&
|
||||
output["voice"] == defaultVoice && turnDetectionPresent && input["turn_detection"] == nil
|
||||
}
|
||||
|
||||
func TestNormalizeBaseURLAddsV1(t *testing.T) {
|
||||
baseURL, errNormalize := normalizeBaseURL("http://127.0.0.1:8317/")
|
||||
if errNormalize != nil {
|
||||
t.Fatalf("normalizeBaseURL() error = %v", errNormalize)
|
||||
}
|
||||
if baseURL != "http://127.0.0.1:8317/v1" {
|
||||
t.Fatalf("baseURL = %q", baseURL)
|
||||
}
|
||||
websocketURL, errWebsocketURL := realtimeWebsocketURL(baseURL, defaultModel)
|
||||
if errWebsocketURL != nil {
|
||||
t.Fatalf("realtimeWebsocketURL() error = %v", errWebsocketURL)
|
||||
}
|
||||
wantWebsocketURL := "ws://127.0.0.1:8317/v1/realtime?model=" + defaultModel
|
||||
if websocketURL != wantWebsocketURL {
|
||||
t.Fatalf("websocketURL = %q", websocketURL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadPCM16WAVRejectsWrongSampleRate(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "wrong-rate.wav")
|
||||
if errWrite := writePCM16WAV(path, []byte{1, 2, 3, 4}); errWrite != nil {
|
||||
t.Fatalf("writePCM16WAV() error = %v", errWrite)
|
||||
}
|
||||
payload, errRead := os.ReadFile(path)
|
||||
if errRead != nil {
|
||||
t.Fatalf("read WAV: %v", errRead)
|
||||
}
|
||||
payload[24] = 0x80
|
||||
payload[25] = 0xbb
|
||||
payload[26] = 0x00
|
||||
payload[27] = 0x00
|
||||
if errWrite := os.WriteFile(path, payload, 0o644); errWrite != nil {
|
||||
t.Fatalf("rewrite WAV: %v", errWrite)
|
||||
}
|
||||
if _, errRead = readPCM16WAV(path); errRead == nil || !strings.Contains(errRead.Error(), "24000") {
|
||||
t.Fatalf("readPCM16WAV() error = %v", errRead)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue