Return generation attempt results as one value

This commit is contained in:
2026-11-05 14:25:07 +00:00
parent 8c0fb15727
commit df208e132b
2 changed files with 54 additions and 39 deletions

View File

@@ -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.

View File

@@ -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 {