From ba2ee05fbded59853faef2632285df2b7b6a58b8 Mon Sep 17 00:00:00 2001 From: slzcdhd Date: Fri, 8 May 2026 22:27:58 +0800 Subject: [PATCH] fix: guard against nil response in Generate to prevent panic (#228) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some OpenAI-compatible API endpoints (e.g. DeepSeek) may return a nil response with no error from the underlying SDK under edge conditions (empty response body, connection issues during response parsing, etc.). When this happens, accessing response.Choices (Chat Completions), response.Error (Responses API), or response.Content (Anthropic) causes a nil pointer dereference panic that crashes the process. Add nil response checks in Generate methods for: - providers/openai: languageModel.Generate (Chat Completions API) - providers/openai: responsesLanguageModel.Generate (Responses API) - providers/anthropic: languageModel.Generate Each returns a descriptive fantasy.Error instead of panicking. Co-authored-by: liaoyijun <“liaoyijun@wps.cn”> --- providers/anthropic/anthropic.go | 3 +++ providers/openai/language_model.go | 3 +++ providers/openai/openai_test.go | 25 ++++++++++++++++++++ providers/openai/responses_language_model.go | 3 +++ 4 files changed, 34 insertions(+) diff --git a/providers/anthropic/anthropic.go b/providers/anthropic/anthropic.go index 2760b0d71b244b86f46da5d6fce60c094b8a1128..27dd22698532749c9d35ca86e1a9b507dd1eba1f 100644 --- a/providers/anthropic/anthropic.go +++ b/providers/anthropic/anthropic.go @@ -1171,6 +1171,9 @@ func (a languageModel) Generate(ctx context.Context, call fantasy.Call) (*fantas if err != nil { return nil, toProviderErr(err) } + if response == nil { + return nil, &fantasy.Error{Title: "no response", Message: "provider returned nil response"} + } var content []fantasy.Content for _, block := range response.Content { diff --git a/providers/openai/language_model.go b/providers/openai/language_model.go index 4515dabbd9c89e1ab19279b470acd71d5812209d..2907008de13b0ff05150a18aadf01b54d99213ed 100644 --- a/providers/openai/language_model.go +++ b/providers/openai/language_model.go @@ -251,6 +251,9 @@ func (o languageModel) Generate(ctx context.Context, call fantasy.Call) (*fantas if err != nil { return nil, toProviderErr(err) } + if response == nil { + return nil, &fantasy.Error{Title: "no response", Message: "provider returned nil response"} + } if len(response.Choices) == 0 { return nil, &fantasy.Error{Title: "no response", Message: "no response generated"} diff --git a/providers/openai/openai_test.go b/providers/openai/openai_test.go index 254ae635412917fe6c5f9b46db4ec0d9eb6ccabd..2230deae43e26c63435aa0f1235933a661e4b5db 100644 --- a/providers/openai/openai_test.go +++ b/providers/openai/openai_test.go @@ -2010,6 +2010,31 @@ func TestDoGenerate(t *testing.T) { require.Equal(t, "ServiceTier", result.Warnings[0].Setting) require.Contains(t, result.Warnings[0].Details, "priority processing is only available") }) + + t.Run("should return error instead of panic on empty response body", func(t *testing.T) { + t.Parallel() + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + // Write empty body — some OpenAI-compatible endpoints may do this + // under edge conditions, causing the SDK to return (nil, nil). + })) + defer server.Close() + + provider, err := New( + WithAPIKey("test-api-key"), + WithBaseURL(server.URL), + ) + require.NoError(t, err) + model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo") + + require.NotPanics(t, func() { + _, _ = model.Generate(context.Background(), fantasy.Call{ + Prompt: testPrompt, + }) + }) + }) } type streamingMockServer struct { diff --git a/providers/openai/responses_language_model.go b/providers/openai/responses_language_model.go index 06da0aa6e3d869fffe618bf0cd61c30ea5fa35e8..35c88ed7e55a3df649e25e41ebefcbf67c38aafa 100644 --- a/providers/openai/responses_language_model.go +++ b/providers/openai/responses_language_model.go @@ -789,6 +789,9 @@ func (o responsesLanguageModel) Generate(ctx context.Context, call fantasy.Call) if err != nil { return nil, toProviderErr(err) } + if response == nil { + return nil, &fantasy.Error{Title: "no response", Message: "provider returned nil response"} + } if response.Error.Message != "" { return nil, &fantasy.Error{