Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions go/plugins/compat_oai/README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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.
Note: Tests will be skipped if the required API keys are not set.
19 changes: 13 additions & 6 deletions go/plugins/compat_oai/compat_oai.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ package compat_oai
import (
"context"
"fmt"
"strings"
"sync"

"github.com/firebase/genkit/go/ai"
Expand Down Expand Up @@ -68,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.
Expand All @@ -78,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
Expand All @@ -99,16 +109,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)
Expand Down Expand Up @@ -197,7 +204,7 @@ func (o *OpenAICompatible) ListActions(ctx context.Context) []api.ActionDesc {
"systemRole": true,
"tools": true,
"toolChoice": true,
"constrained": true,
"constrained": "all",
},
},
"versions": []string{},
Expand Down
2 changes: 1 addition & 1 deletion go/plugins/compat_oai/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
57 changes: 57 additions & 0 deletions go/samples/compat_oai/custom/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// 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 (
"context"
"log"
"os"

"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/genkit"

oai "github.com/firebase/genkit/go/plugins/compat_oai"
"github.com/openai/openai-go"
)

func main() {
ctx := context.Background()
apiKey := os.Getenv("OPENROUTER_API_KEY")
if apiKey == "" {
log.Fatalf("OPENROUTER_API_KEY environment variable not set")
}

g := genkit.Init(ctx, genkit.WithPlugins(&oai.OpenAICompatible{
Provider: "openrouter",
APIKey: apiKey,
BaseURL: "https://openrouter.ai/api/v1",
}),
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 to generate contents: %v", err)
}
log.Println("Joke:", resp.Text())
}
Loading