Repository: sashabaranov/go-openai
Pull request #885 (2024-10-22 18:29:53)

Updated checkPromptType function to handle prompt list in completions
**Describe the change**
The checkPromptType function has been updated to support validation of []interface{} slices, ensuring that each element in the slice is of type string. 
Currently if list of string is provided in prompt field ErrCompletionRequestPromptTypeNotSupported error is returned.
Example:
for this completions request-
```
{
	"model": "gemma-vllm",
	"prompt": ["Explain transformers in atelast 100 words or more","Hello whoare you"],
	"max_tokens": 150
}
```
This error will occur-
```
Completion error: the type of CompletionRequest.Prompt only supports string and []string
```
This is because arrays in JSON are decoded into Go as slices of interface{} ([]interface{}), not as slices of a specific type like []string. Therefore, when a JSON request containing a list of strings is unmarshalled, the resulting value of request.Prompt is a []interface{}. The checkPromptType function only checked for string and []string, so its returning false when request.Prompt is a []interface{} even if all elements within that slice are strings, because []interface{} is not the same type as []string.

This change allows better validation of prompt types, accommodating the scenario where the prompt is provided as a list of strings in an interface{} slice.

**Describe your solution**
The solution involves adding a new type check for []interface{} in the checkPromptType function. The code checks if prompt is of type []interface{} and, if so, iterates over the slice to ensure all elements are strings. If any element is not a string, the validation fails. This logic is integrated alongside the existing checks for string and []string types. This enhancement ensures that the function properly handles various input types for prompts and raises the ErrCompletionRequestPromptTypeNotSupported error if an unsupported type is provided.



