From df208e132be991be4d1e7e2541f4b044061805eb Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Thu, 5 Nov 2026 14:25:07 +0000 Subject: [PATCH] Return generation attempt results as one value --- docs/roadmap/implementation.md | 5 ++ internal/generation/coordinator.go | 88 +++++++++++++++++------------- 2 files changed, 54 insertions(+), 39 deletions(-) diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index ee0c807..40a9717 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -672,6 +672,11 @@ tests pass on Go 1.27.3; the internal source description is updated. response/error behavior, timing facts and cancellation; no new event or HTTP fact is fabricated for an injected client. +Completion: built-in attempts return a private result containing outcome, +dispatch, capacity, duration, and start-time facts. Generation, use-case, and +engine tests pass on Go 1.27.3; permit lifetime and injected dispatch are +unchanged. + ## Stage 26: Share Generation Accounting Projections **Catalog:** 25, completion. **Files:** generation accounting/evidence/observer tests. diff --git a/internal/generation/coordinator.go b/internal/generation/coordinator.go index 3529872..070a903 100644 --- a/internal/generation/coordinator.go +++ b/internal/generation/coordinator.go @@ -126,66 +126,66 @@ func (c *Coordinator) generate( return finish(nil, err) } ordinal := attempts + 1 - outcome, dispatched, capacityWait, generationDuration, startedAt := c.execute(ctx, req.Target.BackendID, prepared, captureEvidence) - if dispatched { + result := c.execute(ctx, req.Target.BackendID, prepared, captureEvidence) + if result.Dispatched { attempts++ } if captureEvidence { attempt := AttemptEvidence{ - StartedAt: startedAt.UTC(), - Dispatched: dispatched, - ResponseReceived: outcome.Evidence.ResponseReceived, - HTTP: cloneHTTPEvidence(outcome.Evidence), - CapacityWait: capacityWait, - RequestDuration: generationDuration, - Failure: ClassifyFailure(outcome.Err), - ResponseDecoded: outcome.ResponseDecoded, - GenerationCompleted: outcome.GenerationCompleted, + StartedAt: result.StartedAt.UTC(), + Dispatched: result.Dispatched, + ResponseReceived: result.Outcome.Evidence.ResponseReceived, + HTTP: cloneHTTPEvidence(result.Outcome.Evidence), + CapacityWait: result.CapacityWait, + RequestDuration: result.RequestDuration, + Failure: ClassifyFailure(result.Outcome.Err), + ResponseDecoded: result.Outcome.ResponseDecoded, + GenerationCompleted: result.Outcome.GenerationCompleted, } - if outcome.StructuredOutputRequested != nil { - requested := *outcome.StructuredOutputRequested + if result.Outcome.StructuredOutputRequested != nil { + requested := *result.Outcome.StructuredOutputRequested attempt.StructuredOutputRequested = &requested } - if dispatched { - usage.Add(outcome.Usage, outcome.UsagePresence) + if result.Dispatched { + usage.Add(result.Outcome.Usage, result.Outcome.UsagePresence) var attemptUsage observation.UsageAccumulator - attemptUsage.Add(outcome.Usage, outcome.UsagePresence) + attemptUsage.Add(result.Outcome.Usage, result.Outcome.UsagePresence) attempt.Usage = attemptUsage.Observed() } evidence.Attempts = append(evidence.Attempts, attempt) } if recorder != nil { recorder.Attempt(observation.AttemptData{Generation: generation, Attempt: ordinal, - Dispatched: dispatched, CapacityWait: capacityWait, GenerationDuration: generationDuration, - Outcome: attemptOutcome(outcome.Err), Failure: ClassifyFailure(outcome.Err), - StatusCode: outcome.StatusCode, Usage: outcome.Usage, UsagePresence: outcome.UsagePresence}) + Dispatched: result.Dispatched, CapacityWait: result.CapacityWait, GenerationDuration: result.RequestDuration, + Outcome: attemptOutcome(result.Outcome.Err), Failure: ClassifyFailure(result.Outcome.Err), + StatusCode: result.Outcome.StatusCode, Usage: result.Outcome.Usage, UsagePresence: result.Outcome.UsagePresence}) } - if outcome.Err == nil { + if result.Outcome.Err == nil { if err := ctx.Err(); err != nil { if captureEvidence { - return finish(outcome.Response, err) + return finish(result.Outcome.Response, err) } return finish(nil, err) } - return finish(outcome.Response, nil) + return finish(result.Outcome.Response, nil) } - if !dispatched || !eligibleStatus(outcome) { + if !result.Dispatched || !eligibleStatus(result.Outcome) { if captureEvidence { - return finish(outcome.Response, outcome.Err) + return finish(result.Outcome.Response, result.Outcome.Err) } - return finish(nil, outcome.Err) + return finish(nil, result.Outcome.Err) } if retriesUsed == req.TransientRetries { if retriesUsed == 0 { - return finish(nil, outcome.Err) + return finish(nil, result.Outcome.Err) } - return finish(nil, &ExhaustedError{attempts: attempts, cause: outcome.Err}) + return finish(nil, &ExhaustedError{attempts: attempts, cause: result.Outcome.Err}) } if err := ctx.Err(); err != nil { return finish(nil, err) } retryOrdinal := retriesUsed + 1 - delay := retryDelay(c.backoff(retryOrdinal), outcome.RetryNotBefore, c.now()) + delay := retryDelay(c.backoff(retryOrdinal), result.Outcome.RetryNotBefore, c.now()) waitStart := c.now() waitErr := c.wait(ctx, delay) waitDuration := elapsed(waitStart, c.now()) @@ -306,27 +306,37 @@ func (c *Coordinator) generateInjected( return finish(response, nil) } -func (c *Coordinator) execute(ctx context.Context, backendID string, prepared *llm.PreparedGeneration, captureEvidence bool) (outcome llm.AttemptOutcome, dispatched bool, capacityWait, generationDuration time.Duration, startedAt time.Time) { - start := c.now() - startedAt = start +type attemptResult struct { + Outcome llm.AttemptOutcome + Dispatched bool + CapacityWait time.Duration + RequestDuration time.Duration + StartedAt time.Time +} + +func (c *Coordinator) execute(ctx context.Context, backendID string, prepared *llm.PreparedGeneration, captureEvidence bool) attemptResult { + result := attemptResult{StartedAt: c.now()} release, err := c.manager.Acquire(ctx, backendID) - capacityWait = c.capacityWait(backendID, start) + result.CapacityWait = c.capacityWait(backendID, result.StartedAt) if err != nil { - return llm.AttemptOutcome{Err: err}, false, capacityWait, 0, startedAt + result.Outcome.Err = err + return result } defer release() if err := ctx.Err(); err != nil { - return llm.AttemptOutcome{Err: err}, false, capacityWait, 0, startedAt + result.Outcome.Err = err + return result } dispatchStart := c.now() if captureEvidence { - outcome = prepared.ExecuteWithEvidence(ctx) - dispatched = outcome.Evidence.DispatchInvoked + result.Outcome = prepared.ExecuteWithEvidence(ctx) + result.Dispatched = result.Outcome.Evidence.DispatchInvoked } else { - outcome = prepared.Execute(ctx) - dispatched = true + result.Outcome = prepared.Execute(ctx) + result.Dispatched = true } - return outcome, dispatched, capacityWait, elapsed(dispatchStart, c.now()), startedAt + result.RequestDuration = elapsed(dispatchStart, c.now()) + return result } func (c *Coordinator) capacityWait(backendID string, start time.Time) time.Duration {