Repository: sashabaranov/go-openai
Pull request #665 (2024-02-20 22:56:39)

Fix for broken Azure Assistants url
Assistants API is broken for Azure OpenAI implementation returning a 404 error
 
Expected URL = https://YOUR_RESOURCE_NAME.openai.azure.com/openai/assistants?api-version=2024-02-15-preview
Current URL = https://YOUR_RESOURCE_NAME.openai.azure.com/openai/deployments/UNKNOWN/assistants?api-version=2024-02-15-preview

Link to Microsoft docs: https://github.com/MicrosoftDocs/azure-docs/blob/bb63f74fabd8a70267e1ab2960773f706c8c50c3/articles/ai-services/openai/assistants-reference.md

Fix removes /deployments/UNKNOWN/ by using same approach for /models API

Added unit tests for Azure Assistants APi


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/sashabaranov/go-openai/pull/665?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 [(`e8b3478`)](https://app.codecov.io/gh/sashabaranov/go-openai/commit/e8b347891b21187740d594409b1c11fb0846577e?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) 98.44% compared to head [(`63e5b35`)](https://app.codecov.io/gh/sashabaranov/go-openai/pull/665?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     #665   +/-   ##
=======================================
  Coverage   98.44%   98.44%           
=======================================
  Files          24       24           
  Lines        1347     1347           
=======================================
  Hits         1326     1326           
  Misses         15       15           
  Partials        6        6           
```



</details>

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


Patch:
diff --git a/client.go b/client.go
index 7fdc36c..e7a4d5b 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") {
+		if strings.Contains(suffix, "/models") || strings.Contains(suffix, "/assistants") {
 			return fmt.Sprintf("%s/%s%s?api-version=%s", baseURL, azureAPIPrefix, suffix, c.config.APIVersion)
 		}
 		azureDeploymentName := "UNKNOWN"


Test patch:
diff --git a/assistant_test.go b/assistant_test.go
index 9e1e3f3..48bc6f9 100644
--- a/assistant_test.go
+++ b/assistant_test.go
@@ -202,3 +202,193 @@ When asked a question, write and run Python code to answer the question.`
 	err = client.DeleteAssistantFile(ctx, assistantID, assistantFileID)
 	checks.NoError(t, err, "DeleteAssistantFile error")
 }
