Update the debug workflow to provide raw LLM output
This commit is contained in:
@@ -2,6 +2,7 @@ package pipeline
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -1289,6 +1290,46 @@ func TestRunRetriesSameModuleInputAfterValidatorRejection(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunDebugFailedChunkAttemptReferencesScopedLLMOutput(t *testing.T) {
|
||||
modules := defaultRunnerModules()
|
||||
modules.chunker.callLLM = true
|
||||
modules.chunker.llmPromptID = "runner.chunk"
|
||||
modules.chunker.err = errors.New("malformed structured output")
|
||||
recorder := newMemoryDebugRecorder()
|
||||
|
||||
_, err := New(newRunnerRegistries(t, modules)).Run(context.Background(), RunInput{
|
||||
Pipeline: resolvedPipeline(),
|
||||
LLMClient: debugResponseLLMClient{content: []byte(`{"raw":true}`), profileID: "debug-profile"},
|
||||
Debug: recorder,
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "malformed structured output") {
|
||||
t.Fatalf("Run() error = %v, want chunk failure", err)
|
||||
}
|
||||
|
||||
attempt := recorder.envelope(t, "chunk/attempt-01.json")
|
||||
if len(attempt.LLMCalls) != 1 {
|
||||
t.Fatalf("llm_calls = %#v, want one scoped call", attempt.LLMCalls)
|
||||
}
|
||||
call := attempt.LLMCalls[0]
|
||||
if call.CallID != "call-0001" || call.CanonicalPath != "llm/call-0001.json" || call.ScopedPath != "chunk/attempt-01/llm-call-0001.json" {
|
||||
t.Fatalf("llm call reference = %#v, want canonical and scoped paths", call)
|
||||
}
|
||||
if call.PromptID != "runner.chunk" || call.ProfileID != "debug-profile" || call.Error {
|
||||
t.Fatalf("llm call metadata = %#v, want prompt/profile and no call error", call)
|
||||
}
|
||||
|
||||
scoped := recorder.envelope(t, call.ScopedPath)
|
||||
scopedPayload, ok := scoped.Payload.(debugStructuredLLMCall)
|
||||
if !ok {
|
||||
t.Fatalf("scoped payload type = %T, want debugStructuredLLMCall", scoped.Payload)
|
||||
}
|
||||
wantContent := base64.StdEncoding.EncodeToString([]byte(`{"raw":true}`))
|
||||
if scopedPayload.Response.Content != wantContent {
|
||||
t.Fatalf("scoped response content = %q, want %q", scopedPayload.Response.Content, wantContent)
|
||||
}
|
||||
_ = recorder.envelope(t, call.CanonicalPath)
|
||||
}
|
||||
|
||||
func TestRunStopsRetryAfterConfiguredAttemptsAndRecordsAttemptCount(t *testing.T) {
|
||||
modules := defaultRunnerModules()
|
||||
validator := &runnerChainValidator{name: "chain-extract", approved: []bool{false}, reason: "bad_extract", message: "extract rejected"}
|
||||
@@ -1915,6 +1956,8 @@ type runnerChunker struct {
|
||||
err error
|
||||
failureErr error
|
||||
failuresBeforeSuccess int
|
||||
callLLM bool
|
||||
llmPromptID string
|
||||
manifestMetadata map[string]any
|
||||
requests []contracts.ChunkRequest
|
||||
}
|
||||
@@ -1937,6 +1980,20 @@ func (chunker *runnerChunker) Chunk(ctx context.Context, req contracts.ChunkRequ
|
||||
}
|
||||
return contracts.ChunkResult{}, err
|
||||
}
|
||||
if chunker.callLLM && req.LLMClient != nil {
|
||||
promptID := strings.TrimSpace(chunker.llmPromptID)
|
||||
if promptID == "" {
|
||||
promptID = "runner.chunk"
|
||||
}
|
||||
var out map[string]any
|
||||
if _, err := req.LLMClient.CompleteStructured(ctx, contracts.StructuredCompletionRequest{
|
||||
StageName: chunker.key,
|
||||
PromptID: promptID,
|
||||
ProfileID: req.LLMProfile,
|
||||
}, &out); err != nil {
|
||||
return contracts.ChunkResult{}, err
|
||||
}
|
||||
}
|
||||
return contracts.ChunkResult{
|
||||
Chunks: chunker.chunks,
|
||||
Warnings: chunker.warnings,
|
||||
@@ -2264,6 +2321,51 @@ func (client fakeLLMClient) CompleteStructured(ctx context.Context, req contract
|
||||
return contracts.StructuredCompletionResponse{}, nil
|
||||
}
|
||||
|
||||
type debugResponseLLMClient struct {
|
||||
content []byte
|
||||
profileID string
|
||||
err error
|
||||
}
|
||||
|
||||
func (client debugResponseLLMClient) CompleteStructured(ctx context.Context, req contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
|
||||
profileID := client.profileID
|
||||
if profileID == "" {
|
||||
profileID = req.ProfileID
|
||||
}
|
||||
return contracts.StructuredCompletionResponse{
|
||||
Content: append([]byte(nil), client.content...),
|
||||
ProfileID: profileID,
|
||||
}, client.err
|
||||
}
|
||||
|
||||
type memoryDebugRecorder struct {
|
||||
payloads map[string]any
|
||||
}
|
||||
|
||||
func newMemoryDebugRecorder() *memoryDebugRecorder {
|
||||
return &memoryDebugRecorder{payloads: map[string]any{}}
|
||||
}
|
||||
|
||||
func (recorder *memoryDebugRecorder) Enabled() bool { return true }
|
||||
|
||||
func (recorder *memoryDebugRecorder) WriteJSON(name string, payload any) error {
|
||||
recorder.payloads[name] = payload
|
||||
return nil
|
||||
}
|
||||
|
||||
func (recorder *memoryDebugRecorder) envelope(t *testing.T, name string) debugTimedEnvelope {
|
||||
t.Helper()
|
||||
payload, ok := recorder.payloads[name]
|
||||
if !ok {
|
||||
t.Fatalf("debug artifact %q not written; got %#v", name, recorder.payloads)
|
||||
}
|
||||
envelope, ok := payload.(debugTimedEnvelope)
|
||||
if !ok {
|
||||
t.Fatalf("debug artifact %q type = %T, want debugTimedEnvelope", name, payload)
|
||||
}
|
||||
return envelope
|
||||
}
|
||||
|
||||
type manifestReportingLLMClient struct {
|
||||
fakeLLMClient
|
||||
profiles []artifacts.LLMProfileManifest
|
||||
|
||||
Reference in New Issue
Block a user