Clean up MVP output handling
This commit is contained in:
@@ -214,9 +214,6 @@ func runPipelineCommand(args []string, stdout, stderr io.Writer, opts Options) i
|
|||||||
}
|
}
|
||||||
|
|
||||||
runOutputDir := filepath.Join(outputRoot(*outputDir), runDir.RunID())
|
runOutputDir := filepath.Join(outputRoot(*outputDir), runDir.RunID())
|
||||||
if err := writeOutputFiles(runOutputDir, output.OutputFiles); err != nil {
|
|
||||||
return failPipelineCommand(stderr, runDir, cfg.Diagnostics.Retention, err)
|
|
||||||
}
|
|
||||||
if err := runDir.WriteRunManifest(output.Manifest); err != nil {
|
if err := runDir.WriteRunManifest(output.Manifest); err != nil {
|
||||||
return failPipelineCommand(stderr, runDir, cfg.Diagnostics.Retention, fmt.Errorf("write diagnostics run manifest: %w", err))
|
return failPipelineCommand(stderr, runDir, cfg.Diagnostics.Retention, fmt.Errorf("write diagnostics run manifest: %w", err))
|
||||||
}
|
}
|
||||||
@@ -235,6 +232,9 @@ func runPipelineCommand(args []string, stdout, stderr io.Writer, opts Options) i
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return failPipelineCommand(stderr, runDir, cfg.Diagnostics.Retention, fmt.Errorf("write diagnostics run report: %w", err))
|
return failPipelineCommand(stderr, runDir, cfg.Diagnostics.Retention, fmt.Errorf("write diagnostics run report: %w", err))
|
||||||
}
|
}
|
||||||
|
if err := writeOutputFiles(runOutputDir, output.OutputFiles); err != nil {
|
||||||
|
return failPipelineCommand(stderr, runDir, cfg.Diagnostics.Retention, err)
|
||||||
|
}
|
||||||
if err := runDir.ApplyRetention(diagnostics.RetentionDecisionInput{
|
if err := runDir.ApplyRetention(diagnostics.RetentionDecisionInput{
|
||||||
RetentionMode: cfg.Diagnostics.Retention,
|
RetentionMode: cfg.Diagnostics.Retention,
|
||||||
RunSucceeded: true,
|
RunSucceeded: true,
|
||||||
|
|||||||
@@ -773,6 +773,40 @@ func TestRunPipelineRejectsUnsafeOutputFileName(t *testing.T) {
|
|||||||
if got := string(readFile(t, filepath.Join(runDir, diagnostics.ArtifactErrorLog))); !strings.Contains(got, "output file name") {
|
if got := string(readFile(t, filepath.Join(runDir, diagnostics.ArtifactErrorLog))); !strings.Contains(got, "output file name") {
|
||||||
t.Fatalf("error log = %q, want unsafe output file error", got)
|
t.Fatalf("error log = %q, want unsafe output file error", got)
|
||||||
}
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(runDir, diagnostics.ArtifactRunManifest)); err != nil {
|
||||||
|
t.Fatalf("expected diagnostics manifest after unsafe output file failure: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunPipelineWritesDiagnosticsArtifactsWhenDurableOutputWriteFails(t *testing.T) {
|
||||||
|
diagnosticsDir := t.TempDir()
|
||||||
|
configPath := writeTestConfig(t, mvpConfigYAMLWithDiagnostics("dnd-session", diagnosticsDir, "always"))
|
||||||
|
inputPath := writeSeriatimInput(t)
|
||||||
|
outputRootFile := writeFile(t, "not-a-directory", "occupied")
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
|
||||||
|
code := RunWithOptions([]string{"run", "dnd-session", "--config", configPath, "--input", inputPath, "--output-dir", outputRootFile}, &stdout, &stderr, Options{
|
||||||
|
LLMClientFactory: fakeLLMFactory(newFakeRunLLMClient(false), nil),
|
||||||
|
})
|
||||||
|
|
||||||
|
if code != 1 {
|
||||||
|
t.Fatalf("RunWithOptions() code = %d, want 1", code)
|
||||||
|
}
|
||||||
|
if !strings.Contains(stderr.String(), "create output directory") {
|
||||||
|
t.Fatalf("stderr = %q, want output directory error", stderr.String())
|
||||||
|
}
|
||||||
|
runDir := onlyChildDir(t, diagnosticsDir)
|
||||||
|
for _, name := range []string{
|
||||||
|
diagnostics.ArtifactRunManifest,
|
||||||
|
diagnostics.ArtifactWarnings,
|
||||||
|
diagnostics.ArtifactRunReport,
|
||||||
|
diagnostics.ArtifactErrorLog,
|
||||||
|
} {
|
||||||
|
if _, err := os.Stat(filepath.Join(runDir, name)); err != nil {
|
||||||
|
t.Fatalf("expected diagnostics artifact %q after durable output write failure: %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunPipelineWritesDiagnosticsArtifactsOnSuccess(t *testing.T) {
|
func TestRunPipelineWritesDiagnosticsArtifactsOnSuccess(t *testing.T) {
|
||||||
|
|||||||
@@ -190,10 +190,6 @@ type OutputFile struct {
|
|||||||
|
|
||||||
type OutputResult struct {
|
type OutputResult struct {
|
||||||
Files []OutputFile `json:"files,omitempty"`
|
Files []OutputFile `json:"files,omitempty"`
|
||||||
// Bytes is the legacy single-output payload. New encoders should return Files.
|
|
||||||
Bytes []byte `json:"-"`
|
|
||||||
// ContentType is the legacy single-output content type. New encoders should return Files.
|
|
||||||
ContentType string `json:"content_type,omitempty"`
|
|
||||||
Warnings []Warning `json:"warnings,omitempty"`
|
Warnings []Warning `json:"warnings,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -193,7 +193,11 @@ func (output integrationOutput) Key() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (output integrationOutput) Encode(ctx context.Context, req contracts.OutputRequest) (contracts.OutputResult, error) {
|
func (output integrationOutput) Encode(ctx context.Context, req contracts.OutputRequest) (contracts.OutputResult, error) {
|
||||||
return contracts.OutputResult{Bytes: []byte(`{}`), ContentType: "application/json"}, nil
|
return contracts.OutputResult{
|
||||||
|
Files: []contracts.OutputFile{
|
||||||
|
{Name: "output.json", ContentType: "application/json", Bytes: []byte(`{}`)},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type integrationValidator struct {
|
type integrationValidator struct {
|
||||||
|
|||||||
@@ -49,10 +49,6 @@ type RunOutput struct {
|
|||||||
Rejected []artifacts.RejectedArtifact `json:"rejected,omitempty"`
|
Rejected []artifacts.RejectedArtifact `json:"rejected,omitempty"`
|
||||||
Warnings []contracts.Warning `json:"warnings,omitempty"`
|
Warnings []contracts.Warning `json:"warnings,omitempty"`
|
||||||
OutputFiles []contracts.OutputFile `json:"-"`
|
OutputFiles []contracts.OutputFile `json:"-"`
|
||||||
// EncodedOutput is the legacy single-output payload. New callers should use OutputFiles.
|
|
||||||
EncodedOutput []byte `json:"-"`
|
|
||||||
// ContentType is the legacy single-output content type. New callers should use OutputFiles.
|
|
||||||
ContentType string `json:"content_type,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runner) Run(ctx context.Context, input RunInput) (RunOutput, error) {
|
func (r *Runner) Run(ctx context.Context, input RunInput) (RunOutput, error) {
|
||||||
@@ -143,10 +139,6 @@ func (r *Runner) Run(ctx context.Context, input RunInput) (RunOutput, error) {
|
|||||||
return failOutput(output), fmt.Errorf("validate output files from encoder %q: %w", encoder.Key(), err)
|
return failOutput(output), fmt.Errorf("validate output files from encoder %q: %w", encoder.Key(), err)
|
||||||
}
|
}
|
||||||
output.OutputFiles = files
|
output.OutputFiles = files
|
||||||
if len(encoded.Files) == 0 {
|
|
||||||
output.EncodedOutput = append([]byte(nil), encoded.Bytes...)
|
|
||||||
output.ContentType = encoded.ContentType
|
|
||||||
}
|
|
||||||
|
|
||||||
return output, nil
|
return output, nil
|
||||||
}
|
}
|
||||||
@@ -425,19 +417,8 @@ func manifestMetadataKey(module any) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func outputFilesFromResult(result contracts.OutputResult) ([]contracts.OutputFile, error) {
|
func outputFilesFromResult(result contracts.OutputResult) ([]contracts.OutputFile, error) {
|
||||||
files := result.Files
|
out := make([]contracts.OutputFile, 0, len(result.Files))
|
||||||
if len(files) == 0 && len(result.Bytes) > 0 {
|
for _, file := range result.Files {
|
||||||
files = []contracts.OutputFile{
|
|
||||||
{
|
|
||||||
Name: "output",
|
|
||||||
ContentType: result.ContentType,
|
|
||||||
Bytes: result.Bytes,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out := make([]contracts.OutputFile, 0, len(files))
|
|
||||||
for _, file := range files {
|
|
||||||
if err := validateOutputFileName(file.Name); err != nil {
|
if err := validateOutputFileName(file.Name); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ func TestNewAndDataTypes(t *testing.T) {
|
|||||||
Rejected: []artifacts.RejectedArtifact{{ValidatorName: "validator"}},
|
Rejected: []artifacts.RejectedArtifact{{ValidatorName: "validator"}},
|
||||||
Warnings: []contracts.Warning{{ReasonCode: "note", Message: "message"}},
|
Warnings: []contracts.Warning{{ReasonCode: "note", Message: "message"}},
|
||||||
OutputFiles: []contracts.OutputFile{{Name: "artifacts/generic.json", ContentType: "application/json", Bytes: []byte(`{}`)}},
|
OutputFiles: []contracts.OutputFile{{Name: "artifacts/generic.json", ContentType: "application/json", Bytes: []byte(`{}`)}},
|
||||||
EncodedOutput: []byte(`{}`),
|
|
||||||
ContentType: "application/json",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.Pipeline.ID != "pipeline-1" || input.SourceID != "source-1" {
|
if input.Pipeline.ID != "pipeline-1" || input.SourceID != "source-1" {
|
||||||
@@ -1142,8 +1140,6 @@ func (validator *runnerValidator) Validate(ctx context.Context, req contracts.Va
|
|||||||
type runnerOutputEncoder struct {
|
type runnerOutputEncoder struct {
|
||||||
key string
|
key string
|
||||||
files []contracts.OutputFile
|
files []contracts.OutputFile
|
||||||
bytes []byte
|
|
||||||
contentType string
|
|
||||||
warnings []contracts.Warning
|
warnings []contracts.Warning
|
||||||
err error
|
err error
|
||||||
requests []contracts.OutputRequest
|
requests []contracts.OutputRequest
|
||||||
@@ -1157,8 +1153,6 @@ func (encoder *runnerOutputEncoder) Encode(ctx context.Context, req contracts.Ou
|
|||||||
encoder.requests = append(encoder.requests, req)
|
encoder.requests = append(encoder.requests, req)
|
||||||
return contracts.OutputResult{
|
return contracts.OutputResult{
|
||||||
Files: encoder.files,
|
Files: encoder.files,
|
||||||
Bytes: encoder.bytes,
|
|
||||||
ContentType: encoder.contentType,
|
|
||||||
Warnings: encoder.warnings,
|
Warnings: encoder.warnings,
|
||||||
}, encoder.err
|
}, encoder.err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,10 +36,13 @@ func TestWalkingSkeletonFixture(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Run() error = %v, want nil", err)
|
t.Fatalf("Run() error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
if output.ContentType != "application/json" {
|
if len(output.OutputFiles) != 1 {
|
||||||
t.Fatalf("ContentType = %q, want application/json", output.ContentType)
|
t.Fatalf("len(OutputFiles) = %d, want 1", len(output.OutputFiles))
|
||||||
}
|
}
|
||||||
assertStructuralJSONEqual(t, output.EncodedOutput, expectedBytes)
|
if output.OutputFiles[0].ContentType != "application/json" {
|
||||||
|
t.Fatalf("ContentType = %q, want application/json", output.OutputFiles[0].ContentType)
|
||||||
|
}
|
||||||
|
assertStructuralJSONEqual(t, output.OutputFiles[0].Bytes, expectedBytes)
|
||||||
if llmClient.calls != 2 {
|
if llmClient.calls != 2 {
|
||||||
t.Fatalf("LLM calls = %d, want chunk count 2", llmClient.calls)
|
t.Fatalf("LLM calls = %d, want chunk count 2", llmClient.calls)
|
||||||
}
|
}
|
||||||
@@ -356,8 +359,9 @@ func (output walkingSkeletonOutput) Encode(ctx context.Context, req contracts.Ou
|
|||||||
return contracts.OutputResult{}, err
|
return contracts.OutputResult{}, err
|
||||||
}
|
}
|
||||||
return contracts.OutputResult{
|
return contracts.OutputResult{
|
||||||
Bytes: encoded,
|
Files: []contracts.OutputFile{
|
||||||
ContentType: "application/json",
|
{Name: "output.json", ContentType: "application/json", Bytes: encoded},
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -257,8 +257,9 @@ func (dndSpellsOutput) Key() string {
|
|||||||
|
|
||||||
func (dndSpellsOutput) Encode(ctx context.Context, req contracts.OutputRequest) (contracts.OutputResult, error) {
|
func (dndSpellsOutput) Encode(ctx context.Context, req contracts.OutputRequest) (contracts.OutputResult, error) {
|
||||||
return contracts.OutputResult{
|
return contracts.OutputResult{
|
||||||
Bytes: []byte(`{"encoded":true}`),
|
Files: []contracts.OutputFile{
|
||||||
ContentType: "application/json",
|
{Name: "output.json", ContentType: "application/json", Bytes: []byte(`{"encoded":true}`)},
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,8 +102,11 @@ func TestRunnerProcessesSeriatimInputWithDNDSpellsExtractor(t *testing.T) {
|
|||||||
extractorMetadata["response_schema_name"] != ResponseSchemaName {
|
extractorMetadata["response_schema_name"] != ResponseSchemaName {
|
||||||
t.Fatalf("extractor metadata = %#v, want prompt/schema identifiers", extractorMetadata)
|
t.Fatalf("extractor metadata = %#v, want prompt/schema identifiers", extractorMetadata)
|
||||||
}
|
}
|
||||||
if output.ContentType != "application/json" {
|
if len(output.OutputFiles) != 1 {
|
||||||
t.Fatalf("ContentType = %q, want application/json", output.ContentType)
|
t.Fatalf("len(OutputFiles) = %d, want 1", len(output.OutputFiles))
|
||||||
|
}
|
||||||
|
if output.OutputFiles[0].ContentType != "application/json" {
|
||||||
|
t.Fatalf("ContentType = %q, want application/json", output.OutputFiles[0].ContentType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,8 +63,11 @@ func TestRunnerProcessesSeriatimInputWithFakeModules(t *testing.T) {
|
|||||||
if extractor.calls != 1 {
|
if extractor.calls != 1 {
|
||||||
t.Fatalf("extractor calls = %d, want 1", extractor.calls)
|
t.Fatalf("extractor calls = %d, want 1", extractor.calls)
|
||||||
}
|
}
|
||||||
if output.ContentType != "application/json" {
|
if len(output.OutputFiles) != 1 {
|
||||||
t.Fatalf("ContentType = %q, want application/json", output.ContentType)
|
t.Fatalf("len(OutputFiles) = %d, want 1", len(output.OutputFiles))
|
||||||
|
}
|
||||||
|
if output.OutputFiles[0].ContentType != "application/json" {
|
||||||
|
t.Fatalf("ContentType = %q, want application/json", output.OutputFiles[0].ContentType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,8 +240,9 @@ func (runnerSeriatimOutput) Key() string {
|
|||||||
|
|
||||||
func (runnerSeriatimOutput) Encode(ctx context.Context, req contracts.OutputRequest) (contracts.OutputResult, error) {
|
func (runnerSeriatimOutput) Encode(ctx context.Context, req contracts.OutputRequest) (contracts.OutputResult, error) {
|
||||||
return contracts.OutputResult{
|
return contracts.OutputResult{
|
||||||
Bytes: []byte(`{"encoded":true}`),
|
Files: []contracts.OutputFile{
|
||||||
ContentType: "application/json",
|
{Name: "output.json", ContentType: "application/json", Bytes: []byte(`{"encoded":true}`)},
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user