docs: 统一文档与代码一致性

- 修复所有 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 时间过滤
This commit is contained in:
shangfangjian
2026-09-15 02:27:48 +08:00
parent 0980c2242a
commit 60e4b89822
8 changed files with 37 additions and 15 deletions
+4 -4
View File
@@ -87,17 +87,17 @@ Base URL: `http://localhost:8000` · 交互式文档: `/docs`(Swagger)与 `/redo
"pred_home_goals": 2.1, "pred_home_goals": 2.1,
"pred_away_goals": 1.0, "pred_away_goals": 1.0,
"pred_1x2": "1", "pred_1x2": "1",
"confidence": 0.68, "subjective_confidence": 0.68,
"reasoning": "综合 xg 报告的进球期望 2.1-1.0 与 form 报告的三连胜势头……", "reasoning": "综合 xg 报告的进球期望 2.1-1.0 与 form 报告的三连胜势头……",
"agent_outputs": [ "agent_outputs": [
{"agent": "h2h", "status": "ok", "data_sufficiency": "medium", {"agent": "h2h", "status": "ok", "data_sufficiency": "medium",
"analysis": "近 5 次交锋主队 3 胜……", "home_edge": 0.4, "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, "exp_home_goals": null, "exp_away_goals": null, "probable_score": null,
"model": "gpt-4o-mini", "latency_ms": 2100, "model": "gpt-4o-mini", "latency_ms": 2100,
"prompt_tokens": 380, "completion_tokens": 120}, "prompt_tokens": 380, "completion_tokens": 120},
{"agent": "injuries", "status": "no_data", "data_sufficiency": "none", {"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, "key_evidence": [], "exp_home_goals": null, "exp_away_goals": null,
"probable_score": null, "model": "", "latency_ms": null, "probable_score": null, "model": "", "latency_ms": null,
"prompt_tokens": null, "completion_tokens": null} "prompt_tokens": null, "completion_tokens": null}
@@ -172,7 +172,7 @@ Base URL: `http://localhost:8000` · 交互式文档: `/docs`(Swagger)与 `/redo
```json ```json
{"summary": [ {"summary": [
{"provider": "openai", "model": "gpt-4o", "total": 12, {"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}
]} ]}
``` ```
+4 -4
View File
@@ -65,7 +65,7 @@ POST /predict {match_id, mode: "multi"}
"data_sufficiency": "high", "data_sufficiency": "high",
"analysis": "近 5 次交锋主队 3 胜 1 平 1 负,主场交锋 3 连胜……", "analysis": "近 5 次交锋主队 3 胜 1 平 1 负,主场交锋 3 连胜……",
"home_edge": 0.4, "home_edge": 0.4,
"confidence": 0.7, "subjective_confidence": 0.7,
"key_evidence": ["近5次交锋主队3胜", "主场交锋3连胜"] "key_evidence": ["近5次交锋主队3胜", "主场交锋3连胜"]
} }
``` ```
@@ -75,7 +75,7 @@ POST /predict {match_id, mode: "multi"}
| `status` | `ok` / `no_data` / `error` / `parse_error` | | `status` | `ok` / `no_data` / `error` / `parse_error` |
| `data_sufficiency` | `high` / `medium` / `low` / `none` | | `data_sufficiency` | `high` / `medium` / `low` / `none` |
| `home_edge` | -1.0 ~ 1.0,正数=利主队,负数=利客队 | | `home_edge` | -1.0 ~ 1.0,正数=利主队,负数=利客队 |
| `confidence` | 0.0 ~ 1.0,该专家对自己分析的信心 | | `subjective_confidence` | 0.0 ~ 1.0,该专家对自己分析的主观信心(非概率) |
| `key_evidence` | 关键证据列表(最多 5 条) | | `key_evidence` | 关键证据列表(最多 5 条) |
### 终裁 Agent 输出 ### 终裁 Agent 输出
@@ -87,13 +87,13 @@ POST /predict {match_id, mode: "multi"}
"pred_home_goals": 2.1, "pred_home_goals": 2.1,
"pred_away_goals": 1.0, "pred_away_goals": 1.0,
"1x2": "1", "1x2": "1",
"confidence": 0.68, "subjective_confidence": 0.68,
"reasoning": "综合 stats 报告的攻防强度与 form 报告的三连胜势头……", "reasoning": "综合 stats 报告的攻防强度与 form 报告的三连胜势头……",
"agent_weights": {"form": 0.9, "stats": 0.8, "home_away": 0.7, "injuries": 0.0, "h2h": 0.8} "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 主观权重,非统计权重。
## 模型分档配置 ## 模型分档配置
+6 -1
View File
@@ -124,7 +124,12 @@ CREATE TABLE predictions (
latency_ms INT, latency_ms INT,
pred_home_goals FLOAT, pred_away_goals FLOAT, pred_home_goals FLOAT, pred_away_goals FLOAT,
pred_1x2 VARCHAR(3), -- '1' / 'X' / '2' 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, reasoning TEXT,
raw_response JSONB, -- LLM 完整原始响应 raw_response JSONB, -- LLM 完整原始响应
agent_outputs JSONB, -- multi 模式: 5 份专家报告 agent_outputs JSONB, -- multi 模式: 5 份专家报告
+1 -1
View File
@@ -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` - `0002_agent_outputs`: predictions 加 `mode` + `agent_outputs`
## 备份与恢复 ## 备份与恢复
+1 -1
View File
@@ -118,7 +118,7 @@ class MockProvider:
return LLMResponse( return LLMResponse(
content="{}", content="{}",
parsed={"data_sufficiency": "high", "analysis": "ok", parsed={"data_sufficiency": "high", "analysis": "ok",
"home_edge": 0.5, "confidence": 0.8, "home_edge": 0.5, "subjective_confidence": 0.8,
"key_evidence": ["证据"]}, "key_evidence": ["证据"]},
prompt_tokens=10, completion_tokens=5, latency_ms=100, prompt_tokens=10, completion_tokens=5, latency_ms=100,
) )
+9 -1
View File
@@ -1,12 +1,16 @@
"""评估路由。""" """评估路由。"""
from __future__ import annotations from __future__ import annotations
import logging
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from src.api.schemas import EvalSummaryOut, SettleRequest from src.api.schemas import EvalSummaryOut, SettleRequest
from src.db.base import AsyncSession, get_db, get_db_read from src.db.base import AsyncSession, get_db, get_db_read
from src.llm.eval import get_eval_summary, settle_prediction from src.llm.eval import get_eval_summary, settle_prediction
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/v1", tags=["eval"]) 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) pred = await settle_prediction(req.prediction_id, req.home_goals, req.away_goals)
return {"id": pred.id, "settled": pred.settled} return {"id": pred.id, "settled": pred.settled}
except ValueError as e: 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) @router.get("/eval/summary", response_model=EvalSummaryOut)
+11 -2
View File
@@ -1,6 +1,8 @@
"""预测路由。""" """预测路由。"""
from __future__ import annotations from __future__ import annotations
import logging
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import selectinload 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.db.models import Prediction
from src.llm.predict import predict_match, PredictResult from src.llm.predict import predict_match, PredictResult
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/v1", tags=["predict"]) router = APIRouter(prefix="/api/v1", tags=["predict"])
@@ -24,9 +28,14 @@ async def predict(req: PredictRequest, db: AsyncSession = Depends(get_db)):
mode=req.mode, mode=req.mode,
) )
except ValueError as e: except ValueError as e:
raise HTTPException(404, str(e)) logger.warning("predict validation error: %s", e)
raise HTTPException(404, "比赛不存在")
except RuntimeError as e: 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 两种结果统一映射 # single / multi 两种结果统一映射
return PredictOut( return PredictOut(
+1 -1
View File
@@ -142,7 +142,7 @@ async def form_slice(header: MatchHeader, *, limit: int = 5, before=None) -> str
else: losses += 1 else: losses += 1
score = f"{fm.home_goals}-{fm.away_goals}" if fm.home_goals is not None else "vs" score = f"{fm.home_goals}-{fm.away_goals}" if fm.home_goals is not None else "vs"
xg = "" 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 own = fm.stats.home_xg if side == "home" else fm.stats.away_xg
xg = f" (xG {own:.1f})" xg = f" (xG {own:.1f})"
opp = fm.away_team.name if side == "home" else fm.home_team.name opp = fm.away_team.name if side == "home" else fm.home_team.name