# DemandAgent 输出调整 MVP 技术方案 ## Summary 这次不是“DemandAgent 对接 ContentFindingAgent”,而是 **DemandAgent 自身输出结构升级**:在不改真实 DB schema 的前提下,核心只调整普通需求池 `demand_content.ext_data`,增加结构化 `evidence_pack`。V1 同时保留两种受控输出模式:`local_json` 用于本地回归,只写本地 JSON;`mysql_demand_content` 用于云端测试服,只写 MySQL `demand_content` 单表。原本会写入 `dwd_multi_demand_pool_di`、`feature_point_data`、`demand_task` 的数据,V1 阶段不真实写入这些表,也不改它们的业务字段结构。 本次 DemandAgent 侧补齐的是上游 exact evidence 基础:它能让需求单携带真实 DB 校验过的 Pattern/itemset/category 证据,但最终链路还要求 ContentFindAgent 读取、继承并沉淀结构化 `source_evidence`,否则最终结果仍无法从 `post_id/aweme_id` exact 回到 Pattern 和分类树节点。 PG Pattern V2 替换更新:正式证据源已经切到 PG `open_aigc.public` 主事实表,`evidence_pack.pattern_source_system` 固定为 `pg_pattern_v2`。MVP 只使用 `pattern_itemset.scope='topic'` 的分类 Pattern;`topic_element`、script、paragraph、group scope 不进入下游 exact 输出,因为它们不能保证每条证据都有 `category_id/category_path` 可回到分类树节点。 改动范围判断:**不改 agent 框架层**,只改 `examples/demand` 这个业务领域实现。`agent/` 框架里的 runner、tool registry、trace store、LLM 调用链路都保持不动。本次即使涉及 `@tool` 参数、`trace_id` 透传,也只是修改 Demand 业务工具和业务输出,不修改通用 Agent 框架机制。 ## 模块改动归类 | 模块 | 是否改 | 原因 | | --- | --- | --- | | `agent/` 框架 | 不改 | 不改 runner、tool registry、trace store、LLM 调用链路;本次只消费现有框架能力,把 Demand 业务输出补充为本地 JSON 和 evidence_pack。 | | `examples/demand/demand_build_agent_tools.py` | 改 | DemandItem 需要新增可选证据引用字段,保留原字段兼容旧能力。 | | `examples/demand/demand.md` | 改 | Prompt 要求 LLM 在创建需求时带上输出证据引用。 | | `examples/demand/run.py` | 改 | 当前真实写库入口集中在这里,需要加本地 JSON 输出模式。 | | `examples/demand/data_query_tools.py` | 改 | 不改 `dwd_multi_demand_pool_di` / `feature_point_data` 业务字段;把现有 Hive 输出拆成可复用的纯映射函数,local 模式只调用映射不写 Hive。 | | `examples/demand/db_manager.py` | 改 | 保留旧函数名作为 PG facade,底层改读 PG Pattern V2,只读补齐 `category/element/itemset/post` 证据。 | | 新增 `examples/demand/pg_pattern_repository.py` | 新增 | PG Pattern V2 只读 repository,统一读取 `pattern_mining_*`、`pattern_itemset*`。 | | 新增 `examples/demand/pattern_builds/pg_pattern_service.py` | 新增 | 保持旧工具返回结构,底层替换为 PG `scope='topic'` Pattern 查询。 | | 新增 `examples/demand/pg_weight_score_builder.py` | 新增 | 从 PG topic 元素/分类聚合本地权重 JSON,替代旧 prepare 产物。 | | 新增 `examples/demand/evidence_pack_builder.py` | 新增 | 专门构造并强校验 `ext_data.evidence_pack`;校验失败的 DemandItem 进入 reject,不进入 `demand_content`。 | | 新增 `examples/demand/local_output_sink.py` | 新增 | 统一本地 JSON sink,稳定输出 7 个本地文件;不进入通用 Agent 框架。 | | 新增 `examples/demand/run_existing_execution_local.py` | 新增 | MVP 测试入口:用已有 `execution_id` 只读 DB,禁止跑写库 prepare。 | ## 输出表改动收敛 虽然 DemandAgent 当前有三类业务结果输出,但本次面向下游证据链的结构性改动只落在 `demand_content`。 | 输出 | 当前业务角色 | 本次是否改业务字段 | MVP 处理方式 | | --- | --- | --- | --- | | `demand_content` | 普通需求池;ContentFindAgent 当前直接领取的需求卡片 | 改 | 在 `ext_data` 中新增 `evidence_pack`,保留原 `reason/desc/type/video_ids` 兼容旧消费。 | | `dwd_multi_demand_pool_di` | 数仓侧需求池同步 / 策略表现参考 | 不改 | 不新增 evidence 字段,不作为下游主证据链载体;MVP 只把原两类策略行镜像到本地 JSON。 | | `feature_point_data` | `全局树` 特征点输出 / 特征表现参考 | 不改 | 和 ContentFindAgent 当前主链路无关;MVP 只保留原字段并镜像到本地 JSON。 | `demand_task` 是任务台账,不算业务结果输出表。本地模式只模拟它的创建和完成状态,方便串起 `demand_task_id`,不改变业务输出结构。 ## V1 阶段开发清单 本清单用于承接“待决策事项”与当前开发执行状态。当前 checkout 中未找到独立的 `待决策事项.md` 文件,因此本节只吸收已经在本方案、`demandagent给下游的迭代需求.md`、真实代码和已拍板沟通中能被证实的事项;未找到原文依据的内容统一放入“待补充”,不擅自替用户补决策。 ### 1. 已拍板并进入 V1 的事项 | 事项 | V1 处理方式 | 真实依据 | | --- | --- | --- | | Pattern 证据源 | 只使用 PG Pattern V2,`pattern_source_system` 唯一合法值为 `pg_pattern_v2` | `pg_pattern_repository.py` 只读 PG `pattern_mining_*` / `pattern_itemset*`;`evidence_pack_builder.py` 固定 `PATTERN_SOURCE_SYSTEM = "pg_pattern_v2"` | | Pattern scope | V1 只输出 `pattern_itemset.scope='topic'` | `evidence_pack_builder.py` 校验 `PATTERN_ITEMSET_SCOPE = "topic"`;`pg_pattern_repository.py` 定义 `TOPIC_SCOPE = "topic"` | | 旧 MySQL Pattern | 删除弃用,不作为任何新版 evidence 来源 | `demandagent给下游的迭代需求.md` 已明确旧 `topic_pattern_*` 链路删除弃用;当前 PG repository 不读取旧 `topic_pattern_*` | | 下游主载体 | `demand_content.ext_data.evidence_pack` 是给下游的主证据包 | `run.py` 构造 `ext_data.evidence_pack`;`mysql_demand_content_sink.py` 入库前强校验该字段 | | LLM 与证据真实性边界 | LLM 只产出候选 `evidence_refs`;最终 `evidence_pack` 必须由代码 DB 强校验补齐 | `run.py` 读取候选 `evidence_refs` 后调用 `build_evidence_pack()`;`evidence_pack_builder.py` 失败返回 reject | | 云端 V1 输出 | 开发/测试服可以真实写云端 MySQL 单表 `demand_content`,但只写这一张表 | `run_existing_execution_mysql.py` 注释和入口约束;`mysql_demand_content_sink.py` 只执行 `INSERT/UPDATE demand_content` | | 本地 V1 输出 | local 回归仍保留 `local_json`,产物写入 `examples/demand/test_output_data/{run_id}` | `run_existing_execution_local.py` 与 `local_output_sink.py` | | Agent 框架边界 | 不改 `agent/` 通用框架;只改 `examples/demand` 业务域 | 本方案“改动归属”已定义;代码改动集中在 `examples/demand` | | 下游消费口径 | 当前验收按本输出契约,不按尚未适配完成的 ContentFindAgent 旧 scheduler 代码验收 | 已拍板:下游代码未完成最新结构适配,本轮按 `demand_output_improve_plan.md` 验收 | ### 2. 需按默认值推进的事项 这些不是新决策,而是当前代码已经存在的默认值或运行约束;V1 执行时按代码默认推进,后续如产品要改,需要单独决策。 | 事项 | 默认值 / 默认行为 | 代码位置与含义 | | --- | --- | --- | | PG execution 入参 | CLI 必须显式传已有 `pattern_mining_execution.id`;测试默认样本使用已验证的 `581` | `run_existing_execution_mysql.py` / `run_existing_execution_local.py` 都要求 `--execution-id` | | execution 状态 | 只允许 `status='success'` | `run_existing_execution_mysql.py` 启动前调用 `query_execution_for_evidence()` 校验 | | MySQL 写入模式 | `DEMAND_OUTPUT_MODE=mysql_demand_content` 只能通过 `examples.demand.run_existing_execution_mysql` 入口设置 | `run.py` 的 `_require_mysql_entrypoint()` | | 本地 JSON 模式 | `DEMAND_OUTPUT_MODE=local_json` 只能通过 `examples.demand.run_existing_execution_local` 入口设置 | `run.py` 的 `_require_local_entrypoint()` | | ID 语义 | `case_id_type` 固定为 `post_id` | `evidence_pack_builder.py` 构造 `case_id_type="post_id"` | | Pattern case 列表 | `video_ids` 与 `case_ids` 默认等于 DB 校验后的 `matched_post_ids` | `evidence_pack_builder.py` 构造两者;`mysql_demand_content_sink.py` 入库前校验相等 | | decode 兼容字段 | `decode_case_ids` 在 PG-only V1 中默认可为空数组,不参与 Pattern exact 闭合 | `evidence_pack_builder.py` 单独查询并写入;未命中不影响 Pattern itemset 通过 | | 证据状态 | accepted demand 固定为 `source_certainty=db_validated`、`validation_status=passed` | `evidence_pack_builder.py` 构造固定状态;MySQL sink 入库前复验 | | MySQL 单表去重 | 同一 `run_label + name + evidence_pack.source_post_id` 已存在时跳过 | `mysql_demand_content_sink.py` 的 `_row_exists_for_run()` | | MySQL 入库回填 | 插入后用真实自增 `id` 回填 `ext_data.evidence_pack.demand_content_id` | `mysql_demand_content_sink.py` 插入后 update 同一行 | | 单表模式任务 ID | 不建 `demand_task` 时,`demand_task_id` 不能伪装成真实 DB ID;可为 `null` 或运行侧模拟值,语义必须在 run manifest / 日志中说明 | 当前 `run_existing_execution_mysql.py` 调用 `main(..., task_id=None)` | | MySQL entrypoint 工具白名单 | 只开放 `get_frequent_itemsets/get_itemset_detail/create_demand_item/create_demand_items` | `run.py` 的 `_enabled_tools_for_run()` | | 工具返回压缩 | `DEMAND_ITEMSET_DETAIL_MAX_IDS=12`、`DEMAND_ITEMSET_DETAIL_MAX_POST_IDS=20` | `run_existing_execution_mysql.py` 设置默认环境变量 | | LLM 模型解析 | 优先读 `DEMAND_LLM_MODEL`,再读 `ARK_MODEL` / OpenRouter 相关变量 | `run.py` 的 `resolve_model()` | | PG 连接配置 | 优先读 `DEMAND_PATTERN_PG_DSN`,再读 `PGVECTOR_DSN` | `pg_pattern_repository.py` 的 `_resolve_dsn()` | | MySQL 连接配置 | 优先读 `DEMAND_CONTENT_MYSQL_DSN`,否则读 `DEMAND_CONTENT_MYSQL_*`,再 fallback 到 `CONTENT_SUPPLY_DB_*` | `mysql_demand_content_sink.py` 的 `_resolve_mysql_config()` | ### 3. 待补充但不能阻塞 V1 单表验证的事项 这些事项尚未形成可执行决策,或当前真实代码/文档还没有完全闭合。V1 可以继续推进 100 条测试,但必须在交付说明中标明边界。 | 待补充事项 | 当前缺口 | V1 临时处理 | | --- | --- | --- | | 原始 `待决策事项.md` | 当前仓库及 `/Users/samlee/Documents/works` 三层内未找到该文件,无法逐条引用原文 | 本节先按已拍板沟通和现有两份文档吸收;若用户提供文件,需要再做一次逐条 diff | | ContentFindAgent 最新适配 | 下游新版代码尚未完成,不按旧 scheduler 验收 | 只按本契约校验 `demand_content.ext_data.evidence_pack`;CFA 后续需继承并沉淀 `source_evidence` | | 100 条正式批次完成报告 | 代码和云端链路已能写 MySQL,但最终批次需要以云端 SQL 统计为准 | 以 `run_label` 前缀查询数量和逐条 evidence 校验,不用本地 JSON 数量替代 | | 生产任务闭环 | 单表 `demand_content` 不能支撑旧调度、去重、结果回写 | V1 只验收 FH/CFA 可读取需求结构;不验收旧 `server.py` 自动调度 | | 独立 evidence 表 | 当前只写 `ext_data.evidence_pack`,未拆关系表 | V1 不拆表;如 `ext_data` 变大或下游要 SQL join,再进入 V1.1 | | `topic_element` exact 方案 | 真实 PG 样本中存在分类路径缺失,不能保证 exact 回分类树 | V1 不输出;后续必须先补齐分类树节点或定义降级语义 | | PG Pattern V2 页面主事实链接 | 当前 evidence 能回 PG 表,但未定义前端页面 URL 或跨系统跳转 ID | V1 只保证表级 exact;页面级跳转另列需求 | ### 4. V1 明确不处理的事项 | 不处理事项 | 原因 | | --- | --- | | 不恢复旧 MySQL `topic_pattern_*` evidence | 已删除弃用,任何最终证据来自旧 MySQL 都必须 rejected | | 不输出 `topic_element`、script、paragraph、group scope | 不能保证每条证据都有 `category_id/category_path` 回到分类树节点 | | 不改 PG Pattern V2 数据库 schema | DemandAgent 是下游消费方,只读 PG 主事实 | | 不改 `agent/` runner / tool registry / trace store / LLM 框架 | 本次是 Demand 业务域输出协议升级 | | 不写 `demand_task`、Hive/ODPS、旧 Pattern 中间表 | V1 云端只写 MySQL `demand_content` 单表;本地模式只写 JSON | | 不保证旧 ContentFindAgent scheduler 自动闭环 | 旧 scheduler 还需要 `demand_find_task` 和结果表,已超出单表 V1 范围 | | 不把 rejected DemandItem 下发给下游 | rejected 只用于 DemandAgent 排查,不能作为可消费需求 | ### 5. V1 验收清单 | 验收项 | 必须满足 | | --- | --- | | 证据真实性 | 每条 accepted row 的 `evidence_pack.source_certainty='db_validated'` 且 `validation_status='passed'` | | PG-only | `evidence_pack.pattern_source_system='pg_pattern_v2'`,不得出现 `mysql_topic_pattern` 或旧 `topic_pattern_*` 来源 | | itemset 闭合 | `pattern_execution_id/mining_config_id/itemset_ids/itemset_items/support/absolute_support` 均来自同一 PG execution 且 scope 为 `topic` | | post 闭合 | `matched_post_ids` 来自 `pattern_itemset_post`,`source_post_id in matched_post_ids`,`video_ids == case_ids == matched_post_ids` | | 分类树闭合 | `itemset_items[]` 与 `category_bindings[]` 均有 `category_id/category_path` | | 元素证据闭合 | `element_bindings[]` 非空,且来自 PG `pattern_mining_element` | | 写库范围 | 云端 V1 只能写 MySQL `demand_content`;不能写 `demand_task`、Hive、PG Pattern 或旧 MySQL Pattern | | 下游契约 | 给 FH/CFA 的主输入是 MySQL `demand_content` 行或其导出的 `demand_content.json`,不是 `demand_items` 或 `rejected_demand_items` | ## 改动归属:Agent 框架 vs examples/demand 业务域 ### 1. 不涉及 Agent 通用框架的改动 本次 MVP 不修改 `agent/` 通用框架目录。 不改范围包括: - `agent/core/runner.py` - 不改 AgentRunner 执行循环。 - 不改 trace 创建、消息持久化、工具调用调度逻辑。 - `trace_id` 只从现有 runner 结果中读取和透传,不改变框架生成 trace 的方式。 - `agent/tools` / tool registry - 不改工具注册框架。 - 不改 `@tool` 装饰器能力。 - 不改工具 schema 生成机制。 - 本次只是修改 `examples/demand` 下业务工具函数的参数和写出内容。 - `agent/trace` - 不改 trace store。 - 不改 `.trace` 文件结构。 - 只把现有 `trace_id` 写入 DemandAgent 本地输出 JSON,作为业务输出字段。 - LLM 调用框架 - 不改模型选择、LLM client、OpenRouter 调用逻辑。 - 不改 `RunConfig` 的通用字段含义。 结论:本次不是 Agent 框架能力升级,而是 DemandAgent 业务输出协议升级。 ### 2. 仅涉及 examples/demand 业务域的改动 本次实际改动都落在 `examples/demand`,属于 DemandAgent 示例业务域,也就是当前 Demand 生成链路的业务实现层。 当前改动已经完成业务入口、DB 强校验和受控 sink 的 V1 闭环:`demand_build_agent_tools.py` 产出候选 `evidence_refs`,`evidence_pack_builder.py` 用真实 DB 只读校验并补齐 `evidence_pack`。`local_json` 模式只把 passed rows 写入本地 `demand_content.json`,失败项写入 `rejected_demand_items.json`;`mysql_demand_content` 模式只把 passed rows 写入测试服 MySQL `demand_content` 单表,失败项只记日志,不进入下游需求池。 具体包括: - `examples/demand/demand_build_agent_tools.py` - 增强 `create_demand_item` / `create_demand_items`。 - 新增可选 `evidence_refs` 字段。 - 这是业务工具输出结构调整,不是 Agent 工具框架调整。 - `examples/demand/demand.md` - 修改 DemandAgent prompt。 - 要求模型在生成 DemandItem 时带上来源证据引用。 - 这是业务 prompt 约束,不是框架 prompt 系统改造。 - `examples/demand/run.py` - 调整结果写出路径。 - 增加 `local_json` 本地回归模式和 `mysql_demand_content` 测试服单表写入模式。 - 避免 V1 测试时真实写 `demand_task` / Hive / PG Pattern / 旧 MySQL Pattern。 - 这是 DemandAgent 业务 orchestration 改动。 - `examples/demand/data_query_tools.py` - 把 Hive 写入逻辑拆成“构造输出行”和“真实写入”两层。 - 本地模式只调用纯映射生成 `dwd_multi_demand_pool_di.json` / `feature_point_data.json` 镜像。 - 不向这两张表增加 `evidence_pack`,不改变它们的业务字段。 - `examples/demand/evidence_pack_builder.py` - 新增 `evidence_pack` 构造逻辑。 - 从 PG Pattern V2 真实 DB 只读补齐 itemset/category/element/post 证据。 - 这是 DemandAgent 输出字段补齐逻辑。 - `examples/demand/local_output_sink.py` - 新增本地 JSON sink。 - 由 `run.py` 统一模拟 `demand_task`、`demand_content`、Hive 输出表。 - 其中只有 `demand_content` 的 `ext_data.evidence_pack` 是新增业务结构;Hive 输出表只是原样镜像。 - 这是 MVP 测试输出设施,不进入通用框架。 - `examples/demand/run_existing_execution_local.py` - 新增只读测试入口。 - 只允许传已有 PG `pattern_mining_execution.id`。 - 禁止调用 `run_mining`,避免写 Pattern 中间表。 - `examples/demand/run_existing_execution_mysql.py` - 新增云端测试服单表写入入口。 - 只允许传已有 PG `pattern_mining_execution.id`。 - 强制 `DEMAND_OUTPUT_MODE=mysql_demand_content`,只写 MySQL `demand_content`。 - `examples/demand/mysql_demand_content_sink.py` - 新增测试服 MySQL 单表 sink。 - 入库前强校验 `ext_data.evidence_pack`,插入后回填真实 `demand_content_id`。 - 不创建或写入 `demand_task`、Hive、PG Pattern、旧 MySQL Pattern。 ## Key Changes 1. DemandItem 输出结构增强 在 `create_demand_item/create_demand_items` 增加可选 `evidence_refs`,原有 `element_names/reason/desc/type` 不变。 ```python record = { "element_names": element_names, "reason": reason, "desc": desc, "type": type, "evidence_refs": evidence_refs or {}, } ``` 推荐 `evidence_refs` 格式: ```json { "source_kind": "pattern_itemset", "source_tool": "get_itemset_detail", "itemset_ids": [123], "category_ids": [456, 789], "source_post_id": "55157577", "case_ids": { "pattern_itemset": ["55157577"] }, "seed_terms": ["猫咪", "拟人化"], "notes": "source_post_id 必须来自 get_itemset_detail.post_ids / matched_post_ids" } ``` `case_ids` 在候选阶段按 `source_kind` 分组,而不是一个裸数组。示例:`{"pattern_itemset": ["post_id"], "direct_case": ["case_id"]}`。若当前来源没有 Case seed,对应 source_kind 可以填空数组,但不能把其他来源的 Case 混入本来源。 2. `demand_content.ext_data` 增加 `evidence_pack` 本地输出的 `demand_content.json` 保持真实表字段形态: 当前落地阶段已经接入 DB 强校验 builder:`ext_data.evidence_refs` 只作为 LLM 候选输入;只有校验通过后才会在 `ext_data.evidence_pack` 中写入 `source_certainty=db_validated`、`validation_status=passed`。校验失败的 DemandItem 不进入 `demand_content.json`。 ```json { "id": 1, "merge_leve2": "贪污腐败", "name": "权钱交易,利益输送", "reason": "...", "suggestion": "...", "score": 0.73, "ext_data": { "reason": "...", "desc": "...", "type": "pattern", "video_ids": ["..."], "evidence_pack": { "pattern_source_system": "pg_pattern_v2", "pattern_execution_id": 581, "mining_config_id": 2081, "source_kind": "pattern_itemset", "case_id_type": "post_id", "source_post_id": "02454b8dab4c13c9fa21042bdc92d122", "category_bindings": [ { "itemset_id": 1607313, "itemset_item_id": 6051234, "category_id": 123456, "category_name": "示例分类", "category_path": "/形式/...", "category_full_path": "/形式/...", "point_type": "关键点", "dimension": "形式", "element_name": null } ], "element_bindings": [ { "itemset_id": 1607313, "itemset_item_id": 6051234, "category_id": 123456, "point_type": "关键点", "dimension": "形式", "element_name": "示例元素", "matched_element_count": 1, "matched_post_count": 1, "matched_post_ids": ["02454b8dab4c13c9fa21042bdc92d122"] } ], "itemset_ids": [1607313], "itemset_items": [ { "itemset_id": 1607313, "category_id": 123456, "category_path": "/形式/...", "point_type": "关键点", "dimension": "形式", "element_name": null } ], "support": 0.18, "absolute_support": 12, "matched_post_ids": ["..."], "case_ids": ["..."], "decode_case_ids": [], "seed_terms": ["权钱交易", "利益输送"], "trace_id": "...", "demand_task_id": null, "demand_content_id": 1, "source_certainty": "db_validated", "validation_status": "passed" } }, "dt": "20260604" } ``` 3. `dwd_multi_demand_pool_di` / `feature_point_data` 不改业务结构,仅做本地镜像 本地目录仍保留多文件,目的是完整复刻本次 run 的所有输出,便于回归验证 `local_json` 模式不真实写 DB / Hive: ```text test_output_data/{run_id}/ run_manifest.json demand_task.json demand_items.json rejected_demand_items.json demand_content.json dwd_multi_demand_pool_di.json feature_point_data.json ``` `dwd_multi_demand_pool_di.json` 只镜像当前两种策略,不新增证据字段: - `当下供需gap` - `当下供需gap-分词` `feature_point_data.json` 始终作为本地契约文件存在;仅在 `merge_leve2 == "全局树"` 时有非空业务行,字段仍沿用当前实现中的 `特征点`、`总分发曝光pv`、`质bn_rovn`、`dt`。注意:真实表/代码里的历史字段拼写是 `merge_leve2`,CLI 参数可以叫 `--merge-level2`,但输出 JSON 和文档中的表字段应保持 `merge_leve2`,不要误改成 `merge_level2`。 4. `evidence_pack` 采用强校验:LLM 只提供候选引用,代码校验失败则拒绝该需求 `evidence_refs` 只是 DemandAgent LLM 在生成需求时给出的候选证据引用,不直接下发给 ContentFindAgent。真正进入 `demand_content.ext_data.evidence_pack` 的,只能是代码按当前 `execution_id` 从 DB 校验通过并补齐后的证据。 原则: - 下游消费的 `evidence_pack` 必须是真实、可追溯、DB 校验通过的证据链。 - 如果 `itemset_ids/category_ids/element_bindings/video_ids` 任一必需证据无法校验,当前 DemandItem 失败,不写入 `demand_content`。对 Pattern itemset 来源,`case_ids` 按 `post_id` 语义等于已校验的 `matched_post_ids`,可为空的是 `decode_case_ids`;`source_post_id/itemset_ids/itemset_items/mining_config_id/matched_post_ids/support/absolute_support` 必须强校验通过。若 LLM 漏填 `source_kind`,但已给出 `itemset_ids` 或 `source_tool=get_itemset_detail`,DemandAgent 可内部补齐为 `pattern_itemset` 后继续 DB 强校验。 - 失败的 DemandItem 只进入本地 `rejected_demand_items.json` 供排查,Agent 继续生成下一条需求。 - 不允许把候选反查、跨 execution 命中、DB 错误、LLM 抄错 ID 的结果伪装成下游可消费证据。 字段来源和校验口径如下: | 字段 | 主要来源 | 真实性判断 | 是否可能受 LLM 影响 | | --- | --- | --- | --- | | `pattern_source_system` | 代码按当前读取链路设置,MVP 固定为 `pg_pattern_v2` | 标记证据来自 PG `open_aigc.public` Pattern V2 主事实 | 否 | | `pattern_execution_id` | 当前运行参数 / PG `pattern_mining_execution.id` | 必须存在且 `status='success'` | 否 | | `mining_config_id` | PG `pattern_itemset.mining_config_id` + `pattern_mining_config.id` | 必须来自同一 execution,且 config `scope='topic'` | 否 | | `case_id_type` | 代码按证据类型设置,Pattern case 链路固定为 `post_id` | 用于标准化 `source_post_id/matched_post_ids/aweme_id` 的 ID 语义 | 否 | | `source_post_id` | LLM 候选 + PG `pattern_itemset_post.post_id` 校验 | 必须出现在已校验 itemset 的 `matched_post_ids` 中,否则拒绝该需求 | 候选阶段受影响,最终输出必须 DB 校验通过 | | `itemset_ids` | LLM 在 `evidence_refs` 中给出候选,代码按 `execution_id` 回查校验 | 只有属于当前 execution 且和需求证据匹配时才进入 evidence_pack;否则该需求拒绝 | 候选阶段受影响,最终输出必须 DB 校验通过 | | `itemset_items[]` | PG `pattern_itemset_item` + `pattern_mining_category` | 必须来自已校验 `scope='topic'` itemset,且每个 `category_id/category_path` 能 join 到当前 execution 的分类节点 | 否 | | `support` / `absolute_support` | PG `pattern_itemset` | DB 可验证事实 | 否 | | `matched_post_ids` | PG `pattern_itemset_post` 聚合 | DB 可验证事实;`COUNT(DISTINCT post_id)` 必须等于 `absolute_support` | 否 | | `category_bindings` | PG `pattern_itemset_item` + `pattern_mining_category` | 必须来自已校验 itemset/category;查不到则拒绝该需求 | 否 | | `element_bindings` | PG `pattern_mining_element`,限定 `source_table='post_decode_topic_point_element'` | 必须能在当前 execution/source_post 下精确绑定到 itemset item;只查到候选时拒绝该需求 | 否 | | `video_ids` | 优先来自已校验 itemset 的 `matched_post_ids` | 必须来自已校验证据链;不再把宽松反查结果作为下游证据 | 否 | | `case_ids` | 已校验 itemset 的 `matched_post_ids` | Pattern 来源下固定是 `post_id` 语义,和 `case_id_type=post_id` 对齐 | 否 | | `decode_case_ids` | PG-only MVP 不再查旧 MySQL decode 表,保留为空数组兼容字段 | 可选补充字段;命中不到不影响 Pattern itemset 来源通过 | 否 | | `seed_terms` | LLM 候选 + 代码校验后的 element/category/itemset 反推 | 最终输出必须能被已校验证据覆盖;否则拒绝该需求 | 候选阶段受影响,最终输出必须被证据覆盖 | | `source_kind` | LLM 候选明确声明;若缺失但已有 `itemset_ids` 或 `source_tool=get_itemset_detail`,DemandAgent 内部可安全补齐为 `pattern_itemset` | MVP 只支持 `pattern_itemset`;补齐后仍必须走 DB 强校验,不是 Pattern itemset 则拒绝 | 候选阶段受影响,最终输出必须 DB 校验通过 | | `trace_id` | 现有 Agent runner / trace store | 框架事实 | 否 | | `demand_task_id` | 本地回归可为模拟任务 ID;云端单表模式因不建 `demand_task` 可为 `null` | 不能伪装成真实 DB task id | 否 | | `demand_content_id` | 云端单表写库后为真实 `demand_content.id`;本地回归为模拟 ID | 云端测试服真实,本地回归模拟 | 否 | 因此最终进入 `demand_content` 的 `evidence_pack` 必须包含完整证据字段;其中状态字段的取值只能是: ```json { "source_certainty": "db_validated", "validation_status": "passed" } ``` 如果 LLM 给出的 `itemset_ids/category_ids/source_post_id` 查不到、跨 execution,`source_post_id` 不在已校验 itemset 的 `matched_post_ids` 中,`itemset_items[]` 或 `mining_config_id` 补不齐,和 `element_names/seed_terms` 没有强匹配,或者 DB 校验失败,则当前 DemandItem 不生成 `demand_content`,只记录到本地拒绝清单: ```json { "element_names": ["地貌水文", "互动设计"], "reject_reason": "itemset_id 377530 not found under execution_id 1987", "validation_status": "failed" } ``` 业务含义:给下游 ContentFindAgent 的任务证据链必须真实。只要 DemandAgent 无法确认证据真实性,这条需求就不进入需求池;Agent 应继续生成下一条可强校验的需求。 5. 输入仍从真实 DB 拉 MVP 入口只接受已有 PG `pattern_mining_execution.id`: ```bash DEMAND_OUTPUT_MODE=local_json python -m examples.demand.run_existing_execution_local \ --execution-id 581 \ --merge-level2 贪污腐败 \ --platform-type piaoquan \ --count 5 \ --run-id local_581_smoke ``` local 模式只能通过 `examples.demand.run_existing_execution_local` 进入;`run.py` 会校验 `DEMAND_LOCAL_ENTRYPOINT=run_existing_execution_local`。该入口必须传已有 PG execution,禁止调用 `run_mining` 或各平台 prepare,避免写任何 Pattern 中间表。 本地入口会把三类运行产物统一重定向到 `examples/demand/test_output_data/{run_id}` 下。可传 `--run-id` 指定目录名;如果传 `--output-root`,也必须位于 `examples/demand/test_output_data/` 之内。 ```text {output_root}/ intermediate/result/ intermediate/data/ .trace/ output/ run_manifest.json demand_task.json demand_items.json rejected_demand_items.json demand_content.json dwd_multi_demand_pool_di.json feature_point_data.json ``` 若未传 `--output-root`,默认写到 `examples/demand/test_output_data/execution_{execution_id}_{timestamp}/`。local 模式会从 PG `pattern_mining_element` 聚合生成兼容的权重 JSON 到 `intermediate/data/{execution_id}/`,`get_weight_score_*` 读取该目录,不写 `examples/demand/data`。 ## Test Plan - 写库屏障测试:`local_json` 模式下 monkeypatch `mysql_db.insert/update/insert_many`、`execute_odps_sql` 和 PG DML/DDL,若被调用则测试失败;PG repository 只允许 `SELECT/SHOW/EXPLAIN`。 - 云端单表写库测试:`mysql_demand_content` 模式下只允许 MySQL `demand_content` 的 `INSERT/UPDATE`;不得调用 `_create_demand_task/_finish_demand_task`、Hive/ODPS 写入、PG DML/DDL 或旧 MySQL Pattern 写入。 - 输出结构测试:本地回归校验 JSON 文件存在;云端测试服按 `run_label` 查询 MySQL `demand_content` 行。两种模式都重点校验 `demand_content.ext_data.evidence_pack`,同时确认 `dwd_multi_demand_pool_di` / `feature_point_data` 不新增业务字段。 - evidence 测试:校验所有进入 `demand_content` 的 `evidence_pack.pattern_execution_id/source_kind/seed_terms/trace_id/source_certainty/validation_status` 必填,`validation_status` 必须为 `passed`,`source_certainty` 必须为 `db_validated`。 - Pattern itemset evidence 测试:当 `source_kind=pattern_itemset` 时,`pattern_source_system/case_id_type/source_post_id/mining_config_id/itemset_ids/itemset_items/matched_post_ids/support/absolute_support` 必填,且 `source_post_id` 必须在 `matched_post_ids` 中,`len(matched_post_ids)` 必须等于 `absolute_support`。 - 严格拒绝测试:构造 LLM 声明错误 itemset、跨 execution itemset、缺少 `matched_post_ids/source_post_id/itemset_items/mining_config_id`、DB 校验失败等样本,验证这些 DemandItem 不进入 `demand_content`,只进入 `rejected_demand_items.json`。 - 回归测试:不改 `agent/` 通用框架;DemandAgent pattern 工具名保持不变,但底层均走 PG adapter。 - 多 subagent 验证:分别做 DB 血缘审计、输出契约审计、旧能力回归审计,交叉确认没有改坏原能力。 ## Assumptions - V1 不做 DB schema migration;本地回归模式不写 DB,云端测试服模式只写 MySQL `demand_content` 单表。 - 本次只调整 DemandAgent 输出,不改 `agent/` 通用框架。 - 本次下游证据链只以 `demand_content.ext_data.evidence_pack` 为载体;`dwd_multi_demand_pool_di` 和 `feature_point_data` 不承载 evidence_pack。 - DemandAgent 当前正式证据源是 PG `open_aigc.public` Pattern V2 主事实;旧 MySQL Pattern 兼容层只作为历史背景,不再作为 evidence 来源。 - 本 MVP 只输出 PG `pattern_itemset.scope='topic'`;`topic_element`、script、paragraph、group scope 因无法保证分类树节点闭合,不进入 exact evidence。 - 对纯 Pattern itemset 来源,`case_ids` 按 `post_id` 语义直接等于已校验的 `matched_post_ids`,`decode_case_ids` 保留为空数组兼容字段。`source_post_id`、`matched_post_ids`、`itemset_ids`、`itemset_items[]`、`mining_config_id`、`support`、`absolute_support` 必须强校验通过。