Aucune description

xueyiming 4aeb20c296 增加结果要求 il y a 45 minutes
agents 4aeb20c296 增加结果要求 il y a 45 minutes
alembic 408d9fcaef 寻找agent优化 il y a 7 heures
api 408d9fcaef 寻找agent优化 il y a 7 heures
deploy ab6c43d20a 增加寻找视频可视化 il y a 22 heures
prd b653d4bd55 Merge branch 'master' into feature/zhangbo il y a 5 heures
problem 49335d909e 0730问题总结 il y a 5 heures
scripts 2ec5029419 增加登录系统 il y a 1 jour
sql 408d9fcaef 寻找agent优化 il y a 7 heures
supply_agent 84de103d2a 增加结束守卫 il y a 1 jour
supply_infra 501883f0b2 增加并发 il y a 1 heure
tests 501883f0b2 增加并发 il y a 1 heure
visualization 17a937b573 增加需求汇总页面 il y a 1 semaine
web 7db8805a05 增加寻找视频可视化 il y a 22 heures
.dockerignore eaa7681db1 修复prompt不展示的问题 il y a 4 jours
.env.example 408d9fcaef 寻找agent优化 il y a 7 heures
.gitignore ab6c43d20a 增加寻找视频可视化 il y a 22 heures
ARCHITECTURE.md bfcf716d5f 修改agent基础框架和问题 il y a 2 jours
Dockerfile bfcf716d5f 修改agent基础框架和问题 il y a 2 jours
PRD.md ab6c43d20a 增加寻找视频可视化 il y a 22 heures
README.md b653d4bd55 Merge branch 'master' into feature/zhangbo il y a 5 heures
alembic.ini c21b5e69c9 feat: 重构定时任务为持久化流水线 il y a 4 jours
pyproject.toml c21b5e69c9 feat: 重构定时任务为持久化流水线 il y a 4 jours
requirements.txt c21b5e69c9 feat: 重构定时任务为持久化流水线 il y a 4 jours

README.md

SupplyAgent

一个现代、可扩展的 Python AI Agent 框架,支持 OpenRouter 多模型、Tools 工具调用和 Skills 技能系统。

架构

supply_agent/
├── config.py          # 配置管理(环境变量 / .env)
├── types.py           # 核心类型定义
├── llm/
│   └── client.py      # OpenRouter LLM 客户端(OpenAI 兼容 API)
├── tools/
│   ├── base.py        # @tool 装饰器 & Tool 类
│   └── registry.py    # 工具注册表 & 执行器
├── skills/
│   ├── loader.py      # SKILL.md 加载器(兼容 Cursor 格式)
│   └── registry.py    # 技能注册表
├── logging/
│   ├── logger.py      # 运行日志(.log + .jsonl)
│   ├── parser.py      # 日志解析
│   └── visualize.py   # HTML 可视化生成
└── agent/
    ├── core.py        # Agent 主类
    └── loop.py        # ReAct 循环(Reason → Act → Observe)

核心设计

模块 职责
LLM Client 通过 OpenRouter 调用任意模型,支持同步/异步/流式
Tool Registry 注册工具、自动生成 JSON Schema、执行工具调用
Skill Registry SKILL.md 加载专业技能指令,按需注入上下文
Agent Loop ReAct 循环:模型推理 → 工具调用 → 观察结果 → 重复

快速开始

1. 安装依赖

pip install -e ".[dev]"

2. 配置环境变量

cp .env.example .env
# 编辑 .env,填入你的 OpenRouter API Key

3. 运行示例

# 基础对话
python examples/basic_agent.py

# 带自定义工具
python examples/with_tools.py

# 带 Skills 技能
python examples/with_skills.py

# 流式事件
python examples/streaming.py

使用指南

创建 Agent

from supply_agent import Agent

# 使用默认配置(从 .env 读取)
agent = Agent()

# 指定模型
agent = Agent(model="openai/gpt-4o")

# 运行时切换模型
agent.model = "google/gemini-2.5-pro-preview"

注册 Tools

from supply_agent.tools import tool

@tool
def search(query: str, limit: int = 10) -> str:
    """Search the web for information."""
    return f"Results for: {query}"

agent = Agent()
agent.tools.from_decorated(search)

result = agent.run("Search for Python tutorials")
print(result.content)

使用 Skills

skills/ 目录下创建 SKILL.md 文件(兼容 Cursor Skills 格式):

skills/
└── my-skill/
    └── SKILL.md

Agent 会自动发现技能。模型可通过内置的 load_skill 工具按需加载专业技能指令。

流式事件

from supply_agent.types import AgentEventType

for event in agent.stream("Your question"):
    if event.type == AgentEventType.TOOL_CALL:
        print(f"Calling: {event.data['name']}")
    elif event.type == AgentEventType.MESSAGE:
        print(event.data["content"])

异步 API

result = await agent.arun("Your question")

async for event in agent.astream("Your question"):
  ...

支持的模型

通过 OpenRouter 可使用任意支持的模型,例如:

  • google/gemini-2.5-flash(默认)
  • anthropic/claude-sonnet-5
  • openai/gpt-4o
  • google/gemini-2.5-pro-preview
  • meta-llama/llama-4-maverick

完整列表见 OpenRouter Models

环境变量

变量 说明 默认值
OPENROUTER_API_KEY OpenRouter API 密钥 (必填)
OPENROUTER_MODEL 默认模型 google/gemini-2.5-flash
OPENROUTER_TIMEOUT_SECONDS 单次模型请求超时秒数 120
AGENT_MAX_ITERATIONS 最大循环次数 20
AGENT_TEMPERATURE 生成温度 0.7
SKILLS_DIR Skills 目录 skills
LOGS_DIR Agent 运行日志目录 logs
LOG_ENABLED 是否写入运行日志 true

find_agent 本地:FIND_AGENT_TIMEOUT_SECONDS(默认 600,即 10 分钟)由 agents/find_agent/runtime.py 读取,不属于通用 supply_agent 配置。

运行日志与可视化

每次 agent.run() 会在 logs/ 下写出:

