Files
story/cmd/agent-eval-collect/main.go
T

121 lines
3.5 KiB
Go
Raw Normal View History

2026-08-26 06:41:07 +08:00
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 <suite.json> -manifest <manifest.json> [-api <url>] [-access-token-file <path>] [-out <observations.json>]")
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
}