Skip to content

Creating Genkit plugins

Genkit’s capabilities are designed to be extended by plugins. Genkit plugins are configurable modules that can provide models, retrievers, trace stores, and more. You’ve already seen plugins in action just by using Genkit:

import (
"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/genkit"
"github.com/firebase/genkit/go/plugins/googlegenai"
"github.com/firebase/genkit/go/plugins/server"
)
g := genkit.Init(ctx,
genkit.WithPlugins(
&googlegenai.GoogleAI{APIKey: ...},
&googlegenai.VertexAI{ProjectID: "my-project", Location: "us-central1"},
),
)

The Vertex AI plugin takes configuration (such as the user’s Google Cloud project ID) and registers a variety of new models, embedders, and more with the Genkit registry. The registry serves as a lookup service for named actions at runtime, and powers Genkit’s local UI for running and inspecting models, prompts, and more.

In Go, a Genkit plugin is a type that implements the api.Plugin interface. A single module can contain several plugins.

Every plugin must have a unique identifier string that distinguishes it from other plugins. Genkit uses this identifier as a namespace for every resource your plugin defines, to prevent naming conflicts with other plugins.

For example, if your plugin has an ID yourplugin and provides a model called text-generator, the full model identifier will be yourplugin/text-generator.

This provider ID needs to be exported and you should define it once for your plugin and use it consistently when required by a Genkit function.

package yourplugin
const providerID = "yourplugin"

Every plugin exports a struct type that encapsulates all of the configuration options accepted by the plugin, and implements Name() and Init() on it.

For any plugin options that are secret values, such as API keys, you should offer both a config option and a default environment variable to configure it. This lets your plugin take advantage of the secret-management features offered by many hosting providers, such as Cloud Secret Manager, which you can use with Cloud Run.

package myplugin
import (
"context"
"os"
"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/core/api"
)
const providerID = "myprovider"
// MyPlugin holds every option the plugin accepts.
type MyPlugin struct {
// APIKey is the credential. If empty, MYPROVIDER_API_KEY is consulted.
APIKey string
// Models overrides what the plugin knows about a model, keyed by model ID.
// Fields left at their zero value keep what the plugin resolved.
Models map[string]ai.ModelOptions
client *apiClient
}
// Name returns the provider ID.
func (p *MyPlugin) Name() string { return providerID }
// Init creates shared resources and returns the actions to register.
func (p *MyPlugin) Init(ctx context.Context) []api.Action {
apiKey := p.APIKey
if apiKey == "" {
apiKey = os.Getenv("MYPROVIDER_API_KEY")
}
if apiKey == "" {
panic("myprovider plugin initialization failed: apiKey is required")
}
p.client = newAPIClient(apiKey)
return []api.Action{
p.newModel("my-model"),
}
}
var _ api.Plugin = (*MyPlugin)(nil)

Init is called automatically during genkit.Init() when the user passes the plugin into the WithPlugins() option. In it:

  • Confirm that any required configuration values are specified and assign default values to any unspecified optional settings.
  • Verify that the given configuration options are valid together.
  • Create any shared resources required by the rest of your plugin. For example, create clients for any services your plugin accesses.
  • Construct your actions and return them. The framework registers whatever Init returns, so a plugin never reaches into the registry itself.

To the extent possible, the resources provided by your plugin shouldn’t assume that any other plugins have been installed before this one.

A provider that serves more models than are worth registering up front can also implement api.DynamicPlugin, which adds two methods. ListActions advertises everything the plugin could serve, so the Developer UI can list it, and ResolveAction builds one on demand when a name is looked up and nothing is registered under it.

// ListActions advertises every model the plugin could serve.
func (p *MyPlugin) ListActions(ctx context.Context) []api.ActionDesc {
var descs []api.ActionDesc
for _, id := range p.client.listModels(ctx) {
descs = append(descs, p.newModel(id).Desc())
}
return descs
}
// ResolveAction builds a model on demand. id arrives bare: the registry has
// already matched the provider prefix to this plugin.
func (p *MyPlugin) ResolveAction(atype api.ActionType, id string) api.Action {
if atype != api.ActionTypeModel {
return nil
}
return p.newModel(id)
}

Because an unknown ID still resolves, a curated catalog is a source of capability metadata rather than an allowlist.

Genkit has two audiences and two matching styles, and it is worth knowing which one you are writing against.

