全量修复:预测系统正确性、安全性与部署问题
P0 严重问题修复: - 修复 form_slice/stats_slice 主客身份反转(历史比赛视角错误) - 修复 understat.py httpx 未导入导致的 NameError - 修复 LLM 解析失败时静默产生假成功预测(0-0 平局+置信度0.5) 预测路径修复: - multi-agent 路径增加 backtest cutoff 透传,回测防泄漏生效 - H2H 切片汇总统计改为从当前主队视角计数 - 预测唯一约束增加 mode+run_type 维度,防止回测覆盖实盘预测 伤停管线修复: - IntegrityError 后不再整批回滚丢数据(改用逐条 flush) - return_date 正确解析并写入 - retrieved_at 比较统一用 date() 避免当天数据不可见 - 唯一索引改为 partial unique index(排除 NULL 重复) - HTTP 缓存 TTL 从 7 天改为 6 小时 安全与连接管理: - /api/v1/predict 增加内存滑动窗口限流(10次/分钟/IP) - 预测路由改用短 session 模式,LLM 调用期间不持有 DB 连接 Docker 部署修复: - 修复 .dockerignore 排除 *.md 导致 COPY README.md 失败 - 容器内 DATABASE_URL 使用 postgres 服务名(非 localhost) - 启动时自动执行 alembic upgrade head - 前端改用多阶段构建(Dockerfile.frontend) 新增测试(5个文件,24+用例): - test_p0_home_away.py: 主客身份反转回归测试 - test_p0_parse_failure.py: LLM 解析失败回归测试 - test_multi_agent_cutoff.py: multi-agent cutoff 透传测试 - test_h2h_perspective.py: H2H 视角测试 - test_injuries_pipeline.py: 伤停管线 5 项修复测试 - test_predict_protection.py: 限流+短 session 测试 - test_prediction_unique_constraint.py: 唯一约束测试 迁移: - 0012_injuries_partial_unique_and_return_date.py - 0013_predictions_unique_constraint_mode_run_type.py
This commit is contained in:
@@ -144,10 +144,14 @@ async def run_specialists(
|
||||
header: MatchHeader,
|
||||
*,
|
||||
version: str = "v1",
|
||||
before=None,
|
||||
) -> list[AgentReport]:
|
||||
"""并行执行 5 个专家 agent。fail-open: 单个失败不影响其他。"""
|
||||
"""并行执行 5 个专家 agent。fail-open: 单个失败不影响其他。
|
||||
|
||||
before: 数据截止时间(回测防泄漏)。None 表示不限制。
|
||||
"""
|
||||
tasks = [
|
||||
_run_one(spec, header, await _agent_provider(spec.name, tier="specialist"), version=version)
|
||||
_run_one(spec, header, await _agent_provider(spec.name, tier="specialist"), version=version, before=before)
|
||||
for spec in SPECIALIST_SPECS
|
||||
]
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
@@ -161,10 +165,10 @@ async def run_specialists(
|
||||
return reports
|
||||
|
||||
|
||||
async def _run_one(spec, header, provider, *, version) -> AgentReport:
|
||||
async def _run_one(spec, header, provider, *, version, before=None) -> AgentReport:
|
||||
from src.llm.agents.base import run_agent
|
||||
|
||||
return await run_agent(spec, header, provider, before=header.match_dt, version=version)
|
||||
return await run_agent(spec, header, provider, before=before, version=version)
|
||||
|
||||
|
||||
def _reports_to_json(reports: list[AgentReport]) -> str:
|
||||
@@ -210,18 +214,34 @@ async def predict_match_multi(
|
||||
*,
|
||||
provider: LLMProvider | None = None,
|
||||
version: str = "v1",
|
||||
backtest: bool = False,
|
||||
cutoff_at=None,
|
||||
) -> MultiPredictResult:
|
||||
"""多 agent 端到端预测: 切片 → 并行专家 → 终裁 → 存库。"""
|
||||
"""多 agent 端到端预测: 切片 → 并行专家 → 终裁 → 存库。
|
||||
|
||||
backtest: 回测模式。True 时 cutoff 自动设为 match_dt - 1 天。
|
||||
cutoff_at: 显式截止时间(优先于 backtest 自动计算)。
|
||||
"""
|
||||
start = time.perf_counter()
|
||||
|
||||
# 1. 比赛头(各 agent 共享;不存在则 404)
|
||||
header = await load_match_header(match_id)
|
||||
match_kickoff_at = header.match_dt
|
||||
prediction_cutoff_at = header.match_dt # 默认:比赛时间作为数据截止
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
# 2. 并行专家(各自独立配置)
|
||||
reports = await run_specialists(header, version=version)
|
||||
# 计算真正的数据截止时间(回测防泄漏)
|
||||
# 优先级: 显式 cutoff_at > backtest 自动计算 > 默认(比赛时间)
|
||||
if cutoff_at is not None:
|
||||
cutoff = cutoff_at
|
||||
elif backtest and header.match_dt:
|
||||
from datetime import timedelta
|
||||
cutoff = header.match_dt - timedelta(days=1)
|
||||
else:
|
||||
cutoff = header.match_dt
|
||||
prediction_cutoff_at = cutoff
|
||||
|
||||
# 2. 并行专家(各自独立配置,使用统一 cutoff)
|
||||
reports = await run_specialists(header, version=version, before=cutoff)
|
||||
|
||||
# 3. 终裁
|
||||
aggregator_provider = await _agent_provider("aggregator", tier="aggregator")
|
||||
@@ -257,6 +277,7 @@ async def predict_match_multi(
|
||||
provider_name=settings.LLM_PROVIDER,
|
||||
model=aggregator_provider.model,
|
||||
mode="multi",
|
||||
run_type="backtest" if backtest else "live",
|
||||
values={
|
||||
"prompt_version": f"multi_{version}",
|
||||
"prompt_tokens": sum(r.prompt_tokens or 0 for r in reports) + agg_prompt_tokens,
|
||||
|
||||
Reference in New Issue
Block a user