From 60e4b8982218e376ff377598f6eec00f2dc1ec60 Mon Sep 17 00:00:00 2001 From: shangfangjian Date: Tue, 15 Sep 2026 02:27:48 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E7=BB=9F=E4=B8=80=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E4=B8=8E=E4=BB=A3=E7=A0=81=E4=B8=80=E8=87=B4=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复所有 confidence → subjective_confidence 残留(03-api, 04-agents, 05-data, 07-development) - 修复 5 张表 → 6 张表残留(06-deployment) - 同步 API schema 示例与实际模型一致 - 更新 predictions 表结构文档(新增 status/cutoff/input_hash 字段) code: 修复 API 异常处理(eval/predict)和 context_builder stats 时间过滤 --- docs/03-api.md | 8 ++++---- docs/04-agents.md | 8 ++++---- docs/05-data.md | 7 ++++++- docs/06-deployment.md | 2 +- docs/07-development.md | 2 +- src/api/routes/eval.py | 10 +++++++++- src/api/routes/predict.py | 13 +++++++++++-- src/llm/context_builder.py | 2 +- 8 files changed, 37 insertions(+), 15 deletions(-) diff --git a/docs/03-api.md b/docs/03-api.md index 8d7e429..4ab9422 100644 --- a/docs/03-api.md +++ b/docs/03-api.md @@ -87,17 +87,17 @@ Base URL: `http://localhost:8000` · 交互式文档: `/docs`(Swagger)与 `/redo "pred_home_goals": 2.1, "pred_away_goals": 1.0, "pred_1x2": "1", - "confidence": 0.68, + "subjective_confidence": 0.68, "reasoning": "综合 xg 报告的进球期望 2.1-1.0 与 form 报告的三连胜势头……", "agent_outputs": [ {"agent": "h2h", "status": "ok", "data_sufficiency": "medium", "analysis": "近 5 次交锋主队 3 胜……", "home_edge": 0.4, - "confidence": 0.7, "key_evidence": ["近5次交锋主队3胜", "主场交锋3连胜"], + "subjective_confidence": 0.7, "key_evidence": ["近5次交锋主队3胜", "主场交锋3连胜"], "exp_home_goals": null, "exp_away_goals": null, "probable_score": null, "model": "gpt-4o-mini", "latency_ms": 2100, "prompt_tokens": 380, "completion_tokens": 120}, {"agent": "injuries", "status": "no_data", "data_sufficiency": "none", - "analysis": "该维度无数据,跳过分析。", "home_edge": null, "confidence": null, + "analysis": "该维度无数据,跳过分析。", "home_edge": null, "subjective_confidence": null, "key_evidence": [], "exp_home_goals": null, "exp_away_goals": null, "probable_score": null, "model": "", "latency_ms": null, "prompt_tokens": null, "completion_tokens": null} @@ -172,7 +172,7 @@ Base URL: `http://localhost:8000` · 交互式文档: `/docs`(Swagger)与 `/redo ```json {"summary": [ {"provider": "openai", "model": "gpt-4o", "total": 12, - "accuracy_1x2": 58.3, "avg_score_rmse": 1.21, "avg_confidence": 0.65} + "accuracy_1x2": 58.3, "avg_score_rmse": 1.21, "avg_subjective_confidence": 0.65} ]} ``` diff --git a/docs/04-agents.md b/docs/04-agents.md index 7cb47f2..e9e4942 100644 --- a/docs/04-agents.md +++ b/docs/04-agents.md @@ -65,7 +65,7 @@ POST /predict {match_id, mode: "multi"} "data_sufficiency": "high", "analysis": "近 5 次交锋主队 3 胜 1 平 1 负,主场交锋 3 连胜……", "home_edge": 0.4, - "confidence": 0.7, + "subjective_confidence": 0.7, "key_evidence": ["近5次交锋主队3胜", "主场交锋3连胜"] } ``` @@ -75,7 +75,7 @@ POST /predict {match_id, mode: "multi"} | `status` | `ok` / `no_data` / `error` / `parse_error` | | `data_sufficiency` | `high` / `medium` / `low` / `none` | | `home_edge` | -1.0 ~ 1.0,正数=利主队,负数=利客队 | -| `confidence` | 0.0 ~ 1.0,该专家对自己分析的信心 | +| `subjective_confidence` | 0.0 ~ 1.0,该专家对自己分析的主观信心(非概率) | | `key_evidence` | 关键证据列表(最多 5 条) | ### 终裁 Agent 输出 @@ -87,13 +87,13 @@ POST /predict {match_id, mode: "multi"} "pred_home_goals": 2.1, "pred_away_goals": 1.0, "1x2": "1", - "confidence": 0.68, + "subjective_confidence": 0.68, "reasoning": "综合 stats 报告的攻防强度与 form 报告的三连胜势头……", "agent_weights": {"form": 0.9, "stats": 0.8, "home_away": 0.7, "injuries": 0.0, "h2h": 0.8} } ``` -`agent_weights` 体现终裁对各专家报告的采信度(0–1),可用于后续分析"哪个维度对预测贡献大"。 +`agent_weights` 体现终裁对各专家报告的采信度(0–1),可用于后续分析"哪个维度对预测贡献大"。注意:这是 LLM 主观权重,非统计权重。 ## 模型分档配置 diff --git a/docs/05-data.md b/docs/05-data.md index a89ae72..96bff48 100644 --- a/docs/05-data.md +++ b/docs/05-data.md @@ -124,7 +124,12 @@ CREATE TABLE predictions ( latency_ms INT, pred_home_goals FLOAT, pred_away_goals FLOAT, pred_1x2 VARCHAR(3), -- '1' / 'X' / '2' - confidence FLOAT, + subjective_confidence FLOAT, -- LLM 主观置信度(非概率) + status VARCHAR(20) NOT NULL DEFAULT 'success', -- 'success' / 'failed' / 'degraded' + match_kickoff_at TIMESTAMPTZ, -- 比赛时间 + prediction_created_at TIMESTAMPTZ, -- 预测创建时间 + prediction_cutoff_at TIMESTAMPTZ, -- 数据截止时间 + input_hash VARCHAR(64), -- 输入快照 hash reasoning TEXT, raw_response JSONB, -- LLM 完整原始响应 agent_outputs JSONB, -- multi 模式: 5 份专家报告 diff --git a/docs/06-deployment.md b/docs/06-deployment.md index 5bc5cd8..b09188b 100644 --- a/docs/06-deployment.md +++ b/docs/06-deployment.md @@ -128,7 +128,7 @@ alembic revision -m "描述" ``` 已有迁移: -- `0001_initial`: 初始 5 张表(leagues/teams/matches/match_stats/predictions) +- `0001_initial`: 初始 5 张表,0003 增加 injuries,0004 增加约束,0005 增加时间语义 - `0002_agent_outputs`: predictions 加 `mode` + `agent_outputs` ## 备份与恢复 diff --git a/docs/07-development.md b/docs/07-development.md index 94c53e5..93994ef 100644 --- a/docs/07-development.md +++ b/docs/07-development.md @@ -118,7 +118,7 @@ class MockProvider: return LLMResponse( content="{}", parsed={"data_sufficiency": "high", "analysis": "ok", - "home_edge": 0.5, "confidence": 0.8, + "home_edge": 0.5, "subjective_confidence": 0.8, "key_evidence": ["证据"]}, prompt_tokens=10, completion_tokens=5, latency_ms=100, ) diff --git a/src/api/routes/eval.py b/src/api/routes/eval.py index a204428..2db2a35 100644 --- a/src/api/routes/eval.py +++ b/src/api/routes/eval.py @@ -1,12 +1,16 @@ """评估路由。""" from __future__ import annotations +import logging + from fastapi import APIRouter, Depends, HTTPException from src.api.schemas import EvalSummaryOut, SettleRequest from src.db.base import AsyncSession, get_db, get_db_read from src.llm.eval import get_eval_summary, settle_prediction +logger = logging.getLogger(__name__) + router = APIRouter(prefix="/api/v1", tags=["eval"]) @@ -17,7 +21,11 @@ async def settle(req: SettleRequest, db: AsyncSession = Depends(get_db)): pred = await settle_prediction(req.prediction_id, req.home_goals, req.away_goals) return {"id": pred.id, "settled": pred.settled} except ValueError as e: - raise HTTPException(404, str(e)) + logger.warning("settle failed: %s", e) + raise HTTPException(404, "预测记录不存在") + except Exception as e: + logger.exception("settle error") + raise HTTPException(500, "回填失败,请查看服务器日志") @router.get("/eval/summary", response_model=EvalSummaryOut) diff --git a/src/api/routes/predict.py b/src/api/routes/predict.py index 91e07cc..5d4ac04 100644 --- a/src/api/routes/predict.py +++ b/src/api/routes/predict.py @@ -1,6 +1,8 @@ """预测路由。""" from __future__ import annotations +import logging + from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy import select from sqlalchemy.orm import selectinload @@ -10,6 +12,8 @@ from src.db.base import AsyncSession, get_db, get_db_read from src.db.models import Prediction from src.llm.predict import predict_match, PredictResult +logger = logging.getLogger(__name__) + router = APIRouter(prefix="/api/v1", tags=["predict"]) @@ -24,9 +28,14 @@ async def predict(req: PredictRequest, db: AsyncSession = Depends(get_db)): mode=req.mode, ) except ValueError as e: - raise HTTPException(404, str(e)) + logger.warning("predict validation error: %s", e) + raise HTTPException(404, "比赛不存在") except RuntimeError as e: - raise HTTPException(502, str(e)) + logger.error("predict LLM error: %s", e) + raise HTTPException(502, "LLM 预测失败,请查看服务器日志") + except Exception as e: + logger.exception("predict unexpected error") + raise HTTPException(500, "预测失败,请查看服务器日志") # single / multi 两种结果统一映射 return PredictOut( diff --git a/src/llm/context_builder.py b/src/llm/context_builder.py index 6c77786..c40934f 100644 --- a/src/llm/context_builder.py +++ b/src/llm/context_builder.py @@ -142,7 +142,7 @@ async def form_slice(header: MatchHeader, *, limit: int = 5, before=None) -> str else: losses += 1 score = f"{fm.home_goals}-{fm.away_goals}" if fm.home_goals is not None else "vs" xg = "" - if fm.stats and fm.stats.home_xg is not None: + if fm.stats and _is_stats_available(fm.stats, before) and fm.stats.home_xg is not None: own = fm.stats.home_xg if side == "home" else fm.stats.away_xg xg = f" (xG {own:.1f})" opp = fm.away_team.name if side == "home" else fm.home_team.name