The plugin-facing surface, core.New*Of and ai.New*Action, reads identity, then descriptor, then implementation. Identity is positional because it is always required: core.New*Of takes the action type and then the name, and ai.New*Action takes the name. One options struct follows and carries the descriptor. For core.New*Of it is core.ActionOptions, holding the description, the metadata, and each schema; for ai.New*Action it is a primitive-specific struct such as ai.ModelOptions or ai.RetrieverOptions, holding the label, the capabilities, and the config schema. The implementation function is positional, and optional lifecycle hooks are fields on the options struct. Shaped this way, a new descriptor slot or a new hook is never a signature break.

The app-facing surface is genkit.Define* and the ai.With* request options. The request options, and the tool and prompt definers, stay variadic functional options, because an application names only the few things it cares about. Each genkit.Define*Action wrapper takes the same options struct as the plugin-side constructor it wraps, because it is that constructor plus registration in one call.

A single plugin can activate many new things within Genkit. For example, the Vertex AI plugin activates several new models as well as an embedder. Here’s how to build some common plugin types.

Genkit model plugins add one or more generative AI models to the Genkit registry. A model represents any generative model that is capable of receiving a prompt as input and generating text, media, or data as output.

A model definition consists of three components:

  1. Metadata declaring the model’s capabilities.
  2. A configuration type with any specific parameters supported by the model.
  3. A generation function that accepts an ai.ModelRequest and returns an ai.ModelResponse, presumably using an AI model to generate the latter.

ai.NewModelAction binds all three. Its Config type parameter is inferred from the generation function, so the signature you write is the contract:

// MyModelConfig defines the configuration options for your model. Its JSON
// schema is inferred from this type and enforced on every request.
type MyModelConfig struct {
Temperature *float64 `json:"temperature,omitempty" jsonschema:"minimum=0,maximum=2"`
MaxTokens int `json:"maxTokens,omitempty" jsonschema:"minimum=1"`
}
func (p *MyPlugin) newModel(id string) *ai.ModelAction {
opts := ai.ModelOptions{
Label: "My Model", // User-friendly label
Supports: &ai.ModelSupports{
Multiturn: true, // Does the model support multi-turn chats?
SystemRole: true, // Does the model support system messages?
Media: false, // Can the model accept media input?
Tools: false, // Does the model support function calling (tools)?
},
Versions: []string{"my-model-001"}, // List supported versions/aliases
}
// An application entry corrects or extends what the plugin resolved.
opts = opts.Overlay(p.Models[id])
return ai.NewModelAction(providerID+"/"+id, &opts,
func(ctx context.Context, req *ai.ModelRequest, cfg *MyModelConfig, cb ai.ModelStreamCallback) (*ai.ModelResponse, error) {
// cfg arrives deserialized and validated. It is nil when the caller
// sent no config, because Config here is a pointer type.
if cfg == nil {
cfg = &MyModelConfig{}
}
// Use your custom logic to convert Genkit's ai.ModelRequest into a
// form usable by the model's native API.
apiReq, err := apiRequestFromGenkitRequest(req, cfg)
if err != nil {
return nil, err
}
// Send the request to the model API, using your own code or the
// model API's client library.
apiResp, err := p.client.generate(ctx, apiReq)
if err != nil {
return nil, wrapAPIError(err)
}
// Convert the model's response to Genkit's ai.ModelResponse.
return genkitResponseFromAPIResponse(req, apiResp)
})
}

ai.NewModelAction returns an unregistered *ai.ModelAction that satisfies both ai.Model and api.Action, so Init can return it in an []api.Action without a type assertion. ai.ModelOptions.Overlay returns the receiver with every field set in the override replacing it, which is how an application corrects one capability without restating the label and the versions.

To specify the generation options a model supports, define and export a configuration type and use it as the Config type parameter. jsonschema struct tags become the constraints, and jsonschema_description tags become the field documentation the Developer UI renders in its config sidebar. Genkit has an ai.GenerationCommonConfig type that contains options frequently supported by generative AI model services, which you can embed if your provider matches it.

The framework infers the config JSON schema from Config and validates every request against it before your generation function runs, so the function never needs a type check. Config sent as the exact type, as a pointer to it, or as a map[string]any from a JSON caller such as the Developer UI is converted to the typed value; anything else fails with INVALID_ARGUMENT. When Config is a pointer type, a request that carries no config hands your function a nil pointer, so nil-check it. When Config is a value type, you get the zero value.

Every model definition must contain, as part of its metadata, an ai.ModelSupports value that declares which features the model supports. Genkit uses this information to determine certain behaviors, such as verifying whether certain inputs are valid for the model. For example, if the model doesn’t support multi-turn interactions, then it’s an error to pass it a message history.

