返回 DeepSeek-Reasonix
serve.go
根目录 / cmd / e2ebench / serve.go
1 package main
2
3 import (
4 "encoding/json"
5 "fmt"
6 "io"
7 "net/http"
8 "os"
9 "path/filepath"
10 "sort"
11 "strings"
12 "time"
13 )
14
15 // serveState is one /api/state response: the per-task live digest the
16 // dashboard polls while a bench run is still writing trajectories.
17 type serveState struct {
18 Dir string `json:"dir"`
19 Now int64 `json:"now"`
20 Suite []string `json:"suite,omitempty"`
21 Tasks []serveTask `json:"tasks"`
22 }
23
24 type serveTask struct {
25 ID string `json:"id"`
26 Records int `json:"records"`
27 SpanMs int64 `json:"span_ms"`
28 ModelRounds int `json:"model_rounds"`
29 ToolMs int64 `json:"tool_ms"`
30 AgoMs int64 `json:"ago_ms"`
31 NoProgress int `json:"no_progress"`
32 Outcome *outcomeSummary `json:"outcome,omitempty"`
33 Rounds []serveRound `json:"rounds"`
34 }
35
36 type serveRound struct {
37 TS int64 `json:"t,omitempty"`
38 Exploration int `json:"e,omitempty"`
39 Verification int `json:"v,omitempty"`
40 Objective int `json:"o,omitempty"`
41 Regression int `json:"r,omitempty"`
42 Churn int `json:"c,omitempty"`
43 Legacy int `json:"g,omitempty"`
44 }
45
46 func runServeMode(dir, suite, addr string) error {
47 if dir == "" {
48 return fmt.Errorf("serve mode needs -trajectories <dir>")
49 }
50 var suiteIDs []string
51 if tasks, err := loadTasks(suite); err == nil {
52 for _, t := range tasks {
53 suiteIDs = append(suiteIDs, t.ID)
54 }
55 }
56 mux := http.NewServeMux()
57 mux.HandleFunc("/api/state", func(w http.ResponseWriter, _ *http.Request) {
58 state, err := collectServeState(dir)
59 if err != nil {
60 http.Error(w, err.Error(), http.StatusInternalServerError)
61 return
62 }
63 state.Suite = suiteIDs
64 w.Header().Set("Content-Type", "application/json")
65 _ = json.NewEncoder(w).Encode(state)
66 })
67 mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
68 w.Header().Set("Content-Type", "text/html; charset=utf-8")
69 _, _ = io.WriteString(w, serveHTML)
70 })
71 fmt.Printf("e2ebench live dashboard: http://%s (watching %s)\n", addr, dir)
72 return http.ListenAndServe(addr, mux)
73 }
74
75 // collectServeState re-summarizes every trajectory on each poll. Files are
76 // small and flushed per record, so live reads see every completed line.
77 func collectServeState(dir string) (*serveState, error) {
78 paths, err := filepath.Glob(filepath.Join(dir, "*.trajectory.jsonl"))
79 if err != nil {
80 return nil, err
81 }
82 sort.Strings(paths)
83 state := &serveState{Dir: dir, Now: time.Now().UnixMilli(), Tasks: []serveTask{}}
84 for _, path := range paths {
85 scan, err := scanTrajectoryFile(path)
86 if err != nil {
87 continue
88 }
89 s := scan.finish()
90 t := serveTask{
91 ID: strings.TrimSuffix(filepath.Base(path), ".trajectory.jsonl"),
92 Records: s.Records,
93 SpanMs: s.SpanMs,
94 ModelRounds: s.ModelRounds,
95 ToolMs: s.toolWall(),
96 AgoMs: -1,
97 NoProgress: s.NoProgressSignals,
98 Outcome: s.Outcome,
99 Rounds: make([]serveRound, 0, len(scan.outcomePoints)),
100 }
101 if fi, err := os.Stat(path); err == nil {
102 t.AgoMs = time.Since(fi.ModTime()).Milliseconds()
103 }
104 for _, p := range scan.outcomePoints {
105 t.Rounds = append(t.Rounds, serveRound{
106 TS: p.ts,
107 Exploration: p.exploration, Verification: p.verification,
108 Objective: p.objective, Regression: p.regression,
109 Churn: p.churn, Legacy: p.legacyGain,
110 })
111 }
112 state.Tasks = append(state.Tasks, t)
113 }
114 return state, nil
115 }
116
116 lines GO