]> git.giacomolli.uk - onechat/blobdiff - build_run.go
Submit root events through the local socket
[onechat] / build_run.go
index 9c6b7671cb9b5262121662461ab6b6087919e262..44d636db9502e2b57bb0fd8951ef35f998346727 100644 (file)
@@ -19,12 +19,12 @@ import (
 // buildLineBytes is the most bytes of one line in a build input or usage log.
 const buildLineBytes = 1 << 20
 
+// buildWaitTimeout is the longest wait for pending memory builds.
+const buildWaitTimeout = time.Second
+
 // buildQuotaLimit is the largest increase in used quota points of a run.
 const buildQuotaLimit = 15.0
 
-// buildCallLimit is the most model calls of a run.
-const buildCallLimit = 660
-
 // BuildRun appends messages, builds their tree, and stops the run by the rules
 // of buildStop.
 type BuildRun struct {
@@ -33,6 +33,13 @@ type BuildRun struct {
        usagePath string
 }
 
+// DatedMessage is one message with an optional RFC 3339 date.
+type DatedMessage struct {
+       Kind string `json:"kind"`
+       Text string `json:"text"`
+       Date string `json:"date,omitempty"`
+}
+
 // Run appends the entries. It waits for the builds only before a user message,
 // as a turn does, so up to buildMax builds run at once in between, and it waits
 // for all builds at the end. It returns the number of messages appended and why
@@ -116,19 +123,12 @@ func (r *BuildRun) check() string {
 
 // waitBuilds waits up to one second without leaving a child context open.
 func (r *BuildRun) waitBuilds(ctx context.Context) error {
-       ctx, cancel := context.WithTimeout(ctx, time.Second)
+       ctx, cancel := context.WithTimeout(ctx, buildWaitTimeout)
        defer cancel()
 
        return r.memory.Wait(ctx)
 }
 
-// DatedMessage is one message with an optional RFC 3339 date.
-type DatedMessage struct {
-       Kind string `json:"kind"`
-       Text string `json:"text"`
-       Date string `json:"date,omitempty"`
-}
-
 // buildReadUsage reads the records of usage.jsonl at path, in the order of the
 // start of their calls. The file has them in the order of the end of their
 // calls, which differs when calls run at once.
@@ -151,8 +151,10 @@ func buildReadUsage(path string) (records []ModelUsageRecord, err error) {
 
        scanner := bufio.NewScanner(file)
        scanner.Buffer(make([]byte, 64*1024), buildLineBytes)
+
        for scanner.Scan() {
                var record ModelUsageRecord
+
                err = json.Unmarshal(scanner.Bytes(), &record)
                if err != nil {
                        return nil, fmt.Errorf("decode usage: %w", err)
@@ -173,9 +175,64 @@ func buildReadUsage(path string) (records []ModelUsageRecord, err error) {
        return records, nil
 }
 
+// buildStop returns why a build run must stop: a failed call, or a quota window
+// whose used percent grew by more than buildQuotaLimit points since the first
+// call. It returns "" when the run can go on.
+func buildStop(records []ModelUsageRecord) string {
+       for _, record := range records {
+               if record.Error != "" {
+                       return "call failed: " + record.Error
+               }
+       }
+
+       if len(records) == 0 {
+               return ""
+       }
+
+       first := buildUsedPercents(records[0])
+       for name, used := range buildUsedPercents(records[len(records)-1]) {
+               start, found := first[name]
+               if found && used-start > buildQuotaLimit {
+                       return fmt.Sprintf("%s grew from %g to %g", name, start, used)
+               }
+       }
+
+       return ""
+}
+
+// buildCallStart returns the start of the call of record, or the zero time when
+// its date is not valid.
+func buildCallStart(record ModelUsageRecord) time.Time {
+       start, err := time.Parse(time.RFC3339Nano, record.Date)
+       if err != nil {
+               return time.Time{}
+       }
+
+       return start
+}
+
+// buildUsedPercents returns the used percent of each quota window of record, by
+// header name.
+func buildUsedPercents(record ModelUsageRecord) map[string]float64 {
+       used := make(map[string]float64)
+       for name, value := range record.Quota {
+               if !strings.HasSuffix(name, "-used-percent") {
+                       continue
+               }
+
+               percent, err := strconv.ParseFloat(value, 64)
+               if err == nil {
+                       used[name] = percent
+               }
+       }
+
+       return used
+}
+
 // buildWriteReport writes the result of a build run.
 func buildWriteReport(out io.Writer, appended int, stop string, records []ModelUsageRecord, sizes []int) error {
        var input, cached, output, failed int
+
        for _, record := range records {
                input += record.InputTokens
                cached += record.CachedTokens
@@ -186,6 +243,7 @@ func buildWriteReport(out io.Writer, appended int, stop string, records []ModelU
        }
 
        var lines []string
+
        if stop != "" {
                lines = append(lines, "stopped early: "+stop)
        }
@@ -220,42 +278,6 @@ func buildWriteReport(out io.Writer, appended int, stop string, records []ModelU
        return nil
 }
 
-// buildStop returns why a build run must stop: a failed call, or a quota window
-// whose used percent grew by more than buildQuotaLimit points since the first
-// call. It returns "" when the run can go on.
-func buildStop(records []ModelUsageRecord) string {
-       for _, record := range records {
-               if record.Error != "" {
-                       return "call failed: " + record.Error
-               }
-       }
-
-       if len(records) == 0 {
-               return ""
-       }
-
-       first := buildUsedPercents(records[0])
-       for name, used := range buildUsedPercents(records[len(records)-1]) {
-               start, found := first[name]
-               if found && used-start > buildQuotaLimit {
-                       return fmt.Sprintf("%s grew from %g to %g", name, start, used)
-               }
-       }
-
-       return ""
-}
-
-// buildCallStart returns the start of the call of record, or the zero time when
-// its date is not valid.
-func buildCallStart(record ModelUsageRecord) time.Time {
-       start, err := time.Parse(time.RFC3339Nano, record.Date)
-       if err != nil {
-               return time.Time{}
-       }
-
-       return start
-}
-
 // buildRatio returns a / b, or 0 when b is 0.
 func buildRatio(a, b int) float64 {
        if b == 0 {
@@ -288,6 +310,7 @@ func buildTimeText(records []ModelUsageRecord) string {
        }
 
        var events []event
+
        seconds := make([]float64, 0, len(records))
        total := 0.0
        for _, record := range records {
@@ -324,6 +347,7 @@ func buildTimeText(records []ModelUsageRecord) string {
        }
 
        wall := events[len(events)-1].at.Sub(events[0].at).Seconds()
+
        slices.Sort(seconds)
 
        return fmt.Sprintf("time: %.0f s in all, %.1f calls per minute, %.1f s per call (median %.1f, max %.1f), calls at once: at most %d, %.1f on average", wall, 60*float64(len(seconds))/max(wall, 1e-9), total/float64(len(seconds)), seconds[len(seconds)/2], seconds[len(seconds)-1], most, total/max(wall, 1e-9))
@@ -339,21 +363,3 @@ func buildQuotaText(record ModelUsageRecord) string {
 
        return strings.Join(parts, " ")
 }
-
-// buildUsedPercents returns the used percent of each quota window of record, by
-// header name.
-func buildUsedPercents(record ModelUsageRecord) map[string]float64 {
-       used := make(map[string]float64)
-       for name, value := range record.Quota {
-               if !strings.HasSuffix(name, "-used-percent") {
-                       continue
-               }
-
-               percent, err := strconv.ParseFloat(value, 64)
-               if err == nil {
-                       used[name] = percent
-               }
-       }
-
-       return used
-}