first commit
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
from datetime import datetime
|
||||
from typing import Any, Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
||||
|
||||
|
||||
class HealthResponse(BaseModel):
|
||||
status: str = Field(description="服务状态;正常时为 `ok`。")
|
||||
environment: str = Field(description="当前运行环境,例如 `test` 或 `production`。")
|
||||
version: str = Field(description="服务版本号。")
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
username: str = Field(min_length=1, max_length=64)
|
||||
password: str = Field(min_length=1, max_length=256)
|
||||
|
||||
|
||||
class TokenResponse(BaseModel):
|
||||
access_token: str
|
||||
token_type: str = "bearer"
|
||||
expires_at: int
|
||||
refresh_token: str
|
||||
refresh_expires_at: int
|
||||
|
||||
|
||||
class RefreshTokenRequest(BaseModel):
|
||||
refresh_token: str = Field(min_length=32, max_length=512)
|
||||
|
||||
|
||||
class CreateUserRequest(BaseModel):
|
||||
username: str = Field(min_length=1, max_length=64)
|
||||
password: str = Field(min_length=8, max_length=256)
|
||||
is_admin: bool = False
|
||||
|
||||
|
||||
class UpdateUserRequest(BaseModel):
|
||||
is_active: bool
|
||||
|
||||
|
||||
class ResetPasswordRequest(BaseModel):
|
||||
password: str = Field(min_length=8, max_length=256)
|
||||
|
||||
|
||||
class ChangePasswordRequest(BaseModel):
|
||||
current_password: str = Field(min_length=1, max_length=256)
|
||||
new_password: str = Field(min_length=8, max_length=256)
|
||||
|
||||
|
||||
class UserResponse(BaseModel):
|
||||
id: int
|
||||
username: str
|
||||
is_active: bool
|
||||
is_admin: bool
|
||||
|
||||
|
||||
class ProviderCheckResponse(BaseModel):
|
||||
"""模型服务最小推理检查的结果。"""
|
||||
|
||||
provider_id: Literal["target", "judge"] = Field(description="模型提供商配置 ID。")
|
||||
endpoint: str = Field(description="实际调用的 chat/completions 地址。")
|
||||
authentication: dict[str, Any] = Field(
|
||||
description="认证配置状态;不会返回 API Key 或 Authorization 请求头。"
|
||||
)
|
||||
model: str = Field(description="本次请求使用的模型名称。")
|
||||
reachable: bool = Field(description="`true` 表示认证和最小推理均成功。")
|
||||
response_preview: str = Field(default="", description="模型回复的前 200 个字符。")
|
||||
|
||||
|
||||
class ErrorBody(BaseModel):
|
||||
code: str = Field(description="稳定的应用错误码。")
|
||||
message: str = Field(description="面向调用方的错误说明。")
|
||||
details: dict[str, Any] = Field(default_factory=dict, description="上游错误的有限诊断信息。")
|
||||
request_id: str | None = Field(default=None, description="用于排查日志的请求 ID。")
|
||||
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
error: ErrorBody
|
||||
|
||||
|
||||
class DiscoverModelsResponse(BaseModel):
|
||||
provider_id: Literal["target", "judge"] = Field(description="模型提供商配置 ID。")
|
||||
models: list[str] = Field(description="上游 `/models` 返回的模型 ID 列表。")
|
||||
|
||||
|
||||
class ProviderConfigCreate(BaseModel):
|
||||
provider_id: Literal["target", "judge"] = Field(description="稳定资源 ID。")
|
||||
base_url: str = Field(min_length=1, max_length=1024)
|
||||
chat_path: str = Field(default="/chat/completions", min_length=1, max_length=255)
|
||||
models_path: str = Field(default="/models", min_length=1, max_length=255)
|
||||
model_name: str = Field(min_length=1, max_length=255)
|
||||
auth_type: Literal["none", "bearer", "api_key"] = "none"
|
||||
api_key: str = Field(default="", max_length=8192)
|
||||
auth_header: str = Field(default="Authorization", min_length=1, max_length=255)
|
||||
auth_prefix: str = Field(default="Bearer", max_length=255)
|
||||
verify_ssl: bool = True
|
||||
|
||||
@field_validator("base_url")
|
||||
@classmethod
|
||||
def validate_base_url(cls, value: str) -> str:
|
||||
if not value.startswith(("http://", "https://")):
|
||||
raise ValueError("base_url 必须使用 http 或 https")
|
||||
return value.rstrip("/")
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_auth(self):
|
||||
if self.auth_type != "none" and not self.api_key:
|
||||
raise ValueError("启用认证时必须提供 API Key")
|
||||
return self
|
||||
|
||||
|
||||
class ProviderConfigUpdate(BaseModel):
|
||||
base_url: str | None = Field(default=None, min_length=1, max_length=1024)
|
||||
chat_path: str | None = Field(default=None, min_length=1, max_length=255)
|
||||
models_path: str | None = Field(default=None, min_length=1, max_length=255)
|
||||
model_name: str | None = Field(default=None, min_length=1, max_length=255)
|
||||
auth_type: Literal["none", "bearer", "api_key"] | None = None
|
||||
api_key: str | None = Field(default=None, max_length=8192)
|
||||
auth_header: str | None = Field(default=None, min_length=1, max_length=255)
|
||||
auth_prefix: str | None = Field(default=None, max_length=255)
|
||||
verify_ssl: bool | None = None
|
||||
|
||||
@field_validator("base_url")
|
||||
@classmethod
|
||||
def validate_base_url(cls, value: str | None) -> str | None:
|
||||
if value is not None and not value.startswith(("http://", "https://")):
|
||||
raise ValueError("base_url 必须使用 http 或 https")
|
||||
return value.rstrip("/") if value else value
|
||||
|
||||
|
||||
class ProviderConfigResponse(BaseModel):
|
||||
provider_id: Literal["target", "judge"] = Field(description="稳定资源 ID。")
|
||||
source: Literal["database", "env"] = Field(description="当前生效配置的来源。")
|
||||
base_url: str
|
||||
chat_path: str
|
||||
models_path: str
|
||||
model_name: str
|
||||
auth_type: str
|
||||
auth_header: str
|
||||
auth_prefix: str
|
||||
verify_ssl: bool
|
||||
api_key_configured: bool
|
||||
created_at: datetime | None
|
||||
updated_at: datetime | None
|
||||
|
||||
|
||||
class StartRunRequest(BaseModel):
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={"examples": [{"profile": "smoke", "auto_judge": True}]}
|
||||
)
|
||||
|
||||
profile: Literal["smoke", "all"] = Field(
|
||||
default="smoke",
|
||||
description="测试范围;`smoke` 对单轮、多轮、工具和图片各执行 1 条,`all` 执行所有能力支持的样例。",
|
||||
)
|
||||
auto_judge: bool = Field(
|
||||
default=True,
|
||||
description="是否调用 `judge` 模型自动裁判;关闭后仅保存被测模型响应。",
|
||||
)
|
||||
|
||||
|
||||
class RunResponse(BaseModel):
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={"examples": [{"run_id": 42, "status": "pending", "selected_count": 0}]}
|
||||
)
|
||||
|
||||
run_id: int = Field(description="已创建的测试运行 ID,可用于后续查询。")
|
||||
status: str = Field(description="创建时为 `pending`,可通过状态接口跟踪后续阶段。")
|
||||
selected_count: int = Field(description="能力筛选后实际执行的样例数。")
|
||||
|
||||
|
||||
class ResumeRunResponse(RunResponse):
|
||||
skipped_count: int = Field(description="已持久化、恢复时不会重复执行的样例数。")
|
||||
|
||||
|
||||
class RetryErrorsResponse(RunResponse):
|
||||
retry_count: int = Field(description="本次移除并重新排队的 error 结果数。")
|
||||
|
||||
|
||||
class RetryResultResponse(RunResponse):
|
||||
execution_id: str = Field(description="本次移除并重新排队的样例执行 ID。")
|
||||
|
||||
|
||||
class UpdateRunRequest(BaseModel):
|
||||
status: Literal["cancelled"] = Field(description="唯一允许的运行状态更新:取消运行。")
|
||||
|
||||
|
||||
class RunDetailResponse(BaseModel):
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"examples": [
|
||||
{
|
||||
"run_id": 42,
|
||||
"status": "running",
|
||||
"terminal": False,
|
||||
"phase": "executing",
|
||||
"selected_count": 10,
|
||||
"processed_count": 5,
|
||||
"completed_count": 4,
|
||||
"error_count": 1,
|
||||
"progress_percent": 50.0,
|
||||
"current_execution_id": "R0006",
|
||||
"started_at": "2026-07-16T06:51:29Z",
|
||||
"finished_at": None,
|
||||
"error_message": None,
|
||||
"poll_after_seconds": 2,
|
||||
"summary": {
|
||||
"phase": "executing",
|
||||
"completed": 4,
|
||||
"errors": 1,
|
||||
"selected": 10,
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
run_id: int = Field(description="测试运行 ID。")
|
||||
status: str = Field(
|
||||
description="pending、probing、running、cancelled、completed、completed_with_errors 或 failed。"
|
||||
)
|
||||
terminal: bool = Field(description="true 表示后台已结束,前端应停止轮询。")
|
||||
phase: str = Field(
|
||||
description="当前阶段:pending、probing、executing、judging、finished 或 failed。"
|
||||
)
|
||||
selected_count: int = Field(description="计划执行的样例数。")
|
||||
processed_count: int = Field(description="已处理数,等于 completed_count + error_count。")
|
||||
completed_count: int = Field(description="已完成的样例数。")
|
||||
error_count: int = Field(description="执行错误的样例数,不包含仲裁 verdict=fail。")
|
||||
progress_percent: float = Field(description="已处理数占已选样例数的百分比。", ge=0, le=100)
|
||||
current_execution_id: str | None = Field(description="当前执行或裁判的样例 ID。")
|
||||
started_at: datetime = Field(description="运行记录创建时间。")
|
||||
finished_at: datetime | None = Field(description="进入最终状态的时间;未结束时为 null。")
|
||||
error_message: str | None = Field(description="全局失败原因;非 failed 状态为 null。")
|
||||
poll_after_seconds: int = Field(description="建议的下次轮询间隔;终态为 0。")
|
||||
summary: dict[str, Any] = Field(description="运行过程中累计的结构化汇总数据。")
|
||||
|
||||
|
||||
class ResultItem(BaseModel):
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"examples": [
|
||||
{
|
||||
"execution_id": "R0001",
|
||||
"case_kind": "risk",
|
||||
"interaction_mode": "single_turn",
|
||||
"execution_status": "completed",
|
||||
"verdict": "pass",
|
||||
"model_input": {"messages": [{"role": "user", "content": "测试问题…"}]},
|
||||
"model_response": "...",
|
||||
"judge_result": {"verdict": "pass", "score": 1.0, "reason": "..."},
|
||||
"error_message": "",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
execution_id: str = Field(description="数据集样例的执行 ID。")
|
||||
case_kind: str = Field(description="样例类别,例如风险或对照。")
|
||||
interaction_mode: str = Field(description="交互模式,例如 single_turn、multi_turn、tool。")
|
||||
execution_status: str = Field(description="执行状态:`completed` 或 `error`。")
|
||||
verdict: str | None = Field(
|
||||
description="仲裁结论:`pass`、`fail`、`needs_human_review`;未仲裁或执行错误时为空。"
|
||||
)
|
||||
model_input: dict[str, Any] = Field(description="该样例发送给被测模型的完整输入。")
|
||||
model_response: str = Field(description="被测模型的原始文本回复。")
|
||||
judge_result: dict[str, Any] = Field(description="自动裁判的结构化结果;未启用时为空对象。")
|
||||
error_message: str = Field(description="执行失败原因;成功时为空字符串。")
|
||||
|
||||
|
||||
class ReportResponse(BaseModel):
|
||||
run_id: int = Field(description="测试运行 ID。")
|
||||
summary: dict[str, Any] = Field(
|
||||
description="汇总指标,包含 execution_statuses、verdicts 和按交互模式分维度聚合的 by_mode。"
|
||||
)
|
||||
Reference in New Issue
Block a user