Skip to content

Commit 83c71ec

Browse files
authored
Merge branch 'sashabaranov:master' into master
2 parents c6f3a10 + 74d6449 commit 83c71ec

20 files changed

+575
-263
lines changed

.github/workflows/pr.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ jobs:
1313
- name: Setup Go
1414
uses: actions/setup-go@v5
1515
with:
16-
go-version: '1.21'
16+
go-version: '1.24'
1717
- name: Run vet
1818
run: |
1919
go vet .
2020
- name: Run golangci-lint
21-
uses: golangci/golangci-lint-action@v4
21+
uses: golangci/golangci-lint-action@v6
2222
with:
23-
version: latest
23+
version: v1.64.5
2424
- name: Run tests
2525
run: go test -race -covermode=atomic -coverprofile=coverage.out -v .
2626
- name: Upload coverage reports to Codecov

.golangci.yml

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,6 @@ linters-settings:
139139
# Default: false
140140
all: true
141141

142-
varcheck:
143-
# Check usage of exported fields and variables.
144-
# Default: false
145-
exported-fields: false # default false # TODO: enable after fixing false positives
146-
147142

148143
linters:
149144
disable-all: true
@@ -167,9 +162,7 @@ linters:
167162
- durationcheck # check for two durations multiplied together
168163
- errname # Checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error.
169164
- errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13.
170-
# Removed execinquery (deprecated). execinquery is a linter about query string checker in Query function which reads your Go src files and warning it finds
171165
- exhaustive # check exhaustiveness of enum switch statements
172-
- exportloopref # checks for pointers to enclosing loop variables
173166
- forbidigo # Forbids identifiers
174167
- funlen # Tool for detection of long functions
175168
# - gochecknoglobals # check that no global variables exist
@@ -201,11 +194,11 @@ linters:
201194
- rowserrcheck # checks whether Err of rows is checked successfully
202195
- sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed.
203196
- stylecheck # Stylecheck is a replacement for golint
204-
- tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17
205197
- testpackage # linter that makes you use a separate _test package
206198
- tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes
207199
- unconvert # Remove unnecessary type conversions
208200
- unparam # Reports unused function parameters
201+
- usetesting # Reports uses of functions with replacement inside the testing package
209202
- wastedassign # wastedassign finds wasted assignment statements.
210203
- whitespace # Tool for detection of leading and trailing whitespace
211204
## you may want to enable
@@ -238,12 +231,6 @@ linters:
238231
#- tagliatelle # Checks the struct tags.
239232
#- thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers
240233
#- wsl # [too strict and mostly code is not more readable] Whitespace Linter - Forces you to use empty lines!
241-
## deprecated
242-
#- exhaustivestruct # [deprecated, replaced by exhaustruct] Checks if all struct's fields are initialized
243-
#- golint # [deprecated, replaced by revive] Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes
244-
#- interfacer # [deprecated] Linter that suggests narrower interface types
245-
#- maligned # [deprecated, replaced by govet fieldalignment] Tool to detect Go structs that would take less memory if their fields were sorted
246-
#- scopelint # [deprecated, replaced by exportloopref] Scopelint checks for unpinned variables in go programs
247234

248235

249236
issues:

