Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(go/plugins): Generic OpenAI plugin #2440

Open
wants to merge 52 commits into
base: main
Choose a base branch
from

Conversation

kekoawong
Copy link
Contributor

@kekoawong kekoawong commented Mar 23, 2025

Adding support for an openai go plugin, along with additional generic support for any providers compatible with the openai (from feature request #2204). Collaboration between @xavidop @yukinagae @kekoawong.

Checklist:

  • Support complete response from chat completions
  • Support streaming from chat completions
  • Add generic compatibility
  • Add embedding support
  • Add tools support

@github-actions github-actions bot added docs Improvements or additions to documentation go labels Mar 23, 2025
Copy link
Contributor

@yukinagae yukinagae left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

o.mu.Lock()
defer o.mu.Unlock()
if o.initted {
panic("compat_oai.Init already called")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return error here instead of panicking.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed here: kekoawong#11

}

// DefineModel defines a model in the registry
func (o *OpenAICompatible) DefineModel(g *genkit.Genkit, name string, info ai.ModelInfo, provider string) (ai.Model, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func (o *OpenAICompatible) DefineModel(g *genkit.Genkit, name string, info ai.ModelInfo, provider string) (ai.Model, error) {
func (o *OpenAICompatible) DefineModel(g *genkit.Genkit, provider, name string, info ai.ModelInfo) (ai.Model, error) {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed here: kekoawong#11

o.mu.Lock()
defer o.mu.Unlock()
if !o.initted {
panic("OpenAICompatible.Init not called")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return error here instead of panicking.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed here: kekoawong#11

o.mu.Lock()
defer o.mu.Unlock()
if !o.initted {
panic("OpenAICompatible.Init not called")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return error here instead of panicking.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed here: kekoawong#11

Comment on lines +28 to +41
ctx := context.Background()
g, err := genkit.Init(ctx)
if err != nil {
log.Fatalf("failed to create Genkit: %v", err)
}

apiKey := os.Getenv("OPENAI_API_KEY")
apiKeyOption := option.WithAPIKey(apiKey)
oai := oai.OpenAI{
Opts: []option.RequestOption{apiKeyOption},
}

oai.Init(ctx, g)
genkit.WithPlugins(&oai)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ctx := context.Background()
g, err := genkit.Init(ctx)
if err != nil {
log.Fatalf("failed to create Genkit: %v", err)
}
apiKey := os.Getenv("OPENAI_API_KEY")
apiKeyOption := option.WithAPIKey(apiKey)
oai := oai.OpenAI{
Opts: []option.RequestOption{apiKeyOption},
}
oai.Init(ctx, g)
genkit.WithPlugins(&oai)
ctx := context.Background()
g, err := genkit.Init(ctx, genkit.WithPlugins(oai.OpenAI{
Opts: []option.RequestOption{option.WithAPIKey(os.Getenv("OPENAI_API_KEY"))},
})
if err != nil {
log.Fatalf("failed to create Genkit: %v", err)
}

Curious, is there any reason why you didn't do it like this? I wonder if it's not obvious that it should be called this way. The way you had genkit.WithPlugins(&oai) did nothing -- you had already initialized it yourself and the option isn't passed anywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migrated initialization in tests here: kekoawong#10. Will wait for @xavidop to migrate behavior in samples

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added here: kekoawong#11

}

func (o *OpenAI) Init(ctx context.Context, g *genkit.Genkit) error {
err := o.openAICompatible.Init(ctx, g)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: here and elsewhere, can do this which is more succinct and preferred:

if err := o.openAICompatible.Init(ctx, g); err != nil {
     return err
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed here: kekoawong#11

Multiturn: true,
Tools: true,
SystemRole: true,
Media: true,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ToolChoice: true?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added here: kekoawong#11

initted bool
client *openaiGo.Client
Opts []option.RequestOption
Provider string
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments here would be good. What is provider?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added here: kekoawong#11

},
{
name: "float and int fields",
config: &openai.ChatCompletionNewParams{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should try not to depend directly on the config type defined elsewhere (like in 3P SDKs) so that the SDKs are implementation details and we could in theory swap out to another or even call the REST API directly. We sometimes implement configuration via native Genkit features so if we have it both natively and in the config, it will be confusing as to what happens when you combine them.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed here: kekoawong#11

t.Log("genkit initialized")

// Initialize the OpenAI plugin
apiKeyOption := option.WithAPIKey(apiKey)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which other options do we (want to) support? Maybe APIKey should be a top level field in OpenAI like it is in GoogleAI? Better to err on the side of being simple and complete rather than supporting everything that the SDK supports.

feat(go/openai plugin): support additional OpenAI models
)

type OpenAI struct {
Opts []option.RequestOption
Copy link
Contributor

@yukinagae yukinagae Apr 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Opts field is never used, in other words, the current implementation does not handle the passed options.

For example, even if the API key is passed via the option like this, the OpenAI client is reading the environment variable instead:

apiKeyOption := option.WithAPIKey("sk-xxxxx")
oai := &openai.OpenAI{
    Opts: []option.RequestOption{apiKeyOption},
}

Copy link
Collaborator

@apascal07 apascal07 Apr 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, we might want to just bring out APIKey as a top level field in the OpenAI struct.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in here: kekoawong#11

t.Logf("streaming response: %+v", finalOutput)
})

t.Run("tool usage with basic completion", func(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function calling test is flaky depending on the model. It seems to be related to math.Pow(), and I'll give it a try to fix it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is fixed already

t.Logf("tool usage with basic completion response: %+v", out)
})

t.Run("tool usage with streaming", func(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's flaky, just like the function calling test with basic completion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is fixed already

@xavidop xavidop mentioned this pull request Apr 9, 2025
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Improvements or additions to documentation go
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

4 participants