From 5a6cc1b833df57c109626b3ba77e242503679a25 Mon Sep 17 00:00:00 2001 From: Alex Pascal Date: Sat, 1 Aug 2026 10:09:17 -0700 Subject: [PATCH 1/3] refactor(go)!: replace core.DefineSchemaFor with variadic core.DefineSchemasFor DefineSchemaFor took one type parameter per call, so registering the handful of schemas a .prompt-using app needs meant one statement each. DefineSchemasFor takes values instead of a type parameter and registers any number of them in a single call. Taking values also lets it reject inputs the type-parameter form could not: a map now panics pointing at DefineSchema(name, schema) instead of being registered as a schema named "", and nil or an unnamed type panics rather than dereferencing a nil reflect.Type. core is plugin and internal plumbing, so the old function is removed outright rather than kept as a shim. genkit.DefineSchemaFor, the app-facing touchpoint, is unaffected and stays. --- go/core/core.go | 25 +++++++++++------ go/core/core_test.go | 62 +++++++++++++++++++++++++++++++++++++++-- go/core/doc.go | 4 +-- go/core/example_test.go | 6 ++-- 4 files changed, 80 insertions(+), 17 deletions(-) diff --git a/go/core/core.go b/go/core/core.go index 8ee55231da..e17111175d 100644 --- a/go/core/core.go +++ b/go/core/core.go @@ -42,16 +42,23 @@ func DefineSchema(r api.Registry, name string, schema map[string]any) { r.RegisterSchema(name, schema) } -// DefineSchemaFor defines a named JSON schema derived from a Go type -// and registers it in the registry using the type's name. -func DefineSchemaFor[T any](r api.Registry) { - var v T - t := reflect.TypeOf(v) - for t.Kind() == reflect.Ptr { - t = t.Elem() +// DefineSchemasFor defines named JSON schemas derived from the given values' +// Go types and registers them in the registry, each under its type's name. +// It panics if a value is a map, nil, or of an unnamed type. +func DefineSchemasFor(r api.Registry, values ...any) { + for _, v := range values { + t := reflect.TypeOf(v) + for t != nil && t.Kind() == reflect.Ptr { + t = t.Elem() + } + switch { + case t != nil && t.Kind() == reflect.Map: + panic("core.DefineSchemasFor: got a map; use DefineSchema(name, schema) to register a raw JSON schema") + case t == nil || t.Name() == "": + panic("core.DefineSchemasFor: value must be of a named type; use DefineSchema(name, schema) to name it explicitly") + } + r.RegisterSchema(t.Name(), InferSchemaMap(v)) } - name := t.Name() - r.RegisterSchema(name, InferSchemaMap(v)) } // SchemaRef returns a JSON schema reference map for the given name. diff --git a/go/core/core_test.go b/go/core/core_test.go index 67ee0d912c..bfe8d8a691 100644 --- a/go/core/core_test.go +++ b/go/core/core_test.go @@ -47,7 +47,7 @@ func TestDefineSchema(t *testing.T) { }) } -func TestDefineSchemaFor(t *testing.T) { +func TestDefineSchemasFor(t *testing.T) { t.Run("registers schema derived from Go type", func(t *testing.T) { r := registry.New() @@ -56,7 +56,7 @@ func TestDefineSchemaFor(t *testing.T) { Email string `json:"email"` } - DefineSchemaFor[User](r) + DefineSchemasFor(r, User{}) found := r.LookupSchema("User") if found == nil { @@ -82,13 +82,69 @@ func TestDefineSchemaFor(t *testing.T) { Debug bool `json:"debug"` } - DefineSchemaFor[*Config](r) + DefineSchemasFor(r, &Config{}) found := r.LookupSchema("Config") if found == nil { t.Fatal("schema not found in registry for pointer type") } }) + + t.Run("registers multiple schemas at once", func(t *testing.T) { + r := registry.New() + + type User struct { + Name string `json:"name"` + } + type Order struct { + ID string `json:"id"` + } + + DefineSchemasFor(r, User{}, Order{}) + + if r.LookupSchema("User") == nil { + t.Error("schema User not found in registry") + } + if r.LookupSchema("Order") == nil { + t.Error("schema Order not found in registry") + } + }) + + t.Run("panics on map value", func(t *testing.T) { + r := registry.New() + + defer func() { + if recover() == nil { + t.Error("expected panic for map value") + } + }() + + DefineSchemasFor(r, map[string]any{"type": "object"}) + }) + + t.Run("panics on unnamed type", func(t *testing.T) { + r := registry.New() + + defer func() { + if recover() == nil { + t.Error("expected panic for unnamed type") + } + }() + + DefineSchemasFor(r, struct{ Name string }{}) + }) + + t.Run("panics on nil value", func(t *testing.T) { + r := registry.New() + + defer func() { + if recover() == nil { + t.Error("expected panic for nil value") + } + }() + + DefineSchemasFor(r, nil) + }) } func TestSchemaRef(t *testing.T) { diff --git a/go/core/doc.go b/go/core/doc.go index e4528df7f6..aca636df1e 100644 --- a/go/core/doc.go +++ b/go/core/doc.go @@ -136,8 +136,8 @@ Register JSON schemas for use in prompts and validation: "required": []any{"name"}, }) - // Define a schema from a Go type (recommended) - core.DefineSchemaFor[Person](registry) + // Define schemas from Go types (recommended) + core.DefineSchemasFor(registry, Person{}, Address{}) Schemas can be referenced in .prompt files by name. diff --git a/go/core/example_test.go b/go/core/example_test.go index c6212c3e9d..d3747879db 100644 --- a/go/core/example_test.go +++ b/go/core/example_test.go @@ -116,8 +116,8 @@ func ExampleRun() { // Output: RESULT: HELLO } -// This example demonstrates defining a schema from a Go type. -func ExampleDefineSchemaFor() { +// This example demonstrates defining schemas from Go types. +func ExampleDefineSchemasFor() { r := registry.New() // Define a struct type @@ -127,7 +127,7 @@ func ExampleDefineSchemaFor() { } // Register the schema - core.DefineSchemaFor[Person](r) + core.DefineSchemasFor(r, Person{}) // The schema is now registered and can be referenced in .prompt files fmt.Println("Schema registered") From cf6fc01e15aacf7613827ef3280c97c96f78c80b Mon Sep 17 00:00:00 2001 From: Alex Pascal Date: Sat, 1 Aug 2026 10:09:20 -0700 Subject: [PATCH 2/3] feat(go): add genkit.DefineSchemasFor Registers any number of Go-type schemas in one call: genkit.DefineSchemasFor(g, JokeRequest{}, Joke{}, Recipe{}) genkit.DefineSchemaFor stays as the single-type form for when naming the type reads better than constructing a value of it, and now delegates to core.DefineSchemasFor so both paths register identical schemas. --- go/genkit/doc.go | 2 +- go/genkit/example_test.go | 6 +++--- go/genkit/genkit.go | 32 +++++++++++++++++++++++++++++--- go/genkit/genkit_test.go | 24 ++++++++++++++++++++++-- 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/go/genkit/doc.go b/go/genkit/doc.go index 360f05ff32..452baf7ee7 100644 --- a/go/genkit/doc.go +++ b/go/genkit/doc.go @@ -169,7 +169,7 @@ Load prompts from .prompt files by specifying a prompt directory: When using .prompt files with custom output schemas, register the schema first: - genkit.DefineSchemaFor[Recipe](g) + genkit.DefineSchemasFor(g, Recipe{}) # Tools diff --git a/go/genkit/example_test.go b/go/genkit/example_test.go index 917e8dc49c..b39cd8891c 100644 --- a/go/genkit/example_test.go +++ b/go/genkit/example_test.go @@ -188,8 +188,8 @@ func ExampleDefinePrompt() { // Output: Say hello to Alice in a friendly way. } -// This example demonstrates registering a Go type as a named schema. -func ExampleDefineSchemaFor() { +// This example demonstrates registering Go types as named schemas. +func ExampleDefineSchemasFor() { ctx := context.Background() g := genkit.Init(ctx) @@ -201,7 +201,7 @@ func ExampleDefineSchemaFor() { // Register the schema - this makes it available for .prompt files // that reference it by name (e.g., "output: { schema: Person }") - genkit.DefineSchemaFor[Person](g) + genkit.DefineSchemasFor(g, Person{}) fmt.Println("Schema registered: Person") // Output: Schema registered: Person diff --git a/go/genkit/genkit.go b/go/genkit/genkit.go index 45f4913f34..dfa247610f 100644 --- a/go/genkit/genkit.go +++ b/go/genkit/genkit.go @@ -937,10 +937,35 @@ func DefineSchema(g *Genkit, name string, schema map[string]any) { core.DefineSchema(g.reg, name, schema) } +// DefineSchemasFor defines named JSON schemas derived from the given values' +// Go types and registers them, each under its type's name. +// +// This is an alternative to [DefineSchema] for schemas that mirror existing Go +// types. Applications commonly register several schemas up front for `.prompt` +// files to reference, so it takes one or many in a single call. It panics if a +// value is a map, nil, or of an unnamed type; use [DefineSchema] to register a +// raw JSON schema under an explicit name. +// +// Example: +// +// type User struct { +// Name string `json:"name"` +// Age int `json:"age"` +// } +// +// genkit.DefineSchemasFor(g, User{}, Order{}) +// +// genkit.Generate(ctx, g, ai.WithOutputSchemaName("User"), ai.WithPrompt("What is your name?")) +func DefineSchemasFor(g *Genkit, values ...any) { + core.DefineSchemasFor(g.reg, values...) +} + // DefineSchemaFor defines a named JSON schema derived from a Go type -// and registers it in the registry. +// and registers it under that type's name. // -// This is an alternative to [DefineSchema]. +// It is the single-type form of [DefineSchemasFor], for when naming the type is +// more natural than constructing a value of it. Both register the same schema +// under the same name; prefer [DefineSchemasFor] when registering several. // // Example: // @@ -953,7 +978,8 @@ func DefineSchema(g *Genkit, name string, schema map[string]any) { // // genkit.Generate(ctx, g, ai.WithOutputSchemaName("User"), ai.WithPrompt("What is your name?")) func DefineSchemaFor[T any](g *Genkit) { - core.DefineSchemaFor[T](g.reg) + var v T + core.DefineSchemasFor(g.reg, v) } // DefineDataPrompt creates a new [ai.DataPrompt] with strongly-typed input and output. diff --git a/go/genkit/genkit_test.go b/go/genkit/genkit_test.go index 633fc079d9..deb0867444 100644 --- a/go/genkit/genkit_test.go +++ b/go/genkit/genkit_test.go @@ -68,7 +68,7 @@ func TestDefineSchemaWithType(t *testing.T) { Age int `json:"age,omitempty"` } - DefineSchemaFor[UserInfo](g) + DefineSchemasFor(g, UserInfo{}) schema := g.reg.LookupSchema("UserInfo") if schema == nil { @@ -122,7 +122,27 @@ func TestDefineSchemaWithType_Error(t *testing.T) { Foo func() `json:"foo"` } - DefineSchemaFor[Invalid](g) + DefineSchemasFor(g, Invalid{}) +} + +func TestDefineSchemaFor(t *testing.T) { + g := Init(context.Background()) + + type Legacy struct { + Name string `json:"name"` + } + type LegacyPtr struct { + Name string `json:"name"` + } + + DefineSchemaFor[Legacy](g) + DefineSchemaFor[*LegacyPtr](g) + + for _, name := range []string{"Legacy", "LegacyPtr"} { + if g.reg.LookupSchema(name) == nil { + t.Errorf("Schema %s not found", name) + } + } } func TestWithPromptFS(t *testing.T) { From 987a3b719e700b7a72565db37bd1d635545fb969 Mon Sep 17 00:00:00 2001 From: Alex Pascal Date: Sat, 1 Aug 2026 10:09:23 -0700 Subject: [PATCH 3/3] docs(go): register schemas with DefineSchemasFor in samples and README basic-prompts collapses five DefineSchemaFor statements into one call. --- go/README.md | 5 ++--- go/samples/basic-agents/chef.go | 4 ++-- go/samples/basic-prompts/main.go | 8 ++------ 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/go/README.md b/go/README.md index 1770975933..4b13b3d2f6 100644 --- a/go/README.md +++ b/go/README.md @@ -168,7 +168,7 @@ type ChatInput struct { } // Register the schema so the .prompt file can reference it by name. -genkit.DefineSchemaFor[ChatInput](g) +genkit.DefineSchemasFor(g, ChatInput{}) // Agent "chat" renders ./prompts/chat.prompt every turn (no source option needed). chatAgent := genkitx.DefinePromptAgent(g, "chat", @@ -858,8 +858,7 @@ Dietary restrictions: {{#each dietaryRestrictions}}{{this}}{{#unless @last}}, {{ ```go // Register schemas so .prompt files can reference them by name -genkit.DefineSchemaFor[RecipeRequest](g) -genkit.DefineSchemaFor[Recipe](g) +genkit.DefineSchemasFor(g, RecipeRequest{}, Recipe{}) // Look up and execute the prompt recipePrompt := genkit.LookupDataPrompt[RecipeRequest, *Recipe](g, "recipe") diff --git a/go/samples/basic-agents/chef.go b/go/samples/basic-agents/chef.go index 4406962df1..f24cd7d65c 100644 --- a/go/samples/basic-agents/chef.go +++ b/go/samples/basic-agents/chef.go @@ -21,7 +21,7 @@ import ( ) // ChatPromptInput is the input schema referenced by ./prompts/chef.prompt. -// Registering it via DefineSchemaFor lets the .prompt file refer to it by +// Registering it via DefineSchemasFor lets the .prompt file refer to it by // name in its YAML frontmatter. type ChatPromptInput struct { Personality string `json:"personality"` @@ -44,7 +44,7 @@ func definePromptAgent(g *genkit.Genkit) *aix.Agent[any] { // chef.prompt's frontmatter references ChatPromptInput by name, so the // schema must be registered before DefinePromptAgent renders the prompt // at definition time. - genkit.DefineSchemaFor[ChatPromptInput](g) + genkit.DefineSchemasFor(g, ChatPromptInput{}) return genkitx.DefinePromptAgent(g, name, aix.WithSessionStore(mustStore(name)), aix.WithDescription[any]("Michelin-starred chef (prompt loaded from ./prompts/chef.prompt)"), diff --git a/go/samples/basic-prompts/main.go b/go/samples/basic-prompts/main.go index 3616851a20..b9b77d9910 100644 --- a/go/samples/basic-prompts/main.go +++ b/go/samples/basic-prompts/main.go @@ -110,11 +110,7 @@ func main() { // Define schemas for the expected input and output types so that the Dotprompt files can reference them. // Alternatively, you can specify the JSON schema by hand in the Dotprompt metadata. // Code-defined prompts do not need to have schemas defined in advance but they too can reference them. - genkit.DefineSchemaFor[JokeRequest](g) - genkit.DefineSchemaFor[Joke](g) - genkit.DefineSchemaFor[RecipeRequest](g) - genkit.DefineSchemaFor[Recipe](g) - genkit.DefineSchemaFor[AssistantRequest](g) + genkit.DefineSchemasFor(g, JokeRequest{}, Joke{}, RecipeRequest{}, Recipe{}, AssistantRequest{}) // TODO: Include partials and helpers. @@ -228,7 +224,7 @@ func DefineStructuredJokeWithInlinePrompt(g *genkit.Genkit) { // DefineStructuredJokeWithDotprompt demonstrates LookupDataPrompt to wrap a .prompt file // with Go type information. The .prompt file references registered schemas by name -// (e.g., "schema: Joke"), which must be defined via DefineSchemaFor before loading. +// (e.g., "schema: Joke"), which must be defined via DefineSchemasFor before loading. func DefineStructuredJokeWithDotprompt(g *genkit.Genkit) { genkit.DefineStreamingFlow(g, "structuredJokeDotpromptFlow", func(ctx context.Context, input JokeRequest, sendChunk core.StreamCallback[*Joke]) (*Joke, error) {