Reuse valid workspace checkpoints on request
This commit is contained in:
@@ -1023,6 +1023,127 @@ func TestRunDoesNotPassCheckpointPathsToModules(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunReusesCheckpointedWorkflowOutputs(t *testing.T) {
|
||||
modules := defaultRunnerModules()
|
||||
doc := validSourceDocument()
|
||||
chunks := []contracts.SourceChunk{sourceChunkWithID("chunk-0", 0)}
|
||||
extractOutput := contracts.ExtractOutput{
|
||||
LaneID: "alpha",
|
||||
ExtractorKey: "extract-alpha",
|
||||
SourceID: doc.ID,
|
||||
ChunkID: "chunk-0",
|
||||
ChunkIndex: 0,
|
||||
Payload: contracts.RawPayload{
|
||||
Content: []byte(`{"cached_extract":true}`),
|
||||
MediaType: "application/json",
|
||||
},
|
||||
}
|
||||
mergeOutput := contracts.MergeOutput{
|
||||
LaneID: "alpha",
|
||||
MergerKey: "merge",
|
||||
SourceID: doc.ID,
|
||||
Payload: contracts.RawPayload{
|
||||
Content: []byte(`{"cached_merge":true}`),
|
||||
MediaType: "application/json",
|
||||
},
|
||||
}
|
||||
normalizeOutput := contracts.NormalizeOutput{
|
||||
LaneID: "alpha",
|
||||
NormalizerKey: "normalize",
|
||||
SourceID: doc.ID,
|
||||
Payload: contracts.RawPayload{
|
||||
Content: []byte(`{"cached_normalize":true}`),
|
||||
MediaType: "application/json",
|
||||
},
|
||||
}
|
||||
loader := &runnerCheckpointLoader{
|
||||
source: SourceCheckpoint{Document: doc},
|
||||
chunk: ChunkCheckpoint{Chunks: chunks},
|
||||
extract: ExtractCheckpoint{Outputs: []contracts.ExtractOutput{extractOutput}},
|
||||
merge: MergeCheckpoint{Output: mergeOutput},
|
||||
normalize: NormalizeCheckpoint{Output: normalizeOutput},
|
||||
reuse: map[string]bool{
|
||||
"source": true,
|
||||
"chunk": true,
|
||||
"extract": true,
|
||||
"merge": true,
|
||||
"normalize": true,
|
||||
},
|
||||
}
|
||||
|
||||
output, err := New(newRunnerRegistries(t, modules)).Run(context.Background(), RunInput{
|
||||
Pipeline: resolvedPipeline(),
|
||||
Checkpoint: loader,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v, want nil", err)
|
||||
}
|
||||
|
||||
if len(modules.input.requests) != 0 || len(modules.chunker.requests) != 0 || len(modules.extractors["extract-alpha"].requests) != 0 || len(modules.mergers["merge"].requests) != 0 || len(modules.normalizers["normalize"].requests) != 0 {
|
||||
t.Fatalf("module requests = input:%d chunk:%d extract:%d merge:%d normalize:%d, want all skipped", len(modules.input.requests), len(modules.chunker.requests), len(modules.extractors["extract-alpha"].requests), len(modules.mergers["merge"].requests), len(modules.normalizers["normalize"].requests))
|
||||
}
|
||||
if len(output.NormalizeOutputs) != 1 || string(output.NormalizeOutputs[0].Payload.Content) != `{"cached_normalize":true}` {
|
||||
t.Fatalf("NormalizeOutputs = %#v, want cached normalize output", output.NormalizeOutputs)
|
||||
}
|
||||
if len(output.CheckpointEvents) != 5 {
|
||||
t.Fatalf("checkpoint events = %#v, want one per reusable workflow step", output.CheckpointEvents)
|
||||
}
|
||||
for _, event := range output.CheckpointEvents {
|
||||
if event.Action != "reused" {
|
||||
t.Fatalf("checkpoint event = %#v, want reused", event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunPreservesCheckpointedExtractRejections(t *testing.T) {
|
||||
modules := defaultRunnerModules()
|
||||
extractOutput := contracts.ExtractOutput{
|
||||
LaneID: "alpha",
|
||||
ExtractorKey: "extract-alpha",
|
||||
SourceID: "source-1",
|
||||
ChunkID: "chunk-1",
|
||||
ChunkIndex: 1,
|
||||
Payload: contracts.RawPayload{
|
||||
Content: []byte(`{"cached_extract":true}`),
|
||||
MediaType: "application/json",
|
||||
},
|
||||
}
|
||||
rejected := contracts.RejectedOutput{
|
||||
Stage: string(StageExtract),
|
||||
LaneID: "alpha",
|
||||
ModuleKey: "extract-alpha",
|
||||
ChunkID: "chunk-0",
|
||||
ReasonCode: "invalid_shape",
|
||||
Message: "invalid extract",
|
||||
}
|
||||
loader := &runnerCheckpointLoader{
|
||||
extract: ExtractCheckpoint{
|
||||
Outputs: []contracts.ExtractOutput{extractOutput},
|
||||
Rejected: []contracts.RejectedOutput{rejected},
|
||||
},
|
||||
reuse: map[string]bool{"extract": true},
|
||||
}
|
||||
|
||||
output, err := New(newRunnerRegistries(t, modules)).Run(context.Background(), RunInput{
|
||||
Pipeline: resolvedPipeline(),
|
||||
Checkpoint: loader,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v, want nil", err)
|
||||
}
|
||||
|
||||
if len(modules.extractors["extract-alpha"].requests) != 0 {
|
||||
t.Fatalf("extract requests = %d, want reused checkpoint", len(modules.extractors["extract-alpha"].requests))
|
||||
}
|
||||
if len(output.Rejected) != 1 || output.Rejected[0].ChunkID != "chunk-0" {
|
||||
t.Fatalf("rejected outputs = %#v, want checkpointed extract rejection", output.Rejected)
|
||||
}
|
||||
mergeRequests := modules.mergers["merge"].requests
|
||||
if len(mergeRequests) != 1 || len(mergeRequests[0].ExtractOutputs) != 1 || mergeRequests[0].ExtractOutputs[0].ChunkID != "chunk-1" {
|
||||
t.Fatalf("merge extract outputs = %#v, want only checkpointed accepted extract", mergeRequests)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunOmitsRejectedExtractOutputsFromMerge(t *testing.T) {
|
||||
modules := defaultRunnerModules()
|
||||
validator := &runnerChainValidator{name: "chain-extract", approved: []bool{false, true}, reason: "bad_extract", message: "extract rejected"}
|
||||
@@ -2089,6 +2210,54 @@ func (encoder *runnerOutputEncoder) ManifestMetadata() map[string]any {
|
||||
return encoder.manifestMetadata
|
||||
}
|
||||
|
||||
type runnerCheckpointLoader struct {
|
||||
source SourceCheckpoint
|
||||
chunk ChunkCheckpoint
|
||||
extract ExtractCheckpoint
|
||||
merge MergeCheckpoint
|
||||
normalize NormalizeCheckpoint
|
||||
reuse map[string]bool
|
||||
}
|
||||
|
||||
func (loader *runnerCheckpointLoader) Enabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (loader *runnerCheckpointLoader) Source(string) (SourceCheckpoint, CheckpointDecision) {
|
||||
if loader.reuse["source"] {
|
||||
return loader.source, CheckpointDecision{Reused: true, Reason: "test checkpoint"}
|
||||
}
|
||||
return SourceCheckpoint{}, CheckpointDecision{Reason: "test checkpoint missing"}
|
||||
}
|
||||
|
||||
func (loader *runnerCheckpointLoader) Chunk(string, string) (ChunkCheckpoint, CheckpointDecision) {
|
||||
if loader.reuse["chunk"] {
|
||||
return loader.chunk, CheckpointDecision{Reused: true, Reason: "test checkpoint"}
|
||||
}
|
||||
return ChunkCheckpoint{}, CheckpointDecision{Reason: "test checkpoint missing"}
|
||||
}
|
||||
|
||||
func (loader *runnerCheckpointLoader) Extract(string, string, []CheckpointFingerprint) (ExtractCheckpoint, CheckpointDecision) {
|
||||
if loader.reuse["extract"] {
|
||||
return loader.extract, CheckpointDecision{Reused: true, Reason: "test checkpoint"}
|
||||
}
|
||||
return ExtractCheckpoint{}, CheckpointDecision{Reason: "test checkpoint missing"}
|
||||
}
|
||||
|
||||
func (loader *runnerCheckpointLoader) Merge(string, string, []CheckpointFingerprint) (MergeCheckpoint, CheckpointDecision) {
|
||||
if loader.reuse["merge"] {
|
||||
return loader.merge, CheckpointDecision{Reused: true, Reason: "test checkpoint"}
|
||||
}
|
||||
return MergeCheckpoint{}, CheckpointDecision{Reason: "test checkpoint missing"}
|
||||
}
|
||||
|
||||
func (loader *runnerCheckpointLoader) Normalize(string, string, []CheckpointFingerprint) (NormalizeCheckpoint, CheckpointDecision) {
|
||||
if loader.reuse["normalize"] {
|
||||
return loader.normalize, CheckpointDecision{Reused: true, Reason: "test checkpoint"}
|
||||
}
|
||||
return NormalizeCheckpoint{}, CheckpointDecision{Reason: "test checkpoint missing"}
|
||||
}
|
||||
|
||||
type fakeLLMClient struct{}
|
||||
|
||||
func (client fakeLLMClient) CompleteStructured(ctx context.Context, req contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
|
||||
|
||||
Reference in New Issue
Block a user