275 lines
8.2 KiB
Go
275 lines
8.2 KiB
Go
// Package promptexec defines Weatherreporter's provider-neutral prompt execution contract.
|
|
package promptexec
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
maxValidationDiagnostics = 10
|
|
maxDiagnosticBytes = 1024
|
|
maxErrorMessageBytes = 2048
|
|
)
|
|
|
|
// Executor inspects and executes configured prompts without exposing provider types.
|
|
// Inspection is side-effect-free. Execute invokes prepared exactly once after a
|
|
// successful preparation and before provider execution. If prepared returns an
|
|
// error, Execute must not call the provider. Completed validation rejection is
|
|
// returned as an Execution with a failed Validation status; operational failures
|
|
// return no Execution. Sensitive debug values are populated only when requested.
|
|
type Executor interface {
|
|
InspectPrompt(context.Context, string, string) (PromptInspection, error)
|
|
InspectProfile(context.Context, string) (ProfileInspection, error)
|
|
Execute(context.Context, ExecuteRequest, PreparationCallback) (*Execution, error)
|
|
}
|
|
|
|
// PromptInspection describes one exact prompt definition without selecting a profile.
|
|
type PromptInspection struct {
|
|
PromptID string
|
|
PromptVersion string
|
|
PromptHash string
|
|
DefaultProfileID string
|
|
Inputs []InputDefinition
|
|
Output OutputContract
|
|
}
|
|
|
|
// InputDefinition describes one declared prompt input.
|
|
type InputDefinition struct {
|
|
Name string
|
|
Required bool
|
|
ContentType string
|
|
Description string
|
|
}
|
|
|
|
// OutputContract summarizes the output requirements declared by a prompt.
|
|
type OutputContract struct {
|
|
Format string
|
|
ValidationMode string
|
|
SchemaPath string
|
|
}
|
|
|
|
// ProfileInspection describes the safe, selected execution identity for one profile.
|
|
type ProfileInspection struct {
|
|
ProfileID string
|
|
BackendID string
|
|
ModelName string
|
|
CredentialRequired bool
|
|
APIKeyEnv string
|
|
}
|
|
|
|
// ExecuteRequest selects one exact prompt execution. DataPackage is the exact
|
|
// YAML input; implementations must copy it before retaining it.
|
|
type ExecuteRequest struct {
|
|
PromptID string
|
|
PromptVersion string
|
|
ProfileID string
|
|
DataPackage []byte
|
|
CaptureDebug bool
|
|
}
|
|
|
|
// PreparationCallback receives safe preparation provenance before provider work.
|
|
// The callback receives independent copies which it may retain or mutate.
|
|
type PreparationCallback func(Preparation, *PreparationDebug) error
|
|
|
|
// Preparation contains non-sensitive provenance from a completed preparation.
|
|
type Preparation struct {
|
|
PromptID string
|
|
PromptVersion string
|
|
PromptHash string
|
|
RenderedPromptHash string
|
|
InputHashes map[string]string
|
|
ProfileID string
|
|
BackendID string
|
|
ModelName string
|
|
Output OutputContract
|
|
StartedAt time.Time
|
|
EndedAt time.Time
|
|
Duration time.Duration
|
|
}
|
|
|
|
// PreparationDebug contains content-rich preparation details for an explicitly
|
|
// enabled sensitive-debug destination. It must never be persisted routinely.
|
|
type PreparationDebug struct {
|
|
RenderedMessages []RenderedMessage
|
|
StructuredSchema []byte
|
|
Endpoint string
|
|
ParametersJSON []byte
|
|
}
|
|
|
|
// RenderedMessage is one fully rendered model message for sensitive debugging.
|
|
type RenderedMessage struct {
|
|
Role string
|
|
Content string
|
|
}
|
|
|
|
// Execution contains the completed result of one provider run. RawOutput is
|
|
// the generated content, not a provider transport response body. It is copied
|
|
// before return and must be persisted separately from routine metadata.
|
|
type Execution struct {
|
|
RunID string
|
|
PromptID string
|
|
PromptVersion string
|
|
PromptHash string
|
|
RenderedPromptHash string
|
|
InputHashes map[string]string
|
|
ProfileID string
|
|
BackendID string
|
|
ModelName string
|
|
GeneratedHash string
|
|
Usage TokenUsage
|
|
StartedAt time.Time
|
|
EndedAt time.Time
|
|
Duration time.Duration
|
|
Validation Validation
|
|
RawOutput []byte
|
|
Debug *ExecutionDebug
|
|
}
|
|
|
|
// TokenUsage is provider-reported token accounting.
|
|
type TokenUsage struct {
|
|
PromptTokens int
|
|
CompletionTokens int
|
|
TotalTokens int
|
|
CachedTokens int
|
|
CacheWriteTokens int
|
|
}
|
|
|
|
// Validation records a completed output validation check.
|
|
type Validation struct {
|
|
Status ValidationStatus
|
|
Mode string
|
|
SchemaPath string
|
|
Diagnostics []string
|
|
}
|
|
|
|
// NewValidation returns a completed validation value with bounded diagnostics.
|
|
func NewValidation(status ValidationStatus, mode string, schemaPath string, diagnostics []string) Validation {
|
|
return Validation{
|
|
Status: status,
|
|
Mode: mode,
|
|
SchemaPath: schemaPath,
|
|
Diagnostics: boundDiagnostics(diagnostics),
|
|
}
|
|
}
|
|
|
|
// ValidationStatus identifies the completed validation state.
|
|
type ValidationStatus string
|
|
|
|
const (
|
|
ValidationPassed ValidationStatus = "passed"
|
|
ValidationFailed ValidationStatus = "failed"
|
|
ValidationSkipped ValidationStatus = "skipped"
|
|
)
|
|
|
|
// ExecutionDebug contains content-rich execution details for explicitly enabled
|
|
// sensitive debugging. It must never be persisted routinely.
|
|
type ExecutionDebug struct {
|
|
RawOutput []byte
|
|
ValidationDiagnostics []string
|
|
}
|
|
|
|
// ErrorCategory classifies a project-owned operational failure.
|
|
type ErrorCategory string
|
|
|
|
const (
|
|
InvalidConfiguration ErrorCategory = "invalid_configuration"
|
|
InvalidRequest ErrorCategory = "invalid_request"
|
|
PromptNotFound ErrorCategory = "prompt_not_found"
|
|
PromptLoad ErrorCategory = "prompt_load"
|
|
ProfileNotFound ErrorCategory = "profile_not_found"
|
|
ProfileLoad ErrorCategory = "profile_load"
|
|
MissingCredential ErrorCategory = "missing_credential"
|
|
ArtifactLoad ErrorCategory = "artifact_load"
|
|
PromptRender ErrorCategory = "prompt_render"
|
|
Capacity ErrorCategory = "capacity"
|
|
Generation ErrorCategory = "generation"
|
|
OperationalValidation ErrorCategory = "operational_validation"
|
|
ValidationRejected ErrorCategory = "validation_rejected"
|
|
Canceled ErrorCategory = "canceled"
|
|
DeadlineExceeded ErrorCategory = "deadline_exceeded"
|
|
)
|
|
|
|
// Error is a bounded safe error suitable for workflow and persistence records.
|
|
// Its optional cause remains available to errors.Is and errors.As but is never
|
|
// included in Error's text.
|
|
type Error struct {
|
|
category ErrorCategory
|
|
message string
|
|
cause error
|
|
}
|
|
|
|
// NewError returns a classified error with a bounded, Weatherreporter-owned message.
|
|
func NewError(category ErrorCategory, message string, cause error) *Error {
|
|
messageLimit := maxErrorMessageBytes - len(category) - len(": ")
|
|
return &Error{category: category, message: boundText(message, messageLimit), cause: cause}
|
|
}
|
|
|
|
func (e *Error) Error() string {
|
|
if e == nil {
|
|
return ""
|
|
}
|
|
if e.message == "" {
|
|
return string(e.category)
|
|
}
|
|
return fmt.Sprintf("%s: %s", e.category, e.message)
|
|
}
|
|
|
|
// Unwrap preserves an underlying error identity without exposing its text.
|
|
func (e *Error) Unwrap() error {
|
|
if e == nil {
|
|
return nil
|
|
}
|
|
return e.cause
|
|
}
|
|
|
|
// Category returns the stable classification.
|
|
func (e *Error) Category() ErrorCategory {
|
|
if e == nil {
|
|
return ""
|
|
}
|
|
return e.category
|
|
}
|
|
|
|
// CapacityError adds the safe backend identity to a capacity failure.
|
|
type CapacityError struct {
|
|
BackendID string
|
|
Err *Error
|
|
}
|
|
|
|
// NewCapacityError returns a classified capacity failure for backendID.
|
|
func NewCapacityError(backendID string, message string, cause error) *CapacityError {
|
|
return &CapacityError{BackendID: boundText(backendID, 256), Err: NewError(Capacity, message, cause)}
|
|
}
|
|
|
|
func (e *CapacityError) Error() string {
|
|
if e == nil {
|
|
return ""
|
|
}
|
|
if e.BackendID == "" {
|
|
return e.Err.Error()
|
|
}
|
|
return boundText(fmt.Sprintf("%s (backend %q)", e.Err.Error(), e.BackendID), maxErrorMessageBytes)
|
|
}
|
|
|
|
func (e *CapacityError) Unwrap() error {
|
|
if e == nil {
|
|
return nil
|
|
}
|
|
return e.Err
|
|
}
|
|
|
|
// Category returns Capacity for every capacity error.
|
|
func (e *CapacityError) Category() ErrorCategory { return Capacity }
|
|
|
|
// CategoryOf returns the classification carried by err, including wrapped errors.
|
|
func CategoryOf(err error) ErrorCategory {
|
|
var categorized interface{ Category() ErrorCategory }
|
|
if errors.As(err, &categorized) {
|
|
return categorized.Category()
|
|
}
|
|
return ""
|
|
}
|