Note that these declarations refer to the capabilities of the model as provided by your plugin, and do not necessarily map one-to-one to the capabilities of the underlying model and model API. For example, even if the model API doesn’t provide a specific way to define system messages, your plugin might still declare support for the system role, and implement it as special logic that inserts system messages into the user prompt.

The two ways to be wrong are not symmetric. Declaring too narrowly is refused by Genkit before the request leaves the process, blocking a model that would have worked, while an over-wide claim reaches the provider and comes back with the real reason.

The generation function carries out the primary work of a Genkit model plugin: transforming the ai.ModelRequest from Genkit’s common format into a format that is supported by your model’s API, and then transforming the response from your model into the ai.ModelResponse format used by Genkit.

Sometimes, this may require massaging or manipulating data to work around model limitations. For example, if your model does not natively support a system message, you may need to transform a prompt’s system message into a user-model message pair.

Retry and fallback middleware branch on the status an error carries, so classify your SDK’s errors at the boundary rather than returning them raw.

// wrapAPIError classifies an SDK error so status-aware middleware can tell a
// rate limit from a request the provider rejected.
func wrapAPIError(err error) error {
var apiErr *apiError
if !errors.As(err, &apiErr) {
return err
}
return status.Errorf(status.Base(status.FromHTTPCode(apiErr.StatusCode)), "%w", err)
}

status.FromHTTPCode maps an HTTP status code to a canonical status name, and status.Base turns that name into the sentinel status.Errorf classifies with. Recording the cause with %w keeps the original error reachable through errors.Is and errors.As.

Leave anything that is not an API error unclassified. The retry middleware retries an unclassified error unconditionally, which is the right answer for a dial timeout, and the fallback middleware leaves it alone, because failing over to a different billed model is worth requiring an explicit classification for. A classified error is retried only when its status is on the middleware’s list, so classifying a 401 as UNAUTHENTICATED is what stops it being reissued until the attempts run out. The retry and fallback sample shows both from the application side.

Wrap with %w, never %v. Flattening the error destroys the classification, which silently turns a non-retryable INVALID_ARGUMENT into an unclassified error that is always retried.

In addition to the resources that all plugins must export, a model plugin should also export the following:

  • A generation config type, as discussed earlier.

  • A ModelRef function, which creates a model reference paired with its config so the two travel together and the config is typed at the call site:

func ModelRef(id string, config *MyModelConfig) ai.ModelRef {
return ai.NewModelRef(api.NewName(providerID, id), config)
}

Callers then write ai.WithModel(myplugin.ModelRef("my-model", &myplugin.MyModelConfig{...})).

  • A Models map[string]ai.ModelOptions field on the plugin struct, so an application can correct a capability your catalog got wrong, or describe a model released after your plugin was. Route every path that describes a model through one method that applies the overlay, so an entry is authoritative whether Init, ListActions, or ResolveAction reaches the model first.

    Prefer this to an exported DefineModel function. Correction is data rather than API surface, and a model that Init has already registered cannot be re-registered.

The other primitives follow the same shape as models: ai.NewRetrieverAction, ai.NewEmbedderAction, ai.NewEvaluatorAction, ai.NewBatchEvaluatorAction, and ai.NewBackgroundModelAction, each with its own options struct and its own typed Config type parameter, validated the same way. ai.NewBatchEvaluatorAction shares ai.EvaluatorOptions with ai.NewEvaluatorAction; there is no separate batch options type.

Applications reach the same constructors through the registering genkit.Define*Action wrappers:

type MyRetrieverConfig struct {
K int `json:"k,omitempty" jsonschema:"minimum=1"`
}
type MyEmbedderConfig struct {
Dimensions int `json:"dimensions,omitempty" jsonschema:"minimum=1"`
}
genkit.DefineRetrieverAction(g, "myprovider/docs",
&ai.RetrieverOptions{Label: "My Docs"},
func(ctx context.Context, req *ai.RetrieverRequest, cfg *MyRetrieverConfig) (*ai.RetrieverResponse, error) {
k := 10
if cfg != nil && cfg.K > 0 {
k = cfg.K
}
docs, err := search(ctx, req.Query, k)
if err != nil {
return nil, err
}
return &ai.RetrieverResponse{Documents: docs}, nil
})
genkit.DefineEmbedderAction(g, "myprovider/embed-001",
&ai.EmbedderOptions{Label: "My Embedder", Dimensions: 768},
func(ctx context.Context, req *ai.EmbedRequest, cfg *MyEmbedderConfig) (*ai.EmbedResponse, error) {
vectors, err := embed(ctx, req.Input)
if err != nil {
return nil, err
}
return &ai.EmbedResponse{Embeddings: vectors}, nil
})

