refactor: return `error` on `{provider}.New()`

Andrey Nering created

Change summary

providers/anthropic/anthropic.go       |   4 
providers/azure/azure.go               |   2 
providers/bedrock/bedrock.go           |   2 
providers/google/google.go             |  12 +
providers/openai/openai.go             |   4 
providers/openai/openai_test.go        | 169 +++++++++++++++++----------
providers/openaicompat/openaicompat.go |   2 
providers/openrouter/openrouter.go     |   2 
providertests/anthropic_test.go        |   5 
providertests/azure_test.go            |  15 +
providertests/bedrock_test.go          |  20 ++
providertests/google_test.go           |  10 +
providertests/image_upload_test.go     |  15 +
providertests/openai_responses_test.go |   5 
providertests/openai_test.go           |   5 
providertests/openaicompat_test.go     |  30 +++
providertests/openrouter_test.go       |   5 
17 files changed, 210 insertions(+), 97 deletions(-)

Detailed changes

providers/anthropic/anthropic.go 🔗

@@ -50,7 +50,7 @@ type provider struct {
 type Option = func(*options)
 
 // New creates a new Anthropic provider with the given options.
-func New(opts ...Option) fantasy.Provider {
+func New(opts ...Option) (fantasy.Provider, error) {
 	providerOptions := options{
 		headers: map[string]string{},
 	}
@@ -60,7 +60,7 @@ func New(opts ...Option) fantasy.Provider {
 
 	providerOptions.baseURL = cmp.Or(providerOptions.baseURL, DefaultURL)
 	providerOptions.name = cmp.Or(providerOptions.name, Name)
-	return &provider{options: providerOptions}
+	return &provider{options: providerOptions}, nil
 }
 
 // WithBaseURL sets the base URL for the Anthropic provider.

providers/azure/azure.go 🔗

@@ -27,7 +27,7 @@ const (
 type Option = func(*options)
 
 // New creates a new Azure provider with the given options.
-func New(opts ...Option) fantasy.Provider {
+func New(opts ...Option) (fantasy.Provider, error) {
 	o := options{
 		apiVersion: defaultAPIVersion,
 	}

providers/bedrock/bedrock.go 🔗

@@ -21,7 +21,7 @@ const (
 type Option = func(*options)
 
 // New creates a new Bedrock provider with the given options.
-func New(opts ...Option) fantasy.Provider {
+func New(opts ...Option) (fantasy.Provider, error) {
 	var o options
 	for _, opt := range opts {
 		opt(&o)

providers/google/google.go 🔗

@@ -41,7 +41,7 @@ type options struct {
 type Option = func(*options)
 
 // New creates a new Google provider with the given options.
-func New(opts ...Option) fantasy.Provider {
+func New(opts ...Option) (fantasy.Provider, error) {
 	options := options{
 		headers: map[string]string{},
 	}
@@ -53,7 +53,7 @@ func New(opts ...Option) fantasy.Provider {
 
 	return &provider{
 		options: options,
-	}
+	}, nil
 }
 
 // WithBaseURL sets the base URL for the Google provider.
@@ -128,11 +128,15 @@ type languageModel struct {
 // LanguageModel implements fantasy.Provider.
 func (a *provider) LanguageModel(modelID string) (fantasy.LanguageModel, error) {
 	if strings.Contains(modelID, "anthropic") || strings.Contains(modelID, "claude") {
-		return anthropic.New(
+		p, err := anthropic.New(
 			anthropic.WithVertex(a.options.project, a.options.location),
 			anthropic.WithHTTPClient(a.options.client),
 			anthropic.WithSkipAuth(a.options.skipAuth),
-		).LanguageModel(modelID)
+		)
+		if err != nil {
+			return nil, err
+		}
+		return p.LanguageModel(modelID)
 	}
 
 	cc := &genai.ClientConfig{

providers/openai/openai.go 🔗

@@ -38,7 +38,7 @@ type options struct {
 type Option = func(*options)
 
 // New creates a new OpenAI provider with the given options.
-func New(opts ...Option) fantasy.Provider {
+func New(opts ...Option) (fantasy.Provider, error) {
 	providerOptions := options{
 		headers:              map[string]string{},
 		languageModelOptions: make([]LanguageModelOption, 0),
@@ -57,7 +57,7 @@ func New(opts ...Option) fantasy.Provider {
 		providerOptions.headers["OpenAi-Project"] = providerOptions.project
 	}
 
-	return &provider{options: providerOptions}
+	return &provider{options: providerOptions}, nil
 }
 
 // WithBaseURL sets the base URL for the OpenAI provider.

providers/openai/openai_test.go 🔗

@@ -809,10 +809,11 @@ func TestDoGenerate(t *testing.T) {
 			"content": "Hello, World!",
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
 		result, err := model.Generate(context.Background(), fantasy.Call{
@@ -841,10 +842,11 @@ func TestDoGenerate(t *testing.T) {
 			},
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
 		result, err := model.Generate(context.Background(), fantasy.Call{
@@ -865,13 +867,14 @@ func TestDoGenerate(t *testing.T) {
 
 		server.prepareJSONResponse(map[string]any{})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
-		_, err := model.Generate(context.Background(), fantasy.Call{
+		_, err = model.Generate(context.Background(), fantasy.Call{
 			Prompt: testPrompt,
 		})
 
@@ -905,10 +908,11 @@ func TestDoGenerate(t *testing.T) {
 			},
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
 		result, err := model.Generate(context.Background(), fantasy.Call{
@@ -931,10 +935,11 @@ func TestDoGenerate(t *testing.T) {
 			"logprobs": testLogprobs,
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
 		result, err := model.Generate(context.Background(), fantasy.Call{
@@ -965,10 +970,11 @@ func TestDoGenerate(t *testing.T) {
 			"finish_reason": "stop",
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
 		result, err := model.Generate(context.Background(), fantasy.Call{
@@ -989,10 +995,11 @@ func TestDoGenerate(t *testing.T) {
 			"finish_reason": "eos",
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
 		result, err := model.Generate(context.Background(), fantasy.Call{
@@ -1013,13 +1020,14 @@ func TestDoGenerate(t *testing.T) {
 			"content": "",
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
-		_, err := model.Generate(context.Background(), fantasy.Call{
+		_, err = model.Generate(context.Background(), fantasy.Call{
 			Prompt: testPrompt,
 		})
 
@@ -1045,13 +1053,14 @@ func TestDoGenerate(t *testing.T) {
 
 		server.prepareJSONResponse(map[string]any{})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
-		_, err := model.Generate(context.Background(), fantasy.Call{
+		_, err = model.Generate(context.Background(), fantasy.Call{
 			Prompt: testPrompt,
 			ProviderOptions: NewProviderOptions(&ProviderOptions{
 				LogitBias: map[string]int64{
@@ -1087,13 +1096,14 @@ func TestDoGenerate(t *testing.T) {
 			"content": "",
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("o1-mini")
 
-		_, err := model.Generate(context.Background(), fantasy.Call{
+		_, err = model.Generate(context.Background(), fantasy.Call{
 			Prompt: testPrompt,
 			ProviderOptions: NewProviderOptions(
 				&ProviderOptions{
@@ -1127,13 +1137,14 @@ func TestDoGenerate(t *testing.T) {
 			"content": "",
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-4o")
 
-		_, err := model.Generate(context.Background(), fantasy.Call{
+		_, err = model.Generate(context.Background(), fantasy.Call{
 			Prompt: testPrompt,
 			ProviderOptions: NewProviderOptions(&ProviderOptions{
 				TextVerbosity: fantasy.Opt("low"),
@@ -1165,13 +1176,14 @@ func TestDoGenerate(t *testing.T) {
 			"content": "",
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
-		_, err := model.Generate(context.Background(), fantasy.Call{
+		_, err = model.Generate(context.Background(), fantasy.Call{
 			Prompt: testPrompt,
 			Tools: []fantasy.Tool{
 				fantasy.FunctionTool{
@@ -1237,10 +1249,11 @@ func TestDoGenerate(t *testing.T) {
 			},
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
 		result, err := model.Generate(context.Background(), fantasy.Call{
@@ -1295,10 +1308,11 @@ func TestDoGenerate(t *testing.T) {
 			},
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
 		result, err := model.Generate(context.Background(), fantasy.Call{
@@ -1337,10 +1351,11 @@ func TestDoGenerate(t *testing.T) {
 			},
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-4o-mini")
 
 		result, err := model.Generate(context.Background(), fantasy.Call{
@@ -1372,10 +1387,11 @@ func TestDoGenerate(t *testing.T) {
 			},
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-4o-mini")
 
 		result, err := model.Generate(context.Background(), fantasy.Call{
@@ -1400,10 +1416,11 @@ func TestDoGenerate(t *testing.T) {
 
 		server.prepareJSONResponse(map[string]any{})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("o1-preview")
 
 		result, err := model.Generate(context.Background(), fantasy.Call{
@@ -1448,13 +1465,14 @@ func TestDoGenerate(t *testing.T) {
 
 		server.prepareJSONResponse(map[string]any{})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("o1-preview")
 
-		_, err := model.Generate(context.Background(), fantasy.Call{
+		_, err = model.Generate(context.Background(), fantasy.Call{
 			Prompt:          testPrompt,
 			MaxOutputTokens: &[]int64{1000}[0],
 		})
@@ -1492,10 +1510,11 @@ func TestDoGenerate(t *testing.T) {
 			},
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("o1-preview")
 
 		result, err := model.Generate(context.Background(), fantasy.Call{
@@ -1519,13 +1538,14 @@ func TestDoGenerate(t *testing.T) {
 			"model": "o1-preview",
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("o1-preview")
 
-		_, err := model.Generate(context.Background(), fantasy.Call{
+		_, err = model.Generate(context.Background(), fantasy.Call{
 			Prompt: testPrompt,
 			ProviderOptions: NewProviderOptions(&ProviderOptions{
 				MaxCompletionTokens: fantasy.Opt(int64(255)),
@@ -1557,13 +1577,14 @@ func TestDoGenerate(t *testing.T) {
 			"content": "",
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
-		_, err := model.Generate(context.Background(), fantasy.Call{
+		_, err = model.Generate(context.Background(), fantasy.Call{
 			Prompt: testPrompt,
 			ProviderOptions: NewProviderOptions(&ProviderOptions{
 				Prediction: map[string]any{
@@ -1601,13 +1622,14 @@ func TestDoGenerate(t *testing.T) {
 			"content": "",
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
-		_, err := model.Generate(context.Background(), fantasy.Call{
+		_, err = model.Generate(context.Background(), fantasy.Call{
 			Prompt: testPrompt,
 			ProviderOptions: NewProviderOptions(&ProviderOptions{
 				Store: fantasy.Opt(true),
@@ -1639,13 +1661,14 @@ func TestDoGenerate(t *testing.T) {
 			"content": "",
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
-		_, err := model.Generate(context.Background(), fantasy.Call{
+		_, err = model.Generate(context.Background(), fantasy.Call{
 			Prompt: testPrompt,
 			ProviderOptions: NewProviderOptions(&ProviderOptions{
 				Metadata: map[string]any{
@@ -1681,13 +1704,14 @@ func TestDoGenerate(t *testing.T) {
 			"content": "",
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
-		_, err := model.Generate(context.Background(), fantasy.Call{
+		_, err = model.Generate(context.Background(), fantasy.Call{
 			Prompt: testPrompt,
 			ProviderOptions: NewProviderOptions(&ProviderOptions{
 				PromptCacheKey: fantasy.Opt("test-cache-key-123"),
@@ -1719,13 +1743,14 @@ func TestDoGenerate(t *testing.T) {
 			"content": "",
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
-		_, err := model.Generate(context.Background(), fantasy.Call{
+		_, err = model.Generate(context.Background(), fantasy.Call{
 			Prompt: testPrompt,
 			ProviderOptions: NewProviderOptions(&ProviderOptions{
 				SafetyIdentifier: fantasy.Opt("test-safety-identifier-123"),
@@ -1755,10 +1780,11 @@ func TestDoGenerate(t *testing.T) {
 
 		server.prepareJSONResponse(map[string]any{})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-4o-search-preview")
 
 		result, err := model.Generate(context.Background(), fantasy.Call{
@@ -1789,13 +1815,14 @@ func TestDoGenerate(t *testing.T) {
 			"content": "",
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("o3-mini")
 
-		_, err := model.Generate(context.Background(), fantasy.Call{
+		_, err = model.Generate(context.Background(), fantasy.Call{
 			Prompt: testPrompt,
 			ProviderOptions: NewProviderOptions(&ProviderOptions{
 				ServiceTier: fantasy.Opt("flex"),
@@ -1825,10 +1852,11 @@ func TestDoGenerate(t *testing.T) {
 
 		server.prepareJSONResponse(map[string]any{})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-4o-mini")
 
 		result, err := model.Generate(context.Background(), fantasy.Call{
@@ -1858,13 +1886,14 @@ func TestDoGenerate(t *testing.T) {
 
 		server.prepareJSONResponse(map[string]any{})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-4o-mini")
 
-		_, err := model.Generate(context.Background(), fantasy.Call{
+		_, err = model.Generate(context.Background(), fantasy.Call{
 			Prompt: testPrompt,
 			ProviderOptions: NewProviderOptions(&ProviderOptions{
 				ServiceTier: fantasy.Opt("priority"),
@@ -1894,10 +1923,11 @@ func TestDoGenerate(t *testing.T) {
 
 		server.prepareJSONResponse(map[string]any{})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
 		result, err := model.Generate(context.Background(), fantasy.Call{
@@ -2201,10 +2231,11 @@ func TestDoStream(t *testing.T) {
 			"logprobs": testLogprobs,
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
 		stream, err := model.Stream(context.Background(), fantasy.Call{
@@ -2257,10 +2288,11 @@ func TestDoStream(t *testing.T) {
 
 		server.prepareToolStreamResponse()
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
 		stream, err := model.Stream(context.Background(), fantasy.Call{
@@ -2343,10 +2375,11 @@ func TestDoStream(t *testing.T) {
 			},
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
 		stream, err := model.Stream(context.Background(), fantasy.Call{
@@ -2382,10 +2415,11 @@ func TestDoStream(t *testing.T) {
 
 		server.prepareErrorStreamResponse()
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
 		stream, err := model.Stream(context.Background(), fantasy.Call{
@@ -2423,13 +2457,14 @@ func TestDoStream(t *testing.T) {
 			"content": []string{},
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
-		_, err := model.Stream(context.Background(), fantasy.Call{
+		_, err = model.Stream(context.Background(), fantasy.Call{
 			Prompt: testPrompt,
 		})
 
@@ -2471,10 +2506,11 @@ func TestDoStream(t *testing.T) {
 			},
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
 		stream, err := model.Stream(context.Background(), fantasy.Call{
@@ -2521,10 +2557,11 @@ func TestDoStream(t *testing.T) {
 			},
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
 		stream, err := model.Stream(context.Background(), fantasy.Call{
@@ -2564,13 +2601,14 @@ func TestDoStream(t *testing.T) {
 			"content": []string{},
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
-		_, err := model.Stream(context.Background(), fantasy.Call{
+		_, err = model.Stream(context.Background(), fantasy.Call{
 			Prompt: testPrompt,
 			ProviderOptions: NewProviderOptions(&ProviderOptions{
 				Store: fantasy.Opt(true),
@@ -2606,13 +2644,14 @@ func TestDoStream(t *testing.T) {
 			"content": []string{},
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-3.5-turbo")
 
-		_, err := model.Stream(context.Background(), fantasy.Call{
+		_, err = model.Stream(context.Background(), fantasy.Call{
 			Prompt: testPrompt,
 			ProviderOptions: NewProviderOptions(&ProviderOptions{
 				Metadata: map[string]any{
@@ -2652,13 +2691,14 @@ func TestDoStream(t *testing.T) {
 			"content": []string{},
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("o3-mini")
 
-		_, err := model.Stream(context.Background(), fantasy.Call{
+		_, err = model.Stream(context.Background(), fantasy.Call{
 			Prompt: testPrompt,
 			ProviderOptions: NewProviderOptions(&ProviderOptions{
 				ServiceTier: fantasy.Opt("flex"),
@@ -2694,13 +2734,14 @@ func TestDoStream(t *testing.T) {
 			"content": []string{},
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("gpt-4o-mini")
 
-		_, err := model.Stream(context.Background(), fantasy.Call{
+		_, err = model.Stream(context.Background(), fantasy.Call{
 			Prompt: testPrompt,
 			ProviderOptions: NewProviderOptions(&ProviderOptions{
 				ServiceTier: fantasy.Opt("priority"),
@@ -2737,10 +2778,11 @@ func TestDoStream(t *testing.T) {
 			"model":   "o1-preview",
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("o1-preview")
 
 		stream, err := model.Stream(context.Background(), fantasy.Call{
@@ -2783,10 +2825,11 @@ func TestDoStream(t *testing.T) {
 			},
 		})
 
-		provider := New(
+		provider, err := New(
 			WithAPIKey("test-api-key"),
 			WithBaseURL(server.server.URL),
 		)
+		require.NoError(t, err)
 		model, _ := provider.LanguageModel("o1-preview")
 
 		stream, err := model.Stream(context.Background(), fantasy.Call{

providers/openaicompat/openaicompat.go 🔗

@@ -22,7 +22,7 @@ const (
 type Option = func(*options)
 
 // New creates a new OpenAI-compatible provider with the given options.
-func New(opts ...Option) fantasy.Provider {
+func New(opts ...Option) (fantasy.Provider, error) {
 	providerOptions := options{
 		openaiOptions: []openai.Option{
 			openai.WithName(Name),

providers/openrouter/openrouter.go 🔗

@@ -25,7 +25,7 @@ const (
 type Option = func(*options)
 
 // New creates a new OpenRouter provider with the given options.
-func New(opts ...Option) fantasy.Provider {
+func New(opts ...Option) (fantasy.Provider, error) {
 	providerOptions := options{
 		openaiOptions: []openai.Option{
 			openai.WithName(Name),

providertests/anthropic_test.go 🔗

@@ -137,10 +137,13 @@ func testAnthropicThinking(t *testing.T, result *fantasy.AgentResult) {
 
 func anthropicBuilder(model string) builderFunc {
 	return func(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-		provider := anthropic.New(
+		provider, err := anthropic.New(
 			anthropic.WithAPIKey(os.Getenv("FANTASY_ANTHROPIC_API_KEY")),
 			anthropic.WithHTTPClient(&http.Client{Transport: r}),
 		)
+		if err != nil {
+			return nil, err
+		}
 		return provider.LanguageModel(model)
 	}
 }

providertests/azure_test.go 🔗

@@ -40,28 +40,37 @@ func testAzureThinking(t *testing.T, result *fantasy.AgentResult) {
 }
 
 func builderAzureO4Mini(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-	provider := azure.New(
+	provider, err := azure.New(
 		azure.WithBaseURL(cmp.Or(os.Getenv("FANTASY_AZURE_BASE_URL"), defaultBaseURL)),
 		azure.WithAPIKey(cmp.Or(os.Getenv("FANTASY_AZURE_API_KEY"), "(missing)")),
 		azure.WithHTTPClient(&http.Client{Transport: r}),
 	)
+	if err != nil {
+		return nil, err
+	}
 	return provider.LanguageModel("o4-mini")
 }
 
 func builderAzureGpt5Mini(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-	provider := azure.New(
+	provider, err := azure.New(
 		azure.WithBaseURL(cmp.Or(os.Getenv("FANTASY_AZURE_BASE_URL"), defaultBaseURL)),
 		azure.WithAPIKey(cmp.Or(os.Getenv("FANTASY_AZURE_API_KEY"), "(missing)")),
 		azure.WithHTTPClient(&http.Client{Transport: r}),
 	)
+	if err != nil {
+		return nil, err
+	}
 	return provider.LanguageModel("gpt-5-mini")
 }
 
 func builderAzureGrok3Mini(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-	provider := azure.New(
+	provider, err := azure.New(
 		azure.WithBaseURL(cmp.Or(os.Getenv("FANTASY_AZURE_BASE_URL"), defaultBaseURL)),
 		azure.WithAPIKey(cmp.Or(os.Getenv("FANTASY_AZURE_API_KEY"), "(missing)")),
 		azure.WithHTTPClient(&http.Client{Transport: r}),
 	)
+	if err != nil {
+		return nil, err
+	}
 	return provider.LanguageModel("grok-3-mini")
 }

providertests/bedrock_test.go 🔗

@@ -23,34 +23,46 @@ func TestBedrockBasicAuth(t *testing.T) {
 }
 
 func builderBedrockClaude3Sonnet(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-	provider := bedrock.New(
+	provider, err := bedrock.New(
 		bedrock.WithHTTPClient(&http.Client{Transport: r}),
 		bedrock.WithSkipAuth(!r.IsRecording()),
 	)
+	if err != nil {
+		return nil, err
+	}
 	return provider.LanguageModel("us.anthropic.claude-3-sonnet-20240229-v1:0")
 }
 
 func builderBedrockClaude3Opus(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-	provider := bedrock.New(
+	provider, err := bedrock.New(
 		bedrock.WithHTTPClient(&http.Client{Transport: r}),
 		bedrock.WithSkipAuth(!r.IsRecording()),
 	)
+	if err != nil {
+		return nil, err
+	}
 	return provider.LanguageModel("us.anthropic.claude-3-opus-20240229-v1:0")
 }
 
 func builderBedrockClaude3Haiku(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-	provider := bedrock.New(
+	provider, err := bedrock.New(
 		bedrock.WithHTTPClient(&http.Client{Transport: r}),
 		bedrock.WithSkipAuth(!r.IsRecording()),
 	)
+	if err != nil {
+		return nil, err
+	}
 	return provider.LanguageModel("us.anthropic.claude-3-haiku-20240307-v1:0")
 }
 
 func buildersBedrockBasicAuth(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-	provider := bedrock.New(
+	provider, err := bedrock.New(
 		bedrock.WithHTTPClient(&http.Client{Transport: r}),
 		bedrock.WithAPIKey(os.Getenv("FANTASY_BEDROCK_API_KEY")),
 		bedrock.WithSkipAuth(true),
 	)
+	if err != nil {
+		return nil, err
+	}
 	return provider.LanguageModel("us.anthropic.claude-3-sonnet-20240229-v1:0")
 }

providertests/google_test.go 🔗

@@ -71,21 +71,27 @@ func testGoogleThinking(t *testing.T, result *fantasy.AgentResult) {
 
 func geminiBuilder(model string) builderFunc {
 	return func(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-		provider := google.New(
+		provider, err := google.New(
 			google.WithGeminiAPIKey(cmp.Or(os.Getenv("FANTASY_GEMINI_API_KEY"), "(missing)")),
 			google.WithHTTPClient(&http.Client{Transport: r}),
 		)
+		if err != nil {
+			return nil, err
+		}
 		return provider.LanguageModel(model)
 	}
 }
 
 func vertexBuilder(model string) builderFunc {
 	return func(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-		provider := google.New(
+		provider, err := google.New(
 			google.WithVertex(os.Getenv("FANTASY_VERTEX_PROJECT"), os.Getenv("FANTASY_VERTEX_LOCATION")),
 			google.WithHTTPClient(&http.Client{Transport: r}),
 			google.WithSkipAuth(!r.IsRecording()),
 		)
+		if err != nil {
+			return nil, err
+		}
 		return provider.LanguageModel(model)
 	}
 }

providertests/image_upload_test.go 🔗

@@ -16,30 +16,39 @@ import (
 
 func anthropicImageBuilder(model string) builderFunc {
 	return func(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-		provider := anthropic.New(
+		provider, err := anthropic.New(
 			anthropic.WithAPIKey(cmp.Or(os.Getenv("FANTASY_ANTHROPIC_API_KEY"), "(missing)")),
 			anthropic.WithHTTPClient(&http.Client{Transport: r}),
 		)
+		if err != nil {
+			return nil, err
+		}
 		return provider.LanguageModel(model)
 	}
 }
 
 func openAIImageBuilder(model string) builderFunc {
 	return func(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-		provider := openai.New(
+		provider, err := openai.New(
 			openai.WithAPIKey(cmp.Or(os.Getenv("FANTASY_OPENAI_API_KEY"), "(missing)")),
 			openai.WithHTTPClient(&http.Client{Transport: r}),
 		)
+		if err != nil {
+			return nil, err
+		}
 		return provider.LanguageModel(model)
 	}
 }
 
 func geminiImageBuilder(model string) builderFunc {
 	return func(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-		provider := google.New(
+		provider, err := google.New(
 			google.WithGeminiAPIKey(cmp.Or(os.Getenv("FANTASY_GEMINI_API_KEY"), "(missing)")),
 			google.WithHTTPClient(&http.Client{Transport: r}),
 		)
+		if err != nil {
+			return nil, err
+		}
 		return provider.LanguageModel(model)
 	}
 }

providertests/openai_responses_test.go 🔗

@@ -21,11 +21,14 @@ func TestOpenAIResponsesCommon(t *testing.T) {
 
 func openAIReasoningBuilder(model string) builderFunc {
 	return func(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-		provider := openai.New(
+		provider, err := openai.New(
 			openai.WithAPIKey(os.Getenv("FANTASY_OPENAI_API_KEY")),
 			openai.WithHTTPClient(&http.Client{Transport: r}),
 			openai.WithUseResponsesAPI(),
 		)
+		if err != nil {
+			return nil, err
+		}
 		return provider.LanguageModel(model)
 	}
 }

providertests/openai_test.go 🔗

@@ -27,10 +27,13 @@ func TestOpenAICommon(t *testing.T) {
 
 func openAIBuilder(model string) builderFunc {
 	return func(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-		provider := openai.New(
+		provider, err := openai.New(
 			openai.WithAPIKey(os.Getenv("FANTASY_OPENAI_API_KEY")),
 			openai.WithHTTPClient(&http.Client{Transport: r}),
 		)
+		if err != nil {
+			return nil, err
+		}
 		return provider.LanguageModel(model)
 	}
 }

providertests/openaicompat_test.go 🔗

@@ -49,55 +49,73 @@ func testOpenAICompatThinking(t *testing.T, result *fantasy.AgentResult) {
 }
 
 func builderXAIGrokCodeFast(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-	provider := openaicompat.New(
+	provider, err := openaicompat.New(
 		openaicompat.WithBaseURL("https://api.x.ai/v1"),
 		openaicompat.WithAPIKey(os.Getenv("FANTASY_XAI_API_KEY")),
 		openaicompat.WithHTTPClient(&http.Client{Transport: r}),
 	)
+	if err != nil {
+		return nil, err
+	}
 	return provider.LanguageModel("grok-code-fast-1")
 }
 
 func builderXAIGrok4Fast(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-	provider := openaicompat.New(
+	provider, err := openaicompat.New(
 		openaicompat.WithBaseURL("https://api.x.ai/v1"),
 		openaicompat.WithAPIKey(os.Getenv("FANTASY_XAI_API_KEY")),
 		openaicompat.WithHTTPClient(&http.Client{Transport: r}),
 	)
+	if err != nil {
+		return nil, err
+	}
 	return provider.LanguageModel("grok-4-fast")
 }
 
 func builderXAIGrok3Mini(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-	provider := openaicompat.New(
+	provider, err := openaicompat.New(
 		openaicompat.WithBaseURL("https://api.x.ai/v1"),
 		openaicompat.WithAPIKey(os.Getenv("FANTASY_XAI_API_KEY")),
 		openaicompat.WithHTTPClient(&http.Client{Transport: r}),
 	)
+	if err != nil {
+		return nil, err
+	}
 	return provider.LanguageModel("grok-3-mini")
 }
 
 func builderZAIGLM45(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-	provider := openaicompat.New(
+	provider, err := openaicompat.New(
 		openaicompat.WithBaseURL("https://api.z.ai/api/coding/paas/v4"),
 		openaicompat.WithAPIKey(os.Getenv("FANTASY_ZAI_API_KEY")),
 		openaicompat.WithHTTPClient(&http.Client{Transport: r}),
 	)
+	if err != nil {
+		return nil, err
+	}
 	return provider.LanguageModel("glm-4.5")
 }
 
 func builderGroq(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-	provider := openaicompat.New(
+	provider, err := openaicompat.New(
 		openaicompat.WithBaseURL("https://api.groq.com/openai/v1"),
 		openaicompat.WithAPIKey(os.Getenv("FANTASY_GROQ_API_KEY")),
 		openaicompat.WithHTTPClient(&http.Client{Transport: r}),
 	)
+	if err != nil {
+		return nil, err
+	}
 	return provider.LanguageModel("moonshotai/kimi-k2-instruct-0905")
 }
 
 func builderHuggingFace(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-	provider := openaicompat.New(
+	provider, err := openaicompat.New(
 		openaicompat.WithBaseURL("https://router.huggingface.co/v1"),
 		openaicompat.WithAPIKey(os.Getenv("FANTASY_HUGGINGFACE_API_KEY")),
 		openaicompat.WithHTTPClient(&http.Client{Transport: r}),
 	)
+	if err != nil {
+		return nil, err
+	}
 	return provider.LanguageModel("Qwen/Qwen3-Coder-480B-A35B-Instruct:cerebras")
 }

providertests/openrouter_test.go 🔗

@@ -116,10 +116,13 @@ func testOpenrouterThinking(t *testing.T, result *fantasy.AgentResult) {
 
 func openrouterBuilder(model string) builderFunc {
 	return func(r *recorder.Recorder) (fantasy.LanguageModel, error) {
-		provider := openrouter.New(
+		provider, err := openrouter.New(
 			openrouter.WithAPIKey(os.Getenv("FANTASY_OPENROUTER_API_KEY")),
 			openrouter.WithHTTPClient(&http.Client{Transport: r}),
 		)
+		if err != nil {
+			return nil, err
+		}
 		return provider.LanguageModel(model)
 	}
 }