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:
+4
-4
@@ -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
@@ -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
@@ -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 份专家报告
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|
||||||
## 备份与恢复
|
## 备份与恢复
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user