返回 DeepSeek-Reasonix
rounds.go
根目录 / cmd / e2ebench / rounds.go
1 package main
2
3 import (
4 "fmt"
5 "slices"
6 "strings"
7 )
8
9 // renderRoundEfficiency is the knife-target line: how many rounds bought
10 // progress, where the wasted model seconds went, and what a solve pays for
11 // the waste (failed runs' waste charged to the solves, like every per-solved
12 // figure).
13 func renderRoundEfficiency(results []result) string {
14 useful, classified, solved := 0, 0, 0
15 var wastedMs int64
16 wasteCount := map[string]int{}
17 wasteMs := map[string]int64{}
18 for _, r := range results {
19 if r.Passed {
20 solved++
21 }
22 if r.Trajectory == nil {
23 continue
24 }
25 useful += r.Trajectory.UsefulRounds
26 wastedMs += r.Trajectory.WastedGapMs
27 for outcome, n := range r.Trajectory.RoundOutcomes {
28 classified += n
29 if !productiveOutcomes[outcome] {
30 wasteCount[outcome] += n
31 wasteMs[outcome] += r.Trajectory.RoundOutcomeMs[outcome]
32 }
33 }
34 }
35 if classified == 0 {
36 return ""
37 }
38 line := fmt.Sprintf("\n\n**Round efficiency**: **useful rounds** %d/%d (%s) · **wasted model time** %s",
39 useful, classified, pct(useful, classified), dur(wastedMs))
40 if solved > 0 {
41 line += fmt.Sprintf(" (**%s/solved**)", dur(wastedMs/int64(solved)))
42 }
43 outcomes := make([]string, 0, len(wasteMs))
44 for outcome := range wasteMs {
45 outcomes = append(outcomes, outcome)
46 }
47 slices.SortFunc(outcomes, func(a, b string) int {
48 if wasteMs[a] != wasteMs[b] {
49 return int(wasteMs[b] - wasteMs[a])
50 }
51 return strings.Compare(a, b)
52 })
53 parts := make([]string, 0, len(outcomes))
54 for _, outcome := range outcomes {
55 parts = append(parts, fmt.Sprintf("%s ×%d (%s)", outcome, wasteCount[outcome], dur(wasteMs[outcome])))
56 }
57 if len(parts) > 0 {
58 line += " · **waste breakdown**: " + strings.Join(parts, " · ")
59 }
60 return line
61 }
62
63 // trajScan is the running state of one trajectory pass.
64 type trajScan struct {
65 s *trajectorySummary
66 firstTS, lastTS int64
67 orphanMs, gapStart int64
68 gaps, cleanGaps []int64
69 delays []int64
70 allIntervals [][2]int64
71 inModel bool
72 taint string
73 streakRun int
74 batch *toolBatch
75
76 attemptBegin map[string]int64
77 attempts []modelAttempt
78 lastAttempt int // most recent closed attempt awaiting a usage tag
79 pendingRetry, compFrom int64
80 retryIvs, compIvs [][2]int64
81 firstDelta, firstToolTS int64
82
83 pendingGaps []gapInfo
84 seen map[string]bool // (name, args) pairs already dispatched
85 gapPlanner, gapCompact, gapHandoff bool
86 sawCallIDs bool
87
88 outcomePoints []outcomePoint
89 verifySeen, verifyPass map[string]bool
90 verifyPoints []verifyPoint
91
92 gapReason, gapCompl, gapPrompt int64
93
94 denyDelegations map[string]bool
95 delegationToolMs map[string]int64
96 }
97
98 // modelAttempt is one sampling attempt's wall interval; planner marks attempts
99 // whose closing usage event carried source "planner".
100 type modelAttempt struct {
101 iv [2]int64
102 planner bool
103 }
104
105 // productiveOutcomes are rounds that moved the task forward; everything else
106 // is the wasted/questionable bucket the report itemizes.
107 var productiveOutcomes = map[string]bool{
108 "evidence_gain": true, "mutation": true, "verification": true, "finalization": true,
109 "delegation": true,
110 }
111
112 // delegationTools are calls whose cost story is the delegation itself, not the
113 // local mutation/verification the batch would otherwise classify as.
114 var delegationTools = map[string]bool{
115 "task": true, "parallel_tasks": true, "fleet": true, "research": true,
116 }
117
118 // bookkeepingTools are ledger tools whose rounds cost a full round-trip
119 // without touching the workspace — bookkeeping cost remains visible even
120 // though ordered complete_step sign-offs may now share a provider round.
121 var bookkeepingTools = map[string]bool{
122 "complete_step": true, "todo_write": true, "wait": true, "bash_output": true,
123 }
124
125 // classifyRound names what one round's gap bought. Gap-level signals outrank
126 // batch analysis; a nil batch is the final answer round. Repeated failures
127 // land in duplicate_work; a first failure still counts as evidence (it
128 // localizes), matching the progress guard's scoring.
129 func classifyRound(gap gapInfo, b *toolBatch) string {
130 switch {
131 case gap.tainted:
132 return "recovery"
133 case gap.compaction:
134 return "compaction"
135 case gap.planner:
136 return "planning"
137 case gap.handoff:
138 return "handoff_retry"
139 }
140 if b == nil {
141 return "finalization"
142 }
143 verification, mutation, delegation := false, false, false
144 allBookkeeping, allDup := true, true
145 for _, c := range b.infos {
146 if c.verification == "passed" || c.verification == "failed" {
147 verification = true
148 }
149 if delegationTools[c.name] {
150 delegation = true
151 }
152 if c.resolved && !c.readOnly && !c.errored && !bookkeepingTools[c.name] {
153 mutation = true
154 }
155 if !bookkeepingTools[c.name] {
156 allBookkeeping = false
157 }
158 if !c.dup {
159 allDup = false
160 }
161 }
162 switch {
163 case delegation:
164 return "delegation"
165 case verification:
166 return "verification"
167 case mutation:
168 return "mutation"
169 case allBookkeeping:
170 return "bookkeeping"
171 case allDup:
172 return "duplicate_work"
173 }
174 return "evidence_gain"
175 }
176
177 func (t *trajScan) recordOutcome(outcome string, ms int64) {
178 s := t.s
179 if s.RoundOutcomes == nil {
180 s.RoundOutcomes = map[string]int{}
181 s.RoundOutcomeMs = map[string]int64{}
182 }
183 s.RoundOutcomes[outcome]++
184 s.RoundOutcomeMs[outcome] += ms
185 if productiveOutcomes[outcome] {
186 s.UsefulRounds++
187 return
188 }
189 s.WastedGapMs += ms
190 }
191
191 lines GO