feat: add all client events & server events

This commit is contained in:
WqyJh
2024-10-15 18:44:01 +08:00
parent 4434550c04
commit f5fb53c5bd
14 changed files with 2553 additions and 0 deletions
+3
View File
@@ -23,3 +23,6 @@ go.work.sum
# env file
.env
.vscode
.DS_Store
+122
View File
@@ -0,0 +1,122 @@
package openairt
import (
"context"
"net/http"
"net/url"
"github.com/coder/websocket"
)
const (
GPT4oRealtimePreview = "gpt-4o-realtime-preview"
GPT4oRealtimePreview20241001 = "gpt-4o-realtime-preview-2024-10-01"
)
// Client is OpenAI Realtime API client.
type Client struct {
config ClientConfig
}
// NewClient creates new OpenAI Realtime API client.
func NewClient(authToken string) *Client {
config := DefaultConfig(authToken)
return NewClientWithConfig(config)
}
// NewClientWithConfig creates new OpenAI Realtime API client for specified config.
func NewClientWithConfig(config ClientConfig) *Client {
return &Client{
config: config,
}
}
func (c *Client) getUrl(model string) string {
query := url.Values{}
if c.config.APIType == APITypeAzure {
query.Set("api-version", c.config.APIVersion)
query.Set("deployment", model)
} else {
query.Set("model", model)
}
return c.config.BaseURL + "?" + query.Encode()
}
func (c *Client) getHeaders() http.Header {
headers := http.Header{}
if c.config.APIType == APITypeAzure {
headers.Set("api-key", c.config.authToken)
} else {
headers.Set("Authorization", "Bearer "+c.config.authToken)
headers.Set("OpenAI-Beta", "realtime=v1")
}
return headers
}
type connectOption struct {
model string
dialOptions *websocket.DialOptions
readLimit int64
}
type ConnectOption func(*connectOption)
func WithModel(model string) ConnectOption {
return func(opts *connectOption) {
opts.model = model
}
}
func WithDialOptions(dialOptions *websocket.DialOptions) ConnectOption {
return func(opts *connectOption) {
opts.dialOptions = dialOptions
}
}
func WithReadLimit(limit int64) ConnectOption {
return func(opts *connectOption) {
opts.readLimit = limit
}
}
func (c *Client) Connect(ctx context.Context, opts ...ConnectOption) (*Conn, error) {
connectOpts := connectOption{
model: GPT4oRealtimePreview,
}
for _, opt := range opts {
opt(&connectOpts)
}
// default headers
headers := c.getHeaders()
// dialOptions
if connectOpts.dialOptions != nil {
for k, v := range connectOpts.dialOptions.HTTPHeader {
headers[k] = append(headers[k], v...)
}
connectOpts.dialOptions.HTTPHeader = headers
} else {
connectOpts.dialOptions = &websocket.DialOptions{
HTTPHeader: headers,
}
}
// get url by model
url := c.getUrl(connectOpts.model)
// dial
conn, _, err := websocket.Dial(ctx, url, connectOpts.dialOptions)
if err != nil {
return nil, err
}
// set readLimit
if connectOpts.readLimit > 0 {
conn.SetReadLimit(connectOpts.readLimit)
}
return &Conn{conn: conn}, nil
}
+243
View File
@@ -0,0 +1,243 @@
package openairt
import "encoding/json"
type ClientEventType string
const (
ClientEventTypeSessionUpdate ClientEventType = "session.update"
ClientEventTypeInputAudioBufferAppend ClientEventType = "input_audio_buffer.append"
ClientEventTypeInputAudioBufferCommit ClientEventType = "input_audio_buffer.commit"
ClientEventTypeInputAudioBufferClear ClientEventType = "input_audio_buffer.clear"
ClientEventTypeConversationItemCreate ClientEventType = "conversation.item.create"
ClientEventTypeConversationItemTruncate ClientEventType = "conversation.item.truncate"
ClientEventTypeConversationItemDelete ClientEventType = "conversation.item.delete"
ClientEventTypeResponseCreate ClientEventType = "response.create"
ClientEventTypeResponseCancel ClientEventType = "response.cancel"
)
type ClientEvent interface {
ClientEventType() ClientEventType
}
type EventBase struct {
EventID string `json:"event_id,omitempty"`
}
type ClientSession struct {
Modalities []Modality `json:"modalities,omitempty"`
Instructions string `json:"instructions,omitempty"`
Voice Voice `json:"voice,omitempty"`
InputAudioFormat AudioFormat `json:"input_audio_format,omitempty"`
OutputAudioFormat AudioFormat `json:"output_audio_format,omitempty"`
InputAudioTranscription *InputAudioTranscription `json:"input_audio_transcription,omitempty"`
TurnDetection *TurnDetection `json:"turn_detection,omitempty"`
Tools []Tool `json:"tools,omitempty"`
ToolChoice ToolChoiceInterface `json:"tool_choice,omitempty"`
Temperature *float32 `json:"temperature,omitempty"`
MaxOutputTokens int `json:"max_output_tokens,omitempty"`
}
type SessionUpdateEvent struct {
EventBase
Session ClientSession `json:"session"`
}
func (m SessionUpdateEvent) ClientEventType() ClientEventType {
return ClientEventTypeSessionUpdate
}
func (m SessionUpdateEvent) MarshalJSON() ([]byte, error) {
type sessionUpdateEvent SessionUpdateEvent
v := struct {
*sessionUpdateEvent
Type ClientEventType `json:"type"`
}{
sessionUpdateEvent: (*sessionUpdateEvent)(&m),
Type: m.ClientEventType(),
}
return json.Marshal(v)
}
type InputAudioBufferAppendEvent struct {
EventBase
Audio string `json:"audio"`
}
func (m InputAudioBufferAppendEvent) ClientEventType() ClientEventType {
return ClientEventTypeInputAudioBufferAppend
}
func (m InputAudioBufferAppendEvent) MarshalJSON() ([]byte, error) {
type inputAudioBufferAppendEvent InputAudioBufferAppendEvent
v := struct {
*inputAudioBufferAppendEvent
Type ClientEventType `json:"type"`
}{
inputAudioBufferAppendEvent: (*inputAudioBufferAppendEvent)(&m),
Type: m.ClientEventType(),
}
return json.Marshal(v)
}
type InputAudioBufferCommitEvent struct {
EventBase
}
func (m InputAudioBufferCommitEvent) ClientEventType() ClientEventType {
return ClientEventTypeInputAudioBufferCommit
}
func (m InputAudioBufferCommitEvent) MarshalJSON() ([]byte, error) {
type inputAudioBufferCommitEvent InputAudioBufferCommitEvent
v := struct {
*inputAudioBufferCommitEvent
Type ClientEventType `json:"type"`
}{
inputAudioBufferCommitEvent: (*inputAudioBufferCommitEvent)(&m),
Type: m.ClientEventType(),
}
return json.Marshal(v)
}
type InputAudioBufferClearEvent struct {
EventBase
}
func (m InputAudioBufferClearEvent) ClientEventType() ClientEventType {
return ClientEventTypeInputAudioBufferClear
}
func (m InputAudioBufferClearEvent) MarshalJSON() ([]byte, error) {
type inputAudioBufferClearEvent InputAudioBufferClearEvent
v := struct {
*inputAudioBufferClearEvent
Type ClientEventType `json:"type"`
}{
inputAudioBufferClearEvent: (*inputAudioBufferClearEvent)(&m),
Type: m.ClientEventType(),
}
return json.Marshal(v)
}
type ConversationItemCreateEvent struct {
EventBase
PreviousItemID string `json:"previous_item_id,omitempty"`
Item MessageItem `json:"item"`
}
func (m ConversationItemCreateEvent) ClientEventType() ClientEventType {
return ClientEventTypeConversationItemCreate
}
func (m ConversationItemCreateEvent) MarshalJSON() ([]byte, error) {
type conversationItemCreateEvent ConversationItemCreateEvent
v := struct {
*conversationItemCreateEvent
Type ClientEventType `json:"type"`
}{
conversationItemCreateEvent: (*conversationItemCreateEvent)(&m),
Type: m.ClientEventType(),
}
return json.Marshal(v)
}
type ConversationItemTruncateEvent struct {
EventBase
ItemID string `json:"item_id"`
ContentIndex int `json:"content_index"`
AudioEndMs int `json:"audio_end_ms"`
}
func (m ConversationItemTruncateEvent) ClientEventType() ClientEventType {
return ClientEventTypeConversationItemTruncate
}
func (m ConversationItemTruncateEvent) MarshalJSON() ([]byte, error) {
type conversationItemTruncateEvent ConversationItemTruncateEvent
v := struct {
*conversationItemTruncateEvent
Type ClientEventType `json:"type"`
}{
conversationItemTruncateEvent: (*conversationItemTruncateEvent)(&m),
Type: m.ClientEventType(),
}
return json.Marshal(v)
}
type ConversationItemDeleteEvent struct {
EventBase
ItemID string `json:"item_id"`
}
func (m ConversationItemDeleteEvent) ClientEventType() ClientEventType {
return ClientEventTypeConversationItemDelete
}
func (m ConversationItemDeleteEvent) MarshalJSON() ([]byte, error) {
type conversationItemDeleteEvent ConversationItemDeleteEvent
v := struct {
*conversationItemDeleteEvent
Type ClientEventType `json:"type"`
}{
conversationItemDeleteEvent: (*conversationItemDeleteEvent)(&m),
Type: m.ClientEventType(),
}
return json.Marshal(v)
}
type ResponseCreateParams struct {
Modalities []Modality `json:"modalities,omitempty"`
Instructions string `json:"instructions,omitempty"`
Voice Voice `json:"voice,omitempty"`
OutputAudioFormat AudioFormat `json:"output_audio_format,omitempty"`
Tools []Tool `json:"tools,omitempty"`
ToolChoice ToolChoiceInterface `json:"tool_choice,omitempty"`
Temperature *float32 `json:"temperature,omitempty"`
MaxOutputTokens int `json:"max_output_tokens,omitempty"`
}
type ResponseCreateEvent struct {
EventBase
Response ResponseCreateParams `json:"response"`
}
func (m ResponseCreateEvent) ClientEventType() ClientEventType {
return ClientEventTypeResponseCreate
}
func (m ResponseCreateEvent) MarshalJSON() ([]byte, error) {
type responseCreateEvent ResponseCreateEvent
v := struct {
*responseCreateEvent
Type ClientEventType `json:"type"`
}{
responseCreateEvent: (*responseCreateEvent)(&m),
Type: m.ClientEventType(),
}
return json.Marshal(v)
}
type ResponseCancelEvent struct {
EventBase
}
func (m ResponseCancelEvent) ClientEventType() ClientEventType {
return ClientEventTypeResponseCancel
}
func (m ResponseCancelEvent) MarshalJSON() ([]byte, error) {
type responseCancelEvent ResponseCancelEvent
v := struct {
*responseCancelEvent
Type ClientEventType `json:"type"`
}{
responseCancelEvent: (*responseCancelEvent)(&m),
Type: m.ClientEventType(),
}
return json.Marshal(v)
}
func MarshalClientEvent(event ClientEvent) ([]byte, error) {
return json.Marshal(event)
}
+356
View File
@@ -0,0 +1,356 @@
package openairt_test
import (
"encoding/json"
"testing"
openairt "github.com/WqyJh/go-openai-realtime"
"github.com/sashabaranov/go-openai"
"github.com/sashabaranov/go-openai/jsonschema"
"github.com/stretchr/testify/assert"
)
func TestSessionUpdateEvent(t *testing.T) {
temperature := float32(0.5)
message := openairt.SessionUpdateEvent{
EventBase: openairt.EventBase{
EventID: "test-id",
},
Session: openairt.ClientSession{
Modalities: []openairt.Modality{openairt.ModalityText, openairt.ModalityAudio},
Instructions: "test-instructions",
Voice: openairt.VoiceAlloy,
InputAudioFormat: openairt.AudioFormatPcm16,
OutputAudioFormat: openairt.AudioFormatG711Ulaw,
InputAudioTranscription: &openairt.InputAudioTranscription{
Enabled: true,
Model: openai.Whisper1,
},
TurnDetection: &openairt.TurnDetection{
Type: openairt.TurnDetectionTypeServerVad,
Threshold: 0.5,
PrefixPaddingMs: 1000,
SilenceDurationMs: 2000,
},
Tools: []openairt.Tool{
{
Type: openairt.ToolTypeFunction,
Name: "test-tool",
Parameters: jsonschema.Definition{
Type: "object",
Properties: map[string]jsonschema.Definition{
"location": {
Type: jsonschema.String,
},
},
Required: []string{"location"},
},
},
},
ToolChoice: openairt.ToolChoice{
Type: openairt.ToolTypeFunction,
Function: openairt.ToolFunction{
Name: "test-tool",
},
},
Temperature: &temperature,
MaxOutputTokens: 100,
},
}
// data, err := json.MarshalIndent(message, "", "\t")
data, err := json.Marshal(message)
assert.NoError(t, err)
expected := `{
"event_id": "test-id",
"session": {
"modalities": [
"text",
"audio"
],
"instructions": "test-instructions",
"voice": "alloy",
"input_audio_format": "pcm16",
"output_audio_format": "g711-ulaw",
"input_audio_transcription": {
"enabled": true,
"model": "whisper-1"
},
"turn_detection": {
"type": "server_vad",
"threshold": 0.5,
"prefix_padding_ms": 1000,
"silence_duration_ms": 2000
},
"tools": [
{
"type": "function",
"name": "test-tool",
"description": "",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string"
}
},
"required": [
"location"
]
}
}
],
"tool_choice": {
"type": "function",
"function": {
"name": "test-tool"
}
},
"temperature": 0.5,
"max_output_tokens": 100
},
"type": "session.update"
}`
assert.JSONEq(t, expected, string(data))
}
func TestSessionUpdateEventSimple(t *testing.T) {
temperature := float32(0.5)
message := openairt.SessionUpdateEvent{
Session: openairt.ClientSession{
Modalities: []openairt.Modality{openairt.ModalityText},
Instructions: "test-instructions",
Voice: openairt.VoiceAlloy,
InputAudioFormat: openairt.AudioFormatPcm16,
OutputAudioFormat: openairt.AudioFormatG711Ulaw,
InputAudioTranscription: nil,
TurnDetection: nil,
Tools: nil,
ToolChoice: nil,
Temperature: &temperature,
MaxOutputTokens: 100,
},
}
// data, err := json.MarshalIndent(message, "", "\t")
data, err := json.Marshal(message)
assert.NoError(t, err)
expected := `{
"session": {
"modalities": [
"text"
],
"instructions": "test-instructions",
"voice": "alloy",
"input_audio_format": "pcm16",
"output_audio_format": "g711-ulaw",
"temperature": 0.5,
"max_output_tokens": 100
},
"type": "session.update"
}`
assert.JSONEq(t, expected, string(data))
}
func TestInputAudioBufferAppendEvent(t *testing.T) {
message := openairt.InputAudioBufferAppendEvent{
Audio: "test-audio",
}
data, err := json.Marshal(message)
assert.NoError(t, err)
expected := `{"audio":"test-audio","type":"input_audio_buffer.append"}`
assert.JSONEq(t, expected, string(data))
message.EventBase.EventID = "test-id"
data, err = json.Marshal(message)
assert.NoError(t, err)
expected = `{"event_id":"test-id","audio":"test-audio","type":"input_audio_buffer.append"}`
assert.JSONEq(t, expected, string(data))
}
func TestInputAudioBufferCommitEvent(t *testing.T) {
message := openairt.InputAudioBufferCommitEvent{}
data, err := json.Marshal(message)
assert.NoError(t, err)
expected := `{"type":"input_audio_buffer.commit"}`
assert.JSONEq(t, expected, string(data))
message.EventBase.EventID = "test-id"
data, err = json.Marshal(message)
assert.NoError(t, err)
expected = `{"event_id":"test-id","type":"input_audio_buffer.commit"}`
assert.JSONEq(t, expected, string(data))
}
func TestInputAudioBufferClearEvent(t *testing.T) {
message := openairt.InputAudioBufferClearEvent{}
data, err := json.Marshal(message)
assert.NoError(t, err)
expected := `{"type":"input_audio_buffer.clear"}`
assert.JSONEq(t, expected, string(data))
message.EventBase.EventID = "test-id"
data, err = json.Marshal(message)
assert.NoError(t, err)
expected = `{"event_id":"test-id","type":"input_audio_buffer.clear"}`
assert.JSONEq(t, expected, string(data))
}
func TestConversationItemCreateEvent(t *testing.T) {
message := openairt.ConversationItemCreateEvent{
PreviousItemID: "test-previous-item-id",
Item: openairt.MessageItem{
ID: "test-id",
Type: openairt.MessageItemTypeMessage,
Status: openairt.ItemStatusCompleted,
Role: openairt.MessageRoleUser,
Content: []openairt.MessageContentPart{
{Type: openairt.MessageContentTypeText, Text: "test-content"},
{Type: openairt.MessageContentTypeAudio, Audio: "test-audio"},
{Type: openairt.MessageContentTypeTranscript, Transcript: "test-transcript"},
},
},
}
data, err := json.MarshalIndent(message, "", "\t")
assert.NoError(t, err)
expected := `{
"previous_item_id": "test-previous-item-id",
"item": {
"id": "test-id",
"type": "message",
"status": "completed",
"role": "user",
"content": [
{
"type": "text",
"text": "test-content"
},
{
"type": "audio",
"audio": "test-audio"
},
{
"type": "transcript",
"transcript": "test-transcript"
}
]
},
"type": "conversation.item.create"
}`
assert.JSONEq(t, expected, string(data))
message.EventBase.EventID = "test-id"
data, err = json.Marshal(message)
assert.NoError(t, err)
expected = `{
"event_id": "test-id",
"previous_item_id": "test-previous-item-id",
"item": {
"id": "test-id",
"type": "message",
"status": "completed",
"role": "user",
"content": [
{
"type": "text",
"text": "test-content"
},
{
"type": "audio",
"audio": "test-audio"
},
{
"type": "transcript",
"transcript": "test-transcript"
}
]
},
"type": "conversation.item.create"
}`
assert.JSONEq(t, expected, string(data))
}
func TestConversationItemTruncateEvent(t *testing.T) {
message := openairt.ConversationItemTruncateEvent{
ItemID: "test-item-id",
ContentIndex: 1,
AudioEndMs: 2000,
}
data, err := json.Marshal(message)
assert.NoError(t, err)
expected := `{"item_id":"test-item-id","content_index":1,"audio_end_ms":2000,"type":"conversation.item.truncate"}`
assert.JSONEq(t, expected, string(data))
message.EventBase.EventID = "test-id"
data, err = json.Marshal(message)
assert.NoError(t, err)
expected = `{"event_id":"test-id","item_id":"test-item-id","content_index":1,"audio_end_ms":2000,"type":"conversation.item.truncate"}`
assert.JSONEq(t, expected, string(data))
}
func TestConversationItemDeleteEvent(t *testing.T) {
message := openairt.ConversationItemDeleteEvent{
ItemID: "test-item-id",
}
data, err := json.Marshal(message)
assert.NoError(t, err)
expected := `{"item_id":"test-item-id","type":"conversation.item.delete"}`
assert.JSONEq(t, expected, string(data))
message.EventBase.EventID = "test-id"
data, err = json.Marshal(message)
assert.NoError(t, err)
expected = `{"event_id":"test-id","item_id":"test-item-id","type":"conversation.item.delete"}`
assert.JSONEq(t, expected, string(data))
}
func TestResponseCreateEvent(t *testing.T) {
message := openairt.ResponseCreateEvent{
Response: openairt.ResponseCreateParams{
Modalities: []openairt.Modality{openairt.ModalityText, openairt.ModalityAudio},
Instructions: "test-instructions",
Voice: openairt.VoiceAlloy,
OutputAudioFormat: openairt.AudioFormatG711Ulaw,
Tools: nil,
ToolChoice: openairt.ToolChoiceAuto,
Temperature: nil,
MaxOutputTokens: 100,
},
}
data, err := json.MarshalIndent(message, "", "\t")
assert.NoError(t, err)
expected := `{
"response": {
"modalities": [
"text",
"audio"
],
"instructions": "test-instructions",
"voice": "alloy",
"output_audio_format": "g711-ulaw",
"tool_choice": "auto",
"max_output_tokens": 100
},
"type": "response.create"
}`
assert.JSONEq(t, expected, string(data))
message.EventBase.EventID = "test-id"
data, err = json.Marshal(message)
assert.NoError(t, err)
expected = `{"event_id":"test-id","response":{"modalities":["text","audio"],"instructions":"test-instructions","voice":"alloy","output_audio_format":"g711-ulaw","tool_choice":"auto","max_output_tokens":100},"type":"response.create"}`
assert.JSONEq(t, expected, string(data))
}
func TestResponseCancelEvent(t *testing.T) {
message := openairt.ResponseCancelEvent{}
data, err := json.Marshal(message)
assert.NoError(t, err)
expected := `{"type":"response.cancel"}`
assert.JSONEq(t, expected, string(data))
message.EventBase.EventID = "test-id"
data, err = json.Marshal(message)
assert.NoError(t, err)
expected = `{"event_id":"test-id","type":"response.cancel"}`
assert.JSONEq(t, expected, string(data))
}
+36
View File
@@ -0,0 +1,36 @@
package openairt
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestClient(t *testing.T) {
mockToken := "test"
client := NewClient(mockToken)
assert.Equal(t, client.config.authToken, mockToken)
config := DefaultConfig(mockToken)
client = NewClientWithConfig(config)
assert.Equal(t, client.config.authToken, mockToken)
assert.Equal(t, client.config.BaseURL, openaiAPIURLv1)
assert.Equal(t, client.config.APIType, APITypeOpenAI)
url := client.getUrl("test-model")
assert.Equal(t, url, openaiAPIURLv1+"?model=test-model")
headers := client.getHeaders()
assert.Equal(t, headers.Get("Authorization"), "Bearer "+mockToken)
assert.Equal(t, headers.Get("OpenAI-Beta"), "realtime=v1")
azureUrl := "wss://my-eastus2-openai-resource.openai.azure.com/openai/realtime"
config = DefaultAzureConfig(mockToken, azureUrl)
client = NewClientWithConfig(config)
assert.Equal(t, client.config.authToken, mockToken)
assert.Equal(t, client.config.BaseURL, azureUrl)
assert.Equal(t, client.config.APIType, APITypeAzure)
assert.Equal(t, client.config.APIVersion, azureAPIVersion20241001Preview)
url = client.getUrl("test-model")
assert.Equal(t, url, azureUrl+"?api-version="+azureAPIVersion20241001Preview+"&deployment=test-model")
headers = client.getHeaders()
assert.Equal(t, headers.Get("api-key"), mockToken)
}
+45
View File
@@ -0,0 +1,45 @@
package openairt
type APIType string
const (
APITypeOpenAI APIType = "OPEN_AI"
APITypeAzure APIType = "AZURE"
)
const (
openaiAPIURLv1 = "wss://api.openai.com/v1/realtime"
)
const (
azureAPIVersion20241001Preview = "2024-10-01-preview"
)
type ClientConfig struct {
authToken string
BaseURL string
APIType APIType
APIVersion string // required when APIType is APITypeAzure
}
func DefaultConfig(authToken string) ClientConfig {
return ClientConfig{
authToken: authToken,
BaseURL: openaiAPIURLv1,
APIType: APITypeOpenAI,
}
}
func DefaultAzureConfig(apiKey, baseURL string) ClientConfig {
return ClientConfig{
authToken: apiKey,
BaseURL: baseURL,
APIType: APITypeAzure,
APIVersion: azureAPIVersion20241001Preview,
}
}
func (ClientConfig) String() string {
return "<OpenAI Realtime API ClientConfig>"
}
+98
View File
@@ -0,0 +1,98 @@
package openairt
import (
"context"
"fmt"
"log"
"sync"
"github.com/coder/websocket"
)
type ServerEventHandler func(ctx context.Context, event ServerEvent)
type Conn struct {
conn *websocket.Conn
}
func (c *Conn) Close() error {
return c.conn.Close(websocket.StatusNormalClosure, "")
}
func (c *Conn) SendMessage(ctx context.Context, msg ClientEvent) error {
data, err := MarshalClientEvent(msg)
if err != nil {
return err
}
return c.conn.Write(ctx, websocket.MessageText, data)
}
func (c *Conn) ReadMessage(ctx context.Context) (ServerEvent, error) {
messageType, data, err := c.conn.Read(ctx)
if err != nil {
return nil, err
}
if messageType != websocket.MessageText {
return nil, fmt.Errorf("expected text message, got %d", messageType)
}
event, err := UnmarshalServerEvent(data)
if err != nil {
return nil, err
}
return event, nil
}
type ConnHandler struct {
ctx context.Context
conn *Conn
handlers []ServerEventHandler
cancel context.CancelFunc
wg sync.WaitGroup
err error
}
func NewConnHandler(ctx context.Context, conn *Conn, handlers ...ServerEventHandler) *ConnHandler {
ctx, cancel := context.WithCancel(ctx)
return &ConnHandler{
ctx: ctx,
conn: conn,
handlers: handlers,
cancel: cancel,
}
}
func (c *ConnHandler) Start() {
c.wg.Add(1)
go func() {
defer c.wg.Done()
c.err = c.run()
}()
}
func (c *ConnHandler) run() error {
for {
select {
case <-c.ctx.Done():
return nil
default:
}
msg, err := c.conn.ReadMessage(c.ctx)
if err != nil {
log.Printf("ReadMessage: %v", err)
continue
}
for _, handler := range c.handlers {
handler(c.ctx, msg)
}
}
}
func (c *ConnHandler) Stop() {
c.cancel()
c.wg.Wait()
}
func (c *ConnHandler) Err() error {
return c.err
}
+15
View File
@@ -0,0 +1,15 @@
module github.com/WqyJh/go-openai-realtime
go 1.19
require (
github.com/coder/websocket v1.8.12
github.com/sashabaranov/go-openai v1.32.0
github.com/stretchr/testify v1.9.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+14
View File
@@ -0,0 +1,14 @@
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+372
View File
@@ -0,0 +1,372 @@
package openairt
import (
"encoding/json"
"fmt"
)
type ServerEventType string
const (
ServerEventTypeError ServerEventType = "error"
ServerEventTypeSessionCreated ServerEventType = "session.created"
ServerEventTypeSessionUpdated ServerEventType = "session.updated"
ServerEventTypeConversationCreated ServerEventType = "conversation.created"
ServerEventTypeInputAudioBufferCommitted ServerEventType = "input_audio_buffer.committed"
ServerEventTypeInputAudioBufferCleared ServerEventType = "input_audio_buffer.cleared"
ServerEventTypeInputAudioBufferSpeechStarted ServerEventType = "input_audio_buffer.speech_started"
ServerEventTypeInputAudioBufferSpeechStopped ServerEventType = "input_audio_buffer.speech_stopped"
ServerEventTypeConversationItemCreated ServerEventType = "conversation.item.created"
ServerEventTypeConversationItemInputAudioTranscriptionCompleted ServerEventType = "conversation.item.input_audio_transcription.completed"
ServerEventTypeConversationItemInputAudioTranscriptionFailed ServerEventType = "conversation.item.input_audio_transcription.failed"
ServerEventTypeConversationItemTruncated ServerEventType = "conversation.item.truncated"
ServerEventTypeConversationItemDeleted ServerEventType = "conversation.item.deleted"
ServerEventTypeResponseCreated ServerEventType = "response.created"
ServerEventTypeResponseDone ServerEventType = "response.done"
ServerEventTypeResponseOutputItemAdded ServerEventType = "response.output_item.added"
ServerEventTypeResponseOutputItemDone ServerEventType = "response.output_item.done"
ServerEventTypeResponseContentPartAdded ServerEventType = "response.content_part.added"
ServerEventTypeResponseContentPartDone ServerEventType = "response.content_part.done"
ServerEventTypeResponseTextDelta ServerEventType = "response.text.delta"
ServerEventTypeResponseTextDone ServerEventType = "response.text.done"
ServerEventTypeResponseAudioTranscriptDelta ServerEventType = "response.audio_transcript.delta"
ServerEventTypeResponseAudioTranscriptDone ServerEventType = "response.audio_transcript.done"
ServerEventTypeResponseAudioDelta ServerEventType = "response.audio.delta"
ServerEventTypeResponseAudioDone ServerEventType = "response.audio.done"
ServerEventTypeResponseFunctionCallArgumentsDelta ServerEventType = "response.function_call_arguments.delta"
ServerEventTypeResponseFunctionCallArgumentsDone ServerEventType = "response.function_call_arguments.done"
ServerEventTypeRateLimitsUpdated ServerEventType = "rate_limits.updated"
)
type ServerEvent interface {
ServerEventType() ServerEventType
}
type ServerEventBase struct {
EventID string `json:"event_id,omitempty"`
Type ServerEventType `json:"type"`
}
func (m ServerEventBase) ServerEventType() ServerEventType {
return m.Type
}
type ErrorEvent struct {
ServerEventBase
Error Error `json:"error"`
}
type SessionCreatedEvent struct {
ServerEventBase
Session ServerSession `json:"session"`
}
type SessionUpdatedEvent struct {
ServerEventBase
Session ServerSession `json:"session"`
}
type ConversationCreatedEvent struct {
ServerEventBase
Conversation Conversation `json:"conversation"`
}
type InputAudioBufferCommittedEvent struct {
ServerEventBase
PreviousItemID string `json:"previous_item_id,omitempty"`
ItemID string `json:"item_id"`
}
type InputAudioBufferClearedEvent struct {
ServerEventBase
}
type InputAudioBufferSpeechStartedEvent struct {
ServerEventBase
AudioStartMs int64 `json:"audio_start_ms"` // Milliseconds since the session started when speech was detected.
ItemID string `json:"item_id"`
}
type InputAudioBufferSpeechStoppedEvent struct {
ServerEventBase
AudioEndMs int64 `json:"audio_end_ms"` // Milliseconds since the session started when speech stopped.
ItemID string `json:"item_id"`
}
type ConversationItemCreatedEvent struct {
ServerEventBase
PreviousItemID string `json:"previous_item_id,omitempty"`
Item ResponseMessageItem `json:"item"`
}
type ConversationItemInputAudioTranscriptionCompletedEvent struct {
ServerEventBase
ItemID string `json:"item_id"`
ContentIndex int `json:"content_index"`
Transcript string `json:"transcript"`
}
type ConversationItemInputAudioTranscriptionFailedEvent struct {
ServerEventBase
ItemID string `json:"item_id"`
ContentIndex int `json:"content_index"`
Error Error `json:"error"`
}
type ConversationItemTruncatedEvent struct {
ServerEventBase
ItemID string `json:"item_id"` // The ID of the assistant message item that was truncated.
ContentIndex int `json:"content_index"` // The index of the content part that was truncated.
AudioEndMs int `json:"audio_end_ms"` // The duration up to which the audio was truncated, in milliseconds.
}
type ConversationItemDeletedEvent struct {
ServerEventBase
ItemID string `json:"item_id"` // The ID of the item that was deleted.
}
type ResponseCreatedEvent struct {
ServerEventBase
Response Response `json:"response"`
}
type ResponseDoneEvent struct {
ServerEventBase
Response Response `json:"response"`
}
type ResponseOutputItemAddedEvent struct {
ServerEventBase
ResponseID string `json:"response_id"`
OutputIndex int `json:"output_index"`
Item ResponseMessageItem `json:"item"`
}
type ResponseOutputItemDoneEvent struct {
ServerEventBase
ResponseID string `json:"response_id"`
OutputIndex int `json:"output_index"`
Item ResponseMessageItem `json:"item"`
}
type ResponseContentPartAddedEvent struct {
ServerEventBase
ResponseID string `json:"response_id"`
ItemID string `json:"item_id"`
OutputIndex int `json:"output_index"`
ContentIndex int `json:"content_index"`
Part MessageContentPart `json:"part"`
}
type ResponseContentPartDoneEvent struct {
ServerEventBase
ResponseID string `json:"response_id"`
ItemID string `json:"item_id"`
OutputIndex int `json:"output_index"`
ContentIndex int `json:"content_index"`
Part MessageContentPart `json:"part"`
}
type ResponseTextDeltaEvent struct {
ServerEventBase
ResponseID string `json:"response_id"`
ItemID string `json:"item_id"`
OutputIndex int `json:"output_index"`
ContentIndex int `json:"content_index"`
Delta string `json:"delta"`
}
type ResponseTextDoneEvent struct {
ServerEventBase
ResponseID string `json:"response_id"`
ItemID string `json:"item_id"`
OutputIndex int `json:"output_index"`
ContentIndex int `json:"content_index"`
Text string `json:"text"`
}
type ResponseAudioTranscriptDeltaEvent struct {
ServerEventBase
ResponseID string `json:"response_id"`
ItemID string `json:"item_id"`
OutputIndex int `json:"output_index"`
ContentIndex int `json:"content_index"`
Delta string `json:"delta"`
}
type ResponseAudioTranscriptDoneEvent struct {
ServerEventBase
ResponseID string `json:"response_id"`
ItemID string `json:"item_id"`
OutputIndex int `json:"output_index"`
ContentIndex int `json:"content_index"`
Transcript string `json:"transcript"`
}
type ResponseAudioDeltaEvent struct {
ServerEventBase
ResponseID string `json:"response_id"`
ItemID string `json:"item_id"`
OutputIndex int `json:"output_index"`
ContentIndex int `json:"content_index"`
Delta string `json:"delta"`
}
type ResponseAudioDoneEvent struct {
ServerEventBase
ResponseID string `json:"response_id"`
ItemID string `json:"item_id"`
OutputIndex int `json:"output_index"`
ContentIndex int `json:"content_index"`
}
type ResponseFunctionCallArgumentsDeltaEvent struct {
ServerEventBase
ResponseID string `json:"response_id"`
ItemID string `json:"item_id"`
OutputIndex int `json:"output_index"`
CallID string `json:"call_id"`
Delta string `json:"delta"`
}
type ResponseFunctionCallArgumentsDoneEvent struct {
ServerEventBase
ResponseID string `json:"response_id"`
ItemID string `json:"item_id"`
OutputIndex int `json:"output_index"`
CallID string `json:"call_id"`
Name string `json:"name"`
Arguments string `json:"arguments"`
}
type RateLimitsUpdatedEvent struct {
ServerEventBase
RateLimits []RateLimit `json:"rate_limits"`
}
func UnmarshalServerEvent(data []byte) (ServerEvent, error) {
var eventType struct {
Type ServerEventType `json:"type"`
}
err := json.Unmarshal(data, &eventType)
if err != nil {
return nil, err
}
switch eventType.Type {
case ServerEventTypeError:
var event ErrorEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeSessionCreated:
var event SessionCreatedEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeSessionUpdated:
var event SessionUpdatedEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeConversationCreated:
var event ConversationCreatedEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeInputAudioBufferCommitted:
var event InputAudioBufferCommittedEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeInputAudioBufferCleared:
var event InputAudioBufferClearedEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeInputAudioBufferSpeechStarted:
var event InputAudioBufferSpeechStartedEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeInputAudioBufferSpeechStopped:
var event InputAudioBufferSpeechStoppedEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeConversationItemCreated:
var event ConversationItemCreatedEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeConversationItemInputAudioTranscriptionCompleted:
var event ConversationItemInputAudioTranscriptionCompletedEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeConversationItemInputAudioTranscriptionFailed:
var event ConversationItemInputAudioTranscriptionFailedEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeConversationItemTruncated:
var event ConversationItemTruncatedEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeConversationItemDeleted:
var event ConversationItemDeletedEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeResponseCreated:
var event ResponseCreatedEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeResponseDone:
var event ResponseDoneEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeResponseOutputItemAdded:
var event ResponseOutputItemAddedEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeResponseOutputItemDone:
var event ResponseOutputItemDoneEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeResponseContentPartAdded:
var event ResponseContentPartAddedEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeResponseContentPartDone:
var event ResponseContentPartDoneEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeResponseTextDelta:
var event ResponseTextDeltaEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeResponseTextDone:
var event ResponseTextDoneEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeResponseAudioTranscriptDelta:
var event ResponseAudioTranscriptDeltaEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeResponseAudioTranscriptDone:
var event ResponseAudioTranscriptDoneEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeResponseAudioDelta:
var event ResponseAudioDeltaEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeResponseAudioDone:
var event ResponseAudioDoneEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeResponseFunctionCallArgumentsDelta:
var event ResponseFunctionCallArgumentsDeltaEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeResponseFunctionCallArgumentsDone:
var event ResponseFunctionCallArgumentsDoneEvent
err = json.Unmarshal(data, &event)
return event, err
case ServerEventTypeRateLimitsUpdated:
var event RateLimitsUpdatedEvent
err = json.Unmarshal(data, &event)
return event, err
default:
// This should never happen.
return nil, fmt.Errorf("unknown server event type: %s", eventType.Type)
}
}
+968
View File
@@ -0,0 +1,968 @@
package openairt_test
import (
"encoding/json"
"testing"
openairt "github.com/WqyJh/go-openai-realtime"
"github.com/stretchr/testify/assert"
)
func TestErrorEvent(t *testing.T) {
data := `{
"event_id": "event_890",
"type": "error",
"error": {
"type": "invalid_request_error",
"code": "invalid_event",
"message": "The 'type' field is missing.",
"param": null,
"event_id": "event_567"
}
}`
expected := openairt.ErrorEvent{
ServerEventBase: openairt.ServerEventBase{
Type: openairt.ServerEventTypeError,
EventID: "event_890",
},
Error: openairt.Error{
Type: "invalid_request_error",
Code: "invalid_event",
Message: "The 'type' field is missing.",
Param: "",
EventID: "event_567",
},
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeError, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ErrorEvent))
}
func TestServerToolChoice(t *testing.T) {
data := `{"type": "function", "function": {"name": "get_current_weather"}}`
expectedFunction := openairt.ToolChoice{
Type: openairt.ToolTypeFunction,
Function: openairt.ToolFunction{
Name: "get_current_weather",
},
}
expected := openairt.ServerToolChoice{Function: expectedFunction}
actual := openairt.ServerToolChoice{}
err := json.Unmarshal([]byte(data), &actual)
assert.NoError(t, err)
assert.Equal(t, expected, actual)
assert.Equal(t, expectedFunction, actual.Get())
data = `"auto"`
expected = openairt.ServerToolChoice{String: openairt.ToolChoiceAuto}
actual = openairt.ServerToolChoice{}
err = json.Unmarshal([]byte(data), &actual)
assert.NoError(t, err)
assert.Equal(t, expected, actual)
assert.Equal(t, openairt.ToolChoiceAuto, actual.Get())
data = `"none"`
expected = openairt.ServerToolChoice{String: openairt.ToolChoiceNone}
actual = openairt.ServerToolChoice{}
err = json.Unmarshal([]byte(data), &actual)
assert.NoError(t, err)
assert.Equal(t, expected, actual)
assert.Equal(t, openairt.ToolChoiceNone, actual.Get())
data = `"required"`
expected = openairt.ServerToolChoice{String: openairt.ToolChoiceRequired}
actual = openairt.ServerToolChoice{}
err = json.Unmarshal([]byte(data), &actual)
assert.NoError(t, err)
assert.Equal(t, expected, actual)
assert.Equal(t, openairt.ToolChoiceRequired, actual.Get())
}
func TestSessionCreatedEvent(t *testing.T) {
data := `{
"event_id": "event_1234",
"type": "session.created",
"session": {
"id": "sess_001",
"object": "realtime.session",
"model": "gpt-4o-realtime-preview-2024-10-01",
"modalities": ["text", "audio"],
"instructions": "",
"voice": "alloy",
"input_audio_format": "pcm16",
"output_audio_format": "pcm16",
"input_audio_transcription": null,
"turn_detection": {
"type": "server_vad",
"threshold": 0.5,
"prefix_padding_ms": 300,
"silence_duration_ms": 200
},
"tools": [],
"tool_choice": "auto",
"temperature": 0.8,
"max_output_tokens": null
}
}`
temperature := float32(0.8)
expected := openairt.SessionCreatedEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_1234",
Type: openairt.ServerEventTypeSessionCreated,
},
Session: openairt.ServerSession{
ID: "sess_001",
Object: "realtime.session",
Model: openairt.GPT4oRealtimePreview20241001,
Modalities: []openairt.Modality{openairt.ModalityText, openairt.ModalityAudio},
Instructions: "",
Voice: openairt.VoiceAlloy,
InputAudioFormat: openairt.AudioFormatPcm16,
OutputAudioFormat: openairt.AudioFormatPcm16,
InputAudioTranscription: nil,
TurnDetection: &openairt.TurnDetection{
Type: openairt.TurnDetectionTypeServerVad,
Threshold: 0.5,
PrefixPaddingMs: 300,
SilenceDurationMs: 200,
},
Tools: []openairt.Tool{},
ToolChoice: openairt.ServerToolChoice{String: openairt.ToolChoiceAuto},
Temperature: &temperature,
MaxOutputTokens: 0,
},
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeSessionCreated, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.SessionCreatedEvent))
}
func TestSessionUpdatedEvent(t *testing.T) {
data := `{
"event_id": "event_5678",
"type": "session.updated",
"session": {
"id": "sess_001",
"object": "realtime.session",
"model": "gpt-4o-realtime-preview-2024-10-01",
"modalities": ["text"],
"instructions": "New instructions",
"voice": "alloy",
"input_audio_format": "pcm16",
"output_audio_format": "pcm16",
"input_audio_transcription": {
"enabled": true,
"model": "whisper-1"
},
"turn_detection": {
"type": "none"
},
"tools": [],
"tool_choice": "none",
"temperature": 0.7,
"max_output_tokens": 200
}
}`
temperature := float32(0.7)
expected := openairt.SessionUpdatedEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_5678",
Type: openairt.ServerEventTypeSessionUpdated,
},
Session: openairt.ServerSession{
ID: "sess_001",
Object: "realtime.session",
Model: openairt.GPT4oRealtimePreview20241001,
Modalities: []openairt.Modality{openairt.ModalityText},
Instructions: "New instructions",
Voice: openairt.VoiceAlloy,
InputAudioFormat: openairt.AudioFormatPcm16,
OutputAudioFormat: openairt.AudioFormatPcm16,
InputAudioTranscription: &openairt.InputAudioTranscription{
Enabled: true,
Model: "whisper-1",
},
TurnDetection: &openairt.TurnDetection{
Type: openairt.TurnDetectionTypeNone,
},
Tools: []openairt.Tool{},
ToolChoice: openairt.ServerToolChoice{String: openairt.ToolChoiceNone},
Temperature: &temperature,
MaxOutputTokens: 200,
},
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeSessionUpdated, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.SessionUpdatedEvent))
}
func TestConversationCreatedEvent(t *testing.T) {
data := `{
"event_id": "event_9101",
"type": "conversation.created",
"conversation": {
"id": "conv_001",
"object": "realtime.conversation"
}
}`
expected := openairt.ConversationCreatedEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_9101",
Type: openairt.ServerEventTypeConversationCreated,
},
Conversation: openairt.Conversation{
ID: "conv_001",
Object: "realtime.conversation",
},
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeConversationCreated, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ConversationCreatedEvent))
}
func TestConversationItemCreatedEvent(t *testing.T) {
data := `{
"event_id": "event_1920",
"type": "conversation.item.created",
"previous_item_id": "msg_002",
"item": {
"id": "msg_003",
"object": "realtime.item",
"type": "message",
"status": "completed",
"role": "user",
"content": [
{
"type": "input_audio",
"transcript": null
}
]
}
}`
expected := openairt.ConversationItemCreatedEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_1920",
Type: openairt.ServerEventTypeConversationItemCreated,
},
PreviousItemID: "msg_002",
Item: openairt.ResponseMessageItem{
Object: "realtime.item",
MessageItem: openairt.MessageItem{
ID: "msg_003",
Type: openairt.MessageItemTypeMessage,
Status: openairt.ItemStatusCompleted,
Role: openairt.MessageRoleUser,
Content: []openairt.MessageContentPart{
{
Type: openairt.MessageContentTypeInputAudio,
Transcript: "",
},
},
},
},
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeConversationItemCreated, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ConversationItemCreatedEvent))
}
func TestConversationItemInputAudioTranscriptionCompletedEvent(t *testing.T) {
data := `{
"event_id": "event_2122",
"type": "conversation.item.input_audio_transcription.completed",
"item_id": "msg_003",
"content_index": 1,
"transcript": "Hello, how are you?"
}`
expected := openairt.ConversationItemInputAudioTranscriptionCompletedEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_2122",
Type: openairt.ServerEventTypeConversationItemInputAudioTranscriptionCompleted,
},
ItemID: "msg_003",
ContentIndex: 1,
Transcript: "Hello, how are you?",
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeConversationItemInputAudioTranscriptionCompleted, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ConversationItemInputAudioTranscriptionCompletedEvent))
}
func TestConversationItemInputAudioTranscriptionFailedEvent(t *testing.T) {
data := `{
"event_id": "event_2324",
"type": "conversation.item.input_audio_transcription.failed",
"item_id": "msg_003",
"content_index": 0,
"error": {
"type": "transcription_error",
"code": "audio_unintelligible",
"message": "The audio could not be transcribed.",
"param": null
}
}`
expected := openairt.ConversationItemInputAudioTranscriptionFailedEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_2324",
Type: openairt.ServerEventTypeConversationItemInputAudioTranscriptionFailed,
},
ItemID: "msg_003",
ContentIndex: 0,
Error: openairt.Error{
Type: "transcription_error",
Code: "audio_unintelligible",
Message: "The audio could not be transcribed.",
Param: "",
EventID: "",
},
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeConversationItemInputAudioTranscriptionFailed, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ConversationItemInputAudioTranscriptionFailedEvent))
}
func TestConversationItemTruncatedEvent(t *testing.T) {
data := `{
"event_id": "event_2526",
"type": "conversation.item.truncated",
"item_id": "msg_004",
"content_index": 2,
"audio_end_ms": 1500
}`
expected := openairt.ConversationItemTruncatedEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_2526",
Type: openairt.ServerEventTypeConversationItemTruncated,
},
ItemID: "msg_004",
ContentIndex: 2,
AudioEndMs: 1500,
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeConversationItemTruncated, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ConversationItemTruncatedEvent))
}
func TestConversationItemDeletedEvent(t *testing.T) {
data := `{
"event_id": "event_2728",
"type": "conversation.item.deleted",
"item_id": "msg_005"
}`
expected := openairt.ConversationItemDeletedEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_2728",
Type: openairt.ServerEventTypeConversationItemDeleted,
},
ItemID: "msg_005",
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeConversationItemDeleted, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ConversationItemDeletedEvent))
}
func TestInputAudioBufferCommittedEvent(t *testing.T) {
data := `{
"event_id": "event_1121",
"type": "input_audio_buffer.committed",
"previous_item_id": "msg_001",
"item_id": "msg_002"
}`
expected := openairt.InputAudioBufferCommittedEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_1121",
Type: openairt.ServerEventTypeInputAudioBufferCommitted,
},
PreviousItemID: "msg_001",
ItemID: "msg_002",
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeInputAudioBufferCommitted, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.InputAudioBufferCommittedEvent))
}
func TestInputAudioBufferClearedEvent(t *testing.T) {
data := `{
"event_id": "event_1314",
"type": "input_audio_buffer.cleared"
}`
expected := openairt.InputAudioBufferClearedEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_1314",
Type: openairt.ServerEventTypeInputAudioBufferCleared,
},
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeInputAudioBufferCleared, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.InputAudioBufferClearedEvent))
}
func TestInputAudioBufferSpeechStartedEvent(t *testing.T) {
data := `{
"event_id": "event_1516",
"type": "input_audio_buffer.speech_started",
"audio_start_ms": 1000,
"item_id": "msg_003"
}`
expected := openairt.InputAudioBufferSpeechStartedEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_1516",
Type: openairt.ServerEventTypeInputAudioBufferSpeechStarted,
},
AudioStartMs: 1000,
ItemID: "msg_003",
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeInputAudioBufferSpeechStarted, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.InputAudioBufferSpeechStartedEvent))
}
func TestInputAudioBufferSpeechStoppedEvent(t *testing.T) {
data := `{
"event_id": "event_1718",
"type": "input_audio_buffer.speech_stopped",
"audio_end_ms": 2000,
"item_id": "msg_003"
}`
expected := openairt.InputAudioBufferSpeechStoppedEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_1718",
Type: openairt.ServerEventTypeInputAudioBufferSpeechStopped,
},
AudioEndMs: 2000,
ItemID: "msg_003",
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeInputAudioBufferSpeechStopped, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.InputAudioBufferSpeechStoppedEvent))
}
func TestResponseCreatedEvent(t *testing.T) {
data := `{
"event_id": "event_2930",
"type": "response.created",
"response": {
"id": "resp_001",
"object": "realtime.response",
"status": "in_progress",
"status_details": null,
"output": [],
"usage": null
}
}`
expected := openairt.ResponseCreatedEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_2930",
Type: openairt.ServerEventTypeResponseCreated,
},
Response: openairt.Response{
ID: "resp_001",
Object: "realtime.response",
Status: "in_progress",
StatusDetails: nil,
Output: []openairt.ResponseMessageItem{},
Usage: nil,
},
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeResponseCreated, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ResponseCreatedEvent))
}
func TestResponseDoneEvent(t *testing.T) {
data := `{
"event_id": "event_3132",
"type": "response.done",
"response": {
"id": "resp_001",
"object": "realtime.response",
"status": "completed",
"status_details": null,
"output": [
{
"id": "msg_006",
"object": "realtime.item",
"type": "message",
"status": "completed",
"role": "assistant",
"content": [
{
"type": "text",
"text": "Sure, how can I assist you today?"
}
]
}
],
"usage": {
"total_tokens": 50,
"input_tokens": 20,
"output_tokens": 30
}
}
}`
expected := openairt.ResponseDoneEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_3132",
Type: openairt.ServerEventTypeResponseDone,
},
Response: openairt.Response{
ID: "resp_001",
Object: "realtime.response",
Status: openairt.ResponseStatusCompleted,
StatusDetails: nil,
Output: []openairt.ResponseMessageItem{
{
Object: "realtime.item",
MessageItem: openairt.MessageItem{
ID: "msg_006",
Type: openairt.MessageItemTypeMessage,
Status: openairt.ItemStatusCompleted,
Role: openairt.MessageRoleAssistant,
Content: []openairt.MessageContentPart{
{
Type: openairt.MessageContentTypeText,
Text: "Sure, how can I assist you today?",
},
},
},
},
},
Usage: &openairt.Usage{
TotalTokens: 50,
InputTokens: 20,
OutputTokens: 30,
},
},
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeResponseDone, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ResponseDoneEvent))
}
func TestResponseOutputItemAddedEvent(t *testing.T) {
data := `{
"event_id": "event_3334",
"type": "response.output_item.added",
"response_id": "resp_001",
"output_index": 10,
"item": {
"id": "msg_007",
"object": "realtime.item",
"type": "message",
"status": "in_progress",
"role": "assistant",
"content": []
}
}`
expected := openairt.ResponseOutputItemAddedEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_3334",
Type: openairt.ServerEventTypeResponseOutputItemAdded,
},
ResponseID: "resp_001",
OutputIndex: 10,
Item: openairt.ResponseMessageItem{
Object: "realtime.item",
MessageItem: openairt.MessageItem{
ID: "msg_007",
Type: openairt.MessageItemTypeMessage,
Status: openairt.ItemStatusInProgress,
Role: openairt.MessageRoleAssistant,
Content: []openairt.MessageContentPart{},
},
},
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeResponseOutputItemAdded, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ResponseOutputItemAddedEvent))
}
func TestResponseOutputItemDoneEvent(t *testing.T) {
data := `{
"event_id": "event_3536",
"type": "response.output_item.done",
"response_id": "resp_001",
"output_index": 0,
"item": {
"id": "msg_007",
"object": "realtime.item",
"type": "message",
"status": "completed",
"role": "assistant",
"content": [
{
"type": "text",
"text": "Sure, I can help with that."
}
]
}
}`
expected := openairt.ResponseOutputItemDoneEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_3536",
Type: openairt.ServerEventTypeResponseOutputItemDone,
},
ResponseID: "resp_001",
OutputIndex: 0,
Item: openairt.ResponseMessageItem{
Object: "realtime.item",
MessageItem: openairt.MessageItem{
ID: "msg_007",
Type: openairt.MessageItemTypeMessage,
Status: openairt.ItemStatusCompleted,
Role: openairt.MessageRoleAssistant,
Content: []openairt.MessageContentPart{
{
Type: openairt.MessageContentTypeText,
Text: "Sure, I can help with that.",
},
},
},
},
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeResponseOutputItemDone, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ResponseOutputItemDoneEvent))
}
func TestResponseContentPartAddedEvent(t *testing.T) {
data := `{
"event_id": "event_3738",
"type": "response.content_part.added",
"response_id": "resp_001",
"item_id": "msg_007",
"output_index": 1,
"content_index": 2,
"part": {
"type": "text",
"text": ""
}
}`
expected := openairt.ResponseContentPartAddedEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_3738",
Type: openairt.ServerEventTypeResponseContentPartAdded,
},
ResponseID: "resp_001",
ItemID: "msg_007",
OutputIndex: 1,
ContentIndex: 2,
Part: openairt.MessageContentPart{
Type: openairt.MessageContentTypeText,
Text: "",
},
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeResponseContentPartAdded, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ResponseContentPartAddedEvent))
}
func TestResponseContentPartDoneEvent(t *testing.T) {
data := `{
"event_id": "event_3940",
"type": "response.content_part.done",
"response_id": "resp_001",
"item_id": "msg_007",
"output_index": 0,
"content_index": 0,
"part": {
"type": "text",
"text": "Sure, I can help with that."
}
}`
expected := openairt.ResponseContentPartDoneEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_3940",
Type: openairt.ServerEventTypeResponseContentPartDone,
},
ResponseID: "resp_001",
ItemID: "msg_007",
OutputIndex: 0,
ContentIndex: 0,
Part: openairt.MessageContentPart{
Type: openairt.MessageContentTypeText,
Text: "Sure, I can help with that.",
},
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeResponseContentPartDone, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ResponseContentPartDoneEvent))
}
func TestResponseTextDelta(t *testing.T) {
data := `{
"event_id": "event_4142",
"type": "response.text.delta",
"response_id": "resp_001",
"item_id": "msg_007",
"output_index": 0,
"content_index": 0,
"delta": "Sure, I can h"
}`
expected := openairt.ResponseTextDeltaEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_4142",
Type: openairt.ServerEventTypeResponseTextDelta,
},
ResponseID: "resp_001",
ItemID: "msg_007",
OutputIndex: 0,
ContentIndex: 0,
Delta: "Sure, I can h",
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeResponseTextDelta, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ResponseTextDeltaEvent))
}
func TestResponseTextDoneEvent(t *testing.T) {
data := `{
"event_id": "event_4344",
"type": "response.text.done",
"response_id": "resp_001",
"item_id": "msg_007",
"output_index": 0,
"content_index": 0,
"text": "Sure, I can help with that."
}`
expected := openairt.ResponseTextDoneEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_4344",
Type: openairt.ServerEventTypeResponseTextDone,
},
ResponseID: "resp_001",
ItemID: "msg_007",
OutputIndex: 0,
ContentIndex: 0,
Text: "Sure, I can help with that.",
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeResponseTextDone, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ResponseTextDoneEvent))
}
func TestResponseAudioTranscriptDelta(t *testing.T) {
data := `{
"event_id": "event_4546",
"type": "response.audio_transcript.delta",
"response_id": "resp_001",
"item_id": "msg_008",
"output_index": 1,
"content_index": 2,
"delta": "Hello, how can I a"
}`
expected := openairt.ResponseAudioTranscriptDeltaEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_4546",
Type: openairt.ServerEventTypeResponseAudioTranscriptDelta,
},
ResponseID: "resp_001",
ItemID: "msg_008",
OutputIndex: 1,
ContentIndex: 2,
Delta: "Hello, how can I a",
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeResponseAudioTranscriptDelta, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ResponseAudioTranscriptDeltaEvent))
}
func TestResponseAudioTranscriptDoneEvent(t *testing.T) {
data := `{
"event_id": "event_4748",
"type": "response.audio_transcript.done",
"response_id": "resp_001",
"item_id": "msg_008",
"output_index": 0,
"content_index": 0,
"transcript": "Hello, how can I assist you today?"
}`
expected := openairt.ResponseAudioTranscriptDoneEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_4748",
Type: openairt.ServerEventTypeResponseAudioTranscriptDone,
},
ResponseID: "resp_001",
ItemID: "msg_008",
OutputIndex: 0,
ContentIndex: 0,
Transcript: "Hello, how can I assist you today?",
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeResponseAudioTranscriptDone, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ResponseAudioTranscriptDoneEvent))
}
func TestResponseAudioDeltaEvent(t *testing.T) {
data := `{
"event_id": "event_4950",
"type": "response.audio.delta",
"response_id": "resp_001",
"item_id": "msg_008",
"output_index": 0,
"content_index": 0,
"delta": "Base64EncodedAudioDelta"
}`
expected := openairt.ResponseAudioDeltaEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_4950",
Type: openairt.ServerEventTypeResponseAudioDelta,
},
ResponseID: "resp_001",
ItemID: "msg_008",
OutputIndex: 0,
ContentIndex: 0,
Delta: "Base64EncodedAudioDelta",
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeResponseAudioDelta, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ResponseAudioDeltaEvent))
}
func TestResponseAudioDoneEvent(t *testing.T) {
data := `{
"event_id": "event_5152",
"type": "response.audio.done",
"response_id": "resp_001",
"item_id": "msg_008",
"output_index": 0,
"content_index": 0
}`
expected := openairt.ResponseAudioDoneEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_5152",
Type: openairt.ServerEventTypeResponseAudioDone,
},
ResponseID: "resp_001",
ItemID: "msg_008",
OutputIndex: 0,
ContentIndex: 0,
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeResponseAudioDone, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ResponseAudioDoneEvent))
}
func TestResponseFunctionCallArgumentsDelta(t *testing.T) {
data := `{
"event_id": "event_5354",
"type": "response.function_call_arguments.delta",
"response_id": "resp_002",
"item_id": "fc_001",
"output_index": 0,
"call_id": "call_001",
"delta": "{\"location\": \"San\""
}`
expected := openairt.ResponseFunctionCallArgumentsDeltaEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_5354",
Type: openairt.ServerEventTypeResponseFunctionCallArgumentsDelta,
},
ResponseID: "resp_002",
ItemID: "fc_001",
OutputIndex: 0,
CallID: "call_001",
Delta: "{\"location\": \"San\"",
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeResponseFunctionCallArgumentsDelta, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ResponseFunctionCallArgumentsDeltaEvent))
}
func TestResponseFunctionCallArgumentsDoneEvent(t *testing.T) {
data := `{
"event_id": "event_5556",
"type": "response.function_call_arguments.done",
"response_id": "resp_002",
"item_id": "fc_001",
"output_index": 1,
"call_id": "call_001",
"arguments": "{\"location\": \"San Francisco\"}"
}`
expected := openairt.ResponseFunctionCallArgumentsDoneEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_5556",
Type: openairt.ServerEventTypeResponseFunctionCallArgumentsDone,
},
ResponseID: "resp_002",
ItemID: "fc_001",
OutputIndex: 1,
CallID: "call_001",
Arguments: "{\"location\": \"San Francisco\"}",
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeResponseFunctionCallArgumentsDone, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.ResponseFunctionCallArgumentsDoneEvent))
}
func TestRateLimitsUpdatedEvent(t *testing.T) {
data := `{
"event_id": "event_5758",
"type": "rate_limits.updated",
"rate_limits": [
{
"name": "requests",
"limit": 1000,
"remaining": 999,
"reset_seconds": 60.0
},
{
"name": "tokens",
"limit": 50000,
"remaining": 49950,
"reset_seconds": 60.0
}
]
}`
expected := openairt.RateLimitsUpdatedEvent{
ServerEventBase: openairt.ServerEventBase{
EventID: "event_5758",
Type: openairt.ServerEventTypeRateLimitsUpdated,
},
RateLimits: []openairt.RateLimit{
{
Name: "requests",
Limit: 1000,
Remaining: 999,
ResetSeconds: 60,
},
{
Name: "tokens",
Limit: 50000,
Remaining: 49950,
ResetSeconds: 60,
},
},
}
actual, err := openairt.UnmarshalServerEvent([]byte(data))
assert.NoError(t, err)
assert.Equal(t, openairt.ServerEventTypeRateLimitsUpdated, actual.ServerEventType())
assert.Equal(t, expected, actual.(openairt.RateLimitsUpdatedEvent))
}
+239
View File
@@ -0,0 +1,239 @@
package openairt
import (
"encoding/json"
"github.com/sashabaranov/go-openai/jsonschema"
)
type Voice string
const (
VoiceAlloy Voice = "alloy"
VoiceShimmer Voice = "shimmer"
VoiceEcho Voice = "echo"
)
type AudioFormat string
const (
AudioFormatPcm16 AudioFormat = "pcm16"
AudioFormatG711Ulaw AudioFormat = "g711-ulaw"
AudioFormatG711Alaw AudioFormat = "g711-alaw"
)
type Modality string
const (
ModalityText Modality = "text"
ModalityAudio Modality = "audio"
)
type TurnDetectionType string
const (
TurnDetectionTypeNone TurnDetectionType = "none"
TurnDetectionTypeServerVad TurnDetectionType = "server_vad"
)
type TurnDetection struct {
Type TurnDetectionType `json:"type"`
Threshold float64 `json:"threshold,omitempty"`
PrefixPaddingMs int `json:"prefix_padding_ms,omitempty"`
SilenceDurationMs int `json:"silence_duration_ms,omitempty"`
}
type ToolType string
const (
ToolTypeFunction ToolType = "function"
)
type ToolChoiceInterface interface {
ToolChoice()
}
type ToolChoiceString string
func (ToolChoiceString) ToolChoice() {}
const (
ToolChoiceAuto ToolChoiceString = "auto"
ToolChoiceNone ToolChoiceString = "none"
ToolChoiceRequired ToolChoiceString = "required"
)
type ToolChoice struct {
Type ToolType `json:"type"`
Function ToolFunction `json:"function,omitempty"`
}
func (t ToolChoice) ToolChoice() {}
type ToolFunction struct {
Name string `json:"name"`
}
type MessageRole string
const (
MessageRoleSystem MessageRole = "system"
MessageRoleAssistant MessageRole = "assistant"
MessageRoleUser MessageRole = "user"
)
type InputAudioTranscription struct {
Enabled bool `json:"enabled"`
Model string `json:"model"`
}
type Tool struct {
Type ToolType `json:"type"`
Name string `json:"name"`
Description string `json:"description"`
Parameters jsonschema.Definition `json:"parameters"`
}
type MessageItemType string
const (
MessageItemTypeMessage MessageItemType = "message"
MessageItemTypeFunctionCall MessageItemType = "function_call"
MessageItemTypeFunctionCallOutput MessageItemType = "function_call_output"
)
type MessageContentType string
const (
MessageContentTypeText MessageContentType = "text"
MessageContentTypeAudio MessageContentType = "audio"
MessageContentTypeTranscript MessageContentType = "transcript"
MessageContentTypeInputText MessageContentType = "input_text"
MessageContentTypeInputAudio MessageContentType = "input_audio"
)
type MessageContentPart struct {
Type MessageContentType `json:"type"`
Text string `json:"text,omitempty"`
Audio string `json:"audio,omitempty"`
Transcript string `json:"transcript,omitempty"`
}
type MessageItem struct {
ID string `json:"id"`
Type MessageItemType `json:"type"`
Status ItemStatus `json:"status"`
Role MessageRole `json:"role"`
Content []MessageContentPart `json:"content"`
}
type ResponseMessageItem struct {
MessageItem
Object string `json:"object,omitempty"`
}
type Error struct {
Message string `json:"message,omitempty"`
Type string `json:"type,omitempty"`
Code string `json:"code,omitempty"`
Param string `json:"param,omitempty"`
EventID string `json:"event_id,omitempty"`
}
// ServerToolChoice is a type that can be used to choose a tool response from the server.
type ServerToolChoice struct {
String ToolChoiceString
Function ToolChoice
}
// UnmarshalJSON is a custom unmarshaler for ServerToolChoice.
func (m *ServerToolChoice) UnmarshalJSON(data []byte) error {
err := json.Unmarshal(data, &m.Function)
if err != nil {
if data[0] == '"' {
data = data[1:]
}
if data[len(data)-1] == '"' {
data = data[:len(data)-1]
}
m.String = ToolChoiceString(data)
m.Function = ToolChoice{}
return nil
}
return nil
}
// IsFunction returns true if the tool choice is a function call.
func (m *ServerToolChoice) IsFunction() bool {
return m.Function.Type == ToolTypeFunction
}
// Get returns the ToolChoiceInterface based on the type of tool choice.
func (m ServerToolChoice) Get() ToolChoiceInterface {
if m.IsFunction() {
return m.Function
}
return m.String
}
type ServerSession struct {
ID string `json:"id"`
Object string `json:"object"`
Model string `json:"model"`
Modalities []Modality `json:"modalities,omitempty"`
Instructions string `json:"instructions,omitempty"`
Voice Voice `json:"voice,omitempty"`
InputAudioFormat AudioFormat `json:"input_audio_format,omitempty"`
OutputAudioFormat AudioFormat `json:"output_audio_format,omitempty"`
InputAudioTranscription *InputAudioTranscription `json:"input_audio_transcription,omitempty"`
TurnDetection *TurnDetection `json:"turn_detection,omitempty"`
Tools []Tool `json:"tools,omitempty"`
ToolChoice ServerToolChoice `json:"tool_choice,omitempty"`
Temperature *float32 `json:"temperature,omitempty"`
MaxOutputTokens int `json:"max_output_tokens,omitempty"`
}
type ItemStatus string
const (
ItemStatusInProgress ItemStatus = "in_progress"
ItemStatusCompleted ItemStatus = "completed"
ItemStatusIncomplete ItemStatus = "incomplete"
)
type Conversation struct {
ID string `json:"id"`
Object string `json:"object"`
}
type ResponseStatus string
const (
ResponseStatusInProgress ResponseStatus = "in_progress"
ResponseStatusCompleted ResponseStatus = "completed"
ResponseStatusCancelled ResponseStatus = "cancelled"
ResponseStatusIncomplete ResponseStatus = "incomplete"
ResponseStatusFailed ResponseStatus = "failed"
)
type Usage struct {
TotalTokens int `json:"total_tokens"`
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
}
type Response struct {
ID string `json:"id"`
Object string `json:"object"`
Status ResponseStatus `json:"status"`
StatusDetails any `json:"status_details,omitempty"`
Output []ResponseMessageItem `json:"output"`
Usage *Usage `json:"usage,omitempty"`
}
type RateLimit struct {
Name string `json:"name"`
Limit int `json:"limit"`
Remaining int `json:"remaining"`
ResetSeconds float64 `json:"reset_seconds"`
}
+21
View File
@@ -0,0 +1,21 @@
package openairt
import (
"crypto/rand"
"math/big"
)
func GenerateId(prefix string, length int) string {
const chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
if length <= len(prefix) {
return prefix
}
result := make([]byte, length-len(prefix))
for i := range result {
num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(chars))))
result[i] = chars[num.Int64()]
}
return prefix + string(result)
}
+21
View File
@@ -0,0 +1,21 @@
package openairt_test
import (
"strings"
"testing"
openairt "github.com/WqyJh/go-openai-realtime"
"github.com/stretchr/testify/assert"
)
func TestGenerateId(t *testing.T) {
generated := openairt.GenerateId("test_", 10)
t.Logf("generated: %s", generated)
assert.True(t, strings.HasPrefix(generated, "test_"))
assert.Equal(t, 10, len(generated))
generated = openairt.GenerateId("test_", 20)
t.Logf("generated: %s", generated)
assert.True(t, strings.HasPrefix(generated, "test_"))
assert.Equal(t, 20, len(generated))
}