Repository: sashabaranov/go-openai
Pull request #499 (2023-09-25 06:45:35)

fix: use any for n_epochs
Issue: #496 

**Describe the change**
Hyper parameter `n_epochs` can be either a string or a int value.

**Describe your solution**
Develop a custom un/marshaller for `n_epochs` parameter.




Patch:
diff --git a/README.md b/README.md
index 440c409..c618cd7 100644
--- a/README.md
+++ b/README.md
@@ -483,6 +483,62 @@ func main() {
 ```
 </details>
 
+<detail>
+<summary>Embedding Semantic Similarity</summary>
+
+```go
+package main
+
+import (
+	"context"
+	"log"
+	openai "github.com/sashabaranov/go-openai"
+
+)
+
+func main() {
+	client := openai.NewClient("your-token")
+
+	// Create an EmbeddingRequest for the user query
+	queryReq := openai.EmbeddingRequest{
+		Input: []string{"How many chucks would a woodchuck chuck"},
+		Model: openai.AdaEmbeddingv2,
+	}
+
+	// Create an embedding for the user query
+	queryResponse, err := client.CreateEmbeddings(context.Background(), queryReq)
+	if err != nil {
+		log.Fatal("Error creating query embedding:", err)
+	}
+
+	// Create an EmbeddingRequest for the target text
+	targetReq := openai.EmbeddingRequest{
+		Input: []string{"How many chucks would a woodchuck chuck if the woodchuck could chuck wood"},
+		Model: openai.AdaEmbeddingv2,
+	}
+
+	// Create an embedding for the target text
+	targetResponse, err := client.CreateEmbeddings(context.Background(), targetReq)
+	if err != nil {
+		log.Fatal("Error creating target embedding:", err)
+	}
+
+	// Now that we have the embeddings for the user query and the target text, we
+	// can calculate their similarity.
+	queryEmbedding := queryResponse.Data[0]
+	targetEmbedding := targetResponse.Data[0]
+
+	similarity, err := queryEmbedding.DotProduct(&targetEmbedding)
+	if err != nil {
+		log.Fatal("Error calculating dot product:", err)
+	}
+
+	log.Printf("The similarity score between the query and the target is %f", similarity)
+}
+
+```
+</detail>
+
 <details>
 <summary>Azure OpenAI Embeddings</summary>
 
diff --git a/embeddings.go b/embeddings.go
index 5ba91f2..660bc24 100644
--- a/embeddings.go
+++ b/embeddings.go
@@ -4,10 +4,13 @@ import (
 	"context"
 	"encoding/base64"
 	"encoding/binary"
+	"errors"
 	"math"
 	"net/http"
 )
 
+var ErrVectorLengthMismatch = errors.New("vector length mismatch")
+
 // EmbeddingModel enumerates the models which can be used
 // to generate Embedding vectors.
 type EmbeddingModel int
@@ -124,6 +127,23 @@ type Embedding struct {
 	Index     int       `json:"index"`
 }
 
+// DotProduct calculates the dot product of the embedding vector with another
+// embedding vector. Both vectors must have the same length; otherwise, an
+// ErrVectorLengthMismatch is returned. The method returns the calculated dot
+// product as a float32 value.
+func (e *Embedding) DotProduct(other *Embedding) (float32, error) {
+	if len(e.Embedding) != len(other.Embedding) {
+		return 0, ErrVectorLengthMismatch
+	}
+
+	var dotProduct float32
+	for i := range e.Embedding {
+		dotProduct += e.Embedding[i] * other.Embedding[i]
+	}
+
+	return dotProduct, nil
+}
+
 // EmbeddingResponse is the response from a Create embeddings request.
 type EmbeddingResponse struct {
 	Object string         `json:"object"`
diff --git a/fine_tuning_job.go b/fine_tuning_job.go
index a840b7e..07b0c33 100644
--- a/fine_tuning_job.go
+++ b/fine_tuning_job.go
@@ -24,7 +24,7 @@ type FineTuningJob struct {
 }
 
 type Hyperparameters struct {
-	Epochs int `json:"n_epochs"`
+	Epochs any `json:"n_epochs,omitempty"`
 }
 
 type FineTuningJobRequest struct {
diff --git a/models.go b/models.go
index 560402e..c207f0a 100644
--- a/models.go
+++ b/models.go
@@ -33,6 +33,13 @@ type Permission struct {
 	IsBlocking         bool        `json:"is_blocking"`
 }
 
+// FineTuneModelDeleteResponse represents the deletion status of a fine-tuned model.
+type FineTuneModelDeleteResponse struct {
+	ID      string `json:"id"`
+	Object  string `json:"object"`
+	Deleted bool   `json:"deleted"`
+}
+
 // ModelsList is a list of models, including those that belong to the user or organization.
 type ModelsList struct {
 	Models []Model `json:"data"`
@@ -62,3 +69,16 @@ func (c *Client) GetModel(ctx context.Context, modelID string) (model Model, err
 	err = c.sendRequest(req, &model)
 	return
 }
+
+// DeleteFineTuneModel Deletes a fine-tune model. You must have the Owner
+// role in your organization to delete a model.
+func (c *Client) DeleteFineTuneModel(ctx context.Context, modelID string) (
+	response FineTuneModelDeleteResponse, err error) {
+	req, err := c.newRequest(ctx, http.MethodDelete, c.fullURL("/models/"+modelID))
+	if err != nil {
+		return
+	}
+
+	err = c.sendRequest(req, &response)
+	return
+}


Test patch:
diff --git a/client_test.go b/client_test.go
index 9b50468..2c1d749 100644
--- a/client_test.go
+++ b/client_test.go
@@ -271,6 +271,9 @@ func TestClientReturnsRequestBuilderErrors(t *testing.T) {
 		{"GetModel", func() (any, error) {
 			return client.GetModel(ctx, "text-davinci-003")
 		}},
+		{"DeleteFineTuneModel", func() (any, error) {
+			return client.DeleteFineTuneModel(ctx, "")
+		}},
 	}
 
 	for _, testCase := range testCases {
diff --git a/embeddings_test.go b/embeddings_test.go
index 9c48c5b..72e8c24 100644
--- a/embeddings_test.go
+++ b/embeddings_test.go
@@ -4,7 +4,9 @@ import (
 	"bytes"
 	"context"
 	"encoding/json"
+	"errors"
 	"fmt"
+	"math"
 	"net/http"
 	"reflect"
 	"testing"
@@ -233,3 +235,39 @@ func TestEmbeddingResponseBase64_ToEmbeddingResponse(t *testing.T) {
 		})
 	}
 }
+
+func TestDotProduct(t *testing.T) {
+	v1 := &Embedding{Embedding: []float32{1, 2, 3}}
+	v2 := &Embedding{Embedding: []float32{2, 4, 6}}
+	expected := float32(28.0)
+
+	result, err := v1.DotProduct(v2)
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	if math.Abs(float64(result-expected)) > 1e-12 {
+		t.Errorf("Unexpected result. Expected: %v, but got %v", expected, result)
+	}
+
+	v1 = &Embedding{Embedding: []float32{1, 0, 0}}
+	v2 = &Embedding{Embedding: []float32{0, 1, 0}}
+	expected = float32(0.0)
+
+	result, err = v1.DotProduct(v2)
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	if math.Abs(float64(result-expected)) > 1e-12 {
+		t.Errorf("Unexpected result. Expected: %v, but got %v", expected, result)
+	}
+
+	// Test for VectorLengthMismatchError
+	v1 = &Embedding{Embedding: []float32{1, 0, 0}}
+	v2 = &Embedding{Embedding: []float32{0, 1}}
+	_, err = v1.DotProduct(v2)
+	if !errors.Is(err, ErrVectorLengthMismatch) {
+		t.Errorf("Expected Vector Length Mismatch Error, but got: %v", err)
+	}
+}
diff --git a/fine_tuning_job_test.go b/fine_tuning_job_test.go
index 519c6cd..f6d41c3 100644
--- a/fine_tuning_job_test.go
+++ b/fine_tuning_job_test.go
@@ -21,8 +21,23 @@ func TestFineTuningJob(t *testing.T) {
 	server.RegisterHandler(
 		"/v1/fine_tuning/jobs",
 		func(w http.ResponseWriter, r *http.Request) {
-			var resBytes []byte
-			resBytes, _ = json.Marshal(FineTuningJob{})
+			resBytes, _ := json.Marshal(FineTuningJob{
+				Object:         "fine_tuning.job",
+				ID:             testFineTuninigJobID,
+				Model:          "davinci-002",
+				CreatedAt:      1692661014,
+				FinishedAt:     1692661190,
+				FineTunedModel: "ft:davinci-002:my-org:custom_suffix:7q8mpxmy",
+				OrganizationID: "org-123",
+				ResultFiles:    []string{"file-abc123"},
+				Status:         "succeeded",
+				ValidationFile: "",
+				TrainingFile:   "file-abc123",
+				Hyperparameters: Hyperparameters{
+					Epochs: "auto",
+				},
+				TrainedTokens: 5768,
+			})
 			fmt.Fprintln(w, string(resBytes))
 		},
 	)
diff --git a/models_test.go b/models_test.go
index 59b4f5e..9ff7304 100644
--- a/models_test.go
+++ b/models_test.go
@@ -14,6 +14,8 @@ import (
 	"testing"
 )
 
+const testFineTuneModelID = "fine-tune-model-id"
+
 // TestListModels Tests the list models endpoint of the API using the mocked server.
 func TestListModels(t *testing.T) {
 	client, server, teardown := setupOpenAITestServer()
@@ -78,3 +80,16 @@ func TestGetModelReturnTimeoutError(t *testing.T) {
 		t.Fatal("Did not return timeout error")
 	}
 }
+
+func TestDeleteFineTuneModel(t *testing.T) {
+	client, server, teardown := setupOpenAITestServer()
+	defer teardown()
+	server.RegisterHandler("/v1/models/"+testFineTuneModelID, handleDeleteFineTuneModelEndpoint)
+	_, err := client.DeleteFineTuneModel(context.Background(), testFineTuneModelID)
+	checks.NoError(t, err, "DeleteFineTuneModel error")
+}
+
+func handleDeleteFineTuneModelEndpoint(w http.ResponseWriter, _ *http.Request) {
+	resBytes, _ := json.Marshal(FineTuneModelDeleteResponse{})
+	fmt.Fprintln(w, string(resBytes))
+}