Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 2 additions & 3 deletions go/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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")
Expand Down
25 changes: 16 additions & 9 deletions go/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
62 changes: 59 additions & 3 deletions go/core/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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 {
Expand All @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions go/core/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
6 changes: 3 additions & 3 deletions go/core/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion go/genkit/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions go/genkit/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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
Expand Down
32 changes: 29 additions & 3 deletions go/genkit/genkit.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
//
Expand All @@ -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.
Expand Down
24 changes: 22 additions & 2 deletions go/genkit/genkit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions go/samples/basic-agents/chef.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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)"),
Expand Down
8 changes: 2 additions & 6 deletions go/samples/basic-prompts/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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) {
Expand Down
Loading