Skip to content
This repository was archived by the owner on Sep 30, 2024. It is now read-only.

Commit 7485a9c

Browse files
authored
fix/LLM API: return helpful error message on missing model (#64474)
Previously, the `/.api/llm/chat/completions` API returned an error message saying a model was unsupported if it used the wrong syntax. Now, we only validate that the request is using the new modelref syntax and otherwise delegate to the underlying `/.api/completions/stream` endpoint to return the error message. This error message at least mentions the default model, which I've found very helpful when debugging why things are not working as expected. ``` unsupported chat model "openai::2024-02-01::gpt-4o" (default "anthropic::unknown::claude-3-sonnet-20240229" ``` <!-- PR description tips: https://www.notion.so/sourcegraph/Write-a-good-pull-request-description-610a7fd3e613496eb76f450db5a49b6e --> ## Test plan See updated unit test. Also manually tested locally ``` ❯ curl 'https://sourcegraph.test:3443/.api/llm/chat/completions' \ -H 'Content-Type: application/json' \ -H "Authorization: token $HURL_token" \ --data-raw '{ "maxTokensToSample": 4000, "messages": [ { "role": "user", "content": "Respond with \"no\" and nothing else." } ], "model": "openai::2024-02-01::gpt-4o", "temperature": 0, "topK": -1, "topP": -1, "stream": false }' failed to forward request to apiHandler: handler returned unexpected status code: got 400 want 200, response body: unsupported chat model "openai::2024-02-01::gpt-4o" (default "anthropic::unknown::claude-3-sonnet-20240229") ``` <!-- REQUIRED; info at https://docs-legacy.sourcegraph.com/dev/background-information/testing_principles --> ## Changelog <!-- OPTIONAL; info at https://www.notion.so/sourcegraph/Writing-a-changelog-entry-dd997f411d524caabf0d8d38a24a878c -->
1 parent 68e0fb0 commit 7485a9c

File tree

5 files changed

+53
-34
lines changed

5 files changed

+53
-34
lines changed

cmd/frontend/internal/llmapi/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ go_library(
1313
visibility = ["//cmd/frontend:__subpackages__"],
1414
deps = [
1515
"//internal/completions/types",
16+
"//internal/modelconfig",
1617
"//internal/modelconfig/types",
1718
"//internal/openapi/goapi",
1819
"//lib/errors",

cmd/frontend/internal/llmapi/golly-recordings/TestChatCompletionsHandler.recording.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,29 @@ recordings:
160160
headers:
161161
- key: Content-Type
162162
value: text/plain; charset=utf-8
163+
- hash: 94b132c1d5c4a93c9d8f71a01b7b01b2118f16e67747fb028b71a46887f78c4a
164+
request:
165+
recording_date: "2024-08-14T23:16:24+02:00"
166+
url: https://sourcegraph.com/.api/completions/stream?api-version=1&client-name=openai-rest-api&client-version=6.0.0
167+
method: POST
168+
headers:
169+
- key: Content-Type
170+
value: application/json
171+
- key: Authorization
172+
value: token REDACTED_51c20f884ac371e6e12fe635336ac83af942ddc816c96d99dd3e49b3f8dfeb26
173+
body: |
174+
Fast: false
175+
logprobs: null
176+
maxTokensToSample: 16
177+
messages:
178+
- speaker: human
179+
text: Hello
180+
model: anthropic::unknown::claude-gpt
181+
stream: false
182+
response:
183+
status_code: 400
184+
body: |
185+
the requested chat model is not available ("anthropic::unknown::claude-gpt", onProTier=true)
186+
headers:
187+
- key: Content-Type
188+
value: text/plain; charset=utf-8

cmd/frontend/internal/llmapi/handler_chat_completions.go

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/sourcegraph/sourcegraph/lib/errors"
1717

1818
completions "github.com/sourcegraph/sourcegraph/internal/completions/types"
19+
"github.com/sourcegraph/sourcegraph/internal/modelconfig"
1920
types "github.com/sourcegraph/sourcegraph/internal/modelconfig/types"
2021
"github.com/sourcegraph/sourcegraph/internal/openapi/goapi"
2122
)
@@ -30,8 +31,6 @@ type chatCompletionsHandler struct {
3031
// would have an in-house service we can use instead of going via HTTP but using HTTP
3132
// simplifies a lof of things (including testing).
3233
apiHandler http.Handler
33-
34-
GetModelConfig GetModelConfigurationFunc
3534
}
3635

3736
var _ http.Handler = (*chatCompletionsHandler)(nil)
@@ -46,12 +45,6 @@ func (h *chatCompletionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
4645

4746
decoder := json.NewDecoder(io.NopCloser(bytes.NewBuffer(body)))
4847

49-
currentModelConfig, err := h.GetModelConfig()
50-
if err != nil {
51-
http.Error(w, fmt.Sprintf("modelConfigSvc.Get: %v", err), http.StatusInternalServerError)
52-
return
53-
}
54-
5548
if err := decoder.Decode(&chatCompletionRequest); err != nil {
5649
http.Error(w, fmt.Sprintf("decoder.Decode: %v", err), http.StatusInternalServerError)
5750
return
@@ -62,7 +55,7 @@ func (h *chatCompletionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
6255
return
6356
}
6457

65-
if errorMsg := validateRequestedModel(chatCompletionRequest, currentModelConfig); errorMsg != "" {
58+
if errorMsg := validateRequestedModel(chatCompletionRequest); errorMsg != "" {
6659
http.Error(w, errorMsg, http.StatusBadRequest)
6760
return
6861
}
@@ -79,27 +72,18 @@ func (h *chatCompletionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
7972
serveJSON(w, r, h.logger, chatCompletionResponse)
8073
}
8174

82-
// validateRequestedModel checks that are only use the modelref syntax
83-
// (${ProviderID}::${APIVersionID}::${ModelID}). If the user passes the old
84-
// syntax `${ProviderID}/${ModelID}`, then we try to return a helpful error
85-
// message suggesting to use the new modelref syntax.
86-
func validateRequestedModel(chatCompletionRequest goapi.CreateChatCompletionRequest, modelConfig *types.ModelConfiguration) string {
87-
closestModelRef := ""
88-
for _, model := range modelConfig.Models {
89-
if string(model.ModelRef) == chatCompletionRequest.Model {
90-
return ""
91-
}
92-
if model.DisplayName == chatCompletionRequest.Model || model.ModelName == chatCompletionRequest.Model {
93-
closestModelRef = string(model.ModelRef)
94-
} else if chatCompletionRequest.Model == fmt.Sprintf("%s/%s", model.ModelRef.ProviderID(), model.ModelRef.ModelID()) {
95-
closestModelRef = string(model.ModelRef)
96-
}
75+
// Require client to use the new modelref syntax
76+
// (${ProviderID}::${APIVersionID}::${ModelID}). We don't validate that the
77+
// actual model exists because the underlying `/.api/completions/stream`
78+
// endpoint already does this validation and reports helpful error messages. We
79+
// just want to reject requests for models using the old non-modelref syntax
80+
// (example: anthropic/claude-3-haiku).
81+
func validateRequestedModel(chatCompletionRequest goapi.CreateChatCompletionRequest) string {
82+
maybeMRef := types.ModelRef(chatCompletionRequest.Model)
83+
if err := modelconfig.ValidateModelRef(maybeMRef); err != nil {
84+
return fmt.Sprintf("requested model '%s' failed validation: %s. Expected format '${ProviderID}::${APIVersionID}::${ModelID}'. To fix this problem, send a request to `GET /.api/llm/models` to see the list of supported models.", chatCompletionRequest.Model, err)
9785
}
98-
didYouMean := ""
99-
if closestModelRef != "" {
100-
didYouMean = fmt.Sprintf(" (similar to %s)", closestModelRef)
101-
}
102-
return fmt.Sprintf("model %s is not supported%s", chatCompletionRequest.Model, didYouMean)
86+
return ""
10387
}
10488

10589
func validateChatCompletionRequest(chatCompletionRequest goapi.CreateChatCompletionRequest) string {

cmd/frontend/internal/llmapi/handler_chat_completions_test.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,17 @@ func TestChatCompletionsHandler(t *testing.T) {
5757
// For now, we reject requests when the model is not using the new ModelRef format.
5858
assert.Equal(t, http.StatusBadRequest, rr.Code)
5959

60-
// Assert that we give a helpful error message nudging the user to use modelref instead of the old syntax.
61-
assert.Equal(t, "model anthropic/claude-3-haiku-20240307 is not supported (similar to anthropic::unknown::claude-3-haiku-20240307)\n", rr.Body.String())
60+
assert.Equal(t, "requested model 'anthropic/claude-3-haiku-20240307' failed validation: modelRef syntax error. Expected format '${ProviderID}::${APIVersionID}::${ModelID}'. To fix this problem, send a request to `GET /.api/llm/models` to see the list of supported models.\n", rr.Body.String())
61+
})
62+
63+
t.Run("/.api/llm/chat/completions (400 model is invalid model)", func(t *testing.T) {
64+
rr := c.chatCompletions(t, `{
65+
"model": "anthropic::unknown::claude-gpt",
66+
"messages": [{"role": "user", "content": "Hello"}]
67+
}`)
68+
// For now, we reject requests when the model is not using the new ModelRef format.
69+
assert.Equal(t, http.StatusInternalServerError, rr.Code) // Should be 400 Bad Request, see CODY-3318
70+
assert.Equal(t, "failed to forward request to apiHandler: handler returned unexpected status code: got 400 want 200, response body: the requested chat model is not available (\"anthropic::unknown::claude-gpt\", onProTier=true)\n", rr.Body.String())
6271
})
6372

6473
t.Run("/.api/llm/chat/completions (200 OK)", func(t *testing.T) {

cmd/frontend/internal/llmapi/httpapi.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ func RegisterHandlers(m *mux.Router, apiHandler http.Handler, getModelConfigFunc
1818
logger := sglog.Scoped("llmapi")
1919

2020
m.Path("/chat/completions").Methods("POST").Handler(&chatCompletionsHandler{
21-
logger: logger,
22-
apiHandler: apiHandler,
23-
GetModelConfig: getModelConfigFunc,
21+
logger: logger,
22+
apiHandler: apiHandler,
2423
})
2524
m.Path("/models").Methods("GET").Handler(&modelsHandler{
2625
logger: logger,

0 commit comments

Comments
 (0)