Repository: sashabaranov/go-openai
Pull request #437 (2023-07-12 01:39:07)

Check if the model param is valid for moderations endpoint
Currently there are only [two models available](https://platform.openai.com/docs/api-reference/moderations/create) for OpenAI /moderations endpoint, which are `text-moderation-stable` and `text-moderation-latest`. In the official python library `openai-python`. The method Moderation will check if the model param is in `VALID_MODEL_NAMES` and returns an error when it's not. 

```python
 def _prepare_create(cls, input, model, api_key):
        if model is not None and model not in cls.VALID_MODEL_NAMES:
            raise ValueError(
                f"The parameter model should be chosen from {cls.VALID_MODEL_NAMES} "
                f"and it is default to be None."
            )

        instance = cls(api_key=api_key)
        params = {"input": input}
        if model is not None:
            params["model"] = model
        return instance, params
```

It would be great if we can add similar checks before passing the request to `/v1/moderations` endpoint.



codecov[bot]: ## [Codecov](https://app.codecov.io/gh/sashabaranov/go-openai/pull/437?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#437](https://app.codecov.io/gh/sashabaranov/go-openai/pull/437?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (e579559) into [master](https://app.codecov.io/gh/sashabaranov/go-openai/commit/f028c289d2e2ae7562d97594d122447fd23a632d?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (f028c28) will **increase** coverage by `0.01%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##           master     #437      +/-   ##
==========================================
+ Coverage   97.02%   97.03%   +0.01%     
==========================================
  Files          17       17              
  Lines         705      709       +4     
==========================================
+ Hits          684      688       +4     
  Misses         15       15              
  Partials        6        6              
```


| [Impacted Files](https://app.codecov.io/gh/sashabaranov/go-openai/pull/437?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [moderation.go](https://app.codecov.io/gh/sashabaranov/go-openai/pull/437?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-bW9kZXJhdGlvbi5nbw==) | `100.00% <100.00%> (ø)` | |


vvatanabe: Confirmed not supported just in case. 👍 
```
$ curl https://api.openai.com/v1/moderations \
                    -H "Content-Type: application/json" \
                    -H "Authorization: Bearer $OPENAI_API_KEY" \
                    -d '{
                  "input": "I want to kill them.", "model": "text-moderation-001"
                }'
{

  "error": {
    "message": "Invalid value for 'model' = text-moderation-001. Please check the OpenAI documentation and try again.",
    "type": "invalid_request_error",
    "param": "model",
    "code": null
  }
}
```
MunaerYesiyan: > Confirmed not supported just in case. 👍
> 
> ```
> $ curl https://api.openai.com/v1/moderations \
>                     -H "Content-Type: application/json" \
>                     -H "Authorization: Bearer $OPENAI_API_KEY" \
>                     -d '{
>                   "input": "I want to kill them.", "model": "text-moderation-001"
>                 }'
> {
> 
>   "error": {
>     "message": "Invalid value for 'model' = text-moderation-001. Please check the OpenAI documentation and try again.",
>     "type": "invalid_request_error",
>     "param": "model",
>     "code": null
>   }
> }
> ```

And it's kinda confusing as the official document uses `text-moderation-005` in the response [example ](https://platform.openai.com/docs/api-reference/moderations/create)


Patch:
diff --git a/moderation.go b/moderation.go
index a58d759..a32f123 100644
--- a/moderation.go
+++ b/moderation.go
@@ -2,6 +2,7 @@ package openai
 
 import (
 	"context"
+	"errors"
 	"net/http"
 )
 
@@ -15,9 +16,19 @@ import (
 const (
 	ModerationTextStable = "text-moderation-stable"
 	ModerationTextLatest = "text-moderation-latest"
-	ModerationText001    = "text-moderation-001"
+	// Deprecated: use ModerationTextStable and ModerationTextLatest instead.
+	ModerationText001 = "text-moderation-001"
 )
 
+var (
+	ErrModerationInvalidModel = errors.New("this model is not supported with moderation, please use text-moderation-stable or text-moderation-latest instead") //nolint:lll
+)
+
+var validModerationModel = map[string]struct{}{
+	ModerationTextStable: {},
+	ModerationTextLatest: {},
+}
+
 // ModerationRequest represents a request structure for moderation API.
 type ModerationRequest struct {
 	Input string `json:"input,omitempty"`
@@ -63,6 +74,10 @@ type ModerationResponse struct {
 // Moderations — perform a moderation api call over a string.
 // Input can be an array or slice but a string will reduce the complexity.
 func (c *Client) Moderations(ctx context.Context, request ModerationRequest) (response ModerationResponse, err error) {
+	if _, ok := validModerationModel[request.Model]; len(request.Model) > 0 && !ok {
+		err = ErrModerationInvalidModel
+		return
+	}
 	req, err := c.newRequest(ctx, http.MethodPost, c.fullURL("/moderations", request.Model), withBody(&request))
 	if err != nil {
 		return


Test patch:
diff --git a/moderation_test.go b/moderation_test.go
index 4e75613..68f9565 100644
--- a/moderation_test.go
+++ b/moderation_test.go
@@ -27,6 +27,41 @@ func TestModerations(t *testing.T) {
 	checks.NoError(t, err, "Moderation error")
 }
 
+// TestModerationsWithIncorrectModel Tests passing valid and invalid models to moderations endpoint.
+func TestModerationsWithDifferentModelOptions(t *testing.T) {
+	var modelOptions []struct {
+		model  string
+		expect error
+	}
+	modelOptions = append(modelOptions,
+		getModerationModelTestOption(GPT3Dot5Turbo, ErrModerationInvalidModel),
+		getModerationModelTestOption(ModerationTextStable, nil),
+		getModerationModelTestOption(ModerationTextLatest, nil),
+		getModerationModelTestOption("", nil),
+	)
+	client, server, teardown := setupOpenAITestServer()
+	defer teardown()
+	server.RegisterHandler("/v1/moderations", handleModerationEndpoint)
+	for _, modelTest := range modelOptions {
+		_, err := client.Moderations(context.Background(), ModerationRequest{
+			Model: modelTest.model,
+			Input: "I want to kill them.",
+		})
+		checks.ErrorIs(t, err, modelTest.expect,
+			fmt.Sprintf("Moderations(..) expects err: %v, actual err:%v", modelTest.expect, err))
+	}
+}
+
+func getModerationModelTestOption(model string, expect error) struct {
+	model  string
+	expect error
+} {
+	return struct {
+		model  string
+		expect error
+	}{model: model, expect: expect}
+}
+
 // handleModerationEndpoint Handles the moderation endpoint by the test server.
 func handleModerationEndpoint(w http.ResponseWriter, r *http.Request) {
 	var err error