package main import ( "context" "encoding/json" "errors" "flag" "fmt" "io" "net/http" "os" "strings" "time" "data-terminal-cloud/internal/agenteval" ) func main() { os.Exit(run(os.Args[1:], os.Stdout, os.Stderr)) } func run(args []string, stdout, stderr io.Writer) int { flags := flag.NewFlagSet("agent-eval-collect", flag.ContinueOnError) flags.SetOutput(stderr) suitePath := flags.String("suite", "", "评测集JSON路径") manifestPath := flags.String("manifest", "", "用例、通话和脱敏评分卡映射JSON路径") outputPath := flags.String("out", "", "观测结果输出路径;省略时输出到stdout") apiBaseURL := flags.String("api", "http://127.0.0.1:58080", "SaaS API基础地址") tokenFile := flags.String("access-token-file", "", "OIDC访问令牌文件;省略时读取AGENT_EVAL_ACCESS_TOKEN") if err := flags.Parse(args); err != nil { return 2 } if *suitePath == "" || *manifestPath == "" || flags.NArg() != 0 { fmt.Fprintln(stderr, "usage: agent-eval-collect -suite -manifest [-api ] [-access-token-file ] [-out ]") return 2 } var suite agenteval.Suite if err := decodeFile(*suitePath, &suite); err != nil { fmt.Fprintf(stderr, "读取评测集失败: %v\n", err) return 2 } var manifest agenteval.CollectionManifest if err := decodeFile(*manifestPath, &manifest); err != nil { fmt.Fprintf(stderr, "读取采集清单失败: %v\n", err) return 2 } token, err := readToken(*tokenFile) if err != nil { fmt.Fprintf(stderr, "读取访问令牌失败: %v\n", err) return 2 } collector, err := agenteval.NewCollector(*apiBaseURL, token, &http.Client{Timeout: 15 * time.Second}) if err != nil { fmt.Fprintf(stderr, "创建采集器失败: %v\n", err) return 2 } runResult, err := collector.Collect(context.Background(), suite, manifest) if err != nil { fmt.Fprintf(stderr, "采集评测观测失败: %v\n", err) return 1 } encoded, err := json.MarshalIndent(runResult, "", " ") if err != nil { fmt.Fprintf(stderr, "生成观测文件失败: %v\n", err) return 2 } encoded = append(encoded, '\n') if *outputPath == "" { if _, err = stdout.Write(encoded); err != nil { fmt.Fprintf(stderr, "输出观测文件失败: %v\n", err) return 2 } } else if err = os.WriteFile(*outputPath, encoded, 0o600); err != nil { fmt.Fprintf(stderr, "写入观测文件失败: %v\n", err) return 2 } else { fmt.Fprintf(stdout, "已采集%d个用例:%s\n", len(runResult.Observations), *outputPath) } return 0 } func readToken(path string) (string, error) { if strings.TrimSpace(path) == "" { value := strings.TrimSpace(os.Getenv("AGENT_EVAL_ACCESS_TOKEN")) if value == "" { return "", errors.New("set AGENT_EVAL_ACCESS_TOKEN or -access-token-file") } return value, nil } content, err := os.ReadFile(path) if err != nil { return "", err } value := strings.TrimSpace(string(content)) if value == "" || strings.ContainsAny(value, "\r\n\x00") { return "", errors.New("token file must contain one non-empty line") } return value, nil } func decodeFile(path string, target any) error { file, err := os.Open(path) if err != nil { return err } defer file.Close() decoder := json.NewDecoder(file) decoder.DisallowUnknownFields() if err = decoder.Decode(target); err != nil { return err } var trailing any if err = decoder.Decode(&trailing); !errors.Is(err, io.EOF) { if err == nil { return errors.New("JSON文件包含多个顶层值") } return err } return nil }