forked from niuniu/llm-intelligence
1502 lines
53 KiB
Markdown
1502 lines
53 KiB
Markdown
|
|
# LLM Intelligence Hub — 技术设计文档 v1.0
|
|||
|
|
|
|||
|
|
> 文档版本:v1.0
|
|||
|
|
> 日期:2026-05-04
|
|||
|
|
> 负责人:宰相(AI 辅助)
|
|||
|
|
> 状态:初稿
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、系统架构概览
|
|||
|
|
|
|||
|
|
### 1.1 整体架构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌──────────────────────────────────────────────────────────────────────┐
|
|||
|
|
│ LLM Intelligence Hub │
|
|||
|
|
├──────────────────────────────────────────────────────────────────────┤
|
|||
|
|
│ │
|
|||
|
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
|
|||
|
|
│ │ 报告 │ │ Web UI │ │ AI Agent / MCP Client │ │
|
|||
|
|
│ │ Phase 2才推送│ │ (Explorer+报告) │ │ (REST API / MCP) │ │
|
|||
|
|
│ └──────┬──────┘ └──────┬──────┘ └────────────┬────────────┘ │
|
|||
|
|
│ │ │ │ │
|
|||
|
|
│ ┌──────▼──────────────────▼────────────────────────▼────────────┐ │
|
|||
|
|
│ │ Service Layer (Python) │ │
|
|||
|
|
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌──────────┐ │ │
|
|||
|
|
│ │ │ Report │ │ API │ │ Scheduler │ │ Notifier │ │ │
|
|||
|
|
│ │ │ Generator │ │ Server │ │ (cron) │ │ (告警) │ │ │
|
|||
|
|
│ │ └────────────┘ └────────────┘ └────────────┘ └──────────┘ │ │
|
|||
|
|
│ └───────────────────────────┬────────────────────────────────────┘ │
|
|||
|
|
│ │ │
|
|||
|
|
│ ┌───────────────────────────▼────────────────────────────────────┐ │
|
|||
|
|
│ │ Data Access Layer (SQLAlchemy ORM) │ │
|
|||
|
|
│ └───────────────────────────┬────────────────────────────────────┘ │
|
|||
|
|
│ │ │
|
|||
|
|
│ ┌───────────────────────────▼────────────────────────────────────┐ │
|
|||
|
|
│ │ Storage Layer (PostgreSQL) │ │
|
|||
|
|
│ └────────────────────────────────────────────────────────────────┘ │
|
|||
|
|
│ │
|
|||
|
|
│ ┌────────────────────────────────────────────────────────────────┐ │
|
|||
|
|
│ │ Data Collection Layer │ │
|
|||
|
|
│ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ │
|
|||
|
|
│ │ │ OpenRouter │ │ Phase 2才扩充厂商/中转平台 │ │ 中转平台采集器 │ │ │
|
|||
|
|
│ │ │ Collector │ │ (10家厂商) │ │ (硅基流动等) │ │ │
|
|||
|
|
│ │ └─────────────┘ └──────────────┘ └──────────────────────┘ │ │
|
|||
|
|
│ └────────────────────────────────────────────────────────────────┘ │
|
|||
|
|
└──────────────────────────────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.2 各层职责
|
|||
|
|
|
|||
|
|
| 层级 | 职责 | 技术选型 |
|
|||
|
|
| **数据采集层** | 从 OpenRouter 抓取模型元数据、定价 | Python + requests |
|
|||
|
|
| **存储层** | 结构化数据持久化 + 数据库内任务队列 | PostgreSQL(与立交桥技术栈统一) |
|
|||
|
|
| **服务层** | 报告生成、API 服务、调度 | Python 3.11 + Flask(Phase 1 API)+ Jinja2(报告模板) |
|
|||
|
|
| **前端层** | 静态 Web 页面展示(Explorer / 报告) | 纯 HTML/CSS/JS + Bootstrap 5 + ECharts 5 |
|
|||
|
|
|
|||
|
|
### 1.3 技术架构决策(与立交桥技术栈统一)
|
|||
|
|
|
|||
|
|
**核心约束**:与立交桥技术栈保持一致,Phase 1 直接使用 PostgreSQL。
|
|||
|
|
|
|||
|
|
| 决策 | 选型 | 理由 |
|
|||
|
|
| 数据库 | **PostgreSQL** | 与立交桥统一;支持 JSONB/数组类型;数据库内队列替代第三方消息组件 |
|
|||
|
|
| API 框架 | **Flask** | 轻量,1 进程运行 |
|
|||
|
|
| 前端 | **纯静态 HTML** | 无需 Node.js 服务端渲染 |
|
|||
|
|
| 爬虫框架 | **requests + BeautifulSoup** | 成熟稳定 |
|
|||
|
|
| 调度 | **系统 cron + Python script** | 无需 Celery/RQ |
|
|||
|
|
| 日志/监控 | **文件系统日志** | loguru 写入文件 |
|
|||
|
|
| 告警 | **Phase 2** | 钉钉/飞书 Webhook 推送 |
|
|||
|
|
|
|||
|
|
**Phase 1 单机部署拓扑**:
|
|||
|
|
```
|
|||
|
|
Phase 1 单机部署
|
|||
|
|
├── PostgreSQL DB
|
|||
|
|
├── 采集脚本(cron 触发,直写 DB)
|
|||
|
|
├── 日报生成命令(Markdown 输出)
|
|||
|
|
└── 备份脚本(Phase 2 才推送至 OSS)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、技术选型详解
|
|||
|
|
|
|||
|
|
### 2.1 语言与运行时
|
|||
|
|
|
|||
|
|
| 组件 | 选型 | 版本 | 依据 |
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
| 主力语言 | **Python** | 3.11+ | 爬虫/数据处理生态最成熟;Flask/Jinja2 配套完善 |
|
|||
|
|
| 前端 | **Vanilla JS + HTML5** | — | 无需 Node.js 构建链,静态文件 CDN 托管,降低成本 |
|
|||
|
|
|
|||
|
|
### 2.2 框架与工具库
|
|||
|
|
|
|||
|
|
| 用途 | 库/工具 | 用途说明 |
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
| HTTP 请求 | `requests` | 数据采集主库,轻量稳定 |
|
|||
|
|
| HTML 解析 | `BeautifulSoup4` | 静态页面解析 |
|
|||
|
|
| 动态页面 | `playwright` | JavaScript 渲染页(如某些国内定价页) |
|
|||
|
|
| ORM | `SQLAlchemy` | 数据库抽象,直接对接 PostgreSQL |
|
|||
|
|
| API 框架 | `Flask` | 轻量 REST API,Gunicorn 部署 |
|
|||
|
|
| 模板引擎 | `Jinja2` | HTML 报告模板渲染 |
|
|||
|
|
| 图表 | `ECharts` | 前端可视化(价格趋势/排行榜) |
|
|||
|
|
| 调度 | `APScheduler` | Python 内置调度(辅助 cron) |
|
|||
|
|
| 日志 | `loguru` | 结构化日志,低配置 |
|
|||
|
|
| 日期处理 | `pandas` | 数据清洗、价格计算 |
|
|||
|
|
| 货币换算 | `forex-python` | USD/CNY/EUR 汇率获取 |
|
|||
|
|
|
|||
|
|
### 2.3 数据库
|
|||
|
|
|
|||
|
|
**Phase 1:PostgreSQL**
|
|||
|
|
- 与立交桥技术栈统一
|
|||
|
|
- 利用 PostgreSQL JSONB 存储灵活字段(如 capabilities 数组)
|
|||
|
|
- 使用 PostgreSQL job queue 表实现异步任务队列,无须第三方消息组件
|
|||
|
|
- Schema 设计直接以 PostgreSQL 语法编写
|
|||
|
|
|
|||
|
|
### 2.4 为什么不用这些
|
|||
|
|
|
|||
|
|
| 未选方案 | 原因 |
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
| SQLite | 不符合与立交桥技术栈统一的要求 |
|
|||
|
|
| FastAPI | Swagger UI 增加包体积,Flask 在 Phase 1 足够轻量 |
|
|||
|
|
| Scrapy | 重量级框架,Phase 1 采集规模不需要分布式 |
|
|||
|
|
| Celery + RabbitMQ | 增加运维复杂度,用 PostgreSQL job queue 替代 |
|
|||
|
|
| React/Vue | 需要 Node.js 构建链,增加部署复杂度 |
|
|||
|
|
| Deno/Bun | 生态不如 Python 成熟,数据处理库少 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、数据库设计(DDL)
|
|||
|
|
|
|||
|
|
> 以下 DDL 以 PostgreSQL 语法编写,与立交桥技术栈统一。
|
|||
|
|
|
|||
|
|
### 3.1 model_provider(模型商)
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE model_provider (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
name TEXT NOT NULL UNIQUE, -- "OpenAI", "百度", "DeepSeek"
|
|||
|
|
name_cn TEXT, -- 中文名:"百度智能云"
|
|||
|
|
country TEXT NOT NULL, -- "US" / "CN" / "EU"
|
|||
|
|
website TEXT, -- 官网 URL
|
|||
|
|
founded_year INTEGER, -- 成立年份
|
|||
|
|
description TEXT, -- 简介
|
|||
|
|
logo_url TEXT, -- 厂商 Logo
|
|||
|
|
status TEXT NOT NULL DEFAULT 'active', -- active / deprecated
|
|||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX idx_provider_country ON model_provider(country);
|
|||
|
|
CREATE INDEX idx_provider_status ON model_provider(status);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 model(模型)
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE model (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
provider_id INTEGER NOT NULL,
|
|||
|
|
name TEXT NOT NULL, -- "GPT-4o", "ERNIE-4.0"
|
|||
|
|
version TEXT, -- "2025-12", "V3.2"
|
|||
|
|
modality TEXT NOT NULL, -- text / vision / audio / video / code
|
|||
|
|
context_length INTEGER NOT NULL DEFAULT 0, -- 上下文窗口,0=未知
|
|||
|
|
capabilities TEXT, -- JSON数组: ["function_calling","vision"]
|
|||
|
|
release_date DATE, -- 发布日期
|
|||
|
|
status TEXT NOT NULL DEFAULT 'active', -- active / deprecated / discontinued
|
|||
|
|
parent_model_id INTEGER, -- 父模型ID(区分 Turbo/Lite 变体)
|
|||
|
|
elo_score REAL, -- ELO 分数(OpenRouter)
|
|||
|
|
benchmark_scores TEXT, -- JSON: {"mmlu": 88.5, "humaneval": 90.2}
|
|||
|
|
source_url TEXT, -- 来源 URL
|
|||
|
|
data_confidence TEXT DEFAULT 'official', -- official / inferred / unverified
|
|||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
FOREIGN KEY (provider_id) REFERENCES model_provider(id) ON DELETE CASCADE,
|
|||
|
|
UNIQUE(provider_id, name, version)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX idx_model_provider ON model(provider_id);
|
|||
|
|
CREATE INDEX idx_model_modality ON model(modality);
|
|||
|
|
CREATE INDEX idx_model_status ON model(status);
|
|||
|
|
CREATE INDEX idx_model_name ON model(name);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.3 operator(运营商/云平台)
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE operator (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
name TEXT NOT NULL UNIQUE, -- "AWS Bedrock", "硅基流动"
|
|||
|
|
name_cn TEXT, -- 中文名
|
|||
|
|
type TEXT NOT NULL, -- cloud / reseller / official
|
|||
|
|
country TEXT NOT NULL, -- 运营主体国籍
|
|||
|
|
website TEXT, -- 控制台地址
|
|||
|
|
api_endpoint TEXT, -- API 基础 URL
|
|||
|
|
auth_type TEXT NOT NULL, -- api_key / oauth / sts
|
|||
|
|
is_cn_accessible BOOLEAN DEFAULT 1, -- 国内是否可访问
|
|||
|
|
stability_grade TEXT DEFAULT 'B', -- A/B/C/D 稳定性评级
|
|||
|
|
status TEXT NOT NULL DEFAULT 'active', -- active / deprecated
|
|||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX idx_operator_type ON operator(type);
|
|||
|
|
CREATE INDEX idx_operator_country ON operator(country);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.4 region_pricing(区域定价)
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE region_pricing (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
operator_id INTEGER NOT NULL,
|
|||
|
|
model_id INTEGER NOT NULL,
|
|||
|
|
region TEXT NOT NULL DEFAULT 'GLOBAL', -- CN / US / EU / GLOBAL
|
|||
|
|
currency TEXT NOT NULL, -- CNY / USD / EUR
|
|||
|
|
input_price_per_mtok REAL NOT NULL, -- 元/百万Token
|
|||
|
|
output_price_per_mtok REAL NOT NULL,
|
|||
|
|
unit TEXT DEFAULT 'per_mtok', -- per_mtok / per_1k / per_token
|
|||
|
|
free_tier_id INTEGER, -- 关联 free_tier 表
|
|||
|
|
rate_limit TEXT, -- JSON: {"rpm": 60, "tpm": 100000}
|
|||
|
|
free_limitations TEXT, -- JSON数组: ["仅限国内IP","新用户专享"]
|
|||
|
|
last_updated DATE NOT NULL,
|
|||
|
|
source_url TEXT,
|
|||
|
|
data_confidence TEXT DEFAULT 'official', -- official / inferred / expired
|
|||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
FOREIGN KEY (operator_id) REFERENCES operator(id) ON DELETE CASCADE,
|
|||
|
|
FOREIGN KEY (model_id) REFERENCES model(id) ON DELETE CASCADE,
|
|||
|
|
UNIQUE(operator_id, model_id, region, currency)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX idx_pricing_operator ON region_pricing(operator_id);
|
|||
|
|
CREATE INDEX idx_pricing_model ON region_pricing(model_id);
|
|||
|
|
CREATE INDEX idx_pricing_region ON region_pricing(region);
|
|||
|
|
CREATE INDEX idx_pricing_currency ON region_pricing(currency);
|
|||
|
|
CREATE INDEX idx_pricing_input_cost ON region_pricing(input_price_per_mtok);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.5 pricing_history(价格历史)
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE pricing_history (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
region_pricing_id INTEGER NOT NULL,
|
|||
|
|
model_id INTEGER NOT NULL,
|
|||
|
|
operator_id INTEGER NOT NULL,
|
|||
|
|
region TEXT NOT NULL,
|
|||
|
|
currency TEXT NOT NULL,
|
|||
|
|
old_input_price REAL,
|
|||
|
|
new_input_price REAL NOT NULL,
|
|||
|
|
old_output_price REAL,
|
|||
|
|
new_output_price REAL NOT NULL,
|
|||
|
|
change_pct REAL, -- 变动百分比(自动计算)
|
|||
|
|
change_type TEXT NOT NULL, -- increase / decrease / new_model / discontinued
|
|||
|
|
recorded_at DATE NOT NULL, -- 记录日期
|
|||
|
|
source_url TEXT,
|
|||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
FOREIGN KEY (region_pricing_id) REFERENCES region_pricing(id) ON DELETE CASCADE,
|
|||
|
|
FOREIGN KEY (model_id) REFERENCES model(id) ON DELETE CASCADE,
|
|||
|
|
FOREIGN KEY (operator_id) REFERENCES operator(id) ON DELETE CASCADE
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX idx_history_model ON pricing_history(model_id);
|
|||
|
|
CREATE INDEX idx_history_operator ON pricing_history(operator_id);
|
|||
|
|
CREATE INDEX idx_history_recorded ON pricing_history(recorded_at);
|
|||
|
|
CREATE INDEX idx_history_change_type ON pricing_history(change_type);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.6 free_tier(免费政策)
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE free_tier (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
operator_id INTEGER NOT NULL,
|
|||
|
|
model_id INTEGER, -- NULL表示该平台全部免费额度
|
|||
|
|
free_model_name TEXT, -- 免费模型名称(展示用)
|
|||
|
|
quota_type TEXT NOT NULL, -- daily / monthly / one_time / unlimited
|
|||
|
|
quota_amount REAL, -- 配额数量
|
|||
|
|
quota_unit TEXT, -- requests / tokens / minutes
|
|||
|
|
tpm_limit INTEGER, -- tokens per minute 限制
|
|||
|
|
rpm_limit INTEGER, -- requests per minute 限制
|
|||
|
|
daily_req_limit INTEGER, -- 每日请求上限
|
|||
|
|
monthly_req_limit INTEGER, -- 每月请求上限
|
|||
|
|
token_limit_per_req INTEGER, -- 单次请求Token上限
|
|||
|
|
requires_credit_card BOOLEAN DEFAULT 0, -- 是否需要绑定信用卡
|
|||
|
|
requires_verification BOOLEAN DEFAULT 0, -- 是否需要实名认证
|
|||
|
|
region_restrictions TEXT, -- JSON: ["仅限部分地区"]
|
|||
|
|
applicable_scenarios TEXT, -- JSON: ["仅限新用户"]
|
|||
|
|
special_notes TEXT, -- 特殊说明
|
|||
|
|
effective_from DATE,
|
|||
|
|
effective_until DATE, -- NULL表示长期有效
|
|||
|
|
last_updated DATE,
|
|||
|
|
source_url TEXT,
|
|||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
FOREIGN KEY (operator_id) REFERENCES operator(id) ON DELETE CASCADE,
|
|||
|
|
FOREIGN KEY (model_id) REFERENCES model(id) ON DELETE SET NULL
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX idx_free_operator ON free_tier(operator_id);
|
|||
|
|
CREATE INDEX idx_free_model ON free_tier(model_id);
|
|||
|
|
CREATE INDEX idx_free_quota_type ON free_tier(quota_type);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.7 daily_report(每日报告)
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE daily_report (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
report_date DATE NOT NULL UNIQUE,
|
|||
|
|
new_models TEXT, -- JSON数组:新上线模型
|
|||
|
|
price_changes TEXT, -- JSON数组:价格变动
|
|||
|
|
free_changes TEXT, -- JSON数组:免费政策变更
|
|||
|
|
top_recommendations TEXT, -- JSON对象:场景推荐
|
|||
|
|
cost_alerts TEXT, -- JSON数组:成本告警
|
|||
|
|
html_content TEXT, -- 完整HTML报告内容
|
|||
|
|
summary_md TEXT, -- Markdown摘要
|
|||
|
|
status TEXT NOT NULL DEFAULT 'generated', -- generated / failed / partial
|
|||
|
|
generated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
error_message TEXT
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX idx_report_date ON daily_report(report_date);
|
|||
|
|
CREATE INDEX idx_report_status ON daily_report(status);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.8 user_subscription(用户订阅)
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE user_subscription (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
user_id TEXT NOT NULL, -- 统一用户ID
|
|||
|
|
email TEXT,
|
|||
|
|
phone TEXT,
|
|||
|
|
subscription_tier TEXT NOT NULL DEFAULT 'free', -- free / pro / team / enterprise
|
|||
|
|
subscription_start DATE,
|
|||
|
|
subscription_end DATE,
|
|||
|
|
notify_channels TEXT, -- JSON: ["feishu","email","dingtalk"]
|
|||
|
|
feishu_webhook TEXT,
|
|||
|
|
dingtalk_webhook TEXT,
|
|||
|
|
email_webhook TEXT,
|
|||
|
|
model_watchlist TEXT, -- JSON数组:关注模型
|
|||
|
|
operator_watchlist TEXT, -- JSON数组:关注平台
|
|||
|
|
price_alert_threshold REAL DEFAULT 10.0, -- 告警阈值(%)
|
|||
|
|
monthly_token_limit INTEGER, -- 月度Token限制
|
|||
|
|
monthly_token_used INTEGER DEFAULT 0,
|
|||
|
|
stripe_customer_id TEXT,
|
|||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
UNIQUE(email)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX idx_sub_user ON user_subscription(user_id);
|
|||
|
|
CREATE INDEX idx_sub_tier ON user_subscription(subscription_tier);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、API 设计
|
|||
|
|
|
|||
|
|
### 4.1 内部采集 API(Collector → Server)
|
|||
|
|
|
|||
|
|
#### POST /api/v1/collect/push
|
|||
|
|
采集器推送采集结果(Phase 2 分布式采集节点使用)
|
|||
|
|
|
|||
|
|
**Request:**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"batch": [
|
|||
|
|
{
|
|||
|
|
"provider_name": "OpenAI",
|
|||
|
|
"model_name": "GPT-4o",
|
|||
|
|
"version": "2025-01",
|
|||
|
|
"operator_name": "OpenRouter",
|
|||
|
|
"region": "GLOBAL",
|
|||
|
|
"currency": "USD",
|
|||
|
|
"input_price": 2.50,
|
|||
|
|
"output_price": 10.0,
|
|||
|
|
"context_length": 128000,
|
|||
|
|
"capabilities": ["vision", "function_calling", "json_mode"],
|
|||
|
|
"free_tier": null,
|
|||
|
|
"source_url": "https://openrouter.ai/api/v1/models"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"collected_at": "2026-05-04T08:00:00+08:00"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Response:**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"status": "ok",
|
|||
|
|
"inserted": 365,
|
|||
|
|
"updated": 12,
|
|||
|
|
"errors": 0
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4.2 对外 REST API
|
|||
|
|
|
|||
|
|
#### GET /api/v1/models
|
|||
|
|
查询模型列表
|
|||
|
|
|
|||
|
|
**Query Parameters:**
|
|||
|
|
| 参数 | 类型 | 默认值 | 说明 |
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
| `provider` | string | — | 模型商名称过滤 |
|
|||
|
|
| `modality` | string | — | text/vision/audio/video/code |
|
|||
|
|
| `min_context` | int | — | 最小上下文长度 |
|
|||
|
|
| `max_input_price` | float | — | 最大输入价格(/MTok) |
|
|||
|
|
| `has_free` | bool | false | 仅显示有免费额的模型 |
|
|||
|
|
| `search` | string | — | 关键词搜索(模型名/capabilities) |
|
|||
|
|
| `sort` | string | `input_price` | 排序字段 |
|
|||
|
|
| `order` | string | `asc` | asc/desc |
|
|||
|
|
| `page` | int | 1 | 页码 |
|
|||
|
|
| `page_size` | int | 20 | 每页数量(max 100) |
|
|||
|
|
|
|||
|
|
**Response:**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"total": 523,
|
|||
|
|
"page": 1,
|
|||
|
|
"page_size": 20,
|
|||
|
|
"models": [
|
|||
|
|
{
|
|||
|
|
"id": 42,
|
|||
|
|
"name": "DeepSeek V4-Flash",
|
|||
|
|
"provider": "DeepSeek",
|
|||
|
|
"provider_cn": "深度求索",
|
|||
|
|
"modality": "text",
|
|||
|
|
"context_length": 1048576,
|
|||
|
|
"capabilities": ["function_calling", "json_mode"],
|
|||
|
|
"status": "active",
|
|||
|
|
"lowest_price": {
|
|||
|
|
"operator": "硅基流动",
|
|||
|
|
"currency": "CNY",
|
|||
|
|
"input": 0.14,
|
|||
|
|
"output": 0.028,
|
|||
|
|
"region": "CN"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### GET /api/v1/models/{id}
|
|||
|
|
查询单个模型详情
|
|||
|
|
|
|||
|
|
**Response:**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"id": 42,
|
|||
|
|
"name": "DeepSeek V4-Flash",
|
|||
|
|
"provider": {
|
|||
|
|
"id": 5,
|
|||
|
|
"name": "DeepSeek",
|
|||
|
|
"country": "CN"
|
|||
|
|
},
|
|||
|
|
"version": "V4-Flash",
|
|||
|
|
"modality": "text",
|
|||
|
|
"context_length": 1048576,
|
|||
|
|
"capabilities": ["function_calling", "json_mode"],
|
|||
|
|
"release_date": "2026-04-15",
|
|||
|
|
"status": "active",
|
|||
|
|
"elo_score": 1382.5,
|
|||
|
|
"pricing": [
|
|||
|
|
{
|
|||
|
|
"operator": "硅基流动",
|
|||
|
|
"region": "CN",
|
|||
|
|
"currency": "CNY",
|
|||
|
|
"input": 0.14,
|
|||
|
|
"output": 0.028,
|
|||
|
|
"source_url": "https://siliconflow.cn"
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"operator": "OpenRouter",
|
|||
|
|
"region": "GLOBAL",
|
|||
|
|
"currency": "USD",
|
|||
|
|
"input": 0.02,
|
|||
|
|
"output": 0.004
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"free_tier": {
|
|||
|
|
"quota_type": "monthly",
|
|||
|
|
"quota_amount": 5000000,
|
|||
|
|
"quota_unit": "tokens",
|
|||
|
|
"requires_credit_card": false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### GET /api/v1/cost
|
|||
|
|
成本计算器
|
|||
|
|
|
|||
|
|
**Query Parameters:**
|
|||
|
|
| 参数 | 类型 | 必填 | 说明 |
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
| `input_tokens` | int | 是 | 输入 Token 数 |
|
|||
|
|
| `output_tokens` | int | 否 | 输出 Token 数(默认=input_tokens×0.3) |
|
|||
|
|
| `modality` | string | 否 | 模态过滤 |
|
|||
|
|
| `region` | string | 否 | 区域(CN/US/GLOBAL) |
|
|||
|
|
| `currency` | string | CNY | 显示货币 |
|
|||
|
|
| `top_n` | int | 10 | 返回前N个最低价 |
|
|||
|
|
|
|||
|
|
**Response:**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"input_tokens": 1000000,
|
|||
|
|
"output_tokens": 300000,
|
|||
|
|
"currency": "CNY",
|
|||
|
|
"results": [
|
|||
|
|
{
|
|||
|
|
"rank": 1,
|
|||
|
|
"model": "DeepSeek V4-Flash",
|
|||
|
|
"provider": "DeepSeek",
|
|||
|
|
"operator": "硅基流动",
|
|||
|
|
"input_cost": 0.14,
|
|||
|
|
"output_cost": 0.0084,
|
|||
|
|
"total_cost": 0.1484,
|
|||
|
|
"total_cost_usd": 0.020
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"rank": 2,
|
|||
|
|
"model": "Kimi K2.5",
|
|||
|
|
"provider": "Moonshot",
|
|||
|
|
"operator": "硅基流动",
|
|||
|
|
"input_cost": 0.23,
|
|||
|
|
"output_cost": 0.021,
|
|||
|
|
"total_cost": 0.251,
|
|||
|
|
"total_cost_usd": 0.034
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### GET /api/v1/recommend
|
|||
|
|
模型推荐
|
|||
|
|
|
|||
|
|
**Query Parameters:**
|
|||
|
|
| 参数 | 类型 | 必填 | 说明 |
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
| `use_case` | string | 是 | 场景:coding/writing/reasoning/free/vision |
|
|||
|
|
| `min_context` | int | — | 最小上下文需求 |
|
|||
|
|
| `budget` | float | — | 预算上限(/MTok input) |
|
|||
|
|
| `region` | string | CN | 区域偏好 |
|
|||
|
|
| `limit` | int | 5 | 返回数量 |
|
|||
|
|
|
|||
|
|
**Response:**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"use_case": "coding",
|
|||
|
|
"recommendations": [
|
|||
|
|
{
|
|||
|
|
"rank": 1,
|
|||
|
|
"model": "Kimi K2.6",
|
|||
|
|
"provider": "Moonshot",
|
|||
|
|
"reason": "SWE-Bench Pro 超越 GPT-5.4,编码能力最强",
|
|||
|
|
"input_price": 0.95,
|
|||
|
|
"currency": "CNY",
|
|||
|
|
"free_option": null
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"rank": 2,
|
|||
|
|
"model": "GLM-5.1",
|
|||
|
|
"provider": "智谱",
|
|||
|
|
"reason": "编码能力接近 Opus 4.6,性价比高",
|
|||
|
|
"input_price": 1.40,
|
|||
|
|
"currency": "CNY",
|
|||
|
|
"free_option": null
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### GET /api/v1/reports
|
|||
|
|
每日报告列表
|
|||
|
|
|
|||
|
|
**Query Parameters:**
|
|||
|
|
| 参数 | 类型 | 默认值 | 说明 |
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
| `from` | date | 30天前 | 开始日期 |
|
|||
|
|
| `to` | date | 今天 | 结束日期 |
|
|||
|
|
| `page` | int | 1 | 页码 |
|
|||
|
|
|
|||
|
|
**Response:**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"total": 30,
|
|||
|
|
"reports": [
|
|||
|
|
{
|
|||
|
|
"id": 30,
|
|||
|
|
"report_date": "2026-05-04",
|
|||
|
|
"status": "generated",
|
|||
|
|
"summary": "新上线3个模型,价格变动2项,免费政策更新1项",
|
|||
|
|
"generated_at": "2026-05-04T08:00:45+08:00"
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### GET /api/v1/reports/{date}
|
|||
|
|
获取指定日期报告内容
|
|||
|
|
|
|||
|
|
**Response:**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"id": 30,
|
|||
|
|
"report_date": "2026-05-04",
|
|||
|
|
"html_content": "<html>...</html>",
|
|||
|
|
"new_models": [
|
|||
|
|
{"name": "xAI Grok 4.1 Fast", "provider": "xAI", "input_price": 0.20, "currency": "USD"}
|
|||
|
|
],
|
|||
|
|
"price_changes": [
|
|||
|
|
{
|
|||
|
|
"model": "Claude Opus 4.6",
|
|||
|
|
"operator": "Anthropic",
|
|||
|
|
"old_price": 15.0,
|
|||
|
|
"new_price": 5.0,
|
|||
|
|
"change_pct": -66.7,
|
|||
|
|
"currency": "USD"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"free_changes": [
|
|||
|
|
{
|
|||
|
|
"model": "Gemini 2.5 Pro",
|
|||
|
|
"operator": "Google",
|
|||
|
|
"change": "免费层下线,需付费使用"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"top_recommendations": {
|
|||
|
|
"coding": {"model": "Kimi K2.6", "provider": "Moonshot"},
|
|||
|
|
"writing": {"model": "GLM-5.1", "provider": "智谱"},
|
|||
|
|
"free": {"model": "DeepSeek R1", "provider": "DeepSeek"},
|
|||
|
|
"cheapest": {"model": "Step 3.5 Flash", "provider": "字节"}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### GET /api/v1/health
|
|||
|
|
健康检查
|
|||
|
|
|
|||
|
|
**Response:**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"status": "ok",
|
|||
|
|
"version": "1.0.0",
|
|||
|
|
"db_record_count": {
|
|||
|
|
"models": 523,
|
|||
|
|
"providers": 22,
|
|||
|
|
"operators": 31,
|
|||
|
|
"pricing_records": 1847
|
|||
|
|
},
|
|||
|
|
"last_collect_time": "2026-05-04T08:00:12+08:00",
|
|||
|
|
"last_report_time": "2026-05-04T08:00:45+08:00"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五、数据采集 Pipeline
|
|||
|
|
|
|||
|
|
### 5.1 OpenRouter 采集流程
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────────────┐
|
|||
|
|
│ 每日 08:00 │
|
|||
|
|
│ cron 触发 │
|
|||
|
|
└────────┬────────┘
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
┌─────────────────────────────────────────────────┐
|
|||
|
|
│ GET https://openrouter.ai/api/v1/models │
|
|||
|
|
│ Headers: Authorization: Bearer <OPENROUTER_KEY>│
|
|||
|
|
└────────┬────────────────────────────────────────┘
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
┌─────────────────────────────────────────────────┐
|
|||
|
|
│ 解析响应 JSON │
|
|||
|
|
│ 字段映射: │
|
|||
|
|
│ id → model.name (如 "anthropic/claude-3.5-sonnet")│
|
|||
|
|
│ name → display_name │
|
|||
|
|
│ pricing.input * 1e6 → input_price_per_mtok │
|
|||
|
|
│ pricing.output * 1e6 → output_price_per_mtok│
|
|||
|
|
│ context_length → context_length │
|
|||
|
|
│ supported_parameters → capabilities │
|
|||
|
|
│ opensource → modality (text/vision/etc) │
|
|||
|
|
└────────┬────────────────────────────────────────┘
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
┌─────────────────────────────────────────────────┐
|
|||
|
|
│ 识别 provider_name (从 id 前缀提取) │
|
|||
|
|
│ 示例: "anthropic/claude-3.5-sonnet" → │
|
|||
|
|
│ provider="Anthropic", model="Claude 3.5 Sonnet"│
|
|||
|
|
└────────┬────────────────────────────────────────┘
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
┌─────────────────────────────────────────────────┐
|
|||
|
|
│ Upsert: │
|
|||
|
|
│ INSERT OR REPLACE INTO model_provider (...) │
|
|||
|
|
│ INSERT OR REPLACE INTO model (...) │
|
|||
|
|
│ INSERT OR REPLACE INTO region_pricing (...) │
|
|||
|
|
└────────┬────────────────────────────────────────┘
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
┌─────────────────────────────────────────────────┐
|
|||
|
|
│ 检测价格变动: │
|
|||
|
|
│ SELECT old_price FROM pricing_history │
|
|||
|
|
│ WHERE model_id = x AND operator_id = y │
|
|||
|
|
│ IF new_price != old_price: │
|
|||
|
|
│ INSERT INTO pricing_history (...) │
|
|||
|
|
│ IF abs(change_pct) > 5%: 标记为高亮变动 │
|
|||
|
|
└─────────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 国内厂商采集流程 — Phase 2
|
|||
|
|
|
|||
|
|
每个国内厂商独立采集器(`collectors/` 目录),统一接口输出:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# collectors/base.py
|
|||
|
|
class BaseCollector:
|
|||
|
|
def collect(self) -> List[ModelRecord]:
|
|||
|
|
"""返回标准化采集记录"""
|
|||
|
|
raise NotImplementedError
|
|||
|
|
|
|||
|
|
def get_schedule(self) -> str:
|
|||
|
|
"""返回 cron 表达式,如 "0 8 * * *" """
|
|||
|
|
return "0 8 * * *"
|
|||
|
|
|
|||
|
|
def get_timeout(self) -> int:
|
|||
|
|
"""超时秒数"""
|
|||
|
|
return 60
|
|||
|
|
|
|||
|
|
def get_retry(self) -> int:
|
|||
|
|
"""重试次数"""
|
|||
|
|
return 3
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 采集器清单(Phase 1)
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
#### 统一字段映射
|
|||
|
|
|
|||
|
|
每个采集器输出标准化 `CollectedRecord`:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
@dataclass
|
|||
|
|
class CollectedRecord:
|
|||
|
|
provider_name: str # "DeepSeek"
|
|||
|
|
provider_name_cn: str # "深度求索"
|
|||
|
|
model_name: str # "V4-Flash"
|
|||
|
|
model_version: str # "V4-Flash"
|
|||
|
|
modality: str # "text"
|
|||
|
|
context_length: int # 1048576
|
|||
|
|
capabilities: List[str] # ["function_calling", "vision"]
|
|||
|
|
operator_name: str # "硅基流动"
|
|||
|
|
operator_type: str # "reseller"
|
|||
|
|
region: str # "CN" / "US" / "GLOBAL"
|
|||
|
|
currency: str # "CNY" / "USD"
|
|||
|
|
input_price_per_mtok: float
|
|||
|
|
output_price_per_mtok: float
|
|||
|
|
free_tier: Optional[FreeTierRecord] = None
|
|||
|
|
source_url: str
|
|||
|
|
collected_at: datetime
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
统一 `ProviderMapper` 将各厂商原始名称映射到标准名称:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
PROVIDER_NAME_MAP = {
|
|||
|
|
# DeepSeek
|
|||
|
|
"deepseek-ai/DeepSeek-V3": {"provider": "DeepSeek", "model": "V3.2", "version": "2026-03"},
|
|||
|
|
"deepseek-ai/DeepSeek-V4": {"provider": "DeepSeek", "model": "V4", "version": "2026-04"},
|
|||
|
|
"deepseek-ai/DeepSeek-R1": {"provider": "DeepSeek", "model": "R1", "version": "2026-01"},
|
|||
|
|
# 阿里
|
|||
|
|
"qwen/Qwen3-VL-32B": {"provider": "阿里云", "model": "Qwen3-VL-32B", "version": "2026-03"},
|
|||
|
|
"qwen/Qwen3-VL-8B": {"provider": "阿里云", "model": "Qwen3-VL-8B", "version": "2026-03"},
|
|||
|
|
# Moonshot
|
|||
|
|
"moonshotai/Kimi-K2.6": {"provider": "Moonshot", "model": "K2.6", "version": "2026-04"},
|
|||
|
|
"moonshotai/Kimi-K2.5": {"provider": "Moonshot", "model": "K2.5", "version": "2026-03"},
|
|||
|
|
# 智谱
|
|||
|
|
"zhipuai/GLM-5.1": {"provider": "智谱", "model": "GLM-5.1", "version": "2026-03"},
|
|||
|
|
"zhipuai/GLM-4.7": {"provider": "智谱", "model": "GLM-4.7", "version": "2025-12"},
|
|||
|
|
# ... 其他厂商
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.3 每日调度设计
|
|||
|
|
|
|||
|
|
**调度策略**:系统 cron 统一调度,无外部消息队列依赖。
|
|||
|
|
|
|||
|
|
```crontab
|
|||
|
|
# /etc/crontab
|
|||
|
|
|
|||
|
|
# 每日 08:00 触发全量采集 + 报告生成
|
|||
|
|
0 8 * * * root /opt/llm-hub/scripts/run_daily.sh >> /var/log/llm-hub/daily.log 2>&1
|
|||
|
|
|
|||
|
|
# 每日 09:00 触发数据备份
|
|||
|
|
0 9 * * * root /opt/llm-hub/scripts/backup.sh >> /var/log/llm-hub/backup.log 2>&1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
#!/bin/bash
|
|||
|
|
# run_daily.sh
|
|||
|
|
|
|||
|
|
set -e
|
|||
|
|
|
|||
|
|
LOG_FILE="/var/log/llm-hub/daily.log"
|
|||
|
|
echo "[$(date)] 开始每日采集任务" >> $LOG_FILE
|
|||
|
|
|
|||
|
|
# 1. 采集 OpenRouter(海外模型,优先级最高)
|
|||
|
|
cd /opt/llm-hub
|
|||
|
|
python -m collectors.openrouter >> $LOG_FILE 2>&1
|
|||
|
|
|
|||
|
|
# 2. Phase 2 才并行采集国内厂商(DeepSeek/阿里/Kimi/智谱等)
|
|||
|
|
|
|||
|
|
echo "[$(date)] 采集完成,开始生成报告" >> $LOG_FILE
|
|||
|
|
|
|||
|
|
# 3. 生成每日报告
|
|||
|
|
python -m services.report_generator >> $LOG_FILE 2>&1
|
|||
|
|
|
|||
|
|
# 4. Phase 2 才检测价格变动并告警
|
|||
|
|
echo "[$(date)] 每日任务完成" >> $LOG_FILE
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.4 失败重试 + 告警机制
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# services/retry_handler.py
|
|||
|
|
|
|||
|
|
import time
|
|||
|
|
import loguru
|
|||
|
|
from functools import wraps
|
|||
|
|
from typing import Callable, Any
|
|||
|
|
|
|||
|
|
logger = loguru.logger
|
|||
|
|
|
|||
|
|
def retry(max_attempts: int = 3, delay: int = 5, backoff: float = 2.0):
|
|||
|
|
"""指数退避重试装饰器"""
|
|||
|
|
def decorator(func: Callable) -> Callable:
|
|||
|
|
@wraps(func)
|
|||
|
|
def wrapper(*args, **kwargs) -> Any:
|
|||
|
|
attempt = 0
|
|||
|
|
while attempt < max_attempts:
|
|||
|
|
try:
|
|||
|
|
return func(*args, **kwargs)
|
|||
|
|
except Exception as e:
|
|||
|
|
attempt += 1
|
|||
|
|
wait = delay * (backoff ** (attempt - 1))
|
|||
|
|
logger.warning(
|
|||
|
|
f"Attempt {attempt}/{max_attempts} failed for {func.__name__}: {e}. "
|
|||
|
|
f"Retrying in {wait}s..."
|
|||
|
|
)
|
|||
|
|
if attempt >= max_attempts:
|
|||
|
|
logger.error(f"All {max_attempts} attempts failed for {func.__name__}")
|
|||
|
|
raise
|
|||
|
|
time.sleep(wait)
|
|||
|
|
return wrapper
|
|||
|
|
return decorator
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 采集器调用示例
|
|||
|
|
@retry(max_attempts=3, delay=10, backoff=2.0)
|
|||
|
|
def collect_with_retry(collector_name: str):
|
|||
|
|
collector = get_collector(collector_name)
|
|||
|
|
records = collector.collect()
|
|||
|
|
save_to_db(records)
|
|||
|
|
logger.info(f"{collector_name}: collected {len(records)} records")
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 告警触发逻辑
|
|||
|
|
def check_and_alert_price_change(model_id: int, operator_id: int, new_price: float):
|
|||
|
|
old_price = get_last_price(model_id, operator_id)
|
|||
|
|
if old_price is None:
|
|||
|
|
return # 首 次录入,不告警
|
|||
|
|
|
|||
|
|
change_pct = (new_price - old_price) / old_price * 100
|
|||
|
|
|
|||
|
|
if abs(change_pct) > 10:
|
|||
|
|
alert_msg = (
|
|||
|
|
f"⚠️ 价格变动告警\n"
|
|||
|
|
f"模型: {get_model_name(model_id)}\n"
|
|||
|
|
f"平台: {get_operator_name(operator_id)}\n"
|
|||
|
|
f"原价: {old_price}\n"
|
|||
|
|
f"新价: {new_price}\n"
|
|||
|
|
f"变动: {change_pct:+.1f}%"
|
|||
|
|
)
|
|||
|
|
send_dingtalk_alert(alert_msg)
|
|||
|
|
send_feishu_alert(alert_msg)
|
|||
|
|
logger.warning(alert_msg)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**告警规则:**
|
|||
|
|
|
|||
|
|
| 条件 | 动作 |
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
| 单个采集器失败 | 记录日志,保留旧数据,发送低优先级告警 |
|
|||
|
|
| 连续 3 天同一采集器失败 | 发送高优先级告警(钉钉/飞书) |
|
|||
|
|
| 价格变动 > 10% | 立即触发告警 |
|
|||
|
|
| 价格变动 > 20% | 立即触发告警 + 暂停该平台数据(人工确认) |
|
|||
|
|
| 报告生成失败 | 发送告警,保留前一天报告 |
|
|||
|
|
| 数据库写入失败 | 立即告警,回滚事务 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 六、前端架构
|
|||
|
|
|
|||
|
|
### 6.1 技术栈
|
|||
|
|
|
|||
|
|
| 组件 | 选型 | 理由 |
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
| **页面框架** | 纯 HTML5 + Bootstrap 5 | 无需 Node.js 构建,CDN 托管,零运维 |
|
|||
|
|
| **图表库** | ECharts 5 | 免费,功能全面,支持中文,体积小(~1MB) |
|
|||
|
|
| **图标** | Bootstrap Icons | 与 Bootstrap 5 原生集成 |
|
|||
|
|
| **搜索** | 前端 Fuse.js | 轻量模糊搜索,< 100KB,无需服务端 |
|
|||
|
|
| **布局** | Bootstrap 5 响应式网格 | 移动端适配 |
|
|||
|
|
| **构建** | 无(纯静态文件) | Phase 2才引入 Nginx,静态 CDN 托管 |
|
|||
|
|
|
|||
|
|
### 6.2 页面清单
|
|||
|
|
|
|||
|
|
| 页面 | 路径 | 功能说明 |
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
| **首页 / 报告列表** | `/` | 展示最新每日报告入口,显示近期报告摘要 |
|
|||
|
|
| **报告详情** | `/reports/{date}.html` | 单日报告完整内容(新模型/价格变动/推荐) |
|
|||
|
|
| **模型浏览器** | `/explorer.html` | 组合筛选 + 卡片/表格视图 + 搜索 |
|
|||
|
|
| **模型详情** | `/model/{id}.html` | 模型完整信息 + 全平台定价对比 |
|
|||
|
|
Phase 2|Phase 2 Phase 2~Phase 2~Phase 2*Phase 2*Phase 2成Phase 2本Phase 2计Phase 2算Phase 2器Phase 2*Phase 2*Phase 2~Phase 2~Phase 2 Phase 2|Phase 2 Phase 2`Phase 2/Phase 2cPhase 2aPhase 2lPhase 2cPhase 2uPhase 2lPhase 2aPhase 2tPhase 2oPhase 2rPhase 2.Phase 2hPhase 2tPhase 2mPhase 2lPhase 2`Phase 2 Phase 2|Phase 2 Phase 2TPhase 2oPhase 2kPhase 2ePhase 2nPhase 2 Phase 2用Phase 2量Phase 2 Phase 2→Phase 2 Phase 2多Phase 2平Phase 2台Phase 2成Phase 2本Phase 2对Phase 2比Phase 2排Phase 2行Phase 2 Phase 2|Phase 2
|
|||
|
|
Phase 2Phase 2|Phase 2 Phase 2~Phase 2~Phase 2*Phase 2*Phase 2趋Phase 2势Phase 2图Phase 2*Phase 2*Phase 2~Phase 2~Phase 2 Phase 2|Phase 2 Phase 2`Phase 2/Phase 2tPhase 2rPhase 2ePhase 2nPhase 2dPhase 2sPhase 2.Phase 2hPhase 2tPhase 2mPhase 2lPhase 2`Phase 2 Phase 2|Phase 2 Phase 2价Phase 2格Phase 2/Phase 2模Phase 2型Phase 2能Phase 2力Phase 2历Phase 2史Phase 2趋Phase 2势Phase 2(Phase 2EPhase 2CPhase 2hPhase 2aPhase 2rPhase 2tPhase 2sPhase 2)Phase 2 Phase 2|Phase 2
|
|||
|
|
Phase 2| **关于我们** | `/about.html` | 项目介绍、数据来源说明 |
|
|||
|
|
|
|||
|
|
### 6.3 与后端的数据交互
|
|||
|
|
|
|||
|
|
**模式**:纯前端 SPA(Single Page Application),通过 Fetch API 调用后端 REST API。
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
前端静态文件(Phase 2才 Nginx 托管)
|
|||
|
|
│
|
|||
|
|
├── GET /api/v1/models → Flask API 返回 JSON
|
|||
|
|
├── GET /api/v1/models/{id} → 模型详情 JSON
|
|||
|
|
├── GET /api/v1/cost → 成本计算 JSON
|
|||
|
|
├── GET /api/v1/recommend → 推荐结果 JSON
|
|||
|
|
└── GET /api/v1/reports/{date} → 报告 JSON
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**前端数据层(dataService.js)**:
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 统一 API 调用封装
|
|||
|
|
const API_BASE = '/api/v1';
|
|||
|
|
|
|||
|
|
async function apiGet(endpoint, params = {}) {
|
|||
|
|
const url = new URL(`${API_BASE}${endpoint}`, window.location.origin);
|
|||
|
|
Object.entries(params).forEach(([k, v]) => v != null && url.searchParams.set(k, v));
|
|||
|
|
const resp = await fetch(url);
|
|||
|
|
if (!resp.ok) throw new Error(`API error: ${resp.status}`);
|
|||
|
|
return resp.json();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 主要接口封装
|
|||
|
|
const api = {
|
|||
|
|
models: {
|
|||
|
|
list: (params) => apiGet('/models', params),
|
|||
|
|
detail: (id) => apiGet(`/models/${id}`)
|
|||
|
|
},
|
|||
|
|
cost: {
|
|||
|
|
calculate: (params) => apiGet('/cost', params)
|
|||
|
|
},
|
|||
|
|
recommend: (params) => apiGet('/recommend', params),
|
|||
|
|
reports: {
|
|||
|
|
list: (params) => apiGet('/reports', params),
|
|||
|
|
get: (date) => apiGet(`/reports/${date}`)
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.4 模型浏览器页面结构
|
|||
|
|
|
|||
|
|
```html
|
|||
|
|
<!-- explorer.html -->
|
|||
|
|
|
|||
|
|
<!-- 筛选栏 -->
|
|||
|
|
<div class="row mb-3">
|
|||
|
|
<div class="col-md-2">
|
|||
|
|
<select id="filter-provider" class="form-select">
|
|||
|
|
<option value="">全部厂商</option>
|
|||
|
|
<option value="DeepSeek">DeepSeek</option>
|
|||
|
|
<option value="阿里云">阿里云</option>
|
|||
|
|
<!-- ... -->
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
<div class="col-md-2">
|
|||
|
|
<select id="filter-modality" class="form-select">
|
|||
|
|
<option value="">全部模态</option>
|
|||
|
|
<option value="text">文字</option>
|
|||
|
|
<option value="vision">视觉</option>
|
|||
|
|
<option value="code">代码</option>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
<div class="col-md-2">
|
|||
|
|
<input type="number" id="filter-max-price" class="form-control"
|
|||
|
|
placeholder="最大输入价(¥/MT)">
|
|||
|
|
</div>
|
|||
|
|
<div class="col-md-3">
|
|||
|
|
<input type="text" id="search-keyword" class="form-control"
|
|||
|
|
placeholder="搜索模型名称...">
|
|||
|
|
</div>
|
|||
|
|
<div class="col-md-3">
|
|||
|
|
<div class="btn-group" role="group">
|
|||
|
|
<button class="btn btn-outline-primary active" data-view="card">卡片</button>
|
|||
|
|
<button class="btn btn-outline-primary" data-view="table">表格</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 结果区域 -->
|
|||
|
|
<div id="results" class="row">
|
|||
|
|
<!-- 动态渲染卡片或表格 -->
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 分页 -->
|
|||
|
|
<nav><ul class="pagination" id="pagination"></ul></nav>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 七、部署架构
|
|||
|
|
|
|||
|
|
### 7.1 Docker 配置
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
# docker-compose.yml
|
|||
|
|
version: '3.8'
|
|||
|
|
|
|||
|
|
services:
|
|||
|
|
# --- Phase 1 核心服务 ---
|
|||
|
|
|
|||
|
|
collector:
|
|||
|
|
build:
|
|||
|
|
context: .
|
|||
|
|
dockerfile: Dockerfile.collector
|
|||
|
|
volumes:
|
|||
|
|
- ./data:/opt/llm-hub/data # PostgreSQL 数据持久化
|
|||
|
|
- ./logs:/var/log/llm-hub # 日志持久化
|
|||
|
|
- ./reports:/opt/llm-hub/reports # 报告输出
|
|||
|
|
env_file:
|
|||
|
|
- .env
|
|||
|
|
restart: unless-stopped
|
|||
|
|
networks:
|
|||
|
|
- llm-hub-net
|
|||
|
|
|
|||
|
|
api:
|
|||
|
|
build:
|
|||
|
|
context: .
|
|||
|
|
dockerfile: Dockerfile.api
|
|||
|
|
ports:
|
|||
|
|
- "5000:5000"
|
|||
|
|
volumes:
|
|||
|
|
- ./data:/opt/llm-hub/data
|
|||
|
|
- ./reports:/opt/llm-hub/reports
|
|||
|
|
env_file:
|
|||
|
|
- .env
|
|||
|
|
restart: unless-stopped
|
|||
|
|
depends_on:
|
|||
|
|
- collector
|
|||
|
|
networks:
|
|||
|
|
- llm-hub-net
|
|||
|
|
|
|||
|
|
# --- Phase 2 才引入 Nginx(内网访问 + 静态文件服务)---
|
|||
|
|
|
|||
|
|
networks:
|
|||
|
|
llm-hub-net:
|
|||
|
|
driver: bridge
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7.2 内网部署要求
|
|||
|
|
|
|||
|
|
**部署前提**:
|
|||
|
|
- 一台可访问外网的服务器(境外更好,便于访问 OpenRouter)
|
|||
|
|
- 域名(可选,用于 HTTPS + 钉钉/飞书 Webhook 回调)
|
|||
|
|
- Docker + Docker Compose
|
|||
|
|
|
|||
|
|
**网络访问需求**:
|
|||
|
|
|
|||
|
|
| 目的地 | 用途 | 协议 |
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
| `openrouter.ai` | 采集海外模型数据 | HTTPS |
|
|||
|
|
| `api.deepseek.com` | 采集 DeepSeek 定价 | HTTPS |
|
|||
|
|
| `dashscope.aliyuncs.com` | 采集阿里云定价 | HTTPS |
|
|||
|
|
| `api.moonshot.cn` | 采集 Kimi 定价 | HTTPS |
|
|||
|
|
| `open.bigmodel.cn` | 采集智谱定价 | HTTPS |
|
|||
|
|
| `api.siliconflow.cn` | 采集硅基流动定价 | HTTPS |
|
|||
|
|
| `oapi.dingtalk.com` | Phase 2 钉钉告警 | HTTPS |
|
|||
|
|
| `open.feishu.cn` | Phase 2 飞书告警 | HTTPS |
|
|||
|
|
| **无需访问** | 国内云厂商定价页(如阿里云控制台) | — |
|
|||
|
|
|
|||
|
|
### 7.3 环境变量清单
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# .env 文件(Phase 1 最小配置)
|
|||
|
|
|
|||
|
|
# === 数据库 ===
|
|||
|
|
DATABASE_URL=postgresql://user:pass@localhost:5432/llmhub
|
|||
|
|
|
|||
|
|
# === OpenRouter ===
|
|||
|
|
OPENROUTER_API_KEY=sk-or-v1-xxxxx
|
|||
|
|
|
|||
|
|
# === 国内厂商 API Keys ===
|
|||
|
|
DEEPSEEK_API_KEY=sk-xxxxx
|
|||
|
|
DASHSCOPE_API_KEY=sk-xxxxx
|
|||
|
|
MOONSHOT_API_KEY=sk-xxxxx
|
|||
|
|
ZHIPU_API_KEY=xxxxx
|
|||
|
|
MINIMAX_API_KEY=xxxxx
|
|||
|
|
VOLCENGINE_API_KEY=xxxxx
|
|||
|
|
VOLCENGINE_SECRET_KEY=xxxxx
|
|||
|
|
TENCENT_SECRET_ID=xxxxx
|
|||
|
|
TENCENT_SECRET_KEY=xxxxx
|
|||
|
|
BAIDU_QIANFAN_API_KEY=xxxxx
|
|||
|
|
BAIDU_QIANFAN_SECRET_KEY=xxxxx
|
|||
|
|
SILICONFLOW_API_KEY=sk-xxxxx
|
|||
|
|
|
|||
|
|
# === 告警配置(Phase 2 才启用)===
|
|||
|
|
# DINGTALK_WEBHOOK=https://oapi.dingtalk.com/robot/send?access_token=xxxxx
|
|||
|
|
# FEISHU_WEBHOOK=https://open.feishu.cn/open-apis/bot/v2/hook/xxxxx
|
|||
|
|
ALERT_THRESHOLD_PCT=10
|
|||
|
|
ALERT_THRESHOLD_CRITICAL_PCT=20
|
|||
|
|
|
|||
|
|
# === 邮件配置(可选)===
|
|||
|
|
SMTP_HOST=smtp.example.com
|
|||
|
|
SMTP_PORT=587
|
|||
|
|
SMTP_USER=noreply@example.com
|
|||
|
|
SMTP_PASS=xxxxx
|
|||
|
|
|
|||
|
|
# === 备份配置 ===
|
|||
|
|
BACKUP_OSS_ENDPOINT=https://oss-cn-hangzhou.aliyuncs.com
|
|||
|
|
BACKUP_OSS_BUCKET=llm-hub-backup
|
|||
|
|
BACKUP_OSS_KEY=xxxxx
|
|||
|
|
BACKUP_OSS_SECRET=xxxxx
|
|||
|
|
|
|||
|
|
# === 系统 ===
|
|||
|
|
LOG_LEVEL=INFO
|
|||
|
|
TZ=Asia/Shanghai
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7.4 Nginx 配置
|
|||
|
|
|
|||
|
|
```nginx
|
|||
|
|
# nginx.conf
|
|||
|
|
worker_processes auto;
|
|||
|
|
error_log /var/log/nginx/error.log warn;
|
|||
|
|
pid /var/run/nginx.pid;
|
|||
|
|
|
|||
|
|
events {
|
|||
|
|
worker_connections 1024;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
http {
|
|||
|
|
include /etc/nginx/mime.types;
|
|||
|
|
default_type application/octet-stream;
|
|||
|
|
|
|||
|
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
|||
|
|
'$status $body_bytes_sent "$http_referer" '
|
|||
|
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
|||
|
|
|
|||
|
|
access_log /var/log/nginx/access.log main;
|
|||
|
|
|
|||
|
|
sendfile on;
|
|||
|
|
keepalive_timeout 65;
|
|||
|
|
|
|||
|
|
# --- 静态文件服务(前端)---
|
|||
|
|
server {
|
|||
|
|
listen 80;
|
|||
|
|
server_name _;
|
|||
|
|
|
|||
|
|
root /usr/share/nginx/html;
|
|||
|
|
index index.html;
|
|||
|
|
|
|||
|
|
# 前端静态页面
|
|||
|
|
location / {
|
|||
|
|
try_files $uri $uri/ /index.html;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 每日报告 HTML
|
|||
|
|
location /reports/ {
|
|||
|
|
alias /usr/share/nginx/html/reports/;
|
|||
|
|
expires 7d;
|
|||
|
|
add_header Cache-Control "public, immutable";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# --- API 反向代理 ---
|
|||
|
|
location /api/ {
|
|||
|
|
proxy_pass http://api:5000/api/;
|
|||
|
|
proxy_set_header Host $host;
|
|||
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|||
|
|
proxy_read_timeout 60s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 健康检查(无需认证)
|
|||
|
|
location /health {
|
|||
|
|
proxy_pass http://api:5000/api/v1/health;
|
|||
|
|
proxy_set_header Host $host;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 八、Phase 1 技术路线(3个月)
|
|||
|
|
|
|||
|
|
### 8.1 Sprint 划分
|
|||
|
|
|
|||
|
|
| Sprint | 周期 | 目标 | 交付物 |
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
| **Sprint 0** | Week 1 | 技术方案确认 + 环境搭建 | `TECHNICAL_DESIGN.md` 终稿;开发环境就绪 |
|
|||
|
|
| **Sprint 1** | Week 2-3 | OpenRouter 采集器 + 数据库 Schema | 371 海外模型入库;数据库 DDL 可执行 |
|
|||
|
|
| **Sprint 2** | Week 4-5 | PostgreSQL migration + 日报生成器 | 三张表落地;Markdown 报告输出到 reports/daily/ |
|
|||
|
|
| **Sprint 2** | Week 4-5 | 每日报告生成 + Explorer 页面 | Markdown 报告生成;Explorer 页面上线;Markdown 报告可输出到 reports/daily/ |
|
|||
|
|
| **Sprint 4** | Week 8-9 | 模型浏览器 + 搜索筛选 | `/explorer.html` 上线;卡片/表格视图 |
|
|||
|
|
| **Sprint 5** | Week 10-11 | Explorer 页面完善 + Dashboard 占位图 | 表格/筛选排序;价格趋势占位图 |
|
|||
|
|
| **Sprint 6** | Week 12 | 收尾 + 部署 + 验证脚本 | Docker Compose 部署文档;验证脚本;备份策略 |
|
|||
|
|
|
|||
|
|
### 8.2 Sprint 1 详细任务(OpenRouter 采集器)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Sprint 1 目标:从 OpenRouter API 采集 371 模型,建立基础数据库
|
|||
|
|
|
|||
|
|
任务分解:
|
|||
|
|
├── T1.1 数据库 Schema 部署
|
|||
|
|
│ ├── [ ] 创建所有 DDL 表(model_provider/model/operator/region_pricing/...)
|
|||
|
|
│ ├── [ ] 编写 PostgreSQL Schema 部署脚本(deploy.sh)
|
|||
|
|
│ └── [ ] 验证:查询所有表,返回空表,数量正确
|
|||
|
|
│
|
|||
|
|
├── T1.2 OpenRouter 采集器实现
|
|||
|
|
│ ├── [ ] 实现 collectors/openrouter.py
|
|||
|
|
│ │ ├── 调用 GET https://openrouter.ai/api/v1/models
|
|||
|
|
│ │ ├── 解析 id/name/pricing/context_length/capabilities
|
|||
|
|
│ │ ├── 从 id 前缀提取 provider(anthropic/claude-3.5-sonnet → Anthropic)
|
|||
|
|
│ │ ├── 处理免费模型(id 包含 :free 后缀)
|
|||
|
|
│ │ └── 错误处理(401/429/500)
|
|||
|
|
│ ├── [ ] 实现 base collector 抽象类
|
|||
|
|
│ ├── [ ] 实现数据清洗逻辑(去除异常价格、统一单位)
|
|||
|
|
│ └── [ ] 验证:371 模型全部入库,无重复,数据正确
|
|||
|
|
│
|
|||
|
|
├── T1.3 数据映射 + Provider 标准化
|
|||
|
|
│ ├── [ ] 建立 PROVIDER_NAME_MAP(OpenRouter id → 标准厂商名)
|
|||
|
|
│ ├── [ ] 验证:所有 provider 名称统一(无别名)
|
|||
|
|
│ └── [ ] 补充 provider logo_url / description
|
|||
|
|
│
|
|||
|
|
├── T1.4 初始数据导入
|
|||
|
|
│ ├── [ ] 运行 OpenRouter 采集器,导入 371 模型
|
|||
|
|
│ ├── [ ] 质量检查:随机抽 10 条数据,验证价格/上下文长度
|
|||
|
|
│ └── [ ] 导出数据字典文档
|
|||
|
|
│
|
|||
|
|
└── T1.5 采集脚本 + cron 配置
|
|||
|
|
├── [ ] 编写 scripts/run_openrouter_collect.sh
|
|||
|
|
├── [ ] 配置 crontab(08:00 每日执行)
|
|||
|
|
├── [ ] 编写失败重试逻辑
|
|||
|
|
└── [ ] 验证:手动运行脚本成功,数据入库
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8.3 Phase 1 关键技术决策记录
|
|||
|
|
|
|||
|
|
| 决策 | 选型 | 记录时间 | 理由 |
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
| Phase 1 数据库用 PostgreSQL | ✅ 确认 | Sprint 0 | 与立交桥技术栈统一;支持 JSONB/数组类型;数据库内队列 |
|
|||
|
|
| 数据采集用 Python requests | ✅ 确认 | Sprint 0 | 生态成熟,内存占用低 |
|
|||
|
|
| 报告生成用 Jinja2 模板 | ✅ 确认 | Sprint 0 | 模板复用,减少前端维护成本 |
|
|||
|
|
| 告警用 Webhook 直推 | ✅ 确认 | Sprint 0 | 无需消息队列,降低复杂度 |
|
|||
|
|
| OpenRouter ELO 数据暂不采集 | ⚠️ 延期 | Sprint 1 | ELO API 可能收费,Phase 1 跳过 |
|
|||
|
|
| 国内厂商优先级:DeepSeek > 阿里 > Kimi > 智谱 > MiniMax > 火山 > 腾讯 > 百度 | ✅ 确认 | Sprint 2 | 按市场热度排序 |
|
|||
|
|
|
|||
|
|
### 8.4 质量检查清单(Phase 1 上线前)
|
|||
|
|
|
|||
|
|
#### 功能验证
|
|||
|
|
- [ ] OpenRouter 371 模型全部入库,覆盖率 100%
|
|||
|
|
# (Phase 2 才采集国内厂商)
|
|||
|
|
- [ ] 每日 08:00 cron 触发采集,报告自动生成
|
|||
|
|
- [ ] 报告内容包含:新模型、价格变动(>5% 高亮)、场景推荐
|
|||
|
|
- [ ] `/explorer.html` 搜索响应 < 500ms
|
|||
|
|
# (Phase 2 才实现告警推送)
|
|||
|
|
|
|||
|
|
#### 数据质量验证
|
|||
|
|
- [ ] 每条数据有 `source_url` 来源标注
|
|||
|
|
- [ ] 置信度分级标注(official / inferred / expired)
|
|||
|
|
- [ ] 价格单位统一为 ¥/MTok 或 $/MTok
|
|||
|
|
- [ ] 同模型多源价格差异 > 20% 时标注"待核实"
|
|||
|
|
- [ ] 采集失败写入日志,保留旧数据
|
|||
|
|
|
|||
|
|
#### 部署验证
|
|||
|
|
- [ ] `docker-compose up` 可正常启动所有服务
|
|||
|
|
- [ ] PostgreSQL 数据库持久化到 `data/` 目录
|
|||
|
|
- [ ] 报告 HTML 生成到 `reports/` 目录
|
|||
|
|
# Phase 2 才引入 Nginx
|
|||
|
|
- [ ] API `/api/v1/health` 返回 200
|
|||
|
|
- [ ] 备份脚本每日推送至 OSS 成功
|
|||
|
|
|
|||
|
|
#### 性能验证
|
|||
|
|
- [ ] 371 模型采集完成 < 5 分钟
|
|||
|
|
- [ ] 报告生成 < 30 秒
|
|||
|
|
- [ ] API 查询响应 < 500ms(/models, 20 条)
|
|||
|
|
- [ ] 并发 10 个采集器同时运行,内存 < 2GB
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 附录:目录结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
llm-intelligence/
|
|||
|
|
├── TECHNICAL_DESIGN.md # 本文档
|
|||
|
|
├── PRD.md # 产品需求文档
|
|||
|
|
├── FEATURE_LIST.md # 功能清单
|
|||
|
|
├── BUSINESS_MODEL.md # 商业模式
|
|||
|
|
├── MARKET_ANALYSIS.md # 市场调研
|
|||
|
|
│
|
|||
|
|
├── Dockerfile.collector # 采集器镜像
|
|||
|
|
├── Dockerfile.api # API 服务镜像
|
|||
|
|
├── docker-compose.yml # 容器编排
|
|||
|
|
├── .env.example # 环境变量模板
|
|||
|
|
├── nginx.conf # Nginx 配置
|
|||
|
|
│
|
|||
|
|
├── collectors/ # 数据采集器
|
|||
|
|
│ ├── __init__.py
|
|||
|
|
│ ├── base.py # 采集器基类
|
|||
|
|
│ ├── openrouter.py # OpenRouter 采集器
|
|||
|
|
│ ├── deepseek.py # DeepSeek 采集器
|
|||
|
|
│ ├── aliyun.py # 阿里云 DashScope 采集器
|
|||
|
|
│ ├── kimi.py # Moonshot/Kimi 采集器
|
|||
|
|
│ ├── zhipu.py # 智谱 BigModel 采集器
|
|||
|
|
│ ├── minimax.py # MiniMax 采集器
|
|||
|
|
│ ├── volcengine.py # 火山引擎采集器
|
|||
|
|
│ ├── tencent.py # 腾讯云采集器
|
|||
|
|
│ ├── baidu.py # 百度 Qianfan 采集器
|
|||
|
|
│ └── siliconflow.py # 硅基流动采集器
|
|||
|
|
│
|
|||
|
|
├── services/ # 服务层
|
|||
|
|
│ ├── __init__.py
|
|||
|
|
│ ├── database.py # SQLAlchemy 数据库连接
|
|||
|
|
│ ├── models.py # ORM 模型定义
|
|||
|
|
│ ├── report_generator.py # 每日报告生成器
|
|||
|
|
│ ├── price_alert.py # 价格告警服务
|
|||
|
|
│ ├── notifier.py # 钉钉/飞书推送
|
|||
|
|
│ └── recommendation.py # 模型推荐引擎
|
|||
|
|
│
|
|||
|
|
├── api/ # REST API
|
|||
|
|
│ ├── __init__.py
|
|||
|
|
│ ├── app.py # Flask 应用入口
|
|||
|
|
│ ├── routes/
|
|||
|
|
│ │ ├── __init__.py
|
|||
|
|
│ │ ├── models.py # /models 路由
|
|||
|
|
│ │ ├── cost.py # /cost 路由
|
|||
|
|
│ │ ├── recommend.py # /recommend 路由
|
|||
|
|
│ │ └── reports.py # /reports 路由
|
|||
|
|
│ └── schemas.py # Pydantic 请求/响应模型
|
|||
|
|
│
|
|||
|
|
├── static/ # 前端静态文件
|
|||
|
|
│ ├── index.html # 首页/报告列表
|
|||
|
|
│ ├── explorer.html # 模型浏览器
|
|||
|
|
│ ├── calculator.html # 成本计算器
|
|||
|
|
│ ├── trends.html # 趋势分析
|
|||
|
|
│ ├── css/
|
|||
|
|
│ │ └── style.css
|
|||
|
|
│ └── js/
|
|||
|
|
│ ├── dataService.js # API 调用封装
|
|||
|
|
│ ├── explorer.js # 模型浏览器逻辑
|
|||
|
|
│ ├── calculator.js # 计算器逻辑
|
|||
|
|
│ └── charts.js # ECharts 封装
|
|||
|
|
│
|
|||
|
|
├── templates/ # Jinja2 报告模板
|
|||
|
|
│ └── report.html # 每日报告 HTML 模板
|
|||
|
|
│
|
|||
|
|
├── reports/ # 生成的报告 HTML 输出
|
|||
|
|
│ └── 2026-05-04.html
|
|||
|
|
│
|
|||
|
|
├── scripts/ # 运维脚本
|
|||
|
|
│ ├── run_daily.sh # 每日采集 + 报告脚本
|
|||
|
|
│ ├── backup.sh # 数据库备份脚本
|
|||
|
|
│ ├── migrate.sh # PostgreSQL Schema 部署脚本
|
|||
|
|
│ └── init_db.py # 数据库初始化脚本
|
|||
|
|
│
|
|||
|
|
├── tests/ # 单元测试
|
|||
|
|
│ ├── test_collectors.py
|
|||
|
|
│ ├── test_api.py
|
|||
|
|
│ └── test_report.py
|
|||
|
|
│
|
|||
|
|
├── data/ # PostgreSQL 数据目录(运行时生成)
|
|||
|
|
│ └── llm_intelligence.db
|
|||
|
|
│
|
|||
|
|
└── logs/ # 日志文件(运行时生成)
|
|||
|
|
├── collector.log
|
|||
|
|
├── api.log
|
|||
|
|
└── backup.log
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**文档状态:** 设计修订完成 ✅
|
|||
|
|
|
|||
|
|
**修订内容(2026-05-06):**
|
|||
|
|
- SQLite → PostgreSQL(与立交桥技术栈统一)
|
|||
|
|
- 移除第三方消息组件依赖,改用 PostgreSQL 数据库内队列
|
|||
|
|
- 技术架构简洁化
|
|||
|
|
|
|||
|
|
**下一步行动:**
|
|||
|
|
- [ ] 技术负责人评审架构设计
|
|||
|
|
- [ ] 确认数据库选型(已确定为 PostgreSQL)
|
|||
|
|
- [ ] 确认 OpenRouter API Key 获取方式
|
|||
|
|
- [ ] Sprint 1 任务分配
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
_文档编制:宰相(AI 辅助)_
|
|||
|
|
_基于 PRD.md(v0.3)、FEATURE_LIST.md(v1.0)、BUSINESS_MODEL.md(v1.0)、MARKET_ANALYSIS.md(v3.0)_
|