Schema registration lives in package genkit only. Register a schema when a .prompt file, ai.WithOutputSchemaName, or ai.WithInputSchemaName refers to one by name. The call must run before the prompt is rendered.

// Register several Go types at once, each under its type's name.
genkit.DefineSchemasFor(g, JokeRequest{}, Joke{})
// The single-type form.
genkit.DefineSchemaFor[Recipe](g)
// A raw JSON schema needs an explicit name.
genkit.DefineSchema(g, "Rating", map[string]any{
"type": "integer", "minimum": 1, "maximum": 5,
})

Each name may be registered once per Genkit instance. A second registration of the same name panics, so call these helpers once at startup. DefineSchemasFor and DefineSchemaFor take Go types, and panic on a map, a nil value, or an unnamed type, pointing you at DefineSchema for a raw schema.

Package core keeps the schema utilities that do not register anything. core.InferSchemaMap turns a Go type into a schema map, which is how a plugin fills an options struct’s ConfigSchema:

opts := ai.ModelOptions{ConfigSchema: core.InferSchemaMap(MyModelConfig{})}

For anything that is not one of the AI primitives, package core has the generic constructors: core.NewActionOf, core.NewStreamingActionOf, core.NewBidiActionOf, and core.NewBackgroundActionOf. Each takes the action type first, then the name, then an options struct, then the implementation.

action := core.NewActionOf(api.ActionTypeCustom, "myAction",
&core.ActionOptions{
Description: "Processes a string",
Metadata: map[string]any{"team": "search"},
// InputSchema, OutputSchema and StreamSchema left nil are inferred
// from the type parameters.
},
func(ctx context.Context, input string) (string, error) {
return "processed: " + input, nil
})

Return the action from your plugin’s Init, as in the Init example above. The framework registers whatever Init returns.

A nil options value is legal and infers every schema. core.NewBackgroundActionOf takes a core.BackgroundActionOptions[In, Out], whose Check field is required and whose Cancel field is optional: omitting Cancel means the action does not support cancellation.

If your provider speaks the OpenAI chat-completions API, build on plugins/compat_oai rather than writing a client from scratch. Point the base plugin at the endpoint and every model resolves by name, taking the OpenAI SDK’s own request type as its config:

g := genkit.Init(ctx, genkit.WithPlugins(&compat_oai.OpenAICompatible{
Provider: "myprovider",
APIKey: apiKey,
BaseURL: "https://api.example.com/v1",
}))
model := ai.NewModelRef("myprovider/some-model", &openai.ChatCompletionNewParams{
Temperature: openai.Float(0.7),
})

The custom provider sample is the worked example.

To give your provider a config of its own, and a home for the extensions the SDK request type has no room for, declare a struct that embeds compat_oai.RequestConfig, implement ApplyToChatCompletion, and build models with compat_oai.NewChatModel[Config]:

type ChatConfig struct {
compat_oai.RequestConfig
Temperature *float64 `json:"temperature,omitempty" jsonschema:"minimum=0,maximum=2"`
EnableSearch *bool `json:"enableSearch,omitempty"`
}
func (c ChatConfig) ApplyToChatCompletion(params *openai.ChatCompletionNewParams) {
c.ApplyVersion(params)
if c.Temperature != nil {
params.Temperature = openai.Float(*c.Temperature)
}
if c.EnableSearch != nil {
compat_oai.AddExtraFields(params, map[string]any{"enable_search": *c.EnableSearch})
}
}
func (p *MyPlugin) newChatModel(id string, opts ai.ModelOptions) *ai.ModelAction {
return compat_oai.NewChatModel[ChatConfig](&p.openAICompatible, id, opts)
}

Embedding RequestConfig gives every config three shared settings: a code-only APIKey for a per-request credential, Version to pin an exact model version, and Extra for undeclared body fields sent verbatim under the provider’s wire names. compat_oai.ListChatActions and compat_oai.ResolveChatAction are the matching DynamicPlugin methods, and compat_oai.ModelOptionsFor applies a caller’s Models entry over what your catalog resolved.