文件 说明
run_<agent>_<id>.log 人类可读的完整日志
run_<agent>_<id>.jsonl 结构化事件流(推荐用于可视化)

运行结束后会自动:生成 .html 可视化页 → 上传 .log / .jsonl / .html 到 OSS(supply_agent/<agent_name>/)→ 写入 MySQL oss_logs

可用 LOG_OSS_UPLOAD_ENABLED=false 关闭上传。

事件类型:run_startllm_inputllm_output(含 reasoning)→ tool_call(完整入参/返回)→ … → run_end

手动生成可视化页面:

# 无参数:为 logs/ 下全部运行生成可视化页面
python scripts/visualize_run.py

# 指定某次运行
python scripts/visualize_run.py logs/run_20260714_134901_9daf6fe1.jsonl

# 最新一次运行,并打开浏览器
python scripts/visualize_run.py --latest --open

# 安装后也可用
supply-visualize --open

页面按步骤展示:LLM 输入(messages / tools)、思考过程、模型输出、工具调用的输入与输出。

全局分类树(Web)

后端 FastAPI(端口 8080)一次性返回 global_tree_category 整棵树;前端在仓库根目录 web/(Vue 3)。

# 后端 API
.venv/bin/python -m api
# 或: .venv/bin/supply-api

# 前端(另开终端)
cd web && npm install && npm run dev
  • API: GET http://127.0.0.1:8080/api/category-tree
  • 前端: http://127.0.0.1:5173 (开发代理 /api → 8080)
  • 默认展开 3 层,可选择展开层数,支持节点手动展开/收起

登录与权限

Web 控制台使用本地账号和服务端 Session。系统包含两个固定角色:

  • admin:全部页面和 API 权限,并可在“用户管理”中创建、禁用和重置账号;
  • user:仅可访问“全局需求地图”和“需求汇总”及其只读 API。

每次成功登录都会创建一条独立、不可刷新的会话 Token 记录,同一账号可同时在任意数量 的浏览器或设备登录。退出当前设备只删除当前 Token;管理员修改账号角色、禁用账号或 重置密码时,会统一使该账号的旧 Token 失效。

首次部署前先执行数据库迁移,并通过环境变量创建初始管理员:

alembic upgrade head
export AUTH_BOOTSTRAP_ADMIN_USERNAME=admin
export AUTH_BOOTSTRAP_ADMIN_PASSWORD='replace-with-a-strong-password'
python -m api

初始管理员只会在该用户名不存在时创建,修改环境变量不会重置已有密码。生产 HTTPS 环境必须设置 AUTH_COOKIE_SECURE=true

运行测试

pytest

License

MIT

SupplyAgent 业务框架设计

1. 项目定位

SupplyAgent 是一个面向平台内容供给的需求汇总工具。

它要解决的核心问题不是“从数据中找出若干热门词”,而是:

汇总来自不同渠道、不同时间尺度和不同验证阶段的需求信号,将其统一挂靠到一棵全局分类树上,形成一张可追溯、可解释、可持续反馈的需求关系图,最终输出数百条平台级需求。

最终结果同时服务于两种业务视角:

  1. 从全局分类树和关系图观察平台需求版图、层级、覆盖和交织关系。
  2. 从排序后的需求清单直接开展内容发现、内容生产、供给调度和效果验证。

项目最终交付的不是一张扁平需求表,而是:

  • 一棵稳定的全局分类树;
  • 数百条挂靠在树上的平台需求;
  • 需求与多个树节点之间的关系线;
  • 需求背后的多维数据证据;
  • 需求与内容、线上表现之间的反馈闭环。

2. 对现有项目业务结构的理解

当前项目已经形成了需求汇总的基础业务链路。

2.1 上游需求池

项目从上游策略需求池接收不同类型的需求信号。目前已经体现出的主要维度包括:

  • 外部热度;
  • 平台持续热度;
  • 平台去年同期热度;
  • 平台近期供需缺口;
  • 近 7 日真实 ROV;
  • 近 7 日真实 VOV。

前四类数据主要回答“什么需求可能值得做”,属于先验信号。

真实 ROV、VOV 主要回答“需求被内容承接并上线后是否真的有效”,属于后验反馈。

2.2 全局分类树

global_tree_category 所表达的是一棵统一的全局语义分类树,而不是多棵相互独立的人物树、事件树或情感树。

分类树从左向右按层级展开,例如:

L1        L2        L3          L4          L5
知识  →   历史  →   历史时期  →  古代史

事件  →   社会事件  →  人物故事  →  个人经历
                              →  名人故事

树上的正式节点是稳定、抽象、可治理的业务分类。具体的人物、事件、情感和需求表达,不一定都要成为正式树节点。

2.3 需求词归类

上游需求中的词语或短语会被挂靠到全局分类树的合适节点上。

归类的业务意义是为需求建立稳定坐标,使需求可以:

  • 沿树向上汇总;
  • 在同类需求间比较;
  • 观察不同分支的需求覆盖;
  • 计算分类节点在不同信号维度下的强度;
  • 支撑后续平台需求生成。

2.4 分类节点强度

当前项目会把需求词级别的数据向分类树节点及其祖先聚合。

因此分类树不仅承担知识组织,还承担需求统计和强度观察:

  • 叶子或挂载节点反映局部、具体需求;
  • 中间节点反映某个业务方向的整体强度;
  • 高层节点反映平台需求版图中的大方向。

2.5 平台需求生成

现有需求生成 Agent 已经体现了以下层次:

来源维度 → 整体方向 → 汇总事件 → 原始需求名称

这个结构可以作为初始生成方式,但最终业务模型需要进一步升级为:

全局分类树 + 需求节点 + 跨分支关系 + 多维证据 + 线上反馈闭环

3. 核心设计原则

3.1 一棵树,而不是多棵业务树

全局分类树是整个系统唯一的正式分类骨架。

人物、事件、情感、知识、历史、行为等概念分布在同一棵树的不同分支中。平台需求通过同时连接多个分支,表达真实用户兴趣的交织关系。

3.2 以树确定归属,以图表达关系

