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 }