Repository: sashabaranov/go-openai
Pull request #959 (2025-03-27 18:27:34)

Add CompletionRequest.StreamOptions
**Describe the change**

The legacy completion API supports [a `stream_options` object when `stream` is set to true](https://platform.openai.com/docs/api-reference/completions/create#completions-create-stream_options). This adds a StreamOptions property to the CompletionRequest struct to support this setting.

**Provide OpenAI documentation link**

https://platform.openai.com/docs/api-reference/completions/create#completions-create-stream_options

**Describe your solution**

This adds a `StreamOptions` property (marshaled as `stream_options`) to the `CompletionRequest` struct to expose the setting. This matches the `ChatCompletionRequest` property.



codecov[bot]: ## [Codecov](https://app.codecov.io/gh/sashabaranov/go-openai/pull/959?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alex+Baranov) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 98.88%. Comparing base [(`774fc9d`)](https://app.codecov.io/gh/sashabaranov/go-openai/commit/774fc9dd12ed60c10a9f9f03319ddb9cd5f8780c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alex+Baranov) to head [(`ec76779`)](https://app.codecov.io/gh/sashabaranov/go-openai/commit/ec76779e7f1e512688454de3a7d053ea6f88362e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alex+Baranov).
> Report is 88 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #959      +/-   ##
==========================================
+ Coverage   98.46%   98.88%   +0.41%     
==========================================
  Files          24       27       +3     
  Lines        1364     1786     +422     
==========================================
+ Hits         1343     1766     +423     
+ Misses         15       14       -1     
  Partials        6        6              
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/sashabaranov/go-openai/pull/959?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alex+Baranov).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alex+Baranov).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 818a884..f4cbe7c 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -22,6 +22,6 @@ jobs:
       with:
         version: v1.64.5
     - name: Run tests
-      run: go test -race -covermode=atomic -coverprofile=coverage.out -v .
+      run: go test -race -covermode=atomic -coverprofile=coverage.out -v ./...
     - name: Upload coverage reports to Codecov
       uses: codecov/codecov-action@v4
