2026-08-02 05:35:59 +00:00
package app
import (
"context"
"fmt"
2026-08-13 01:47:47 +00:00
"gitea.maximumdirect.net/eric/weatherreporter/internal/generatedtext"
2026-08-02 05:35:59 +00:00
"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
2026-08-13 01:47:47 +00:00
if err := generatedtext . ValidateRawOutput ( execution . RawOutput ) ; err != nil {
return outcome , nil , & profileExecutionError { operation : "validate generated text" , err : err }
}
2026-08-02 05:35:59 +00:00
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 }
}
2026-08-13 02:11:25 +00:00
identity , snapshot , derived , err := req . Prepared . renderInputs ( )
2026-08-02 05:35:59 +00:00
if err != nil {
return outcome , nil , & profileExecutionError { operation : "copy prepared render inputs" , err : err }
}
2026-08-13 02:11:25 +00:00
renderContext , err := req . Prepared . handler . BuildRenderContext ( identity , snapshot , derived , generatedText )
2026-08-02 05:35:59 +00:00
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
}