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

147 lines
4.9 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"
"github.com/google/uuid"
)
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-register", flag.ContinueOnError)
flags.SetOutput(stderr)
suitePath := flags.String("suite", "", "评测集JSON路径")
observationsPath := flags.String("observations", "", "生成本报告的脱敏观测JSON路径")
reportPath := flags.String("report", "", "agent-eval生成的完整报告路径")
tenantID := flags.String("tenant", "", "运营范围客户组织UUID")
configID := flags.String("config", "", "待登记证据的运行配置版本UUID")
apiBaseURL := flags.String("api", "http://127.0.0.1:58080", "SaaS API基础地址")
tokenFile := flags.String("access-token-file", "", "OIDC访问令牌文件;省略时读取AGENT_EVAL_ACCESS_TOKEN")
allowFailed := flags.Bool("allow-failed", false, "明确允许登记FAILED报告;FAILED不会满足发布门禁")
dryRun := flags.Bool("dry-run", false, "只校验并显示登记摘要,不发送请求")
if err := flags.Parse(args); err != nil {
return 2
}
if *suitePath == "" || *observationsPath == "" || *reportPath == "" || *tenantID == "" || *configID == "" || flags.NArg() != 0 {
fmt.Fprintln(stderr, "usage: agent-eval-register -suite <suite.json> -observations <observations.json> -report <report.json> -tenant <uuid> -config <uuid> [-api <url>] [-access-token-file <path>] [-allow-failed] [-dry-run]")
return 2
}
if _, err := uuid.Parse(*tenantID); err != nil {
fmt.Fprintln(stderr, "tenant必须是有效UUID")
return 2
}
if _, err := uuid.Parse(*configID); err != nil {
fmt.Fprintln(stderr, "config必须是有效UUID")
return 2
}
var suite agenteval.Suite
if err := decodeFile(*suitePath, &suite); err != nil {
fmt.Fprintf(stderr, "读取评测集失败: %v\n", err)
return 2
}
var artifact agenteval.Artifact
if err := decodeFile(*reportPath, &artifact); err != nil {
fmt.Fprintf(stderr, "读取评测报告失败: %v\n", err)
return 2
}
var observations agenteval.ObservationRun
if err := decodeFile(*observationsPath, &observations); err != nil {
fmt.Fprintf(stderr, "读取评测观测失败: %v\n", err)
return 2
}
if err := agenteval.ValidateArtifact(suite, observations, artifact, *allowFailed); err != nil {
fmt.Fprintf(stderr, "评测报告不能登记: %v\n", err)
return 2
}
key, err := agenteval.RegistrationIdempotencyKey(*configID, artifact.RegistrationPayload)
if err != nil {
fmt.Fprintf(stderr, "生成幂等键失败: %v\n", err)
return 2
}
if *dryRun {
summary := map[string]any{
"dry_run": true, "tenant_id": *tenantID, "config_version_id": *configID,
"suite_version": artifact.Report.SuiteVersion, "suite_sha256": artifact.Report.SuiteSHA256,
"agent_version": artifact.Report.AgentVersion, "result": artifact.Report.Result,
"report_ref": artifact.RegistrationPayload.ReportRef, "idempotency_key": key,
}
_ = json.NewEncoder(stdout).Encode(summary)
return 0
}
token, err := readToken(*tokenFile)
if err != nil {
fmt.Fprintf(stderr, "读取访问令牌失败: %v\n", err)
return 2
}
registrar, err := agenteval.NewRegistrar(*apiBaseURL, token, &http.Client{Timeout: 15 * time.Second})
if err != nil {
fmt.Fprintf(stderr, "创建登记客户端失败: %v\n", err)
return 2
}
result, err := registrar.Register(context.Background(), *tenantID, *configID, artifact.RegistrationPayload)
if err != nil {
fmt.Fprintf(stderr, "登记评测证据失败: %v;request_id=%s\n", err, result.RequestID)
return 1
}
_ = json.NewEncoder(stdout).Encode(map[string]any{
"evaluation_id": result.EvaluationID, "status_code": result.StatusCode,
"idempotency_replayed": result.Replayed, "request_id": result.RequestID,
"idempotency_key": key,
})
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
}