docs: update

This commit is contained in:
WqyJh
2024-10-19 16:06:03 +08:00
parent a531e9c675
commit eea5c92d2c
5 changed files with 169 additions and 49 deletions
+125 -41
View File
@@ -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)
+13 -1
View File
@@ -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
View File
@@ -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 => ../
+9
View File
@@ -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
View File
@@ -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
}