Load bundled and operator prompt resources

This commit is contained in:
2026-11-10 16:13:53 +00:00
parent 7c64592cab
commit 497a1a18c5
13 changed files with 502 additions and 353 deletions

View File

@@ -10,147 +10,108 @@ import (
"strings"
"sync/atomic"
ck "gitea.maximumdirect.net/eric/ConfigKit"
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
"gitea.maximumdirect.net/eric/audita/v3/internal/core/redaction"
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/responseschema"
"gitea.maximumdirect.net/eric/audita/v3/internal/prompts"
pk "gitea.maximumdirect.net/eric/promptkit"
ck "go.maximumdirect.net/configkit"
pk "go.maximumdirect.net/promptkit"
)
// Runtime owns one PromptKit engine and the validated selections for both roles.
// The same engine is shared by the role-bound clients during processing.
// Runtime owns the captured engine shared by all processing clients.
type Runtime struct {
Config config.Config
Engine *pk.Engine
Profiles map[string]pk.ProfileInspection
// SourcePaths contains host configuration and message files captured by PromptKit.
Config config.Config
Engine *pk.Engine
SourcePaths []string
nextCallID atomic.Uint64
}
// AssetError indicates a broken embedded prompt or schema rather than an
// operator-provided setting or profile.
type AssetError struct{ Err error }
func (e *AssetError) Error() string { return e.Err.Error() }
func (e *AssetError) Unwrap() error { return e.Err }
func NewRuntime(ctx context.Context, c config.Config, mainPath string, client *http.Client) (*Runtime, error) {
return newRuntimeWithPromptFS(ctx, c, mainPath, client, prompts.Filesystem())
const operatorFilesystem = "Audita operator prompts"
const operatorImports = `config:
imports:
- path: prompts.yml
required: false
- dir: prompts
required: false
recursive: true
pattern: ["*.yml", "*.yaml"]
`
func NewRuntime(ctx context.Context, loaded *ck.Result[config.Config], client *http.Client) (*Runtime, error) {
return newRuntimeWithPromptFS(ctx, loaded, client, prompts.Filesystem())
}
func newRuntimeWithPromptFS(ctx context.Context, c config.Config, mainPath string, client *http.Client, promptFS fs.FS) (*Runtime, error) {
func newRuntimeWithPromptFS(ctx context.Context, loaded *ck.Result[config.Config], client *http.Client, promptFS fs.FS) (*Runtime, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
profileDir := c.ProfilesDir
if !filepath.IsAbs(profileDir) {
profileDir = filepath.Join(filepath.Dir(mainPath), profileDir)
if loaded == nil {
return nil, fmt.Errorf("application configuration is required")
}
promptInputs, err := pk.FilesystemInputs("Audita embedded prompts", promptFS, prompts.AssetRoot)
embedded, err := pk.FilesystemInputs("Audita embedded prompts", promptFS, prompts.AssetRoot)
if err != nil {
if ctxErr := ctx.Err(); ctxErr != nil {
return nil, ctxErr
}
return nil, &AssetError{Err: fmt.Errorf("discover embedded prompts: %w", err)}
}
profileInputs, err := pk.DirectoryInputs(profileDir)
if err != nil {
if ctxErr := ctx.Err(); ctxErr != nil {
return nil, ctxErr
sources := pk.ConfigurationSources{Underlays: []ck.Underlay{{Inputs: embedded}}}
var hostRoot string
for _, source := range loaded.Sources() {
if source.Layer != ck.LayerMain || source.ImportedFrom != nil {
continue
}
return nil, fmt.Errorf("discover operator definitions: %w", err)
}
if err := ctx.Err(); err != nil {
return nil, err
}
applicationBackends := make(map[string]any, len(c.Backends))
for id := range c.Backends {
backend := c.Backends[id]
applicationBackends[id] = map[string]any{
"endpoint": backend.Endpoint,
"concurrency_limit": backend.Concurrency,
"queue_capacity": backend.QueueCapacity,
"api_key_env": nil,
context, ok := loaded.SourceContext(source.ID)
if !ok {
break
}
filesystem := ck.NewFilesystem(operatorFilesystem, context.FS)
sources.Underlays = append(sources.Underlays, ck.Underlay{Inputs: []ck.Input{{
Source: ck.Bytes("Audita automatic prompt imports", []byte(operatorImports)), Imports: true,
Filesystem: &ck.FilesystemContext{FS: &filesystem, Root: ".", BaseDir: context.BaseDir},
}}})
if source.Kind == ck.SourceKindFile && source.Path != "" {
hostRoot = filepath.Dir(source.Path)
if context.BaseDir != "." {
for range strings.Split(context.BaseDir, "/") {
hostRoot = filepath.Dir(hostRoot)
}
}
}
break
}
sources := pk.ConfigurationSources{
Underlays: []ck.Underlay{
{Inputs: promptInputs},
{Inputs: profileInputs},
},
Main: &ck.Input{Source: ck.Mapping("application backends", map[string]any{"backends": applicationBackends})},
}
engine, err := pk.NewEngine(pk.Config{
Sources: sources,
HTTPClient: client,
TransientRetries: &c.LLM.TransientRetries,
}, pk.WithSchemaFS(responseschema.Filesystem(), responseschema.AssetRoot))
engine, err := pk.NewEngine(pk.Config{Sources: sources, HTTPClient: client}, pk.WithSchemaFS(responseschema.Filesystem(), responseschema.AssetRoot))
if err != nil {
return nil, embeddedAssetOrInputError(ctx, promptInputs, fmt.Errorf("assemble LLM runtime: %w", err))
return nil, embeddedAssetOrInputError(ctx, embedded, fmt.Errorf("assemble LLM runtime: %w", err))
}
for _, meta := range prompts.RegisteredMetadata() {
if _, err := engine.InspectPrompt(ctx, meta.PromptID, meta.PromptVersion); err != nil {
cause := fmt.Errorf("inspect prompt %s: %w", meta.PromptID, err)
return nil, embeddedAssetOrInputError(ctx, promptInputs, cause)
if _, err := engine.InspectPrompt(ctx, meta.PromptID); err != nil {
return nil, embeddedAssetOrInputError(ctx, embedded, fmt.Errorf("inspect prompt %s: %w", meta.PromptID, err))
}
}
runtime := &Runtime{Config: c, Engine: engine, Profiles: make(map[string]pk.ProfileInspection, 2)}
for _, selection := range []struct{ role, id string }{{"proposal", c.LLM.ProposalProfile}, {"validation", c.LLM.ValidationProfile}} {
profile, err := engine.InspectProfile(ctx, selection.id)
if err != nil {
return nil, fmt.Errorf("inspect %s profile: %w", selection.role, err)
}
target := profile.EffectiveModelParams
backend, ok := c.Backends[target.BackendID]
if !ok {
return nil, fmt.Errorf("%s profile must select a registered application backend", selection.role)
}
if strings.TrimSpace(target.Model) == "" {
return nil, fmt.Errorf("%s profile must select a nonblank model", selection.role)
}
if strings.TrimSpace(target.Endpoint) == "" {
return nil, fmt.Errorf("%s profile must resolve to a nonblank endpoint", selection.role)
}
endpoint := func(value string) string { return strings.TrimRight(strings.TrimSpace(value), "/") }
if endpoint(target.Endpoint) != endpoint(backend.Endpoint) {
return nil, fmt.Errorf("%s profile endpoint must match its registered backend", selection.role)
}
if target.APIKeyEnv != "" {
return nil, fmt.Errorf("%s profile must use the backend credential, not its own environment lookup", selection.role)
}
if profile.APIKeyRequired && backend.Credential == nil {
return nil, fmt.Errorf("%s profile requires a backend credential", selection.role)
}
runtime.Profiles[selection.role] = *profile
}
runtime.SourcePaths, err = capturedSourcePaths(engine)
paths, err := capturedSourcePaths(engine, hostRoot)
if err != nil {
return nil, fmt.Errorf("inspect captured LLM sources: %w", err)
}
return runtime, nil
return &Runtime{Config: loaded.Config, Engine: engine, SourcePaths: paths}, nil
}
func embeddedAssetOrInputError(ctx context.Context, promptInputs []ck.Input, cause error) error {
func embeddedAssetOrInputError(ctx context.Context, inputs []ck.Input, cause error) error {
if err := ctx.Err(); err != nil {
return err
}
embeddedSources := pk.ConfigurationSources{
Underlays: []ck.Underlay{{Inputs: promptInputs}},
}
diagnosticEngine, err := pk.NewEngine(pk.Config{Sources: embeddedSources},
pk.WithSchemaFS(responseschema.Filesystem(), responseschema.AssetRoot))
engine, err := pk.NewEngine(pk.Config{Sources: pk.ConfigurationSources{Underlays: []ck.Underlay{{Inputs: inputs}}}}, pk.WithSchemaFS(responseschema.Filesystem(), responseschema.AssetRoot))
if err == nil {
for _, meta := range prompts.RegisteredMetadata() {
if _, inspectErr := diagnosticEngine.InspectPrompt(ctx, meta.PromptID, meta.PromptVersion); inspectErr != nil {
err = fmt.Errorf("inspect embedded prompt %s: %w", meta.PromptID, inspectErr)
if _, err = engine.InspectPrompt(ctx, meta.PromptID); err != nil {
break
}
}
}
if ctxErr := ctx.Err(); ctxErr != nil {
return ctxErr
if err := ctx.Err(); err != nil {
return err
}
if err != nil {
return &AssetError{Err: fmt.Errorf("validate embedded prompt assets: %w", err)}
@@ -160,8 +121,6 @@ func embeddedAssetOrInputError(ctx context.Context, promptInputs []ck.Input, cau
func IsAssetError(err error) bool { var target *AssetError; return errors.As(err, &target) }
// RedactError replaces every configured resolved backend secret before a
// library error is surfaced in CLI output or an artifact.
func RedactError(err error, c config.Config) string {
if err == nil {
return ""