Expose captured resource inspection through the engine
This commit is contained in:
70
convert.go
70
convert.go
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"unicode/utf8"
|
||||
|
||||
"go.maximumdirect.net/promptkit/internal/configuration"
|
||||
"go.maximumdirect.net/promptkit/internal/domain"
|
||||
"go.maximumdirect.net/promptkit/internal/jsonvalue"
|
||||
)
|
||||
@@ -228,37 +229,58 @@ func fromDomainExecutionTargetWithOwnedParams(target domain.ExecutionTarget, ext
|
||||
}
|
||||
}
|
||||
|
||||
func fromDomainProfileInspection(inspection *domain.ProfileInspection) *ProfileInspection {
|
||||
if inspection == nil {
|
||||
return nil
|
||||
}
|
||||
return &ProfileInspection{
|
||||
ProfileID: inspection.ProfileID,
|
||||
EffectiveModelParams: fromDomainExecutionTarget(inspection.EffectiveModelParams),
|
||||
APIKeyRequired: inspection.APIKeyRequired,
|
||||
func fromProfileResource(v *configuration.ProfileResource) *ProfileInspection {
|
||||
out := &ProfileInspection{ProfileID: v.ID, Backend: copyStringPtr(v.Backend), Endpoint: copyStringPtr(v.Endpoint), Model: copyStringPtr(v.Model), Temperature: copyFloat64Ptr(v.Temperature), TopP: copyFloat64Ptr(v.TopP), MaxTokens: copyIntPtr(v.MaxTokens), TimeoutSeconds: copyIntPtr(v.TimeoutSeconds), ServiceTier: copyStringPtr(v.ServiceTier), ReasoningEffort: copyStringPtr(v.ReasoningEffort), APIKeyEnv: copyStringPtr(v.APIKeyEnv), APIKeyRequired: copyBoolPtr(v.APIKeyRequired)}
|
||||
if v.ExtraParams != nil {
|
||||
data := copyAnyMap(v.ExtraParams)
|
||||
out.ExtraParams = &data
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func fromDomainPromptInspection(inspection *domain.PromptInspection) *PromptInspection {
|
||||
if inspection == nil {
|
||||
return nil
|
||||
func fromBackendResource(v *configuration.BackendResource) *BackendInspection {
|
||||
out := &BackendInspection{BackendID: v.ID, Endpoint: v.Endpoint, APIKeyEnv: copyStringPtr(v.APIKeyEnv), ConcurrencyLimit: copyIntPtr(v.ConcurrencyLimit), QueueCapacity: copyIntPtr(v.QueueCapacity)}
|
||||
if v.ExtraParams != nil {
|
||||
data := copyAnyMap(v.ExtraParams)
|
||||
out.ExtraParams = &data
|
||||
}
|
||||
inputs := make([]PromptInputDefinition, len(inspection.Inputs))
|
||||
for i, input := range inspection.Inputs {
|
||||
inputs[i] = PromptInputDefinition{
|
||||
Name: input.Name,
|
||||
Required: input.Required,
|
||||
ContentType: input.ContentType,
|
||||
Description: input.Description,
|
||||
if v.SmokeTests != nil {
|
||||
out.SmokeTests = &BackendSmokeTestConfig{DefaultProfile: copyStringPtr(v.SmokeTests.DefaultProfile)}
|
||||
if v.SmokeTests.Reachability != nil {
|
||||
out.SmokeTests.Reachability = &ReachabilityProbeConfig{Endpoint: copyStringPtr(v.SmokeTests.Reachability.Endpoint)}
|
||||
}
|
||||
if v.SmokeTests.Authentication != nil {
|
||||
out.SmokeTests.Authentication = &AuthenticationProbeConfig{Kind: copyStringPtr(v.SmokeTests.Authentication.Kind), Endpoint: copyStringPtr(v.SmokeTests.Authentication.Endpoint)}
|
||||
}
|
||||
}
|
||||
return &PromptInspection{
|
||||
PromptID: inspection.PromptID,
|
||||
PromptHash: inspection.PromptHash,
|
||||
DefaultProfileID: inspection.DefaultProfileID,
|
||||
Inputs: inputs,
|
||||
OutputContract: fromDomainOutputContract(inspection.OutputContract),
|
||||
return out
|
||||
}
|
||||
|
||||
func fromPromptResource(v *configuration.PromptResource) *PromptInspection {
|
||||
out := &PromptInspection{PromptID: v.ID, PromptHash: v.Hash, DefaultProfileID: copyStringPtr(v.DefaultProfile), Description: copyStringPtr(v.Description), SessionID: copyStringPtr(v.SessionID), OutputContract: PromptOutputInspection{Format: OutputFormat(v.Output.Format), ValidationMode: ValidationMode(v.Output.ValidationMode), SchemaPath: copyStringPtr(v.Output.SchemaPath), RepairAttempts: copyIntPtr(v.Output.RepairAttempts)}}
|
||||
if v.Inputs != nil {
|
||||
inputs := make([]PromptInputInspection, len(*v.Inputs))
|
||||
for i, input := range *v.Inputs {
|
||||
inputs[i] = PromptInputInspection{Name: input.Name, Required: copyBoolPtr(input.Required), ContentType: copyStringPtr(input.ContentType), Description: copyStringPtr(input.Description)}
|
||||
}
|
||||
out.Inputs = &inputs
|
||||
}
|
||||
out.Messages = make([]PromptMessageInspection, len(v.Messages))
|
||||
for i, message := range v.Messages {
|
||||
out.Messages[i] = PromptMessageInspection{Role: message.Role, Content: message.Content}
|
||||
if message.CacheControl != nil {
|
||||
out.Messages[i].CacheControl = &PromptCacheControlInspection{Type: CacheControlType(message.CacheControl.Type), TTL: copyStringPtr(message.CacheControl.TTL)}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func copyBoolPtr(src *bool) *bool {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
value := *src
|
||||
return &value
|
||||
}
|
||||
|
||||
func fromDomainExecutionTargetPresence(presence domain.ExecutionTargetPresence) ExecutionTargetPresence {
|
||||
|
||||
@@ -343,6 +343,10 @@ the resource report or trigger a second read.
|
||||
|
||||
## Stage 5: Wire Resource Inspection Through The Engine And Facade
|
||||
|
||||
**Completed:** Captured resource views are wired through namespace selection
|
||||
and detached public projections. An isolated assembled public-API test passes;
|
||||
existing consumers and examples are migrated in the next stage.
|
||||
|
||||
**Scope:** Selection, public conversion, backend entry point, and errors.
|
||||
|
||||
1. Add the three-method resource-inspection interface and captured collaborator
|
||||
|
||||
45
engine.go
45
engine.go
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"go.maximumdirect.net/configkit"
|
||||
"go.maximumdirect.net/promptkit/internal/backend"
|
||||
"go.maximumdirect.net/promptkit/internal/domain"
|
||||
internalengine "go.maximumdirect.net/promptkit/internal/engine"
|
||||
"go.maximumdirect.net/promptkit/internal/observation"
|
||||
@@ -30,6 +31,8 @@ var (
|
||||
// ErrProfileNotFound identifies a selected profile ID that is absent from
|
||||
// every configured profile source. It does not also match ErrProfileLoad.
|
||||
ErrProfileNotFound = errors.New("profile not found")
|
||||
// ErrBackendNotFound identifies a backend absent from resource inspection.
|
||||
ErrBackendNotFound = errors.New("backend not found")
|
||||
// ErrProfileRequired identifies a request for which neither RunRequest.ProfileID
|
||||
// nor the selected prompt's default profile is present. Such an error also
|
||||
// matches ErrInvalidRequest.
|
||||
@@ -81,12 +84,12 @@ var (
|
||||
ErrValidation = errors.New("failed to validate output")
|
||||
)
|
||||
|
||||
// Engine inspects prompts and profiles, enumerates prompt requirements, and
|
||||
// Engine inspects resources, enumerates prompt requirements, and
|
||||
// prepares and runs Promptkit prompt requests.
|
||||
//
|
||||
// An Engine is safe for concurrent calls to [Engine.WithNamespaces],
|
||||
// [Engine.InspectConfiguration],
|
||||
// [Engine.InspectPrompt],
|
||||
// [Engine.InspectPrompt], [Engine.InspectBackend],
|
||||
// [Engine.InspectRequirements], [Engine.InspectProfile], [Engine.Prepare], [Engine.PrepareExecution],
|
||||
// [Engine.Run], [Engine.RunPrepared], [Engine.SmokeTest], [Engine.ListProfiles],
|
||||
// [Engine.ListPrompts], [Engine.ListBackends], [Engine.ListNamespaces], and
|
||||
@@ -267,8 +270,9 @@ func NewEngine(cfg Config, opts ...Option) (*Engine, error) {
|
||||
// qualified IDs pass through.
|
||||
//
|
||||
// A successful result proves that the selected definition and any referenced
|
||||
// message content files were structurally loaded. Inputs are returned in
|
||||
// definition order. DefaultProfileID is declared metadata only and is not
|
||||
// message content files were structurally loaded. Inputs and captured,
|
||||
// unrendered messages are returned in definition order. DefaultProfileID is
|
||||
// declared metadata only and is not
|
||||
// resolved. OutputContract is the normalized declared contract, with a JSON
|
||||
// Schema path when declared but without loading or compiling that schema.
|
||||
// PromptHash is the same opaque equality value as PreparedRun.PromptHash for
|
||||
@@ -299,7 +303,7 @@ func (e *Engine) InspectPrompt(
|
||||
if err != nil {
|
||||
return nil, mapPublicError(err)
|
||||
}
|
||||
return fromDomainPromptInspection(inspection), nil
|
||||
return fromPromptResource(inspection), nil
|
||||
}
|
||||
|
||||
// InspectProfile resolves one explicit profile without selecting a prompt or
|
||||
@@ -310,8 +314,8 @@ func (e *Engine) InspectPrompt(
|
||||
// resource without backend completion, execution defaults, or request overrides.
|
||||
//
|
||||
// APIKeyEnv in the returned resource is an environment-variable name, never its
|
||||
// value. APIKeyRequired reports that a credential is required; an explicit
|
||||
// profile environment source can also be present. InspectProfile neither derives
|
||||
// value. APIKeyRequired, when present and true, reports that a credential is
|
||||
// required; an explicit profile environment source can also be present. InspectProfile neither derives
|
||||
// an ID from a prompt default_profile nor checks credential availability, so an
|
||||
// absent or blank named environment variable is not an error.
|
||||
//
|
||||
@@ -335,7 +339,32 @@ func (e *Engine) InspectProfile(ctx context.Context, profileID string) (*Profile
|
||||
if err != nil {
|
||||
return nil, mapPublicError(err)
|
||||
}
|
||||
return fromDomainProfileInspection(inspection), nil
|
||||
return fromProfileResource(inspection), nil
|
||||
}
|
||||
|
||||
// InspectBackend selects a resolved backend by exact ID from the captured
|
||||
// configuration, qualifying a slashless ID with the backend namespace. It
|
||||
// reports configured resource fields and diagnostics, not credential values,
|
||||
// derived diagnostic destinations, or runtime capacity occupancy. References
|
||||
// are not resolved and no network request is made. The result and its mutable
|
||||
// values are caller-owned. Source changes require a new engine.
|
||||
//
|
||||
// An invalid engine matches ErrInvalidConfig, an invalid ID matches
|
||||
// ErrInvalidRequest, and an absent backend matches ErrBackendNotFound (not
|
||||
// ErrInvalidRequest). Cancellation remains detectable via errors.Is. No
|
||||
// partial result is returned on error.
|
||||
func (e *Engine) InspectBackend(ctx context.Context, backendID string) (*BackendInspection, error) {
|
||||
if e == nil || e.internal == nil {
|
||||
return nil, fmt.Errorf("%w: engine is nil", ErrInvalidConfig)
|
||||
}
|
||||
inspection, err := e.internal.InspectBackend(ctx, backendID)
|
||||
if err != nil {
|
||||
if errors.Is(err, backend.ErrBackendNotFound) {
|
||||
return nil, fmt.Errorf("%w: %w", ErrBackendNotFound, err)
|
||||
}
|
||||
return nil, mapPublicError(err)
|
||||
}
|
||||
return fromBackendResource(inspection), nil
|
||||
}
|
||||
|
||||
// Prepare resolves and renders a prompt request without calling an LLM.
|
||||
|
||||
@@ -87,6 +87,7 @@ type Engine struct {
|
||||
runner *usecase.Runner
|
||||
smokeTester *usecase.SmokeTester
|
||||
inspection configuration.Inspection
|
||||
resources resourceInspection
|
||||
listing identityInventory
|
||||
namespaces resourceNamespaces
|
||||
}
|
||||
@@ -105,6 +106,12 @@ type identityInventory interface {
|
||||
ListCollections(context.Context) ([]configuration.CollectionIdentity, error)
|
||||
}
|
||||
|
||||
type resourceInspection interface {
|
||||
InspectPrompt(context.Context, string) (*configuration.PromptResource, error)
|
||||
InspectProfile(context.Context, string) (*configuration.ProfileResource, error)
|
||||
InspectBackend(context.Context, string) (*configuration.BackendResource, error)
|
||||
}
|
||||
|
||||
// ListingFilterMode selects namespace membership for one identity listing.
|
||||
type ListingFilterMode uint8
|
||||
|
||||
@@ -173,6 +180,7 @@ func New(cfg Config, options Options) (*Engine, error) {
|
||||
runner: runner,
|
||||
smokeTester: usecase.NewSmokeTester(runner, diagnosticHTTPClient),
|
||||
inspection: snapshot.Inspection(),
|
||||
resources: snapshot,
|
||||
listing: snapshot,
|
||||
}, nil
|
||||
}
|
||||
@@ -224,13 +232,16 @@ func fileSource(name string) (fs.FS, string, error) {
|
||||
return os.DirFS(directory), filepath.ToSlash(base), nil
|
||||
}
|
||||
|
||||
func (e *Engine) InspectPrompt(ctx context.Context, id string) (*domain.PromptInspection, error) {
|
||||
func (e *Engine) InspectPrompt(ctx context.Context, id string) (*configuration.PromptResource, error) {
|
||||
if strings.TrimSpace(id) == "" {
|
||||
return nil, fmt.Errorf("%w: prompt id is required", usecase.ErrInvalidRequest)
|
||||
}
|
||||
var err error
|
||||
id, err = e.qualifyResourceID(id, e.namespaces.prompt, "prompt")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e.runner.InspectPrompt(ctx, id)
|
||||
return e.resources.InspectPrompt(ctx, id)
|
||||
}
|
||||
func (e *Engine) InspectRequirements(
|
||||
ctx context.Context,
|
||||
@@ -247,13 +258,27 @@ func (e *Engine) InspectRequirements(
|
||||
}
|
||||
return e.runner.InspectRequirements(ctx, request)
|
||||
}
|
||||
func (e *Engine) InspectProfile(ctx context.Context, id string) (*domain.ProfileInspection, error) {
|
||||
func (e *Engine) InspectProfile(ctx context.Context, id string) (*configuration.ProfileResource, error) {
|
||||
if strings.TrimSpace(id) == "" {
|
||||
return nil, fmt.Errorf("%w: profile id is required", usecase.ErrInvalidRequest)
|
||||
}
|
||||
var err error
|
||||
id, err = e.qualifyResourceID(id, e.namespaces.profile, "profile")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e.runner.InspectProfile(ctx, id)
|
||||
return e.resources.InspectProfile(ctx, id)
|
||||
}
|
||||
|
||||
func (e *Engine) InspectBackend(ctx context.Context, id string) (*configuration.BackendResource, error) {
|
||||
if strings.TrimSpace(id) == "" {
|
||||
return nil, fmt.Errorf("%w: backend id is required", usecase.ErrInvalidRequest)
|
||||
}
|
||||
id, err := e.qualifyResourceID(id, e.namespaces.backend, "backend")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e.resources.InspectBackend(ctx, id)
|
||||
}
|
||||
|
||||
func (e *Engine) ListBackends(ctx context.Context, filters ...ListingFilter) ([]string, error) {
|
||||
|
||||
45
resource_inspection_api_test.go
Normal file
45
resource_inspection_api_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package promptkit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"go.maximumdirect.net/configkit"
|
||||
)
|
||||
|
||||
func TestResourceInspectionSelections(t *testing.T) {
|
||||
engine, err := NewEngine(Config{Sources: ConfigurationSources{Main: &configkit.Input{Source: configkit.Mapping("resources", map[string]any{
|
||||
"backends": []any{map[string]any{"id": "local", "endpoint": "https://example.test/v1"}},
|
||||
"profiles": []any{map[string]any{"id": "partial", "backend": "local"}},
|
||||
"prompts": []any{map[string]any{"id": "prompt", "messages": []any{map[string]any{"role": "user", "content": "hello"}}, "output": map[string]any{"format": "text", "validation_mode": "basic"}}},
|
||||
})}}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx := context.Background()
|
||||
if got, err := engine.InspectBackend(ctx, "local"); err != nil || got.BackendID != "local" {
|
||||
t.Fatalf("backend = %+v, %v", got, err)
|
||||
}
|
||||
if got, err := engine.InspectProfile(ctx, "partial"); err != nil || got.Backend == nil || *got.Backend != "local" || got.Endpoint != nil {
|
||||
t.Fatalf("profile = %+v, %v", got, err)
|
||||
}
|
||||
if got, err := engine.InspectPrompt(ctx, "prompt"); err != nil || got.Messages[0].Content != "hello" {
|
||||
t.Fatalf("prompt = %+v, %v", got, err)
|
||||
}
|
||||
if got, err := engine.InspectBackend(ctx, "missing"); got != nil || !errors.Is(err, ErrBackendNotFound) || errors.Is(err, ErrInvalidRequest) {
|
||||
t.Fatalf("missing = %+v, %v", got, err)
|
||||
}
|
||||
if _, err := engine.InspectBackend(ctx, "bad/id/extra"); !errors.Is(err, ErrInvalidRequest) {
|
||||
t.Fatalf("invalid ID = %v", err)
|
||||
}
|
||||
var nilEngine *Engine
|
||||
if _, err := nilEngine.InspectBackend(ctx, "local"); !errors.Is(err, ErrInvalidConfig) {
|
||||
t.Fatalf("nil engine = %v", err)
|
||||
}
|
||||
canceled, cancel := context.WithCancel(ctx)
|
||||
cancel()
|
||||
if _, err := engine.InspectBackend(canceled, "local"); !errors.Is(err, context.Canceled) {
|
||||
t.Fatalf("canceled = %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user