mirror of
https://github.com/hanzoai/go-openai-realtime.git
synced 2026-09-30 21:54:42 +00:00
docs: update
This commit is contained in:
@@ -20,6 +20,8 @@ Currently, go-openai-realtime requires Go version 1.19 or greater.
|
||||
|
||||
## Usage
|
||||
|
||||
Connect to the OpenAI Realtime API. The default websocket library is [coder/websocket](https://github.com/coder/websocket).
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
@@ -40,59 +42,141 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Use another WebSocket dialer</summary>
|
||||
|
||||
Switch to another websocket dialer [gorilla/websocket](https://github.com/gorilla/websocket).
|
||||
|
||||
```go
|
||||
import (
|
||||
openairt "github.com/WqyJh/go-openai-realtime"
|
||||
gorilla "github.com/WqyJh/go-openai-realtime/contrib/ws-gorilla"
|
||||
)
|
||||
|
||||
func main() {
|
||||
dialer := gorilla.NewWebSocketDialer(gorilla.WebSocketOptions{})
|
||||
conn, err := client.Connect(ctx, openairt.WithDialer(dialer))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Send message</summary>
|
||||
|
||||
```go
|
||||
|
||||
import (
|
||||
openairt "github.com/WqyJh/go-openai-realtime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err = conn.SendMessage(ctx, &openairt.SessionUpdateEvent{
|
||||
Session: openairt.ClientSession{
|
||||
Modalities: []openairt.Modality{openairt.ModalityText},
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Read message</summary>
|
||||
|
||||
`ReadMessage` is a blocking method that reads the next message from the connection.
|
||||
It should be called in a standalone goroutine because it's blocking.
|
||||
If the returned error is Permanent, the future read operations on the same connection will not succeed,
|
||||
that means the connection is broken and should be closed or had already been closed.
|
||||
|
||||
```go
|
||||
for {
|
||||
msg, err := c.conn.ReadMessage(ctx)
|
||||
if err != nil {
|
||||
var permanent *PermanentError
|
||||
if errors.As(err, &permanent) {
|
||||
return permanent.Err
|
||||
}
|
||||
c.conn.logger.Warnf("read message temporary error: %+v", err)
|
||||
continue
|
||||
}
|
||||
// handle message
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Subscribe to events</summary>
|
||||
|
||||
`ConnHandler` is a helper that reads messages from the server in a standalone goroutine and calls the registered handlers.
|
||||
|
||||
Call `openairt.NewConnHandler` to create a `ConnHandler`, then call `Start` to start a new goroutine to read messages.
|
||||
You can specify only one handler to handle all events or specify multiple handlers.
|
||||
It's recommended to specify multiple handlers for different purposes.
|
||||
The registered handlers will be called in the order of registration.
|
||||
|
||||
```go
|
||||
connHandler := openairt.NewConnHandler(ctx, conn, handler1, handler2, ...)
|
||||
connHandler.Start()
|
||||
```
|
||||
|
||||
A handler is function that handle `ServerEvent`. Use `event.ServerEventType()` to determine the type of the event.
|
||||
Based on the event type, you can get the event data by type assertion.
|
||||
|
||||
|
||||
```go
|
||||
// Teletype response
|
||||
responseDeltaHandler := func(ctx context.Context, event openairt.ServerEvent) {
|
||||
switch event.ServerEventType() {
|
||||
case openairt.ServerEventTypeResponseTextDelta:
|
||||
fmt.Printf(event.(openairt.ResponseTextDeltaEvent).Delta)
|
||||
case openairt.ServerEventTypeResponseDone:
|
||||
fmt.Print("\n\n> ")
|
||||
}
|
||||
}
|
||||
|
||||
connHandler := openairt.NewConnHandler(context.Background(), conn, responseDeltaHandler)
|
||||
connHandler.Start()
|
||||
defer connHandler.Stop()
|
||||
|
||||
err = conn.SendMessage(context.Background(), &openairt.SessionUpdateEvent{
|
||||
Session: openairt.ClientSession{
|
||||
Modalities: []openairt.Modality{openairt.ModalityText},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Print("> ")
|
||||
s := bufio.NewScanner(os.Stdin)
|
||||
for s.Scan() {
|
||||
conn.SendMessage(context.Background(), &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(context.Background(), &openairt.ResponseCreateEvent{
|
||||
Response: openairt.ResponseCreateParams{
|
||||
Modalities: []openairt.Modality{openairt.ModalityText},
|
||||
MaxOutputTokens: 4000,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
There's no need to `Stop` the `ConnHandler`, it will exit when the connection is closed.
|
||||
If you want to wait for the `ConnHandler` to exit, you can use `Err()`. This will return
|
||||
a channel to receive the error.
|
||||
|
||||
Note that the receive of the error channel is blocking, so make sure not to call `conn.Close`
|
||||
after it, which cause deadlock.
|
||||
|
||||
```go
|
||||
conn.Close()
|
||||
err = <-connHandler.Err()
|
||||
if err != nil {
|
||||
log.Printf("connection error: %v", err)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
## More examples
|
||||
|
||||
- [Text-To-Text Example](./examples/text-only/README.md)
|
||||
- [Text-To-Voice Example](./examples/voice/text-voice/README.md)
|
||||
- [Voice-To-Voice Example](./examples/voice/voice-voice/README.md)
|
||||
|
||||
## WebSocket Adapter
|
||||
|
||||
The default WebSocket adapter is [coder/websocket](https://github.com/coder/websocket).
|
||||
There's also a [gorilla/websocket](https://github.com/gorilla/websocket) adapter.
|
||||
You can easily implement your own adapter by implementing the `WebSocketConn` interface and `WebSocketDialer` interface.
|
||||
|
||||
Supported adapters:
|
||||
- [coder/websocket](./ws_coder.go)
|
||||
- [gorilla/websocket](./contrib/ws-gorilla)
|
||||
|
||||
@@ -9,15 +9,20 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// WebSocketOptions is the options for WebSocketConn.
|
||||
type WebSocketOptions struct {
|
||||
// ReadLimit is the maximum size of a message in bytes. ReadLimit <= 0 means no limit. Default is -1.
|
||||
ReadLimit int64
|
||||
Dialer *websocket.Dialer
|
||||
// Dialer is the websocket dialer. If nil, the default dialer will be used.
|
||||
Dialer *websocket.Dialer
|
||||
}
|
||||
|
||||
// WebSocketDialer is a WebSocket connection dialer implementation based on gorilla/websocket.
|
||||
type WebSocketDialer struct {
|
||||
options WebSocketOptions
|
||||
}
|
||||
|
||||
// NewWebSocketDialer creates a new WebSocketDialer.
|
||||
func NewWebSocketDialer(options WebSocketOptions) *WebSocketDialer {
|
||||
if options.Dialer == nil {
|
||||
options.Dialer = websocket.DefaultDialer
|
||||
@@ -27,6 +32,7 @@ func NewWebSocketDialer(options WebSocketOptions) *WebSocketDialer {
|
||||
}
|
||||
}
|
||||
|
||||
// Dial establishes a new WebSocket connection to the given URL.
|
||||
func (d *WebSocketDialer) Dial(ctx context.Context, url string, header http.Header) (openairt.WebSocketConn, error) {
|
||||
conn, resp, err := d.options.Dialer.DialContext(ctx, url, header)
|
||||
if resp != nil && resp.Body != nil {
|
||||
@@ -48,12 +54,14 @@ func (d *WebSocketDialer) Dial(ctx context.Context, url string, header http.Head
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WebSocketConn is a WebSocket connection implementation based on gorilla/websocket.
|
||||
type WebSocketConn struct {
|
||||
conn *websocket.Conn
|
||||
resp *http.Response
|
||||
options WebSocketOptions
|
||||
}
|
||||
|
||||
// ReadMessage reads a message from the WebSocket connection.
|
||||
func (c *WebSocketConn) ReadMessage(ctx context.Context) (openairt.MessageType, []byte, error) {
|
||||
deadline, ok := ctx.Deadline()
|
||||
if ok {
|
||||
@@ -83,6 +91,7 @@ func (c *WebSocketConn) ReadMessage(ctx context.Context) (openairt.MessageType,
|
||||
}
|
||||
}
|
||||
|
||||
// WriteMessage writes a message to the WebSocket connection.
|
||||
func (c *WebSocketConn) WriteMessage(ctx context.Context, messageType openairt.MessageType, data []byte) error {
|
||||
deadline, ok := ctx.Deadline()
|
||||
if ok {
|
||||
@@ -99,10 +108,13 @@ func (c *WebSocketConn) WriteMessage(ctx context.Context, messageType openairt.M
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the WebSocket connection.
|
||||
func (c *WebSocketConn) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
// Response returns the *http.Response of the WebSocket connection.
|
||||
// Commonly used to get response headers.
|
||||
func (c *WebSocketConn) Response() *http.Response {
|
||||
return c.resp
|
||||
}
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ module github.com/WqyJh/go-openai-realtime/examples
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/WqyJh/go-openai-realtime v0.0.0
|
||||
github.com/WqyJh/go-openai-realtime v0.2.0-beta
|
||||
github.com/faiface/beep v1.1.0
|
||||
github.com/gordonklaus/portaudio v0.0.0-20230709114228-aafa478834f5
|
||||
github.com/sashabaranov/go-openai v1.32.0
|
||||
@@ -19,4 +19,4 @@ require (
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/WqyJh/go-openai-realtime => ../
|
||||
// replace github.com/WqyJh/go-openai-realtime => ../
|
||||
|
||||
@@ -2,6 +2,7 @@ package openairt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@@ -22,6 +23,8 @@ type WebSocketConn interface {
|
||||
// ReadMessage reads a message from the WebSocket connection.
|
||||
//
|
||||
// The ctx could be used to cancel the read operation. It's behavior depends on the underlying implementation.
|
||||
// If the read succeeds, the returned error should be nil, and the ctx's cancel/timeout shouldn't affect the
|
||||
// connection and future read operations.
|
||||
//
|
||||
// If the returned error is Permanent, the future read operations on the same connection will not succeed,
|
||||
// that means the connection is broken and should be closed or had already been closed.
|
||||
@@ -33,6 +36,7 @@ type WebSocketConn interface {
|
||||
// - If the underlying implementation is gorilla/websocket, the read operation will not be canceled
|
||||
// when the ctx is canceled before its deadline, it will keep reading until the ctx reaches deadline or the connection is closed.
|
||||
ReadMessage(ctx context.Context) (messageType MessageType, p []byte, err error)
|
||||
|
||||
// WriteMessage writes a message to the WebSocket connection.
|
||||
//
|
||||
// The ctx could be used to cancel the write operation. It's behavior depends on the underlying implementation.
|
||||
@@ -43,6 +47,7 @@ type WebSocketConn interface {
|
||||
// In general, once the ctx is canceled before write finishes, the write operation will be canceled and
|
||||
// the connection will be closed.
|
||||
WriteMessage(ctx context.Context, messageType MessageType, data []byte) error
|
||||
|
||||
// Close closes the WebSocket connection.
|
||||
Close() error
|
||||
|
||||
@@ -62,3 +67,7 @@ type WebSocketDialer interface {
|
||||
func DefaultDialer() WebSocketDialer {
|
||||
return NewCoderWebSocketDialer(CoderWebSocketOptions{})
|
||||
}
|
||||
|
||||
var (
|
||||
ErrUnsupportedMessageType = errors.New("unsupported message type")
|
||||
)
|
||||
|
||||
+20
-5
@@ -2,17 +2,13 @@ package openairt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnsupportedMessageType = errors.New("unsupported message type")
|
||||
)
|
||||
|
||||
// CoderWebSocketOptions is the options for CoderWebSocketConn.
|
||||
type CoderWebSocketOptions struct {
|
||||
// ReadLimit is the maximum size of a message in bytes. -1 means no limit. Default is -1.
|
||||
ReadLimit int64
|
||||
@@ -20,10 +16,12 @@ type CoderWebSocketOptions struct {
|
||||
DialOptions *websocket.DialOptions
|
||||
}
|
||||
|
||||
// CoderWebSocketDialer is a WebSocket dialer implementation based on coder/websocket.
|
||||
type CoderWebSocketDialer struct {
|
||||
options CoderWebSocketOptions
|
||||
}
|
||||
|
||||
// NewCoderWebSocketDialer creates a new CoderWebSocketDialer.
|
||||
func NewCoderWebSocketDialer(
|
||||
options CoderWebSocketOptions,
|
||||
) *CoderWebSocketDialer {
|
||||
@@ -36,6 +34,7 @@ func NewCoderWebSocketDialer(
|
||||
}
|
||||
}
|
||||
|
||||
// Dial establishes a new WebSocket connection to the given URL.
|
||||
func (d *CoderWebSocketDialer) Dial(ctx context.Context, url string, header http.Header) (WebSocketConn, error) {
|
||||
mergedHeader := http.Header{}
|
||||
for k, v := range header {
|
||||
@@ -68,12 +67,19 @@ func (d *CoderWebSocketDialer) Dial(ctx context.Context, url string, header http
|
||||
return &CoderWebSocketConn{conn: conn, options: d.options, resp: resp}, nil
|
||||
}
|
||||
|
||||
// CoderWebSocketConn is a WebSocket connection implementation based on coder/websocket.
|
||||
type CoderWebSocketConn struct {
|
||||
conn *websocket.Conn
|
||||
resp *http.Response
|
||||
options CoderWebSocketOptions
|
||||
}
|
||||
|
||||
// ReadMessage reads a message from the WebSocket connection.
|
||||
//
|
||||
// The ctx could be used to cancel the read operation. If the ctx is canceled or timedout,
|
||||
// the read operation will be canceled and the connection will be closed.
|
||||
//
|
||||
// If the returned error is Permanent, the future read operations on the same connection will not succeed.
|
||||
func (c *CoderWebSocketConn) ReadMessage(ctx context.Context) (MessageType, []byte, error) {
|
||||
messageType, r, err := c.conn.Reader(ctx)
|
||||
if err != nil {
|
||||
@@ -95,6 +101,12 @@ func (c *CoderWebSocketConn) ReadMessage(ctx context.Context) (MessageType, []by
|
||||
}
|
||||
}
|
||||
|
||||
// WriteMessage writes a message to the WebSocket connection.
|
||||
//
|
||||
// The ctx could be used to cancel the write operation. If the ctx is canceled or timedout,
|
||||
// the write operation will be canceled and the connection will be closed.
|
||||
//
|
||||
// If the returned error is Permanent, the future write operations on the same connection will not succeed.
|
||||
func (c *CoderWebSocketConn) WriteMessage(ctx context.Context, messageType MessageType, data []byte) error {
|
||||
switch messageType {
|
||||
case MessageText:
|
||||
@@ -106,10 +118,13 @@ func (c *CoderWebSocketConn) WriteMessage(ctx context.Context, messageType Messa
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the WebSocket connection.
|
||||
func (c *CoderWebSocketConn) Close() error {
|
||||
return c.conn.Close(websocket.StatusNormalClosure, "")
|
||||
}
|
||||
|
||||
// Response returns the *http.Response of the WebSocket connection.
|
||||
// Commonly used to get response headers.
|
||||
func (c *CoderWebSocketConn) Response() *http.Response {
|
||||
return c.resp
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user