Expose captured resource inspection through the engine
This commit is contained in:
70
convert.go
70
convert.go
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"go.maximumdirect.net/promptkit/internal/configuration"
|
||||||
"go.maximumdirect.net/promptkit/internal/domain"
|
"go.maximumdirect.net/promptkit/internal/domain"
|
||||||
"go.maximumdirect.net/promptkit/internal/jsonvalue"
|
"go.maximumdirect.net/promptkit/internal/jsonvalue"
|
||||||
)
|
)
|
||||||
@@ -228,37 +229,58 @@ func fromDomainExecutionTargetWithOwnedParams(target domain.ExecutionTarget, ext
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromDomainProfileInspection(inspection *domain.ProfileInspection) *ProfileInspection {
|
func fromProfileResource(v *configuration.ProfileResource) *ProfileInspection {
|
||||||
if inspection == nil {
|
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)}
|
||||||
return nil
|
if v.ExtraParams != nil {
|
||||||
}
|
data := copyAnyMap(v.ExtraParams)
|
||||||
return &ProfileInspection{
|
out.ExtraParams = &data
|
||||||
ProfileID: inspection.ProfileID,
|
|
||||||
EffectiveModelParams: fromDomainExecutionTarget(inspection.EffectiveModelParams),
|
|
||||||
APIKeyRequired: inspection.APIKeyRequired,
|
|
||||||
}
|
}
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromDomainPromptInspection(inspection *domain.PromptInspection) *PromptInspection {
|
func fromBackendResource(v *configuration.BackendResource) *BackendInspection {
|
||||||
if inspection == nil {
|
out := &BackendInspection{BackendID: v.ID, Endpoint: v.Endpoint, APIKeyEnv: copyStringPtr(v.APIKeyEnv), ConcurrencyLimit: copyIntPtr(v.ConcurrencyLimit), QueueCapacity: copyIntPtr(v.QueueCapacity)}
|
||||||
return nil
|
if v.ExtraParams != nil {
|
||||||
|
data := copyAnyMap(v.ExtraParams)
|
||||||
|
out.ExtraParams = &data
|
||||||
}
|
}
|
||||||
inputs := make([]PromptInputDefinition, len(inspection.Inputs))
|
if v.SmokeTests != nil {
|
||||||
for i, input := range inspection.Inputs {
|
out.SmokeTests = &BackendSmokeTestConfig{DefaultProfile: copyStringPtr(v.SmokeTests.DefaultProfile)}
|
||||||
inputs[i] = PromptInputDefinition{
|
if v.SmokeTests.Reachability != nil {
|
||||||
Name: input.Name,
|
out.SmokeTests.Reachability = &ReachabilityProbeConfig{Endpoint: copyStringPtr(v.SmokeTests.Reachability.Endpoint)}
|
||||||
Required: input.Required,
|
}
|
||||||
ContentType: input.ContentType,
|
if v.SmokeTests.Authentication != nil {
|
||||||
Description: input.Description,
|
out.SmokeTests.Authentication = &AuthenticationProbeConfig{Kind: copyStringPtr(v.SmokeTests.Authentication.Kind), Endpoint: copyStringPtr(v.SmokeTests.Authentication.Endpoint)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &PromptInspection{
|
return out
|
||||||
PromptID: inspection.PromptID,
|
}
|
||||||
PromptHash: inspection.PromptHash,
|
|
||||||
DefaultProfileID: inspection.DefaultProfileID,
|
func fromPromptResource(v *configuration.PromptResource) *PromptInspection {
|
||||||
Inputs: inputs,
|
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)}}
|
||||||
OutputContract: fromDomainOutputContract(inspection.OutputContract),
|
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 {
|
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
|
## 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.
|
**Scope:** Selection, public conversion, backend entry point, and errors.
|
||||||
|
|
||||||
1. Add the three-method resource-inspection interface and captured collaborator
|
1. Add the three-method resource-inspection interface and captured collaborator
|
||||||
|
|||||||
45
engine.go
45
engine.go
@@ -9,6 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.maximumdirect.net/configkit"
|
"go.maximumdirect.net/configkit"
|
||||||
|
"go.maximumdirect.net/promptkit/internal/backend"
|
||||||
"go.maximumdirect.net/promptkit/internal/domain"
|
"go.maximumdirect.net/promptkit/internal/domain"
|
||||||
internalengine "go.maximumdirect.net/promptkit/internal/engine"
|
internalengine "go.maximumdirect.net/promptkit/internal/engine"
|
||||||
"go.maximumdirect.net/promptkit/internal/observation"
|
"go.maximumdirect.net/promptkit/internal/observation"
|
||||||
@@ -30,6 +31,8 @@ var (
|
|||||||
// ErrProfileNotFound identifies a selected profile ID that is absent from
|
// ErrProfileNotFound identifies a selected profile ID that is absent from
|
||||||
// every configured profile source. It does not also match ErrProfileLoad.
|
// every configured profile source. It does not also match ErrProfileLoad.
|
||||||
ErrProfileNotFound = errors.New("profile not found")
|
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
|
// ErrProfileRequired identifies a request for which neither RunRequest.ProfileID
|
||||||
// nor the selected prompt's default profile is present. Such an error also
|
// nor the selected prompt's default profile is present. Such an error also
|
||||||
// matches ErrInvalidRequest.
|
// matches ErrInvalidRequest.
|
||||||
@@ -81,12 +84,12 @@ var (
|
|||||||
ErrValidation = errors.New("failed to validate output")
|
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.
|
// prepares and runs Promptkit prompt requests.
|
||||||
//
|
//
|
||||||
// An Engine is safe for concurrent calls to [Engine.WithNamespaces],
|
// An Engine is safe for concurrent calls to [Engine.WithNamespaces],
|
||||||
// [Engine.InspectConfiguration],
|
// [Engine.InspectConfiguration],
|
||||||
// [Engine.InspectPrompt],
|
// [Engine.InspectPrompt], [Engine.InspectBackend],
|
||||||
// [Engine.InspectRequirements], [Engine.InspectProfile], [Engine.Prepare], [Engine.PrepareExecution],
|
// [Engine.InspectRequirements], [Engine.InspectProfile], [Engine.Prepare], [Engine.PrepareExecution],
|
||||||
// [Engine.Run], [Engine.RunPrepared], [Engine.SmokeTest], [Engine.ListProfiles],
|
// [Engine.Run], [Engine.RunPrepared], [Engine.SmokeTest], [Engine.ListProfiles],
|
||||||
// [Engine.ListPrompts], [Engine.ListBackends], [Engine.ListNamespaces], and
|
// [Engine.ListPrompts], [Engine.ListBackends], [Engine.ListNamespaces], and
|
||||||
@@ -267,8 +270,9 @@ func NewEngine(cfg Config, opts ...Option) (*Engine, error) {
|
|||||||
// qualified IDs pass through.
|
// qualified IDs pass through.
|
||||||
//
|
//
|
||||||
// A successful result proves that the selected definition and any referenced
|
// A successful result proves that the selected definition and any referenced
|
||||||
// message content files were structurally loaded. Inputs are returned in
|
// message content files were structurally loaded. Inputs and captured,
|
||||||
// definition order. DefaultProfileID is declared metadata only and is not
|
// 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
|
// resolved. OutputContract is the normalized declared contract, with a JSON
|
||||||
// Schema path when declared but without loading or compiling that schema.
|
// Schema path when declared but without loading or compiling that schema.
|
||||||
// PromptHash is the same opaque equality value as PreparedRun.PromptHash for
|
// PromptHash is the same opaque equality value as PreparedRun.PromptHash for
|
||||||
@@ -299,7 +303,7 @@ func (e *Engine) InspectPrompt(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, mapPublicError(err)
|
return nil, mapPublicError(err)
|
||||||
}
|
}
|
||||||
return fromDomainPromptInspection(inspection), nil
|
return fromPromptResource(inspection), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InspectProfile resolves one explicit profile without selecting a prompt or
|
// 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.
|
// resource without backend completion, execution defaults, or request overrides.
|
||||||
//
|
//
|
||||||
// APIKeyEnv in the returned resource is an environment-variable name, never its
|
// APIKeyEnv in the returned resource is an environment-variable name, never its
|
||||||
// value. APIKeyRequired reports that a credential is required; an explicit
|
// value. APIKeyRequired, when present and true, reports that a credential is
|
||||||
// profile environment source can also be present. InspectProfile neither derives
|
// 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
|
// an ID from a prompt default_profile nor checks credential availability, so an
|
||||||
// absent or blank named environment variable is not an error.
|
// 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 {
|
if err != nil {
|
||||||
return nil, mapPublicError(err)
|
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.
|
// Prepare resolves and renders a prompt request without calling an LLM.
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ type Engine struct {
|
|||||||
runner *usecase.Runner
|
runner *usecase.Runner
|
||||||
smokeTester *usecase.SmokeTester
|
smokeTester *usecase.SmokeTester
|
||||||
inspection configuration.Inspection
|
inspection configuration.Inspection
|
||||||
|
resources resourceInspection
|
||||||
listing identityInventory
|
listing identityInventory
|
||||||
namespaces resourceNamespaces
|
namespaces resourceNamespaces
|
||||||
}
|
}
|
||||||
@@ -105,6 +106,12 @@ type identityInventory interface {
|
|||||||
ListCollections(context.Context) ([]configuration.CollectionIdentity, error)
|
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.
|
// ListingFilterMode selects namespace membership for one identity listing.
|
||||||
type ListingFilterMode uint8
|
type ListingFilterMode uint8
|
||||||
|
|
||||||
@@ -173,6 +180,7 @@ func New(cfg Config, options Options) (*Engine, error) {
|
|||||||
runner: runner,
|
runner: runner,
|
||||||
smokeTester: usecase.NewSmokeTester(runner, diagnosticHTTPClient),
|
smokeTester: usecase.NewSmokeTester(runner, diagnosticHTTPClient),
|
||||||
inspection: snapshot.Inspection(),
|
inspection: snapshot.Inspection(),
|
||||||
|
resources: snapshot,
|
||||||
listing: snapshot,
|
listing: snapshot,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -224,13 +232,16 @@ func fileSource(name string) (fs.FS, string, error) {
|
|||||||
return os.DirFS(directory), filepath.ToSlash(base), nil
|
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
|
var err error
|
||||||
id, err = e.qualifyResourceID(id, e.namespaces.prompt, "prompt")
|
id, err = e.qualifyResourceID(id, e.namespaces.prompt, "prompt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return e.runner.InspectPrompt(ctx, id)
|
return e.resources.InspectPrompt(ctx, id)
|
||||||
}
|
}
|
||||||
func (e *Engine) InspectRequirements(
|
func (e *Engine) InspectRequirements(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
@@ -247,13 +258,27 @@ func (e *Engine) InspectRequirements(
|
|||||||
}
|
}
|
||||||
return e.runner.InspectRequirements(ctx, request)
|
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
|
var err error
|
||||||
id, err = e.qualifyResourceID(id, e.namespaces.profile, "profile")
|
id, err = e.qualifyResourceID(id, e.namespaces.profile, "profile")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
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