Repository: sashabaranov/go-openai
Pull request #668 (2024-02-22 13:04:28)

Fix for broken Azure Threads url
Threads API is broken for Azure OpenAI implementation returning a 404 error

Expected URL = https://your_resource_name.openai.azure.com/openai/threads?api-version=2024-02-15-preview
Current URL = https://your_resource_name.openai.azure.com/openai/deployments/UNKNOWN/threads?api-version=2024-02-15-preview

Link to Microsoft docs: https://github.com/MicrosoftDocs/azure-docs/blob/bb63f74fabd8a70267e1ab2960773f706c8c50c3/articles/ai-services/openai/assistants-reference-threads.md

Fix removes /deployments/UNKNOWN/ by using same approach for /models /assistants API

Added unit tests for Azure Threads API

FYI, this fix also addresses same issue with messages & runs APIs


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/sashabaranov/go-openai/pull/668?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`7381d18`)](https://app.codecov.io/gh/sashabaranov/go-openai/commit/7381d18a75a673d569c7dc7657407381e5c84dd5?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) 98.44% compared to head [(`eb52d98`)](https://app.codecov.io/gh/sashabaranov/go-openai/pull/668?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) 98.44%.


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


```diff
@@           Coverage Diff           @@
##           master     #668   +/-   ##
=======================================
  Coverage   98.44%   98.44%           
=======================================
  Files          24       24           
  Lines        1347     1348    +1     
=======================================
+ Hits         1326     1327    +1     
  Misses         15       15           
  Partials        6        6           
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/sashabaranov/go-openai/pull/668?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).   
: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=None).

coggsflod: @sashabaranov any further comments / issues you'd like to address?

Patch:
diff --git a/client.go b/client.go
index e7a4d5b..7b1a313 100644
--- a/client.go
+++ b/client.go
@@ -221,7 +221,7 @@ func (c *Client) fullURL(suffix string, args ...any) string {
 		baseURL = strings.TrimRight(baseURL, "/")
 		// if suffix is /models change to {endpoint}/openai/models?api-version=2022-12-01
 		// https://learn.microsoft.com/en-us/rest/api/cognitiveservices/azureopenaistable/models/list?tabs=HTTP
-		if strings.Contains(suffix, "/models") || strings.Contains(suffix, "/assistants") {
+		if containsSubstr([]string{"/models", "/assistants", "/threads", "/files"}, suffix) {
 			return fmt.Sprintf("%s/%s%s?api-version=%s", baseURL, azureAPIPrefix, suffix, c.config.APIVersion)
 		}
 		azureDeploymentName := "UNKNOWN"
@@ -258,3 +258,12 @@ func (c *Client) handleErrorResp(resp *http.Response) error {
 	errRes.Error.HTTPStatusCode = resp.StatusCode
 	return errRes.Error
 }
+
+func containsSubstr(s []string, e string) bool {
+	for _, v := range s {
+		if strings.Contains(e, v) {
+			return true
+		}
+	}
+	return false
+}


Test patch:
diff --git a/thread_test.go b/thread_test.go
index 227ab63..1ac0f3c 100644
--- a/thread_test.go
+++ b/thread_test.go
@@ -93,3 +93,86 @@ func TestThread(t *testing.T) {
 	_, err = client.DeleteThread(ctx, threadID)
 	checks.NoError(t, err, "DeleteThread error")
 }
+
+// TestAzureThread Tests the thread endpoint of the API using the Azure mocked server.
+func TestAzureThread(t *testing.T) {
+	threadID := "thread_abc123"
+	client, server, teardown := setupAzureTestServer()
+	defer teardown()
+
+	server.RegisterHandler(
+		"/openai/threads/"+threadID,
+		func(w http.ResponseWriter, r *http.Request) {
+			switch r.Method {
+			case http.MethodGet:
+				resBytes, _ := json.Marshal(openai.Thread{
+					ID:        threadID,
+					Object:    "thread",
+					CreatedAt: 1234567890,
+				})
+				fmt.Fprintln(w, string(resBytes))
+			case http.MethodPost:
+				var request openai.ThreadRequest
+				err := json.NewDecoder(r.Body).Decode(&request)
+				checks.NoError(t, err, "Decode error")
+
+				resBytes, _ := json.Marshal(openai.Thread{
+					ID:        threadID,
+					Object:    "thread",
+					CreatedAt: 1234567890,
+				})
+				fmt.Fprintln(w, string(resBytes))
+			case http.MethodDelete:
+				fmt.Fprintln(w, `{
+					"id": "thread_abc123",
+					"object": "thread.deleted",
+					"deleted": true
+					}`)
+			}
+		},
+	)
+
+	server.RegisterHandler(
+		"/openai/threads",
+		func(w http.ResponseWriter, r *http.Request) {
+			if r.Method == http.MethodPost {
+				var request openai.ModifyThreadRequest
+				err := json.NewDecoder(r.Body).Decode(&request)
+				checks.NoError(t, err, "Decode error")
+
+				resBytes, _ := json.Marshal(openai.Thread{
+					ID:        threadID,
+					Object:    "thread",
+					CreatedAt: 1234567890,
+					Metadata:  request.Metadata,
+				})
+				fmt.Fprintln(w, string(resBytes))
+			}
+		},
+	)
+
+	ctx := context.Background()
+
+	_, err := client.CreateThread(ctx, openai.ThreadRequest{
+		Messages: []openai.ThreadMessage{
+			{
+				Role:    openai.ThreadMessageRoleUser,
+				Content: "Hello, World!",
+			},
+		},
+	})
+	checks.NoError(t, err, "CreateThread error")
+
+	_, err = client.RetrieveThread(ctx, threadID)
+	checks.NoError(t, err, "RetrieveThread error")
+
+	_, err = client.ModifyThread(ctx, threadID, openai.ModifyThreadRequest{
+		Metadata: map[string]interface{}{
+			"key": "value",
+		},
+	})
+	checks.NoError(t, err, "ModifyThread error")
+
+	_, err = client.DeleteThread(ctx, threadID)
+	checks.NoError(t, err, "DeleteThread error")
+}