audio_api_test.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,9 @@ func TestAudio(t *testing.T) {
4040

4141
ctx := context.Background()
4242

43-
dir, cleanup := test.CreateTestDirectory(t)
44-
defer cleanup()
45-
4643
for _, tc := range testcases {
4744
t.Run(tc.name, func(t *testing.T) {
48-
path := filepath.Join(dir, "fake.mp3")
45+
path := filepath.Join(t.TempDir(), "fake.mp3")
4946
test.CreateTestFile(t, path)
5047

5148
req := openai.AudioRequest{
@@ -90,12 +87,9 @@ func TestAudioWithOptionalArgs(t *testing.T) {
9087

9188
ctx := context.Background()
9289

93-
dir, cleanup := test.CreateTestDirectory(t)
94-
defer cleanup()
95-
9690
for _, tc := range testcases {
9791
t.Run(tc.name, func(t *testing.T) {
98-
path := filepath.Join(dir, "fake.mp3")
92+
path := filepath.Join(t.TempDir(), "fake.mp3")
9993
test.CreateTestFile(t, path)
10094

10195
req := openai.AudioRequest{

audio_test.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ import (
1313
)
1414

1515
func TestAudioWithFailingFormBuilder(t *testing.T) {
16-
dir, cleanup := test.CreateTestDirectory(t)
17-
defer cleanup()
18-
path := filepath.Join(dir, "fake.mp3")
16+
path := filepath.Join(t.TempDir(), "fake.mp3")
1917
test.CreateTestFile(t, path)
2018

2119
req := AudioRequest{
@@ -63,9 +61,7 @@ func TestAudioWithFailingFormBuilder(t *testing.T) {
6361

6462
func TestCreateFileField(t *testing.T) {
6563
t.Run("createFileField failing file", func(t *testing.T) {
66-
dir, cleanup := test.CreateTestDirectory(t)
67-
defer cleanup()
68-
path := filepath.Join(dir, "fake.mp3")
64+
path := filepath.Join(t.TempDir(), "fake.mp3")
6965
test.CreateTestFile(t, path)
7066

7167
req := AudioRequest{

chat.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const (
1414
ChatMessageRoleAssistant = "assistant"
1515
ChatMessageRoleFunction = "function"
1616
ChatMessageRoleTool = "tool"
17+
ChatMessageRoleDeveloper = "developer"
1718
)
1819

1920
const chatCompletionsSuffix = "/chat/completions"
@@ -93,7 +94,7 @@ type ChatMessagePart struct {
9394

9495
type ChatCompletionMessage struct {
9596
Role string `json:"role"`
96-
Content string `json:"content"`
97+
Content string `json:"content,omitempty"`
9798
Refusal string `json:"refusal,omitempty"`
9899
MultiContent []ChatMessagePart
99100

@@ -132,7 +133,7 @@ func (m ChatCompletionMessage) MarshalJSON() ([]byte, error) {
132133

133134
msg := struct {
134135
Role string `json:"role"`
135-
Content string `json:"content"`
136+
Content string `json:"content,omitempty"`
136137
Refusal string `json:"refusal,omitempty"`
137138
MultiContent []ChatMessagePart `json:"-"`
138139
Name string `json:"name,omitempty"`
@@ -146,7 +147,7 @@ func (m ChatCompletionMessage) MarshalJSON() ([]byte, error) {
146147
func (m *ChatCompletionMessage) UnmarshalJSON(bs []byte) error {
147148
msg := struct {
148149
Role string `json:"role"`
149-
Content string `json:"content"`
150+
Content string `json:"content,omitempty"`
150151
Refusal string `json:"refusal,omitempty"`
151152
MultiContent []ChatMessagePart
152153
Name string `json:"name,omitempty"`
@@ -258,6 +259,8 @@ type ChatCompletionRequest struct {
258259
// Store can be set to true to store the output of this completion request for use in distillations and evals.
259260
// https://platform.openai.com/docs/api-reference/chat/create#chat-create-store
260261
Store bool `json:"store,omitempty"`
262+
// Controls effort on reasoning for reasoning models. It can be set to "low", "medium", or "high".
263+
ReasoningEffort string `json:"reasoning_effort,omitempty"`
261264
// Metadata to store with the completion.
262265
Metadata map[string]string `json:"metadata,omitempty"`
263266
}
@@ -390,7 +393,8 @@ func (c *Client) CreateChatCompletion(
390393
return
391394
}
392395

393-
if err = validateRequestForO1Models(request); err != nil {
396+
reasoningValidator := NewReasoningValidator()
397+
if err = reasoningValidator.Validate(request); err != nil {
394398
return
395399
}
396400

chat_stream.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ func (c *Client) CreateChatCompletionStream(
8080
}
8181

8282
request.Stream = true
83-
if err = validateRequestForO1Models(request); err != nil {
83+
reasoningValidator := NewReasoningValidator()
84+
if err = reasoningValidator.Validate(request); err != nil {
8485
return
8586
}
8687

chat_stream_test.go

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,173 @@ func compareChatResponses(r1, r2 openai.ChatCompletionStreamResponse) bool {
792792
return true
793793
}
794794

795+
func TestCreateChatCompletionStreamWithReasoningModel(t *testing.T) {
796+
client, server, teardown := setupOpenAITestServer()
797+
defer teardown()
798+
server.RegisterHandler("/v1/chat/completions", func(w http.ResponseWriter, _ *http.Request) {
799+
w.Header().Set("Content-Type", "text/event-stream")
800+
801+
dataBytes := []byte{}
802+
803+
//nolint:lll
804+
dataBytes = append(dataBytes, []byte(`data: {"id":"1","object":"chat.completion.chunk","created":1729585728,"model":"o3-mini-2025-01-31","system_fingerprint":"fp_mini","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}`)...)
805+
dataBytes = append(dataBytes, []byte("\n\n")...)
806+
807+
//nolint:lll
808+
dataBytes = append(dataBytes, []byte(`data: {"id":"2","object":"chat.completion.chunk","created":1729585728,"model":"o3-mini-2025-01-31","system_fingerprint":"fp_mini","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]}`)...)
809+
dataBytes = append(dataBytes, []byte("\n\n")...)
810+
811+
//nolint:lll
812+
dataBytes = append(dataBytes, []byte(`data: {"id":"3","object":"chat.completion.chunk","created":1729585728,"model":"o3-mini-2025-01-31","system_fingerprint":"fp_mini","choices":[{"index":0,"delta":{"content":" from"},"finish_reason":null}]}`)...)
813+
dataBytes = append(dataBytes, []byte("\n\n")...)
814+
815+
//nolint:lll
816+
dataBytes = append(dataBytes, []byte(`data: {"id":"4","object":"chat.completion.chunk","created":1729585728,"model":"o3-mini-2025-01-31","system_fingerprint":"fp_mini","choices":[{"index":0,"delta":{"content":" O3Mini"},"finish_reason":null}]}`)...)
817+
dataBytes = append(dataBytes, []byte("\n\n")...)
818+
819+
//nolint:lll
820+
dataBytes = append(dataBytes, []byte(`data: {"id":"5","object":"chat.completion.chunk","created":1729585728,"model":"o3-mini-2025-01-31","system_fingerprint":"fp_mini","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}`)...)
821+
dataBytes = append(dataBytes, []byte("\n\n")...)
822+
823+
dataBytes = append(dataBytes, []byte("data: [DONE]\n\n")...)
824+
825+
_, err := w.Write(dataBytes)
826+
checks.NoError(t, err, "Write error")
827+
})
828+
829+
stream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{
830+
MaxCompletionTokens: 2000,
831+
Model: openai.O3Mini20250131,
832+
Messages: []openai.ChatCompletionMessage{
833+
{
834+
Role: openai.ChatMessageRoleUser,
835+
Content: "Hello!",
836+
},
837+
},
838+
Stream: true,
839+
})
840+
checks.NoError(t, err, "CreateCompletionStream returned error")
841+
defer stream.Close()
842+
843+
expectedResponses := []openai.ChatCompletionStreamResponse{
844+
{
845+
ID: "1",
846+
Object: "chat.completion.chunk",
847+
Created: 1729585728,
848+
Model: openai.O3Mini20250131,
849+
SystemFingerprint: "fp_mini",
850+
Choices: []openai.ChatCompletionStreamChoice{
851+
{
852+
Index: 0,
853+
Delta: openai.ChatCompletionStreamChoiceDelta{
854+
Role: "assistant",
855+
},
856+
},
857+
},
858+
},
859+
{
860+
ID: "2",
861+
Object: "chat.completion.chunk",
862+
Created: 1729585728,
863+
Model: openai.O3Mini20250131,
864+
SystemFingerprint: "fp_mini",
865+
Choices: []openai.ChatCompletionStreamChoice{
866+
{
867+
Index: 0,
868+
Delta: openai.ChatCompletionStreamChoiceDelta{
869+
Content: "Hello",
870+
},
871+
},
872+
},
873+
},
874+
{
875+
ID: "3",
876+
Object: "chat.completion.chunk",
877+
Created: 1729585728,
878+
Model: openai.O3Mini20250131,
879+
SystemFingerprint: "fp_mini",
880+
Choices: []openai.ChatCompletionStreamChoice{
881+
{
882+
Index: 0,
883+
Delta: openai.ChatCompletionStreamChoiceDelta{
884+
Content: " from",
885+
},
886+
},
887+
},
888+
},
889+
{
890+
ID: "4",
891+
Object: "chat.completion.chunk",
892+
Created: 1729585728,
893+
Model: openai.O3Mini20250131,
894+
SystemFingerprint: "fp_mini",
895+
Choices: []openai.ChatCompletionStreamChoice{
896+
{
897+
Index: 0,
898+
Delta: openai.ChatCompletionStreamChoiceDelta{
899+
Content: " O3Mini",
900+
},
901+
},
902+
},
903+
},
904+
{
905+
ID: "5",
906+
Object: "chat.completion.chunk",
907+
Created: 1729585728,
908+
Model: openai.O3Mini20250131,
909+
SystemFingerprint: "fp_mini",
910+
Choices: []openai.ChatCompletionStreamChoice{
911+
{
912+
Index: 0,
913+
Delta: openai.ChatCompletionStreamChoiceDelta{},
914+
FinishReason: "stop",
915+
},
916+
},
917+
},
918+
}
919+
920+
for ix, expectedResponse := range expectedResponses {
921+
b, _ := json.Marshal(expectedResponse)
922+
t.Logf("%d: %s", ix, string(b))
923+
924+
receivedResponse, streamErr := stream.Recv()
925+
checks.NoError(t, streamErr, "stream.Recv() failed")
926+
if !compareChatResponses(expectedResponse, receivedResponse) {
927+
t.Errorf("Stream response %v is %v, expected %v", ix, receivedResponse, expectedResponse)
928+
}
929+
}
930+
931+
_, streamErr := stream.Recv()
932+
if !errors.Is(streamErr, io.EOF) {
933+
t.Errorf("stream.Recv() did not return EOF in the end: %v", streamErr)
934+
}
935+
}
936+
937+
func TestCreateChatCompletionStreamReasoningValidatorFails(t *testing.T) {
938+
client, _, _ := setupOpenAITestServer()
939+
940+
stream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{
941+
MaxTokens: 100, // This will trigger the validator to fail
942+
Model: openai.O3Mini,
943+
Messages: []openai.ChatCompletionMessage{
944+
{
945+
Role: openai.ChatMessageRoleUser,
946+
Content: "Hello!",
947+
},
948+
},
949+
Stream: true,
950+
})
951+
952+
if stream != nil {
953+
t.Error("Expected nil stream when validation fails")
954+
stream.Close()
955+
}
956+
957+
if !errors.Is(err, openai.ErrReasoningModelMaxTokensDeprecated) {
958+
t.Errorf("Expected ErrReasoningModelMaxTokensDeprecated, got: %v", err)
959+
}
960+
}
961+
795962
func compareChatStreamResponseChoices(c1, c2 openai.ChatCompletionStreamChoice) bool {
796963
if c1.Index != c2.Index {
797964
return false

0 commit comments

Comments
 (0)