Skip to content

Commit 8a73c23

Browse files
committed
feat(go): support using a custom filesystem to load prompts from #3746
1 parent 8e1c4c9 commit 8a73c23

File tree

5 files changed

+69
-5
lines changed

5 files changed

+69
-5
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
model: test-model
3+
maxTurns: 5
4+
description: A test prompt
5+
toolChoice: required
6+
returnToolRequests: true
7+
input:
8+
schema:
9+
type: object
10+
properties:
11+
name:
12+
type: string
13+
default:
14+
name: world
15+
output:
16+
format: text
17+
schema:
18+
type: string
19+
---
20+
Hello, {{name}}!

go/ai/prompt.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"context"
1919
"errors"
2020
"fmt"
21+
"io/fs"
2122
"log/slog"
2223
"maps"
2324
"os"
@@ -505,12 +506,17 @@ func LoadPromptDir(r api.Registry, dir string, namespace string) {
505506
return
506507
}
507508

508-
loadPromptDir(r, path, namespace)
509+
loadPromptDir(r, os.DirFS(dir), dir, namespace)
510+
}
511+
512+
// LoadPromptFS loads prompts and partials from the given filesystem for the given namespace.
513+
func LoadPromptFS(r api.Registry, fsys fs.FS, dir string, namespace string) {
514+
loadPromptDir(r, fsys, dir, namespace)
509515
}
510516

511517
// loadPromptDir recursively loads prompts and partials from the directory.
512-
func loadPromptDir(r api.Registry, dir string, namespace string) {
513-
entries, err := os.ReadDir(dir)
518+
func loadPromptDir(r api.Registry, fsys fs.FS, dir, namespace string) {
519+
entries, err := fs.ReadDir(fsys, dir)
514520
if err != nil {
515521
panic(fmt.Errorf("failed to read prompt directory structure: %w", err))
516522
}
@@ -519,7 +525,7 @@ func loadPromptDir(r api.Registry, dir string, namespace string) {
519525
filename := entry.Name()
520526
path := filepath.Join(dir, filename)
521527
if entry.IsDir() {
522-
loadPromptDir(r, path, namespace)
528+
loadPromptDir(r, fsys, path, namespace)
523529
} else if strings.HasSuffix(filename, ".prompt") {
524530
if strings.HasPrefix(filename, "_") {
525531
partialName := strings.TrimSuffix(filename[1:], ".prompt")

go/ai/prompt_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package ai
1616

1717
import (
1818
"context"
19+
"embed"
1920
"fmt"
2021
"os"
2122
"path/filepath"
@@ -29,6 +30,9 @@ import (
2930
"github.com/google/go-cmp/cmp/cmpopts"
3031
)
3132

33+
//go:embed _test_data/prompts
34+
var embededPrompts embed.FS
35+
3236
type InputOutput struct {
3337
Text string `json:"text"`
3438
}
@@ -877,6 +881,15 @@ func assertResponse(t *testing.T, resp *ModelResponse, want string) {
877881
}
878882
}
879883

884+
func TestLoadPrompt_FromFS(t *testing.T) {
885+
reg := registry.New()
886+
LoadPromptFS(reg, embededPrompts, "_test_data/prompts", "test-namespace")
887+
prompt := LookupPrompt(reg, "test-namespace/example")
888+
if prompt == nil {
889+
t.Fatalf("Prompt was not registered")
890+
}
891+
}
892+
880893
func TestLoadPrompt(t *testing.T) {
881894
// Create a temporary directory for testing
882895
tempDir := t.TempDir()

go/genkit/genkit.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"context"
2222
"errors"
2323
"fmt"
24+
"io/fs"
2425
"log/slog"
2526
"os"
2627
"os/signal"
@@ -46,6 +47,7 @@ type Genkit struct {
4647
type genkitOptions struct {
4748
DefaultModel string // Default model to use if no other model is specified.
4849
PromptDir string // Directory where dotprompts are stored. Will be loaded automatically on initialization.
50+
PromptFS fs.FS // Filesystem that will be used for PromptDir lookup.
4951
Plugins []api.Plugin // Plugin to initialize automatically.
5052
}
5153

@@ -69,6 +71,13 @@ func (o *genkitOptions) apply(gOpts *genkitOptions) error {
6971
gOpts.PromptDir = o.PromptDir
7072
}
7173

74+
if o.PromptFS != nil {
75+
if gOpts.PromptFS != nil {
76+
return errors.New("cannot set prompt filesystem more than once (WithPromptFS)")
77+
}
78+
gOpts.PromptFS = o.PromptFS
79+
}
80+
7281
if len(o.Plugins) > 0 {
7382
if gOpts.Plugins != nil {
7483
return errors.New("cannot set plugins more than once (WithPlugins)")
@@ -106,6 +115,12 @@ func WithPromptDir(dir string) GenkitOption {
106115
return &genkitOptions{PromptDir: dir}
107116
}
108117

118+
// WithPromptFS is a more generic version of `WithPromptDir` and accepts a filesytem
119+
// instead of directory path
120+
func WithPromptFS(fsys fs.FS) GenkitOption {
121+
return &genkitOptions{PromptFS: fsys}
122+
}
123+
109124
// Init creates and initializes a new [Genkit] instance with the provided options.
110125
// It sets up the registry, initializes plugins ([WithPlugins]), loads prompts
111126
// ([WithPromptDir]), and configures other settings like the default model
@@ -184,7 +199,11 @@ func Init(ctx context.Context, opts ...GenkitOption) *Genkit {
184199

185200
ai.ConfigureFormats(r)
186201
ai.DefineGenerateAction(ctx, r)
187-
ai.LoadPromptDir(r, gOpts.PromptDir, "")
202+
if gOpts.PromptFS == nil {
203+
ai.LoadPromptDir(r, gOpts.PromptDir, "")
204+
} else {
205+
ai.LoadPromptFS(r, gOpts.PromptFS, gOpts.PromptDir, "")
206+
}
188207

189208
r.RegisterValue(api.DefaultModelKey, gOpts.DefaultModel)
190209
r.RegisterValue(api.PromptDirKey, gOpts.PromptDir)

go/samples/prompts-dir/main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,25 @@ package main
77
import (
88
"context"
99
"errors"
10+
"embed"
1011

1112
// Import Genkit and the Google AI plugin
1213
"github.com/firebase/genkit/go/ai"
1314
"github.com/firebase/genkit/go/genkit"
1415
"github.com/firebase/genkit/go/plugins/googlegenai"
1516
)
1617

18+
//go:embed prompts
19+
var prompts embed.FS
20+
1721
func main() {
1822
ctx := context.Background()
1923

2024
g := genkit.Init(ctx,
2125
genkit.WithPlugins(&googlegenai.GoogleAI{}),
2226
genkit.WithPromptDir("prompts"),
27+
// Without it OS's filesystem will be used
28+
genkit.WithPromptFS(prompts),
2329
)
2430

2531
type greetingStyle struct {

0 commit comments

Comments
 (0)