Replace the Scriptorium adapter with PromptKit
This commit is contained in:
@@ -51,7 +51,7 @@ catalog used for resolution and the concrete constructors used for preparation.
|
|||||||
Tests may provide a catalog or registries instead; production code must not
|
Tests may provide a catalog or registries instead; production code must not
|
||||||
silently merge an injected partial catalog with production registrations.
|
silently merge an injected partial catalog with production registrations.
|
||||||
|
|
||||||
The production LLM factory builds the Scriptorium-backed client from resolved
|
The production LLM factory builds the PromptKit-backed client from resolved
|
||||||
configuration, creates one scheduler from the effective global LLM limit, and
|
configuration, creates one scheduler from the effective global LLM limit, and
|
||||||
wraps the client before it reaches modules. Registration and LLM construction
|
wraps the client before it reaches modules. Registration and LLM construction
|
||||||
errors are returned before a pipeline is prepared. Concrete module keys and
|
errors are returned before a pipeline is prepared. Concrete module keys and
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# LLM Runtime Internals
|
# LLM Runtime Internals
|
||||||
|
|
||||||
`internal/framework/llm` is Notarius’s provider-independent structured
|
`internal/framework/llm` is Notarius’s provider-independent structured
|
||||||
completion boundary. It adapts framework requests to Scriptorium, bounds
|
completion boundary. It adapts framework requests to PromptKit, bounds
|
||||||
provider calls, assembles registered prompt and schema assets, records selected
|
provider calls, assembles registered prompt and schema assets, records selected
|
||||||
profiles, and redacts provider errors. The architectural boundary is defined in
|
profiles, and redacts provider errors. The architectural boundary is defined in
|
||||||
[Architecture](../policy/architecture.md#llm-boundary); profile sources,
|
[Architecture](../policy/architecture.md#llm-boundary); profile sources,
|
||||||
@@ -24,22 +24,22 @@ adapter does not own source evidence, artifact conversion, normalization, or
|
|||||||
durable schemas. Those responsibilities remain with the module and its
|
durable schemas. Those responsibilities remain with the module and its
|
||||||
[integration contract](../integrations/).
|
[integration contract](../integrations/).
|
||||||
|
|
||||||
`ScriptoriumClient` validates the request target and prompt identity, maps each
|
`PromptKitClient` validates the request target and prompt identity, maps each
|
||||||
named material to a Scriptorium inline artifact while preserving its origin URI,
|
named material to a PromptKit inline artifact while preserving its origin URI,
|
||||||
forwards session and profile selection, then prepares and runs the prompt. It
|
forwards session and profile selection, then prepares and runs the prompt. It
|
||||||
returns Scriptorium’s validated raw bytes rather than re-encoding the decoded
|
returns PromptKit’s validated raw bytes rather than re-encoding the decoded
|
||||||
target. An empty optional material is represented as one space so its named
|
target. An empty optional material is represented as one space so its named
|
||||||
input is retained by Scriptorium.
|
input is retained by PromptKit.
|
||||||
|
|
||||||
An empty request profile lets the prompt select its configured default. The CLI
|
An empty request profile lets the prompt select its configured default. The CLI
|
||||||
prepares every explicitly selected binding profile before a run begins, so a
|
prepares every explicitly selected binding profile before a run begins, so a
|
||||||
missing explicit profile fails before stage execution. Calls record the profile
|
missing explicit profile fails before stage execution. Calls record the profile
|
||||||
actually selected by Scriptorium; the recorder deduplicates non-secret profile
|
actually selected by PromptKit; the recorder deduplicates non-secret profile
|
||||||
identity, provider, and model values for manifest use.
|
identity, provider, and model values for manifest use.
|
||||||
|
|
||||||
## Shared Provider-Call Limit
|
## Shared Provider-Call Limit
|
||||||
|
|
||||||
Production construction creates one Scriptorium client and wraps it in one
|
Production construction creates one PromptKit client and wraps it in one
|
||||||
scheduled client. The scheduler has a fixed, positive permit limit, serves
|
scheduled client. The scheduler has a fixed, positive permit limit, serves
|
||||||
queued calls in FIFO order, and removes a queued call when its context is
|
queued calls in FIFO order, and removes a queued call when its context is
|
||||||
cancelled. A granted permit is released exactly once on every completion path.
|
cancelled. A granted permit is released exactly once on every completion path.
|
||||||
@@ -54,7 +54,7 @@ and its effective default are owned by
|
|||||||
## Prompt And Schema Assets
|
## Prompt And Schema Assets
|
||||||
|
|
||||||
An `AssetRegistry` collects prompt and schema filesystems from production module
|
An `AssetRegistry` collects prompt and schema filesystems from production module
|
||||||
families. It flattens registered roots into the Scriptorium filesystems and
|
families. It flattens registered roots into the PromptKit filesystems and
|
||||||
rejects invalid roots, unreadable assets, duplicate paths, and missing prompt
|
rejects invalid roots, unreadable assets, duplicate paths, and missing prompt
|
||||||
or schema files during preparation. The framework’s `promptfs` helper combines
|
or schema files during preparation. The framework’s `promptfs` helper combines
|
||||||
module-owned prompt files with reusable domain fragments without making the
|
module-owned prompt files with reusable domain fragments without making the
|
||||||
@@ -95,14 +95,14 @@ meaningful input placement and cache controls of the prompt being changed.
|
|||||||
|
|
||||||
## Validation, Repair, And Retries
|
## Validation, Repair, And Retries
|
||||||
|
|
||||||
Scriptorium performs prompt rendering, provider execution, and the prompt’s
|
PromptKit performs prompt rendering, provider execution, and the prompt’s
|
||||||
structured-output validation. The adapter reports an empty result, validation
|
structured-output validation. The adapter reports an empty result, validation
|
||||||
failure, empty structured body, or decode failure as
|
failure, empty structured body, or decode failure as
|
||||||
`ErrInvalidStructuredOutput`, while retaining the returned raw bytes and debug
|
`ErrInvalidStructuredOutput`, while retaining the returned raw bytes and debug
|
||||||
material when they exist. Provider failures remain operational errors rather
|
material when they exist. Provider failures remain operational errors rather
|
||||||
than output-validation failures.
|
than output-validation failures.
|
||||||
|
|
||||||
Prompt-declared repair is executed within Scriptorium’s structured-output flow.
|
Prompt-declared repair is executed within PromptKit’s structured-output flow.
|
||||||
The current production D&D prompt manifests set repair attempts to zero. That
|
The current production D&D prompt manifests set repair attempts to zero. That
|
||||||
setting does not replace pipeline retry behavior: a binding’s configured retry
|
setting does not replace pipeline retry behavior: a binding’s configured retry
|
||||||
count reruns its stage attempt after an error or rejection, and an exhausted
|
count reruns its stage attempt after an error or rejection, and an exhausted
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Planned.
|
In progress. The dependency and framework adapter replacement is implemented;
|
||||||
|
the configuration, module asset terminology, and remaining provenance alignment
|
||||||
|
are still planned.
|
||||||
|
|
||||||
## Objective
|
## Objective
|
||||||
|
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -3,7 +3,7 @@ module gitea.maximumdirect.net/eric/notarius
|
|||||||
go 1.25.5
|
go 1.25.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gitea.maximumdirect.net/eric/scriptorium v0.11.1
|
gitea.maximumdirect.net/eric/promptkit v0.1.0
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -1,13 +1,9 @@
|
|||||||
gitea.maximumdirect.net/eric/scriptorium v0.11.0 h1:rjvbt9FTaWHxYlHq7QlUzmMVUt3QdbTmeCkmH81N//o=
|
gitea.maximumdirect.net/eric/promptkit v0.1.0 h1:vuKeBxkiY8E54LRFbLQFjlJJCiOfMvB1++DYBCrD/ug=
|
||||||
gitea.maximumdirect.net/eric/scriptorium v0.11.0/go.mod h1:FQ5lEuNxmrQyNgIomkpZdxvfTC0jWjbXYuq3tbJWF64=
|
gitea.maximumdirect.net/eric/promptkit v0.1.0/go.mod h1:R95NM6fbMDGDC0/UomgnSBP6ui2ns+8SZb8bESNvrDQ=
|
||||||
gitea.maximumdirect.net/eric/scriptorium v0.11.1 h1:zBKtB3+fP8FcHGI8DJD99CiTL6crAGitBhWtE+xYJHc=
|
|
||||||
gitea.maximumdirect.net/eric/scriptorium v0.11.1/go.mod h1:FQ5lEuNxmrQyNgIomkpZdxvfTC0jWjbXYuq3tbJWF64=
|
|
||||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
|
||||||
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||||
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
|||||||
@@ -168,14 +168,14 @@ func buildProductionLLMClient(ctx context.Context, cfg config.Config, profileID
|
|||||||
return nil, nil, fmt.Errorf("production asset registry must not be nil")
|
return nil, nil, fmt.Errorf("production asset registry must not be nil")
|
||||||
}
|
}
|
||||||
recorder := llm.NewLLMProfileRecorder()
|
recorder := llm.NewLLMProfileRecorder()
|
||||||
client, err := llm.NewScriptoriumClient(llm.ScriptoriumClientConfig{
|
client, err := llm.NewPromptKitClient(llm.PromptKitClientConfig{
|
||||||
ProfileDir: cfg.Scriptorium.ProfileDir,
|
ProfileDir: cfg.Scriptorium.ProfileDir,
|
||||||
ProfileFile: cfg.Scriptorium.ProfileFile,
|
ProfileFile: cfg.Scriptorium.ProfileFile,
|
||||||
Assets: assets,
|
Assets: assets,
|
||||||
Recorder: recorder,
|
Recorder: recorder,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("create Scriptorium-backed LLM client: %w", err)
|
return nil, nil, fmt.Errorf("create PromptKit-backed LLM client: %w", err)
|
||||||
}
|
}
|
||||||
scheduler, err := llm.NewScheduler(cfg.Concurrency.TotalLLM)
|
scheduler, err := llm.NewScheduler(cfg.Concurrency.TotalLLM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"testing/fstest"
|
"testing/fstest"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/core/config"
|
"gitea.maximumdirect.net/eric/notarius/internal/core/config"
|
||||||
"gitea.maximumdirect.net/eric/scriptorium"
|
"gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
const profileCheckPromptID = "notarius.profile.check"
|
const profileCheckPromptID = "notarius.profile.check"
|
||||||
@@ -38,14 +38,14 @@ func validateExplicitScriptoriumProfiles(ctx context.Context, cfg config.Config,
|
|||||||
return fmt.Errorf("load Scriptorium profiles: %w", err)
|
return fmt.Errorf("load Scriptorium profiles: %w", err)
|
||||||
}
|
}
|
||||||
for _, profileID := range profileIDs {
|
for _, profileID := range profileIDs {
|
||||||
if _, err := engine.Prepare(ctx, scriptorium.RunRequest{
|
if _, err := engine.Prepare(ctx, promptkit.RunRequest{
|
||||||
PromptID: profileCheckPromptID,
|
PromptID: profileCheckPromptID,
|
||||||
ProfileID: profileID,
|
ProfileID: profileID,
|
||||||
Inputs: map[string]scriptorium.ArtifactRef{
|
Inputs: map[string]promptkit.ArtifactRef{
|
||||||
"transcript": scriptorium.Inline("profile check"),
|
"transcript": promptkit.Inline("profile check"),
|
||||||
},
|
},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
if errors.Is(err, scriptorium.ErrProfileNotFound) {
|
if errors.Is(err, promptkit.ErrProfileNotFound) {
|
||||||
return fmt.Errorf("Scriptorium profile %q is not configured", profileID)
|
return fmt.Errorf("Scriptorium profile %q is not configured", profileID)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("validate Scriptorium profile %q: %w", profileID, err)
|
return fmt.Errorf("validate Scriptorium profile %q: %w", profileID, err)
|
||||||
@@ -54,14 +54,14 @@ func validateExplicitScriptoriumProfiles(ctx context.Context, cfg config.Config,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newProfileValidationEngine(cfg config.Config) (*scriptorium.Engine, error) {
|
func newProfileValidationEngine(cfg config.Config) (*promptkit.Engine, error) {
|
||||||
opts := []scriptorium.Option{
|
opts := []promptkit.Option{
|
||||||
scriptorium.WithPromptFS(profileCheckPromptFS, "prompts"),
|
promptkit.WithPromptFS(profileCheckPromptFS, "prompts"),
|
||||||
}
|
}
|
||||||
if cfg.Scriptorium.ProfileFile != "" {
|
if cfg.Scriptorium.ProfileFile != "" {
|
||||||
opts = append(opts, scriptorium.WithProfileFile(cfg.Scriptorium.ProfileFile))
|
opts = append(opts, promptkit.WithProfileFile(cfg.Scriptorium.ProfileFile))
|
||||||
}
|
}
|
||||||
return scriptorium.NewEngine(scriptorium.Config{
|
return promptkit.NewEngine(promptkit.Config{
|
||||||
PromptDir: "unused",
|
PromptDir: "unused",
|
||||||
ProfileDir: cfg.Scriptorium.ProfileDir,
|
ProfileDir: cfg.Scriptorium.ProfileDir,
|
||||||
}, opts...)
|
}, opts...)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/scriptorium"
|
"gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AssetSource struct {
|
type AssetSource struct {
|
||||||
@@ -72,7 +72,7 @@ func (r *AssetRegistry) SchemaFS() (fs.FS, error) {
|
|||||||
return flattenAssetSources(r.schemas)
|
return flattenAssetSources(r.schemas)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *AssetRegistry) ScriptoriumOptions() ([]scriptorium.Option, error) {
|
func (r *AssetRegistry) PromptKitOptions() ([]promptkit.Option, error) {
|
||||||
promptFS, err := r.PromptFS()
|
promptFS, err := r.PromptFS()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("prepare prompt assets: %w", err)
|
return nil, fmt.Errorf("prepare prompt assets: %w", err)
|
||||||
@@ -81,9 +81,9 @@ func (r *AssetRegistry) ScriptoriumOptions() ([]scriptorium.Option, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("prepare schema assets: %w", err)
|
return nil, fmt.Errorf("prepare schema assets: %w", err)
|
||||||
}
|
}
|
||||||
return []scriptorium.Option{
|
return []promptkit.Option{
|
||||||
scriptorium.WithPromptFS(promptFS, "."),
|
promptkit.WithPromptFS(promptFS, "."),
|
||||||
scriptorium.WithSchemaFS(schemaFS, "."),
|
promptkit.WithSchemaFS(schemaFS, "."),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"testing/fstest"
|
"testing/fstest"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/scriptorium"
|
"gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAssetRegistryCombinesPromptAndSchemaSources(t *testing.T) {
|
func TestAssetRegistryCombinesPromptAndSchemaSources(t *testing.T) {
|
||||||
@@ -22,12 +22,12 @@ func TestAssetRegistryCombinesPromptAndSchemaSources(t *testing.T) {
|
|||||||
"root/schemas/out.json": {Data: []byte(`{"type":"object"}`)},
|
"root/schemas/out.json": {Data: []byte(`{"type":"object"}`)},
|
||||||
}, "root")
|
}, "root")
|
||||||
|
|
||||||
engine := newAssetTestEngine(t, registry)
|
engine := newPromptKitAssetTestEngine(t, registry)
|
||||||
prepared, err := engine.Prepare(context.Background(), scriptorium.RunRequest{
|
prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{
|
||||||
PromptID: "asset.test",
|
PromptID: "asset.test",
|
||||||
ProfileID: "asset-test-profile",
|
ProfileID: "asset-test-profile",
|
||||||
Inputs: map[string]scriptorium.ArtifactRef{
|
Inputs: map[string]promptkit.ArtifactRef{
|
||||||
"transcript": scriptorium.Inline(`{"ok":true}`),
|
"transcript": promptkit.Inline(`{"ok":true}`),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -50,12 +50,12 @@ func TestAssetRegistryPrepareFailsForMissingPromptAsset(t *testing.T) {
|
|||||||
"out.json": {Data: []byte(`{"type":"object"}`)},
|
"out.json": {Data: []byte(`{"type":"object"}`)},
|
||||||
}, ".")
|
}, ".")
|
||||||
|
|
||||||
engine := newAssetTestEngine(t, registry)
|
engine := newPromptKitAssetTestEngine(t, registry)
|
||||||
_, err := engine.Prepare(context.Background(), scriptorium.RunRequest{
|
_, err := engine.Prepare(context.Background(), promptkit.RunRequest{
|
||||||
PromptID: "asset.test",
|
PromptID: "asset.test",
|
||||||
ProfileID: "asset-test-profile",
|
ProfileID: "asset-test-profile",
|
||||||
Inputs: map[string]scriptorium.ArtifactRef{
|
Inputs: map[string]promptkit.ArtifactRef{
|
||||||
"transcript": scriptorium.Inline(`{"ok":true}`),
|
"transcript": promptkit.Inline(`{"ok":true}`),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err == nil || !strings.Contains(err.Error(), "content_file") {
|
if err == nil || !strings.Contains(err.Error(), "content_file") {
|
||||||
@@ -74,12 +74,12 @@ func TestAssetRegistryPrepareFailsForMissingSchemaAsset(t *testing.T) {
|
|||||||
"present.json": {Data: []byte(`{"type":"object"}`)},
|
"present.json": {Data: []byte(`{"type":"object"}`)},
|
||||||
}, ".")
|
}, ".")
|
||||||
|
|
||||||
engine := newAssetTestEngine(t, registry)
|
engine := newPromptKitAssetTestEngine(t, registry)
|
||||||
_, err := engine.Prepare(context.Background(), scriptorium.RunRequest{
|
_, err := engine.Prepare(context.Background(), promptkit.RunRequest{
|
||||||
PromptID: "asset.test",
|
PromptID: "asset.test",
|
||||||
ProfileID: "asset-test-profile",
|
ProfileID: "asset-test-profile",
|
||||||
Inputs: map[string]scriptorium.ArtifactRef{
|
Inputs: map[string]promptkit.ArtifactRef{
|
||||||
"transcript": scriptorium.Inline(`{"ok":true}`),
|
"transcript": promptkit.Inline(`{"ok":true}`),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err == nil || !strings.Contains(err.Error(), "missing.json") {
|
if err == nil || !strings.Contains(err.Error(), "missing.json") {
|
||||||
@@ -145,18 +145,18 @@ func TestHashAssetsOmitsRawAssetContent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAssetTestEngine(t *testing.T, registry *AssetRegistry) *scriptorium.Engine {
|
func newPromptKitAssetTestEngine(t *testing.T, registry *AssetRegistry) *promptkit.Engine {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
options, err := registry.ScriptoriumOptions()
|
options, err := registry.PromptKitOptions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ScriptoriumOptions() error = %v, want nil", err)
|
t.Fatalf("PromptKitOptions() error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
options = append(options, scriptorium.WithProfiles(scriptorium.OpenAICompatibleProfile(scriptorium.OpenAICompatibleProfileConfig{
|
options = append(options, promptkit.WithProfiles(promptkit.OpenAICompatibleProfile(promptkit.OpenAICompatibleProfileConfig{
|
||||||
ID: "asset-test-profile",
|
ID: "asset-test-profile",
|
||||||
Endpoint: "http://127.0.0.1:1/v1",
|
Endpoint: "http://127.0.0.1:1/v1",
|
||||||
Model: "asset-test-model",
|
Model: "asset-test-model",
|
||||||
})))
|
})))
|
||||||
engine, err := scriptorium.NewEngine(scriptorium.Config{Timeout: time.Second}, options...)
|
engine, err := promptkit.NewEngine(promptkit.Config{Timeout: time.Second}, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewEngine() error = %v, want nil", err)
|
t.Fatalf("NewEngine() error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,23 +13,23 @@ import (
|
|||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/core/artifacts"
|
"gitea.maximumdirect.net/eric/notarius/internal/core/artifacts"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/scriptorium"
|
"gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
const scriptoriumProviderName = "scriptorium"
|
const promptKitProviderName = "promptkit"
|
||||||
|
|
||||||
type ScriptoriumClientConfig struct {
|
type PromptKitClientConfig struct {
|
||||||
ProfileDir string
|
ProfileDir string
|
||||||
ProfileFile string
|
ProfileFile string
|
||||||
Assets *AssetRegistry
|
Assets *AssetRegistry
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
HTTPClient *http.Client
|
HTTPClient *http.Client
|
||||||
EngineOptions []scriptorium.Option
|
EngineOptions []promptkit.Option
|
||||||
Recorder *LLMProfileRecorder
|
Recorder *LLMProfileRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScriptoriumClient struct {
|
type PromptKitClient struct {
|
||||||
engine *scriptorium.Engine
|
engine *promptkit.Engine
|
||||||
recorder *LLMProfileRecorder
|
recorder *LLMProfileRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,49 +38,49 @@ type LLMProfileRecorder struct {
|
|||||||
profiles map[string]artifacts.LLMProfileManifest
|
profiles map[string]artifacts.LLMProfileManifest
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ contracts.StructuredLLMClient = (*ScriptoriumClient)(nil)
|
var _ contracts.StructuredLLMClient = (*PromptKitClient)(nil)
|
||||||
var _ contracts.LLMProfileManifestProvider = (*ScriptoriumClient)(nil)
|
var _ contracts.LLMProfileManifestProvider = (*PromptKitClient)(nil)
|
||||||
|
|
||||||
func NewScriptoriumClient(cfg ScriptoriumClientConfig) (*ScriptoriumClient, error) {
|
func NewPromptKitClient(cfg PromptKitClientConfig) (*PromptKitClient, error) {
|
||||||
if cfg.Assets == nil {
|
if cfg.Assets == nil {
|
||||||
return nil, fmt.Errorf("scriptorium client assets must not be nil")
|
return nil, fmt.Errorf("PromptKit client assets must not be nil")
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(cfg.ProfileDir) != "" && strings.TrimSpace(cfg.ProfileFile) != "" {
|
if strings.TrimSpace(cfg.ProfileDir) != "" && strings.TrimSpace(cfg.ProfileFile) != "" {
|
||||||
return nil, fmt.Errorf("scriptorium profile_dir and profile_file are mutually exclusive")
|
return nil, fmt.Errorf("PromptKit profile_dir and profile_file are mutually exclusive")
|
||||||
}
|
}
|
||||||
options, err := cfg.Assets.ScriptoriumOptions()
|
options, err := cfg.Assets.PromptKitOptions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if profileFile := strings.TrimSpace(cfg.ProfileFile); profileFile != "" {
|
if profileFile := strings.TrimSpace(cfg.ProfileFile); profileFile != "" {
|
||||||
options = append(options, scriptorium.WithProfileFile(profileFile))
|
options = append(options, promptkit.WithProfileFile(profileFile))
|
||||||
}
|
}
|
||||||
options = append(options, cfg.EngineOptions...)
|
options = append(options, cfg.EngineOptions...)
|
||||||
|
|
||||||
engine, err := scriptorium.NewEngine(scriptorium.Config{
|
engine, err := promptkit.NewEngine(promptkit.Config{
|
||||||
ProfileDir: strings.TrimSpace(cfg.ProfileDir),
|
ProfileDir: strings.TrimSpace(cfg.ProfileDir),
|
||||||
Timeout: cfg.Timeout,
|
Timeout: cfg.Timeout,
|
||||||
HTTPClient: cfg.HTTPClient,
|
HTTPClient: cfg.HTTPClient,
|
||||||
}, options...)
|
}, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create Scriptorium engine: %w", err)
|
return nil, fmt.Errorf("create PromptKit engine: %w", err)
|
||||||
}
|
}
|
||||||
recorder := cfg.Recorder
|
recorder := cfg.Recorder
|
||||||
if recorder == nil {
|
if recorder == nil {
|
||||||
recorder = NewLLMProfileRecorder()
|
recorder = NewLLMProfileRecorder()
|
||||||
}
|
}
|
||||||
return &ScriptoriumClient{
|
return &PromptKitClient{
|
||||||
engine: engine,
|
engine: engine,
|
||||||
recorder: recorder,
|
recorder: recorder,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ScriptoriumClient) CompleteStructured(ctx context.Context, req contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
|
func (c *PromptKitClient) CompleteStructured(ctx context.Context, req contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("scriptorium client must not be nil")
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("PromptKit client must not be nil")
|
||||||
}
|
}
|
||||||
if c.engine == nil {
|
if c.engine == nil {
|
||||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("scriptorium client engine must not be nil")
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("PromptKit client engine must not be nil")
|
||||||
}
|
}
|
||||||
if err := validateOutputTarget(out); err != nil {
|
if err := validateOutputTarget(out); err != nil {
|
||||||
return contracts.StructuredCompletionResponse{}, err
|
return contracts.StructuredCompletionResponse{}, err
|
||||||
@@ -90,52 +90,52 @@ func (c *ScriptoriumClient) CompleteStructured(ctx context.Context, req contract
|
|||||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("structured completion prompt_id must not be empty")
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("structured completion prompt_id must not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
runReq := scriptorium.RunRequest{
|
runReq := promptkit.RunRequest{
|
||||||
PromptID: promptID,
|
PromptID: promptID,
|
||||||
PromptVersion: strings.TrimSpace(req.PromptVersion),
|
PromptVersion: strings.TrimSpace(req.PromptVersion),
|
||||||
ProfileID: strings.TrimSpace(req.ProfileID),
|
ProfileID: strings.TrimSpace(req.ProfileID),
|
||||||
Inputs: scriptoriumInputs(req.Inputs),
|
Inputs: promptKitInputs(req.Inputs),
|
||||||
Vars: scriptoriumVars(req),
|
Vars: promptKitVars(req),
|
||||||
Metadata: scriptoriumMetadata(req),
|
Metadata: promptKitMetadata(req),
|
||||||
}
|
}
|
||||||
prepared, err := c.engine.Prepare(ctx, runReq)
|
prepared, err := c.engine.Prepare(ctx, runReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||||
return contracts.StructuredCompletionResponse{}, ctxErr
|
return contracts.StructuredCompletionResponse{}, ctxErr
|
||||||
}
|
}
|
||||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("prepare Scriptorium prompt %q: %w", promptID, redactScriptoriumError(err))
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("prepare PromptKit prompt %q: %w", promptID, redactPromptKitError(err))
|
||||||
}
|
}
|
||||||
result, err := c.engine.Run(ctx, runReq)
|
result, err := c.engine.Run(ctx, runReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||||
return contracts.StructuredCompletionResponse{}, ctxErr
|
return contracts.StructuredCompletionResponse{}, ctxErr
|
||||||
}
|
}
|
||||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("run Scriptorium prompt %q: %w", promptID, redactScriptoriumError(err))
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("run PromptKit prompt %q: %w", promptID, redactPromptKitError(err))
|
||||||
}
|
}
|
||||||
if result == nil {
|
if result == nil {
|
||||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("run Scriptorium prompt %q: %w: empty result", promptID, contracts.ErrInvalidStructuredOutput)
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("run PromptKit prompt %q: %w: empty result", promptID, contracts.ErrInvalidStructuredOutput)
|
||||||
}
|
}
|
||||||
response := c.responseFromResult(result, prepared)
|
response := c.responseFromResult(result, prepared)
|
||||||
if result.Validation.Status == scriptorium.ValidationFailed || !result.Validation.IsValid {
|
if result.Validation.Status == promptkit.ValidationFailed || !result.Validation.IsValid {
|
||||||
return response, fmt.Errorf("run Scriptorium prompt %q: %w: validation failed: %s", promptID, contracts.ErrInvalidStructuredOutput, strings.Join(result.Validation.Errors, "; "))
|
return response, fmt.Errorf("run PromptKit prompt %q: %w: validation failed: %s", promptID, contracts.ErrInvalidStructuredOutput, strings.Join(result.Validation.Errors, "; "))
|
||||||
}
|
}
|
||||||
if len(strings.TrimSpace(string(response.Content))) == 0 {
|
if len(strings.TrimSpace(string(response.Content))) == 0 {
|
||||||
return response, fmt.Errorf("run Scriptorium prompt %q: %w: empty structured output", promptID, contracts.ErrInvalidStructuredOutput)
|
return response, fmt.Errorf("run PromptKit prompt %q: %w: empty structured output", promptID, contracts.ErrInvalidStructuredOutput)
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(response.Content, out); err != nil {
|
if err := json.Unmarshal(response.Content, out); err != nil {
|
||||||
return response, fmt.Errorf("decode Scriptorium structured output for prompt %q: %w: %w", promptID, contracts.ErrInvalidStructuredOutput, err)
|
return response, fmt.Errorf("decode PromptKit structured output for prompt %q: %w: %w", promptID, contracts.ErrInvalidStructuredOutput, err)
|
||||||
}
|
}
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ScriptoriumClient) responseFromResult(result *scriptorium.RunResult, prepared *scriptorium.PreparedRun) contracts.StructuredCompletionResponse {
|
func (c *PromptKitClient) responseFromResult(result *promptkit.RunResult, prepared *promptkit.PreparedRun) contracts.StructuredCompletionResponse {
|
||||||
content := result.Artifact.Body
|
content := result.Artifact.Body
|
||||||
if len(content) == 0 {
|
if len(content) == 0 {
|
||||||
content = []byte(result.RawOutput)
|
content = []byte(result.RawOutput)
|
||||||
}
|
}
|
||||||
profile := artifacts.LLMProfileManifest{
|
profile := artifacts.LLMProfileManifest{
|
||||||
ID: strings.TrimSpace(result.SelectedProfileID),
|
ID: strings.TrimSpace(result.SelectedProfileID),
|
||||||
Provider: scriptoriumProviderName,
|
Provider: promptKitProviderName,
|
||||||
Model: firstNonEmpty(result.ModelName, result.EffectiveModelParams.Model),
|
Model: firstNonEmpty(result.ModelName, result.EffectiveModelParams.Model),
|
||||||
}
|
}
|
||||||
if c.recorder != nil {
|
if c.recorder != nil {
|
||||||
@@ -149,17 +149,17 @@ func (c *ScriptoriumClient) responseFromResult(result *scriptorium.RunResult, pr
|
|||||||
PromptTokens: result.Usage.PromptTokens,
|
PromptTokens: result.Usage.PromptTokens,
|
||||||
CompletionTokens: result.Usage.CompletionTokens,
|
CompletionTokens: result.Usage.CompletionTokens,
|
||||||
TotalTokens: result.Usage.TotalTokens,
|
TotalTokens: result.Usage.TotalTokens,
|
||||||
Debug: scriptoriumDebugMaterial(prepared, result),
|
Debug: promptKitDebugMaterial(prepared, result),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func scriptoriumDebugMaterial(prepared *scriptorium.PreparedRun, result *scriptorium.RunResult) *contracts.LLMDebugMaterial {
|
func promptKitDebugMaterial(prepared *promptkit.PreparedRun, result *promptkit.RunResult) *contracts.LLMDebugMaterial {
|
||||||
material := &contracts.LLMDebugMaterial{}
|
material := &contracts.LLMDebugMaterial{}
|
||||||
if prepared != nil {
|
if prepared != nil {
|
||||||
material.Prompt = scriptoriumDebugPrompt(prepared)
|
material.Prompt = promptKitDebugPrompt(prepared)
|
||||||
}
|
}
|
||||||
if result != nil {
|
if result != nil {
|
||||||
material.Response = scriptoriumDebugResponse(result)
|
material.Response = promptKitDebugResponse(result)
|
||||||
}
|
}
|
||||||
if material.Prompt == nil && material.Response == nil {
|
if material.Prompt == nil && material.Response == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -167,7 +167,7 @@ func scriptoriumDebugMaterial(prepared *scriptorium.PreparedRun, result *scripto
|
|||||||
return material
|
return material
|
||||||
}
|
}
|
||||||
|
|
||||||
func scriptoriumDebugPrompt(prepared *scriptorium.PreparedRun) *contracts.LLMDebugPrompt {
|
func promptKitDebugPrompt(prepared *promptkit.PreparedRun) *contracts.LLMDebugPrompt {
|
||||||
if prepared == nil {
|
if prepared == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -194,7 +194,7 @@ func scriptoriumDebugPrompt(prepared *scriptorium.PreparedRun) *contracts.LLMDeb
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func scriptoriumDebugResponse(result *scriptorium.RunResult) *contracts.LLMDebugResponse {
|
func promptKitDebugResponse(result *promptkit.RunResult) *contracts.LLMDebugResponse {
|
||||||
if result == nil {
|
if result == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -254,7 +254,7 @@ func cloneStringMap(values map[string]string) map[string]string {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ScriptoriumClient) LLMProfileManifests() []artifacts.LLMProfileManifest {
|
func (c *PromptKitClient) LLMProfileManifests() []artifacts.LLMProfileManifest {
|
||||||
if c == nil || c.recorder == nil {
|
if c == nil || c.recorder == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -302,11 +302,11 @@ func (r *LLMProfileRecorder) Manifests() []artifacts.LLMProfileManifest {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func scriptoriumInputs(inputs contracts.LLMInputSet) map[string]scriptorium.ArtifactRef {
|
func promptKitInputs(inputs contracts.LLMInputSet) map[string]promptkit.ArtifactRef {
|
||||||
if len(inputs) == 0 {
|
if len(inputs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
out := make(map[string]scriptorium.ArtifactRef, len(inputs))
|
out := make(map[string]promptkit.ArtifactRef, len(inputs))
|
||||||
for key, material := range inputs {
|
for key, material := range inputs {
|
||||||
name := strings.TrimSpace(key)
|
name := strings.TrimSpace(key)
|
||||||
if name == "" {
|
if name == "" {
|
||||||
@@ -320,15 +320,15 @@ func scriptoriumInputs(inputs contracts.LLMInputSet) map[string]scriptorium.Arti
|
|||||||
body = " "
|
body = " "
|
||||||
}
|
}
|
||||||
if origin := strings.TrimSpace(material.OriginURI); origin != "" {
|
if origin := strings.TrimSpace(material.OriginURI); origin != "" {
|
||||||
out[name] = scriptorium.InlineWithURI(origin, body)
|
out[name] = promptkit.InlineWithURI(origin, body)
|
||||||
} else {
|
} else {
|
||||||
out[name] = scriptorium.Inline(body)
|
out[name] = promptkit.Inline(body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func scriptoriumVars(req contracts.StructuredCompletionRequest) map[string]string {
|
func promptKitVars(req contracts.StructuredCompletionRequest) map[string]string {
|
||||||
vars := make(map[string]string, len(req.Vars)+1)
|
vars := make(map[string]string, len(req.Vars)+1)
|
||||||
for key, value := range req.Vars {
|
for key, value := range req.Vars {
|
||||||
name := strings.TrimSpace(key)
|
name := strings.TrimSpace(key)
|
||||||
@@ -346,7 +346,7 @@ func scriptoriumVars(req contracts.StructuredCompletionRequest) map[string]strin
|
|||||||
return vars
|
return vars
|
||||||
}
|
}
|
||||||
|
|
||||||
func scriptoriumMetadata(req contracts.StructuredCompletionRequest) map[string]string {
|
func promptKitMetadata(req contracts.StructuredCompletionRequest) map[string]string {
|
||||||
metadata := map[string]string{}
|
metadata := map[string]string{}
|
||||||
if stageName := strings.TrimSpace(req.StageName); stageName != "" {
|
if stageName := strings.TrimSpace(req.StageName); stageName != "" {
|
||||||
metadata["stage_name"] = stageName
|
metadata["stage_name"] = stageName
|
||||||
@@ -359,7 +359,7 @@ func scriptoriumMetadata(req contracts.StructuredCompletionRequest) map[string]s
|
|||||||
|
|
||||||
var bearerTokenPattern = regexp.MustCompile(`(?i)Bearer\s+[A-Za-z0-9._~+/=-]+`)
|
var bearerTokenPattern = regexp.MustCompile(`(?i)Bearer\s+[A-Za-z0-9._~+/=-]+`)
|
||||||
|
|
||||||
func redactScriptoriumError(err error) error {
|
func redactPromptKitError(err error) error {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -12,12 +14,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/scriptorium"
|
"gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestScriptoriumClientMapsPromptRequestAndUnmarshalsOutput(t *testing.T) {
|
func TestPromptKitClientMapsPromptRequestAndUnmarshalsOutput(t *testing.T) {
|
||||||
fake := &fakeScriptoriumLLM{content: `{"ok":true}`}
|
fake := &fakePromptKitLLM{content: `{"ok":true}`}
|
||||||
client := newTestScriptoriumClient(t, fake)
|
client := newTestPromptKitClient(t, fake)
|
||||||
|
|
||||||
var out struct {
|
var out struct {
|
||||||
OK bool `json:"ok"`
|
OK bool `json:"ok"`
|
||||||
@@ -39,7 +41,7 @@ func TestScriptoriumClientMapsPromptRequestAndUnmarshalsOutput(t *testing.T) {
|
|||||||
if !out.OK {
|
if !out.OK {
|
||||||
t.Fatalf("decoded output OK = false, want true")
|
t.Fatalf("decoded output OK = false, want true")
|
||||||
}
|
}
|
||||||
if resp.Provider != scriptoriumProviderName || resp.Model != "explicit-model" || resp.ProfileID != "explicit-profile" {
|
if resp.Provider != promptKitProviderName || resp.Model != "explicit-model" || resp.ProfileID != "explicit-profile" {
|
||||||
t.Fatalf("response metadata = %#v", resp)
|
t.Fatalf("response metadata = %#v", resp)
|
||||||
}
|
}
|
||||||
if resp.PromptTokens != 11 || resp.CompletionTokens != 7 || resp.TotalTokens != 18 {
|
if resp.PromptTokens != 11 || resp.CompletionTokens != 7 || resp.TotalTokens != 18 {
|
||||||
@@ -57,6 +59,9 @@ func TestScriptoriumClientMapsPromptRequestAndUnmarshalsOutput(t *testing.T) {
|
|||||||
if resp.Debug.Response == nil || resp.Debug.Response.Content != `{"ok":true}` {
|
if resp.Debug.Response == nil || resp.Debug.Response.Content != `{"ok":true}` {
|
||||||
t.Fatalf("debug response = %#v, want raw response content", resp.Debug.Response)
|
t.Fatalf("debug response = %#v, want raw response content", resp.Debug.Response)
|
||||||
}
|
}
|
||||||
|
if resp.Debug.Response.Usage.CachedTokens != 5 || resp.Debug.Response.Usage.CacheWriteTokens != 3 {
|
||||||
|
t.Fatalf("debug usage = %#v, want cached token counts", resp.Debug.Response.Usage)
|
||||||
|
}
|
||||||
debugJSON, err := json.Marshal(resp.Debug)
|
debugJSON, err := json.Marshal(resp.Debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("marshal debug material: %v", err)
|
t.Fatalf("marshal debug material: %v", err)
|
||||||
@@ -71,21 +76,52 @@ func TestScriptoriumClientMapsPromptRequestAndUnmarshalsOutput(t *testing.T) {
|
|||||||
if gotReq.Target.Model != "explicit-model" {
|
if gotReq.Target.Model != "explicit-model" {
|
||||||
t.Fatalf("model = %q, want explicit-model", gotReq.Target.Model)
|
t.Fatalf("model = %q, want explicit-model", gotReq.Target.Model)
|
||||||
}
|
}
|
||||||
if len(gotReq.Prompt.Messages) != 1 || !strings.Contains(gotReq.Prompt.Messages[0].Content, `{"source":true}`) {
|
if len(gotReq.Prompt.Messages) != 1 ||
|
||||||
t.Fatalf("rendered messages = %#v, want transcript input content", gotReq.Prompt.Messages)
|
!strings.Contains(gotReq.Prompt.Messages[0].Content, `{"source":true}`) ||
|
||||||
|
!strings.Contains(gotReq.Prompt.Messages[0].Content, "value") {
|
||||||
|
t.Fatalf("rendered messages = %#v, want transcript input and variable content", gotReq.Prompt.Messages)
|
||||||
}
|
}
|
||||||
if gotReq.StructuredOutput == nil {
|
if gotReq.StructuredOutput == nil {
|
||||||
t.Fatalf("structured output = nil, want JSON schema")
|
t.Fatalf("structured output = nil, want JSON schema")
|
||||||
}
|
}
|
||||||
manifests := client.LLMProfileManifests()
|
manifests := client.LLMProfileManifests()
|
||||||
if len(manifests) != 1 || manifests[0].ID != "explicit-profile" || manifests[0].Model != "explicit-model" {
|
if len(manifests) != 1 ||
|
||||||
|
manifests[0].ID != "explicit-profile" ||
|
||||||
|
manifests[0].Provider != promptKitProviderName ||
|
||||||
|
manifests[0].Model != "explicit-model" {
|
||||||
t.Fatalf("profile manifests = %#v", manifests)
|
t.Fatalf("profile manifests = %#v", manifests)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScriptoriumClientUsesPromptDefaultProfileWhenRequestProfileEmpty(t *testing.T) {
|
func TestNewPromptKitClientReportsAssetAndEngineConstructionFailures(t *testing.T) {
|
||||||
fake := &fakeScriptoriumLLM{content: `{"ok":true}`}
|
t.Run("assets", func(t *testing.T) {
|
||||||
client := newTestScriptoriumClient(t, fake)
|
registry := NewAssetRegistry()
|
||||||
|
for _, root := range []string{"one", "two"} {
|
||||||
|
if err := registry.RegisterPromptFS(fstest.MapFS{
|
||||||
|
root + "/prompt.yaml": {Data: []byte("id: duplicate")},
|
||||||
|
}, root); err != nil {
|
||||||
|
t.Fatalf("RegisterPromptFS() error = %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := NewPromptKitClient(PromptKitClientConfig{Assets: registry}); err == nil ||
|
||||||
|
!strings.Contains(err.Error(), "duplicate asset path") {
|
||||||
|
t.Fatalf("NewPromptKitClient() error = %v, want asset construction failure", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("engine", func(t *testing.T) {
|
||||||
|
if _, err := NewPromptKitClient(PromptKitClientConfig{
|
||||||
|
Assets: newTestPromptKitAssets(t),
|
||||||
|
EngineOptions: []promptkit.Option{promptkit.WithProfileFile("")},
|
||||||
|
}); err == nil || !strings.Contains(err.Error(), "create PromptKit engine") {
|
||||||
|
t.Fatalf("NewPromptKitClient() error = %v, want engine construction failure", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPromptKitClientUsesPromptDefaultProfileWhenRequestProfileEmpty(t *testing.T) {
|
||||||
|
fake := &fakePromptKitLLM{content: `{"ok":true}`}
|
||||||
|
client := newTestPromptKitClient(t, fake)
|
||||||
|
|
||||||
var out map[string]any
|
var out map[string]any
|
||||||
if _, err := client.CompleteStructured(context.Background(), contracts.StructuredCompletionRequest{
|
if _, err := client.CompleteStructured(context.Background(), contracts.StructuredCompletionRequest{
|
||||||
@@ -102,8 +138,8 @@ func TestScriptoriumClientUsesPromptDefaultProfileWhenRequestProfileEmpty(t *tes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScriptoriumClientValidationFailureReturnsError(t *testing.T) {
|
func TestPromptKitClientValidationFailureReturnsError(t *testing.T) {
|
||||||
client := newTestScriptoriumClient(t, &fakeScriptoriumLLM{content: `{"bad":true}`})
|
client := newTestPromptKitClient(t, &fakePromptKitLLM{content: `{"bad":true}`})
|
||||||
|
|
||||||
var out map[string]any
|
var out map[string]any
|
||||||
resp, err := client.CompleteStructured(context.Background(), contracts.StructuredCompletionRequest{
|
resp, err := client.CompleteStructured(context.Background(), contracts.StructuredCompletionRequest{
|
||||||
@@ -127,8 +163,8 @@ func TestScriptoriumClientValidationFailureReturnsError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScriptoriumClientDecodeFailureReturnsRawResponse(t *testing.T) {
|
func TestPromptKitClientDecodeFailureReturnsRawResponse(t *testing.T) {
|
||||||
client := newTestScriptoriumClient(t, &fakeScriptoriumLLM{content: `{"ok":true}`})
|
client := newTestPromptKitClient(t, &fakePromptKitLLM{content: `{"ok":true}`})
|
||||||
|
|
||||||
var out []any
|
var out []any
|
||||||
resp, err := client.CompleteStructured(context.Background(), contracts.StructuredCompletionRequest{
|
resp, err := client.CompleteStructured(context.Background(), contracts.StructuredCompletionRequest{
|
||||||
@@ -138,7 +174,7 @@ func TestScriptoriumClientDecodeFailureReturnsRawResponse(t *testing.T) {
|
|||||||
"transcript": contracts.NewLLMInputMaterial("transcript", "application/json", []byte(`{"source":true}`), "", ""),
|
"transcript": contracts.NewLLMInputMaterial("transcript", "application/json", []byte(`{"source":true}`), "", ""),
|
||||||
},
|
},
|
||||||
}, &out)
|
}, &out)
|
||||||
if err == nil || !errors.Is(err, contracts.ErrInvalidStructuredOutput) || !strings.Contains(err.Error(), "decode Scriptorium structured output") {
|
if err == nil || !errors.Is(err, contracts.ErrInvalidStructuredOutput) || !strings.Contains(err.Error(), "decode PromptKit structured output") {
|
||||||
t.Fatalf("CompleteStructured() error = %v, want decode failure", err)
|
t.Fatalf("CompleteStructured() error = %v, want decode failure", err)
|
||||||
}
|
}
|
||||||
if got := string(resp.Content); got != `{"ok":true}` {
|
if got := string(resp.Content); got != `{"ok":true}` {
|
||||||
@@ -149,8 +185,8 @@ func TestScriptoriumClientDecodeFailureReturnsRawResponse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScriptoriumClientProviderFailureIncludesContextAndRedactsBearerToken(t *testing.T) {
|
func TestPromptKitClientProviderFailureIncludesContextAndRedactsBearerToken(t *testing.T) {
|
||||||
client := newTestScriptoriumClient(t, &fakeScriptoriumLLM{err: errors.New("provider failed with Bearer secret-token")})
|
client := newTestPromptKitClient(t, &fakePromptKitLLM{err: errors.New("provider failed with Bearer secret-token")})
|
||||||
|
|
||||||
var out map[string]any
|
var out map[string]any
|
||||||
resp, err := client.CompleteStructured(context.Background(), contracts.StructuredCompletionRequest{
|
resp, err := client.CompleteStructured(context.Background(), contracts.StructuredCompletionRequest{
|
||||||
@@ -166,7 +202,7 @@ func TestScriptoriumClientProviderFailureIncludesContextAndRedactsBearerToken(t
|
|||||||
if errors.Is(err, contracts.ErrInvalidStructuredOutput) {
|
if errors.Is(err, contracts.ErrInvalidStructuredOutput) {
|
||||||
t.Fatalf("provider error = %v, must not be classified as invalid structured output", err)
|
t.Fatalf("provider error = %v, must not be classified as invalid structured output", err)
|
||||||
}
|
}
|
||||||
if !strings.Contains(err.Error(), `run Scriptorium prompt "adapter.test"`) {
|
if !strings.Contains(err.Error(), `run PromptKit prompt "adapter.test"`) {
|
||||||
t.Fatalf("error = %q, want operation context", err.Error())
|
t.Fatalf("error = %q, want operation context", err.Error())
|
||||||
}
|
}
|
||||||
if strings.Contains(err.Error(), "secret-token") || !strings.Contains(err.Error(), "Bearer [REDACTED]") {
|
if strings.Contains(err.Error(), "secret-token") || !strings.Contains(err.Error(), "Bearer [REDACTED]") {
|
||||||
@@ -177,10 +213,10 @@ func TestScriptoriumClientProviderFailureIncludesContextAndRedactsBearerToken(t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScriptoriumClientContextCancellationIsRespected(t *testing.T) {
|
func TestPromptKitClientContextCancellationIsRespected(t *testing.T) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
cancel()
|
cancel()
|
||||||
client := newTestScriptoriumClient(t, &fakeScriptoriumLLM{content: `{"ok":true}`})
|
client := newTestPromptKitClient(t, &fakePromptKitLLM{content: `{"ok":true}`})
|
||||||
|
|
||||||
var out map[string]any
|
var out map[string]any
|
||||||
_, err := client.CompleteStructured(ctx, contracts.StructuredCompletionRequest{
|
_, err := client.CompleteStructured(ctx, contracts.StructuredCompletionRequest{
|
||||||
@@ -194,8 +230,57 @@ func TestScriptoriumClientContextCancellationIsRespected(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScriptoriumClientClassifiesEmptyStructuredCompletion(t *testing.T) {
|
func TestPromptKitClientForwardsConfiguredTransportTimeout(t *testing.T) {
|
||||||
client := newTestScriptoriumClient(t, &fakeScriptoriumLLM{allowEmpty: true})
|
var remaining time.Duration
|
||||||
|
transport := roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
|
deadline, ok := req.Context().Deadline()
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("outbound request context has no deadline")
|
||||||
|
}
|
||||||
|
remaining = time.Until(deadline)
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Status: "200 OK",
|
||||||
|
Header: make(http.Header),
|
||||||
|
Body: io.NopCloser(strings.NewReader(
|
||||||
|
`{"choices":[{"message":{"content":"{\"ok\":true}"}}]}`,
|
||||||
|
)),
|
||||||
|
Request: req,
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
const configuredTimeout = 2 * time.Second
|
||||||
|
client, err := NewPromptKitClient(PromptKitClientConfig{
|
||||||
|
Assets: newTestPromptKitAssets(t),
|
||||||
|
Timeout: configuredTimeout,
|
||||||
|
HTTPClient: &http.Client{Transport: transport},
|
||||||
|
EngineOptions: []promptkit.Option{
|
||||||
|
promptkit.WithProfiles(promptkit.OpenAICompatibleProfile(promptkit.OpenAICompatibleProfileConfig{
|
||||||
|
ID: "default-profile",
|
||||||
|
Endpoint: "http://promptkit.test/v1",
|
||||||
|
Model: "default-model",
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewPromptKitClient() error = %v", err)
|
||||||
|
}
|
||||||
|
var out map[string]any
|
||||||
|
if _, err := client.CompleteStructured(context.Background(), contracts.StructuredCompletionRequest{
|
||||||
|
PromptID: "adapter.test",
|
||||||
|
SessionID: "session-123",
|
||||||
|
Inputs: contracts.LLMInputSet{
|
||||||
|
"transcript": contracts.NewLLMInputMaterial("transcript", "application/json", []byte(`{"source":true}`), "", ""),
|
||||||
|
},
|
||||||
|
}, &out); err != nil {
|
||||||
|
t.Fatalf("CompleteStructured() error = %v", err)
|
||||||
|
}
|
||||||
|
if remaining < configuredTimeout-time.Second || remaining > configuredTimeout {
|
||||||
|
t.Fatalf("transport deadline remaining = %v, want near %v", remaining, configuredTimeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPromptKitClientClassifiesEmptyStructuredCompletion(t *testing.T) {
|
||||||
|
client := newTestPromptKitClient(t, &fakePromptKitLLM{allowEmpty: true})
|
||||||
|
|
||||||
var out map[string]any
|
var out map[string]any
|
||||||
_, err := client.CompleteStructured(context.Background(), contracts.StructuredCompletionRequest{
|
_, err := client.CompleteStructured(context.Background(), contracts.StructuredCompletionRequest{
|
||||||
@@ -210,12 +295,12 @@ func TestScriptoriumClientClassifiesEmptyStructuredCompletion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScheduledScriptoriumClientBoundsConcurrentCalls(t *testing.T) {
|
func TestScheduledPromptKitClientBoundsConcurrentCalls(t *testing.T) {
|
||||||
fake := &fakeScriptoriumLLM{
|
fake := &fakePromptKitLLM{
|
||||||
content: `{"ok":true}`,
|
content: `{"ok":true}`,
|
||||||
block: make(chan struct{}),
|
block: make(chan struct{}),
|
||||||
}
|
}
|
||||||
client := newTestScriptoriumClient(t, fake)
|
client := newTestPromptKitClient(t, fake)
|
||||||
scheduler, err := NewScheduler(1)
|
scheduler, err := NewScheduler(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewScheduler() error = %v, want nil", err)
|
t.Fatalf("NewScheduler() error = %v, want nil", err)
|
||||||
@@ -249,8 +334,8 @@ func TestScheduledScriptoriumClientBoundsConcurrentCalls(t *testing.T) {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScriptoriumClientValidatesRequest(t *testing.T) {
|
func TestPromptKitClientValidatesRequest(t *testing.T) {
|
||||||
client := newTestScriptoriumClient(t, &fakeScriptoriumLLM{content: `{"ok":true}`})
|
client := newTestPromptKitClient(t, &fakePromptKitLLM{content: `{"ok":true}`})
|
||||||
var out map[string]any
|
var out map[string]any
|
||||||
if _, err := client.CompleteStructured(context.Background(), contracts.StructuredCompletionRequest{}, &out); err == nil || !strings.Contains(err.Error(), "prompt_id") {
|
if _, err := client.CompleteStructured(context.Background(), contracts.StructuredCompletionRequest{}, &out); err == nil || !strings.Contains(err.Error(), "prompt_id") {
|
||||||
t.Fatalf("missing prompt id error = %v, want prompt_id validation", err)
|
t.Fatalf("missing prompt id error = %v, want prompt_id validation", err)
|
||||||
@@ -260,7 +345,34 @@ func TestScriptoriumClientValidatesRequest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestScriptoriumClient(t *testing.T, fake *fakeScriptoriumLLM) *ScriptoriumClient {
|
func newTestPromptKitClient(t *testing.T, fake *fakePromptKitLLM) *PromptKitClient {
|
||||||
|
t.Helper()
|
||||||
|
registry := newTestPromptKitAssets(t)
|
||||||
|
client, err := NewPromptKitClient(PromptKitClientConfig{
|
||||||
|
Assets: registry,
|
||||||
|
EngineOptions: []promptkit.Option{
|
||||||
|
promptkit.WithProfiles(
|
||||||
|
promptkit.OpenAICompatibleProfile(promptkit.OpenAICompatibleProfileConfig{
|
||||||
|
ID: "default-profile",
|
||||||
|
Endpoint: "http://127.0.0.1:1/v1",
|
||||||
|
Model: "default-model",
|
||||||
|
}),
|
||||||
|
promptkit.OpenAICompatibleProfile(promptkit.OpenAICompatibleProfileConfig{
|
||||||
|
ID: "explicit-profile",
|
||||||
|
Endpoint: "http://127.0.0.1:1/v1",
|
||||||
|
Model: "explicit-model",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
promptkit.WithLLMClient(fake),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewPromptKitClient() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestPromptKitAssets(t *testing.T) *AssetRegistry {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
registry := NewAssetRegistry()
|
registry := NewAssetRegistry()
|
||||||
if err := registry.RegisterPromptFS(fstest.MapFS{
|
if err := registry.RegisterPromptFS(fstest.MapFS{
|
||||||
@@ -274,7 +386,7 @@ inputs:
|
|||||||
content_type: application/json
|
content_type: application/json
|
||||||
messages:
|
messages:
|
||||||
- role: user
|
- role: user
|
||||||
content: "Transcript: {{ input \"transcript\" }}"
|
content: "Transcript: {{ input \"transcript\" }} Custom: {{ index . \"custom\" }}"
|
||||||
output:
|
output:
|
||||||
format: json
|
format: json
|
||||||
validation_mode: json_schema
|
validation_mode: json_schema
|
||||||
@@ -289,43 +401,22 @@ output:
|
|||||||
}, "."); err != nil {
|
}, "."); err != nil {
|
||||||
t.Fatalf("RegisterSchemaFS() error = %v", err)
|
t.Fatalf("RegisterSchemaFS() error = %v", err)
|
||||||
}
|
}
|
||||||
client, err := NewScriptoriumClient(ScriptoriumClientConfig{
|
return registry
|
||||||
Assets: registry,
|
|
||||||
EngineOptions: []scriptorium.Option{
|
|
||||||
scriptorium.WithProfiles(
|
|
||||||
scriptorium.OpenAICompatibleProfile(scriptorium.OpenAICompatibleProfileConfig{
|
|
||||||
ID: "default-profile",
|
|
||||||
Endpoint: "http://127.0.0.1:1/v1",
|
|
||||||
Model: "default-model",
|
|
||||||
}),
|
|
||||||
scriptorium.OpenAICompatibleProfile(scriptorium.OpenAICompatibleProfileConfig{
|
|
||||||
ID: "explicit-profile",
|
|
||||||
Endpoint: "http://127.0.0.1:1/v1",
|
|
||||||
Model: "explicit-model",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
scriptorium.WithLLMClient(fake),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("NewScriptoriumClient() error = %v, want nil", err)
|
|
||||||
}
|
|
||||||
return client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type fakeScriptoriumLLM struct {
|
type fakePromptKitLLM struct {
|
||||||
content string
|
content string
|
||||||
allowEmpty bool
|
allowEmpty bool
|
||||||
err error
|
err error
|
||||||
block chan struct{}
|
block chan struct{}
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
last scriptorium.GenerateRequest
|
last promptkit.GenerateRequest
|
||||||
calls int32
|
calls int32
|
||||||
inFlight int32
|
inFlight int32
|
||||||
maxInFlight int32
|
maxInFlight int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeScriptoriumLLM) Generate(ctx context.Context, req scriptorium.GenerateRequest) (*scriptorium.GenerateResponse, error) {
|
func (f *fakePromptKitLLM) Generate(ctx context.Context, req promptkit.GenerateRequest) (*promptkit.GenerateResponse, error) {
|
||||||
f.mu.Lock()
|
f.mu.Lock()
|
||||||
f.last = req
|
f.last = req
|
||||||
f.mu.Unlock()
|
f.mu.Unlock()
|
||||||
@@ -355,17 +446,25 @@ func (f *fakeScriptoriumLLM) Generate(ctx context.Context, req scriptorium.Gener
|
|||||||
if !f.allowEmpty && !json.Valid([]byte(content)) {
|
if !f.allowEmpty && !json.Valid([]byte(content)) {
|
||||||
return nil, errors.New("test fake must return JSON content")
|
return nil, errors.New("test fake must return JSON content")
|
||||||
}
|
}
|
||||||
return &scriptorium.GenerateResponse{
|
return &promptkit.GenerateResponse{
|
||||||
Content: content,
|
Content: content,
|
||||||
Usage: scriptorium.TokenUsage{
|
Usage: promptkit.TokenUsage{
|
||||||
PromptTokens: 11,
|
PromptTokens: 11,
|
||||||
CompletionTokens: 7,
|
CompletionTokens: 7,
|
||||||
TotalTokens: 18,
|
TotalTokens: 18,
|
||||||
|
CachedTokens: 5,
|
||||||
|
CacheWriteTokens: 3,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeScriptoriumLLM) lastRequest() scriptorium.GenerateRequest {
|
type roundTripFunc func(*http.Request) (*http.Response, error)
|
||||||
|
|
||||||
|
func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
return f(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakePromptKitLLM) lastRequest() promptkit.GenerateRequest {
|
||||||
f.mu.Lock()
|
f.mu.Lock()
|
||||||
defer f.mu.Unlock()
|
defer f.mu.Unlock()
|
||||||
return f.last
|
return f.last
|
||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
||||||
"gitea.maximumdirect.net/eric/scriptorium"
|
"gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestScriptoriumPromptPreparesTranscriptAndTaskMessages(t *testing.T) {
|
func TestScriptoriumPromptPreparesTranscriptAndTaskMessages(t *testing.T) {
|
||||||
@@ -27,7 +27,7 @@ func TestScriptoriumPromptPreparesTranscriptAndTaskMessages(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, index := range []int{1, 4} {
|
for _, index := range []int{1, 4} {
|
||||||
if cache := prepared.Messages[index].CacheControl; cache == nil || cache.Type != scriptorium.CacheControlEphemeral {
|
if cache := prepared.Messages[index].CacheControl; cache == nil || cache.Type != promptkit.CacheControlEphemeral {
|
||||||
t.Errorf("message %d cache control = %#v, want ephemeral", index, cache)
|
t.Errorf("message %d cache control = %#v, want ephemeral", index, cache)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,22 +94,22 @@ func TestScriptoriumPromptDiagnosticsOmitRawMaterials(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareScenesPrompt(t *testing.T, transcript []byte, players string, party string, glossary string) *scriptorium.PreparedRun {
|
func prepareScenesPrompt(t *testing.T, transcript []byte, players string, party string, glossary string) *promptkit.PreparedRun {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
registry := llm.NewAssetRegistry()
|
registry := llm.NewAssetRegistry()
|
||||||
if err := RegisterPromptAssets(registry); err != nil {
|
if err := RegisterPromptAssets(registry); err != nil {
|
||||||
t.Fatalf("register scene prompt assets: %v", err)
|
t.Fatalf("register scene prompt assets: %v", err)
|
||||||
}
|
}
|
||||||
engine := newScenesScriptoriumEngine(t, registry)
|
engine := newScenesScriptoriumEngine(t, registry)
|
||||||
prepared, err := engine.Prepare(context.Background(), scriptorium.RunRequest{
|
prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{
|
||||||
PromptID: PromptID,
|
PromptID: PromptID,
|
||||||
PromptVersion: ResponseSchemaVersion,
|
PromptVersion: ResponseSchemaVersion,
|
||||||
ProfileID: "scene-test-profile",
|
ProfileID: "scene-test-profile",
|
||||||
Inputs: map[string]scriptorium.ArtifactRef{
|
Inputs: map[string]promptkit.ArtifactRef{
|
||||||
"transcript": scriptorium.InlineWithURI("file:///session.json", string(transcript)),
|
"transcript": promptkit.InlineWithURI("file:///session.json", string(transcript)),
|
||||||
"players": scriptorium.Inline(players),
|
"players": promptkit.Inline(players),
|
||||||
"party": scriptorium.Inline(party),
|
"party": promptkit.Inline(party),
|
||||||
"glossary": scriptorium.Inline(glossary),
|
"glossary": promptkit.Inline(glossary),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -118,18 +118,18 @@ func prepareScenesPrompt(t *testing.T, transcript []byte, players string, party
|
|||||||
return prepared
|
return prepared
|
||||||
}
|
}
|
||||||
|
|
||||||
func newScenesScriptoriumEngine(t *testing.T, registry *llm.AssetRegistry) *scriptorium.Engine {
|
func newScenesScriptoriumEngine(t *testing.T, registry *llm.AssetRegistry) *promptkit.Engine {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
options, err := registry.ScriptoriumOptions()
|
options, err := registry.PromptKitOptions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ScriptoriumOptions() error = %v, want nil", err)
|
t.Fatalf("PromptKitOptions() error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
options = append(options, scriptorium.WithProfiles(scriptorium.OpenAICompatibleProfile(scriptorium.OpenAICompatibleProfileConfig{
|
options = append(options, promptkit.WithProfiles(promptkit.OpenAICompatibleProfile(promptkit.OpenAICompatibleProfileConfig{
|
||||||
ID: "scene-test-profile",
|
ID: "scene-test-profile",
|
||||||
Endpoint: "http://127.0.0.1:1/v1",
|
Endpoint: "http://127.0.0.1:1/v1",
|
||||||
Model: "scene-test-model",
|
Model: "scene-test-model",
|
||||||
})))
|
})))
|
||||||
engine, err := scriptorium.NewEngine(scriptorium.Config{Timeout: time.Second}, options...)
|
engine, err := promptkit.NewEngine(promptkit.Config{Timeout: time.Second}, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewEngine() error = %v, want nil", err)
|
t.Fatalf("NewEngine() error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
||||||
"gitea.maximumdirect.net/eric/scriptorium"
|
"gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRegisterPromptAssetsAndPrepareCombatPrompt(t *testing.T) {
|
func TestRegisterPromptAssetsAndPrepareCombatPrompt(t *testing.T) {
|
||||||
@@ -34,26 +34,26 @@ func TestScriptoriumPromptPreparesRequiredInputs(t *testing.T) {
|
|||||||
if err := RegisterPromptAssets(registry); err != nil {
|
if err := RegisterPromptAssets(registry); err != nil {
|
||||||
t.Fatalf("RegisterPromptAssets() error = %v, want nil", err)
|
t.Fatalf("RegisterPromptAssets() error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
options, err := registry.ScriptoriumOptions()
|
options, err := registry.PromptKitOptions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ScriptoriumOptions() error = %v, want nil", err)
|
t.Fatalf("PromptKitOptions() error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
options = append(options, scriptorium.WithProfiles(scriptorium.OpenAICompatibleProfile(scriptorium.OpenAICompatibleProfileConfig{
|
options = append(options, promptkit.WithProfiles(promptkit.OpenAICompatibleProfile(promptkit.OpenAICompatibleProfileConfig{
|
||||||
ID: "combat-test-profile", Endpoint: "http://127.0.0.1:1/v1", Model: "combat-test-model",
|
ID: "combat-test-profile", Endpoint: "http://127.0.0.1:1/v1", Model: "combat-test-model",
|
||||||
})))
|
})))
|
||||||
engine, err := scriptorium.NewEngine(scriptorium.Config{Timeout: time.Second}, options...)
|
engine, err := promptkit.NewEngine(promptkit.Config{Timeout: time.Second}, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewEngine() error = %v, want nil", err)
|
t.Fatalf("NewEngine() error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
transcript := `{"units":[1]}`
|
transcript := `{"units":[1]}`
|
||||||
prepared, err := engine.Prepare(context.Background(), scriptorium.RunRequest{
|
prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{
|
||||||
PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "combat-test-profile",
|
PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "combat-test-profile",
|
||||||
Inputs: map[string]scriptorium.ArtifactRef{
|
Inputs: map[string]promptkit.ArtifactRef{
|
||||||
"transcript": scriptorium.InlineWithURI("file:///session.json", transcript),
|
"transcript": promptkit.InlineWithURI("file:///session.json", transcript),
|
||||||
"players": scriptorium.Inline(" "),
|
"players": promptkit.Inline(" "),
|
||||||
"party": scriptorium.Inline(" "),
|
"party": promptkit.Inline(" "),
|
||||||
"glossary": scriptorium.Inline(" "),
|
"glossary": promptkit.Inline(" "),
|
||||||
"npcs": scriptorium.Inline(" "),
|
"npcs": promptkit.Inline(" "),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
||||||
"gitea.maximumdirect.net/eric/scriptorium"
|
"gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPromptAssetsPrepareItemEventPrompt(t *testing.T) {
|
func TestPromptAssetsPrepareItemEventPrompt(t *testing.T) {
|
||||||
@@ -17,24 +17,24 @@ func TestPromptAssetsPrepareItemEventPrompt(t *testing.T) {
|
|||||||
if err := RegisterPromptAssets(registry); err != nil {
|
if err := RegisterPromptAssets(registry); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
options, err := registry.ScriptoriumOptions()
|
options, err := registry.PromptKitOptions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
options = append(options, scriptorium.WithProfiles(scriptorium.OpenAICompatibleProfile(scriptorium.OpenAICompatibleProfileConfig{
|
options = append(options, promptkit.WithProfiles(promptkit.OpenAICompatibleProfile(promptkit.OpenAICompatibleProfileConfig{
|
||||||
ID: "item-events-test-profile", Endpoint: "http://127.0.0.1:1/v1", Model: "item-events-test-model",
|
ID: "item-events-test-profile", Endpoint: "http://127.0.0.1:1/v1", Model: "item-events-test-model",
|
||||||
})))
|
})))
|
||||||
engine, err := scriptorium.NewEngine(scriptorium.Config{Timeout: time.Second}, options...)
|
engine, err := promptkit.NewEngine(promptkit.Config{Timeout: time.Second}, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
prepared, err := engine.Prepare(context.Background(), scriptorium.RunRequest{
|
prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{
|
||||||
PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "item-events-test-profile",
|
PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "item-events-test-profile",
|
||||||
Inputs: map[string]scriptorium.ArtifactRef{
|
Inputs: map[string]promptkit.ArtifactRef{
|
||||||
"transcript": scriptorium.InlineWithURI("file:///session.json", `{"segments":[1]}`),
|
"transcript": promptkit.InlineWithURI("file:///session.json", `{"segments":[1]}`),
|
||||||
"players": scriptorium.Inline(" "),
|
"players": promptkit.Inline(" "),
|
||||||
"party": scriptorium.Inline(" "),
|
"party": promptkit.Inline(" "),
|
||||||
"glossary": scriptorium.Inline(" "),
|
"glossary": promptkit.Inline(" "),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
||||||
"gitea.maximumdirect.net/eric/scriptorium"
|
"gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRegisterPromptAssetsAndPrepareInteractionPrompt(t *testing.T) {
|
func TestRegisterPromptAssetsAndPrepareInteractionPrompt(t *testing.T) {
|
||||||
@@ -23,26 +23,26 @@ func TestRegisterPromptAssetsAndPrepareInteractionPrompt(t *testing.T) {
|
|||||||
if _, err := fs.ReadFile(schemaFS, "dnd_npc_interactions_llm.v1.json"); err != nil {
|
if _, err := fs.ReadFile(schemaFS, "dnd_npc_interactions_llm.v1.json"); err != nil {
|
||||||
t.Fatalf("response schema asset: %v", err)
|
t.Fatalf("response schema asset: %v", err)
|
||||||
}
|
}
|
||||||
options, err := registry.ScriptoriumOptions()
|
options, err := registry.PromptKitOptions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
options = append(options, scriptorium.WithProfiles(scriptorium.OpenAICompatibleProfile(scriptorium.OpenAICompatibleProfileConfig{
|
options = append(options, promptkit.WithProfiles(promptkit.OpenAICompatibleProfile(promptkit.OpenAICompatibleProfileConfig{
|
||||||
ID: "npc-interactions-test-profile", Endpoint: "http://127.0.0.1:1/v1", Model: "npc-interactions-test-model",
|
ID: "npc-interactions-test-profile", Endpoint: "http://127.0.0.1:1/v1", Model: "npc-interactions-test-model",
|
||||||
})))
|
})))
|
||||||
engine, err := scriptorium.NewEngine(scriptorium.Config{Timeout: time.Second}, options...)
|
engine, err := promptkit.NewEngine(promptkit.Config{Timeout: time.Second}, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
transcript := `{"units":[1]}`
|
transcript := `{"units":[1]}`
|
||||||
prepared, err := engine.Prepare(context.Background(), scriptorium.RunRequest{
|
prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{
|
||||||
PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "npc-interactions-test-profile",
|
PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "npc-interactions-test-profile",
|
||||||
Inputs: map[string]scriptorium.ArtifactRef{
|
Inputs: map[string]promptkit.ArtifactRef{
|
||||||
"transcript": scriptorium.InlineWithURI("file:///session.json", transcript),
|
"transcript": promptkit.InlineWithURI("file:///session.json", transcript),
|
||||||
"players": scriptorium.Inline("Dana: Mira"),
|
"players": promptkit.Inline("Dana: Mira"),
|
||||||
"party": scriptorium.Inline("Mira: ranger"),
|
"party": promptkit.Inline("Mira: ranger"),
|
||||||
"glossary": scriptorium.Inline("Greencloak: title"),
|
"glossary": promptkit.Inline("Greencloak: title"),
|
||||||
"npcs": scriptorium.Inline(`{"npcs":[{"name":"Mira Thorn"}]}`),
|
"npcs": promptkit.Inline(`{"npcs":[{"name":"Mira Thorn"}]}`),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
||||||
"gitea.maximumdirect.net/eric/scriptorium"
|
"gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRegisterPromptAssetsAndPrepareNPCPrompt(t *testing.T) {
|
func TestRegisterPromptAssetsAndPrepareNPCPrompt(t *testing.T) {
|
||||||
@@ -16,24 +16,24 @@ func TestRegisterPromptAssetsAndPrepareNPCPrompt(t *testing.T) {
|
|||||||
if err := RegisterPromptAssets(registry); err != nil {
|
if err := RegisterPromptAssets(registry); err != nil {
|
||||||
t.Fatalf("RegisterPromptAssets() error = %v, want nil", err)
|
t.Fatalf("RegisterPromptAssets() error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
options, err := registry.ScriptoriumOptions()
|
options, err := registry.PromptKitOptions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ScriptoriumOptions() error = %v, want nil", err)
|
t.Fatalf("PromptKitOptions() error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
options = append(options, scriptorium.WithProfiles(scriptorium.OpenAICompatibleProfile(scriptorium.OpenAICompatibleProfileConfig{
|
options = append(options, promptkit.WithProfiles(promptkit.OpenAICompatibleProfile(promptkit.OpenAICompatibleProfileConfig{
|
||||||
ID: "npc-test-profile", Endpoint: "http://127.0.0.1:1/v1", Model: "npc-test-model",
|
ID: "npc-test-profile", Endpoint: "http://127.0.0.1:1/v1", Model: "npc-test-model",
|
||||||
})))
|
})))
|
||||||
engine, err := scriptorium.NewEngine(scriptorium.Config{Timeout: time.Second}, options...)
|
engine, err := promptkit.NewEngine(promptkit.Config{Timeout: time.Second}, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewEngine() error = %v, want nil", err)
|
t.Fatalf("NewEngine() error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
prepared, err := engine.Prepare(context.Background(), scriptorium.RunRequest{
|
prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{
|
||||||
PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "npc-test-profile",
|
PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "npc-test-profile",
|
||||||
Inputs: map[string]scriptorium.ArtifactRef{
|
Inputs: map[string]promptkit.ArtifactRef{
|
||||||
"transcript": scriptorium.InlineWithURI("file:///session.json", `{"units":[1]}`),
|
"transcript": promptkit.InlineWithURI("file:///session.json", `{"units":[1]}`),
|
||||||
"players": scriptorium.Inline(" "),
|
"players": promptkit.Inline(" "),
|
||||||
"party": scriptorium.Inline(" "),
|
"party": promptkit.Inline(" "),
|
||||||
"glossary": scriptorium.Inline(" "),
|
"glossary": promptkit.Inline(" "),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
||||||
"gitea.maximumdirect.net/eric/scriptorium"
|
"gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRegisterPromptAssetsPreparesSceneDescriptionPrompt(t *testing.T) {
|
func TestRegisterPromptAssetsPreparesSceneDescriptionPrompt(t *testing.T) {
|
||||||
@@ -16,24 +16,24 @@ func TestRegisterPromptAssetsPreparesSceneDescriptionPrompt(t *testing.T) {
|
|||||||
if err := RegisterPromptAssets(registry); err != nil {
|
if err := RegisterPromptAssets(registry); err != nil {
|
||||||
t.Fatalf("RegisterPromptAssets() error = %v, want nil", err)
|
t.Fatalf("RegisterPromptAssets() error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
options, err := registry.ScriptoriumOptions()
|
options, err := registry.PromptKitOptions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ScriptoriumOptions() error = %v, want nil", err)
|
t.Fatalf("PromptKitOptions() error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
options = append(options, scriptorium.WithProfiles(scriptorium.OpenAICompatibleProfile(scriptorium.OpenAICompatibleProfileConfig{
|
options = append(options, promptkit.WithProfiles(promptkit.OpenAICompatibleProfile(promptkit.OpenAICompatibleProfileConfig{
|
||||||
ID: "scene-description-test-profile", Endpoint: "http://127.0.0.1:1/v1", Model: "scene-description-test-model",
|
ID: "scene-description-test-profile", Endpoint: "http://127.0.0.1:1/v1", Model: "scene-description-test-model",
|
||||||
})))
|
})))
|
||||||
engine, err := scriptorium.NewEngine(scriptorium.Config{Timeout: time.Second}, options...)
|
engine, err := promptkit.NewEngine(promptkit.Config{Timeout: time.Second}, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewEngine() error = %v, want nil", err)
|
t.Fatalf("NewEngine() error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
prepared, err := engine.Prepare(context.Background(), scriptorium.RunRequest{
|
prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{
|
||||||
PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "scene-description-test-profile",
|
PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "scene-description-test-profile",
|
||||||
Inputs: map[string]scriptorium.ArtifactRef{
|
Inputs: map[string]promptkit.ArtifactRef{
|
||||||
"transcript": scriptorium.InlineWithURI("file:///session.json", `{"units":[1]}`),
|
"transcript": promptkit.InlineWithURI("file:///session.json", `{"units":[1]}`),
|
||||||
"players": scriptorium.Inline(" "),
|
"players": promptkit.Inline(" "),
|
||||||
"party": scriptorium.Inline(" "),
|
"party": promptkit.Inline(" "),
|
||||||
"glossary": scriptorium.Inline(" "),
|
"glossary": promptkit.Inline(" "),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
||||||
"gitea.maximumdirect.net/eric/scriptorium"
|
"gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestScriptoriumPromptPreparesSpellPrompt(t *testing.T) {
|
func TestScriptoriumPromptPreparesSpellPrompt(t *testing.T) {
|
||||||
@@ -82,36 +82,36 @@ func TestScriptoriumPromptDiagnosticsOmitRawMaterials(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareSpellsPrompt(t *testing.T, transcript []byte, players string, party string, glossary string) *scriptorium.PreparedRun {
|
func prepareSpellsPrompt(t *testing.T, transcript []byte, players string, party string, glossary string) *promptkit.PreparedRun {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
registry := llm.NewAssetRegistry()
|
registry := llm.NewAssetRegistry()
|
||||||
if err := RegisterPromptAssets(registry); err != nil {
|
if err := RegisterPromptAssets(registry); err != nil {
|
||||||
t.Fatalf("register spell prompt assets: %v", err)
|
t.Fatalf("register spell prompt assets: %v", err)
|
||||||
}
|
}
|
||||||
options, err := registry.ScriptoriumOptions()
|
options, err := registry.PromptKitOptions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ScriptoriumOptions() error = %v, want nil", err)
|
t.Fatalf("PromptKitOptions() error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
options = append(options, scriptorium.WithProfiles(scriptorium.OpenAICompatibleProfile(scriptorium.OpenAICompatibleProfileConfig{
|
options = append(options, promptkit.WithProfiles(promptkit.OpenAICompatibleProfile(promptkit.OpenAICompatibleProfileConfig{
|
||||||
ID: "spell-test-profile",
|
ID: "spell-test-profile",
|
||||||
Endpoint: "http://127.0.0.1:1/v1",
|
Endpoint: "http://127.0.0.1:1/v1",
|
||||||
Model: "spell-test-model",
|
Model: "spell-test-model",
|
||||||
})))
|
})))
|
||||||
engine, err := scriptorium.NewEngine(scriptorium.Config{Timeout: time.Second}, options...)
|
engine, err := promptkit.NewEngine(promptkit.Config{Timeout: time.Second}, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewEngine() error = %v, want nil", err)
|
t.Fatalf("NewEngine() error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
prepared, err := engine.Prepare(context.Background(), scriptorium.RunRequest{
|
prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{
|
||||||
PromptID: PromptID,
|
PromptID: PromptID,
|
||||||
PromptVersion: SchemaVersion,
|
PromptVersion: SchemaVersion,
|
||||||
ProfileID: "spell-test-profile",
|
ProfileID: "spell-test-profile",
|
||||||
Inputs: map[string]scriptorium.ArtifactRef{
|
Inputs: map[string]promptkit.ArtifactRef{
|
||||||
"transcript": scriptorium.InlineWithURI("file:///session.json", string(transcript)),
|
"transcript": promptkit.InlineWithURI("file:///session.json", string(transcript)),
|
||||||
"spell_catalog": scriptorium.Inline(`{"spell_names":["Cure Wounds"]}`),
|
"spell_catalog": promptkit.Inline(`{"spell_names":["Cure Wounds"]}`),
|
||||||
"npcs": scriptorium.Inline(`{"npcs":[]}`),
|
"npcs": promptkit.Inline(`{"npcs":[]}`),
|
||||||
"players": scriptorium.Inline(players),
|
"players": promptkit.Inline(players),
|
||||||
"party": scriptorium.Inline(party),
|
"party": promptkit.Inline(party),
|
||||||
"glossary": scriptorium.Inline(glossary),
|
"glossary": promptkit.Inline(glossary),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
||||||
"gitea.maximumdirect.net/eric/scriptorium"
|
"gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRegisterPromptAssetsPreparesNormalizationPrompt(t *testing.T) {
|
func TestRegisterPromptAssetsPreparesNormalizationPrompt(t *testing.T) {
|
||||||
@@ -22,22 +22,22 @@ func TestRegisterPromptAssetsPreparesNormalizationPrompt(t *testing.T) {
|
|||||||
if err := RegisterPromptAssets(registry); err != nil {
|
if err := RegisterPromptAssets(registry); err != nil {
|
||||||
t.Fatalf("RegisterPromptAssets() error = %v", err)
|
t.Fatalf("RegisterPromptAssets() error = %v", err)
|
||||||
}
|
}
|
||||||
options, err := registry.ScriptoriumOptions()
|
options, err := registry.PromptKitOptions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ScriptoriumOptions() error = %v", err)
|
t.Fatalf("PromptKitOptions() error = %v", err)
|
||||||
}
|
}
|
||||||
options = append(options, scriptorium.WithProfiles(scriptorium.OpenAICompatibleProfile(scriptorium.OpenAICompatibleProfileConfig{
|
options = append(options, promptkit.WithProfiles(promptkit.OpenAICompatibleProfile(promptkit.OpenAICompatibleProfileConfig{
|
||||||
ID: "normalize-test-profile", Endpoint: "http://127.0.0.1:1/v1", Model: "normalize-test-model",
|
ID: "normalize-test-profile", Endpoint: "http://127.0.0.1:1/v1", Model: "normalize-test-model",
|
||||||
})))
|
})))
|
||||||
engine, err := scriptorium.NewEngine(scriptorium.Config{Timeout: time.Second}, options...)
|
engine, err := promptkit.NewEngine(promptkit.Config{Timeout: time.Second}, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewEngine() error = %v", err)
|
t.Fatalf("NewEngine() error = %v", err)
|
||||||
}
|
}
|
||||||
prepared, err := engine.Prepare(context.Background(), scriptorium.RunRequest{
|
prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{
|
||||||
PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "normalize-test-profile",
|
PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "normalize-test-profile",
|
||||||
Inputs: map[string]scriptorium.ArtifactRef{
|
Inputs: map[string]promptkit.ArtifactRef{
|
||||||
"candidates": scriptorium.Inline(`{"npcs":[{"name":"Mira","source_refs":[]}]}`),
|
"candidates": promptkit.Inline(`{"npcs":[{"name":"Mira","source_refs":[]}]}`),
|
||||||
"transcript": scriptorium.Inline(`{"windows":[{"units":[]}]}`),
|
"transcript": promptkit.Inline(`{"windows":[{"units":[]}]}`),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -55,7 +55,7 @@ func TestRegisterPromptAssetsPreparesNormalizationPrompt(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, index := range []int{2, 4} {
|
for _, index := range []int{2, 4} {
|
||||||
if cache := prepared.Messages[index].CacheControl; cache == nil || cache.Type != scriptorium.CacheControlEphemeral {
|
if cache := prepared.Messages[index].CacheControl; cache == nil || cache.Type != promptkit.CacheControlEphemeral {
|
||||||
t.Errorf("message %d cache control = %#v, want ephemeral", index, cache)
|
t.Errorf("message %d cache control = %#v, want ephemeral", index, cache)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcs"
|
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcs"
|
||||||
scenedescriptionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
scenedescriptionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
||||||
spellextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells"
|
spellextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells"
|
||||||
"gitea.maximumdirect.net/eric/scriptorium"
|
"gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExtractionPromptsShareRenderedPrefix(t *testing.T) {
|
func TestExtractionPromptsShareRenderedPrefix(t *testing.T) {
|
||||||
@@ -31,17 +31,17 @@ func TestExtractionPromptsShareRenderedPrefix(t *testing.T) {
|
|||||||
t.Fatalf("registerPromptAssets() error = %v", err)
|
t.Fatalf("registerPromptAssets() error = %v", err)
|
||||||
}
|
}
|
||||||
engine := newPromptCacheEngine(t, registry)
|
engine := newPromptCacheEngine(t, registry)
|
||||||
commonInputs := map[string]scriptorium.ArtifactRef{
|
commonInputs := map[string]promptkit.ArtifactRef{
|
||||||
"transcript": scriptorium.InlineWithURI("file:///session.json", `{"sentinel":"`+transcriptSentinel+`"}`),
|
"transcript": promptkit.InlineWithURI("file:///session.json", `{"sentinel":"`+transcriptSentinel+`"}`),
|
||||||
"players": scriptorium.Inline(playersSentinel),
|
"players": promptkit.Inline(playersSentinel),
|
||||||
"party": scriptorium.Inline(partySentinel),
|
"party": promptkit.Inline(partySentinel),
|
||||||
"glossary": scriptorium.Inline(glossarySentinel),
|
"glossary": promptkit.Inline(glossarySentinel),
|
||||||
}
|
}
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
promptID string
|
promptID string
|
||||||
promptVersion string
|
promptVersion string
|
||||||
inputs map[string]scriptorium.ArtifactRef
|
inputs map[string]promptkit.ArtifactRef
|
||||||
npcInput bool
|
npcInput bool
|
||||||
spellCatalogInput bool
|
spellCatalogInput bool
|
||||||
}{
|
}{
|
||||||
@@ -52,8 +52,8 @@ func TestExtractionPromptsShareRenderedPrefix(t *testing.T) {
|
|||||||
name: "combat turns",
|
name: "combat turns",
|
||||||
promptID: combatextract.PromptID,
|
promptID: combatextract.PromptID,
|
||||||
promptVersion: combatextract.SchemaVersion,
|
promptVersion: combatextract.SchemaVersion,
|
||||||
inputs: withPromptInputs(commonInputs, map[string]scriptorium.ArtifactRef{
|
inputs: withPromptInputs(commonInputs, map[string]promptkit.ArtifactRef{
|
||||||
"npcs": scriptorium.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
"npcs": promptkit.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
||||||
}),
|
}),
|
||||||
npcInput: true,
|
npcInput: true,
|
||||||
},
|
},
|
||||||
@@ -61,8 +61,8 @@ func TestExtractionPromptsShareRenderedPrefix(t *testing.T) {
|
|||||||
name: "npc interactions",
|
name: "npc interactions",
|
||||||
promptID: interactionextract.PromptID,
|
promptID: interactionextract.PromptID,
|
||||||
promptVersion: interactionextract.SchemaVersion,
|
promptVersion: interactionextract.SchemaVersion,
|
||||||
inputs: withPromptInputs(commonInputs, map[string]scriptorium.ArtifactRef{
|
inputs: withPromptInputs(commonInputs, map[string]promptkit.ArtifactRef{
|
||||||
"npcs": scriptorium.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
"npcs": promptkit.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
||||||
}),
|
}),
|
||||||
npcInput: true,
|
npcInput: true,
|
||||||
},
|
},
|
||||||
@@ -70,19 +70,19 @@ func TestExtractionPromptsShareRenderedPrefix(t *testing.T) {
|
|||||||
name: "spells",
|
name: "spells",
|
||||||
promptID: spellextract.PromptID,
|
promptID: spellextract.PromptID,
|
||||||
promptVersion: spellextract.SchemaVersion,
|
promptVersion: spellextract.SchemaVersion,
|
||||||
inputs: withPromptInputs(commonInputs, map[string]scriptorium.ArtifactRef{
|
inputs: withPromptInputs(commonInputs, map[string]promptkit.ArtifactRef{
|
||||||
"npcs": scriptorium.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
"npcs": promptkit.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
||||||
"spell_catalog": scriptorium.Inline(`{"sentinel":"` + catalogSentinel + `"}`),
|
"spell_catalog": promptkit.Inline(`{"sentinel":"` + catalogSentinel + `"}`),
|
||||||
}),
|
}),
|
||||||
npcInput: true,
|
npcInput: true,
|
||||||
spellCatalogInput: true,
|
spellCatalogInput: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var sharedPrefix []scriptorium.RenderedMessage
|
var sharedPrefix []promptkit.RenderedMessage
|
||||||
for _, testCase := range cases {
|
for _, testCase := range cases {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
prepared, err := engine.Prepare(context.Background(), scriptorium.RunRequest{
|
prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{
|
||||||
PromptID: testCase.promptID,
|
PromptID: testCase.promptID,
|
||||||
PromptVersion: testCase.promptVersion,
|
PromptVersion: testCase.promptVersion,
|
||||||
ProfileID: "prompt-cache-test-profile",
|
ProfileID: "prompt-cache-test-profile",
|
||||||
@@ -100,7 +100,7 @@ func TestExtractionPromptsShareRenderedPrefix(t *testing.T) {
|
|||||||
t.Fatalf("prepared prompt has %d messages, want lane-specific suffix after transcript", len(prepared.Messages))
|
t.Fatalf("prepared prompt has %d messages, want lane-specific suffix after transcript", len(prepared.Messages))
|
||||||
}
|
}
|
||||||
if sharedPrefix == nil {
|
if sharedPrefix == nil {
|
||||||
sharedPrefix = append([]scriptorium.RenderedMessage(nil), prefix...)
|
sharedPrefix = append([]promptkit.RenderedMessage(nil), prefix...)
|
||||||
} else if !reflect.DeepEqual(prefix, sharedPrefix) {
|
} else if !reflect.DeepEqual(prefix, sharedPrefix) {
|
||||||
t.Fatalf("rendered prefix = %#v, want %#v", prefix, sharedPrefix)
|
t.Fatalf("rendered prefix = %#v, want %#v", prefix, sharedPrefix)
|
||||||
}
|
}
|
||||||
@@ -114,24 +114,24 @@ func TestExtractionPromptsShareRenderedPrefix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPromptCacheEngine(t *testing.T, registry *llm.AssetRegistry) *scriptorium.Engine {
|
func newPromptCacheEngine(t *testing.T, registry *llm.AssetRegistry) *promptkit.Engine {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
options, err := registry.ScriptoriumOptions()
|
options, err := registry.PromptKitOptions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ScriptoriumOptions() error = %v", err)
|
t.Fatalf("PromptKitOptions() error = %v", err)
|
||||||
}
|
}
|
||||||
options = append(options, scriptorium.WithProfiles(scriptorium.OpenAICompatibleProfile(scriptorium.OpenAICompatibleProfileConfig{
|
options = append(options, promptkit.WithProfiles(promptkit.OpenAICompatibleProfile(promptkit.OpenAICompatibleProfileConfig{
|
||||||
ID: "prompt-cache-test-profile", Endpoint: "http://127.0.0.1:1/v1", Model: "prompt-cache-test-model",
|
ID: "prompt-cache-test-profile", Endpoint: "http://127.0.0.1:1/v1", Model: "prompt-cache-test-model",
|
||||||
})))
|
})))
|
||||||
engine, err := scriptorium.NewEngine(scriptorium.Config{Timeout: time.Second}, options...)
|
engine, err := promptkit.NewEngine(promptkit.Config{Timeout: time.Second}, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewEngine() error = %v", err)
|
t.Fatalf("NewEngine() error = %v", err)
|
||||||
}
|
}
|
||||||
return engine
|
return engine
|
||||||
}
|
}
|
||||||
|
|
||||||
func withPromptInputs(inputs, extras map[string]scriptorium.ArtifactRef) map[string]scriptorium.ArtifactRef {
|
func withPromptInputs(inputs, extras map[string]promptkit.ArtifactRef) map[string]promptkit.ArtifactRef {
|
||||||
merged := make(map[string]scriptorium.ArtifactRef, len(inputs)+len(extras))
|
merged := make(map[string]promptkit.ArtifactRef, len(inputs)+len(extras))
|
||||||
for name, input := range inputs {
|
for name, input := range inputs {
|
||||||
merged[name] = input
|
merged[name] = input
|
||||||
}
|
}
|
||||||
@@ -141,14 +141,14 @@ func withPromptInputs(inputs, extras map[string]scriptorium.ArtifactRef) map[str
|
|||||||
return merged
|
return merged
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertRenderedInputAfter(t *testing.T, messages []scriptorium.RenderedMessage, sentinel string, index int) {
|
func assertRenderedInputAfter(t *testing.T, messages []promptkit.RenderedMessage, sentinel string, index int) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if inputIndex := renderedInputMessageIndex(t, messages, sentinel); inputIndex <= index {
|
if inputIndex := renderedInputMessageIndex(t, messages, sentinel); inputIndex <= index {
|
||||||
t.Fatalf("input sentinel %q rendered at message %d, want after transcript message %d", sentinel, inputIndex, index)
|
t.Fatalf("input sentinel %q rendered at message %d, want after transcript message %d", sentinel, inputIndex, index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderedInputMessageIndex(t *testing.T, messages []scriptorium.RenderedMessage, sentinel string) int {
|
func renderedInputMessageIndex(t *testing.T, messages []promptkit.RenderedMessage, sentinel string) int {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
index := -1
|
index := -1
|
||||||
occurrences := 0
|
occurrences := 0
|
||||||
|
|||||||
Reference in New Issue
Block a user