# 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": "...", "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 │ └────────┬────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────┐ │ 解析响应 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
``` --- ## 七、部署架构 ### 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)_