Move option source construction into the internal engine
This commit is contained in:
@@ -93,7 +93,7 @@ func WithBackend(backend Backend) Option {
|
|||||||
queueCapacity = *backend.QueueCapacity
|
queueCapacity = *backend.QueueCapacity
|
||||||
}
|
}
|
||||||
return optionFunc(func(options *engineOptions) error {
|
return optionFunc(func(options *engineOptions) error {
|
||||||
options.backends = append(options.backends, domain.Backend{
|
options.internal.AddBackend(domain.Backend{
|
||||||
ID: backend.ID,
|
ID: backend.ID,
|
||||||
Endpoint: backend.Endpoint,
|
Endpoint: backend.Endpoint,
|
||||||
APIKeyEnv: backend.APIKeyEnv,
|
APIKeyEnv: backend.APIKeyEnv,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ stable JSON values.
|
|||||||
|
|
||||||
## Construction And Pool Lifecycle
|
## Construction And Pool Lifecycle
|
||||||
|
|
||||||
Each root `NewEngine` call obtains a normalized capacity-policy snapshot from
|
Each internal engine construction obtains a normalized capacity-policy snapshot from
|
||||||
its immutable backend registry and constructs a new `Manager`. The manager
|
its immutable backend registry and constructs a new `Manager`. The manager
|
||||||
creates one pool for each limited backend ID. It has no package-global mutable
|
creates one pool for each limited backend ID. It has no package-global mutable
|
||||||
state, background workers, shutdown protocol, or persistence, so engines with
|
state, background workers, shutdown protocol, or persistence, so engines with
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ owns the observable outbound HTTP contract. The
|
|||||||
[framework format reference](../formats.md) owns the profile and prompt
|
[framework format reference](../formats.md) owns the profile and prompt
|
||||||
settings consumed by the client.
|
settings consumed by the client.
|
||||||
|
|
||||||
The concrete client remains under `internal/llm`. The root engine assembles it
|
The concrete client remains under `internal/llm`. The internal engine assembles it
|
||||||
as the default implementation behind Promptkit's public client boundary.
|
as the default implementation behind Promptkit's public client boundary.
|
||||||
|
|
||||||
## Components And Flow
|
## Components And Flow
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ contributor workflow and validation.
|
|||||||
| `internal/llm` | Defines the internal generation boundary and implements outbound OpenAI-compatible chat requests from resolved execution targets, including bounded structured non-success response decoding, successful-response decoding, authentication, deadline handling, and ownership of the OpenAI-compatible reserved request-field policy. | [Internal model client](llm.md) |
|
| `internal/llm` | Defines the internal generation boundary and implements outbound OpenAI-compatible chat requests from resolved execution targets, including bounded structured non-success response decoding, successful-response decoding, authentication, deadline handling, and ownership of the OpenAI-compatible reserved request-field policy. | [Internal model client](llm.md) |
|
||||||
| `internal/usecase` | Resolves prompt definitions and hashes, profiles, backends, and targets for exact inspection and request settings for preparation, and coordinates ordinary execution and one-attempt prepared execution across internal sources, rendering, artifact loading, operation-local validation plans, generation, capacity, and bounded repair. | [Internal runner](runner.md), [prepared-execution implementation](../../internal/usecase/prepared_execution.go) |
|
| `internal/usecase` | Resolves prompt definitions and hashes, profiles, backends, and targets for exact inspection and request settings for preparation, and coordinates ordinary execution and one-attempt prepared execution across internal sources, rendering, artifact loading, operation-local validation plans, generation, capacity, and bounded repair. | [Internal runner](runner.md), [prepared-execution implementation](../../internal/usecase/prepared_execution.go) |
|
||||||
|
|
||||||
The root package assembles these internal components without exposing their
|
The internal engine assembles these internal components without exposing their
|
||||||
representations. Consumers depend only on the root facade.
|
representations. Consumers depend only on the root facade.
|
||||||
|
|
||||||
## Maintenance
|
## Maintenance
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ repositories described above.
|
|||||||
`internal/catalog` validates the imported immutable OpenRouter and
|
`internal/catalog` validates the imported immutable OpenRouter and
|
||||||
Rakestrawhome asset modules as one private adapter boundary. It enforces their
|
Rakestrawhome asset modules as one private adapter boundary. It enforces their
|
||||||
manifest, layout, profile ownership, inheritance, and secret-safety rules
|
manifest, layout, profile ownership, inheritance, and secret-safety rules
|
||||||
before returning raw catalog sources. Root assembly uses those validated
|
before returning raw catalog sources. Internal engine assembly uses those validated
|
||||||
catalogs as the maintained lowest-precedence profile source.
|
catalogs as the maintained lowest-precedence profile source.
|
||||||
|
|
||||||
The overlay repository consults the next repository only when the
|
The overlay repository consults the next repository only when the
|
||||||
|
|||||||
@@ -55,15 +55,15 @@ packages or participate in internal assembly.
|
|||||||
|
|
||||||
The internal engine assembles one immutable backend registry, one capacity manager,
|
The internal engine assembles one immutable backend registry, one capacity manager,
|
||||||
the internal repositories, renderer, validator, outbound client, and use-case
|
the internal repositories, renderer, validator, outbound client, and use-case
|
||||||
runner while translating public values and errors at the library boundary. The
|
runner. The registry contains validated maintained definitions plus engine-scoped
|
||||||
registry contains validated maintained definitions plus engine-scoped consumer
|
consumer additions. The root facade translates public values and errors at the
|
||||||
additions.
|
library boundary.
|
||||||
The facade constructs the capacity manager from the registry's immutable
|
The internal engine constructs the capacity manager from the registry's immutable
|
||||||
policy snapshot, wraps the selected built-in or injected model client, and
|
policy snapshot, wraps the selected built-in or injected model client, and
|
||||||
supplies bounded admission to the runner. The defaults and renderer depend on
|
supplies bounded admission to the runner. The defaults and renderer depend on
|
||||||
the domain model. Prompt-definition and profile repositories use the domain
|
the domain model. Prompt-definition and profile repositories use the domain
|
||||||
model, file catalog, and YAML decoder. The catalog adapter validates imported
|
model, file catalog, and YAML decoder. The catalog adapter validates imported
|
||||||
immutable maintained data before root assembly supplies it to the profile
|
immutable maintained data before internal engine assembly supplies it to the profile
|
||||||
package. Artifact reading uses the domain
|
package. Artifact reading uses the domain
|
||||||
model and application-neutral defaults. Validation uses the domain model, file
|
model and application-neutral defaults. Validation uses the domain model, file
|
||||||
catalog, and JSON Schema implementation. The model client uses the domain
|
catalog, and JSON Schema implementation. The model client uses the domain
|
||||||
|
|||||||
@@ -229,6 +229,17 @@ capacity managers, catalogs, or runners. Public options still have their existin
|
|||||||
names, signatures, ordering, capture, and error behavior. No transitional state
|
names, signatures, ordering, capture, and error behavior. No transitional state
|
||||||
mapping or duplicate source-assembly path remains.
|
mapping or duplicate source-assembly path remains.
|
||||||
|
|
||||||
|
### Completion
|
||||||
|
|
||||||
|
- [x] Wrapped the root option state around internal engine options with private
|
||||||
|
construction state.
|
||||||
|
- [x] Moved filesystem and file source setup, source replacement, profile
|
||||||
|
repository construction, validator construction, and backend accumulation
|
||||||
|
into internal option methods.
|
||||||
|
- [x] Kept ordered public option application, boundary conversion, collaborator
|
||||||
|
adaptation, and error translation in the root facade.
|
||||||
|
- [x] Updated current architecture and internal ownership documentation.
|
||||||
|
|
||||||
## Stage 5: Guard The Public Boundary And Dependency Direction
|
## Stage 5: Guard The Public Boundary And Dependency Direction
|
||||||
|
|
||||||
Purpose: protect the intended architecture without freezing private layout.
|
Purpose: protect the intended architecture without freezing private layout.
|
||||||
|
|||||||
129
engine.go
129
engine.go
@@ -6,19 +6,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
artifactadapter "gitea.maximumdirect.net/eric/promptkit/internal/artifact"
|
|
||||||
"gitea.maximumdirect.net/eric/promptkit/internal/domain"
|
"gitea.maximumdirect.net/eric/promptkit/internal/domain"
|
||||||
internalengine "gitea.maximumdirect.net/eric/promptkit/internal/engine"
|
internalengine "gitea.maximumdirect.net/eric/promptkit/internal/engine"
|
||||||
"gitea.maximumdirect.net/eric/promptkit/internal/llm"
|
|
||||||
"gitea.maximumdirect.net/eric/promptkit/internal/profile"
|
|
||||||
"gitea.maximumdirect.net/eric/promptkit/internal/promptdef"
|
|
||||||
"gitea.maximumdirect.net/eric/promptkit/internal/usecase"
|
"gitea.maximumdirect.net/eric/promptkit/internal/usecase"
|
||||||
"gitea.maximumdirect.net/eric/promptkit/internal/validate"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrInvalidConfig identifies invalid engine construction, including missing
|
// ErrInvalidConfig identifies invalid engine construction, including missing
|
||||||
@@ -134,20 +126,7 @@ func (f optionFunc) apply(options *engineOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type engineOptions struct {
|
type engineOptions struct {
|
||||||
llmClient llm.Client
|
internal internalengine.Options
|
||||||
artifactReader artifactadapter.Reader
|
|
||||||
promptDefs promptdef.Repository
|
|
||||||
profiles profile.Repository
|
|
||||||
fallbackProfiles profile.Repository
|
|
||||||
memoryProfiles profile.Repository
|
|
||||||
backends []domain.Backend
|
|
||||||
validator validate.Validator
|
|
||||||
promptSource bool
|
|
||||||
profileSource bool
|
|
||||||
fallbackProfileSource bool
|
|
||||||
memorySource bool
|
|
||||||
validatorSource bool
|
|
||||||
artifactSource bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithLLMClient replaces the built-in model client used by [Engine.Run] and
|
// WithLLMClient replaces the built-in model client used by [Engine.Run] and
|
||||||
@@ -163,7 +142,7 @@ func WithLLMClient(client LLMClient) Option {
|
|||||||
if client == nil {
|
if client == nil {
|
||||||
return ErrInvalidConfig
|
return ErrInvalidConfig
|
||||||
}
|
}
|
||||||
options.llmClient = publicLLMClientAdapter{client: client}
|
options.internal.SetLLMClient(publicLLMClientAdapter{client: client})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -178,8 +157,7 @@ func WithArtifactReader(reader ArtifactReader) Option {
|
|||||||
if reader == nil {
|
if reader == nil {
|
||||||
return ErrInvalidConfig
|
return ErrInvalidConfig
|
||||||
}
|
}
|
||||||
options.artifactReader = publicArtifactReaderAdapter{reader: reader}
|
options.internal.SetArtifactReader(publicArtifactReaderAdapter{reader: reader})
|
||||||
options.artifactSource = true
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -193,15 +171,7 @@ func WithArtifactReader(reader ArtifactReader) Option {
|
|||||||
// prompt-source options.
|
// prompt-source options.
|
||||||
func WithPromptFS(fsys fs.FS, root string) Option {
|
func WithPromptFS(fsys fs.FS, root string) Option {
|
||||||
return optionFunc(func(options *engineOptions) error {
|
return optionFunc(func(options *engineOptions) error {
|
||||||
if fsys == nil {
|
return options.internal.SetPromptFS(fsys, root)
|
||||||
return ErrInvalidConfig
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(root) == "" {
|
|
||||||
return ErrInvalidConfig
|
|
||||||
}
|
|
||||||
options.promptDefs = promptdef.NewFSRepository(fsys, root)
|
|
||||||
options.promptSource = true
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,13 +182,7 @@ func WithPromptFS(fsys fs.FS, root string) Option {
|
|||||||
// This option replaces Config.PromptDir and earlier prompt-source options.
|
// This option replaces Config.PromptDir and earlier prompt-source options.
|
||||||
func WithPromptFile(path string) Option {
|
func WithPromptFile(path string) Option {
|
||||||
return optionFunc(func(options *engineOptions) error {
|
return optionFunc(func(options *engineOptions) error {
|
||||||
fsys, root, err := fileSource(path)
|
return options.internal.SetPromptFile(path)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
options.promptDefs = promptdef.NewFileRepository(fsys, root, filepath.Dir(path))
|
|
||||||
options.promptSource = true
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,15 +196,7 @@ func WithPromptFile(path string) Option {
|
|||||||
// FS profile-source options, but remains below WithProfiles in precedence.
|
// FS profile-source options, but remains below WithProfiles in precedence.
|
||||||
func WithProfileFS(fsys fs.FS, root string) Option {
|
func WithProfileFS(fsys fs.FS, root string) Option {
|
||||||
return optionFunc(func(options *engineOptions) error {
|
return optionFunc(func(options *engineOptions) error {
|
||||||
if fsys == nil {
|
return options.internal.SetProfileFS(fsys, root)
|
||||||
return ErrInvalidConfig
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(root) == "" {
|
|
||||||
return ErrInvalidConfig
|
|
||||||
}
|
|
||||||
options.profiles = profile.NewFSRepository(fsys, root)
|
|
||||||
options.profileSource = true
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,13 +210,7 @@ func WithProfileFS(fsys fs.FS, root string) Option {
|
|||||||
// precedence.
|
// precedence.
|
||||||
func WithProfileFile(path string) Option {
|
func WithProfileFile(path string) Option {
|
||||||
return optionFunc(func(options *engineOptions) error {
|
return optionFunc(func(options *engineOptions) error {
|
||||||
fsys, root, err := fileSource(path)
|
return options.internal.SetProfileFile(path)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
options.profiles = profile.NewFSRepository(fsys, root)
|
|
||||||
options.profileSource = true
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,15 +235,7 @@ func WithProfileFile(path string) Option {
|
|||||||
// failover.
|
// failover.
|
||||||
func WithFallbackProfileFS(fsys fs.FS, root string) Option {
|
func WithFallbackProfileFS(fsys fs.FS, root string) Option {
|
||||||
return optionFunc(func(options *engineOptions) error {
|
return optionFunc(func(options *engineOptions) error {
|
||||||
if fsys == nil {
|
return options.internal.SetFallbackProfileFS(fsys, root)
|
||||||
return ErrInvalidConfig
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(root) == "" {
|
|
||||||
return ErrInvalidConfig
|
|
||||||
}
|
|
||||||
options.fallbackProfiles = profile.NewFSRepository(fsys, root)
|
|
||||||
options.fallbackProfileSource = true
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,13 +254,7 @@ func WithProfiles(profiles ...Profile) Option {
|
|||||||
for index, publicProfile := range profiles {
|
for index, publicProfile := range profiles {
|
||||||
definitions[index] = toDomainProfile(publicProfile)
|
definitions[index] = toDomainProfile(publicProfile)
|
||||||
}
|
}
|
||||||
repo, err := profile.NewMemoryRepository(definitions)
|
return options.internal.SetMemoryProfiles(definitions)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
options.memoryProfiles = repo
|
|
||||||
options.memorySource = true
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,15 +266,7 @@ func WithProfiles(profiles ...Profile) Option {
|
|||||||
// replaces Config.SchemaDir and earlier schema-source options.
|
// replaces Config.SchemaDir and earlier schema-source options.
|
||||||
func WithSchemaFS(fsys fs.FS, root string) Option {
|
func WithSchemaFS(fsys fs.FS, root string) Option {
|
||||||
return optionFunc(func(options *engineOptions) error {
|
return optionFunc(func(options *engineOptions) error {
|
||||||
if fsys == nil {
|
return options.internal.SetSchemaFS(fsys, root)
|
||||||
return ErrInvalidConfig
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(root) == "" {
|
|
||||||
return ErrInvalidConfig
|
|
||||||
}
|
|
||||||
options.validator = validate.NewFSValidator(fsys, root)
|
|
||||||
options.validatorSource = true
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,13 +277,7 @@ func WithSchemaFS(fsys fs.FS, root string) Option {
|
|||||||
// replaces Config.SchemaDir and earlier schema-source options.
|
// replaces Config.SchemaDir and earlier schema-source options.
|
||||||
func WithSchemaFile(path string) Option {
|
func WithSchemaFile(path string) Option {
|
||||||
return optionFunc(func(options *engineOptions) error {
|
return optionFunc(func(options *engineOptions) error {
|
||||||
fsys, root, err := fileSource(path)
|
return options.internal.SetSchemaFile(path)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
options.validator = validate.NewFSValidator(fsys, root)
|
|
||||||
options.validatorSource = true
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,15 +306,7 @@ func NewEngine(cfg Config, opts ...Option) (*Engine, error) {
|
|||||||
|
|
||||||
assembled, err := internalengine.New(
|
assembled, err := internalengine.New(
|
||||||
internalengine.Config{PromptDir: cfg.PromptDir, ProfileDir: cfg.ProfileDir, SchemaDir: cfg.SchemaDir, Timeout: cfg.Timeout, HTTPClient: cfg.HTTPClient},
|
internalengine.Config{PromptDir: cfg.PromptDir, ProfileDir: cfg.ProfileDir, SchemaDir: cfg.SchemaDir, Timeout: cfg.Timeout, HTTPClient: cfg.HTTPClient},
|
||||||
internalengine.Options{
|
options.internal,
|
||||||
LLMClient: options.llmClient, ArtifactReader: options.artifactReader,
|
|
||||||
PromptDefinitions: options.promptDefs, Profiles: options.profiles,
|
|
||||||
FallbackProfiles: options.fallbackProfiles, MemoryProfiles: options.memoryProfiles,
|
|
||||||
Backends: options.backends, Validator: options.validator,
|
|
||||||
PromptSource: options.promptSource, ProfileSource: options.profileSource,
|
|
||||||
FallbackProfileSource: options.fallbackProfileSource, MemorySource: options.memorySource,
|
|
||||||
ValidatorSource: options.validatorSource, ArtifactSource: options.artifactSource,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%w: %v", ErrInvalidConfig, err)
|
return nil, fmt.Errorf("%w: %v", ErrInvalidConfig, err)
|
||||||
@@ -400,25 +314,6 @@ func NewEngine(cfg Config, opts ...Option) (*Engine, error) {
|
|||||||
return &Engine{internal: assembled}, nil
|
return &Engine{internal: assembled}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fileSource(name string) (fs.FS, string, error) {
|
|
||||||
if strings.TrimSpace(name) == "" {
|
|
||||||
return nil, "", ErrInvalidConfig
|
|
||||||
}
|
|
||||||
dir := filepath.Dir(name)
|
|
||||||
base := filepath.Base(name)
|
|
||||||
if base == "." || base == string(filepath.Separator) {
|
|
||||||
return nil, "", ErrInvalidConfig
|
|
||||||
}
|
|
||||||
info, err := os.Stat(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("%w: failed to access source file %q: %v", ErrInvalidConfig, name, err)
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
return nil, "", fmt.Errorf("%w: source path %q must be a file", ErrInvalidConfig, name)
|
|
||||||
}
|
|
||||||
return os.DirFS(dir), filepath.ToSlash(base), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// InspectPrompt resolves one explicit prompt definition without selecting a
|
// InspectPrompt resolves one explicit prompt definition without selecting a
|
||||||
// profile or starting execution work.
|
// profile or starting execution work.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ package engine
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -33,23 +36,118 @@ type Config struct {
|
|||||||
HTTPClient *http.Client
|
HTTPClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options carries collaborators prepared by the public facade during the
|
// Options collects construction choices from the public facade.
|
||||||
// transitional composition split.
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
LLMClient llm.Client
|
llmClient llm.Client
|
||||||
ArtifactReader artifact.Reader
|
artifactReader artifact.Reader
|
||||||
PromptDefinitions promptdef.Repository
|
promptDefinitions promptdef.Repository
|
||||||
Profiles profile.Repository
|
profiles profile.Repository
|
||||||
FallbackProfiles profile.Repository
|
fallbackProfiles profile.Repository
|
||||||
MemoryProfiles profile.Repository
|
memoryProfiles profile.Repository
|
||||||
Backends []domain.Backend
|
backends []domain.Backend
|
||||||
Validator validate.Validator
|
validator validate.Validator
|
||||||
PromptSource bool
|
promptSource bool
|
||||||
ProfileSource bool
|
profileSource bool
|
||||||
FallbackProfileSource bool
|
fallbackProfileSource bool
|
||||||
MemorySource bool
|
memorySource bool
|
||||||
ValidatorSource bool
|
validatorSource bool
|
||||||
ArtifactSource bool
|
artifactSource bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLLMClient selects the client collaborator for one engine.
|
||||||
|
func (o *Options) SetLLMClient(client llm.Client) { o.llmClient = client }
|
||||||
|
|
||||||
|
// SetArtifactReader selects the artifact collaborator for one engine.
|
||||||
|
func (o *Options) SetArtifactReader(reader artifact.Reader) {
|
||||||
|
o.artifactReader = reader
|
||||||
|
o.artifactSource = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPromptFS selects a prompt source rooted in fsys.
|
||||||
|
func (o *Options) SetPromptFS(fsys fs.FS, root string) error {
|
||||||
|
if err := validateSourceFS(fsys, root); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.promptDefinitions = promptdef.NewFSRepository(fsys, root)
|
||||||
|
o.promptSource = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPromptFile selects a single prompt file.
|
||||||
|
func (o *Options) SetPromptFile(path string) error {
|
||||||
|
fsys, root, directory, err := fileSource(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.promptDefinitions = promptdef.NewFileRepository(fsys, root, directory)
|
||||||
|
o.promptSource = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProfileFS selects an ordinary profile source rooted in fsys.
|
||||||
|
func (o *Options) SetProfileFS(fsys fs.FS, root string) error {
|
||||||
|
if err := validateSourceFS(fsys, root); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.profiles = profile.NewFSRepository(fsys, root)
|
||||||
|
o.profileSource = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProfileFile selects a single ordinary profile file.
|
||||||
|
func (o *Options) SetProfileFile(path string) error {
|
||||||
|
fsys, root, _, err := fileSource(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.profiles = profile.NewFSRepository(fsys, root)
|
||||||
|
o.profileSource = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFallbackProfileFS selects a fallback profile source rooted in fsys.
|
||||||
|
func (o *Options) SetFallbackProfileFS(fsys fs.FS, root string) error {
|
||||||
|
if err := validateSourceFS(fsys, root); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.fallbackProfiles = profile.NewFSRepository(fsys, root)
|
||||||
|
o.fallbackProfileSource = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMemoryProfiles validates and stores application-owned profile definitions.
|
||||||
|
func (o *Options) SetMemoryProfiles(definitions []domain.ExecutionProfile) error {
|
||||||
|
repository, err := profile.NewMemoryRepository(definitions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.memoryProfiles = repository
|
||||||
|
o.memorySource = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBackend appends one backend registration.
|
||||||
|
func (o *Options) AddBackend(backend domain.Backend) { o.backends = append(o.backends, backend) }
|
||||||
|
|
||||||
|
// SetSchemaFS selects a schema source rooted in fsys.
|
||||||
|
func (o *Options) SetSchemaFS(fsys fs.FS, root string) error {
|
||||||
|
if err := validateSourceFS(fsys, root); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.validator = validate.NewFSValidator(fsys, root)
|
||||||
|
o.validatorSource = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSchemaFile selects a single schema file.
|
||||||
|
func (o *Options) SetSchemaFile(path string) error {
|
||||||
|
fsys, root, _, err := fileSource(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.validator = validate.NewFSValidator(fsys, root)
|
||||||
|
o.validatorSource = true
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Engine forwards domain operations to the single assembled use-case runner.
|
// Engine forwards domain operations to the single assembled use-case runner.
|
||||||
@@ -59,8 +157,8 @@ type Engine struct {
|
|||||||
|
|
||||||
// New assembles an engine from facade-selected configuration and collaborators.
|
// New assembles an engine from facade-selected configuration and collaborators.
|
||||||
func New(cfg Config, options Options) (*Engine, error) {
|
func New(cfg Config, options Options) (*Engine, error) {
|
||||||
promptDefinitions := options.PromptDefinitions
|
promptDefinitions := options.promptDefinitions
|
||||||
if !options.PromptSource {
|
if !options.promptSource {
|
||||||
if strings.TrimSpace(cfg.PromptDir) == "" {
|
if strings.TrimSpace(cfg.PromptDir) == "" {
|
||||||
return nil, fmt.Errorf("prompt directory is required")
|
return nil, fmt.Errorf("prompt directory is required")
|
||||||
}
|
}
|
||||||
@@ -76,7 +174,7 @@ func New(cfg Config, options Options) (*Engine, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
profiles := newProfileRepository(cfg.ProfileDir, options, maintained.Profiles)
|
profiles := newProfileRepository(cfg.ProfileDir, options, maintained.Profiles)
|
||||||
registry, err := backend.NewRegistry(maintained.Backends, options.Backends)
|
registry, err := backend.NewRegistry(maintained.Backends, options.backends)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to construct backend registry: %w", err)
|
return nil, fmt.Errorf("failed to construct backend registry: %w", err)
|
||||||
}
|
}
|
||||||
@@ -85,15 +183,15 @@ func New(cfg Config, options Options) (*Engine, error) {
|
|||||||
return nil, fmt.Errorf("failed to construct backend capacity manager: %w", err)
|
return nil, fmt.Errorf("failed to construct backend capacity manager: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
validator := options.Validator
|
validator := options.validator
|
||||||
if !options.ValidatorSource {
|
if !options.validatorSource {
|
||||||
schemaDir := cfg.SchemaDir
|
schemaDir := cfg.SchemaDir
|
||||||
if strings.TrimSpace(schemaDir) == "" {
|
if strings.TrimSpace(schemaDir) == "" {
|
||||||
schemaDir = defaults.SchemaDirDefault
|
schemaDir = defaults.SchemaDirDefault
|
||||||
}
|
}
|
||||||
validator = validate.NewStandardValidator(schemaDir)
|
validator = validate.NewStandardValidator(schemaDir)
|
||||||
}
|
}
|
||||||
client := options.LLMClient
|
client := options.llmClient
|
||||||
if client == nil {
|
if client == nil {
|
||||||
client, err = llm.NewOpenAICompatibleClient(llm.OpenAICompatibleConfig{Timeout: cfg.Timeout, HTTPClient: cfg.HTTPClient})
|
client, err = llm.NewOpenAICompatibleClient(llm.OpenAICompatibleConfig{Timeout: cfg.Timeout, HTTPClient: cfg.HTTPClient})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -101,8 +199,8 @@ func New(cfg Config, options Options) (*Engine, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
client = capacity.NewClient(manager, client)
|
client = capacity.NewClient(manager, client)
|
||||||
reader := options.ArtifactReader
|
reader := options.artifactReader
|
||||||
if !options.ArtifactSource {
|
if !options.artifactSource {
|
||||||
reader = artifact.NewCompositeReader()
|
reader = artifact.NewCompositeReader()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,20 +209,49 @@ func New(cfg Config, options Options) (*Engine, error) {
|
|||||||
|
|
||||||
func newProfileRepository(profileDir string, options Options, maintained profile.Repository) profile.Repository {
|
func newProfileRepository(profileDir string, options Options, maintained profile.Repository) profile.Repository {
|
||||||
repository := maintained
|
repository := maintained
|
||||||
if options.FallbackProfileSource {
|
if options.fallbackProfileSource {
|
||||||
repository = profile.NewOverlayRepository(options.FallbackProfiles, repository)
|
repository = profile.NewOverlayRepository(options.fallbackProfiles, repository)
|
||||||
}
|
}
|
||||||
if options.ProfileSource {
|
if options.profileSource {
|
||||||
repository = profile.NewOverlayRepository(options.Profiles, repository)
|
repository = profile.NewOverlayRepository(options.profiles, repository)
|
||||||
} else if strings.TrimSpace(profileDir) != "" {
|
} else if strings.TrimSpace(profileDir) != "" {
|
||||||
repository = profile.NewOverlayRepository(profile.NewFilesystemRepository(profileDir), repository)
|
repository = profile.NewOverlayRepository(profile.NewFilesystemRepository(profileDir), repository)
|
||||||
}
|
}
|
||||||
if options.MemorySource {
|
if options.memorySource {
|
||||||
repository = profile.NewOverlayRepository(options.MemoryProfiles, repository)
|
repository = profile.NewOverlayRepository(options.memoryProfiles, repository)
|
||||||
}
|
}
|
||||||
return profile.NewResolvingRepository(repository)
|
return profile.NewResolvingRepository(repository)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateSourceFS(fsys fs.FS, root string) error {
|
||||||
|
if fsys == nil {
|
||||||
|
return fmt.Errorf("source filesystem is nil")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(root) == "" {
|
||||||
|
return fmt.Errorf("source root is required")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileSource(name string) (fs.FS, string, string, error) {
|
||||||
|
if strings.TrimSpace(name) == "" {
|
||||||
|
return nil, "", "", fmt.Errorf("source path is required")
|
||||||
|
}
|
||||||
|
directory := filepath.Dir(name)
|
||||||
|
base := filepath.Base(name)
|
||||||
|
if base == "." || base == string(filepath.Separator) {
|
||||||
|
return nil, "", "", fmt.Errorf("source path must name a file")
|
||||||
|
}
|
||||||
|
info, err := os.Stat(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", "", fmt.Errorf("failed to access source file %q: %w", name, err)
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil, "", "", fmt.Errorf("source path %q must be a file", name)
|
||||||
|
}
|
||||||
|
return os.DirFS(directory), filepath.ToSlash(base), directory, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Engine) InspectPrompt(ctx context.Context, id, version string) (*domain.PromptInspection, error) {
|
func (e *Engine) InspectPrompt(ctx context.Context, id, version string) (*domain.PromptInspection, error) {
|
||||||
return e.runner.InspectPrompt(ctx, id, version)
|
return e.runner.InspectPrompt(ctx, id, version)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user