codecov[bot]: ## [Codecov](https://app.codecov.io/gh/sashabaranov/go-openai/pull/885?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alex+Baranov) Report
Attention: Patch coverage is `25.00000%` with `6 lines` in your changes missing coverage. Please review.
> Project coverage is 98.51%. 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 [(`16684b5`)](https://app.codecov.io/gh/sashabaranov/go-openai/commit/16684b58814b250008806904888276d959eab226?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alex+Baranov).
> Report is 66 commits behind head on master.

| [Files with missing lines](https://app.codecov.io/gh/sashabaranov/go-openai/pull/885?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alex+Baranov) | Patch % | Lines |
|---|---|---|
| [completion.go](https://app.codecov.io/gh/sashabaranov/go-openai/pull/885?src=pr&el=tree&filepath=completion.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alex+Baranov#diff-Y29tcGxldGlvbi5nbw==) | 25.00% | [5 Missing and 1 partial :warning: ](https://app.codecov.io/gh/sashabaranov/go-openai/pull/885?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alex+Baranov) |

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


```diff
@@            Coverage Diff             @@
##           master     #885      +/-   ##
==========================================
+ Coverage   98.46%   98.51%   +0.05%     
==========================================
  Files          24       26       +2     
  Lines        1364     1756     +392     
==========================================
+ Hits         1343     1730     +387     
- Misses         15       19       +4     
- Partials        6        7       +1     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/sashabaranov/go-openai/pull/885?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).

johnugeorge: @sashabaranov  Can you review? Currently, completions api is not working due to this bug 
sashabaranov: Thank you!

Patch:
diff --git a/completion.go b/completion.go
index 84ef2ad..77ea8c3 100644
--- a/completion.go
+++ b/completion.go
@@ -161,7 +161,23 @@ func checkEndpointSupportsModel(endpoint, model string) bool {
 func checkPromptType(prompt any) bool {
 	_, isString := prompt.(string)
 	_, isStringSlice := prompt.([]string)
-	return isString || isStringSlice
+	if isString || isStringSlice {
+		return true
+	}
+
+	// check if it is prompt is []string hidden under []any
+	slice, isSlice := prompt.([]any)
+	if !isSlice {
+		return false
+	}
+
+	for _, item := range slice {
+		_, itemIsString := item.(string)
+		if !itemIsString {
+			return false
+		}
+	}
+	return true // all items in the slice are string, so it is []string
 }
 
 var unsupportedToolsForO1Models = map[ToolType]struct{}{


Test patch:
diff --git a/completion_test.go b/completion_test.go
index 89950bf..935bbe8 100644
--- a/completion_test.go
+++ b/completion_test.go
@@ -59,6 +59,38 @@ func TestCompletions(t *testing.T) {
 	checks.NoError(t, err, "CreateCompletion error")
 }
 
+// TestMultiplePromptsCompletionsWrong Tests the completions endpoint of the API using the mocked server
+// where the completions requests has a list of prompts with wrong type.
+func TestMultiplePromptsCompletionsWrong(t *testing.T) {
+	client, server, teardown := setupOpenAITestServer()
+	defer teardown()
+	server.RegisterHandler("/v1/completions", handleCompletionEndpoint)
+	req := openai.CompletionRequest{
+		MaxTokens: 5,
+		Model:     "ada",
+		Prompt:    []interface{}{"Lorem ipsum", 9},
+	}
+	_, err := client.CreateCompletion(context.Background(), req)
+	if !errors.Is(err, openai.ErrCompletionRequestPromptTypeNotSupported) {
+		t.Fatalf("CreateCompletion should return ErrCompletionRequestPromptTypeNotSupported, but returned: %v", err)
+	}
+}
+
+// TestMultiplePromptsCompletions Tests the completions endpoint of the API using the mocked server
+// where the completions requests has a list of prompts.
+func TestMultiplePromptsCompletions(t *testing.T) {
+	client, server, teardown := setupOpenAITestServer()
+	defer teardown()
+	server.RegisterHandler("/v1/completions", handleCompletionEndpoint)
+	req := openai.CompletionRequest{
+		MaxTokens: 5,
+		Model:     "ada",
+		Prompt:    []interface{}{"Lorem ipsum", "Lorem ipsum"},
+	}
+	_, err := client.CreateCompletion(context.Background(), req)
+	checks.NoError(t, err, "CreateCompletion error")
+}
+
 // handleCompletionEndpoint Handles the completion endpoint by the test server.
 func handleCompletionEndpoint(w http.ResponseWriter, r *http.Request) {
 	var err error
@@ -87,24 +119,50 @@ func handleCompletionEndpoint(w http.ResponseWriter, r *http.Request) {
 	if n == 0 {
 		n = 1
 	}
+	// Handle different types of prompts: single string or list of strings
+	prompts := []string{}
+	switch v := completionReq.Prompt.(type) {
+	case string:
+		prompts = append(prompts, v)
+	case []interface{}:
+		for _, item := range v {
+			if str, ok := item.(string); ok {
+				prompts = append(prompts, str)
+			}
+		}
+	default:
+		http.Error(w, "Invalid prompt type", http.StatusBadRequest)
+		return
+	}
+
 	for i := 0; i < n; i++ {
-		// generate a random string of length completionReq.Length
-		completionStr := strings.Repeat("a", completionReq.MaxTokens)
-		if completionReq.Echo {
-			completionStr = completionReq.Prompt.(string) + completionStr
+		for _, prompt := range prompts {
+			// Generate a random string of length completionReq.MaxTokens
+			completionStr := strings.Repeat("a", completionReq.MaxTokens)
+			if completionReq.Echo {
+				completionStr = prompt + completionStr
+			}
+
+			res.Choices = append(res.Choices, openai.CompletionChoice{
+				Text:  completionStr,
+				Index: len(res.Choices),
+			})
 		}
-		res.Choices = append(res.Choices, openai.CompletionChoice{
-			Text:  completionStr,
-			Index: i,
-		})
 	}
-	inputTokens := numTokens(completionReq.Prompt.(string)) * n
-	completionTokens := completionReq.MaxTokens * n
+
+	inputTokens := 0
+	for _, prompt := range prompts {
+		inputTokens += numTokens(prompt)
+	}
+	inputTokens *= n
+	completionTokens := completionReq.MaxTokens * len(prompts) * n
 	res.Usage = openai.Usage{
 		PromptTokens:     inputTokens,
 		CompletionTokens: completionTokens,
 		TotalTokens:      inputTokens + completionTokens,
 	}
+
+	// Serialize the response and send it back
 	resBytes, _ = json.Marshal(res)
 	fmt.Fprintln(w, string(resBytes))
 }