树内父子关系负责表达:

  • 层级;
  • 上下位关系;
  • 统计汇总路径;
  • 业务覆盖;
  • 正式分类治理。

图上的跨节点关系负责表达:

  • 一个需求同时涉及哪些分类分支;
  • 人物、事件、行为、情感等元素如何共同组成需求;
  • 不同需求之间是否存在重合、包含、相似或关联;
  • 哪些上游信号共同支持同一个需求。

3.3 每条需求必须挂树

每条正式平台需求至少挂靠一个真实存在的分类树节点。

一条需求可以同时挂靠多个正式树节点。多个挂靠点共同定义需求,不要求在业务语义上强制区分主次。

例如同一条需求可以同时挂靠:

  • 人物故事;
  • 个人经历;
  • 历史解读;
  • 诗词创作;
  • 与具体战争时期相关的历史分类。

如果某些统计、展示或资源分配场景必须使用唯一口径,可以额外指定一个“统计主归属节点”。统计主归属只用于避免重复计数,不代表其他挂靠点在业务语义上更次要。

3.4 用户意图优先

需求的多个挂靠点应共同表达“用户为什么对此感兴趣”,而不是只机械选择最具体的对象节点。

例如需求:

毛泽东在抗日战争和解放战争时期的诗词创作

可能涉及:

  • 人物对象:毛泽东;
  • 历史背景:抗日战争、解放战争;
  • 行为:写诗、诗词创作;
  • 内容意图:人物经历、作品背景、历史解读。

需求可以同时挂靠“人物故事”“个人经历”“历史解读”或与诗词创作相关的正式节点,具体取决于上游数据实际支持了哪些用户意图。

“毛泽东”“抗日战争”“解放战争”“诗词创作”等相关语义通过多个挂靠点、需求元素和关系线共同表达。

3.5 数据驱动为主,自由推演为辅

平台需求必须主要来源于上游数据。

建议的总体比例是:

  • 80%~90% 为上游数据直接支持的核心需求;
  • 10%~20% 为基于已有数据、节点和关联关系形成的邻近机会需求。

自由推演不得:

  • 凭空创造没有来源的人物、事件、情感或主题;
  • 用模型常识替代上游证据;
  • 将弱关联包装成确定关系;
  • 与数据支持的需求混淆展示。

推演需求必须单独标记,并能说明它是从哪些已有数据和关系扩展而来。

3.6 原始事实与语义推断分离

上游目前主要提供“关联”关系。

系统必须永久保留原始“关联”边,不得覆盖或篡改。

系统可以在有依据时,把普通关联扩展解释为新的语义关系,例如:

  • 人物参与事件;
  • 事件发生于某个历史时期;
  • 人物在某个时期进行诗词创作;
  • 作品表达某种精神或情感;
  • 某个历史背景影响作品主题。

所有推断语义关系必须附带:

  • 推断说明;
  • 原始关联证据;
  • 数据来源;
  • 置信度;
  • 是否经过人工确认;
  • 创建或更新时间。

证据不足时继续保留“关联”,不强行解释。


4. 整体业务图结构

整张图由六类核心业务对象组成:

  • 分类树节点;
  • 细节元素;
  • 视频或内容实例;
  • 明确语言观点与长段讨论;
  • 平台需求;
  • 证据、状态与反馈。

4.1 分类树节点

正式、稳定、可治理的分类节点。

分类树节点之间保留原始父子关系,并从左向右按层级展开。

4.2 需求元素节点

从上游需求中识别出的具体业务元素,例如:

  • 人物:毛泽东;
  • 历史事件:抗日战争、解放战争;
  • 行为:写诗;
  • 作品或内容:战争时期诗词;
  • 情感或精神:革命乐观主义、家国情怀。

需求元素首先来自上游数据。系统只负责归一别名、识别类型并保留来源。

4.3 平台需求节点

平台需求是最终业务输出的核心实体,不等同于某个分类节点,也不等同于某个原始需求词。

例如:

毛泽东在抗日战争时期创作过哪些诗词

毛泽东的战争诗词如何反映当时的历史环境

从抗日战争到解放战争,毛泽东诗词主题发生了什么变化

这些需求可以共同涉及“毛泽东、抗日战争、解放战争、写诗”等元素,但它们表达的是不同用户意图,因此应当是不同的平台需求节点。

4.4 内容节点

能够承接某条需求的真实内容,包括:

  • 已有内容;
  • 搜索发现的内容;
  • 新生产的内容;
  • 已上线并获得真实反馈的内容。

需求和内容之间允许多对多关系:

  • 一条需求可以由多条内容承接;
  • 一条内容也可能同时承接多个需求。

但需要区分内容的主需求和辅助需求,避免效果归因失真。

4.5 证据与状态节点

记录需求在各个独立维度上的证据、状态和变化,包括:

  • 外部热度;
  • 平台持续热度;
  • 去年同期热度;
  • 近期供需缺口;
  • 真实 ROV、VOV;
  • 样本量;
  • 数据日期;
  • 来源可信度;
  • 生命周期状态;
  • 推演标记;
  • 风险或抑制原因。

4.6 分类节点下的证据子图

正式分类树不是信息下钻的终点。

每一个分类节点下面,还可以继续挂载与它相关的细节元素、真实视频和语言化内容,形成一套“节点下证据子图”:

正式分类节点
   ↓
细节元素
   ↓
真实视频实例
   ↓
明确语言观点或问题
   ↓
长段讨论
   ↓
平台需求

这里的“继续下钻”是产品浏览和证据展开,不是继续增加正式分类树层级。

元素、视频、短观点和长段讨论不应被强行定义成 L6、L7、L8 分类节点。它们挂在正式分类节点下面,但属于不同类型的业务对象。

这样可以同时保证:

  • 全局分类树结构稳定、纯净;
  • 节点可以持续承载越来越丰富的细节信息;
  • 用户能够从抽象分类一路下卷到真实内容;
  • 平台需求能够追溯到具体视频和语言证据;
  • 真实内容表现能够逐层回流到元素、需求和分类节点。

4.7 细节元素

