From b3f83879d723adab3d59c4d3a4ac5bf580416a43 Mon Sep 17 00:00:00 2001 From: Hugo Aguirre Parra Date: Fri, 19 Sep 2025 00:05:18 +0000 Subject: [PATCH 1/5] fix(go/plugins/compat_oai): add support for custom providers --- go/plugins/compat_oai/README.md | 4 +-- go/plugins/compat_oai/compat_oai.go | 8 ++--- go/plugins/compat_oai/generate.go | 2 +- go/samples/compat_oai/custom/main.go | 49 ++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 9 deletions(-) create mode 100644 go/samples/compat_oai/custom/main.go diff --git a/go/plugins/compat_oai/README.md b/go/plugins/compat_oai/README.md index 509145e122..317f4f2b0b 100644 --- a/go/plugins/compat_oai/README.md +++ b/go/plugins/compat_oai/README.md @@ -1,6 +1,6 @@ # OpenAI-Compatible Plugin Package -This directory contains a package for building plugins that are compatible with the OpenAI API specification, along with plugins built on top of this package. +This directory contains a package for building plugins that are compatible with the OpenAI API specification, along with plugins built on top of this package. ## Package Overview @@ -74,4 +74,4 @@ go test -v ./openai go test -v ./anthropic ``` -Note: Tests will be skipped if the required API keys are not set. \ No newline at end of file +Note: Tests will be skipped if the required API keys are not set. diff --git a/go/plugins/compat_oai/compat_oai.go b/go/plugins/compat_oai/compat_oai.go index 41adb4b44e..b1701d0c7d 100644 --- a/go/plugins/compat_oai/compat_oai.go +++ b/go/plugins/compat_oai/compat_oai.go @@ -17,7 +17,6 @@ package compat_oai import ( "context" "fmt" - "strings" "sync" "github.com/firebase/genkit/go/ai" @@ -99,16 +98,13 @@ func (o *OpenAICompatible) DefineModel(provider, id string, opts ai.ModelOptions panic("OpenAICompatible.Init not called") } - // Strip provider prefix if present to check against supportedModels - modelName := strings.TrimPrefix(id, provider+"/") - return ai.NewModel(api.NewName(provider, id), &opts, func( ctx context.Context, input *ai.ModelRequest, cb func(context.Context, *ai.ModelResponseChunk) error, ) (*ai.ModelResponse, error) { // Configure the response generator with input - generator := NewModelGenerator(o.client, modelName).WithMessages(input.Messages).WithConfig(input.Config).WithTools(input.Tools) + generator := NewModelGenerator(o.client, id).WithMessages(input.Messages).WithConfig(input.Config).WithTools(input.Tools) // Generate response resp, err := generator.Generate(ctx, cb) @@ -197,7 +193,7 @@ func (o *OpenAICompatible) ListActions(ctx context.Context) []api.ActionDesc { "systemRole": true, "tools": true, "toolChoice": true, - "constrained": true, + "constrained": "all", }, }, "versions": []string{}, diff --git a/go/plugins/compat_oai/generate.go b/go/plugins/compat_oai/generate.go index 72cbf8d17b..ac9ee7518f 100644 --- a/go/plugins/compat_oai/generate.go +++ b/go/plugins/compat_oai/generate.go @@ -158,7 +158,7 @@ func (g *ModelGenerator) WithConfig(config any) *ModelGenerator { openaiConfig = *cfg case map[string]any: if err := mapToStruct(cfg, &openaiConfig); err != nil { - g.err = fmt.Errorf("failed to convert config to OpenAIConfig: %w", err) + g.err = fmt.Errorf("failed to convert config to openai.ChatCompletionNewParams: %w", err) return g } default: diff --git a/go/samples/compat_oai/custom/main.go b/go/samples/compat_oai/custom/main.go new file mode 100644 index 0000000000..ea294423ca --- /dev/null +++ b/go/samples/compat_oai/custom/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "context" + "log" + "os" + + "github.com/firebase/genkit/go/ai" + "github.com/firebase/genkit/go/genkit" + "github.com/firebase/genkit/go/plugins/compat_oai" + "github.com/openai/openai-go" + "github.com/openai/openai-go/option" +) + +func main() { + apiKey := os.Getenv("OPENROUTER_API_KEY") + if apiKey == "" { + log.Fatalf("no OPENROUTER_API_KEY environment variable set") + } + + opts := []option.RequestOption{ + option.WithAPIKey(apiKey), + option.WithBaseURL("https://openrouter.ai/api/v1"), + } + + plugin := &compat_oai.OpenAICompatible{ + Opts: opts, + Provider: "openrouter", + } + + g := genkit.Init(context.Background(), + genkit.WithPlugins(plugin), + genkit.WithDefaultModel("openrouter/tngtech/deepseek-r1t2-chimera:free"), + ) + + prompt := "tell me a joke" + config := &openai.ChatCompletionNewParams{ + Temperature: openai.Float(0.7), + MaxTokens: openai.Int(1000), + TopP: openai.Float(0.9), + } + + resp, err := genkit.Generate(context.Background(), g, ai.WithConfig(config), ai.WithPrompt(prompt)) + if err != nil { + log.Fatalf("failed generating: %v", err) + } + + log.Println("resp: ", resp.Text()) +} From a25f16793e10d43a644dd6283cc3f8618ac69147 Mon Sep 17 00:00:00 2001 From: Hugo Aguirre Parra Date: Fri, 19 Sep 2025 00:54:38 +0000 Subject: [PATCH 2/5] refine sample --- go/plugins/compat_oai/compat_oai.go | 11 ++++++++++ go/samples/compat_oai/custom/main.go | 31 ++++++++++++---------------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/go/plugins/compat_oai/compat_oai.go b/go/plugins/compat_oai/compat_oai.go index b1701d0c7d..97ae410fc8 100644 --- a/go/plugins/compat_oai/compat_oai.go +++ b/go/plugins/compat_oai/compat_oai.go @@ -67,6 +67,9 @@ type OpenAICompatible struct { // This will be used as a prefix for model names (e.g., "myprovider/model-name"). // Should be lowercase and match the plugin's Name() method. Provider string + + APIKey string + BaseURL string } // Init implements genkit.Plugin. @@ -77,6 +80,14 @@ func (o *OpenAICompatible) Init(ctx context.Context) []api.Action { panic("compat_oai.Init already called") } + if o.APIKey != "" { + o.Opts = append([]option.RequestOption{option.WithAPIKey(o.APIKey)}, o.Opts...) + } + + if o.BaseURL != "" { + o.Opts = append([]option.RequestOption{option.WithBaseURL(o.BaseURL)}, o.Opts...) + } + // create client client := openai.NewClient(o.Opts...) o.client = &client diff --git a/go/samples/compat_oai/custom/main.go b/go/samples/compat_oai/custom/main.go index ea294423ca..a39b1ce2e2 100644 --- a/go/samples/compat_oai/custom/main.go +++ b/go/samples/compat_oai/custom/main.go @@ -7,31 +7,24 @@ import ( "github.com/firebase/genkit/go/ai" "github.com/firebase/genkit/go/genkit" - "github.com/firebase/genkit/go/plugins/compat_oai" + + oai "github.com/firebase/genkit/go/plugins/compat_oai" "github.com/openai/openai-go" - "github.com/openai/openai-go/option" ) func main() { + ctx := context.Background() apiKey := os.Getenv("OPENROUTER_API_KEY") if apiKey == "" { - log.Fatalf("no OPENROUTER_API_KEY environment variable set") - } - - opts := []option.RequestOption{ - option.WithAPIKey(apiKey), - option.WithBaseURL("https://openrouter.ai/api/v1"), + log.Fatalf("OPENROUTER_API_KEY environment variable not set") } - plugin := &compat_oai.OpenAICompatible{ - Opts: opts, + g := genkit.Init(ctx, genkit.WithPlugins(&oai.OpenAICompatible{ Provider: "openrouter", - } - - g := genkit.Init(context.Background(), - genkit.WithPlugins(plugin), - genkit.WithDefaultModel("openrouter/tngtech/deepseek-r1t2-chimera:free"), - ) + APIKey: apiKey, + BaseURL: "https://openrouter.ai/api/v1", + }), + genkit.WithDefaultModel("openrouter/tngtech/deepseek-r1t2-chimera:free")) prompt := "tell me a joke" config := &openai.ChatCompletionNewParams{ @@ -40,10 +33,12 @@ func main() { TopP: openai.Float(0.9), } - resp, err := genkit.Generate(context.Background(), g, ai.WithConfig(config), ai.WithPrompt(prompt)) + resp, err := genkit.Generate(context.Background(), g, + ai.WithConfig(config), + ai.WithPrompt(prompt)) if err != nil { log.Fatalf("failed generating: %v", err) } - log.Println("resp: ", resp.Text()) + log.Println("Joke:", resp.Text()) } From 591e8506fd1b84d6b54608591dc32cb11eec28fb Mon Sep 17 00:00:00 2001 From: Hugo Aguirre Parra Date: Fri, 19 Sep 2025 00:58:29 +0000 Subject: [PATCH 3/5] add license --- go/samples/compat_oai/custom/main.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/go/samples/compat_oai/custom/main.go b/go/samples/compat_oai/custom/main.go index a39b1ce2e2..371bf14b22 100644 --- a/go/samples/compat_oai/custom/main.go +++ b/go/samples/compat_oai/custom/main.go @@ -1,3 +1,17 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main import ( From 416eb6f90e9f1b2ffe6bcc170bb5e0e913056583 Mon Sep 17 00:00:00 2001 From: Hugo Aguirre Parra Date: Fri, 19 Sep 2025 19:38:04 +0000 Subject: [PATCH 4/5] better log message --- go/samples/compat_oai/custom/main.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/go/samples/compat_oai/custom/main.go b/go/samples/compat_oai/custom/main.go index 371bf14b22..5a084e3686 100644 --- a/go/samples/compat_oai/custom/main.go +++ b/go/samples/compat_oai/custom/main.go @@ -51,8 +51,7 @@ func main() { ai.WithConfig(config), ai.WithPrompt(prompt)) if err != nil { - log.Fatalf("failed generating: %v", err) + log.Fatalf("failed to generate contents: %v", err) } - log.Println("Joke:", resp.Text()) } From 43f07cc42dbc9d6d5ca5fff446714ad5cf9a9a36 Mon Sep 17 00:00:00 2001 From: Hugo Aguirre Parra Date: Wed, 24 Sep 2025 19:17:06 +0000 Subject: [PATCH 5/5] add comments --- go/plugins/compat_oai/compat_oai.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/go/plugins/compat_oai/compat_oai.go b/go/plugins/compat_oai/compat_oai.go index 97ae410fc8..c76d16a3c0 100644 --- a/go/plugins/compat_oai/compat_oai.go +++ b/go/plugins/compat_oai/compat_oai.go @@ -68,7 +68,12 @@ type OpenAICompatible struct { // Should be lowercase and match the plugin's Name() method. Provider string - APIKey string + // API key to use with the desired plugin. + APIKey string + + // Base URL to use for custom endpoints. + // This should be used if you are running through a proxy or + // using a non-official endpoint BaseURL string }