package llm import ( "context" "errors" "fmt" "io/fs" "net/http" "path/filepath" "strings" "sync/atomic" "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) { return newRuntimeWithPromptFS(ctx, loaded, client, prompts.Filesystem()) } 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 } 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)) } } 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 } 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) }