Centralize D&D extraction request preparation

This commit is contained in:
2026-07-24 14:38:42 +00:00
parent 8e0b029f5f
commit e2cb0d901a
15 changed files with 144 additions and 185 deletions

View File

@@ -110,22 +110,7 @@ func (e *Extractor) Extract(ctx context.Context, req contracts.TypedExtractionRe
if e.llm == nil {
return contracts.TypedExtractionResult[dnd.NPCList]{}, extractorErrorf("LLM client must not be nil")
}
if ctx == nil {
return contracts.TypedExtractionResult[dnd.NPCList]{}, extractorErrorf("context must not be nil")
}
if err := ctx.Err(); err != nil {
return contracts.TypedExtractionResult[dnd.NPCList]{}, extractorErrorf("context error before extraction: %w", err)
}
if req.Source == nil {
return contracts.TypedExtractionResult[dnd.NPCList]{}, extractorErrorf("source must not be nil")
}
if req.Chunk == nil {
return contracts.TypedExtractionResult[dnd.NPCList]{}, extractorErrorf("chunk must not be nil")
}
if len(req.Chunk.Units) == 0 {
return contracts.TypedExtractionResult[dnd.NPCList]{}, extractorErrorf("chunk %q units must not be empty", req.Chunk.ID)
}
sourceInput, err := shared.ChunkPromptMaterial(req)
sourceInput, err := shared.PrepareChunkExtraction(ctx, req)
if err != nil {
return contracts.TypedExtractionResult[dnd.NPCList]{}, extractorErrorf("%w", err)
}

View File

@@ -173,19 +173,27 @@ func TestExtractMapsRawSemanticCandidatesWithoutRepair(t *testing.T) {
}
}
func TestExtractHandlesCancellationAndProviderErrors(t *testing.T) {
func TestExtractRetainsLocalErrorContextAndProviderFailures(t *testing.T) {
request := extractionRequest()
extractor := newExtractor(t, &fakeNPCsLLMClient{response: extractionResponse{NPCs: []npcResponse{}}})
canceled, cancel := context.WithCancel(context.Background())
cancel()
if _, err := extractor.Extract(canceled, request); err == nil || !strings.Contains(err.Error(), "context") {
t.Fatalf("canceled Extract() error = %v, want context error", err)
var nilExtractor *Extractor
for _, test := range []struct {
name string
extractor *Extractor
req contracts.TypedExtractionRequest
want string
}{
{name: "nil extractor", extractor: nilExtractor, req: request, want: "extractor"},
{name: "nil LLM client", extractor: &Extractor{}, req: request, want: "LLM client"},
{name: "wrapped preflight failure", extractor: extractor, req: mismatchedSourceInputRequest(request), want: "must match chunk"},
} {
t.Run(test.name, func(t *testing.T) {
if _, err := test.extractor.Extract(context.Background(), test.req); err == nil || !strings.Contains(err.Error(), "dnd npcs") || !strings.Contains(err.Error(), test.want) {
t.Fatalf("Extract() error = %v, want contextual local error", err)
}
})
}
_, err := extractor.Extract(context.Background(), mismatchedSourceInputRequest(request))
if err == nil || !strings.Contains(err.Error(), "dnd npcs") || !strings.Contains(err.Error(), "must match chunk") {
t.Fatalf("source input error = %v, want contextual source input error", err)
}
_, err = newExtractor(t, &fakeNPCsLLMClient{err: errors.New("provider unavailable")}).Extract(context.Background(), request)
_, err := newExtractor(t, &fakeNPCsLLMClient{err: errors.New("provider unavailable")}).Extract(context.Background(), request)
if err == nil || !strings.Contains(err.Error(), "dnd npcs") || !strings.Contains(err.Error(), "provider unavailable") {
t.Fatalf("provider Extract() error = %v, want contextual provider error", err)
}

View File

@@ -58,11 +58,6 @@ func newExtractor(t *testing.T, client contracts.StructuredLLMClient, references
return extractor
}
func emptyChunkRequest(req contracts.TypedExtractionRequest) contracts.TypedExtractionRequest {
req.Chunk = &source.Chunk{ID: req.Chunk.ID, SourceID: req.Chunk.SourceID, Index: req.Chunk.Index}
return req
}
func mismatchedSourceInputRequest(req contracts.TypedExtractionRequest) contracts.TypedExtractionRequest {
req.SourceInput = contracts.NewLLMInputMaterial("source", "application/json", []byte(`{"different":true}`), "sha256:other", "file:///other.json")
return req