Files
audita/internal/framework/llm/runtime.go

134 lines
4.3 KiB
Go
Raw Normal View History

package llm
import (
"context"
"errors"
"fmt"
"io/fs"
"net/http"
"path/filepath"
"strings"
"sync/atomic"
2026-10-11 01:40:38 +00:00
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/responseschema"
"gitea.maximumdirect.net/eric/audita/v3/internal/prompts"
ck "go.maximumdirect.net/configkit"
pk "go.maximumdirect.net/promptkit"
)
// Runtime owns the captured engine shared by all processing clients.
type Runtime struct {
Config config.Config
Engine *pk.Engine
SourcePaths []string
nextCallID atomic.Uint64
}
type AssetError struct{ Err error }
func (e *AssetError) Error() string { return e.Err.Error() }
func (e *AssetError) Unwrap() error { return e.Err }
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) {
engine, err := NewEngine(ctx, loaded, client)
if err != nil {
return nil, err
}
paths, err := capturedSourcePaths(engine)
if err != nil {
return nil, fmt.Errorf("inspect captured LLM sources: %w", err)
}
return &Runtime{Config: loaded.Config, Engine: engine, SourcePaths: paths}, nil
}
// NewEngine loads the captured PromptKit resources without processing collision checks.
func NewEngine(ctx context.Context, loaded *ck.Result[config.Config], client *http.Client) (*pk.Engine, error) {
return newEngineWithPromptFS(ctx, loaded, client, prompts.Filesystem())
}
func newEngineWithPromptFS(ctx context.Context, loaded *ck.Result[config.Config], client *http.Client, promptFS fs.FS) (*pk.Engine, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
if loaded == nil {
return nil, fmt.Errorf("application configuration is required")
}
embedded, err := pk.FilesystemInputs("Audita embedded prompts", promptFS, prompts.AssetRoot)
if err != nil {
return nil, &AssetError{Err: fmt.Errorf("discover embedded prompts: %w", err)}
}
sources := pk.ConfigurationSources{Underlays: []ck.Underlay{{Inputs: embedded}}}
for _, source := range loaded.Sources() {
if source.Layer != ck.LayerMain || source.ImportedFrom != nil {
continue
}
context, ok := loaded.SourceContext(source.ID)
if !ok {
break
}
var filesystemContext ck.FilesystemContext
if source.Kind == ck.SourceKindFile && source.Path != "" {
base := filepath.Dir(source.Path)
root := base
if context.BaseDir != "." {
for range strings.Split(context.BaseDir, "/") {
root = filepath.Dir(root)
}
}
filesystemContext = ck.FilesystemContext{Root: root, BaseDir: base}
} else {
filesystem := ck.NewFilesystem("Audita operator prompts", context.FS)
filesystemContext = ck.FilesystemContext{FS: &filesystem, Root: ".", BaseDir: context.BaseDir}
}
sources.Underlays = append(sources.Underlays, ck.Underlay{Inputs: []ck.Input{{
Source: ck.Bytes("Audita automatic prompt imports", []byte(operatorImports)), Imports: true,
Filesystem: &filesystemContext,
}}})
break
}
engine, err := pk.NewEngine(pk.Config{Sources: sources, HTTPClient: client}, pk.WithSchemaFS(responseschema.Filesystem(), responseschema.AssetRoot))
if err != nil {
return nil, embeddedAssetOrInputError(ctx, embedded, fmt.Errorf("assemble LLM runtime: %w", err))
}
for _, meta := range prompts.RegisteredMetadata() {
if _, err := engine.InspectPrompt(ctx, meta.PromptID); err != nil {
return nil, embeddedAssetOrInputError(ctx, embedded, fmt.Errorf("inspect prompt %s: %w", meta.PromptID, err))
}
}
return engine, nil
}
func embeddedAssetOrInputError(ctx context.Context, inputs []ck.Input, cause error) error {
if err := ctx.Err(); err != nil {
return err
}
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 _, err = engine.InspectPrompt(ctx, meta.PromptID); err != nil {
break
}
}
}
if err := ctx.Err(); err != nil {
return err
}
if err != nil {
return &AssetError{Err: fmt.Errorf("validate embedded prompt assets: %w", err)}
}
return cause
}
func IsAssetError(err error) bool { var target *AssetError; return errors.As(err, &target) }