Add generic semantic reconciliation prompt assets
This commit is contained in:
@@ -11,12 +11,14 @@ import (
|
||||
)
|
||||
|
||||
// PromptAssetManifest is the ordered set of assets that make up one prompt.
|
||||
// Module files are addressed in the owning module filesystem; shared files use
|
||||
// the names in sharedPromptPaths and are mounted beneath sharedassets.
|
||||
// Module files are addressed in the owning module filesystem. SharedFiles use
|
||||
// names in sharedPromptPaths, while ExternalSharedFiles are explicit
|
||||
// caller-owned descriptors. Both kinds are mounted beneath sharedassets.
|
||||
type PromptAssetManifest struct {
|
||||
ModuleDir string
|
||||
ModuleFiles []promptfs.ModulePromptFile
|
||||
SharedFiles []string
|
||||
ModuleDir string
|
||||
ModuleFiles []promptfs.ModulePromptFile
|
||||
SharedFiles []string
|
||||
ExternalSharedFiles []promptfs.SharedPromptFile
|
||||
}
|
||||
|
||||
var sharedPromptPaths = map[string]string{
|
||||
@@ -40,7 +42,7 @@ func sharedAssetFS() (fs.FS, error) {
|
||||
}
|
||||
|
||||
func (manifest PromptAssetManifest) PromptFS(moduleFS fs.FS) (fs.FS, error) {
|
||||
sharedFiles, err := resolveSharedPromptFiles(manifest.SharedFiles)
|
||||
sharedFiles, err := manifest.sharedPromptFiles()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -49,10 +51,14 @@ func (manifest PromptAssetManifest) PromptFS(moduleFS fs.FS) (fs.FS, error) {
|
||||
}
|
||||
|
||||
func (manifest PromptAssetManifest) Hash(moduleFS fs.FS) (string, error) {
|
||||
sharedFiles, err := resolveSharedPromptFiles(manifest.SharedFiles)
|
||||
sharedFiles, err := manifest.sharedPromptFiles()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
moduleFiles := append([]promptfs.ModulePromptFile(nil), manifest.ModuleFiles...)
|
||||
if _, err := promptfs.ModulePromptFS(manifest.ModuleDir, moduleFS, moduleFiles, sharedFiles...); err != nil {
|
||||
return "", err
|
||||
}
|
||||
parts := make([]llm.AssetHashPart, 0, len(manifest.ModuleFiles)+len(sharedFiles))
|
||||
for _, file := range manifest.ModuleFiles {
|
||||
parts = append(parts, llm.AssetHashPart{FS: moduleFS, Path: file.Path})
|
||||
@@ -63,6 +69,14 @@ func (manifest PromptAssetManifest) Hash(moduleFS fs.FS) (string, error) {
|
||||
return llm.HashAssets(parts)
|
||||
}
|
||||
|
||||
func (manifest PromptAssetManifest) sharedPromptFiles() ([]promptfs.SharedPromptFile, error) {
|
||||
files, err := resolveSharedPromptFiles(manifest.SharedFiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(files, manifest.ExternalSharedFiles...), nil
|
||||
}
|
||||
|
||||
func resolveSharedPromptFiles(names []string) ([]promptfs.SharedPromptFile, error) {
|
||||
assets, err := sharedAssetFS()
|
||||
if err != nil {
|
||||
|
||||
@@ -207,3 +207,106 @@ func TestSharedPromptDescriptorsReturnFreshCopies(t *testing.T) {
|
||||
t.Fatalf("resolveSharedPromptFiles() reused descriptor state: first=%#v second=%#v", first, second)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromptAssetManifestMountsExternalSharedFiles(t *testing.T) {
|
||||
external := fstest.MapFS{"core/protocol.md": {Data: []byte("integer protocol")}}
|
||||
manifest := PromptAssetManifest{
|
||||
ModuleDir: "dnd.test",
|
||||
ModuleFiles: []promptfs.ModulePromptFile{
|
||||
{Name: "prompt.yaml", Path: "prompts/prompt.yaml"},
|
||||
},
|
||||
ExternalSharedFiles: []promptfs.SharedPromptFile{
|
||||
{Name: "protocol.md", FS: external, Path: "core/protocol.md"},
|
||||
},
|
||||
}
|
||||
fys, err := manifest.PromptFS(fstest.MapFS{
|
||||
"prompts/prompt.yaml": {Data: []byte("id: dnd.test")},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("PromptFS() error = %v, want nil", err)
|
||||
}
|
||||
content, err := fs.ReadFile(fys, "assets/prompts/dnd.test/sharedassets/protocol.md")
|
||||
if err != nil || string(content) != "integer protocol" {
|
||||
t.Fatalf("mounted external protocol = %q, %v", content, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromptAssetManifestHashIncludesExternalSharedFiles(t *testing.T) {
|
||||
moduleFS := fstest.MapFS{"prompts/prompt.yaml": {Data: []byte("id: dnd.test")}}
|
||||
external := fstest.MapFS{"core/protocol.md": {Data: []byte("integer protocol")}}
|
||||
manifest := PromptAssetManifest{
|
||||
ModuleDir: "dnd.test",
|
||||
ModuleFiles: []promptfs.ModulePromptFile{
|
||||
{Name: "prompt.yaml", Path: "prompts/prompt.yaml"},
|
||||
},
|
||||
ExternalSharedFiles: []promptfs.SharedPromptFile{
|
||||
{Name: "protocol.md", FS: external, Path: "core/protocol.md"},
|
||||
},
|
||||
}
|
||||
got, err := manifest.Hash(moduleFS)
|
||||
if err != nil {
|
||||
t.Fatalf("Hash() error = %v, want nil", err)
|
||||
}
|
||||
want, err := llm.HashAssets([]llm.AssetHashPart{
|
||||
{FS: moduleFS, Path: "prompts/prompt.yaml"},
|
||||
{FS: external, Path: "core/protocol.md"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != want {
|
||||
t.Fatalf("Hash() = %q, want external-aware hash %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromptAssetManifestRejectsInvalidExternalSharedFiles(t *testing.T) {
|
||||
moduleFS := fstest.MapFS{"prompts/prompt.yaml": {Data: []byte("id: dnd.test")}}
|
||||
tests := []struct {
|
||||
name string
|
||||
external []promptfs.SharedPromptFile
|
||||
shared []string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "missing",
|
||||
external: []promptfs.SharedPromptFile{
|
||||
{Name: "protocol.md", FS: fstest.MapFS{}, Path: "core/protocol.md"},
|
||||
},
|
||||
want: "read shared prompt asset core/protocol.md",
|
||||
},
|
||||
{
|
||||
name: "duplicate external destinations",
|
||||
external: []promptfs.SharedPromptFile{
|
||||
{Name: "protocol.md", FS: fstest.MapFS{"a.md": {Data: []byte("a")}}, Path: "a.md"},
|
||||
{Name: " protocol.md ", FS: fstest.MapFS{"b.md": {Data: []byte("b")}}, Path: "b.md"},
|
||||
},
|
||||
want: `duplicate sharedassets prompt destination "assets/prompts/dnd.test/sharedassets/protocol.md"`,
|
||||
},
|
||||
{
|
||||
name: "duplicate named and external destinations",
|
||||
shared: []string{"common-dnd-system.md"},
|
||||
external: []promptfs.SharedPromptFile{
|
||||
{Name: "common-dnd-system.md", FS: fstest.MapFS{"system.md": {Data: []byte("external")}}, Path: "system.md"},
|
||||
},
|
||||
want: `duplicate sharedassets prompt destination "assets/prompts/dnd.test/sharedassets/common-dnd-system.md"`,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
manifest := PromptAssetManifest{
|
||||
ModuleDir: "dnd.test",
|
||||
ModuleFiles: []promptfs.ModulePromptFile{
|
||||
{Name: "prompt.yaml", Path: "prompts/prompt.yaml"},
|
||||
},
|
||||
SharedFiles: test.shared,
|
||||
ExternalSharedFiles: test.external,
|
||||
}
|
||||
if _, err := manifest.PromptFS(moduleFS); err == nil || !strings.Contains(err.Error(), test.want) {
|
||||
t.Fatalf("PromptFS() error = %v, want %q", err, test.want)
|
||||
}
|
||||
if _, err := manifest.Hash(moduleFS); err == nil || !strings.Contains(err.Error(), test.want) {
|
||||
t.Fatalf("Hash() error = %v, want %q", err, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user