132 lines
5.0 KiB
Go
132 lines
5.0 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/generatedtext"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/promptdebug"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/promptexec"
|
|
)
|
|
|
|
type profileExecutionRequest struct {
|
|
Prepared preparedReport
|
|
Prompt PromptInspectionResult
|
|
Profile promptexec.ProfileInspection
|
|
Executor promptexec.Executor
|
|
DebugWriter *promptdebug.PromptDebugWriter
|
|
DebugRef *promptdebug.PromptDebugRef
|
|
}
|
|
|
|
type profileExecutionOutcome struct {
|
|
ProfileID string
|
|
BackendID string
|
|
ModelName string
|
|
ValidationStatus promptexec.ValidationStatus
|
|
LLMDebugPath string
|
|
}
|
|
|
|
type profileExecutionError struct {
|
|
operation string
|
|
err error
|
|
callbackFailure bool
|
|
}
|
|
|
|
func (e *profileExecutionError) Error() string {
|
|
return e.operation + ": " + e.err.Error()
|
|
}
|
|
|
|
func (e *profileExecutionError) Unwrap() error {
|
|
return e.err
|
|
}
|
|
|
|
func executePreparedProfile(ctx context.Context, req profileExecutionRequest) (profileExecutionOutcome, []byte, error) {
|
|
outcome := profileExecutionOutcome{
|
|
ProfileID: req.Profile.ProfileID,
|
|
BackendID: req.Profile.BackendID,
|
|
ModelName: req.Profile.ModelName,
|
|
}
|
|
if req.Executor == nil {
|
|
return outcome, nil, &profileExecutionError{operation: "execute prompt", err: promptexec.NewError(promptexec.InvalidConfiguration, "prompt executor is required", nil)}
|
|
}
|
|
|
|
callbackFailed := false
|
|
preparationCallback := func(preparation promptexec.Preparation, debug *promptexec.PreparationDebug) error {
|
|
outcome.ProfileID, outcome.BackendID, outcome.ModelName = preparation.ProfileID, preparation.BackendID, preparation.ModelName
|
|
if req.DebugWriter == nil || !req.DebugWriter.Enabled() {
|
|
return nil
|
|
}
|
|
if req.DebugRef == nil {
|
|
callbackFailed = true
|
|
return promptDebugWriteError(fmt.Errorf("prompt debug reference is required"))
|
|
}
|
|
path, err := req.DebugWriter.WritePreparation(*req.DebugRef, preparation, debug)
|
|
if err != nil {
|
|
callbackFailed = true
|
|
return promptDebugWriteError(err)
|
|
}
|
|
outcome.LLMDebugPath = path
|
|
return nil
|
|
}
|
|
|
|
captureDebug := req.DebugWriter != nil && req.DebugWriter.Enabled()
|
|
execution, err := req.Executor.Execute(ctx, promptexec.ExecuteRequest{
|
|
PromptID: req.Prompt.PromptID,
|
|
PromptVersion: req.Prompt.PromptVersion,
|
|
ProfileID: req.Profile.ProfileID,
|
|
DataPackage: req.Prepared.dataPackageCopy(),
|
|
CaptureDebug: captureDebug,
|
|
}, preparationCallback)
|
|
if err != nil {
|
|
if callbackFailed {
|
|
return outcome, nil, &profileExecutionError{operation: "execute prompt", err: err, callbackFailure: true}
|
|
}
|
|
return outcome, nil, &profileExecutionError{operation: "execute prompt", err: classifiedPromptError("prompt execution failed", err)}
|
|
}
|
|
if execution == nil {
|
|
return outcome, nil, &profileExecutionError{operation: "execute prompt", err: promptexec.NewError(promptexec.Generation, "prompt executor returned no execution", nil)}
|
|
}
|
|
|
|
outcome.ValidationStatus = execution.Validation.Status
|
|
if err := generatedtext.ValidateRawOutput(execution.RawOutput); err != nil {
|
|
return outcome, nil, &profileExecutionError{operation: "validate generated text", err: err}
|
|
}
|
|
if req.DebugWriter != nil && req.DebugWriter.Enabled() {
|
|
if req.DebugRef == nil {
|
|
return outcome, nil, &profileExecutionError{operation: "write prompt debug", err: promptDebugWriteError(fmt.Errorf("prompt debug reference is required"))}
|
|
}
|
|
path, err := req.DebugWriter.WriteExecution(*req.DebugRef, *execution)
|
|
if err != nil {
|
|
return outcome, nil, &profileExecutionError{operation: "write prompt debug", err: promptDebugWriteError(err)}
|
|
}
|
|
if path != "" {
|
|
outcome.LLMDebugPath = path
|
|
}
|
|
}
|
|
|
|
if execution.Validation.Status != promptexec.ValidationPassed && execution.Validation.Status != promptexec.ValidationFailed {
|
|
return outcome, nil, &profileExecutionError{operation: "validate prompt execution", err: promptexec.NewError(promptexec.OperationalValidation, "prompt execution did not complete validation", nil)}
|
|
}
|
|
if execution.Validation.Status == promptexec.ValidationFailed {
|
|
return outcome, nil, &profileExecutionError{operation: "validate prompt execution", err: promptexec.NewError(promptexec.ValidationRejected, "prompt output did not satisfy its schema", nil)}
|
|
}
|
|
|
|
generatedText, _, err := req.Prepared.handler.Validate(execution.RawOutput)
|
|
if err != nil {
|
|
return outcome, nil, &profileExecutionError{operation: "validate generated text", err: err}
|
|
}
|
|
identity, snapshot, derived, err := req.Prepared.renderInputs()
|
|
if err != nil {
|
|
return outcome, nil, &profileExecutionError{operation: "copy prepared render inputs", err: err}
|
|
}
|
|
renderContext, err := req.Prepared.handler.BuildRenderContext(identity, snapshot, derived, generatedText)
|
|
if err != nil {
|
|
return outcome, nil, &profileExecutionError{operation: "build render context", err: err}
|
|
}
|
|
rendered, err := req.Prepared.handler.Render(renderContext)
|
|
if err != nil {
|
|
return outcome, nil, &profileExecutionError{operation: "render template", err: err}
|
|
}
|
|
return outcome, rendered, nil
|
|
}
|