细节元素是分类节点下面更具体的语义索引。

例如某个“人物故事”或“历史人物”分类节点下面,可以挂载:

  • 毛泽东;
  • 抗日战争;
  • 解放战争;
  • 写诗;
  • 诗词创作;
  • 战争时期诗词;
  • 革命乐观主义;
  • 家国情怀。

元素来自真实上游数据、视频解析、内容标签或人工治理。

一个元素可以:

  • 挂在多个正式分类节点下;
  • 与其他元素保留原始“关联”关系;
  • 连接多条真实视频;
  • 被多个平台需求共同引用;
  • 根据视频数量、出现次数和线上表现形成元素强度。

分类节点下的元素列表应展示:

  • 元素名称;
  • 元素类型;
  • 出现次数;
  • 关联视频数量;
  • 支持的平台需求数量;
  • 线上表现摘要;
  • 数据来源;
  • 更新时间。

4.8 视频实例

元素可以继续下钻到具体视频。

视频不是单纯的播放链接,而是需求提取和效果反馈的事实实例。每条视频应尽量保留:

  • 视频标题;
  • 视频链接;
  • 作者和发布时间;
  • 视频转写或内容描述;
  • 命中的分类节点;
  • 命中的细节元素;
  • 对应的平台需求;
  • 点赞、评论、分享、收藏等表现;
  • 真实 ROV、VOV;
  • 内容质量或解析可信度;
  • 是否属于主承接内容。

同一条视频可以连接多个元素和需求,但需要区分主要承接和辅助承接。

4.9 明确语言观点

视频解析后,可以从内容中提取能够独立表达的短语言单元,例如:

  • 一个明确事实;
  • 一个观点;
  • 一个判断;
  • 一个问题;
  • 一句可用于需求命名的话;
  • 一条用户容易理解和传播的内容结论。

例如:

战争环境并未中断诗词创作,反而强化了作品的历史表达。

毛泽东为什么在战争时期持续进行诗词创作?

短语言单元可以用于:

  • 快速理解视频贡献了什么;
  • 比较不同视频是否表达同一观点;
  • 形成需求候选名称;
  • 聚合同义需求;
  • 支撑需求关系说明。

所有语言提取都要保留对应视频、原始片段和提取说明,避免模型生成的语言脱离真实内容。

4.10 长段讨论

多个视频、观点和元素还可以进一步形成长段讨论。

长段讨论用于表达短句无法承载的内容,例如:

  • 一个问题的完整背景;
  • 多个视频观点之间的共同点和差异;
  • 历史过程和人物行为之间的联系;
  • 一个需求为什么值得形成;
  • 某个需求存在什么争议;
  • 内容供给可以从哪些角度展开;
  • 线上反馈为什么支持或否定该需求。

例如可以围绕以下主题形成讨论:

从抗日战争到解放战争,毛泽东的诗词既记录时代环境,也呈现政治理想、个人情感和历史判断。不同视频分别提供作品背景、创作动机、表达方式和受众理解方面的证据。

长段讨论不是脱离数据的自由文章。它必须引用实际元素、视频和明确观点,并标识:

  • 讨论依据;
  • 主要证据;
  • 推断内容;
  • 不确定点;
  • 支持或关联的平台需求。

4.11 节点下钻的产品形态

用户从全局分类树继续下卷时,建议按以下顺序展开:

分类节点概览
→ 高频或高价值元素
→ 元素关联的视频
→ 视频转写与明确观点
→ 多视频形成的长段讨论
→ 从证据中提取的平台需求
→ 需求对应的线上验证结果

每一级都要能够返回上一级,并保留:

  • 来源;
  • 出现次数;
  • 贡献度;
  • 时间;
  • 置信度;
  • 真实线上表现;
  • 与需求之间的关系。

节点下钻同时服务两个目的:

  1. 展示:让业务人员理解这个分类节点下具体有什么。
  2. 提取:让系统从元素、视频和语言证据中发现、生成、合并和验证需求。

5. 图中的关系类型

5.1 树内父子关系

正式分类树原有的层级关系,是全图最稳定的结构。

5.2 需求多挂靠关系

每条平台需求可以同时挂靠多个正式分类节点。每条挂靠关系都应记录:

  • 挂靠节点;
  • 挂靠理由;
  • 支持该挂靠的上游数据;
  • 关系置信度;
  • 是否属于正式挂靠或待审核挂靠;
  • 生效和更新时间。

多个挂靠点共同表达需求的完整语义和跨分支交织关系。

例如“毛泽东的战争诗词如何反映当时的历史环境”可以同时挂靠:

  • 历史解读;
  • 人物故事;
  • 个人经历;
  • 与诗词创作相关的正式节点;
  • 与抗日战争、解放战争所处历史时期相关的正式节点。

当主题统计需要避免重复计数时,可以为需求设置一个“统计主归属节点”,或者按明确的分摊规则将需求计入多个节点。该统计属性与业务挂靠关系分开管理。

5.4 原始关联关系

来自上游数据的事实关系,关系类型统一为“关联”。

原始关联边必须保留完整的来源和时间信息。

5.5 推断语义关系

系统根据多个原始关联、树上位置和上下文推断出的补充关系。

推断边只用于:

  • 增强解释;
  • 帮助聚类;
  • 提供低权重排序增益;
  • 辅助发现邻近机会需求。

推断边不能取代原始关联边。

5.6 需求与内容关系

表达内容承接了哪个需求,以及承接程度:

  • 主承接;
  • 辅助承接;
  • 部分覆盖;
  • 待验证;
  • 已上线;
  • 已形成有效样本。

5.7 内容表现回流关系

表达某批内容的真实线上表现如何反向影响需求强度。

5.8 分类节点与元素关系

表达某个细节元素下挂在哪些正式分类节点下。

该关系应记录元素为何属于该分类、来源和出现次数。一个元素允许同时下挂多个分类节点。

5.9 元素与视频关系

表达某条视频包含、体现或讨论了哪些元素。

上游只有“关联”时保留原始关联;需要扩展为“人物出现、事件涉及、行为发生、观点表达”等语义时,必须附带说明和置信度。