On this path you do not classify SDK errors yourself: the shared request path runs compat_oai.WrapAPIError, which wraps an error the OpenAI SDK returned for an HTTP response in a status.Error carrying the status the server reported, and passes everything else through untouched.

The Genkit libraries are instrumented with OpenTelemetry to support collecting traces, metrics, and logs. Genkit users can export this telemetry data to monitoring and visualization tools by installing a plugin that configures the OpenTelemetry Go SDK to export to a particular OpenTelemetry-capable system.

Genkit includes a plugin that configures OpenTelemetry to export data to Google Cloud Monitoring and Cloud Logging. To support other monitoring systems, you can extend Genkit by writing a telemetry plugin. The telemetry sample exercises one end to end.

The primary job of a telemetry plugin is to configure OpenTelemetry to export data to a particular service. To do so, you need the following:

  • An implementation of OpenTelemetry’s SpanExporter interface that exports data to the service of your choice.
  • An implementation of OpenTelemetry’s metric.Exporter interface that exports data to the service of your choice.
  • Either a slog.Logger or an implementation of the slog.Handler interface, that exports logs to the service of your choice.

Depending on the service you’re interested in exporting to, this might be a relatively minor effort or a large one.

Because OpenTelemetry is an industry standard, many monitoring services already have libraries that implement these interfaces. For example, the googlecloud plugin for Genkit makes use of the opentelemetry-operations-go library, maintained by the Google Cloud team. Similarly, many monitoring services provide libraries that implement the standard slog interfaces.

On the other hand, if no such libraries are available for your service, implementing the necessary interfaces can be a substantial project.

Check the OpenTelemetry registry or the monitoring service’s docs to see if integrations are already available.

If you need to build these integrations yourself, take a look at the source of the official OpenTelemetry exporters and the page A Guide to Writing slog Handlers.

Every telemetry plugin needs to import the Genkit core library and several OpenTelemetry libraries:

// Import the Genkit tracing library.
"github.com/firebase/genkit/go/core/tracing"
// Import the OpenTelemetry libraries.
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/metric"
sdktrace "go.opentelemetry.io/otel/sdk/trace"

If you are building a plugin around an existing OpenTelemetry or slog integration, you will also need to import them.

A telemetry plugin should, at a minimum, support the following configuration options:

type Config struct {
// Export even in the dev environment.
ForceExport bool
// The interval for exporting metric data.
// The default is 60 seconds.
MetricInterval time.Duration
// The minimum level at which logs will be written.
// Defaults to [slog.LevelInfo].
LogLevel slog.Leveler
}

Most plugins will also include configuration settings for the service it’s exporting to (API key, project name, and so on).

The Init() function of a telemetry plugin should do all of the following:

  • Return early if Genkit is running in a development environment (such as when running with with genkit start) and the Config.ForceExport option isn’t set:
shouldExport := cfg.ForceExport || os.Getenv("GENKIT_ENV") != "dev"
if !shouldExport {
return nil
}
  • Initialize your trace span exporter and register it with Genkit:
spanProcessor := sdktrace.NewBatchSpanProcessor(YourCustomSpanExporter{})
tracing.TracerProvider().RegisterSpanProcessor(spanProcessor)
  • Initialize your metric exporter and register it with the OpenTelemetry library:
r := metric.NewPeriodicReader(
YourCustomMetricExporter{},
metric.WithInterval(cfg.MetricInterval),
)
mp := metric.NewMeterProvider(metric.WithReader(r))
otel.SetMeterProvider(mp)

Use the user-configured collection interval (Config.MetricInterval) when initializing the PeriodicReader.

  • Register your slog handler as the default logger:
logger := slog.New(YourCustomHandler{
Options: &slog.HandlerOptions{Level: cfg.LogLevel},
})
slog.SetDefault(logger)

You should configure your handler to honor the user-specified minimum log level (Config.LogLevel).

Because most generative AI flows begin with user input of some kind, it’s a likely possibility that some flow traces contain personally-identifiable information (PII). To protect your users’ information, you should redact PII from traces before you export them.

If you are building your own span exporter, you can build this functionality into it.

If you’re building your plugin around an existing OpenTelemetry integration, you can wrap the provided span exporter with a custom exporter that carries out this task. For example, the googlecloud plugin removes the genkit:input and genkit:output attributes from every span before exporting them using a wrapper similar to the following:

