Skip to content
Closed
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
4 changes: 2 additions & 2 deletions go/ai/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func (ga *generatorAction) Generate(ctx context.Context, req *GenerateRequest, c

// conformOutput appends a message to the request indicating conformance to the expected schema.
func conformOutput(req *GenerateRequest) error {
if req.Output.Format == OutputFormatJSON && len(req.Messages) > 0 {
if req.Output != nil && req.Output.Format == OutputFormatJSON && len(req.Messages) > 0 {
jsonBytes, err := json.Marshal(req.Output.Schema)
if err != nil {
return fmt.Errorf("expected schema is not valid: %w", err)
Expand Down Expand Up @@ -193,7 +193,7 @@ func validCandidates(ctx context.Context, resp *GenerateResponse) ([]*Candidate,
// validCandidate will validate the candidate's response against the expected schema.
// It will return an error if it does not match, otherwise it will return a candidate with JSON content and type.
func validCandidate(c *Candidate, output *GenerateRequestOutput) (*Candidate, error) {
if output.Format == OutputFormatJSON {
if output != nil && output.Format == OutputFormatJSON {
text, err := c.Text()
if err != nil {
return nil, err
Expand Down
123 changes: 123 additions & 0 deletions go/samples/menu/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"context"
"fmt"
"log"
"os"

"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/genkit"
"github.com/firebase/genkit/go/plugins/localvec"
"github.com/firebase/genkit/go/plugins/vertexai"
"github.com/invopop/jsonschema"
)

const geminiPro = "gemini-1.0-pro"

// menuItem is the data model for an item on the menu.
type menuItem struct {
Title string `json:"title" jsonschema_description:"The name of the menu item"`
Description string `json:"description" jsonschema_description:"Details including ingredients and preparation"`
Price float64 `json:"price" jsonschema_description:"Price in dollars"`
}

// menuQuestionInput is a question about the menu.
type menuQuestionInput struct {
Question string `json:"question"`
}

// menuQuestionInputSchema is the JSON schema for a menuQuestionInput.
var menuQuestionInputSchema = jsonschema.Reflect(menuQuestionInput{})

// answerOutput is an answer to a question.
type answerOutput struct {
Answer string `json:"answer"`
}

// dataMenuQuestionInput is a question about the menu,
// where the menu is provided in the JSON data.
type dataMenuQuestionInput struct {
MenuData []*menuItem `json:"menuData"`
Question string `json:"question"`
}

// dataMenuQuestionInputSchema is the JSON schema for a dataMenuQuestionInput.
var dataMenuQuestionInputSchema = jsonschema.Reflect(dataMenuQuestionInput{})

// textMenuQuestionInput is for a question about the menu,
// where the menu is provided as unstructured text.
type textMenuQuestionInput struct {
MenuText string `json:"menuText"`
Question string `json:"question"`
}

// textMenuQuestionInputSchema is the JSON schema for a textMenuQuestionInput.
var textMenuQuestionInputSchema = jsonschema.Reflect(textMenuQuestionInput{})

func main() {
projectID := os.Getenv("GCLOUD_PROJECT")
if projectID == "" {
fmt.Fprintln(os.Stderr, "menu example requires setting GCLOUD_PROJECT in the environment.")
os.Exit(1)
}

location := "us-central1"
if envLocation := os.Getenv("GCLOUD_LOCATION"); envLocation != "" {
location = envLocation
}

if err := vertexai.Init(context.Background(), geminiPro, projectID, location); err != nil {
log.Fatal(err)
}

ctx := context.Background()
if err := setup01(ctx); err != nil {
log.Fatal(err)
}
if err := setup02(ctx); err != nil {
log.Fatal(err)
}

generator, err := ai.LookupGeneratorAction("google-vertexai", geminiPro)
if err != nil {
log.Fatal(err)
}
if err := setup03(ctx, generator); err != nil {
log.Fatal(err)
}

embedder, err := vertexai.NewEmbedder(ctx, "textembedding-gecko", projectID, location)
if err != nil {
log.Fatal(err)
}
ds, err := localvec.New(ctx, os.TempDir(), "go-menu-items", embedder, nil)
if err != nil {
log.Fatal(err)
}
if err := setup04(ctx, ds); err != nil {
log.Fatal(err)
}

if err := setup05(ctx); err != nil {
log.Fatal(err)
}

if err := genkit.StartFlowServer(""); err != nil {
log.Fatal(err)
}
}
80 changes: 80 additions & 0 deletions go/samples/menu/s01.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"context"

"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/plugins/dotprompt"
)

func setup01(ctx context.Context) error {
_, err := dotprompt.Define("s01_vanillaPrompt",
`You are acting as a helpful AI assistant named "Walt" that can answer
questions about the food available on the menu at Walt's Burgers.
Customer says: ${input.question}`,
&dotprompt.Config{
Model: "google-vertexai/gemini-1.0-pro",
InputSchema: menuQuestionInputSchema,
},
)
if err != nil {
return err
}

_, err = dotprompt.Define("s01_staticMenuDotPrompt",
`You are acting as a helpful AI assistant named "Walt" that can answer
questions about the food available on the menu at Walt's Burgers.
Here is today's menu:

- The Regular Burger $12
The classic charbroiled to perfection with your choice of cheese

- The Fancy Burger $13
Classic burger topped with bacon & Blue Cheese

- The Bacon Burger $13
Bacon cheeseburger with your choice of cheese.

- Everything Burger $14
Heinz 57 sauce, American cheese, bacon, fried egg & crispy onion bits

- Chicken Breast Sandwich $12
Tender juicy chicken breast on a brioche roll.
Grilled, blackened, or fried

Our fresh 1/2 lb. beef patties are made using choice cut
brisket, short rib & sirloin. Served on a toasted
brioche roll with chips. Served with lettuce, tomato & pickles.
Onions upon request. Substitute veggie patty $2

Answer this customer's question, in a concise and helpful manner,
as long as it is about food.

Question:
{{question}} ?`,
&dotprompt.Config{
Model: "google-vertexai/gemini-1.0-pro",
InputSchema: menuQuestionInputSchema,
OutputFormat: ai.OutputFormatText,
},
)
if err != nil {
return err
}

return nil
}
97 changes: 97 additions & 0 deletions go/samples/menu/s02.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"context"
"encoding/json"
"fmt"
"os"

"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/genkit"
"github.com/firebase/genkit/go/plugins/dotprompt"
)

var menuToolDef = &ai.ToolDefinition{
Name: "todaysMenu",
OutputSchema: map[string]any{
"menuData": []menuItem{},
},
Description: "Use this tool to retrieve all the items on today's menu",
}

func menu(ctx context.Context, input map[string]any) (map[string]any, error) {
f, err := os.Open("testdata/menu.json")
if err != nil {
return nil, err
}
decoder := json.NewDecoder(f)
var s []any
if err := decoder.Decode(&s); err != nil {
return nil, err
}
return map[string]any{"menu": s}, nil
}

func setup02(ctx context.Context) error {
ai.RegisterTool("menu", menuToolDef, nil, menu)

dataMenuPrompt, err := dotprompt.Define("s02_dataMenu",
`You are acting as a helpful AI assistant named Walt that can answer
questions about the food available on the menu at Walt's Burgers.

Answer this customer's question, in a concise and helpful manner,
as long as it is about food on the menu or something harmless like sports.
Use the tools available to answer menu questions.
DO NOT INVENT ITEMS NOT ON THE MENU.

Question:
{{question}} ?`,
&dotprompt.Config{
Model: "google-vertexai/gemini-1.0-pro",
InputSchema: menuQuestionInputSchema,
OutputFormat: ai.OutputFormatText,
Tools: []*ai.ToolDefinition{
menuToolDef,
},
},
)
if err != nil {
return err
}

genkit.DefineFlow("s02_menuQuestion",
func(ctx context.Context, input *menuQuestionInput, _ genkit.NoStream) (*answerOutput, error) {
resp, err := dataMenuPrompt.Generate(ctx,
&ai.PromptRequest{
Variables: input,
},
nil,
)
if err != nil {
return nil, err
}

text, err := resp.Text()
if err != nil {
return nil, fmt.Errorf("s02MenuQuestionFlow: %v", err)
}
return &answerOutput{Answer: text}, nil
},
)

return nil
}
Loading