Separate report execution from publication
This commit is contained in:
127
internal/app/profile_execution.go
Normal file
127
internal/app/profile_execution.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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 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}
|
||||
}
|
||||
metadata, snapshot, reportFacts, err := req.Prepared.renderInputs()
|
||||
if err != nil {
|
||||
return outcome, nil, &profileExecutionError{operation: "copy prepared render inputs", err: err}
|
||||
}
|
||||
renderContext, err := req.Prepared.handler.BuildRenderContext(metadata, snapshot, reportFacts.Collected, reportFacts.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
|
||||
}
|
||||
Reference in New Issue
Block a user