type redactingSpanExporter struct {
trace.SpanExporter
}
func (e *redactingSpanExporter) ExportSpans(ctx context.Context, spanData []trace.ReadOnlySpan) error {
var redacted []trace.ReadOnlySpan
for _, s := range spanData {
redacted = append(redacted, redactedSpan{s})
}
return e.SpanExporter.ExportSpans(ctx, redacted)
}
func (e *redactingSpanExporter) Shutdown(ctx context.Context) error {
return e.SpanExporter.Shutdown(ctx)
}
type redactedSpan struct {
trace.ReadOnlySpan
}
func (s redactedSpan) Attributes() []attribute.KeyValue {
// Omit input and output, which may contain PII.
var ts []attribute.KeyValue
for _, a := range s.ReadOnlySpan.Attributes() {
if a.Key == "genkit:input" || a.Key == "genkit:output" {
continue
}
ts = append(ts, a)
}
return ts
}

If you’re having trouble getting data to show up where you expect, OpenTelemetry provides a useful diagnostic tool that helps locate the source of the problem.

Every Define* helper that took an api.Registry as its first argument is gone. A registry is unobtainable outside the framework, so those helpers had no reachable callers; the replacement is to construct the action and return it from Plugin.Init, which is what the framework registers. Every plugin-side replacement below is a constructor, so return its result from Plugin.Init. Applications keep reaching the same primitives through the genkit.Define* functions, which construct and register in one call.

RemovedPlugin-side replacementApp-side equivalent
core.DefineActioncore.NewActionOfNone
core.DefineStreamingActioncore.NewStreamingActionOfNone
core.DefineBidiActioncore.NewBidiActionOfNone
core.DefineBackgroundActioncore.NewBackgroundActionOfNone
core.DefineFlowcore.NewFlowgenkit.DefineFlow
core.DefineStreamingFlowcore.NewStreamingFlowgenkit.DefineStreamingFlow
core.DefineSchemaNonegenkit.DefineSchema
core.DefineSchemaForNonegenkit.DefineSchemaFor / genkit.DefineSchemasFor
ai.DefineModelai.NewModelActiongenkit.DefineModelAction
ai.DefineBackgroundModelai.NewBackgroundModelActiongenkit.DefineBackgroundModelAction
ai.DefineRetrieverai.NewRetrieverActiongenkit.DefineRetrieverAction
ai.DefineEmbedderai.NewEmbedderActiongenkit.DefineEmbedderAction
ai.DefineEvaluatorai.NewEvaluatorActiongenkit.DefineEvaluatorAction
ai.DefineBatchEvaluatorai.NewBatchEvaluatorActiongenkit.DefineBatchEvaluatorAction
ai.DefineToolai.NewToolgenkit.DefineTool
ai.DefineToolWithInputSchemaai.NewTool with ai.WithInputSchemagenkit.DefineTool with ai.WithInputSchema
ai.DefineMultipartToolai.NewMultipartToolgenkit.DefineMultipartTool
ai.DefineResourceai.NewResourcegenkit.DefineResource
ai.DefineMiddlewareai.NewMiddlewaregenkit.DefineMiddleware
ai.DefineFormatai.DefineFormats, which takes the name from each Formattergenkit.DefineFormats
ai/exp.DefineToolai/exp.NewToolgenkit/exp.DefineTool
ai/exp.DefineInterruptibleToolai/exp.NewInterruptibleToolgenkit/exp.DefineInterruptibleTool

ai.DefinePrompt, ai.DefineDataPrompt, ai.DefineFormats, and ai.DefineGenerateAction still take a registry: there the registry is a structural input rather than a registration target.

Package core’s own error envelope went with them. core.ReflectionError, core.ToReflectionError, and core.SchemaValidationError were deleted with no exported replacement, and core.NewSchemaValidationError gave way to status.Errorf with a sentinel from core/status.

The flat core.NewAction, NewStreamingAction, NewBidiAction, and NewBackgroundAction constructors still work and are deprecated in favor of their Of counterparts. They take name before atype, so swapping the first two arguments is the likeliest mistake when moving a call across. The untyped ai.NewModel, NewRetriever, NewEmbedder, NewEvaluator, NewBatchEvaluator, and NewBackgroundModel constructors, and the matching genkit.Define* names, are likewise deprecated in favor of the typed-config family: they resolve Config to any, which infers no schema and passes the raw config through untouched.

Genkit plugins can be published as normal Go packages. To increase discoverability, your package should have genkit somewhere in its name so it can be found with a simple search on pkg.go.dev. Any of the following are good choices:

  • github.com/yourorg/genkit-plugins/servicename
  • github.com/yourorg/your-repo/genkit/servicename