diff --git a/chat.go b/chat.go
index 995860c..7112dc7 100644
--- a/chat.go
+++ b/chat.go
@@ -104,6 +104,12 @@ type ChatCompletionMessage struct {
 	// - https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
 	Name string `json:"name,omitempty"`
 
+	// This property is used for the "reasoning" feature supported by deepseek-reasoner
+	// which is not in the official documentation.
+	// the doc from deepseek:
+	// - https://api-docs.deepseek.com/api/create-chat-completion#responses
+	ReasoningContent string `json:"reasoning_content,omitempty"`
+
 	FunctionCall *FunctionCall `json:"function_call,omitempty"`
 
 	// For Role=assistant prompts this may be set to the tool calls generated by the model, such as function calls.
@@ -119,41 +125,44 @@ func (m ChatCompletionMessage) MarshalJSON() ([]byte, error) {
 	}
 	if len(m.MultiContent) > 0 {
 		msg := struct {
-			Role         string            `json:"role"`
-			Content      string            `json:"-"`
-			Refusal      string            `json:"refusal,omitempty"`
-			MultiContent []ChatMessagePart `json:"content,omitempty"`
-			Name         string            `json:"name,omitempty"`
-			FunctionCall *FunctionCall     `json:"function_call,omitempty"`
-			ToolCalls    []ToolCall        `json:"tool_calls,omitempty"`
-			ToolCallID   string            `json:"tool_call_id,omitempty"`
+			Role             string            `json:"role"`
+			Content          string            `json:"-"`
+			Refusal          string            `json:"refusal,omitempty"`
+			MultiContent     []ChatMessagePart `json:"content,omitempty"`
+			Name             string            `json:"name,omitempty"`
+			ReasoningContent string            `json:"reasoning_content,omitempty"`
+			FunctionCall     *FunctionCall     `json:"function_call,omitempty"`
+			ToolCalls        []ToolCall        `json:"tool_calls,omitempty"`
+			ToolCallID       string            `json:"tool_call_id,omitempty"`
 		}(m)
 		return json.Marshal(msg)
 	}
 
 	msg := struct {
-		Role         string            `json:"role"`
-		Content      string            `json:"content,omitempty"`
-		Refusal      string            `json:"refusal,omitempty"`
-		MultiContent []ChatMessagePart `json:"-"`
-		Name         string            `json:"name,omitempty"`
-		FunctionCall *FunctionCall     `json:"function_call,omitempty"`
-		ToolCalls    []ToolCall        `json:"tool_calls,omitempty"`
-		ToolCallID   string            `json:"tool_call_id,omitempty"`
+		Role             string            `json:"role"`
+		Content          string            `json:"content,omitempty"`
+		Refusal          string            `json:"refusal,omitempty"`
+		MultiContent     []ChatMessagePart `json:"-"`
+		Name             string            `json:"name,omitempty"`
+		ReasoningContent string            `json:"reasoning_content,omitempty"`
+		FunctionCall     *FunctionCall     `json:"function_call,omitempty"`
+		ToolCalls        []ToolCall        `json:"tool_calls,omitempty"`
+		ToolCallID       string            `json:"tool_call_id,omitempty"`
 	}(m)
 	return json.Marshal(msg)
 }
 
 func (m *ChatCompletionMessage) UnmarshalJSON(bs []byte) error {
 	msg := struct {
-		Role         string `json:"role"`
-		Content      string `json:"content,omitempty"`
-		Refusal      string `json:"refusal,omitempty"`
-		MultiContent []ChatMessagePart
-		Name         string        `json:"name,omitempty"`
-		FunctionCall *FunctionCall `json:"function_call,omitempty"`
-		ToolCalls    []ToolCall    `json:"tool_calls,omitempty"`
-		ToolCallID   string        `json:"tool_call_id,omitempty"`
+		Role             string `json:"role"`
+		Content          string `json:"content"`
+		Refusal          string `json:"refusal,omitempty"`
+		MultiContent     []ChatMessagePart
+		Name             string        `json:"name,omitempty"`
+		ReasoningContent string        `json:"reasoning_content,omitempty"`
+		FunctionCall     *FunctionCall `json:"function_call,omitempty"`
+		ToolCalls        []ToolCall    `json:"tool_calls,omitempty"`
+		ToolCallID       string        `json:"tool_call_id,omitempty"`
 	}{}
 
 	if err := json.Unmarshal(bs, &msg); err == nil {
@@ -161,14 +170,15 @@ func (m *ChatCompletionMessage) UnmarshalJSON(bs []byte) error {
 		return nil
 	}
 	multiMsg := struct {
-		Role         string `json:"role"`
-		Content      string
-		Refusal      string            `json:"refusal,omitempty"`
-		MultiContent []ChatMessagePart `json:"content"`
-		Name         string            `json:"name,omitempty"`
-		FunctionCall *FunctionCall     `json:"function_call,omitempty"`
-		ToolCalls    []ToolCall        `json:"tool_calls,omitempty"`
-		ToolCallID   string            `json:"tool_call_id,omitempty"`
+		Role             string `json:"role"`
+		Content          string
+		Refusal          string            `json:"refusal,omitempty"`
+		MultiContent     []ChatMessagePart `json:"content"`
+		Name             string            `json:"name,omitempty"`
+		ReasoningContent string            `json:"reasoning_content,omitempty"`
+		FunctionCall     *FunctionCall     `json:"function_call,omitempty"`
+		ToolCalls        []ToolCall        `json:"tool_calls,omitempty"`
+		ToolCallID       string            `json:"tool_call_id,omitempty"`
 	}{}
 	if err := json.Unmarshal(bs, &multiMsg); err != nil {
 		return err
diff --git a/chat_stream.go b/chat_stream.go
index 525b445..80d16cc 100644
--- a/chat_stream.go
+++ b/chat_stream.go
@@ -11,6 +11,12 @@ type ChatCompletionStreamChoiceDelta struct {
 	FunctionCall *FunctionCall `json:"function_call,omitempty"`
 	ToolCalls    []ToolCall    `json:"tool_calls,omitempty"`
 	Refusal      string        `json:"refusal,omitempty"`
+
+	// This property is used for the "reasoning" feature supported by deepseek-reasoner
+	// which is not in the official documentation.
+	// the doc from deepseek:
+	// - https://api-docs.deepseek.com/api/create-chat-completion#responses
+	ReasoningContent string `json:"reasoning_content,omitempty"`
 }
 
 type ChatCompletionStreamChoiceLogprobs struct {
diff --git a/completion.go b/completion.go
index 015fa2a..9c3a64d 100644
--- a/completion.go
+++ b/completion.go
@@ -16,8 +16,12 @@ const (
 	O1Preview20240912       = "o1-preview-2024-09-12"
 	O1                      = "o1"
 	O120241217              = "o1-2024-12-17"
+	O3                      = "o3"
+	O320250416              = "o3-2025-04-16"
 	O3Mini                  = "o3-mini"
 	O3Mini20250131          = "o3-mini-2025-01-31"
+	O4Mini                  = "o4-mini"
+	O4Mini2020416           = "o4-mini-2025-04-16"
 	GPT432K0613             = "gpt-4-32k-0613"
 	GPT432K0314             = "gpt-4-32k-0314"
 	GPT432K                 = "gpt-4-32k"
@@ -37,6 +41,12 @@ const (
 	GPT4TurboPreview        = "gpt-4-turbo-preview"
 	GPT4VisionPreview       = "gpt-4-vision-preview"
 	GPT4                    = "gpt-4"
+	GPT4Dot1                = "gpt-4.1"
+	GPT4Dot120250414        = "gpt-4.1-2025-04-14"
+	GPT4Dot1Mini            = "gpt-4.1-mini"
+	GPT4Dot1Mini20250414    = "gpt-4.1-mini-2025-04-14"
+	GPT4Dot1Nano            = "gpt-4.1-nano"
+	GPT4Dot1Nano20250414    = "gpt-4.1-nano-2025-04-14"
 	GPT4Dot5Preview         = "gpt-4.5-preview"
 	GPT4Dot5Preview20250227 = "gpt-4.5-preview-2025-02-27"
 	GPT3Dot5Turbo0125       = "gpt-3.5-turbo-0125"
@@ -93,6 +103,10 @@ var disabledModelsForEndpoints = map[string]map[string]bool{
 		O1Preview20240912:       true,
 		O3Mini:                  true,
 		O3Mini20250131:          true,
+		O4Mini:                  true,
+		O4Mini2020416:           true,
+		O3:                      true,
+		O320250416:              true,
 		GPT3Dot5Turbo:           true,
 		GPT3Dot5Turbo0301:       true,
 		GPT3Dot5Turbo0613:       true,
@@ -121,6 +135,13 @@ var disabledModelsForEndpoints = map[string]map[string]bool{
 		GPT432K:                 true,
 		GPT432K0314:             true,
 		GPT432K0613:             true,
+		O1:                      true,
+		GPT4Dot1:                true,
+		GPT4Dot120250414:        true,
+		GPT4Dot1Mini:            true,
+		GPT4Dot1Mini20250414:    true,
+		GPT4Dot1Nano:            true,
+		GPT4Dot1Nano20250414:    true,
 	},
 	chatCompletionsSuffix: {
 		CodexCodeDavinci002:     true,
@@ -194,6 +215,8 @@ type CompletionRequest struct {
 	Temperature     float32           `json:"temperature,omitempty"`
 	TopP            float32           `json:"top_p,omitempty"`
 	User            string            `json:"user,omitempty"`
+	// Options for streaming response. Only set this when you set stream: true.
+	StreamOptions *StreamOptions `json:"stream_options,omitempty"`
 }
 
 // CompletionChoice represents one of possible completions.
diff --git a/jsonschema/json.go b/jsonschema/json.go
index bcb253f..d458418 100644
--- a/jsonschema/json.go
+++ b/jsonschema/json.go
@@ -46,6 +46,8 @@ type Definition struct {
 	// additionalProperties: false
 	// additionalProperties: jsonschema.Definition{Type: jsonschema.String}
 	AdditionalProperties any `json:"additionalProperties,omitempty"`
+	// Whether the schema is nullable or not.
+	Nullable bool `json:"nullable,omitempty"`
 }
 
 func (d *Definition) MarshalJSON() ([]byte, error) {
@@ -139,6 +141,16 @@ func reflectSchemaObject(t reflect.Type) (*Definition, error) {
 		if description != "" {
 			item.Description = description
 		}
+		enum := field.Tag.Get("enum")
+		if enum != "" {
+			item.Enum = strings.Split(enum, ",")
+		}
+
+		if n := field.Tag.Get("nullable"); n != "" {
+			nullable, _ := strconv.ParseBool(n)
+			item.Nullable = nullable
+		}
+
 		properties[jsonTag] = *item
 
 		if s := field.Tag.Get("required"); s != "" {
diff --git a/reasoning_validator.go b/reasoning_validator.go
index 040d6b4..2910b13 100644
--- a/reasoning_validator.go
+++ b/reasoning_validator.go
@@ -40,8 +40,9 @@ func NewReasoningValidator() *ReasoningValidator {
 func (v *ReasoningValidator) Validate(request ChatCompletionRequest) error {
 	o1Series := strings.HasPrefix(request.Model, "o1")
 	o3Series := strings.HasPrefix(request.Model, "o3")
+	o4Series := strings.HasPrefix(request.Model, "o4")
 
-	if !o1Series && !o3Series {
+	if !o1Series && !o3Series && !o4Series {
 		return nil
 	}
 
diff --git a/speech.go b/speech.go
index 20b52e3..60e7694 100644
--- a/speech.go
+++ b/speech.go
@@ -8,20 +8,25 @@ import (
 type SpeechModel string
 
 const (
-	TTSModel1      SpeechModel = "tts-1"
-	TTSModel1HD    SpeechModel = "tts-1-hd"
-	TTSModelCanary SpeechModel = "canary-tts"
+	TTSModel1         SpeechModel = "tts-1"
+	TTSModel1HD       SpeechModel = "tts-1-hd"
+	TTSModelCanary    SpeechModel = "canary-tts"
+	TTSModelGPT4oMini SpeechModel = "gpt-4o-mini-tts"
 )
 
 type SpeechVoice string
 
 const (
 	VoiceAlloy   SpeechVoice = "alloy"
+	VoiceAsh     SpeechVoice = "ash"
+	VoiceBallad  SpeechVoice = "ballad"
+	VoiceCoral   SpeechVoice = "coral"
 	VoiceEcho    SpeechVoice = "echo"
 	VoiceFable   SpeechVoice = "fable"
 	VoiceOnyx    SpeechVoice = "onyx"
 	VoiceNova    SpeechVoice = "nova"
 	VoiceShimmer SpeechVoice = "shimmer"
+	VoiceVerse   SpeechVoice = "verse"
 )
 
 type SpeechResponseFormat string
@@ -39,6 +44,7 @@ type CreateSpeechRequest struct {
 	Model          SpeechModel          `json:"model"`
 	Input          string               `json:"input"`
 	Voice          SpeechVoice          `json:"voice"`
+	Instructions   string               `json:"instructions,omitempty"`    // Optional, Doesnt work with tts-1 or tts-1-hd.
 	ResponseFormat SpeechResponseFormat `json:"response_format,omitempty"` // Optional, default to mp3
 	Speed          float64              `json:"speed,omitempty"`           // Optional, default to 1.0
 }
diff --git a/stream_reader.go b/stream_reader.go
index ecfa268..6faefe0 100644
--- a/stream_reader.go
+++ b/stream_reader.go
@@ -6,13 +6,14 @@ import (
 	"fmt"
 	"io"
 	"net/http"
+	"regexp"
 
 	utils "github.com/sashabaranov/go-openai/internal"
 )
 
 var (
-	headerData  = []byte("data: ")
-	errorPrefix = []byte(`data: {"error":`)
+	headerData  = regexp.MustCompile(`^data:\s*`)
+	errorPrefix = regexp.MustCompile(`^data:\s*{"error":`)
 )
 
 type streamable interface {
@@ -70,12 +71,12 @@ func (stream *streamReader[T]) processLines() ([]byte, error) {
 		}
 
 		noSpaceLine := bytes.TrimSpace(rawLine)
-		if bytes.HasPrefix(noSpaceLine, errorPrefix) {
+		if errorPrefix.Match(noSpaceLine) {
 			hasErrorPrefix = true
 		}
-		if !bytes.HasPrefix(noSpaceLine, headerData) || hasErrorPrefix {
+		if !headerData.Match(noSpaceLine) || hasErrorPrefix {
 			if hasErrorPrefix {
-				noSpaceLine = bytes.TrimPrefix(noSpaceLine, headerData)
+				noSpaceLine = headerData.ReplaceAll(noSpaceLine, nil)
 			}
 			writeErr := stream.errAccumulator.Write(noSpaceLine)
 			if writeErr != nil {
@@ -89,7 +90,7 @@ func (stream *streamReader[T]) processLines() ([]byte, error) {
 			continue
 		}
 
-		noPrefixLine := bytes.TrimPrefix(noSpaceLine, headerData)
+		noPrefixLine := headerData.ReplaceAll(noSpaceLine, nil)
 		if string(noPrefixLine) == "[DONE]" {
 			stream.isFinished = true
 			return nil, io.EOF


Test patch:
diff --git a/chat_stream_test.go b/chat_stream_test.go
index 4d992e4..eabb0f3 100644
--- a/chat_stream_test.go
+++ b/chat_stream_test.go
@@ -959,6 +959,56 @@ func TestCreateChatCompletionStreamReasoningValidatorFails(t *testing.T) {
 	}
 }
 
+func TestCreateChatCompletionStreamO3ReasoningValidatorFails(t *testing.T) {
+	client, _, _ := setupOpenAITestServer()
+
+	stream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{
+		MaxTokens: 100, // This will trigger the validator to fail
+		Model:     openai.O3,
+		Messages: []openai.ChatCompletionMessage{
+			{
+				Role:    openai.ChatMessageRoleUser,
+				Content: "Hello!",
+			},
+		},
+		Stream: true,
+	})
+
+	if stream != nil {
+		t.Error("Expected nil stream when validation fails")
+		stream.Close()
+	}
+
+	if !errors.Is(err, openai.ErrReasoningModelMaxTokensDeprecated) {
+		t.Errorf("Expected ErrReasoningModelMaxTokensDeprecated for O3, got: %v", err)
+	}
+}
+
+func TestCreateChatCompletionStreamO4MiniReasoningValidatorFails(t *testing.T) {
+	client, _, _ := setupOpenAITestServer()
+
+	stream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{
+		MaxTokens: 100, // This will trigger the validator to fail
+		Model:     openai.O4Mini,
+		Messages: []openai.ChatCompletionMessage{
+			{
+				Role:    openai.ChatMessageRoleUser,
+				Content: "Hello!",
+			},
+		},
+		Stream: true,
+	})
+
+	if stream != nil {
+		t.Error("Expected nil stream when validation fails")
+		stream.Close()
+	}
+
+	if !errors.Is(err, openai.ErrReasoningModelMaxTokensDeprecated) {
+		t.Errorf("Expected ErrReasoningModelMaxTokensDeprecated for O4Mini, got: %v", err)
+	}
+}
+
 func compareChatStreamResponseChoices(c1, c2 openai.ChatCompletionStreamChoice) bool {
 	if c1.Index != c2.Index {
 		return false
diff --git a/chat_test.go b/chat_test.go
index e90142d..514706c 100644
--- a/chat_test.go
+++ b/chat_test.go
@@ -411,6 +411,23 @@ func TestO3ModelChatCompletions(t *testing.T) {
 	checks.NoError(t, err, "CreateChatCompletion error")
 }
 
+func TestDeepseekR1ModelChatCompletions(t *testing.T) {
+	client, server, teardown := setupOpenAITestServer()
+	defer teardown()
+	server.RegisterHandler("/v1/chat/completions", handleDeepseekR1ChatCompletionEndpoint)
+	_, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{
+		Model:               "deepseek-reasoner",
+		MaxCompletionTokens: 100,
+		Messages: []openai.ChatCompletionMessage{
+			{
+				Role:    openai.ChatMessageRoleUser,
+				Content: "Hello!",
+			},
+		},
+	})
+	checks.NoError(t, err, "CreateChatCompletion error")
+}
+
 // TestCompletions Tests the completions endpoint of the API using the mocked server.
 func TestChatCompletionsWithHeaders(t *testing.T) {
 	client, server, teardown := setupOpenAITestServer()
@@ -822,6 +839,68 @@ func handleChatCompletionEndpoint(w http.ResponseWriter, r *http.Request) {
 	fmt.Fprintln(w, string(resBytes))
 }
 
+func handleDeepseekR1ChatCompletionEndpoint(w http.ResponseWriter, r *http.Request) {
+	var err error
+	var resBytes []byte
+
+	// completions only accepts POST requests
+	if r.Method != "POST" {
+		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+	}
+	var completionReq openai.ChatCompletionRequest
+	if completionReq, err = getChatCompletionBody(r); err != nil {
+		http.Error(w, "could not read request", http.StatusInternalServerError)
+		return
+	}
+	res := openai.ChatCompletionResponse{
+		ID:      strconv.Itoa(int(time.Now().Unix())),
+		Object:  "test-object",
+		Created: time.Now().Unix(),
+		// would be nice to validate Model during testing, but
+		// this may not be possible with how much upkeep
+		// would be required / wouldn't make much sense
+		Model: completionReq.Model,
+	}
+	// create completions
+	n := completionReq.N
+	if n == 0 {
+		n = 1
+	}
+	if completionReq.MaxCompletionTokens == 0 {
+		completionReq.MaxCompletionTokens = 1000
+	}
+	for i := 0; i < n; i++ {
+		reasoningContent := "User says hello! And I need to reply"
+		completionStr := strings.Repeat("a", completionReq.MaxCompletionTokens-numTokens(reasoningContent))
+		res.Choices = append(res.Choices, openai.ChatCompletionChoice{
+			Message: openai.ChatCompletionMessage{
+				Role:             openai.ChatMessageRoleAssistant,
+				ReasoningContent: reasoningContent,
+				Content:          completionStr,
+			},
+			Index: i,
+		})
+	}
+	inputTokens := numTokens(completionReq.Messages[0].Content) * n
+	completionTokens := completionReq.MaxTokens * n
+	res.Usage = openai.Usage{
+		PromptTokens:     inputTokens,
+		CompletionTokens: completionTokens,
+		TotalTokens:      inputTokens + completionTokens,
+	}
+	resBytes, _ = json.Marshal(res)
+	w.Header().Set(xCustomHeader, xCustomHeaderValue)
+	for k, v := range rateLimitHeaders {
+		switch val := v.(type) {
+		case int:
+			w.Header().Set(k, strconv.Itoa(val))
+		default:
+			w.Header().Set(k, fmt.Sprintf("%s", v))
+		}
+	}
+	fmt.Fprintln(w, string(resBytes))
+}
+
 // getChatCompletionBody Returns the body of the request to create a completion.
 func getChatCompletionBody(r *http.Request) (openai.ChatCompletionRequest, error) {
 	completion := openai.ChatCompletionRequest{}
diff --git a/completion_test.go b/completion_test.go
index 935bbe8..27e2d15 100644
--- a/completion_test.go
+++ b/completion_test.go
@@ -33,6 +33,42 @@ func TestCompletionsWrongModel(t *testing.T) {
 	}
 }
 
+// TestCompletionsWrongModelO3 Tests the completions endpoint with O3 model which is not supported.
+func TestCompletionsWrongModelO3(t *testing.T) {
+	config := openai.DefaultConfig("whatever")
+	config.BaseURL = "http://localhost/v1"
+	client := openai.NewClientWithConfig(config)
+
+	_, err := client.CreateCompletion(
+		context.Background(),
+		openai.CompletionRequest{
+			MaxTokens: 5,
+			Model:     openai.O3,
+		},
+	)
+	if !errors.Is(err, openai.ErrCompletionUnsupportedModel) {
+		t.Fatalf("CreateCompletion should return ErrCompletionUnsupportedModel for O3, but returned: %v", err)
+	}
+}
+
+// TestCompletionsWrongModelO4Mini Tests the completions endpoint with O4Mini model which is not supported.
+func TestCompletionsWrongModelO4Mini(t *testing.T) {
+	config := openai.DefaultConfig("whatever")
+	config.BaseURL = "http://localhost/v1"
+	client := openai.NewClientWithConfig(config)
+
+	_, err := client.CreateCompletion(
+		context.Background(),
+		openai.CompletionRequest{
+			MaxTokens: 5,
+			Model:     openai.O4Mini,
+		},
+	)
+	if !errors.Is(err, openai.ErrCompletionUnsupportedModel) {
+		t.Fatalf("CreateCompletion should return ErrCompletionUnsupportedModel for O4Mini, but returned: %v", err)
+	}
+}
+
 func TestCompletionWithStream(t *testing.T) {
 	config := openai.DefaultConfig("whatever")
 	client := openai.NewClientWithConfig(config)
@@ -181,3 +217,86 @@ func getCompletionBody(r *http.Request) (openai.CompletionRequest, error) {
 	}
 	return completion, nil
 }
+
+// TestCompletionWithO1Model Tests that O1 model is not supported for completion endpoint.
+func TestCompletionWithO1Model(t *testing.T) {
+	config := openai.DefaultConfig("whatever")
+	config.BaseURL = "http://localhost/v1"
+	client := openai.NewClientWithConfig(config)
+
+	_, err := client.CreateCompletion(
+		context.Background(),
+		openai.CompletionRequest{
+			MaxTokens: 5,
+			Model:     openai.O1,
+		},
+	)
+	if !errors.Is(err, openai.ErrCompletionUnsupportedModel) {
+		t.Fatalf("CreateCompletion should return ErrCompletionUnsupportedModel for O1 model, but returned: %v", err)
+	}
+}
+
+// TestCompletionWithGPT4DotModels Tests that newer GPT4 models are not supported for completion endpoint.
+func TestCompletionWithGPT4DotModels(t *testing.T) {
+	config := openai.DefaultConfig("whatever")
+	config.BaseURL = "http://localhost/v1"
+	client := openai.NewClientWithConfig(config)
+
+	models := []string{
+		openai.GPT4Dot1,
+		openai.GPT4Dot120250414,
+		openai.GPT4Dot1Mini,
+		openai.GPT4Dot1Mini20250414,
+		openai.GPT4Dot1Nano,
+		openai.GPT4Dot1Nano20250414,
+		openai.GPT4Dot5Preview,
+		openai.GPT4Dot5Preview20250227,
+	}
+
+	for _, model := range models {
+		t.Run(model, func(t *testing.T) {
+			_, err := client.CreateCompletion(
+				context.Background(),
+				openai.CompletionRequest{
+					MaxTokens: 5,
+					Model:     model,
+				},
+			)
+			if !errors.Is(err, openai.ErrCompletionUnsupportedModel) {
+				t.Fatalf("CreateCompletion should return ErrCompletionUnsupportedModel for %s model, but returned: %v", model, err)
+			}
+		})
+	}
+}
+
+// TestCompletionWithGPT4oModels Tests that GPT4o models are not supported for completion endpoint.
+func TestCompletionWithGPT4oModels(t *testing.T) {
+	config := openai.DefaultConfig("whatever")
+	config.BaseURL = "http://localhost/v1"
+	client := openai.NewClientWithConfig(config)
+
+	models := []string{
+		openai.GPT4o,
+		openai.GPT4o20240513,
+		openai.GPT4o20240806,
+		openai.GPT4o20241120,
+		openai.GPT4oLatest,
+		openai.GPT4oMini,
+		openai.GPT4oMini20240718,
+	}
+
+	for _, model := range models {
+		t.Run(model, func(t *testing.T) {
+			_, err := client.CreateCompletion(
+				context.Background(),
+				openai.CompletionRequest{
+					MaxTokens: 5,
+					Model:     model,
+				},
+			)
+			if !errors.Is(err, openai.ErrCompletionUnsupportedModel) {
+				t.Fatalf("CreateCompletion should return ErrCompletionUnsupportedModel for %s model, but returned: %v", model, err)
+			}
+		})
+	}
+}
diff --git a/jsonschema/json_test.go b/jsonschema/json_test.go
index 7447060..17f0aba 100644
--- a/jsonschema/json_test.go
+++ b/jsonschema/json_test.go
@@ -17,7 +17,7 @@ func TestDefinition_MarshalJSON(t *testing.T) {
 		{
 			name: "Test with empty Definition",
 			def:  jsonschema.Definition{},
-			want: `{"properties":{}}`,
+			want: `{}`,
 		},
 		{
 			name: "Test with Definition properties set",
@@ -31,15 +31,14 @@ func TestDefinition_MarshalJSON(t *testing.T) {
 				},
 			},
 			want: `{
-   "type":"string",
-   "description":"A string type",
-   "properties":{
-      "name":{
-         "type":"string",
-         "properties":{}
-      }
-   }
-}`,
+				"type":"string",
+				"description":"A string type",
+				"properties":{
+						"name":{
+							"type":"string"
+						}
+				}
+			}`,
 		},
 		{
 			name: "Test with nested Definition properties",
@@ -60,23 +59,21 @@ func TestDefinition_MarshalJSON(t *testing.T) {
 				},
 			},
 			want: `{
-   "type":"object",
-   "properties":{
-      "user":{
-         "type":"object",
-         "properties":{
-            "name":{
-               "type":"string",
-               "properties":{}
-            },
-            "age":{
-               "type":"integer",
-               "properties":{}
-            }
-         }
-      }
-   }
-}`,
+				"type":"object",
+				"properties":{
+						"user":{
+							"type":"object",
+							"properties":{
+									"name":{
+										"type":"string"
+									},
+									"age":{
+										"type":"integer"
+									}
+							}
+						}
+				}
+			}`,
 		},
 		{
 			name: "Test with complex nested Definition",
@@ -108,36 +105,32 @@ func TestDefinition_MarshalJSON(t *testing.T) {
 				},
 			},
 			want: `{
-   "type":"object",
-   "properties":{
-      "user":{
-         "type":"object",
-         "properties":{
-            "name":{
-               "type":"string",
-               "properties":{}
-            },
-            "age":{
-               "type":"integer",
-               "properties":{}
-            },
-            "address":{
-               "type":"object",
-               "properties":{
-                  "city":{
-                     "type":"string",
-                     "properties":{}
-                  },
-                  "country":{
-                     "type":"string",
-                     "properties":{}
-                  }
-               }
-            }
-         }
-      }
-   }
-}`,
+				"type":"object",
+				"properties":{
+						"user":{
+							"type":"object",
+							"properties":{
+									"name":{
+										"type":"string"
+									},
+									"age":{
+										"type":"integer"
+									},
+									"address":{
+										"type":"object",
+										"properties":{
+												"city":{
+													"type":"string"
+												},
+												"country":{
+													"type":"string"
+												}
+										}
+									}
+							}
+						}
+				}
+			}`,
 		},
 		{
 			name: "Test with Array type Definition",
@@ -153,20 +146,16 @@ func TestDefinition_MarshalJSON(t *testing.T) {
 				},
 			},
 			want: `{
-   "type":"array",
-   "items":{
-      "type":"string",
-      "properties":{
-         
-      }
-   },
-   "properties":{
-      "name":{
-         "type":"string",
-         "properties":{}
-      }
-   }
-}`,
+				"type":"array",
+				"items":{
+						"type":"string"
+				},
+				"properties":{
+						"name":{
+							"type":"string"
+						}
+				}
+			}`,
 		},
 	}
 
@@ -193,6 +182,185 @@ func TestDefinition_MarshalJSON(t *testing.T) {
 	}
 }
 
+func TestStructToSchema(t *testing.T) {
+	tests := []struct {
+		name string
+		in   any
+		want string
+	}{
+		{
+			name: "Test with empty struct",
+			in:   struct{}{},
+			want: `{
+				"type":"object",
+				"additionalProperties":false
+			}`,
+		},
+		{
+			name: "Test with struct containing many fields",
+			in: struct {
+				Name   string  `json:"name"`
+				Age    int     `json:"age"`
+				Active bool    `json:"active"`
+				Height float64 `json:"height"`
+				Cities []struct {
+					Name  string `json:"name"`
+					State string `json:"state"`
+				} `json:"cities"`
+			}{
+				Name: "John Doe",
+				Age:  30,
+				Cities: []struct {
+					Name  string `json:"name"`
+					State string `json:"state"`
+				}{
+					{Name: "New York", State: "NY"},
+					{Name: "Los Angeles", State: "CA"},
+				},
+			},
+			want: `{
+				"type":"object",
+				"properties":{
+					"name":{
+						"type":"string"
+					},
+					"age":{
+						"type":"integer"
+					},
+					"active":{
+						"type":"boolean"
+					},
+					"height":{
+						"type":"number"
+					},
+					"cities":{
+						"type":"array",
+						"items":{
+							"additionalProperties":false,
+							"type":"object",
+							"properties":{
+								"name":{
+									"type":"string"
+								},
+								"state":{
+									"type":"string"
+								}
+							},
+							"required":["name","state"]
+						}
+					}
+				},
+				"required":["name","age","active","height","cities"],
+				"additionalProperties":false
+			}`,
+		},
+		{
+			name: "Test with description tag",
+			in: struct {
+				Name string `json:"name" description:"The name of the person"`
+			}{
+				Name: "John Doe",
+			},
+			want: `{
+				"type":"object",
+				"properties":{
+					"name":{
+						"type":"string",
+						"description":"The name of the person"
+					}
+				},
+				"required":["name"],
+				"additionalProperties":false
+			}`,
+		},
+		{
+			name: "Test with required tag",
+			in: struct {
+				Name string `json:"name" required:"false"`
+			}{
+				Name: "John Doe",
+			},
+			want: `{
+				"type":"object",
+				"properties":{
+					"name":{
+						"type":"string"
+					}
+				},
+				"additionalProperties":false
+			}`,
+		},
+		{
+			name: "Test with enum tag",
+			in: struct {
+				Color string `json:"color" enum:"red,green,blue"`
+			}{
+				Color: "red",
+			},
+			want: `{
+				"type":"object",
+				"properties":{
+					"color":{
+						"type":"string",
+						"enum":["red","green","blue"]
+					}
+				},
+				"required":["color"],
+				"additionalProperties":false
+			}`,
+		},
+		{
+			name: "Test with nullable tag",
+			in: struct {
+				Name *string `json:"name" nullable:"true"`
+			}{
+				Name: nil,
+			},
+			want: `{
+
+				"type":"object",
+				"properties":{
+					"name":{
+						"type":"string",
+						"nullable":true
+					}
+				},
+				"required":["name"],
+				"additionalProperties":false
+			}`,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			wantBytes := []byte(tt.want)
+
+			schema, err := jsonschema.GenerateSchemaForType(tt.in)
+			if err != nil {
+				t.Errorf("Failed to generate schema: error = %v", err)
+				return
+			}
+
+			var want map[string]interface{}
+			err = json.Unmarshal(wantBytes, &want)
+			if err != nil {
+				t.Errorf("Failed to Unmarshal JSON: error = %v", err)
+				return
+			}
+
+			got := structToMap(t, schema)
+			gotPtr := structToMap(t, &schema)
+
+			if !reflect.DeepEqual(got, want) {
+				t.Errorf("MarshalJSON() got = %v, want %v", got, want)
+			}
+			if !reflect.DeepEqual(gotPtr, want) {
+				t.Errorf("MarshalJSON() gotPtr = %v, want %v", gotPtr, want)
+			}
+		})
+	}
+}
+
 func structToMap(t *testing.T, v any) map[string]any {
 	t.Helper()
 	gotBytes, err := json.Marshal(v)
diff --git a/models_test.go b/models_test.go
index 24a28ed..7fd010c 100644
--- a/models_test.go
+++ b/models_test.go
@@ -47,6 +47,24 @@ func TestGetModel(t *testing.T) {
 	checks.NoError(t, err, "GetModel error")
 }
 
+// TestGetModelO3 Tests the retrieve O3 model endpoint of the API using the mocked server.
+func TestGetModelO3(t *testing.T) {
+	client, server, teardown := setupOpenAITestServer()
+	defer teardown()
+	server.RegisterHandler("/v1/models/o3", handleGetModelEndpoint)
+	_, err := client.GetModel(context.Background(), "o3")
+	checks.NoError(t, err, "GetModel error for O3")
+}
+
+// TestGetModelO4Mini Tests the retrieve O4Mini model endpoint of the API using the mocked server.
+func TestGetModelO4Mini(t *testing.T) {
+	client, server, teardown := setupOpenAITestServer()
+	defer teardown()
+	server.RegisterHandler("/v1/models/o4-mini", handleGetModelEndpoint)
+	_, err := client.GetModel(context.Background(), "o4-mini")
+	checks.NoError(t, err, "GetModel error for O4Mini")
+}
+
 func TestAzureGetModel(t *testing.T) {
 	client, server, teardown := setupAzureTestServer()
 	defer teardown()
diff --git a/openai_test.go b/openai_test.go
index 6c26eeb..a55f3a8 100644
--- a/openai_test.go
+++ b/openai_test.go
@@ -29,7 +29,7 @@ func setupAzureTestServer() (client *openai.Client, server *test.ServerTest, tea
 
 // numTokens Returns the number of GPT-3 encoded tokens in the given text.
 // This function approximates based on the rule of thumb stated by OpenAI:
-// https://beta.openai.com/tokenizer/
+// https://beta.openai.com/tokenizer.
 //
 // TODO: implement an actual tokenizer for GPT-3 and Codex (once available).
 func numTokens(s string) int {