5.10 视频与语言关系

表达明确观点、问题和长段讨论是从哪些视频或视频片段中提取出来的。

语言内容必须能回看原始视频或转写依据。

5.11 语言与需求关系

表达某个明确观点、问题或长段讨论支持、形成或验证了哪些平台需求。

同一个需求可以由多个语言证据共同支持,同一个语言观点也可以被多个需求引用。


6. 需求汇总业务流程

整体不是一次性的线性任务,而是持续循环的业务系统。

上游需求信号
    ↓
数据归一与来源保留
    ↓
需求元素识别
    ↓
挂靠全局分类树
    ↓
保留原始关联并构建关系图
    ↓
连接真实视频并解析内容
    ↓
提取明确观点和长段讨论
    ↓
合并为唯一平台需求
    ↓
计算先验需求强度
    ↓
形成主题方向与需求池
    ↓
搜索或生产承接内容
    ↓
内容上线并产生真实表现
    ↓
归因到平台需求
    ↓
更新需求验证强度与生命周期
    ↓
影响下一周期的需求排序和内容供给

6.1 数据归一

统一处理:

  • 同义词;
  • 简称与全称;
  • 人物别名;
  • 事件的不同表达;
  • 错别字;
  • 中英文表达;
  • 时间和周期口径。

归一不等于覆盖原始数据。每个标准表达都必须能追溯到原始名称和来源。

6.2 意图挂树

优先挂靠现有正式树节点。

如果现有树无法准确表达需求:

  1. 挂到当前最可靠的上级节点;
  2. 标记表达缺口;
  3. 进入候选节点治理区;
  4. 由人工审核是否需要增加正式节点。

Agent 不直接修改正式分类树。

6.3 需求合并

多种来源命中同一个需求时,不按来源拆成重复需求,而是合并为一个平台需求节点。

建议以以下组合判断需求是否相同:

核心用户意图 + 关键对象 + 适用范围或约束

例如:

  • “毛泽东抗战时期写的诗”
  • “毛泽东在抗日战争阶段创作的诗词”

可以合并为同一平台需求,并保留两个原始表达。

但下面两条不应简单合并:

  • “毛泽东在抗日战争时期创作过哪些诗词”
  • “毛泽东的抗战诗词表达了什么情感”

前者关注作品事实,后者关注作品解读,用户意图不同。

6.4 少量邻近推演

只有在已有数据能够支持时,才允许生成邻近机会需求。

例如上游同时反复出现:

  • 毛泽东;
  • 抗日战争;
  • 解放战争;
  • 诗词创作;

系统可以提出:

从抗日战争到解放战争,毛泽东诗词主题发生了什么变化

但必须说明:

  • 它由哪些上游关联组合而来;
  • 上游是否直接出现过该完整需求;
  • 哪些部分属于模型推断;
  • 当前属于核心需求还是机会需求。

6.5 从视频和语言证据中提取需求

需求不仅可以直接来自上游需求名称,也可以从节点下挂的真实内容中被发现。

提取过程应遵循:

  1. 从分类节点下的高频、高表现元素开始;
  2. 找到承载这些元素的真实视频;
  3. 从视频转写和解析结果中抽取明确事实、观点和问题;
  4. 对多个视频的语言单元进行归并和比较;
  5. 形成有真实内容依据的需求候选;
  6. 将候选需求挂回一个或多个正式分类节点;
  7. 与已有平台需求去重或合并;
  8. 标记数据来源、视频证据和推断部分。

例如,“毛泽东、抗日战争、解放战争、诗词创作”这些元素分别在多条视频中共同出现,并形成以下语言证据:

  • 毛泽东为何在战争时期持续进行诗词创作;
  • 战争环境如何影响诗词主题;
  • 抗日战争和解放战争时期的作品表达有何变化。

系统可以据此提取平台需求,但不能只凭模型常识生成。每一条需求都必须能够回到具体视频和语言证据。


7. 需求—内容—线上表现闭环

真实线上反馈不是普通的附属指标,而是需求汇总系统的核心后验闭环。

平台需求
   ↓
找到或生产内容
   ↓
内容上线和分发
   ↓
获得真实线上表现
   ↓
校正并归因到需求
   ↓
更新需求强度
   ↓
调整下一周期供给

7.1 建立需求与内容的映射

如果需求和内容之间没有明确映射,线上表现就无法准确回流。

每条上线内容至少需要说明:

  • 主要承接哪个平台需求;
  • 是否辅助承接其他需求;
  • 对需求的覆盖程度;
  • 内容上线时间;
  • 当前是否形成有效样本。

7.2 线上表现不能直接等同于需求表现

内容表现还会受以下因素影响:

  • 内容本身质量;
  • 标题、封面和表达方式;
  • 分发流量;
  • 发布时间;
  • 平台环境;
  • 同类内容竞争;
  • 供给数量;
  • 样本量是否充足。

因此需要把真实表现经过归因校正后,再反向更新需求强度。

不能因为一条质量较差的内容表现不好,就直接判定需求不存在。

7.3 反馈影响需求强度

真实线上反馈应当能够:

  • 验证一个新需求是否真实成立;
  • 提高持续表现良好需求的强度;
  • 降低持续表现较差需求的优先级;
  • 判断某个需求是否已经过度供给;
  • 发现先验热度不高但真实表现很好的潜在需求;
  • 影响分类树中相关节点的整体强度;
  • 影响下一周期的主题配额和供给策略。

8. 需求强度模型

需求不能只保留一个不可解释的总分。

每条需求应至少保留“四类分数 + 一个综合等级”。

8.1 先验需求分

回答:

在内容上线验证之前,这个需求有多值得尝试?

主要来源:

  • 外部热度;
  • 平台近期供需缺口;
  • 平台持续热度;
  • 去年同期或周期性热度;
  • 上游来源数量;
  • 样本量和数据时效性。

这些维度应保持正交,分别展示,然后再形成先验综合判断。

8.2 线上验证分

回答:

内容实际承接该需求后,这个需求是否真实成立?

