feat(go/ai): add typed-config NewTyped* constructors - #5849
Open
apascal07 wants to merge 10 commits into
Open
Conversation
Contributor
There was a problem hiding this comment.
Code Review
This pull request introduces generic WithConfig and WithOptions variants for defining and registering actions (including models, background models, embedders, retrievers, and evaluators) in the Go SDK. This allows the framework to automatically deserialize raw, type-erased configuration/options into strongly-typed Go structs before executing user-defined functions. Additionally, action creation in go/core has been refactored to use a consolidated ActionOptions struct, deprecating the older flat-argument constructors. As there are no review comments provided, I have no additional feedback to offer.
apascal07
force-pushed
the
ap/go-with-config-constructors
branch
4 times, most recently
from
July 29, 2026 22:34
51a1b16 to
70ee46b
Compare
WithConfig constructors and core ActionOptionsNewTyped* constructors and core ActionOptions
apascal07
force-pushed
the
ap/go-with-config-constructors
branch
from
July 30, 2026 16:32
f462550 to
34de461
Compare
NewTyped* constructors and core ActionOptionsNewTyped* constructors
apascal07
force-pushed
the
ap/go-with-config-constructors
branch
from
July 30, 2026 17:32
34de461 to
e16ee2b
Compare
This was referenced Jul 30, 2026
NewModelWithConfig, NewEmbedderWithConfig, NewEvaluatorWithConfig, NewBatchEvaluatorWithConfig, and NewBackgroundModelWithConfig take a Config type parameter inferred from the provided function's signature. The framework validates the request's raw config against the config schema, deserializes it into the typed value (exact type, *Config, or map[string]any; anything else is INVALID_ARGUMENT), normalizes the request's type-erased config slot to the converted value, and passes the typed value to the function. This removes the per-provider configFromRequest boilerplate once plugins migrate. The config JSON schema is inferred from Config unless ConfigSchema overrides it (kept for types whose reflection is insufficient, e.g. SDK wrapper generics like Opt[float64]). The request input schema's config slot accepts an explicit JSON null so typed-nil configs pass input validation. Config resolution runs outermost in the model's built-in chain; version validation moves there from validateSupport because it must see the raw, pre-conversion config. Batch evaluators now advertise an inferred input schema. The untyped New*/Define* constructors are deprecated and delegate with Config=any, which passes the raw config through unchanged (pinned by TestDeprecatedModelPassesConfigThrough). Retrievers are left untouched since the subsystem is slated for removal.
DefineModelWithConfig, DefineBackgroundModelWithConfig, DefineEmbedderWithConfig, DefineEvaluatorWithConfig, and DefineBatchEvaluatorWithConfig wrap the ai New*WithConfig constructors as New* + Register(g.reg). The untyped Define* wrappers are deprecated in favor of them.
NewTypedModel, NewTypedEmbedder, NewTypedEvaluator, NewTypedBatchEvaluator, and NewTypedBackgroundModel name what the variant actually changes: the function receives the request's config as a typed value. Func aliases follow (TypedModelFunc[Config] et al.), and the genkit wrappers become DefineTyped*. These names shipped earlier on this branch and were never released, so this is a rename, not a break.
A registry is unobtainable outside the framework: plugins return actions from Init for the framework to register and applications go through the genkit package, so the registry-taking Define helpers had no reachable external callers. Instead of carrying them through a deprecation cycle, delete them; the genkit wrappers remain the user touchpoints and now compose New* + Register directly. Deleted: ai.DefineModel, ai.DefineEmbedder, ai.DefineEvaluator, ai.DefineBatchEvaluator, ai.DefineBackgroundModel, ai.DefineMiddleware, ai.DefineTool, ai.DefineToolWithInputSchema, ai.DefineMultipartTool, ai.DefineResource, and the experimental exp.DefineTool and exp.DefineInterruptibleTool. Kept, because the registry is a structural input rather than just a registration target: ai.DefinePrompt, ai.DefineDataPrompt, ai.DefineFormat, ai.DefineGenerateAction (init bootstrap), the exp agent constructors, and the frozen retriever surface. Tests use package-local define helpers that compose New* + Register, which also exercises the composition path users are pointed at.
Exports the concrete primitive types as ModelAction, EmbedderAction, EvaluatorAction, and BackgroundModelAction, and returns them from the NewTyped* constructors and the genkit.DefineTyped* wrappers instead of the interfaces. A concrete return lets methods be added without the interface-growth breakage problem, satisfies api.Action structurally so plugin Init bodies can append models without type assertions, and matches the existing Tool/ToolDef pattern. The types embed core.Action through an unexported alias so the action methods promote without exporting the embedded field itself. The interfaces stay as the accepted-argument and lookup types, and the deprecated constructors keep their interface returns.
NewTypedBackgroundModel and genkit.DefineTypedBackgroundModel gain a positional nil-able cancelFn, matching core.NewBackgroundActionOf and the invariant that options structs hold descriptor data only. The shipped BackgroundModelOptions.Cancel field is deprecated and kept as a fallback when cancelFn is nil, so the deprecated constructors and existing option-based callers behave unchanged. Also asserts api.Action on BackgroundModelAction: the plugin path registers Init-returned actions via their Register method, so the overriding Register handles the check and cancel sub-actions correctly.
One parameter per line on the NewTyped* and DefineTyped* family so the signatures render readably on pkg.go.dev, matching the formatting of core.NewBackgroundActionOf.
…structor NewTypedBackgroundModel and genkit.DefineTypedBackgroundModel take a dedicated options struct that inlines the ModelOptions fields instead of embedding them, plus a descriptor Metadata slot. The Metadata field is actually wired into the action descriptor (it was silently ignored before, both here and on main), with the reserved type and model keys winning over caller values. BackgroundModelOptions is deprecated and only feeds the deprecated NewBackgroundModel, which converts and forwards its Cancel field positionally, so the typed path no longer reads implementation functions from options.
Caller-provided metadata merges into the model action's descriptor with the reserved type and model keys winning, matching the background model options. Adding the field is additive; the deprecated constructors pick it up through delegation.
…delOptions Provider-surface rule: required functions stay positional (they are compile-enforced and drive type inference), while optional lifecycle hooks live in the options struct so future additions cost a field instead of a signature break. Cancel is the first such hook. The core constructor keeps its positional cancel because holding CancelOpFunc[Out] would force BackgroundActionOptions to become generic.
apascal07
force-pushed
the
ap/go-with-config-constructors
branch
from
July 31, 2026 22:28
1dbc51b to
d712b81
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Introduces a typed-config constructor family for the primitives as a non-breaking addition for the next minor release. Every new function lives alongside its existing counterpart under a new name (
ai.NewTyped*); the old constructors are deprecated and now delegate to the new ones, so behavior is shared on one code path. Built on #5862, which adds thecore.New*ActionOfoptions-struct constructors these compose over. This is the middle PR of the plugin migration train: once providers move toNewTypedModelet al., their hand-rolledconfigFromRequestmapping and validation can be deleted.Only
New*constructors are added; registration composes via the existingRegistermethods, so no parallelDefine*surface ships. Thegenkit.DefineTyped*wrappers are justNewTyped* + Register(g.reg), and the deprecatedDefine*functions point atNew*+Registeras the replacement.Typed config for primitives
ai.NewTypedModel(and the embedder, evaluator, batch-evaluator, and background-model equivalents) takes aConfigtype parameter inferred from the provided function's signature. The framework validates the request's raw config against the config schema, deserializes it into the typed value (exact type,*Config, ormap[string]any; anything else isINVALID_ARGUMENT), normalizes the request's type-erased config slot to the converted value, and passes the typed value to the function.Before: every provider hand-rolls config mapping, and gets no validation for it.
After: the framework validates, deserializes, and hands over the typed value.
ConfigunlessConfigSchemaon the options struct overrides it. The override remains the escape hatch for types whose reflection is insufficient (e.g. SDK wrapper generics likeOpt[float64]); those plugins use the new functions and supply the schema explicitly.anyOf [schema, null]so a typed-nil config (which marshals to JSONnull) passes input validation.normalizeConfig), so the built-in wrappers and the model function all see the typed value. Version validation moved there fromvalidateSupportbecause it must run on the raw config: conversion into aConfigtype without aversionfield would silently drop the key.ModelOptionsgains aMetadataslot merged into the action descriptor (reservedtype/modelkeys win), matchingTypedBackgroundModelOptions.Concrete return types
The typed constructors and the
genkit.DefineTyped*wrappers return newly exported concrete types instead of the interfaces:*ModelAction,*EmbedderAction,*EvaluatorAction, and*BackgroundModelAction. This matches the existingNewToolreturning*ToolDef, lets methods be added later without the interface-growth breakage problem, and satisfiesapi.Actionstructurally, so pluginInitbodies can append models to their returned action slice without type assertions.Each type embeds
core.Action(orcore.BackgroundAction) through an unexported package-level alias, which promotes the action methods (Desc,Register,RunJSON, ...) without exporting the embedded field itself. The interfaces (Model,Embedder,Evaluator,BackgroundModel) are unchanged and remain the accepted-argument and lookup types; the concrete types satisfy them, and the deprecated constructors keep their interface returns.NewTypedBackgroundModel(and its genkit wrapper) takes a dedicated flatTypedBackgroundModelOptionsthat inlines theModelOptionsfields, a descriptorMetadataslot (now actually wired into the descriptor; it was silently ignored before), and the optionalCancelhook. The rule on the provider surface: required functions are positional (compile-enforced, drive type inference), optional lifecycle hooks live in the options struct so future additions are a field rather than a signature break.core.NewBackgroundActionOfkeeps its positional cancel because holding a genericCancelOpFunc[Out]would force the core options struct to become generic. The shippedBackgroundModelOptionsis deprecated and only feeds the deprecatedNewBackgroundModel.Registry-taking Define helpers deleted
Continuing the cleanup from #5862 into the
ailayer: a registry is unobtainable outside the framework, so the registry-takingDefine*helpers had no reachable external callers and are deleted outright rather than deprecated; the genkit wrappers remain the user touchpoints and now composeNew* + Registerdirectly.DefineModel,DefineEmbedder,DefineEvaluator,DefineBatchEvaluator,DefineBackgroundModel,DefineMiddleware,DefineTool,DefineToolWithInputSchema,DefineMultipartTool,DefineResource(plus the experimentalexp.DefineTool/exp.DefineInterruptibleTool)ai.DefinePrompt/DefineDataPrompt,ai.DefineFormat(replacement rides with the format-registration key fix),ai.DefineGenerateAction(init bootstrap), the exp agent constructors, and the frozen retriever surface.Deprecations
Deprecated, delegating to the new functions with identical behavior (
Config=anypasses the raw config through unchanged, pinned by test):ai:NewModel,NewEmbedder,NewEvaluator,NewBatchEvaluator,NewBackgroundModel(pointing atNewTyped*)genkit:DefineModel,DefineBackgroundModel,DefineEmbedder,DefineEvaluator,DefineBatchEvaluator(each gains aDefineTyped*counterpart)When the deprecated functions are removed in the next major, the new ones can collapse back to the plain names with the same signatures, so migrating now is the last rename.
Behavior notes
Intentional changes observable through the deprecated constructors:
versionerror now surfaces before capability errors (version validation runs outermost instead of insidevalidateSupport).BackgroundModelOptions.Metadatanow reaches the action descriptor through the deprecated constructors (it was silently dropped before).ConfigSchemais set, the request input schema's config slot is the null-tolerantanyOfwrapping instead of the raw schema.Scope
Define*counterpart for the typed constructors at theailayer. Registration is oneRegistercall, so aDefineTyped*there would only shadow the genkit wrapper that already exists for applications.Verification
Go 1.26.3, full
go build ./...andgo test ./...ingo/: 40 packages with tests pass, no failures.New coverage in
ai/config_test.go: a typed model receives its config deserialized from each accepted shape and rejects the rest withINVALID_ARGUMENT; the config is normalized before the built-in wrappers run, so they see the typed value; the config schema is inferred fromConfig, overridden by an explicitConfigSchema, and absent forConfig=any; the deprecated constructors pass the raw config through unchanged; andMetadatareaches the descriptor for both the typed model and the typed background model.ai/define_test.go,ai/exp/define_test.go, andplugins/middleware/define_test.gohold test-localNew* + Registerhelpers standing in for the deleted registry-takingDefine*.