# Profeto — 先知 > **给 LLM 提供结构化数据,让 LLM 预测足球比分。** ## 架构 ``` ┌─────────────────────────────────────────────────────┐ │ 前端 (React + Vite + Tailwind) │ │ http://localhost:5173 │ └──────────────────────┬──────────────────────────────┘ │ REST API ┌──────────────────────▼──────────────────────────────┐ │ FastAPI │ │ ├── /api/v1/matches 比赛查询 │ │ ├── /api/v1/predict LLM 预测 (单/多 Agent) │ │ ├── /api/v1/ingest/* 数据采集 │ │ ├── /api/v1/eval/* 评估回填 │ │ └── /api/v1/backtest 回测 │ └──────────┬─────────────────────────────┬────────────┘ │ │ ┌──────────▼──────────┐ ┌─────────────▼────────────┐ │ PostgreSQL │ │ LLM (OpenAI-compatible) │ │ 6 张表 │ │ OpenAI / Deepseek / │ │ leagues/teams/ │ │ Ollama / 任意网关 │ │ matches/match_ │ └──────────────────────────┘ │ stats/predictions/ │ │ injuries │ └─────────────────────┘ ▲ │ 采集 ┌──────────┴─────────────────────────────────────────┐ │ 数据源 (DataSource 协议 + 注册表) │ │ ├── bzzoiro 比分 / 统计 / xG │ │ ├── understat xG 回填 │ │ └── injuries 伤停数据 (api-football) │ └────────────────────────────────────────────────────┘ ``` ### 分层架构 ``` API Route → Application Service → Repository → UnitOfWork → DB ``` - **UnitOfWork**: 统一事务边界,业务层不再自行 commit - **Repository**: 封装数据访问,提供类型化查询接口 - **DataSource**: 采集外部数据,通过注册表动态分发 ### 多 Agent 预测 默认模式 (`mode=multi`) 采用 **5 专家 + 终裁** 架构: ``` 比赛数据 → 切片 ─┬─→ A 近期状态专家 ─┐ ├─→ B 攻防数据专家 ─┤ ├─→ C 主客因素专家 ─┼─→ 终裁专家 ─→ 最终预测 ├─→ D 阵容完整专家 ─┤ └─→ E 历史交锋专家 ─┘ ``` - 各专家**只看到自己维度的数据切片**,避免信息过载 - **fail-open**: 单个专家失败不影响整体 - **no_data 门控**: 无数据维度跳过 LLM 调用,省 token 防幻觉 - 终裁根据各报告的 `subjective_confidence` / `data_sufficiency` 输出 `agent_weights` ### 数据正确性保障 - **Cutoff 机制**: 回测时只使用 `cutoff_at` 之前已采集的数据 - **Injury 防泄漏**: 伤停查询强制 `retrieved_at <= cutoff` - **LLM 输出校验**: Pydantic 严格校验 + 语义一致性检查 - **数据库约束**: CHECK 约束作为最后一道防线 ## 快速开始 ### 前置条件 - Python >= 3.11 - Docker (运行 PostgreSQL) - LLM API Key (OpenAI / Deepseek / Ollama 等) ### 方式一:Docker Compose 部署(推荐) ```bash # 1. 克隆仓库 git clone https://git.bilidili.cn/shangfangjian/Profeto.git cd Profeto # 2. 配置环境变量 cp .env.example .env # 编辑 .env,填入 LLM_API_KEY 和 BZZOIRO_KEY # 3. 启动全部服务(自动构建 + 执行迁移) docker compose up -d --build # 4. 验证 curl http://localhost:8000/health ``` 启动后访问: - API 文档: http://localhost:8000/docs - 前端界面: http://localhost:3000 > **说明**: `api` 容器启动时自动执行 `alembic upgrade head`,无需手动运行迁移。 ### 方式二:本地开发部署 ```bash # 1. 克隆 + 安装 git clone https://git.bilidili.cn/shangfangjian/Profeto.git cd Profeto pip install -e ".[dev]" # 2. 配置环境变量 cp .env.example .env # 编辑 .env,填入 LLM_API_KEY 和 BZZOIRO_KEY # 3. 启动 PostgreSQL docker compose up -d postgres # 4. 执行迁移 alembic upgrade head # 5. 启动后端 (终端 1) uvicorn src.api.app:app --reload # 6. 启动前端 (终端 2) cd frontend && npm install && npm run dev ``` 后端运行在 `http://localhost:8000`,前端在 `http://localhost:5173`。 ## 安全与限流 - `/api/v1/predict`: 内存滑动窗口限流(10 次/分钟/IP),多 worker 时每进程独立计数 - 登录防爆破: 进程内内存计数,同上 - 公网部署建议 Nginx 层限流 + `TRUST_PROXY_HEADERS=True` - 生产环境必须配置 `ADMIN_PASSWORD` 或 `ADMIN_API_KEY`(否则管理接口 503) 详见 [docs/06-deployment.md](docs/06-deployment.md#安全与限流)。 ## API 概览 | 方法 | 路径 | 说明 | |---|---|---| | GET | `/api/v1/matches` | 比赛查询(筛选/分页) | | GET | `/api/v1/leagues` | 联赛列表 | | POST | `/api/v1/predict` | LLM 预测 (`mode=single`/`multi`) | | GET | `/api/v1/predictions` | 预测历史 | | POST | `/api/v1/ingest/bzzoiro` | 采集比分/统计 | | POST | `/api/v1/ingest/understat` | 回填 xG | | POST | `/api/v1/ingest/injuries` | 采集伤停 | | POST | `/api/v1/eval/settle` | 回填实际结果 | | GET | `/api/v1/eval/summary` | 准确率汇总 | | POST | `/api/v1/backtest` | 历史回测 | | GET | `/health` | 存活检查 | | GET | `/health/ready` | 就绪检查(含 DB) | ## 项目结构 ``` Profeto/ ├── src/ │ ├── api/ # FastAPI 路由层 │ │ ├── app.py # 应用工厂 + lifespan │ │ ├── schemas.py # Pydantic 请求/响应模型 │ │ └── routes/ │ │ ├── matches.py # 比赛查询 │ │ ├── predict.py # 预测入口 │ │ ├── ingest.py # 数据采集 │ │ ├── eval.py # 评估回填 │ │ └── backtest.py # 回测 │ ├── core/ # 基础设施 │ │ ├── config.py # pydantic-settings 配置 │ │ ├── http_client.py # 共享 httpx 客户端 │ │ └── retry.py # 重试工具(指数退避) │ ├── data/ # 数据层 │ │ ├── sources.py # DataSource 协议 + 注册表 │ │ ├── normalize.py # 数据规范化契约 │ │ ├── bzzoiro.py # bzzoiro 数据源 │ │ ├── understat.py # understat xG 数据源 │ │ ├── injuries.py # 伤停数据 │ │ ├── config.py # 联赛映射常量 │ │ └── team_names.py # 队名归一化 │ ├── db/ # 数据库 │ │ ├── base.py # SQLAlchemy async engine │ │ ├── models.py # ORM 模型 (6 表) │ │ ├── unit_of_work.py # UnitOfWork 事务封装 │ │ └── repositories.py # Repository 数据访问 │ └── llm/ # LLM 预测核心 │ ├── predict.py # 预测服务 (缓存 + 单/多模式) │ ├── context_builder.py # 数据切片 + 上下文拼接 │ ├── eval.py # 评估统计 │ ├── backtest.py # 回测框架 │ ├── provider.py # 多提供商 LLM 抽象 │ ├── validation.py # LLM 输出校验 │ ├── agents/ │ │ ├── base.py # Agent 基础设施 + 解析 │ │ └── orchestrator.py # 多 Agent 编排 │ └── prompts/ # Prompt 模板 ├── alembic/ # 数据库迁移 ├── frontend/ # React 前端 ├── docs/ # 详细文档 ├── tests/ # 单元测试 ├── docker-compose.yml ├── Dockerfile └── pyproject.toml ``` ## 核心模块 | 模块 | 作用 | |---|---| | `context_builder.py` | **最重要**: 数据切片 + 拼接 LLM 看到的上下文 | | `prompts/` | Prompt 模板 (迭代最频繁) | | `provider.py` | OpenAI-compatible 多提供商抽象 | | `sources.py` | 数据源协议 + 注册表 | | `unit_of_work.py` | 统一事务边界 | | `repositories.py` | 数据访问封装 | | `validation.py` | LLM 输出严格校验 | | `backtest.py` | 回测框架(防未来数据泄漏) | ## 配置 通过 `.env` 或环境变量配置: | 变量 | 说明 | 默认值 | |---|---|---| | `DATABASE_URL` | PostgreSQL 连接 | `postgresql+asyncpg://football:football@localhost:5432/football` | | `LLM_PROVIDER` | 提供商标识 | `openai` | | `LLM_API_KEY` | API 密钥 | *(必填)* | | `LLM_BASE_URL` | API 地址 | `https://api.openai.com/v1` | | `LLM_MODEL` | 模型名 | `gpt-4o` | | `LLM_SPECIALIST_MODEL` | 专家模型 (空=回落 LLM_MODEL) | | | `LLM_AGGREGATOR_MODEL` | 终裁模型 (空=回落 LLM_MODEL) | | | `BZZOIRO_KEY` | bzzoiro API Key | *(必填)* | | `API_FOOTBALL_KEY` | api-football Key (伤停) | | | `CORS_ORIGINS` | 允许的跨域来源 | `http://localhost:5173` | ## 测试 ```bash pytest ``` ## 数据库迁移 ```bash alembic upgrade head ``` ## License 个人研究项目,预测结果不构成投注建议。