主要来源:

  • 真实 ROV;
  • 真实 VOV;
  • 消费、互动或转化表现;
  • 多条内容的一致性;
  • 多个周期的稳定性;
  • 归因校正结果。

8.3 关系增益分

回答:

图上的关系是否进一步增强了该需求成立的可能性?

关系增益只作为低权重补充,不能代替原始数据。

8.4 风险抑制分

主要风险包括:

  • 后验效果持续较差;
  • 数据维度之间明显冲突;
  • 样本量不足;
  • 数据过期;
  • 内容承接失败;
  • 同类需求过度重复;
  • 某个主题过度集中;
  • 需求主要来自自由推演;
  • 关系推断置信度较低。

8.5 综合需求强度

业务上可以将其理解为:

最终需求强度
= 先验需求强度
+ 经归因校正的线上验证强度
+ 低权重关系增益
- 风险抑制

具体权重不应在业务框架阶段固定死,应根据历史验证逐步校准。

无论最终采用什么权重,都必须能够展开查看各个组成部分,避免形成黑盒分数。


9. 需求生命周期

需求是动态实体,不是一经生成就永久有效。

建议设置以下生命周期状态。

9.1 新机会

先验信号明显,但尚未有足够内容和线上反馈。

9.2 待验证

已经找到或安排了承接内容,正在等待有效线上样本。

9.3 增长需求

先验信号增强,或者真实线上表现持续改善。

9.4 已验证需求

已经由足量内容、有效样本或多个周期的表现证明。

9.5 观察需求

存在一定信号,但样本较少、数据冲突或结论不稳定。

9.6 衰退需求

需求热度、真实表现或用户兴趣持续下降。

9.7 抑制需求

经过验证后确认当前不值得继续投入,或供给已经明显过量。

9.8 周期需求

当前强度下降,但预计会在特定日期、节日、纪念日或社会周期重新激活。

9.9 新老需求公平

没有后验数据不等于后验表现差。

为了避免旧需求永久占据高位:

  • 新需求主要依据先验分进入探索区;
  • 老需求的历史表现需要时间衰减;
  • 每个主题保留一定探索额度;
  • 只有形成有效样本后才提高后验权重;
  • 已验证需求也要接受持续复验。

10. 主题方向与数百条需求的组织

最终输出采用两层结构。

10.1 上层:主题方向

主题方向不是另建一棵脱离现有分类树的新树。

它应主要来自:

  • 全局分类树的中高层节点;
  • 某个树分支下的需求聚类;
  • 多个相关分支共同形成的稳定业务方向。

例如:

  • 历史人物故事;
  • 历史人物个人经历;
  • 战争历史与人物;
  • 历史人物作品解读;
  • 历史事件影响;
  • 人物精神与情感。

每个主题方向应包含:

  • 主题名称;
  • 对应的分类树范围;
  • 覆盖的用户意图;
  • 需求数量;
  • 各生命周期数量;
  • 整体需求强度;
  • 主要数据来源;
  • 趋势;
  • 需求集中度;
  • 探索需求占比。

10.2 下层:平台需求

每条平台需求应包含:

  • 唯一需求 ID;
  • 标准需求名称;
  • 原始需求表达;
  • 多个正式挂靠节点;
  • 可选的统计主归属节点;
  • 每个挂靠点的理由和证据;
  • 关联需求元素;
  • 原始关联边;
  • 推断语义边及说明;
  • 所属主题方向;
  • 各维先验信号;
  • 线上验证结果;
  • 关联内容及归因情况;
  • 需求强度;
  • 生命周期;
  • 是否属于推演需求;
  • 风险和抑制原因;
  • 数据更新时间;
  • 强度变化历史。

10.3 需求数量分配

数百条需求不按主题平均分配,也不能完全被少数热门主题占满。

采用:

数据驱动为主,最低覆盖和最高集中度约束为辅。

具体原则:

  • 强信号主题可以拥有更多需求;
  • 弱主题允许较少需求;
  • 无真实信号的主题可以暂时为空;
  • 重要主题保留最低覆盖;
  • 单一主题设置最高集中度;
  • 每个主题保留少量探索位;
  • 推演需求总量保持在受控范围。

11. 最终展示方式

最终使用同一份需求数据提供两个入口。

11.1 全局树图入口

从左向右浏览:

L1 → L2 → L3 → L4 → L5 → 挂载需求

树图中需要保留:

  • 原有树内父子线;
  • 分类节点的需求数量和强度;
  • 平台需求与多个正式树节点之间的挂靠线;
  • 可选的统计主归属标记;
  • 跨分支关联线;
  • 原始关联与推断关系的区别;
  • 需求与内容的承接关系;
  • 线上反馈回流关系。

用户点击某条需求后,应能看到完整追溯链路:

上游数据
→ 原始需求表达
→ 标准化过程
→ 多个挂靠节点及各自理由
→ 关联元素
→ 原始及推断关系
→ 关联内容
→ 线上表现
→ 需求强度变化

11.2 需求清单入口

以业务执行为目的,支持按以下维度查看:

  • 主题方向;
  • 需求强度;
  • 生命周期;
  • 多个挂靠节点;
  • 可选的统计主归属节点;
  • 数据来源;
  • 是否已经验证;
  • 是否已经有内容承接;
  • 是否存在供需缺口;
  • 是否属于推演需求;
  • 更新时间。

树图入口和清单入口指向同一批需求实体,不维护两套独立结果。


12. 分类树治理

现有分类树是基础权威骨架,但不是永远不变。

12.1 正式节点

已经审核并进入全局分类树,可以作为需求挂靠节点和统计归属节点。

12.2 候选节点

当大量上游需求反复出现,而现有树无法准确表达其核心意图时,可以提出候选节点。

候选节点必须说明:

  • 哪些需求无法被现有节点准确表达;
  • 当前临时挂靠在哪里;
  • 出现频率和持续周期;
  • 预期父节点;
  • 与现有节点的差异;
  • 新增后能解决什么业务问题。

12.3 人工审核

Agent 只能提出候选节点,不能直接修改正式树。

