146 lines
5.4 KiB
Go
146 lines
5.4 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/collect"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/fileutil"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/promptdebug"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/promptexec"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
|
)
|
|
|
|
type promptReportRequest struct {
|
|
GenerateRequest
|
|
Resolved report.Resolved
|
|
Collection collect.Result
|
|
Inspection PromptInspectionResult
|
|
DebugWriter *promptdebug.PromptDebugWriter
|
|
Result *ReportResult
|
|
noNotify bool
|
|
}
|
|
|
|
func generatePromptReport(ctx context.Context, req promptReportRequest) (*ReportResult, error) {
|
|
if req.Collection.Bundle == nil {
|
|
return nil, fmt.Errorf("collected weather bundle is required")
|
|
}
|
|
result := req.Result
|
|
if result == nil {
|
|
result = initialReportResult(req.GenerateRequest, req.Resolved, req.Inspection)
|
|
}
|
|
prepared, err := prepareReport(prepareReportRequest{Config: req.Config, Resolved: req.Resolved, Collection: req.Collection, handler: req.Inspection.handler})
|
|
if err != nil {
|
|
return result, generatedPreparationError(req.Resolved, result.RunID, err)
|
|
}
|
|
result.SourceWarnings = prepared.sourceWarningsCopy()
|
|
debugRef := promptdebug.PromptDebugRef{ReportID: result.ReportID, ValidDate: prepared.resolved.ValidPeriod.Start.Format("2006-01-02"), RunID: result.RunID}
|
|
outcome, rendered, err := executePreparedProfile(ctx, profileExecutionRequest{
|
|
Prepared: prepared,
|
|
Prompt: req.Inspection,
|
|
Profile: promptexec.ProfileInspection{
|
|
ProfileID: req.Inspection.ProfileID,
|
|
BackendID: req.Inspection.BackendID,
|
|
ModelName: req.Inspection.ModelName,
|
|
},
|
|
Executor: req.Executor, DebugWriter: req.DebugWriter, DebugRef: &debugRef,
|
|
})
|
|
result.ProfileID, result.BackendID, result.ModelName = outcome.ProfileID, outcome.BackendID, outcome.ModelName
|
|
result.ValidationStatus = outcome.ValidationStatus
|
|
result.LLMDebugPath = outcome.LLMDebugPath
|
|
if err != nil {
|
|
return result, generatedProfileExecutionError(req.Resolved, result.RunID, err)
|
|
}
|
|
return publishPromptReport(ctx, promptPublicationRequest{
|
|
GenerateRequest: req.GenerateRequest,
|
|
Resolved: req.Resolved,
|
|
OutputPath: req.OutputPath,
|
|
Result: result,
|
|
Markdown: rendered,
|
|
suppressNotification: req.noNotify,
|
|
})
|
|
}
|
|
|
|
func initialReportResult(req GenerateRequest, resolved report.Resolved, inspection PromptInspectionResult) *ReportResult {
|
|
metadata := resolved.Metadata()
|
|
return &ReportResult{
|
|
ReportID: resolved.Definition.ID, ReportName: resolved.Definition.Name,
|
|
PromptID: resolved.Definition.PromptID, PromptVersion: resolved.Definition.PromptVersion,
|
|
RunID: metadata.RunID, GeneratedAt: metadata.GeneratedAt, Timezone: req.Config.WeatherAPI.Timezone,
|
|
ValidPeriod: metadata.ValidPeriod,
|
|
ProfileID: inspection.ProfileID, BackendID: inspection.BackendID, ModelName: inspection.ModelName,
|
|
}
|
|
}
|
|
|
|
type promptPublicationRequest struct {
|
|
GenerateRequest
|
|
Resolved report.Resolved
|
|
OutputPath string
|
|
Result *ReportResult
|
|
Markdown []byte
|
|
suppressNotification bool
|
|
}
|
|
|
|
func publishPromptReport(ctx context.Context, req promptPublicationRequest) (*ReportResult, error) {
|
|
if err := publicationContextError(ctx); err != nil {
|
|
return req.Result, generatedReportError(req.Resolved, req.Result.RunID, "publish report", err)
|
|
}
|
|
if err := fileutil.WriteFileAtomicContext(ctx, req.OutputPath, req.Markdown); err != nil {
|
|
if contextErr := publicationContextError(ctx); contextErr != nil {
|
|
return req.Result, generatedReportError(req.Resolved, req.Result.RunID, "publish report", contextErr)
|
|
}
|
|
return req.Result, err
|
|
}
|
|
req.Result.OutputPath = req.OutputPath
|
|
if req.suppressNotification {
|
|
return req.Result, nil
|
|
}
|
|
notification, err := notifyReport(ctx, req.Config, req.Resolved, req.Result.OutputPath, req.Result.RunID, req.Result.GeneratedAt, req.Notifier)
|
|
req.Result.Notification = notification
|
|
if err != nil {
|
|
return req.Result, err
|
|
}
|
|
return req.Result, nil
|
|
}
|
|
|
|
func generatedPreparationError(resolved report.Resolved, runID string, err error) error {
|
|
var preparation *preparationError
|
|
if errors.As(err, &preparation) {
|
|
return generatedReportError(resolved, runID, preparation.operation, preparation.err)
|
|
}
|
|
return generatedReportError(resolved, runID, "prepare report", err)
|
|
}
|
|
|
|
func generatedProfileExecutionError(resolved report.Resolved, runID string, err error) error {
|
|
var execution *profileExecutionError
|
|
if errors.As(err, &execution) {
|
|
if execution.callbackFailure {
|
|
return execution.err
|
|
}
|
|
return generatedReportError(resolved, runID, execution.operation, execution.err)
|
|
}
|
|
return generatedReportError(resolved, runID, "execute prompt", err)
|
|
}
|
|
|
|
func classifiedPromptError(operation string, err error) error {
|
|
if promptexec.CategoryOf(err) != "" {
|
|
return err
|
|
}
|
|
return promptexec.NewError(promptexec.Generation, operation, err)
|
|
}
|
|
|
|
func publicationContextError(ctx context.Context) error {
|
|
if err := ctx.Err(); err != nil {
|
|
if errors.Is(err, context.DeadlineExceeded) {
|
|
return promptexec.NewError(promptexec.DeadlineExceeded, "context expired before output publication", err)
|
|
}
|
|
return promptexec.NewError(promptexec.Canceled, "context canceled before output publication", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func promptDebugWriteError(err error) error {
|
|
return promptexec.NewError(promptexec.InvalidConfiguration, "write requested prompt debug artifact", err)
|
|
}
|