Agent 评估框架后端服务(Go + PostgreSQL + Redis + Kafka)。
eval-server/ ├── cmd/server/ # 程序入口:加载配置、初始化 DI 容器、注册路由、启动 Kafka Worker ├── internal/ │ ├── domain/ # 核心业务逻辑(DDD 聚合根 + 领域服务) │ │ ├── evaluation/ # 评测运行、任务、结果;LLM/规则/span 评估器 │ │ ├── execution/ # 执行运行、任务、Trace Session、Span │ │ ├── suite/ # 测试套件、用例、轮次、标签、Span 期望 │ │ ├── config/ # 评估配置、指标、打分标准、Judge Profile │ │ ├── aggregation/ # 通过率与分数聚合 │ │ ├── governance/ # Golden Set、漂移检测、仲裁 │ │ ├── manualscore/ # 人工打分任务 │ │ ├── sampling/ # 采样策略与规则 │ │ ├── script/ # 可复用评估脚本 │ │ └── shared/ # 基础实体、租户、值对象 │ ├── application/ # 应用层(命令/查询/服务编排) │ │ ├── service/ # 应用服务:ExecutionApp、EvaluationApp、DashboardApp 等 │ │ ├── command/ # 写侧命令结构体 │ │ ├── query/ # 读侧查询接口与类型 │ │ └── dto/generated/ # Thrift 生成的 DTO(`make gen` 产出,已提交) │ ├── interface/http/ # HTTP 层 │ │ ├── handler/ # Gin 请求处理、DTO 解析、响应封装 │ │ └── middleware/ # Auth、RequestID、Tenant 中间件 │ └── infrastructure/ # 基础设施 │ ├── config/ # 配置加载(Viper + 环境变量) │ ├── di/ # 依赖注入容器(Wire 风格手工组装) │ ├── persistence/ # GORM 模型与 Repository 实现 │ ├── kafka/ # Kafka Consumer/Producer、任务 Worker │ ├── llm/ # LiteLLM / Gemini 客户端 │ ├── langfuse/ # Langfuse Trace 拉取客户端 │ ├── cache/ # Redis 缓存 │ ├── observability/ # OpenTelemetry、Prometheus 指标 │ ├── migrations/ # 迁移运行器(在进程内执行) │ └── crypto/ # AES 加密工具 ├── pkg/ # 公共包 │ ├── errors/ # 业务错误类型封装 │ └── logger/ # 结构化日志(zapcore 封装) ├── migrations/ # SQL 迁移文件(golang-migrate,32 个版本) ├── idl/ # Thrift IDL 定义(idl.thrift) ├── deploy/k8s/ # Kubernetes 清单(Deployment、Service、HPA 等) ├── scripts/ # 工具脚本 │ ├── gen_thrift.sh # 生成 Thrift DTO │ ├── openapi/ # 生成 OpenAPI 文档 │ └── seed/ # 数据库 Seed 脚本(`make seed`) ├── tests/ # 测试 │ ├── e2e/ # 端到端测试 │ ├── integration/ # 集成测试 │ └── perf/ # 性能测试(k6 + vegeta) └── docs/ # OpenAPI 文档(openapi.yaml,`make openapi` 生成)
架构分层(从外到内):
interface/http/handler → application/service → domain/*/service → infrastructure/persistence
Go 中禁止就地修改已有结构体,始终返回新结构体或副本:
// 错误:直接修改传入结构
func updateRun(r *EvalRun, status string) {
r.Status = status // 禁止
}
// 正确:返回新值
func withStatus(r EvalRun, status string) EvalRun {
r.Status = status
return r
}
_ = err。fmt.Errorf("...: %w", err) 包装上下文。errors.Is / errors.As 做类型判断。result, err := repo.FindByID(ctx, id)
if err != nil {
return fmt.Errorf("findEvalRun %s: %w", id, err)
}
在 Handler 层对所有入参做显式校验(struct 字段校验或手工检查),不允许裸传至 Service 层:
if req.SuiteID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "suite_id is required"})
return
}
| 场景 | 规范 |
|---|---|
| 导出标识符 | PascalCase |
| 未导出标识符 | camelCase |
| 接口命名 | 动词/能力名词,如 Invoker、Repository |
| 禁止 stutter | 包 evaluation 内不用 EvaluationService,用 Service |
所有密钥、URL、凭证必须通过环境变量注入,由 internal/infrastructure/config/config.go 统一加载:
// 禁止
const apiKey = "sk-xxxxx"
// 正确
apiKey := cfg.LLM.APIKey
context.Context 必须作为第一个参数贯穿整个调用链,不允许存储在结构体中:
func (s *Service) CreateRun(ctx context.Context, cmd CreateRunCommand) (*EvalRun, error)
pkg/logger 提供的结构化日志,禁止 fmt.Println、log.Println、log.Printf 出现在提交中。Debug,正常流程用 Info,可恢复错误用 Warn,不可恢复错误用 Error。make build # 编译二进制到当前目录(输出文件名:server)
make test # 运行全部单元测试
make lint # 运行 golangci-lint(启用 errcheck/govet/staticcheck/gofmt/goimports)
make format # gofmt + goimports 格式化所有 .go 文件
make gen # 执行 go generate(重新生成 Thrift DTO)
make openapi # 生成 docs/openapi.yaml
make migrate-up # 执行数据库迁移(需要 DATABASE_URL 环境变量)
make migrate-down # 回滚最近一次迁移
make seed # 运行 scripts/seed/main.go 填充测试数据
| 依赖 | 版本要求 | 说明 |
|---|---|---|
| Go | 1.23+ | 见 go.mod |
| Docker Desktop | 最新版 | 运行基础服务 |
| make | 系统自带 | 构建工具 |
| migrate | 可选 | 数据库迁移 CLI(go install github.com/golang-migrate/migrate/v4/cmd/migrate@latest) |
| golangci-lint | 可选 | 本地 lint(go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest) |
注意:eval-server 使用独立的 Docker stack(
eval_backend),端口与 eval-invoke 的 stack 不同,请勿混淆。
# 在 eval-server 目录执行
docker compose up -d postgres redis zookeeper kafka
服务端口映射:
| 服务 | 容器端口 | 宿主机端口 |
|---|---|---|
| PostgreSQL 15 | 5432 | 5433 |
| Redis 7 | 6379 | 6380 |
| Zookeeper | 2181 | 2181 |
| Kafka | 9092 / 29092 | 9092 / 29092 (推荐从宿主机连接用 29092) |
eval-server 启动前必须预先创建以下 Dead Letter Queue topic,否则消费失败时 Worker 会无限阻塞:
docker exec eval_backend-kafka-1 kafka-topics --bootstrap-server localhost:9092 --create --topic eval.evaluation-tasks.dlq --partitions 1 --replication-factor 1
docker exec eval_backend-kafka-1 kafka-topics --bootstrap-server localhost:9092 --create --topic eval.execution-tasks.dlq --partitions 1 --replication-factor 1
docker exec eval_backend-kafka-1 kafka-topics --bootstrap-server localhost:9092 --create --topic eval.trace-fetch.dlq --partitions 1 --replication-factor 1
cp .env.example .env
编辑 .env,重点核查以下字段(其余保持默认即可):
DATABASE_URL=postgres://postgres:postgres@localhost:5433/eval_backend?sslmode=disable REDIS_ADDR=localhost:6380 KAFKA_BROKERS=localhost:29092
迁移在服务启动时会自动执行(通过 internal/infrastructure/migrations/runner.go)。
如需手动执行:
make migrate-up
# 需要环境变量 DATABASE_URL,或直接传递:
DATABASE_URL=postgres://postgres:postgres@localhost:5433/eval_backend?sslmode=disable make migrate-up
make gen
go run ./cmd/server
或使用 Docker Compose 一体启动(含热重载):
docker compose up app
默认监听:
:8080:9090eval-server 在执行 exec-run 时通过 HTTP 调用 eval-invoke(路径 /api/eval/invoke)。调用目标地址来自数据库中存储的 agent_endpoints 记录(通过 /api/v1/agent-endpoints API 管理),而非固定配置项。
eval-invoke 使用独立的 Docker stack(agent_v1_3_2,端口 5432/6379),与 eval-server 的 stack 完全隔离。本地开发时默认 eval-invoke 监听 :8001。
curl http://127.0.0.1:8080/health # → {"status":"ok"}
curl http://127.0.0.1:8080/ready # → {"status":"ok"}(含 DB/Redis 连通检查)
curl http://127.0.0.1:8080/health/kafka # → {"status":"ok"}(Kafka broker 可达)
| 现象 | 原因 | 解决方法 |
|---|---|---|
| Kafka eval consumer 卡死,无法消费新消息 | DLQ topic 不存在,失败消息无法投递 | 创建 DLQ topic(见第 2 步),重启 eval-server |
GET /api/v1/scripts 返回 500 | GORM 期望表名 script_models,迁移创建的是 scripts | 检查迁移是否完整执行 |
| Trace session 一直是 PENDING | Trace 拉取失败未重试 | 手动触发 POST /api/v1/trace-sessions/<id>/retry-fetch |
Kafka Zookeeper NodeExists 错误 | Stale broker session | 等待 Zookeeper 就绪后重启 Kafka 容器 |
This repo follows the OpenSpec change plan in openspec/changes/implement-eval-backend/.
POST /api/v1/eval-runs no longer accepts eval_config_id or judge_profile_id.docs/migration-evaluation-start.md