人工审核后可以:

  • 接受为正式节点;
  • 与现有节点合并;
  • 继续观察;
  • 拒绝新增。

13. 业务质量控制

13.1 可追溯

任何平台需求必须能够追溯到上游数据。

13.2 可解释

必须解释:

  • 为什么形成该需求;
  • 为什么挂在这些节点;
  • 为什么与其他表达合并或不合并;
  • 为什么获得当前强度和生命周期;
  • 线上反馈如何影响了它。

13.3 防止过度聚合

一个平台需求应表达一个清晰的用户意图。

不能将大量弱相关人物、事件、作品和情感塞入一个“大合集需求”。

13.4 防止过度拆分

同一用户意图仅因为来源、措辞或时间不同,不应被拆成多个重复需求。

13.5 防止自由推演失控

所有推演需求必须:

  • 有数据起点;
  • 有推演路径;
  • 有置信度;
  • 有独立标识;
  • 有比例限制;
  • 可以被人工拒绝。

13.6 防止后验误判

线上反馈必须经过归因和样本校正,避免将内容质量、流量不足或供给不足误认为需求无效。


14. 项目业务模块的总体框架

从业务职责看,整个项目可以划分为九个部分。

14.1 信号接入

负责接收内外部、近期远期、周期性和真实反馈数据。

14.2 需求理解

负责标准化原始表达、识别需求元素、保留来源和原始关联。

14.3 分类树挂靠

负责为需求建立正式分类坐标,并发现分类树表达缺口。

14.4 树图汇总

负责把分类树、需求元素、需求节点和关系边组织成统一业务图。

14.5 元素与内容下钻

负责展示分类节点下的细节元素,并将元素连接到真实视频、内容标签、转写和线上表现。

14.6 语言观点与讨论提取

负责从真实视频中提取明确事实、观点、问题和长段讨论,并完整保留视频依据和推断说明。

14.7 需求生成与合并

负责形成唯一平台需求,控制需求粒度,避免重复和过度聚合。

14.8 需求决策

负责计算需求强度、生命周期、主题配额和内容供给优先级。

14.9 内容反馈闭环

负责把需求与内容连接起来,并将内容真实线上表现回流到需求和分类节点。

整体业务关系是:

信号接入
   ↓
需求理解
   ↓
分类树挂靠
   ↓
树图汇总
   ↓
元素与视频下钻
   ↓
语言观点与讨论提取
   ↓
需求生成与合并
   ↓
需求决策
   ↓
内容承接和线上反馈
   └──────────────→ 回到需求决策和树图强度

15. 一句话总结

SupplyAgent 的最终业务框架是:

以一棵全局分类树作为稳定骨架,把上游多源需求统一挂树;以平台需求作为独立图节点连接多个分类分支和具体需求元素;以原始数据决定需求是否成立,以低权重语义推断补充关系,以内容真实线上表现持续反向更新需求强度,最终形成几十个主题方向和数百条可执行、可解释、可追溯的平台需求。


16. 业务可视化资料与服务

本次业务框架讨论过程中形成的全部可视化材料统一保存在:

visualization/

目录中同时包含:

  • 业务图结构方案;
  • 从左向右的树图示意;
  • 业务节点和关系模型;
  • 需求—内容—线上表现反馈闭环;
  • 最终主题和平台需求输出结构;
  • 对话过程中的阶段性可视页面;
  • 用户提供的全局分类树局部参考图;
  • 用于本地浏览全部图稿的可视化服务。

目录结构如下:

visualization/
├── README.md
├── run.sh
├── assets/
│   └── tree.jpeg
├── pages/
│   ├── graph-structure-options.html
│   ├── graph-structure-left-to-right.html
│   ├── business-graph-model.html
│   ├── demand-feedback-loop.html
│   ├── final-output-structure.html
│   ├── node-drilldown-evidence-chain.html
│   ├── waiting-business-scope.html
│   ├── waiting-demand-pipeline.html
│   └── waiting-strength-lifecycle.html
└── service/
    └── serve.py

16.1 启动可视化服务

在项目根目录执行:

bash visualization/run.sh

服务默认使用:

http://localhost:8765/

需要指定端口时:

bash visualization/run.sh --port 9000

如果不希望自动打开浏览器:

python3 visualization/service/serve.py

服务只依赖 Python 标准库,不要求安装额外前端依赖。

16.2 图稿与最终业务口径的关系

visualization/pages/ 保留了整个讨论过程,因此早期图稿可能包含后来被修正的中间方案。

最终应以本文档的业务定义为准:

  • 整体只有一棵全局分类树;
  • 分类树从左向右按层级展开;
  • 平台需求是独立的图节点;
  • 一条需求可以同时挂靠多个正式树节点;
  • 多个挂靠点共同定义需求,不强制区分业务主次;
  • 如统计需要唯一口径,可以另设“统计主归属节点”;
  • 上游原始“关联”边永久保留;
  • 推断语义关系必须附带说明、来源和置信度;
  • 内容的真实线上表现必须反向更新需求强度。

16.3 各图稿用途

图稿 主要用途
graph-structure-options.html 记录单一大树、自由图和树骨架关系图三种方案的比较
graph-structure-left-to-right.html 确认整体从左向右的阅读方式
business-graph-model.html 说明分类节点、需求元素、平台需求、关系和证据
demand-feedback-loop.html 说明需求如何找到或生产内容,以及线上表现如何回流
final-output-structure.html 说明主题方向、数百条平台需求和证据卡如何组织
node-drilldown-evidence-chain.html 说明分类节点如何继续下挂元素、视频、明确观点、长段讨论并提取需求
waiting-*.html 保留对话过程中的阶段性页面
assets/tree.jpeg 作为现有全局分类树结构和阅读方式的参考
assets/tree2.png 作为分类节点下挂大量细节元素的参考
assets/case.jpeg 作为元素扩展为明确语言、长段讨论和最终选题的参考

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 留档。

当前系统的核心业务链路是:

ODPS 全局分类与元素
        ↓
MySQL 全局分类树
        ↑
