|
|
@@ -0,0 +1,809 @@
|
|
|
+# SupplyAgent 代码仓库结构总结
|
|
|
+
|
|
|
+## 1. 阅读基线
|
|
|
+
|
|
|
+本文档完全基于当前代码仓库重新阅读后形成,不继承此前对项目的业务推演或可视化设计理解。
|
|
|
+
|
|
|
+代码快照:
|
|
|
+
|
|
|
+- 分支:`feature/zhangbo`
|
|
|
+- 阅读时提交:`2ab77ce`
|
|
|
+- Python 要求:`>= 3.11`
|
|
|
+- 后端:FastAPI + SQLAlchemy 2.x + PyMySQL
|
|
|
+- Agent:OpenRouter/OpenAI 兼容 API + 自研 Tool/Skill/ReAct 框架
|
|
|
+- 数据源:ODPS(MaxCompute)
|
|
|
+- 数据库:MySQL
|
|
|
+- 对象存储:阿里云 OSS
|
|
|
+- 前端:Vue 3 + TypeScript + Vite
|
|
|
+
|
|
|
+`.env` 已被 `.gitignore` 忽略。本文不会记录其中的密码、密钥、数据库地址或 Token。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 2. 当前项目的实际定位
|
|
|
+
|
|
|
+当前 SupplyAgent 由两部分组成:
|
|
|
+
|
|
|
+1. 一套通用 AI Agent 运行框架;
|
|
|
+2. 一套围绕“需求词、全局分类树、热度、视频和需求生成”的业务系统。
|
|
|
+
|
|
|
+业务系统当前主要完成:
|
|
|
+
|
|
|
+- 从 ODPS 同步全局分类树和树下元素;
|
|
|
+- 从 ODPS 同步多策略需求池;
|
|
|
+- 将需求词挂到分类树节点;
|
|
|
+- 汇总四项先验热度和真实 ROV/VOV;
|
|
|
+- 把词级热度沿分类树向上聚合;
|
|
|
+- 按单一热度维度生成并保存平台需求;
|
|
|
+- 把需求词连接到真实视频和视频最终选题;
|
|
|
+- 通过 API 和 Vue 页面展示分类树、热力图和下钻证据;
|
|
|
+- 记录 Agent 完整执行过程,生成 HTML,上传 OSS 并在 MySQL 留档。
|
|
|
+
|
|
|
+当前系统的核心业务链路是:
|
|
|
+
|
|
|
+```text
|
|
|
+ODPS 全局分类与元素
|
|
|
+ ↓
|
|
|
+MySQL 全局分类树
|
|
|
+ ↑
|
|
|
+需求池词语 → 归类 Agent → 需求词挂靠
|
|
|
+ ↓
|
|
|
+四项先验 + 真实 ROV/VOV
|
|
|
+ ↓
|
|
|
+词级统计 → 分类树节点聚合 → 四维全局排名
|
|
|
+ ↓
|
|
|
+需求生成 Agent → generated_demand
|
|
|
+ ↓
|
|
|
+FastAPI → Vue 分类树/热力图/证据下钻
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 3. 仓库分层
|
|
|
+
|
|
|
+```text
|
|
|
+SupplyAgent/
|
|
|
+├── supply_agent/ 通用 Agent 框架
|
|
|
+├── agents/ 业务 Agent 和专属工具
|
|
|
+├── supply_infra/ MySQL、ODPS、OSS、定时任务
|
|
|
+├── api/ FastAPI 查询接口和静态前端托管
|
|
|
+├── web/ Vue 3 业务前端
|
|
|
+├── jobs/ 数据任务的手动 CLI 入口
|
|
|
+├── scripts/ 部署、日志上传、日志可视化脚本
|
|
|
+├── visualization/ 早期/独立的业务设计可视化材料
|
|
|
+├── Dockerfile 前端构建 + Python 运行镜像
|
|
|
+├── pyproject.toml Python 包、依赖和命令入口
|
|
|
+└── requirements.txt 另一份运行依赖清单
|
|
|
+```
|
|
|
+
|
|
|
+模块之间的依赖方向:
|
|
|
+
|
|
|
+```text
|
|
|
+web
|
|
|
+ ↓ HTTP
|
|
|
+api
|
|
|
+ ↓
|
|
|
+supply_infra.db.repositories
|
|
|
+ ↓
|
|
|
+MySQL
|
|
|
+
|
|
|
+agents
|
|
|
+ ↓
|
|
|
+supply_agent(Agent 框架)
|
|
|
+ ↓
|
|
|
+supply_infra(数据库/ODPS/OSS)
|
|
|
+
|
|
|
+jobs / scheduler
|
|
|
+ ↓
|
|
|
+supply_infra.scheduler.jobs
|
|
|
+ ↓
|
|
|
+ODPS + MySQL + 业务 Agent
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 4. 通用 Agent 框架:`supply_agent/`
|
|
|
+
|
|
|
+### 4.1 `supply_agent/config.py`
|
|
|
+
|
|
|
+负责 Agent 侧配置:
|
|
|
+
|
|
|
+- OpenRouter API Key、模型和 Base URL;
|
|
|
+- Agent 最大迭代次数和温度;
|
|
|
+- Skills 目录;
|
|
|
+- 日志目录和日志开关。
|
|
|
+
|
|
|
+配置来自环境变量或项目根目录 `.env`,相对路径会解析到项目根目录。
|
|
|
+
|
|
|
+### 4.2 `supply_agent/llm/client.py`
|
|
|
+
|
|
|
+基于 OpenAI Python SDK 连接 OpenRouter,提供:
|
|
|
+
|
|
|
+- 同步对话 `chat`;
|
|
|
+- 异步对话 `achat`;
|
|
|
+- 同步文本流 `stream`;
|
|
|
+- 异步文本流 `astream`;
|
|
|
+- Tool Calling;
|
|
|
+- OpenRouter reasoning effort;
|
|
|
+- LLM 输入和输出日志。
|
|
|
+
|
|
|
+模型可以在运行时切换。
|
|
|
+
|
|
|
+### 4.3 `supply_agent/tools/`
|
|
|
+
|
|
|
+`base.py` 提供:
|
|
|
+
|
|
|
+- `@tool` 装饰器;
|
|
|
+- 从 Python 函数签名生成 JSON Schema;
|
|
|
+- Optional 和基础类型转换;
|
|
|
+- 同步/异步工具执行;
|
|
|
+- 异常转为 JSON 错误结果。
|
|
|
+
|
|
|
+`registry.py` 提供:
|
|
|
+
|
|
|
+- 工具注册、删除和查询;
|
|
|
+- OpenAI ToolDefinition 列表;
|
|
|
+- 根据模型返回的工具名执行工具;
|
|
|
+- 同步和异步执行;
|
|
|
+- 批量注册被 `@tool` 装饰的函数。
|
|
|
+
|
|
|
+### 4.4 `supply_agent/skills/`
|
|
|
+
|
|
|
+Skill 机制读取指定目录下的 `SKILL.md`:
|
|
|
+
|
|
|
+- 支持 YAML frontmatter 的名称和描述;
|
|
|
+- 启动时把 Skill 目录作为目录索引加入系统提示词;
|
|
|
+- Agent 通过内置 `load_skill` 工具按需加载全文;
|
|
|
+- Skill 加载后会重建系统消息并注入当前上下文。
|
|
|
+
|
|
|
+当前仓库根目录的 `skills/` 被 `.gitignore` 忽略,因此代码支持 Skill,但仓库没有可随代码分发的业务 Skill 内容。
|
|
|
+
|
|
|
+### 4.5 `supply_agent/agent/`
|
|
|
+
|
|
|
+`core.py` 的 `Agent` 负责组装:
|
|
|
+
|
|
|
+- LLMClient;
|
|
|
+- ToolRegistry;
|
|
|
+- SkillRegistry;
|
|
|
+- AgentLoop;
|
|
|
+- AgentLogger。
|
|
|
+
|
|
|
+对外提供:
|
|
|
+
|
|
|
+- `run`;
|
|
|
+- `arun`;
|
|
|
+- `stream`;
|
|
|
+- `astream`。
|
|
|
+
|
|
|
+`loop.py` 实现标准 ReAct/Tool Calling 循环:
|
|
|
+
|
|
|
+```text
|
|
|
+系统消息 + 对话历史
|
|
|
+ ↓
|
|
|
+调用 LLM
|
|
|
+ ↓
|
|
|
+有工具调用?──否──→ 返回最终结果
|
|
|
+ │是
|
|
|
+ ↓
|
|
|
+执行工具并写入 Tool Message
|
|
|
+ ↓
|
|
|
+下一轮 LLM
|
|
|
+```
|
|
|
+
|
|
|
+达到最大迭代次数时,同步和普通异步运行会追加一条用户消息,要求模型给出当前最佳答案;流式路径则直接发出 Max iterations reached。
|
|
|
+
|
|
|
+### 4.6 `supply_agent/logging/`
|
|
|
+
|
|
|
+每次 Agent 运行生成:
|
|
|
+
|
|
|
+- 人类可读 `.log`;
|
|
|
+- 结构化 `.jsonl`。
|
|
|
+
|
|
|
+日志记录:
|
|
|
+
|
|
|
+- run_start;
|
|
|
+- 完整 LLM 输入;
|
|
|
+- 模型输出和 reasoning;
|
|
|
+- 工具调用参数和结果;
|
|
|
+- Skill 加载;
|
|
|
+- run_end。
|
|
|
+
|
|
|
+运行结束后:
|
|
|
+
|
|
|
+1. 将日志渲染成 HTML;
|
|
|
+2. 上传 `.log`、`.jsonl`、`.html` 到 OSS;
|
|
|
+3. 把 HTML 公网地址写入 `oss_logs`;
|
|
|
+4. 上传失败只记录错误,不影响 Agent 主结果。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 5. 三个业务 Agent
|
|
|
+
|
|
|
+## 5.1 `demand_belong_category_agent`
|
|
|
+
|
|
|
+目标:把需求词挂到真实存在的全局分类树节点。
|
|
|
+
|
|
|
+工具:
|
|
|
+
|
|
|
+- `query_global_tree_category`:读取整棵树或指定子树;
|
|
|
+- `batch_insert_demand_belong_category`:批量写入需求词、分类 ID 和原因。
|
|
|
+
|
|
|
+业务流程:
|
|
|
+
|
|
|
+1. 查看顶层分类;
|
|
|
+2. 选择最相关分支;
|
|
|
+3. 逐层下钻;
|
|
|
+4. 不确定时停在更可靠的上层节点;
|
|
|
+5. 将结果写入 `demand_belong_category`。
|
|
|
+
|
|
|
+系统提示允许一个词选择一个或多个高置信节点,但当前数据库和写入工具以 `name` 唯一:
|
|
|
+
|
|
|
+- `demand_belong_category.name` 有唯一约束;
|
|
|
+- 同一次请求也按 name 去重;
|
|
|
+- 因此当前实现实际上只能保存“一词一个分类节点”;
|
|
|
+- 多挂靠尚未在当前表结构中实现。
|
|
|
+
|
|
|
+定时任务会把新需求词按 100 个一批交给该 Agent。
|
|
|
+
|
|
|
+## 5.2 `generate_demand_agent`
|
|
|
+
|
|
|
+目标:从分类树局部热度和已挂靠需求词中,按单一维度生成平台需求结果。
|
|
|
+
|
|
|
+支持的来源维度只有四项先验:
|
|
|
+
|
|
|
+- `ext_pop`:外部热度;
|
|
|
+- `plat_sust_pop`:平台持续热度;
|
|
|
+- `plat_ly_pop`:平台去年同期热度;
|
|
|
+- `recent_pop`:近期热度。
|
|
|
+
|
|
|
+一次运行只允许处理一个维度。
|
|
|
+
|
|
|
+工具:
|
|
|
+
|
|
|
+- `query_latest_biz_dt`:取两张统计表都有数据的最新日期;
|
|
|
+- `query_category_tree_by_dim`:查看指定维度有数据的树;
|
|
|
+- `query_category_leaves_by_dim`:定位有数据的叶子节点;
|
|
|
+- `query_demand_words_by_category`:查询节点或子树下的真实需求词;
|
|
|
+- `query_category_path`:补充根到节点的路径;
|
|
|
+- `batch_save_generated_demands`:写入 `generated_demand`。
|
|
|
+
|
|
|
+输出结构固定为:
|
|
|
+
|
|
|
+```text
|
|
|
+source_dim
|
|
|
+ └── overall_direction
|
|
|
+ └── summary_event
|
|
|
+ └── demand_name
|
|
|
+```
|
|
|
+
|
|
|
+重要约束:
|
|
|
+
|
|
|
+- `demand_name` 必须原样来自 `demand_belong_category.name`;
|
|
|
+- Agent 不能新造需求词;
|
|
|
+- `summary_event` 尽量对应一个需求,最多轻量合并 2~3 个强相关需求;
|
|
|
+- 写入时保存类目、路径、维度 avg/count 快照、reason、业务日和 run_id;
|
|
|
+- 同一次工具调用按 `(source_dim, demand_name)` 去重;
|
|
|
+- 当前 `generated_demand` 没有数据库唯一约束,不同 run 可以重复生成相同需求。
|
|
|
+
|
|
|
+真实 ROV/VOV 当前不属于该 Agent 的 `source_dim`,也没有参与其单维生成工具链。
|
|
|
+
|
|
|
+## 5.3 `find_agent`
|
|
|
+
|
|
|
+目标:搜索和解析抖音视频。
|
|
|
+
|
|
|
+当前真正注册的工具只有:
|
|
|
+
|
|
|
+- `douyin_search`:调用外部抖音关键词搜索服务;
|
|
|
+- `douyin_detail`:按 content_id 获取视频详情和可播放地址;
|
|
|
+- `qwen_video_analyze`:调用千问视频模型解析视频。
|
|
|
+
|
|
|
+搜索和详情接口有约 10 秒的请求间隔限制。
|
|
|
+
|
|
|
+当前系统提示还要求调用:
|
|
|
+
|
|
|
+- `save_video_content`;
|
|
|
+- `query_video_content`。
|
|
|
+
|
|
|
+但仓库中没有这两个工具,也没有被注册。因此当前 `find_agent` 能搜索、取详情和解析视频,但不能按提示完成内容入库或历史查询。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 6. 基础设施:`supply_infra/`
|
|
|
+
|
|
|
+### 6.1 配置
|
|
|
+
|
|
|
+`supply_infra/config.py` 管理:
|
|
|
+
|
|
|
+- MySQL;
|
|
|
+- ODPS;
|
|
|
+- APScheduler;
|
|
|
+- 阿里云 OSS;
|
|
|
+- Agent 日志上传开关。
|
|
|
+
|
|
|
+配置文件固定从项目根目录 `.env` 读取,不依赖当前工作目录。
|
|
|
+
|
|
|
+### 6.2 数据库会话
|
|
|
+
|
|
|
+`supply_infra/db/session.py`:
|
|
|
+
|
|
|
+- 懒加载全局 SQLAlchemy Engine;
|
|
|
+- 使用连接池和 `pool_pre_ping`;
|
|
|
+- `get_session()` 自动提交;
|
|
|
+- 异常时自动回滚;
|
|
|
+- `init_db()` 通过 ORM metadata 创建缺失表。
|
|
|
+
|
|
|
+### 6.3 Repository 规则
|
|
|
+
|
|
|
+业务 Agent、API 和定时任务原则上不直接执行 MySQL SQL,而是通过 Repository:
|
|
|
+
|
|
|
+- 基础 CRUD;
|
|
|
+- 批量插入;
|
|
|
+- insert ignore;
|
|
|
+- upsert;
|
|
|
+- 条件查询;
|
|
|
+- 批量更新。
|
|
|
+
|
|
|
+ODPS 查询仍在 `supply_infra/odps/client.py` 内直接组织 SQL。
|
|
|
+
|
|
|
+### 6.4 ODPS 客户端
|
|
|
+
|
|
|
+当前读取的主要 ODPS 数据:
|
|
|
+
|
|
|
+- `public_pattern_mining_category`:全局分类;
|
|
|
+- `pattern_mining_element`:树下元素;
|
|
|
+- `dwd_multi_demand_pool_di`:多策略需求池;
|
|
|
+- `dwd_topic_decode_result_di`:视频解析和最终选题;
|
|
|
+- `dwd_video_produce_plan_stat_hour`:真实 ROV/VOV。
|
|
|
+
|
|
|
+### 6.5 OSS 客户端
|
|
|
+
|
|
|
+负责:
|
|
|
+
|
|
|
+- 生成对象路径;
|
|
|
+- 上传本地文件;
|
|
|
+- 返回 CDN 公网地址。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 7. MySQL 数据模型
|
|
|
+
|
|
|
+当前 ORM 定义 10 张表。
|
|
|
+
|
|
|
+| 表 | 作用 | 关键唯一性/关联 |
|
|
|
+|---|---|---|
|
|
|
+| `global_tree_category` | 全局分类树节点 | `source_id` 唯一;`parent_id` 形成树 |
|
|
|
+| `global_tree_element` | ODPS 元素与分类挂靠 | `(name, category_id)` 唯一 |
|
|
|
+| `multi_demand_pool_di` | 按日同步的策略需求池 | 代码按 `(strategy, demand_id, biz_dt)` 管理差异 |
|
|
|
+| `demand_belong_category` | 需求词归属分类 | `name` 唯一;当前一词只能保存一个分类 |
|
|
|
+| `demand_belong_pool_rel` | 需求词与需求池行的匹配边 | `(demand_belong_category_id, multi_demand_pool_di_id)` 唯一 |
|
|
|
+| `demand_popularity_stats` | 词级六维 avg/count | `(demand_category_id, biz_dt)` 唯一 |
|
|
|
+| `category_tree_weight` | 分类树节点六维聚合和四维排名分 | `(category_id, biz_dt)` 唯一 |
|
|
|
+| `multi_demand_video_detail` | 视频标题与最终选题 JSON | `vid` 唯一 |
|
|
|
+| `generated_demand` | 需求生成 Agent 输出 | 按 run_id 记录,无数据库唯一约束 |
|
|
|
+| `oss_logs` | Agent 日志 HTML 的 OSS 地址 | 按 agent_name 查询 |
|
|
|
+
|
|
|
+### 7.1 关键关系
|
|
|
+
|
|
|
+```text
|
|
|
+global_tree_category.id
|
|
|
+ ├── global_tree_category.parent_id
|
|
|
+ ├── global_tree_element.category_id
|
|
|
+ ├── demand_belong_category.category_id
|
|
|
+ ├── category_tree_weight.category_id
|
|
|
+ └── generated_demand.category_id
|
|
|
+
|
|
|
+demand_belong_category.id
|
|
|
+ ├── demand_popularity_stats.demand_category_id
|
|
|
+ ├── demand_belong_pool_rel.demand_belong_category_id
|
|
|
+ └── generated_demand.demand_belong_id
|
|
|
+
|
|
|
+multi_demand_pool_di.id
|
|
|
+ └── demand_belong_pool_rel.multi_demand_pool_di_id
|
|
|
+```
|
|
|
+
|
|
|
+这些关联在 ORM 中主要以整数 ID 表达,没有使用 SQLAlchemy relationship,也没有显式数据库 ForeignKey。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 8. 热度数据的真实代码口径
|
|
|
+
|
|
|
+### 8.1 策略到四项先验的映射
|
|
|
+
|
|
|
+代码把需求池策略映射为:
|
|
|
+
|
|
|
+| ODPS strategy | 统计字段 | 页面含义 |
|
|
|
+|---|---|---|
|
|
|
+| `新热事件` | `ext_pop` | 外部热度 |
|
|
|
+| `逐月` | `plat_sust_pop` | 平台持续热度 |
|
|
|
+| `去年同期阳历` | `plat_ly_pop` | 去年同期热度 |
|
|
|
+| `去年同期阴历` | `plat_ly_pop` | 去年同期热度 |
|
|
|
+| `当下供需gap` | `recent_pop` | 近期热度 |
|
|
|
+
|
|
|
+### 8.2 真实 ROV/VOV
|
|
|
+
|
|
|
+从近 7 日 `dwd_video_produce_plan_stat_hour` 获取人工 AGC 和自动 AGC 数据:
|
|
|
+
|
|
|
+- ROV = 回流 UV / 分发曝光 PV;
|
|
|
+- VOV = 拉回曝光 PV / 分发曝光 PV;
|
|
|
+- 同一特征值存在多行时,优先保留 `rov_diff`、`vov_diff` 较高的一行;
|
|
|
+- 按特征值与 `demand_name` 精确匹配,回填 `multi_demand_pool_di`。
|
|
|
+
|
|
|
+### 8.3 词级统计
|
|
|
+
|
|
|
+对每个 `demand_belong_category.name`:
|
|
|
+
|
|
|
+1. 在当日 `multi_demand_pool_di.demand_name` 中执行子串 LIKE 匹配;
|
|
|
+2. 按策略收集非零 weight;
|
|
|
+3. 分别计算四项先验的 avg/count;
|
|
|
+4. 汇总匹配行的真实 ROV/VOV avg/count;
|
|
|
+5. 写入 `demand_popularity_stats`。
|
|
|
+
|
|
|
+因此当前词与需求池的匹配核心是字符串包含关系,不是分词索引、向量匹配或显式语义关系。
|
|
|
+
|
|
|
+### 8.4 分类树节点聚合
|
|
|
+
|
|
|
+分类节点聚合六个独立维度:
|
|
|
+
|
|
|
+- 外部热度;
|
|
|
+- 平台持续热度;
|
|
|
+- 去年同期热度;
|
|
|
+- 近期热度;
|
|
|
+- 真实 ROV;
|
|
|
+- 真实 VOV。
|
|
|
+
|
|
|
+每个节点统计其整个子树内有需求词挂靠的节点:
|
|
|
+
|
|
|
+```text
|
|
|
+节点维度 avg = Σ(挂靠点 avg × count) / Σ(count)
|
|
|
+节点维度 count = Σ(count)
|
|
|
+```
|
|
|
+
|
|
|
+结果写入 `category_tree_weight`。
|
|
|
+
|
|
|
+### 8.5 四维排名分和全局热度
|
|
|
+
|
|
|
+`update_category_tree_rank_scores.py` 只对四项先验排名:
|
|
|
+
|
|
|
+- 各维只让 `count > 0` 的节点参与;
|
|
|
+- 按 avg 降序排名;
|
|
|
+- 同分取平均名次;
|
|
|
+- 映射到 `(0, 1]`;
|
|
|
+- 无数据节点为 0;
|
|
|
+- `total_score` 是四个排名分直接相加,范围 `[0, 4]`。
|
|
|
+
|
|
|
+真实 ROV/VOV 会被聚合并通过 API 返回,但当前不进入 `total_score`。
|
|
|
+
|
|
|
+前端显示“全局热度”时再使用 `total_score / 4` 映射为 `0~1` 色阶。
|
|
|
+
|
|
|
+单一维度热度则由前端根据该维所有有数据节点的 avg 做百分位色阶,不直接使用数据库中的四维 rank score 字段。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 9. 定时任务和数据流水线
|
|
|
+
|
|
|
+### 9.1 已注册的两个定时任务
|
|
|
+
|
|
|
+`supply_infra/scheduler/app.py` 当前只注册:
|
|
|
+
|
|
|
+1. 每天 `02:30`:同步昨天的全局树和元素;
|
|
|
+2. 每天 `12:00`:同步当天的策略需求池并执行后续完整流水线。
|
|
|
+
|
|
|
+时区来自 `SCHEDULER_TIMEZONE`。
|
|
|
+
|
|
|
+### 9.2 全局树同步
|
|
|
+
|
|
|
+`sync_global_tree_odps_to_mysql`:
|
|
|
+
|
|
|
+1. 拉取有效元素;
|
|
|
+2. 拉取全部分类;
|
|
|
+3. 从元素命中的分类向上补齐祖先;
|
|
|
+4. 按 ODPS source_id 去重;
|
|
|
+5. 已有分类保持不变,只插入新分类;
|
|
|
+6. 给新分类分配 MySQL ID 并转换 parent_id;
|
|
|
+7. 写入元素及其 MySQL category_id。
|
|
|
+
|
|
|
+该任务是增量插入,不会根据 ODPS 当前状态自动删除或更新历史分类。
|
|
|
+
|
|
|
+### 9.3 策略需求池完整流水线
|
|
|
+
|
|
|
+`sync_multi_demand_pool_odps_to_mysql` 实际执行顺序:
|
|
|
+
|
|
|
+1. 比较 ODPS 与 MySQL 当日去重行数;
|
|
|
+2. 行数不同时执行差异同步;
|
|
|
+3. 对当日所有 demand_name 按空格拆词;
|
|
|
+4. 调用归类 Agent 处理未出现过的新词;
|
|
|
+5. 建立需求词与需求池的子串匹配边,并回填词级 video_list;
|
|
|
+6. 获取并回填近 7 日真实 ROV/VOV;
|
|
|
+7. 计算词级六维 avg/count;
|
|
|
+8. 计算整棵分类树六维聚合;
|
|
|
+9. 计算四项先验全局排名分和 total_score;
|
|
|
+10. 同步全部待处理视频的标题和最终选题。
|
|
|
+
|
|
|
+当前有一个同步判断风险:只要 ODPS 和 MySQL 行数相同,就跳过需求池差异拉取;如果内容发生变化但总行数不变,该轮不会发现这些变化。
|
|
|
+
|
|
|
+### 9.4 手动任务入口
|
|
|
+
|
|
|
+`jobs/` 提供:
|
|
|
+
|
|
|
+- 初始化数据库;
|
|
|
+- 启动 Scheduler;
|
|
|
+- 单独同步需求池视频;
|
|
|
+- 回填视频列表和标题;
|
|
|
+- 单独计算词级热度;
|
|
|
+- 单独计算树节点权重;
|
|
|
+- 单独计算四维排名;
|
|
|
+- 单独同步需求词与需求池关系。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 10. FastAPI:`api/`
|
|
|
+
|
|
|
+服务端口:`8080`。
|
|
|
+
|
|
|
+| 接口 | 作用 |
|
|
|
+|---|---|
|
|
|
+| `GET /health` | 健康检查 |
|
|
|
+| `GET /api/category-tree?biz_dt=YYYYMMDD` | 返回嵌套分类树、六维 avg/count、total_score 和挂靠词数 |
|
|
|
+| `GET /api/demand-belong-category` | 返回所有有效需求词挂靠 |
|
|
|
+| `GET /api/demand-belong-category/{id}/videos` | 返回需求词关联的视频标题和最终选题 JSON |
|
|
|
+| `GET /api/demand-belong-oss-logs` | 返回归类 Agent 的 OSS 日志列表 |
|
|
|
+
|
|
|
+如果 `web/dist` 存在,FastAPI 会把它挂到 `/`,同一个 8080 服务同时提供 API 和生产前端。
|
|
|
+
|
|
|
+API 当前是同步 SQLAlchemy 查询,没有分页、鉴权或缓存。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 11. Vue 前端:`web/`
|
|
|
+
|
|
|
+### 11.1 路由
|
|
|
+
|
|
|
+| 路由 | 页面 |
|
|
|
+|---|---|
|
|
|
+| `/` | 平台全局需求地图 |
|
|
|
+| `/demand-tree` | 传统横向分类树 |
|
|
|
+| `/demand-process` | 需求归类 Agent 日志 |
|
|
|
+| `/demand-map` | 重定向到 `/` |
|
|
|
+
|
|
|
+当前顶部导航只显示“平台全局需求地图”,另外两个页面有路由但没有导航入口。
|
|
|
+
|
|
|
+### 11.2 平台全局需求地图
|
|
|
+
|
|
|
+`GlobalDemandMapView.vue` 并行读取:
|
|
|
+
|
|
|
+- 完整分类树和热度;
|
|
|
+- 全部需求词挂靠。
|
|
|
+
|
|
|
+`IcicleHeatTree.vue` 使用 Canvas 绘制从左到右的冰柱树:
|
|
|
+
|
|
|
+- 每层固定 `200px` 宽;
|
|
|
+- 全局状态适配视口高度;
|
|
|
+- 选择层级或聚焦节点后按可读高度纵向扩展;
|
|
|
+- 最大逻辑画布高度 `24000px`;
|
|
|
+- 实际 Canvas 始终只有视口大小,只绘制可见区域;
|
|
|
+- 普通滚轮滚动;
|
|
|
+- 拖拽平移;
|
|
|
+- `Ctrl/Command + 滚轮` 缩放;
|
|
|
+- 点击节点聚焦子树;
|
|
|
+- 支持层级起点和祖先面包屑。
|
|
|
+
|
|
|
+热力标签:
|
|
|
+
|
|
|
+- 全局树结构;
|
|
|
+- 全局热度 `total_score`;
|
|
|
+- 外部热度;
|
|
|
+- 平台持续热度;
|
|
|
+- 去年同期热度;
|
|
|
+- 近期热度。
|
|
|
+
|
|
|
+API 虽然返回真实 ROV/VOV,但当前冰柱图标签没有提供真实 ROV/VOV 的独立切换入口。
|
|
|
+
|
|
|
+### 11.3 节点证据下钻
|
|
|
+
|
|
|
+分类节点存在需求词时显示搜索标识。点击后通过 `DemandPathPanel.vue` 展开:
|
|
|
+
|
|
|
+```text
|
|
|
+当前分类节点
|
|
|
+ → 细节元素/需求词
|
|
|
+ → 真实视频实例
|
|
|
+ → 最终选题 JSON
|
|
|
+```
|
|
|
+
|
|
|
+这里展示的是 `demand_belong_category` 需求词,不是 `generated_demand` 中由生成 Agent 产出的四层平台需求。
|
|
|
+
|
|
|
+当前前端没有读取或展示 `generated_demand` 的 API。
|
|
|
+
|
|
|
+### 11.4 传统分类树页面
|
|
|
+
|
|
|
+`CategoryTree.vue` 使用 Vue DOM 递归组件展示:
|
|
|
+
|
|
|
+- 默认展开 3 层;
|
|
|
+- 支持选择展开深度;
|
|
|
+- 支持手动展开和收起;
|
|
|
+- 支持按维度过滤有数据的节点;
|
|
|
+- 支持拖动画布;
|
|
|
+- 支持导出包含数据的独立 HTML;
|
|
|
+- 同样可以下钻需求词、视频和最终选题。
|
|
|
+
|
|
|
+### 11.5 需求归类过程页面
|
|
|
+
|
|
|
+读取 `demand_belong_category_agent` 的 `oss_logs`,按时间倒序显示,点击打开 Agent 运行过程 HTML。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 12. 部署和运行
|
|
|
+
|
|
|
+### 12.1 Python 命令入口
|
|
|
+
|
|
|
+`pyproject.toml` 注册:
|
|
|
+
|
|
|
+- `supply-api`;
|
|
|
+- `supply-scheduler`;
|
|
|
+- `supply-visualize`。
|
|
|
+
|
|
|
+### 12.2 本地开发
|
|
|
+
|
|
|
+后端:
|
|
|
+
|
|
|
+```bash
|
|
|
+python -m api
|
|
|
+```
|
|
|
+
|
|
|
+前端:
|
|
|
+
|
|
|
+```bash
|
|
|
+cd web
|
|
|
+npm install
|
|
|
+npm run dev
|
|
|
+```
|
|
|
+
|
|
|
+Vite 在 `5173`,将 `/api` 代理到 `127.0.0.1:8080`。
|
|
|
+
|
|
|
+### 12.3 Docker
|
|
|
+
|
|
|
+Docker 使用两阶段构建:
|
|
|
+
|
|
|
+1. Node 20 构建 Vue;
|
|
|
+2. Python 3.11 安装项目和 ODPS 可选依赖;
|
|
|
+3. 把 `web/dist` 放入 Python 镜像;
|
|
|
+4. 通过 `python -m api` 启动 8080。
|
|
|
+
|
|
|
+`scripts/docker-deploy.sh` 可以构建并向指定镜像仓库推送时间戳标签和 `latest`。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 13. 当前代码已经实现的能力
|
|
|
+
|
|
|
+- 通用同步/异步 Agent 和 Tool Calling;
|
|
|
+- 动态 Skill 加载机制;
|
|
|
+- 三个业务 Agent;
|
|
|
+- 全量 Agent 日志和 OSS 发布;
|
|
|
+- ODPS 到 MySQL 的全局树同步;
|
|
|
+- 多策略需求池增量同步;
|
|
|
+- 新词自动归类;
|
|
|
+- 需求词与需求池匹配边;
|
|
|
+- 真实 ROV/VOV 回填;
|
|
|
+- 词级六维统计;
|
|
|
+- 分类树六维向上聚合;
|
|
|
+- 四项先验排名和 total_score;
|
|
|
+- 单维度平台需求生成与落库;
|
|
|
+- 视频标题和最终选题同步;
|
|
|
+- FastAPI 查询接口;
|
|
|
+- Vue 全局冰柱热力图;
|
|
|
+- 分类节点到需求词、视频和最终选题的下钻;
|
|
|
+- Docker 构建和部署脚本。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 14. 当前代码未完成或存在偏差的部分
|
|
|
+
|
|
|
+### 14.1 需求多挂靠尚未实现
|
|
|
+
|
|
|
+`demand_belong_category.name` 唯一,当前一个需求词只能保存一个 category_id。系统提示中的“一词多个节点”无法真实落库。
|
|
|
+
|
|
|
+### 14.2 `generated_demand` 未进入产品展示
|
|
|
+
|
|
|
+需求生成 Agent 已能写 `generated_demand`,但:
|
|
|
+
|
|
|
+- 没有对应 API;
|
|
|
+- 前端没有需求清单;
|
|
|
+- 没有与内容、反馈的展示闭环;
|
|
|
+- 当前全局图下钻的是需求词,不是最终生成需求。
|
|
|
+
|
|
|
+### 14.3 后验没有进入全局综合分
|
|
|
+
|
|
|
+真实 ROV/VOV 已采集、聚合并由 API 返回,但:
|
|
|
+
|
|
|
+- 不进入 `total_score`;
|
|
|
+- 不进入 generate_demand_agent 的 source_dim;
|
|
|
+- 当前主热力图没有 ROV/VOV 标签;
|
|
|
+- 尚未形成“后验反向调整最终需求排序”的完整实现。
|
|
|
+
|
|
|
+### 14.4 `find_agent` 提示和工具不一致
|
|
|
+
|
|
|
+提示要求保存和查询视频内容,但相应工具不存在。
|
|
|
+
|
|
|
+### 14.5 关系语义较弱
|
|
|
+
|
|
|
+当前主要关系是:
|
|
|
+
|
|
|
+- 树父子;
|
|
|
+- 需求词到单一分类;
|
|
|
+- 需求词和需求池的字符串子串匹配;
|
|
|
+- 需求词到视频列表。
|
|
|
+
|
|
|
+尚没有独立的语义关系表、关系类型、关系 reason、置信度和人工审核状态。
|
|
|
+
|
|
|
+### 14.6 同步完整性风险
|
|
|
+
|
|
|
+需求池同步先比较总行数。总行数相同但内容变化时,会跳过 ODPS 明细同步。
|
|
|
+
|
|
|
+### 14.7 测试体系缺失
|
|
|
+
|
|
|
+当前仓库没有 `tests/`,并且 `.gitignore` 直接忽略 `tests/`,会阻止正常提交测试目录。
|
|
|
+
|
|
|
+### 14.8 文档已滞后
|
|
|
+
|
|
|
+现有 `README.md`、`ARCHITECTURE.md` 和 `agents/README.md` 包含已经不存在或未实现的结构,例如:
|
|
|
+
|
|
|
+- `video_content` 模型/Repository;
|
|
|
+- find_agent 的保存和历史查询工具;
|
|
|
+- 旧的 Agent 数量和数据流。
|
|
|
+
|
|
|
+后续应以本文和源码为准,并更新旧文档。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 15. 当前本地运行环境与数据库状态
|
|
|
+
|
|
|
+### 15.1 本地 Python 环境不匹配
|
|
|
+
|
|
|
+当前机器默认环境:
|
|
|
+
|
|
|
+- Python `3.10.19`;
|
|
|
+- SQLAlchemy `1.4.51`;
|
|
|
+- 项目根目录没有 `.venv`。
|
|
|
+
|
|
|
+源码要求:
|
|
|
+
|
|
|
+- Python `>=3.11`;
|
|
|
+- SQLAlchemy `>=2.0`。
|
|
|
+
|
|
|
+因此直接使用当前默认 `python3` 导入数据库层会在 `DeclarativeBase` 处失败。应建立 Python 3.11+ 虚拟环境后安装项目依赖。
|
|
|
+
|
|
|
+### 15.2 `.env` 的 MySQL 配置问题
|
|
|
+
|
|
|
+只读连接检查发现:
|
|
|
+
|
|
|
+1. `MYSQL_HOST` 的值开头多了一个 `=`,会导致主机名解析失败;
|
|
|
+2. 临时去掉该字符后可以到达 MySQL 服务,但返回错误 `1045 Access denied`;
|
|
|
+3. 需要核对用户名/密码、RDS 白名单或该用户允许连接的 Host 范围。
|
|
|
+
|
|
|
+由于未通过鉴权,本次无法确认数据库真实表数量、行数和最新业务日。本文的数据结构来自当前 ORM 和 Repository 源码,不冒充线上运行态数据。
|
|
|
+
|
|
|
+### 15.3 前端依赖未安装
|
|
|
+
|
|
|
+当前没有 `web/node_modules`。Node `20.11.1`、npm `10.2.4` 已存在,执行前需要先在 `web/` 运行 `npm install` 或 `npm ci`。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 16. 建议的后续阅读顺序
|
|
|
+
|
|
|
+如果继续开发,建议按以下顺序进入代码:
|
|
|
+
|
|
|
+1. `supply_infra/scheduler/jobs/sync_multi_demand_pool_odps_to_mysql.py`:理解主业务流水线;
|
|
|
+2. `supply_infra/db/models/`:理解真实数据对象;
|
|
|
+3. `supply_infra/scheduler/jobs/compute_category_tree_weight.py`:理解树上热度;
|
|
|
+4. `agents/demand_belong_category_agent/`:理解需求词挂树;
|
|
|
+5. `agents/generate_demand_agent/`:理解平台需求生成;
|
|
|
+6. `api/services/category_tree.py`:理解后端对前端的数据形态;
|
|
|
+7. `web/src/components/IcicleHeatTree.vue`:理解全局热力图;
|
|
|
+8. `web/src/components/DemandPathPanel.vue`:理解节点证据下钻;
|
|
|
+9. `supply_agent/agent/`:理解底层 Agent 执行机制;
|
|
|
+10. `supply_agent/logging/`:理解可追溯运行日志。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 17. 一句话总结
|
|
|
+
|
|
|
+当前 SupplyAgent 是一套以 ODPS 和 MySQL 为数据底座、以全局分类树为组织骨架、以自研 Agent 框架完成需求词归类和单维需求生成、并通过 FastAPI/Vue 展示分类热度与视频证据的业务系统;核心数据流水线已经形成,但多挂靠、最终需求产品化、后验反馈进入综合决策、语义关系图和自动化测试仍未完成。
|