121 lines
3.5 KiB
Go
121 lines
3.5 KiB
Go
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
|
||
|
|
}
|