chore: establish local platform baseline
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user