mirror of
https://github.com/hanzoai/go-openai-realtime.git
synced 2026-09-30 21:54:42 +00:00
feat: add examples
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
module github.com/WqyJh/go-openai-realtime/examples
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/WqyJh/go-openai-realtime v0.0.0
|
||||
github.com/faiface/beep v1.1.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/coder/websocket v1.8.12 // indirect
|
||||
github.com/hajimehoshi/oto v0.7.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/sashabaranov/go-openai v1.32.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 // indirect
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 // indirect
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 // indirect
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 // indirect
|
||||
)
|
||||
|
||||
replace github.com/WqyJh/go-openai-realtime => ../
|
||||
@@ -0,0 +1,45 @@
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
|
||||
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c=
|
||||
github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
|
||||
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
|
||||
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
|
||||
github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
|
||||
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
|
||||
github.com/hajimehoshi/oto v0.7.1 h1:I7maFPz5MBCwiutOrz++DLdbr4rTzBsbBuV2VpgU9kk=
|
||||
github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos=
|
||||
github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
|
||||
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA=
|
||||
github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk=
|
||||
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU=
|
||||
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/sashabaranov/go-openai v1.32.0 h1:Yk3iE9moX3RBXxrof3OBtUBrE7qZR0zF9ebsoO4zVzI=
|
||||
github.com/sashabaranov/go-openai v1.32.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 h1:idBdZTd9UioThJp8KpM/rTSinK/ChZFBE43/WtIy8zg=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 h1:vyLBGJPIl9ZYbcQFM2USFmJBK6KI+t+z6jL0lbwjrnc=
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
@@ -0,0 +1,9 @@
|
||||
# go-openai-realtime example for text-only
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY=<your openai api key>
|
||||
export SOCKS_PROXY=<your socks proxy> # this optional
|
||||
go run .
|
||||
```
|
||||
@@ -0,0 +1,100 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
openairt "github.com/WqyJh/go-openai-realtime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
socksProxy := os.Getenv("SOCKS_PROXY")
|
||||
if socksProxy != "" {
|
||||
proxyURL, err := url.Parse(socksProxy)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
http.DefaultTransport = &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyURL),
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
client := openairt.NewClient(os.Getenv("OPENAI_API_KEY"))
|
||||
conn, err := client.Connect(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Teletype response
|
||||
responseDeltaHandler := func(ctx context.Context, event openairt.ServerEvent) {
|
||||
switch event.ServerEventType() {
|
||||
case openairt.ServerEventTypeResponseTextDelta:
|
||||
fmt.Printf(event.(openairt.ResponseTextDeltaEvent).Delta)
|
||||
}
|
||||
}
|
||||
|
||||
// Full response
|
||||
responseHandler := func(ctx context.Context, event openairt.ServerEvent) {
|
||||
switch event.ServerEventType() {
|
||||
case openairt.ServerEventTypeResponseDone:
|
||||
fmt.Printf("\n\n[full] %s\n\n", event.(openairt.ResponseDoneEvent).Response.Output[0].Content[0].Text)
|
||||
fmt.Print("> ")
|
||||
}
|
||||
}
|
||||
|
||||
connHandler := openairt.NewConnHandler(ctx, conn, responseDeltaHandler, responseHandler)
|
||||
connHandler.Start()
|
||||
defer func() {
|
||||
connHandler.Stop()
|
||||
if err := connHandler.Err(); err != nil {
|
||||
log.Printf("connHandler.Err(): %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = conn.SendMessage(ctx, &openairt.SessionUpdateEvent{
|
||||
Session: openairt.ClientSession{
|
||||
Modalities: []openairt.Modality{openairt.ModalityText},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("Conversation")
|
||||
fmt.Println("---------------------")
|
||||
fmt.Print("> ")
|
||||
s := bufio.NewScanner(os.Stdin)
|
||||
for s.Scan() {
|
||||
if s.Text() == "exit" || s.Text() == "quit" {
|
||||
break
|
||||
}
|
||||
conn.SendMessage(ctx, &openairt.ConversationItemCreateEvent{
|
||||
Item: openairt.MessageItem{
|
||||
ID: openairt.GenerateId("msg_", 10),
|
||||
Status: openairt.ItemStatusCompleted,
|
||||
Type: openairt.MessageItemTypeMessage,
|
||||
Role: openairt.MessageRoleUser,
|
||||
Content: []openairt.MessageContentPart{
|
||||
{
|
||||
Type: openairt.MessageContentTypeInputText,
|
||||
Text: s.Text(),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
conn.SendMessage(ctx, &openairt.ResponseCreateEvent{
|
||||
Response: openairt.ResponseCreateParams{
|
||||
Modalities: []openairt.Modality{openairt.ModalityText},
|
||||
MaxOutputTokens: 100,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# go-openai-realtime example for voice
|
||||
|
||||
## Prerequisite
|
||||
|
||||
This example needs portaudio library to play audio, please install it before running this library.
|
||||
|
||||
On macOS:
|
||||
```bash
|
||||
brew install pkg-config
|
||||
brew install portaudio
|
||||
```
|
||||
|
||||
On Linux:
|
||||
```bash
|
||||
apt-get install portaudio19-dev
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY=<your openai api key>
|
||||
export SOCKS_PROXY=<your socks proxy> # this optional
|
||||
go run .
|
||||
```
|
||||
@@ -0,0 +1,171 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
openairt "github.com/WqyJh/go-openai-realtime"
|
||||
"github.com/WqyJh/go-openai-realtime/examples/voice/pcm"
|
||||
"github.com/faiface/beep"
|
||||
"github.com/faiface/beep/speaker"
|
||||
)
|
||||
|
||||
func main() {
|
||||
socksProxy := os.Getenv("SOCKS_PROXY")
|
||||
if socksProxy != "" {
|
||||
proxyURL, err := url.Parse(socksProxy)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
http.DefaultTransport = &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyURL),
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
client := openairt.NewClient(os.Getenv("OPENAI_API_KEY"))
|
||||
conn, err := client.Connect(ctx, openairt.WithReadLimit(32768*2))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Teletype response
|
||||
responseDeltaHandler := func(ctx context.Context, event openairt.ServerEvent) {
|
||||
switch event.ServerEventType() {
|
||||
case openairt.ServerEventTypeResponseAudioTranscriptDelta:
|
||||
fmt.Printf(event.(openairt.ResponseAudioTranscriptDeltaEvent).Delta)
|
||||
}
|
||||
}
|
||||
|
||||
// Full response
|
||||
responseHandler := func(ctx context.Context, event openairt.ServerEvent) {
|
||||
switch event.ServerEventType() {
|
||||
case openairt.ServerEventTypeResponseAudioTranscriptDone:
|
||||
fmt.Printf("\n\n[full] %s\n\n", event.(openairt.ResponseAudioTranscriptDoneEvent).Transcript)
|
||||
fmt.Print("> ")
|
||||
}
|
||||
}
|
||||
|
||||
// All event handler
|
||||
allHandler := func(ctx context.Context, event openairt.ServerEvent) {
|
||||
// data, err := json.Marshal(event)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// fmt.Printf("[%s] %s\n\n", event.ServerEventType(), string(data))
|
||||
|
||||
// fmt.Printf("[%s]\n\n", event.ServerEventType())
|
||||
}
|
||||
|
||||
// Init speaker
|
||||
speaker.Init(24000, 24000/10)
|
||||
defer speaker.Close()
|
||||
|
||||
streamers := make(chan *pcm.PCMStream, 100)
|
||||
datas := make(chan []byte, 100)
|
||||
|
||||
// Audio response
|
||||
audioResponseHandler := func(ctx context.Context, event openairt.ServerEvent) {
|
||||
switch event.ServerEventType() {
|
||||
case openairt.ServerEventTypeResponseAudioDelta:
|
||||
msg := event.(openairt.ResponseAudioDeltaEvent)
|
||||
// log.Printf("audioResponseHandler: %v", delta)
|
||||
data, err := base64.StdEncoding.DecodeString(msg.Delta)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// os.WriteFile(fmt.Sprintf("%s.pcm", msg.EventID), data, 0o644)
|
||||
// streamer.Append(data)
|
||||
datas <- data
|
||||
case openairt.ServerEventTypeResponseAudioDone:
|
||||
fulldata := []byte{}
|
||||
close(datas)
|
||||
for data := range datas {
|
||||
fulldata = append(fulldata, data...)
|
||||
}
|
||||
streamer := pcm.NewPCMStream(
|
||||
fulldata,
|
||||
beep.Format{
|
||||
SampleRate: beep.SampleRate(24000),
|
||||
NumChannels: 2,
|
||||
Precision: 2, // 16-bit PCM
|
||||
},
|
||||
)
|
||||
datas = make(chan []byte, 100)
|
||||
streamers <- streamer
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case streamer := <-streamers:
|
||||
done := make(chan bool, 1)
|
||||
speaker.Play(beep.Seq(streamer), beep.Callback(func() {
|
||||
done <- true
|
||||
}))
|
||||
<-done // wait for done
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
connHandler := openairt.NewConnHandler(ctx, conn, allHandler, responseDeltaHandler, responseHandler, audioResponseHandler)
|
||||
connHandler.Start()
|
||||
defer func() {
|
||||
connHandler.Stop()
|
||||
if err := connHandler.Err(); err != nil {
|
||||
log.Printf("connHandler.Err(): %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = conn.SendMessage(ctx, &openairt.SessionUpdateEvent{
|
||||
Session: openairt.ClientSession{
|
||||
Modalities: []openairt.Modality{openairt.ModalityText, openairt.ModalityAudio},
|
||||
Voice: openairt.VoiceShimmer,
|
||||
OutputAudioFormat: openairt.AudioFormatPcm16,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("Conversation")
|
||||
fmt.Println("---------------------")
|
||||
fmt.Print("> ")
|
||||
s := bufio.NewScanner(os.Stdin)
|
||||
for s.Scan() {
|
||||
if s.Text() == "exit" || s.Text() == "quit" {
|
||||
break
|
||||
}
|
||||
conn.SendMessage(ctx, &openairt.ConversationItemCreateEvent{
|
||||
Item: openairt.MessageItem{
|
||||
ID: openairt.GenerateId("msg_", 10),
|
||||
Status: openairt.ItemStatusCompleted,
|
||||
Type: openairt.MessageItemTypeMessage,
|
||||
Role: openairt.MessageRoleUser,
|
||||
Content: []openairt.MessageContentPart{
|
||||
{
|
||||
Type: openairt.MessageContentTypeInputText,
|
||||
Text: s.Text(),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
conn.SendMessage(ctx, &openairt.ResponseCreateEvent{
|
||||
Response: openairt.ResponseCreateParams{
|
||||
Modalities: []openairt.Modality{openairt.ModalityText, openairt.ModalityAudio},
|
||||
MaxOutputTokens: 4000,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package pcm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/faiface/beep"
|
||||
)
|
||||
|
||||
type PCMStream struct {
|
||||
data []byte
|
||||
pos int
|
||||
format beep.Format
|
||||
}
|
||||
|
||||
func (s *PCMStream) Stream(samples [][2]float64) (n int, ok bool) {
|
||||
bytesPerSample := s.format.Precision
|
||||
for i := range samples {
|
||||
if s.pos+bytesPerSample > len(s.data) {
|
||||
return i, false
|
||||
}
|
||||
|
||||
// Read PCM16 sample
|
||||
sample := int16(binary.LittleEndian.Uint16(s.data[s.pos:]))
|
||||
s.pos += bytesPerSample
|
||||
|
||||
// Convert to float64 sample
|
||||
floatSample := float64(sample) / (1 << 15)
|
||||
|
||||
// Since it's mono, we copy the same sample to both left and right channels
|
||||
samples[i][0] = floatSample
|
||||
samples[i][1] = floatSample
|
||||
}
|
||||
return len(samples), true
|
||||
}
|
||||
|
||||
func (s *PCMStream) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPCMStream(data []byte, format beep.Format) *PCMStream {
|
||||
return &PCMStream{
|
||||
data: data,
|
||||
format: format,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user