需求池词语 → 归类 Agent → 需求词挂靠
        ↓
四项先验 + 真实 ROV/VOV
        ↓
词级统计 → 分类树节点聚合 → 四维全局排名
        ↓
需求生成 Agent → generated_demand
        ↓
FastAPI → Vue 分类树/热力图/证据下钻

3. 仓库分层

SupplyAgent/
├── supply_agent/       通用 Agent 框架
├── agents/             业务 Agent 和专属工具
├── supply_infra/       MySQL、ODPS、OSS、定时任务
├── api/                FastAPI 查询接口和静态前端托管
├── web/                Vue 3 业务前端
├── scripts/            部署、日志上传、日志可视化脚本
├── visualization/      早期/独立的业务设计可视化材料
├── Dockerfile          前端构建 + Python 运行镜像
├── pyproject.toml      Python 包、依赖和命令入口
└── requirements.txt    另一份运行依赖清单

任务 CLI 已并入 supply_infra/scheduler/jobs/python -m ...)。

模块之间的依赖方向:

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.pyAgent 负责组装:

  • LLMClient;
  • ToolRegistry;
  • SkillRegistry;
  • AgentLoop;
  • AgentLogger。

对外提供:

  • run
  • arun
  • stream
  • astream

loop.py 实现标准 ReAct/Tool Calling 循环:

系统消息 + 对话历史
        ↓
调用 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

输出结构固定为:

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_search_tikhub:调用 TikHub 搜索并保留 search_id/backtrace 分页状态;
  • douyin_user_videos:按作者 sec_uid、排序和游标扩展历史作品;
  • douyin_detail:按 content_id 获取视频详情和可播放地址;
  • get_content_fans_portrait:获取视频点赞用户画像;
  • get_account_fans_portrait:获取作者粉丝画像;
  • batch_fetch_portraits:批量获取视频画像,并可同时获取作者画像;
  • normalize_age_portraits:标准化 50- 等年龄桶及双侧证据;
  • audit_video_discovery_process:结束前审计多词、翻页、扩展、证据和双池分流;
  • qwen_video_analyze:调用千问视频模型解析视频;
  • create_video_discovery_run:创建可追踪的找片运行;
  • record_video_search_page:保存 Agent 自主搜索词、扩展来源、游标与本页结果;
  • batch_save_video_candidate_evaluations:保存证据、评分并分为正式推荐/人工备选/淘汰;
  • query_video_discovery_state:查询搜索树和候选分池;
  • review_video_discovery_candidate:记录用户对推荐或备选的人工选择结果。

搜索和详情接口有约 10 秒的请求间隔限制。

Agent 以需求相关性、老年受众倾向和分享价值的联合目标做判断。系统提示明确区分 “分享量高”和“受众偏老”两类证据,使用视频点赞画像作为内容侧证据、作者粉丝画像 作为账号先验,并对画像缺失或冲突降低置信度。搜索词由 Agent 根据需求、参考标题、 相关点和途中发现的有效标签自主决定;支持多关键词、游标翻页和标签扩展。与需求无关 但老年倾向、分享价值双高的视频保存在人工备选池。

搜索过程持久化到:

  • video_discovery_run:任务输入、意图、状态与计数;
  • video_discovery_search:逐关键词、逐游标页的搜索轨迹;
  • video_discovery_candidate:视频详情、双侧画像、标签、评分、分池与人工审核状态。

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 关键关系

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_diffvov_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。

每个节点统计其整个子树内有需求词挂靠的节点:

节点维度 avg = Σ(挂靠点 avg × count) / Σ(count)
节点维度 count = Σ(count)

结果写入 category_tree_weight

8.5 四维排名分和全局热度

demand_pool/tree_weight.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. 每天 15:00SCHEDULER_TIMEZONE):串行执行全链路 run_supply_pipeline(全局树 → 需求池 → 分级 → 拓展 → 找片 → AIGC 发布)。

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 策略需求池完整流水线

supply_infra.scheduler.jobs.demand_poolsync_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 手动任务入口

python -m 调用(与定时流水线同源):

  • python -m supply_infra.db:初始化数据库;
  • python -m supply_infra.scheduler.jobs.run_supply_pipeline:全链路;
  • python -m supply_infra.scheduler.jobs.demand_pool:需求池同步(含内部阶段);
  • python -m supply_infra.scheduler.jobs.grade_demand_pool:分级(可加 --retry-failed);
  • 以及 global_tree / expand / discover / publish 各步骤模块。

也可通过 API POST /api/scheduler/jobs/{job_id}/run 手动触发。


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 展开:

当前分类节点
  → 细节元素/需求词
  → 真实视频实例
  → 最终选题 JSON

这里展示的是 demand_belong_category 需求词,不是 generated_demand 中由生成 Agent 产出的四层平台需求。

当前前端没有读取或展示 generated_demand 的 API。

11.4 传统分类树页面

CategoryTree.vue 使用 Vue DOM 递归组件展示:

  • 默认展开 3 层;
  • 支持选择展开深度;
  • 支持手动展开和收起;
  • 支持按维度过滤有数据的节点;
  • 支持拖动画布;
  • 支持导出包含数据的独立 HTML;
  • 同样可以下钻需求词、视频和最终选题。

11.5 需求归类过程页面

读取 demand_belong_category_agentoss_logs,按时间倒序显示,点击打开 Agent 运行过程 HTML。


12. 部署和运行

12.1 Python 命令入口

pyproject.toml 注册:

  • supply-api
  • supply-visualize

12.2 本地开发

后端:

python -m api

前端:

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.mdARCHITECTURE.mdagents/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 installnpm ci


16. 建议的后续阅读顺序

如果继续开发,建议按以下顺序进入代码:

  1. supply_infra/scheduler/jobs/demand_pool/:理解需求池同步主业务;
  2. supply_infra/db/models/:理解真实数据对象;
  3. supply_infra/scheduler/jobs/demand_pool/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 展示分类热度与视频证据的业务系统;核心数据流水线已经形成,但多挂靠、最终需求产品化、后验反馈进入综合决策、语义关系图和自动化测试仍未完成。