+
+func TestAzureAssistant(t *testing.T) {
+	assistantID := "asst_abc123"
+	assistantName := "Ambrogio"
+	assistantDescription := "Ambrogio is a friendly assistant."
+	assistantInstructions := `You are a personal math tutor. 
+When asked a question, write and run Python code to answer the question.`
+	assistantFileID := "file-wB6RM6wHdA49HfS2DJ9fEyrH"
+	limit := 20
+	order := "desc"
+	after := "asst_abc122"
+	before := "asst_abc124"
+
+	client, server, teardown := setupAzureTestServer()
+	defer teardown()
+
+	server.RegisterHandler(
+		"/openai/assistants/"+assistantID+"/files/"+assistantFileID,
+		func(w http.ResponseWriter, r *http.Request) {
+			if r.Method == http.MethodGet {
+				resBytes, _ := json.Marshal(openai.AssistantFile{
+					ID:          assistantFileID,
+					Object:      "assistant.file",
+					CreatedAt:   1234567890,
+					AssistantID: assistantID,
+				})
+				fmt.Fprintln(w, string(resBytes))
+			} else if r.Method == http.MethodDelete {
+				fmt.Fprintln(w, `{
+					id: "file-wB6RM6wHdA49HfS2DJ9fEyrH",
+					object: "assistant.file.deleted",
+					deleted: true
+				  }`)
+			}
+		},
+	)
+
+	server.RegisterHandler(
+		"/openai/assistants/"+assistantID+"/files",
+		func(w http.ResponseWriter, r *http.Request) {
+			if r.Method == http.MethodGet {
+				resBytes, _ := json.Marshal(openai.AssistantFilesList{
+					AssistantFiles: []openai.AssistantFile{
+						{
+							ID:          assistantFileID,
+							Object:      "assistant.file",
+							CreatedAt:   1234567890,
+							AssistantID: assistantID,
+						},
+					},
+				})
+				fmt.Fprintln(w, string(resBytes))
+			} else if r.Method == http.MethodPost {
+				var request openai.AssistantFileRequest
+				err := json.NewDecoder(r.Body).Decode(&request)
+				checks.NoError(t, err, "Decode error")
+
+				resBytes, _ := json.Marshal(openai.AssistantFile{
+					ID:          request.FileID,
+					Object:      "assistant.file",
+					CreatedAt:   1234567890,
+					AssistantID: assistantID,
+				})
+				fmt.Fprintln(w, string(resBytes))
+			}
+		},
+	)
+
+	server.RegisterHandler(
+		"/openai/assistants/"+assistantID,
+		func(w http.ResponseWriter, r *http.Request) {
+			switch r.Method {
+			case http.MethodGet:
+				resBytes, _ := json.Marshal(openai.Assistant{
+					ID:           assistantID,
+					Object:       "assistant",
+					CreatedAt:    1234567890,
+					Name:         &assistantName,
+					Model:        openai.GPT4TurboPreview,
+					Description:  &assistantDescription,
+					Instructions: &assistantInstructions,
+				})
+				fmt.Fprintln(w, string(resBytes))
+			case http.MethodPost:
+				var request openai.AssistantRequest
+				err := json.NewDecoder(r.Body).Decode(&request)
+				checks.NoError(t, err, "Decode error")
+
+				resBytes, _ := json.Marshal(openai.Assistant{
+					ID:           assistantID,
+					Object:       "assistant",
+					CreatedAt:    1234567890,
+					Name:         request.Name,
+					Model:        request.Model,
+					Description:  request.Description,
+					Instructions: request.Instructions,
+					Tools:        request.Tools,
+				})
+				fmt.Fprintln(w, string(resBytes))
+			case http.MethodDelete:
+				fmt.Fprintln(w, `{
+					"id": "asst_abc123",
+					"object": "assistant.deleted",
+					"deleted": true
+				  }`)
+			}
+		},
+	)
+
+	server.RegisterHandler(
+		"/openai/assistants",
+		func(w http.ResponseWriter, r *http.Request) {
+			if r.Method == http.MethodPost {
+				var request openai.AssistantRequest
+				err := json.NewDecoder(r.Body).Decode(&request)
+				checks.NoError(t, err, "Decode error")
+
+				resBytes, _ := json.Marshal(openai.Assistant{
+					ID:           assistantID,
+					Object:       "assistant",
+					CreatedAt:    1234567890,
+					Name:         request.Name,
+					Model:        request.Model,
+					Description:  request.Description,
+					Instructions: request.Instructions,
+					Tools:        request.Tools,
+				})
+				fmt.Fprintln(w, string(resBytes))
+			} else if r.Method == http.MethodGet {
+				resBytes, _ := json.Marshal(openai.AssistantsList{
+					LastID:  &assistantID,
+					FirstID: &assistantID,
+					Assistants: []openai.Assistant{
+						{
+							ID:           assistantID,
+							Object:       "assistant",
+							CreatedAt:    1234567890,
+							Name:         &assistantName,
+							Model:        openai.GPT4TurboPreview,
+							Description:  &assistantDescription,
+							Instructions: &assistantInstructions,
+						},
+					},
+				})
+				fmt.Fprintln(w, string(resBytes))
+			}
+		},
+	)
+
+	ctx := context.Background()
+
+	_, err := client.CreateAssistant(ctx, openai.AssistantRequest{
+		Name:         &assistantName,
+		Description:  &assistantDescription,
+		Model:        openai.GPT4TurboPreview,
+		Instructions: &assistantInstructions,
+	})
+	checks.NoError(t, err, "CreateAssistant error")
+
+	_, err = client.RetrieveAssistant(ctx, assistantID)
+	checks.NoError(t, err, "RetrieveAssistant error")
+
+	_, err = client.ModifyAssistant(ctx, assistantID, openai.AssistantRequest{
+		Name:         &assistantName,
+		Description:  &assistantDescription,
+		Model:        openai.GPT4TurboPreview,
+		Instructions: &assistantInstructions,
+	})
+	checks.NoError(t, err, "ModifyAssistant error")
+
+	_, err = client.DeleteAssistant(ctx, assistantID)
+	checks.NoError(t, err, "DeleteAssistant error")
+
+	_, err = client.ListAssistants(ctx, &limit, &order, &after, &before)
+	checks.NoError(t, err, "ListAssistants error")
+
+	_, err = client.CreateAssistantFile(ctx, assistantID, openai.AssistantFileRequest{
+		FileID: assistantFileID,
+	})
+	checks.NoError(t, err, "CreateAssistantFile error")
+
+	_, err = client.ListAssistantFiles(ctx, assistantID, &limit, &order, &after, &before)
+	checks.NoError(t, err, "ListAssistantFiles error")
+
+	_, err = client.RetrieveAssistantFile(ctx, assistantID, assistantFileID)
+	checks.NoError(t, err, "RetrieveAssistantFile error")
+
+	err = client.DeleteAssistantFile(ctx, assistantID, assistantFileID)
+	checks.NoError(t, err, "DeleteAssistantFile error")
+}