Fix the five library adoption review findings

This commit is contained in:
2026-09-24 15:32:13 +00:00
parent c42bc01052
commit 665ee85215
19 changed files with 942 additions and 208 deletions

View File

@@ -1,15 +1,14 @@
package llm
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
)
const redactedSecret = "[REDACTED]"
"gitea.maximumdirect.net/eric/audita/internal/core/redaction"
)
var stageSanitizer = regexp.MustCompile(`[^a-zA-Z0-9_.-]+`)
@@ -30,18 +29,7 @@ type DiagnosticsWriter struct {
// NewDiagnosticsWriter creates a diagnostics writer rooted at dir.
func NewDiagnosticsWriter(dir string, secrets []string) *DiagnosticsWriter {
filtered := make([]string, 0, len(secrets))
for _, secret := range secrets {
secret = strings.TrimSpace(secret)
if secret != "" {
filtered = append(filtered, secret)
}
}
return &DiagnosticsWriter{
dir: dir,
secrets: filtered,
}
return &DiagnosticsWriter{dir: dir, secrets: append([]string(nil), secrets...)}
}
// WriteInteraction writes request/response/error artifacts for a generic LLM
@@ -94,33 +82,13 @@ func sanitizeStageName(stage string) string {
}
func (w *DiagnosticsWriter) writeJSON(path string, payload any) error {
raw, err := json.MarshalIndent(payload, "", " ")
redacted, err := redaction.New(w.secrets).JSON(payload)
if err != nil {
return fmt.Errorf("marshal json artifact %q: %w", filepath.Base(path), err)
}
redacted := redactJSONSecrets(raw, w.secrets)
if !json.Valid(redacted) {
return fmt.Errorf("redacted json artifact %q is invalid", filepath.Base(path))
}
redacted = append(redacted, '\n')
if err := os.WriteFile(path, redacted, 0o644); err != nil {
return fmt.Errorf("write json artifact %q: %w", filepath.Base(path), err)
}
return nil
}
func redactJSONSecrets(raw []byte, secrets []string) []byte {
if len(secrets) == 0 {
return raw
}
result := string(raw)
for _, secret := range secrets {
if secret == "" {
continue
}
result = strings.ReplaceAll(result, "Bearer "+secret, "Bearer "+redactedSecret)
result = strings.ReplaceAll(result, secret, redactedSecret)
}
return []byte(result)
}

View File

@@ -61,7 +61,7 @@ func TestDiagnosticsWriterRedactsSecrets(t *testing.T) {
if strings.Contains(content, secret) {
t.Fatalf("secret leaked in %s: %s", filepath.Base(path), content)
}
if !strings.Contains(content, redactedSecret) {
if !strings.Contains(content, "[REDACTED]") {
t.Fatalf("redaction marker missing in %s: %s", filepath.Base(path), content)
}
if !json.Valid(raw) {

View File

@@ -12,6 +12,7 @@ import (
"sync/atomic"
"gitea.maximumdirect.net/eric/audita/internal/core/config"
"gitea.maximumdirect.net/eric/audita/internal/core/redaction"
"gitea.maximumdirect.net/eric/audita/internal/framework/responseschema"
"gitea.maximumdirect.net/eric/audita/internal/prompts"
pk "gitea.maximumdirect.net/eric/promptkit"
@@ -95,16 +96,5 @@ func RedactError(err error, c config.Config) string {
if err == nil {
return ""
}
message := err.Error()
var secrets []string
for _, backend := range c.Backends {
if backend.Credential != nil && backend.Credential.Value() != "" {
secrets = append(secrets, backend.Credential.Value())
}
}
sort.Slice(secrets, func(i, j int) bool { return len(secrets[i]) > len(secrets[j]) })
for _, secret := range secrets {
message = strings.ReplaceAll(message, secret, "[REDACTED]")
}
return message
return redaction.New(ConfiguredSecrets(&c)).String(err.Error())
}

View File

@@ -6,6 +6,7 @@ import (
ck "gitea.maximumdirect.net/eric/ConfigKit"
"gitea.maximumdirect.net/eric/audita/internal/core/config"
"gitea.maximumdirect.net/eric/audita/internal/core/redaction"
"gopkg.in/yaml.v3"
)
@@ -45,10 +46,14 @@ func (r *Runtime) SelectedProfilesYAML() ([]byte, error) {
if err != nil {
return nil, err
}
return yaml.Marshal(struct {
raw, err := yaml.Marshal(struct {
Proposal selectedProfileView `yaml:"proposal"`
Validation selectedProfileView `yaml:"validation"`
}{proposal, validation})
if err != nil {
return nil, err
}
return redaction.New(ConfiguredSecrets(&r.Config)).YAML(raw)
}
type derivationView struct {

View File

@@ -29,86 +29,74 @@ func BuildUtilization(output *runner.RunOutput, backends map[string]config.Backe
return result
}
result.RunWallTimeMS = output.RunWallTimeMS
for id, backend := range backends {
group := llm.ObservationSnapshot{}
for _, call := range output.Observations.Calls {
if call.Prompt.BackendID == id {
group.Calls = append(group.Calls, call)
addModuleCall(&result, call)
groups := make(map[string]*llm.ObservationSnapshot, len(backends))
callBackends := make(map[uint64]string, len(output.Observations.Calls))
moduleIndices := make(map[string]int)
moduleIndex := func(key, instance string) int {
if i, ok := moduleIndices[instance]; ok {
return i
}
i := len(result.Modules)
moduleIndices[instance] = i
result.Modules = append(result.Modules, reporting.ModuleUtilization{ModuleKey: key, ModuleInstance: instance})
return i
}
// Seed configured order, including modules that made no LLM calls.
for _, module := range output.ModuleResults {
i := moduleIndex(module.ModuleKey, module.ModuleInstance)
result.Modules[i].ModuleWallTimeMS = module.CompletedAt.Sub(module.StartedAt).Milliseconds()
}
for _, call := range output.Observations.Calls {
id := call.Prompt.BackendID
if groups[id] == nil {
groups[id] = &llm.ObservationSnapshot{}
}
groups[id].Calls = append(groups[id].Calls, call)
callBackends[call.CallID] = id
if call.Context.ModuleInstance != "" {
i := moduleIndex(call.Context.ModuleKey, call.Context.ModuleInstance)
switch call.Context.Role {
case "proposal":
result.Modules[i].ProposalLLMCalls++
case "validation":
result.Modules[i].ValidationLLMCalls++
}
}
}
for _, event := range output.Observations.Events {
if id, ok := callBackends[event.CallID]; ok {
groups[id].Events = append(groups[id].Events, event)
}
}
for id, backend := range backends {
group := groups[id]
if group == nil {
group = &llm.ObservationSnapshot{}
}
var wait, generation, retry, wall time.Duration
for _, call := range group.Calls {
wall += call.WallDuration
}
for _, event := range output.Observations.Events {
if hasCall(group.Calls, event.CallID) {
group.Events = append(group.Events, event)
if event.Kind == string(pk.ExecutionFinished) {
wait += event.CapacityWait
generation += event.GenerationDuration
retry += event.RetryDelay
}
for _, event := range group.Events {
if event.Kind == string(pk.ExecutionFinished) {
wait += event.CapacityWait
generation += event.GenerationDuration
retry += event.RetryDelay
}
}
result.Backends[id] = reporting.BackendUtilization{
Concurrency: backend.Concurrency, QueueCapacity: backend.QueueCapacity,
LLMSummary: mapLLMSummary(llm.Summarize(group)),
LLMSummary: mapLLMSummary(llm.Summarize(*group)),
CapacityWaitMS: wait.Milliseconds(), GenerationMS: generation.Milliseconds(),
RetryDelayMS: retry.Milliseconds(), LogicalWallMS: wall.Milliseconds(),
}
}
for _, module := range output.ModuleResults {
found := false
for i := range result.Modules {
if result.Modules[i].ModuleInstance == module.ModuleInstance {
result.Modules[i].ModuleWallTimeMS = module.CompletedAt.Sub(module.StartedAt).Milliseconds()
found = true
break
}
}
if !found {
result.Modules = append(result.Modules, reporting.ModuleUtilization{ModuleKey: module.ModuleKey, ModuleInstance: module.ModuleInstance, ModuleWallTimeMS: module.CompletedAt.Sub(module.StartedAt).Milliseconds()})
}
}
for _, timing := range output.ValidatorTimings {
result.Validators = append(result.Validators, reporting.ValidatorUtilization{ModuleKey: timing.ModuleKey, ModuleInstance: timing.ModuleInstance, ValidatorKey: timing.ValidatorName, LLMBacked: timing.LLMBacked, Calls: timing.Calls, ElapsedMS: timing.ElapsedMS})
}
return result
}
func hasCall(calls []llm.LogicalCall, id uint64) bool {
for _, call := range calls {
if call.CallID == id {
return true
}
}
return false
}
func addModuleCall(result *reporting.UtilizationDiagnostics, call llm.LogicalCall) {
if call.Context.ModuleInstance == "" {
return
}
for i := range result.Modules {
if result.Modules[i].ModuleInstance == call.Context.ModuleInstance {
if call.Context.Role == "proposal" {
result.Modules[i].ProposalLLMCalls++
} else if call.Context.Role == "validation" {
result.Modules[i].ValidationLLMCalls++
}
return
}
}
module := reporting.ModuleUtilization{ModuleKey: call.Context.ModuleKey, ModuleInstance: call.Context.ModuleInstance}
if call.Context.Role == "proposal" {
module.ProposalLLMCalls++
} else if call.Context.Role == "validation" {
module.ValidationLLMCalls++
}
result.Modules = append(result.Modules, module)
}
type executionCall struct {
CallID uint64 `json:"call_id"`
Context structuredoutput.DiagnosticContext `json:"context"`
@@ -150,13 +138,12 @@ type executionEvent struct {
func BuildLLMExecution(snapshot llm.ObservationSnapshot) any {
calls := make([]executionCall, 0, len(snapshot.Calls))
events := make([]executionEvent, 0, len(snapshot.Events))
eventsByCall := make(map[uint64][]llm.ExecutionObservation, len(snapshot.Calls))
for _, event := range snapshot.Events {
eventsByCall[event.CallID] = append(eventsByCall[event.CallID], event)
}
for _, call := range snapshot.Calls {
group := llm.ObservationSnapshot{Calls: []llm.LogicalCall{call}}
for _, event := range snapshot.Events {
if event.CallID == call.CallID {
group.Events = append(group.Events, event)
}
}
group := llm.ObservationSnapshot{Calls: []llm.LogicalCall{call}, Events: eventsByCall[call.CallID]}
var finished *time.Time
if !call.Finished.IsZero() {
value := call.Finished

View File

@@ -2,6 +2,7 @@ package processreport
import (
"encoding/json"
"fmt"
"strings"
"testing"
"time"
@@ -54,3 +55,72 @@ func TestReportSummaryAvailability(t *testing.T) {
t.Fatalf("initialized empty summary: %+v", got)
}
}
func BenchmarkExecutionReports(b *testing.B) {
for _, n := range []int{1000, 10000, 20000} {
snapshot := llm.ObservationSnapshot{}
for i := 0; i < n; i++ {
snapshot.Calls = append(snapshot.Calls, llm.LogicalCall{CallID: uint64(i + 1), Prompt: structuredoutput.PromptMetadata{BackendID: "local"}, Context: structuredoutput.DiagnosticContext{ModuleInstance: "grammar", Role: "proposal"}})
snapshot.Events = append(snapshot.Events, llm.ExecutionObservation{CallID: uint64(i + 1), Kind: string(pk.ExecutionFinished), Attempts: 1})
}
output := &runner.RunOutput{Observations: snapshot}
b.Run(fmt.Sprintf("execution/%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
BuildLLMExecution(snapshot)
}
})
b.Run(fmt.Sprintf("utilization/%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
BuildUtilization(output, map[string]config.Backend{"local": {Concurrency: 1}})
}
})
}
}
func TestIndexedReportsPreserveOrderAndPartialUsage(t *testing.T) {
snapshot := llm.ObservationSnapshot{
Calls: []llm.LogicalCall{
{CallID: 3, Context: structuredoutput.DiagnosticContext{Role: "validation", ModuleInstance: "grammar_2"}, Prompt: structuredoutput.PromptMetadata{BackendID: "b"}},
{CallID: 1, Context: structuredoutput.DiagnosticContext{Role: "proposal", ModuleInstance: "grammar_1"}, Prompt: structuredoutput.PromptMetadata{BackendID: "a"}},
{CallID: 2, Context: structuredoutput.DiagnosticContext{Role: "validation", ModuleInstance: "grammar_1"}, Prompt: structuredoutput.PromptMetadata{BackendID: "a"}},
},
Events: []llm.ExecutionObservation{
{CallID: 1, Kind: string(pk.ExecutionFinished), Attempts: 1, Usage: llm.Usage{Total: llm.TokenCount{Value: 7, Known: true, Complete: true}}},
{CallID: 3, Kind: string(pk.ExecutionFinished), Attempts: 1},
{CallID: 2, Kind: string(pk.ExecutionFinished), Attempts: 1},
},
}
raw, err := json.Marshal(BuildLLMExecution(snapshot))
if err != nil {
t.Fatal(err)
}
var execution struct {
Calls []executionCall `json:"calls"`
Events []executionEvent `json:"events"`
}
if err := json.Unmarshal(raw, &execution); err != nil {
t.Fatal(err)
}
for i, call := range snapshot.Calls {
if execution.Calls[i].CallID != call.CallID {
t.Fatal("call order changed")
}
}
for i, event := range snapshot.Events {
if execution.Events[i].CallID != event.CallID {
t.Fatal("event order changed")
}
}
if execution.Calls[1].Summary.Usage.Total.Value != 7 || !execution.Calls[1].Summary.Usage.Total.Complete || execution.Calls[0].Summary.Usage.Total.Known {
t.Fatalf("call usage: %+v", execution.Calls)
}
output := &runner.RunOutput{Observations: snapshot}
utilization := BuildUtilization(output, map[string]config.Backend{"a": {}, "b": {}})
total := utilization.Backends["a"].Usage.Total
if total.Value != 7 || !total.Known || total.Complete || utilization.Backends["a"].ProviderAttempts != 2 {
t.Fatalf("partial backend usage: %+v", utilization)
}
if len(utilization.Modules) != 2 || utilization.Modules[1].ProposalLLMCalls != 1 || utilization.Modules[1].ValidationLLMCalls != 1 {
t.Fatalf("module grouping: %+v", utilization.Modules)
}
}

View File

@@ -0,0 +1,145 @@
package runner
import (
"context"
"errors"
"net/http"
"os"
"path/filepath"
"sync/atomic"
"testing"
"time"
ck "gitea.maximumdirect.net/eric/ConfigKit"
"gitea.maximumdirect.net/eric/audita/internal/core/chunking"
"gitea.maximumdirect.net/eric/audita/internal/core/config"
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
"gitea.maximumdirect.net/eric/audita/internal/framework/llm"
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
"gitea.maximumdirect.net/eric/audita/internal/framework/responseschema"
"gitea.maximumdirect.net/eric/audita/internal/framework/structuredoutput"
"gitea.maximumdirect.net/eric/audita/internal/framework/validators"
"gitea.maximumdirect.net/eric/audita/internal/prompts"
pk "gitea.maximumdirect.net/eric/promptkit"
)
type contextValidator struct {
validate func(context.Context, validators.Request) (validators.Result, error)
}
func (v contextValidator) Name() string { return "cancel-test" }
func (v contextValidator) Validate(ctx context.Context, req validators.Request) (validators.Result, error) {
return v.validate(ctx, req)
}
type cancelTransport struct {
entered chan struct{}
calls atomic.Int32
}
func (t *cancelTransport) RoundTrip(req *http.Request) (*http.Response, error) {
t.calls.Add(1)
t.entered <- struct{}{}
<-req.Context().Done()
return nil, req.Context().Err()
}
func TestValidatorFailureCancelsActiveAndPendingProviderWork(t *testing.T) {
dir := t.TempDir()
if err := os.Mkdir(filepath.Join(dir, "profiles"), 0700); err != nil {
t.Fatal(err)
}
main := filepath.Join(dir, "config.yml")
body := "version: 2\nbackends:\n shared:\n endpoint: http://provider.invalid/v1\n concurrency: 1\n queue_capacity: 1\nllm:\n proposal_profile: base\n transient_retries: 0\n repair_attempts: 0\n"
if err := os.WriteFile(main, []byte(body), 0600); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "profiles/base.yaml"), []byte("id: base\nbackend: shared\nmodel: local\ntimeout_seconds: 30\n"), 0600); err != nil {
t.Fatal(err)
}
loaded, err := config.Load(ck.File(main), "")
if err != nil {
t.Fatal(err)
}
transport := &cancelTransport{entered: make(chan struct{}, 3)}
runtime, err := llm.NewRuntime(context.Background(), loaded.Config, main, &http.Client{Transport: transport})
if err != nil {
t.Fatal(err)
}
collector := llm.NewObservationCollector()
hooks := collector.Hooks()
started := make(chan struct{}, 2)
hooks.OnEvent = func(event pk.ExecutionEvent) {
if event.Kind == pk.ExecutionStarted {
started <- struct{}{}
}
}
client, err := runtime.NewRoleClient("validation", hooks)
if err != nil {
t.Fatal(err)
}
origin := errors.New("originating validator failure")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
validator := contextValidator{validate: func(ctx context.Context, req validators.Request) (validators.Result, error) {
if *req.SectionIndex == 0 {
// Hold the failure until one call is active and both peer executions started.
for i := 0; i < 2; i++ {
select {
case <-started:
case <-ctx.Done():
return validators.Result{}, ctx.Err()
}
}
select {
case <-transport.entered:
case <-ctx.Done():
return validators.Result{}, ctx.Err()
}
return validators.Result{}, origin
}
var out map[string]any
_, err := client.CompleteStructured(ctx, structuredoutput.Request{
PromptID: prompts.PromptIDValidatorEditorialReview, PromptVersion: prompts.VersionV1,
Variables: map[string]string{"TranscriptDescriptionBlock": "", "PayloadJSON": "[]"}, Schema: responseschema.ValidatorDecisionSetKey,
Context: structuredoutput.DiagnosticContext{ModuleKey: req.ModuleKey, ModuleInstance: req.ModuleInstance, SectionIndex: req.SectionIndex},
}, &out)
return validators.Result{}, err
}}
module := fakeModule{key: "grammar", policy: proposals.ReplacementPolicyRequireUnique, validators: []contracts.Validator{validator}, proposeF: func(req contracts.ProposalRequest) ([]proposals.CorrectionProposal, error) {
return []proposals.CorrectionProposal{{TargetSegmentID: 1, OriginalText: "hello", CorrectedText: "Hello", Confidence: 1}}, nil
}}
sections := []chunking.Section{}
for i := 0; i < 3; i++ {
sections = append(sections, chunking.Section{Index: i, Segments: []schema.Segment{{ID: 1, Text: "hello"}}})
}
done := make(chan error, 1)
go func() {
_, err := runModulePipeline(ctx, collectSectionProposalsInput{Module: module, Spec: contracts.ModuleRunSpec{ModuleKey: "grammar", InstanceName: "grammar"}, Policy: module.policy, Config: &loaded.Config, Sections: sections})
done <- err
}()
select {
case err := <-done:
if !errors.Is(err, origin) {
t.Fatalf("lost originating failure: %v", err)
}
case <-time.After(2 * time.Second):
t.Fatal("validator failure did not cancel and join peers")
}
if transport.calls.Load() != 1 {
t.Fatalf("pending call dispatched: %d", transport.calls.Load())
}
snapshot := collector.Snapshot()
if len(snapshot.Calls) != 2 {
t.Fatalf("partial observations missing: %+v", snapshot)
}
for _, call := range snapshot.Calls {
if call.Finished.IsZero() || call.Failure != structuredoutput.FailureRuntime {
t.Fatalf("unfinished/cause lost: %+v", call)
}
}
if got := llm.Summarize(snapshot); got.ProviderAttempts != 1 {
t.Fatalf("attempt count: %+v", got)
}
}

View File

@@ -332,7 +332,7 @@ func runModulePipeline(ctx context.Context, input collectSectionProposalsInput)
var (
firstErr error
errOnce sync.Once
errMu sync.Mutex
vwg sync.WaitGroup
)
@@ -340,10 +340,17 @@ func runModulePipeline(ctx context.Context, input collectSectionProposalsInput)
if err == nil {
return
}
errOnce.Do(func() {
errMu.Lock()
defer errMu.Unlock()
if firstErr == nil {
firstErr = err
cancel()
})
}
}
getErr := func() error {
errMu.Lock()
defer errMu.Unlock()
return firstErr
}
for result := range proposalResults {
@@ -351,13 +358,16 @@ func runModulePipeline(ctx context.Context, input collectSectionProposalsInput)
setErr(result.err)
continue
}
if firstErr != nil {
if getErr() != nil {
continue
}
out.Warnings = append(out.Warnings, result.warnings...)
pending[result.sectionPos] = result
for {
if getErr() != nil {
break
}
sectionResult, ok := pending[nextSectionToProcess]
if !ok {
break
@@ -385,18 +395,19 @@ func runModulePipeline(ctx context.Context, input collectSectionProposalsInput)
go func(sectionPos int, enriched []proposals.EnrichedCorrectionProposal, sectionMetadata contracts.SectionMetadata) {
defer vwg.Done()
validated, err := validateSectionCandidates(runCtx, validateSectionCandidatesInput{
Spec: input.Spec,
Policy: input.Policy,
Glossary: input.Glossary,
Config: input.Config,
WorkingTranscript: transcriptFromSection(sectionResult.section),
ModuleInstanceForStages: fmt.Sprintf("%s:section-%04d", input.Spec.InstanceName, sectionMetadata.Index),
Validators: validatorsOrdered,
SectionEnriched: enriched,
ValidationLLMClient: input.ValidationClient,
DiagnosticsDir: input.ValidationDiagnosticsDir,
Timings: input.Timings,
Spec: input.Spec,
Policy: input.Policy,
Glossary: input.Glossary,
Config: input.Config,
WorkingTranscript: transcriptFromSection(sectionResult.section),
SectionIndex: &sectionMetadata.Index,
Validators: validatorsOrdered,
SectionEnriched: enriched,
ValidationLLMClient: input.ValidationClient,
DiagnosticsDir: input.ValidationDiagnosticsDir,
Timings: input.Timings,
})
setErr(err)
validationResults <- sectionValidationResult{
sectionPos: sectionPos,
enriched: enriched,
@@ -477,25 +488,25 @@ func runModulePipeline(ctx context.Context, input collectSectionProposalsInput)
return out.Warnings[i].Message < out.Warnings[j].Message
})
if firstErr != nil {
return out, firstErr
if err := getErr(); err != nil {
return out, err
}
return out, nil
}
type validateSectionCandidatesInput struct {
Spec contracts.ModuleRunSpec
Policy proposals.ReplacementPolicy
Glossary *schema.Glossary
Config *config.Config
WorkingTranscript *schema.Transcript
ModuleInstanceForStages string
Validators []contracts.Validator
SectionEnriched []proposals.EnrichedCorrectionProposal
ValidationLLMClient contracts.StructuredLLMClient
DiagnosticsDir string
Timings *validatorTimingCollector
Spec contracts.ModuleRunSpec
Policy proposals.ReplacementPolicy
Glossary *schema.Glossary
Config *config.Config
WorkingTranscript *schema.Transcript
SectionIndex *int
Validators []contracts.Validator
SectionEnriched []proposals.EnrichedCorrectionProposal
ValidationLLMClient contracts.StructuredLLMClient
DiagnosticsDir string
Timings *validatorTimingCollector
}
type validateSectionCandidatesResult struct {
@@ -527,7 +538,8 @@ func validateSectionCandidates(ctx context.Context, input validateSectionCandida
WorkingTranscript: input.WorkingTranscript,
CandidateProposal: eligible,
ModuleKey: input.Spec.ModuleKey,
ModuleInstance: input.ModuleInstanceForStages,
ModuleInstance: input.Spec.InstanceName,
SectionIndex: input.SectionIndex,
ReplacementPolicy: input.Policy,
Glossary: input.Glossary,
Config: input.Config,

View File

@@ -438,8 +438,8 @@ func (c *captureContractStructuredClient) CompleteStructured(ctx context.Context
func (c *stageAwareStructuredClient) CompleteStructured(ctx context.Context, req contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
section := 0
if req.Context.BatchIndex != nil {
section = *req.Context.BatchIndex
if req.Context.SectionIndex != nil {
section = *req.Context.SectionIndex
}
if c.startedSection != nil {
select {

View File

@@ -107,11 +107,15 @@ func (v *LLMBackedValidator) Validate(ctx context.Context, req Request) (Result,
var completion StructuredCompletionResponse
responseSchema := responseschema.MustLookup(responseschema.ValidatorDecisionSetKey)
promptMetadata := validatorPromptMetadata(v.validatorType)
stage := stagename.ValidatorBatch(req.ModuleInstance, v.name, batch.BatchIndex)
stageInstance := req.ModuleInstance
if req.SectionIndex != nil {
stageInstance = fmt.Sprintf("%s:section-%04d", stageInstance, *req.SectionIndex)
}
stage := stagename.ValidatorBatch(stageInstance, v.name, batch.BatchIndex)
completion, err = req.LLMClient.CompleteStructured(ctx, StructuredCompletionRequest{
PromptID: promptMetadata.PromptID, PromptVersion: promptMetadata.PromptVersion,
Variables: variables, Schema: responseschema.ValidatorDecisionSetKey,
Context: structuredoutput.DiagnosticContext{Role: "validation", ModuleKey: req.ModuleKey, ModuleInstance: req.ModuleInstance, BatchIndex: &batch.BatchIndex, ValidatorName: v.name},
Context: structuredoutput.DiagnosticContext{Role: "validation", ModuleKey: req.ModuleKey, ModuleInstance: req.ModuleInstance, SectionIndex: req.SectionIndex, BatchIndex: &batch.BatchIndex, ValidatorName: v.name},
}, &response)
artifacts := InteractionArtifacts{}

View File

@@ -29,6 +29,7 @@ const (
// Request is the runtime input shared by deterministic validators.
type Request struct {
SectionIndex *int `json:"section_index,omitempty"`
WorkingTranscript *schema.Transcript `json:"-"`
CandidateProposal []proposals.EnrichedCorrectionProposal `json:"candidate_proposals"`
ModuleKey string `json:"module_key"`