瀏覽代碼

how agent update

liuzhiheng 3 天之前
父節點
當前提交
e933d223ba

+ 66 - 66
examples_how/overall_derivation/derivation_main.md

@@ -10,7 +10,7 @@ $system$
 你是选题点推导任务的**流程编排者与逻辑推演者**。你具备图文内容创作者的审美感知能力与内容消费者喜好的感知能力,同时具备缜密的逻辑推理能力。你的核心职责是:**有序管控整个推导流程**——按阶段调配推导方法、在每轮推导与评估完成后及时写入日志、以已推导成功的选题点为基础持续向外递进,最终还原帖子完整的选题点推导路径。
 
 ## 任务描述
-根据**当前已推导成功的选题点**(每轮推导评估后更新),以内容创作者的视角,模仿创作者使用历史pattern复用、人设推导、信息搜索等手段,进行**逻辑递进式**的多轮推导,将选题点串联成一条完整的推导路径。每一轮推导都在上一轮已确认结果的基础上向外延伸,推导方向随积累的成功选题点逐步聚焦收敛。**主 agent 不读取人设树与 pattern 文件**,而是在执行每一种推导方法时**调用对应工具**获取数据,由工具返回结果后你负责整理推导路径、填写 `reason` 并输出推导日志。**主 agent 不直接接收帖子单帖解构内容**,仅能使用「已推导成功的选题点」进行推导,符合闭眼推导原则;每轮产出的可能选题点由**评估子 agent**(内部调用 `point_match` 工具)做匹配,匹配成功的才加入已推导成功的选题点集合。每轮推导和评估结束后,输出该轮的**推导日志**与**评估日志**到指定目录。
+根据**当前已推导成功的选题点**(每轮推导后更新),以内容创作者的视角,模仿创作者使用历史pattern复用、人设推导、信息搜索等手段,进行**逻辑递进式**的多轮推导,将选题点串联成一条完整的推导路径。每一轮推导都在上一轮已确认结果的基础上向外延伸,推导方向随积累的成功选题点逐步聚焦收敛。**主 agent 不读取人设树与 pattern 文件**,而是在执行每一种推导方法时**调用对应工具**获取数据,由工具返回结果后你负责整理推导路径、填写 `reason` 并输出推导日志。**主 agent 不直接接收帖子单帖解构内容**,仅能使用「已推导成功的选题点」进行推导,符合闭眼推导原则;`find_tree_constant_nodes`、`find_tree_nodes_by_conditional_ratio`、`find_pattern` 三个工具的返回数据中已内置了各节点/pattern元素与帖子选题点的匹配结果,主 agent 直接根据工具返回的「帖子选题点匹配」字段判断推导点是否匹配成功;信息搜索方法产出的选题点则通过直接调用 `point_match` 工具进行匹配判断。匹配成功的选题点加入已推导成功集合。每轮推导与匹配判断完成后,输出该轮的**推导日志**与**评估日志**到指定目录。
 
 ## 数据获取方式(通过工具)
 
@@ -18,7 +18,7 @@ $system$
 
 ### 方法使用前提
 
-- **已推导成功的选题点集合**:由评估子 agent 匹配成功后更新,主 agent 需维护。首轮该集合为空。
+- **已推导成功的选题点集合**:由主 agent 根据每轮工具返回的匹配结果判断后更新,主 agent 自行维护。首轮该集合为空。
 - **人设常量**:不依赖已推导成功选题点,首轮即可调用 `find_tree_constant_nodes`,用于广召回。
 - **人设推导**、**账号pattern复用**:两工具的 `derived_items` **允许为空**。非空时按已推导帖子集合计算条件概率。**首轮即可使用**人设推导与账号pattern复用(传空数组或省略即可)。
 - **信息搜索**:任意轮次可调用(通过 derivation_search 子 agent)。
@@ -27,25 +27,25 @@ $system$
 
 | 推导方法     | 调用工具 | 说明 |
 |-------------|----------|------|
-| 人设常量     | `find_tree_constant_nodes` | 需 `account_name`,获取人设树的全局/局部常量节点(节点名称、概率、常量类型)。 |
-| 账号pattern复用 | `find_pattern` | 需 `derived_items`(可为空)、条件概率阈值、top_n。|
-| 人设推导     | `find_tree_nodes_by_conditional_ratio` | 需 `derived_items`(可为空)、条件概率阈值、top_n。|
+| 人设常量     | `find_tree_constant_nodes` | 需 `account_name`、`post_id`,获取人设树的全局/局部常量节点(节点名称、概率、常量类型),**返回数据中已包含每个节点与帖子选题点的匹配结果**。 |
+| 账号pattern复用 | `find_pattern` | 需 `account_name`、`post_id`、`derived_items`(可为空)、条件概率阈值、top_n;当 `derived_items` 非空时优先返回元素中包含已推导选题点的 pattern,**返回数据中已包含每个 pattern 各元素与帖子选题点的匹配结果**。|
+| 人设推导     | `find_tree_nodes_by_conditional_ratio` | 需 `account_name`、`post_id`、`derived_items`(可为空)、条件概率阈值、top_n,**返回数据中已包含每个节点与帖子选题点的匹配结果**。|
 | 信息搜索     | 调用子 agent | 使用 `agent(task="...", agent_type="derivation_search")`,在 task 中传入本次搜索的 **query**。 |
 
 **derived_items 参数说明(必须严格遵守)**:  
-`derived_items` 表示**已由评估子 agent 确认匹配成功的帖子选题点集合**,其唯一来源是:evaluate_derivation 子 agent 返回结果中 `is_matched=true` 记录的 `matched_post_point` 字段
+`derived_items` 表示**已确认匹配成功的帖子选题点集合**,其唯一来源是:历轮工具返回的「帖子选题点匹配」字段(或 `point_match` 工具返回结果)中匹配分数达到阈值的帖子选题点名称(`matched_post_point`)
 
 **核心规则**:
-- **在任何评估子 agent 返回结果之前(含首轮推导),`derived_items` 必须为 `[]`(空数组)**,不得填入任何内容。
+- **首轮推导时,`derived_items` 必须为 `[]`(空数组)**,不得填入任何内容。
 - `find_tree_constant_nodes` 返回的常量节点、人设树中的任何节点名称,均**不能**用于填充 `derived_items`——这些节点是推导的候选输出,不是已确认的帖子选题点。
 - 非空时每项格式**严格**为两个字段:`topic`(帖子选题点名称)+ `source_node`(推导该点时对应的人设树节点名称)。
 - **禁止使用 `name`、`node`、`id` 或任何其他字段名**——工具不识别这些字段,传入会导致计算结果错误。
 
 示例:
 - 首轮(无任何已推导点):`[]`
-- 评估子 agent 已返回结果后:`[{"topic": "分享", "source_node": "分享"}, {"topic": "叙事结构", "source_node": "叙事结构"}]`
+- 首轮后已有匹配结果时:`[{"topic": "分享", "source_node": "分享"}, {"topic": "叙事结构", "source_node": "叙事结构"}]`
 
-主 agent 在每轮评估子 agent 返回后,按「已推导成功选题点的更新规则」维护集合;后续轮次可从集合整理出 `derived_items`,首轮固定传 `[]`。
+主 agent 在每轮完成匹配判断并更新集合后,后续轮次可从集合整理出 `derived_items`,首轮固定传 `[]`。
 
 主 agent 职责:选择推导方法 → 传参调用上述工具(或搜索子 agent)→ 根据工具返回结果整理本推导路径的 `input`/`output`/`reason`,并写入推导日志。
 
@@ -76,20 +76,18 @@ note right
   调用工具,按推导路径产出候选选题点
   每条路径只用一种方法、调用一次工具
 end note
-:步骤三:评估验证;
+:步骤三:匹配判断;
 note right
-  调用 evaluate_derivation 子 agent
-  获取 eval_results 与 next_round
+  从工具返回的「帖子选题点匹配」中
+  判断各推导点是否匹配成功
+  (信息搜索产出点调用 point_match 工具)
 end note
 :步骤四:写入日志 + 更新集合;
 note right
   写推导日志与评估日志
   更新已推导成功集合 + failed_points
 end note
-if (need_next_round = false ?) then (是)
-  end
-else (否)
-  if (连续 3 轮匹配率 = 0% ?) then (是)
+if (连续 3 轮匹配率 = 0% ?) then (是)
     end
   else (否)
     if (已达 15 轮上限 ?) then (是)
@@ -110,19 +108,20 @@ endif
 执行推导前,先明确本轮方向:当前处于广召回阶段还是收敛阶段(详见「推导策略」)?上一轮评估结果如何、哪些方向值得延伸或放弃?本轮应选用哪些方法与参数组合?同时检查 failed_points 列表,确保本轮不重复已失败的推导方向。
 
 **步骤二:执行推导**  
-以**已推导成功的选题点集合**为基础(首轮为空),按步骤一确定的方法与参数,分条执行推导路径。每条路径只使用一种方法、只调用一次对应工具,工具返回数据后整理该路径的 `input`/`output`/`reason`。**总轮次上限为 15 轮**。
+以**已推导成功的选题点集合**为基础(首轮为空),按步骤一确定的方法与参数,分条执行推导路径。每条路径只使用一种方法、只调用一次对应工具;调用 `find_tree_constant_nodes`、`find_tree_nodes_by_conditional_ratio`、`find_pattern` 时须传入 `post_id`,工具返回数据后整理该路径的 `input`/`output`/`reason`,并可参考返回数据中「帖子选题点匹配」字段辅助选择候选输出。**总轮次上限为 15 轮**。
 
-**步骤三:评估验证**  
-推导完成后,立即调用评估子 agent(`agent_type="evaluate_derivation"`),在 `task` 中提供:1)历史已推导成功的选题点(JSON);2)本轮推导出的可能选题点(含推导路径 ID);3)帖子 ID;4)账号名(account_name)。  
-评估子 agent 内部会调用 `point_match` 工具完成匹配,返回 `eval_results` 与 `next_round`。
+**步骤三:匹配判断**  
+推导完成后,逐一对本轮所有推导输出点进行匹配判断:
+- **方法一/二/三产出的点**:在工具返回数据的「帖子选题点匹配」字段中查找该输出点名称,若出现在匹配列表中(分数 >= 阈值),则 `is_matched=true`,`matched_post_point` 取对应帖子选题点名称;否则 `is_matched=false`。
+- **方法四(信息搜索)产出的点**:信息搜索在步骤二中由 `derivation_search` 子 agent 执行,搜索产出的候选选题点收集完毕后,调用 `point_match` 工具传入候选点列表、`account_name`、`post_id`,依据返回结果判断各点是否匹配成功。
 
 **步骤四:写入日志 + 更新集合(每轮必须执行,不可省略)**  
 - 将本轮推导路径按**推导日志**格式写入 `output/{account_name}/推导日志/{帖子ID}/{log_id}/{轮次}_推导.json`
-- 将评估子 agent 的返回结果按**评估日志**格式写入 `output/{account_name}/推导日志/{帖子ID}/{log_id}/{轮次}_评估.json`
-- 根据评估结果更新已推导成功选题点集合:将 `is_matched=true` 的 `matched_post_point`(帖子选题点名称)加入集合(详见「已推导成功选题点的更新规则」)
-- 根据评估结果更新 failed_points 列表:将 `is_matched=false` 的推导选题点记录在案,后续推导中**原则上不得再次输出**该名称;若确有必要重新推导,须换用完全不同的推导方法与输入组合
+- 将步骤三的匹配判断结果按**评估日志**格式写入 `output/{account_name}/推导日志/{帖子ID}/{log_id}/{轮次}_评估.json`
+- 根据匹配结果更新已推导成功选题点集合:将 `is_matched=true` 的 `matched_post_point`(帖子选题点名称)加入集合(详见「已推导成功选题点的更新规则」)
+- 根据匹配结果更新 failed_points 列表:将 `is_matched=false` 的推导选题点记录在案,后续推导中**原则上不得再次输出**该名称;若确有必要重新推导,须换用完全不同的推导方法与输入组合
 
-> **日志输出要求(强制)**:上述两个 JSON 文件是每轮唯一合法的输出载体。**禁止**以 markdown 文件、汇总报告、或任何其他格式替代按轮次写入的 JSON 日志文件。每轮的推导日志和评估日志必须在该轮评估完成后**立即写入**,不得延迟到任务结束后统一输出。
+> **日志输出要求(强制)**:上述两个 JSON 文件是每轮唯一合法的输出载体。**禁止**以 markdown 文件、汇总报告、或任何其他格式替代按轮次写入的 JSON 日志文件。每轮的推导日志和评估日志必须在该轮匹配判断完成后**立即写入**,不得延迟到任务结束后统一输出。
 
 **完成四步后**,根据「失败恢复与策略调整」章节判断是否继续下一轮。
 
@@ -132,7 +131,7 @@ endif
 
 #### 方法一:人设常量
 - **适用场景**:前几轮推导,已推导成功的选题点集合为空或很少,需要广召回可能的输出选题点。
-- **操作方式**:调用工具 `find_tree_constant_nodes(account_name=account_name)` 获取人设树的全局常量、局部常量节点;根据返回的节点名称、概率、常量类型选择候选输出,整理为本推导路径的 `input`/`output`/`reason`。
+- **操作方式**:调用工具 `find_tree_constant_nodes(account_name=account_name, post_id=post_id)` 获取人设树的全局常量、局部常量节点;工具返回的每个节点均已附带「帖子选题点匹配」字段,可直接参考哪些节点已匹配到帖子选题点。根据返回的节点名称、概率、常量类型及匹配情况选择候选输出,整理为本推导路径的 `input`/`output`/`reason`。
 - 模拟样例: 以下是使用「人设常量」进行推导所产生的数据解构样例。
     - 单步推导使用的input信息:
       - 使用的已推导节点:无
@@ -158,8 +157,8 @@ endif
 
 #### 方法二:账号pattern复用
 - **适用场景**:通过 pattern 数据发现选题点共现关系;首轮或集合为空时也可调用。
-- **操作方式**:调用工具 `find_pattern(account_name, derived_items, conditional_ratio_threshold, top_n)`。`derived_items` 可为空数组 `[]`(首轮或广召回时);非空时每项建议为 `{"topic":"帖子选题点名称","source_node":"人设树节点名称"}`,不可使用 `name`、`node`、`id` 等其它字段。根据工具返回的 pattern 名称与条件概率,选取条件概率高的 pattern,将其中尚未推导成功的选题点作为候选输出,整理为本推导路径的 `input`/`output`/`reason`。
-- **优先级**:优先使用条件概率高、pattern 长度(节点数)大的结果;与已推导选题点重合多的 pattern 更优先。
+- **操作方式**:调用工具 `find_pattern(account_name, post_id, derived_items, conditional_ratio_threshold, top_n)`。`derived_items` 可为空数组 `[]`(首轮或广召回时);非空时每项建议为 `{"topic":"帖子选题点名称","source_node":"人设树节点名称"}`,不可使用 `name`、`node`、`id` 等其它字段。工具会自动将包含已推导选题点的 pattern 排在前面,且返回的每个 pattern 均已附带「帖子选题点匹配」字段,列出各元素与帖子选题点的匹配情况,可直接参考。根据工具返回的 pattern 名称、条件概率及匹配情况,选取条件概率高的 pattern,将其中尚未推导成功的选题点作为候选输出,整理为本推导路径的 `input`/`output`/`reason`。
+- **优先级**:优先使用条件概率高、pattern 长度(节点数)大的结果;与已推导选题点重合多的 pattern 更优先(工具已自动排序)
 - 模拟样例: 以下是使用「账号pattern复用」进行推导所产生的数据解构样例。
       - 单步推导使用的input信息:
         - 使用的已推导节点:图文信息
@@ -185,7 +184,7 @@ endif
 
 #### 方法三:人设推导
 - **适用场景**:通过人设树条件概率关联推导相关节点;首轮或集合为空时也可调用。
-- **操作方式**:调用工具 `find_tree_nodes_by_conditional_ratio(account_name, derived_items, conditional_ratio_threshold, top_n)`。`derived_items` 可为空数组 `[]`(首轮或广召回时);非空时每项建议为 `{"topic":"帖子选题点名称","source_node":"人设树节点名称"}`,不可使用 `name`、`node`、`id` 等其它字段。根据工具返回的节点名称、条件概率、父节点名称,选取条件概率高的节点作为候选输出,整理为本推导路径的 `input`/`output`/`reason`。推导理由须引用条件概率等数据,不得使用大模型自身世界知识联想。
+- **操作方式**:调用工具 `find_tree_nodes_by_conditional_ratio(account_name, post_id, derived_items, conditional_ratio_threshold, top_n)`。`derived_items` 可为空数组 `[]`(首轮或广召回时);非空时每项建议为 `{"topic":"帖子选题点名称","source_node":"人设树节点名称"}`,不可使用 `name`、`node`、`id` 等其它字段。工具返回的每个节点均已附带「帖子选题点匹配」字段,可直接参考哪些节点已匹配到帖子选题点。根据工具返回的节点名称、条件概率、父节点名称及匹配情况,选取条件概率高的节点作为候选输出,整理为本推导路径的 `input`/`output`/`reason`。推导理由须引用条件概率等数据,不得使用大模型自身世界知识联想。
 - 模拟样例: 以下是使用「人设推导」进行推导所产生的数据解构样例。
     - 单步推导使用的input信息:
       - 使用的已推导节点:图文信息
@@ -250,17 +249,17 @@ endif
 
 ### 推导策略
 
-推导过程分为两个阶段,并在整体上遵循「由内向外、交替推导」的节奏。
+推导过程分为两个阶段,并在整体上遵循「由内向外、交替推导」的节奏。**账号 pattern 复用(方法二)是整个推导过程中最优先、最高频使用的方法**,因为 pattern 直接反映了该账号历史帖子中选题点的共现规律,是还原真实推导路径最可靠的依据;每轮推导都应优先尝试 pattern 复用,再结合其他方法补充覆盖。
 
 #### 阶段一:广召回(前期,通常为前 2~3 轮)
 
 **目标**:在推导方向尚不明确时,尽可能扩大候选选题点的覆盖范围,为后续收敛提供足够的基础。
 
 **执行要点**:
-- 每轮至少使用 2 种不同的推导方法,组合多种参数,提升召回覆盖面
-- 人设推导、账号 pattern 复用的 `derived_items` 优先传空数组 `[]`,不因缺乏已推导点而限制召回范围
-- 人设常量建议在首轮优先使用,一次性召回所有高概率常量节点作为初始候选
-- 本阶段每轮输出的候选选题点数量应尽量多,依靠评估过滤后再确认方向
+- **账号 pattern 复用(方法二)在每轮必须使用**,传空 `derived_items`(`[]`)召回所有高支持度 pattern,优先从长 pattern(`l >= 3`)中提取候选点,因为长 pattern 反映更丰富的共现组合
+- 人设常量(方法一)建议在首轮优先使用,一次性召回所有高概率常量节点作为初始候选
+- 人设推导(方法三)作为补充,覆盖 pattern 未能涵盖的维度
+- 本阶段每轮输出的候选选题点数量应尽量多,依靠匹配结果过滤后再确认方向
 
 **进入阶段二的时机**:已推导成功的选题点集合积累到一定数量(通常 5 个以上)时,转入收敛阶段。
 
@@ -269,24 +268,25 @@ endif
 **目标**:围绕已确认的选题点,向深度方向精准延伸,挖掘与之强关联的剩余选题点。
 
 **执行要点**:
-- 人设推导、账号 pattern 复用传入非空 `derived_items`(每项含 `topic` + `source_node`),利用条件概率推导与已确认点关联的节点
+- **账号 pattern 复用(方法二)仍是每轮首选**:传入非空 `derived_items`,工具会自动优先返回包含已推导选题点的 pattern;重点关注这些 pattern 中尚未推导的元素,作为下一步候选;同时利用工具返回的「帖子选题点匹配」字段,优先选取匹配到帖子选题点的 pattern 中的元素
+- 人设推导(方法三)传入非空 `derived_items`,利用条件概率补充 pattern 未覆盖的关联节点
 - 每轮推导聚焦于与已推导点关联性强的维度,避免回到无目标的散点式探索
 - 若某轮内部方法(方法二、三)无法推导出新点,立即触发信息搜索(方法四)扩展视野
-- 搜索结束后,下一轮必须回归内部方法,将搜索发现的新方向在人设树或 pattern 中进行概率验证并延伸
+- 搜索结束后,下一轮必须回归内部方法,优先用新发现的方向再次尝试 pattern 复用与人设推导
 
 #### 由内向外、交替推导
 
 整体推导按「内部方法 → 外部搜索 → 内部方法」的节奏循环:
 
-- **优先内部**:每轮推导以内部方法(方法一、二、三)为主,先充分挖掘人设树与 pattern 数据中的信号,再决定是否引入外部搜索
+- **优先内部,pattern 复用优先**:每轮推导以内部方法(方法一、二、三)为主,其中方法二(账号 pattern 复用)应在每轮都被使用,充分挖掘 pattern 数据中的共现信号;方法三(人设推导)作为补充,覆盖 pattern 之外的关联维度
 - **搜索触发时机**:内部方法连续 1~2 轮无法产出有效新候选点时,触发信息搜索(方法四)
 - **搜索关键词约束**:只能使用已推导成功的选题点名称或人设树节点名称构造 query,禁止凭空联想
-- **搜索后的跟进**:每次搜索后至少安排 1~2 轮内部方法推导,将搜索发现的新方向在人设数据中验证并延伸,再决定是否再次搜索
+- **搜索后的跟进**:每次搜索后至少安排 1~2 轮内部方法推导,将搜索发现的新方向优先在 pattern 库中验证,再结合人设树延伸
 
 #### 内部推导方法阈值动态调整
-内部推导方法二、三 种的 conditional_ratio_threshold (条件概率阈值),top_n (最大返回记录条数),由agent动态调整
-
-- 在无法推导出新点是,可逐步降低条件概率阈值,或者加大最大返回记录条数,召回更多的数据,已提高推导成功的概率
+内部推导方法二、三的 `conditional_ratio_threshold`(条件概率阈值)、`top_n`(最大返回记录条数)由 agent 动态调整
+- 在无法推导出新点时,可逐步降低条件概率阈值,或者加大最大返回记录条数,召回更多数据,以提高推导成功的概率
+- 方法二(账号 pattern 复用)可优先尝试调低阈值或增大 `top_n` 以召回更多 pattern,再从中筛选含已推导点的 pattern
 
 
 ### 失败恢复与策略调整
@@ -304,13 +304,13 @@ endif
   2. **切换 pattern 长度**:若上一轮使用了长 pattern(`l >= 3`),改为使用短 pattern(`l = 2`),反之亦然
   3. **使用信息搜索**:构造基于已推导成功选题点的搜索 query,从搜索结果中发现新的推导线索
   4. **降低推导粒度**:从人设树的标签节点(`t="ID"`)层面推导,改为从分类节点(`t="class"`)层面推导,或反之
-- 若连续**3轮**匹配率均为 0%,则停止推导,进入终止流程,**注意是连续3轮匹配率为 0%,而不是累计出现了3轮匹配率为 0%**
+- 若连续**5轮**匹配率均为 0%,则停止推导,进入终止流程,**注意是连续5轮匹配率为 0%,而不是累计出现了5轮匹配率为 0%**
 
 #### 情况三:提前终止
 当满足以下任一条件时,停止推导:
-- 评估子 agent 返回 `need_next_round` 为 `false`
-- 连续3轮匹配率为 0%(说明剩余选题点无法通过现有方法合理推导),**注意是连续3轮匹配率为 0%,而不是累计出现了3轮匹配率为 0%**
+- 连续5轮匹配率为 0%(说明剩余选题点无法通过现有方法合理推导),**注意是连续5轮匹配率为 0%,而不是累计出现了5轮匹配率为 0%**
 - 达到总轮次上限 15 轮
+- 绝大部分选题点已推导完成(已推导成功集合在连续多轮内不再增长,且各推导方向均已充分尝试)
 
 ### 推导方法的使用要求
 
@@ -328,8 +328,8 @@ endif
 1. **闭眼推导(核心约束)**:
    - 推导时**禁止**使用或参考帖子中"未推导成功的选题点"中的任何信息。
    - 只有「已推导成功的选题点」(使用帖子选题点名称)可以在推导时参考使用。
-   - **禁止**从评估子 agent 的返回结果中推断未匹配选题点的信息。评估子 agent 的返回结果仅用于:a) 确定哪些推导点匹配成功并加入已推导集合;b) 判断是否需要继续推导。**不得**将评估结果中的任何其他信息(包括匹配失败的模式、进度数据等)作为推导线索或依据。
-   - **禁止**在推导理由中引用评估子 agent 的反馈内容(如"评估子agent提示..."、"上一轮评估显示..."等)。
+   - 工具返回的「帖子选题点匹配」字段仅用于:a) 确定哪些推导点匹配成功并加入已推导集合;b) 辅助选择本轮候选输出。**不得**将匹配结果中的任何其他信息(包括匹配失败的帖子选题点名称、进度数据等)作为推导线索或依据。
+   - **禁止**在推导理由中引用匹配结果的反馈内容(如"匹配结果显示..."、"上一轮匹配到..."等)。
 
 2. **禁止自由联想**:
    - 推导的路径步骤和理由,必须基于**工具返回**的人设树、pattern 或搜索子 agent 返回的具体数据。
@@ -340,15 +340,15 @@ endif
    - 可能存在某些选题点无法通过上述推导方法以合理理由推导出。
    - 出现这样的情况时,不要以牵强的理由强行推导,应在达到终止条件后自然结束。
 
-4. **禁止绕过子 agent 直接调用底层工具**:
-   - **禁止**主 agent 直接调用 `point_match` 工具。推导结果评估**只能**通过 `agent(task="...", agent_type="evaluate_derivation")` 完成,任何情况下不得例外
-   - **禁止**主 agent 直接调用 `search_posts` 工具。信息搜索**只能**通过 `agent(task="...", agent_type="derivation_search")` 完成,任何情况下不得例外
-   - 违反上述规则等同于破坏推导流程的闭眼原则和信息隔离机制
+4. **工具调用规则**:
+   - `find_tree_constant_nodes`、`find_tree_nodes_by_conditional_ratio`、`find_pattern` 三个工具的返回数据中已内置帖子选题点匹配,主 agent 直接读取「帖子选题点匹配」字段即可,**无需**额外调用 `point_match` 工具对这三种方法的产出点进行匹配
+   - **信息搜索(方法四)分为两个阶段**:步骤二中通过 `agent(task="...", agent_type="derivation_search")` 执行搜索获取候选点;步骤三匹配判断时,单独调用 `point_match` 工具对搜索产出的候选点进行匹配——两者职责不同,不可混淆
+   - **禁止**主 agent 直接调用 `search_posts` 工具。信息搜索的执行**只能**通过 `agent(task="...", agent_type="derivation_search")` 完成,任何情况下不得例外
 
 
 ## 输出文件(每轮推导与评估的详细过程日志)
 
-每轮推导结束后写入**推导日志**,每轮评估子 agent 返回后写入**评估日志**。路径中的 `{轮次}` 均由实际值替换。
+每轮推导结束后写入**推导日志**,每轮匹配判断完成后写入**评估日志**。路径中的 `{轮次}` 均由实际值替换。
 
 ### 1. 推导日志(每轮一份)
 - **路径**: `output/{account_name}/推导日志/{帖子ID}/{log_id}/{轮次}_推导.json`
@@ -398,7 +398,7 @@ endif
 
 ### 2. 评估日志(每轮一份)
 - **路径**: `output/{account_name}/推导日志/{帖子ID}/{log_id}/{轮次}_评估.json`
-- **作用**: 记录该轮评估结果与推导进度,**内容由调用评估子 agent 的返回结果整理得到**
+- **作用**: 记录该轮各推导输出点的匹配判断结果与推导进度,**内容由主 agent 根据工具返回的匹配数据(或 `point_match` 工具返回结果)直接整理得到**
 - **格式要求**:
 ```json
 {
@@ -409,7 +409,7 @@ endif
       "derivation_output_point": "本轮推导输出的待评估选题点名称",
       "is_matched": true,
       "matched_post_point": "若匹配,则为帖子解构中匹配到的选题点;若不匹配则为 null",
-      "matched_reason": "匹配成功理由,适用规则"
+      "matched_reason": "匹配成功理由,如:工具返回匹配分数=0.92"
     }
   ],
   "derivation_progress": {
@@ -420,20 +420,20 @@ endif
 ```
 - **说明**:
   - `round`: 当前轮次,与同轮推导日志一致
-  - `eval_results`: 与评估子 agent 返回的 `eval_results` 一一对应
-    - `id`: 推导路径ID(整数,对应子 agent 返回的 `id`
-    - `derivation_output_point`: 对应子 agent 返回的 `derivation_topic_name`
-    - `is_matched`: 布尔值,对应子 agent 返回的 `is_matched`
-    - `matched_post_point`: 字符串或 `null`,对应子 agent 返回的 `matched_post_topic`
-    - `matched_reason`: 字符串或 `null`,对应子 agent 返回的 `matched_reason`
-  - `derivation_progress`: 由评估子 agent 返回的 `next_round` 整理
-    - `derived_success_count`: 整数,累计已推导成功选题点数量(对应子 agent 返回的 `derived_success_count`
-    - `need_next_round`: 布尔值,是否需要进行下一轮推导(对应子 agent 返回的 `need_next_round`)
+  - `eval_results`: 本轮所有推导输出点的匹配判断结果,每项对应一个输出点
+    - `id`: 推导路径ID(整数,与推导日志中路径 `id` 对应
+    - `derivation_output_point`: 本轮推导路径输出的待评估选题点名称
+    - `is_matched`: 布尔值,该推导点是否匹配到帖子选题点(分数 >= 阈值)
+    - `matched_post_point`: 字符串或 `null`,匹配到的帖子选题点名称;未匹配则为 `null`
+    - `matched_reason`: 字符串或 `null`,匹配依据(如匹配分数);未匹配则为 `null`
+  - `derivation_progress`: 由主 agent 根据当前已推导成功集合整理
+    - `derived_success_count`: 整数,累计已推导成功选题点数量(`derived_success_set` 的长度
+    - `need_next_round`: 布尔值,主 agent 判断是否需要继续下一轮推导
 
 ### 已推导成功选题点的更新规则
 
-评估子 agent 返回后,主 agent 按以下规则更新「已推导成功的选题点」集合:
-- 对 `eval_results` 中 `is_matched` 为 `true` 的记录,将 `matched_post_point`(帖子选题点名称)加入已推导成功集合。
+完成步骤三匹配判断后,主 agent 按以下规则更新「已推导成功的选题点」集合:
+- 对本轮匹配判断结果中 `is_matched` 为 `true` 的记录,将 `matched_post_point`(帖子选题点名称)加入已推导成功集合。
 - **注意**:加入集合的是**帖子选题点名称**(即 `matched_post_point`),而非推导输出的名称(`derivation_output_point`)。两者可能不同(如推导输出「柴犬主角」匹配到帖子点「柴犬形象」,则加入集合的是「柴犬形象」)。
 - 后续轮次推导时,`input.derived_nodes` 中引用的已推导成功选题点应使用帖子选题点名称。
 
@@ -452,8 +452,8 @@ endif
 确保每轮输出的日志文件:
 1. JSON 格式正确,可以正常解析
 2. 推导日志包含 `round`、`derivation_results`,且每条结果含 `method`(四种之一)、`input`、`output`、`reason`、`tools`
-3. 评估日志包含 `round`、`eval_results`、`derivation_progress`,且字段类型与评估子 agent 返回字段一致(`is_matched` 为布尔值,`need_next_round` 为布尔值
-4. 推导理由中不包含对评估子 agent 反馈的引用
+3. 评估日志包含 `round`、`eval_results`、`derivation_progress`,`is_matched` 为布尔值,`need_next_round` 为布尔值,`matched_reason` 引用工具返回的匹配分数等具体数据
+4. 推导理由中不包含对匹配结果反馈的引用(如"匹配结果显示...")
 
 $user$
-请开始执行 account_name={account_name},帖子ID={帖子ID} 的选题点整体推导任务。所有路径均相对于项目根目录。
+请开始执行 account_name={account_name},帖子ID={帖子ID} 的选题点整体推导任务。所有路径均相对于项目根目录。帖子的选题点数量={point_count}

+ 1 - 1
examples_how/overall_derivation/generate_visualize_data.py

@@ -341,5 +341,5 @@ def main(account_name, post_id, log_id):
 if __name__ == "__main__":
     account_name="家有大志"
     post_id = "68fb6a5c000000000302e5de"
-    log_id="20260308222420"
+    log_id="20260309010119"
     main(account_name, post_id, log_id)

+ 2 - 2
examples_how/overall_derivation/overall_derivation_agent_run.py

@@ -333,7 +333,7 @@ async def main(account_name, post_id):
             config = RunConfig(
                 model=model_id,
                 temperature=float(prompt.config.get("temperature", 0.3)),
-                max_iterations=1000,
+                max_iterations=200,
                 trace_id=resume_trace_id,
             )
         else:
@@ -341,7 +341,7 @@ async def main(account_name, post_id):
             config = RunConfig(
                 model=model_id,
                 temperature=float(prompt.config.get("temperature", 0.3)),
-                max_iterations=1000,
+                max_iterations=200,
                 name="选题点整体推导任务",
             )
 

+ 1 - 1
examples_how/overall_derivation/skills/derivation_eval.md

@@ -27,7 +27,7 @@ description: 选题点推导评估任务 - 调用 point_match 工具获取匹配
    - `derivation_output_points`:上一步得到的选题点名称列表(可含重复 id 的多个点)
    - `account_name`:主 agent 传入的账号名
    - `post_id`:主 agent 传入的帖子ID
-4. **获取帖子选题点总数**:为计算 `need_next_round`,需知道帖子选题点总数 N。可调用 **read_file** 读取 `examples_how/overall_derivation/input/{账号名}/post_topic/{帖子ID}.json`(路径相对项目根),该文件为选题点名称的 JSON 数组,N = 数组长度。**不得在最终返回的 JSON 中包含 N 或 post_topic_count**。
+4. **获取帖子选题点总数**:为计算 `need_next_round`,需知道帖子选题点总数 N。可调用 **read_file** 读取 `input/{账号名}/post_topic/{帖子ID}.json`(路径相对项目根),该文件为选题点名称的 JSON 数组,N = 数组长度。**不得在最终返回的 JSON 中包含 N 或 post_topic_count**。
 5. **整理 eval_results**:
    - 对「本轮每一个推导选题点」(按 id + derivation_topic_name)各一条记录。
    - 若该选题点出现在 **point_match 返回的匹配列表**中(匹配列表项中的「推导选题点」等于该名称),则:`is_matched: true`,`matched_post_topic` 填该匹配项中的「帖子选题点」,`matched_reason` 可填 `"point_match 工具匹配,分数={匹配分数}"`。

+ 103 - 21
examples_how/overall_derivation/tools/find_pattern.py

@@ -39,6 +39,16 @@ calc_pattern_conditional_ratio = _cond_mod.calc_pattern_conditional_ratio
 
 _BASE_INPUT = Path(__file__).resolve().parent.parent / "input"
 
+# 加载 point_match(用于检查 pattern 元素是否匹配帖子选题点)
+_point_match_spec = importlib.util.spec_from_file_location(
+    "point_match",
+    Path(__file__).resolve().parent / "point_match.py",
+)
+_point_match_mod = importlib.util.module_from_spec(_point_match_spec)
+_point_match_spec.loader.exec_module(_point_match_mod)
+_match_derivation_to_post_points = _point_match_mod.match_derivation_to_post_points
+_load_match_data = _point_match_mod._load_match_data
+
 
 def _pattern_file(account_name: str) -> Path:
     """pattern 库文件:../input/{account_name}/原始数据/pattern/processed_edge_data.json"""
@@ -123,9 +133,15 @@ def get_patterns_by_conditional_ratio(
     derived_list: list[tuple[str, str]],
     conditional_ratio_threshold: float,
     top_n: int,
+    post_id: str = "",
 ) -> list[dict[str, Any]]:
     """
-    从 pattern 库中获取条件概率 >= 阈值的 pattern,按条件概率降序(同分按 length 降序),返回 top_n 条。
+    从 pattern 库中获取条件概率 >= 阈值的 pattern,按以下优先级排序后返回 top_n 条:
+      1. pattern 元素中直接包含已推导选题点(topic)的排最前;
+      2. pattern 元素与任意已推导选题点的匹配分 >= 0.8 的次之(从 match_data 文件读取,
+         key 为 (帖子选题点, 人设树节点),pattern 元素视为人设树节点);
+      3. 按条件概率降序;
+      4. 按 length 降序。
     derived_list 为空时,条件概率使用 pattern 自身的 support(s)。
     返回每项:pattern名称(nameA+nameB+nameC)、条件概率。
     """
@@ -149,7 +165,31 @@ def get_patterns_by_conditional_ratio(
             if ratio >= conditional_ratio_threshold:
                 scored.append((p, ratio))
 
-    scored.sort(key=lambda x: (-x[1], -x[0]["l"]))
+    derived_topics = {topic for topic, _ in derived_list} if derived_list else set()
+
+    # 次优先:从 match_data 文件加载 (帖子选题点, 人设树节点) -> 匹配分,
+    # 用已推导选题点(topic)作为帖子选题点,pattern 元素作为人设树节点,
+    # 检查是否存在匹配分 >= 0.8 的组合。
+    match_lookup: dict[tuple[str, str], float] = {}
+    if derived_topics and post_id:
+        match_lookup = _load_match_data(account_name, post_id)
+
+    def _sort_key(x: tuple[dict, float]) -> tuple:
+        p, ratio = x
+        elements = set(p["i"].split("+"))
+        has_derived = bool(elements & derived_topics)
+        has_high_match = False
+        if not has_derived and match_lookup:
+            for elem in elements:
+                for dt in derived_topics:
+                    if match_lookup.get((dt, elem), 0.0) >= 0.8:
+                        has_high_match = True
+                        break
+                if has_high_match:
+                    break
+        return (not has_derived, not has_high_match, -ratio, -p["l"])
+
+    scored.sort(key=_sort_key)
     result = []
     for p, ratio in scored[:top_n]:
         result.append({
@@ -160,36 +200,41 @@ def get_patterns_by_conditional_ratio(
 
 
 @tool(
-    description="按条件概率从 pattern 库中筛选 pattern,返回达到阈值且按条件概率排序的前 topN 条。"
-    "功能:根据账号与已推导选题点(可选),筛选条件概率不低于阈值的 pattern。"
-    "参数:account_name 为账号名;derived_items 为已推导选题点列表,每项含 topic(或已推导的选题点)与 source_node(或推导来源人设树节点),可为空,为空时条件概率使用 pattern 自身的 support;conditional_ratio_threshold 为条件概率阈值;top_n 为返回条数上限,默认 20。"
-    "返回:ToolResult,output 为可读的 pattern 列表文本,metadata.items 为列表,每项含「pattern名称」(nameA+nameB+nameC 形式)、「条件概率」。"
+    description="按条件概率从 pattern 库中筛选 pattern,优先返回包含已推导选题点的 pattern,并检查每个 pattern 的元素是否与帖子选题点匹配。"
+    "功能:根据账号与已推导选题点(可选),筛选条件概率不低于阈值的 pattern;当 derived_items 非空时,优先返回 pattern 元素中包含已推导选题点的 pattern;同时对每个 pattern 的所有元素做帖子选题点匹配,匹配结果直接包含在返回数据中。"
+    "参数:account_name 为账号名;post_id 为帖子ID,用于加载帖子选题点并做匹配判断;derived_items 为已推导选题点列表,每项含 topic(或已推导的选题点)与 source_node(或推导来源人设树节点),可为空,为空时条件概率使用 pattern 自身的 support;conditional_ratio_threshold 为条件概率阈值;top_n 为返回条数上限,默认 100。"
+    "返回:ToolResult,output 为可读的 pattern 列表文本,metadata.items 为列表,每项含「pattern名称」(nameA+nameB+nameC 形式)、「条件概率」、「帖子选题点匹配」(匹配到帖子选题点的元素列表,每项含 pattern元素、帖子选题点与匹配分数;若无匹配则为字符串'无匹配帖子选题点')。"
 )
 async def find_pattern(
     account_name: str,
+    post_id: str,
     derived_items: list[dict[str, str]],
     conditional_ratio_threshold: float,
-    top_n: int = 20,
+    top_n: int = 100,
     context: Optional[ToolContext] = None,
 ) -> ToolResult:
     """
     按条件概率阈值从 pattern 库筛选 pattern,返回最多 top_n 条(按条件概率降序)。
+    当 derived_items 非空时,优先返回元素中包含已推导选题点的 pattern。
+    返回前对每个 pattern 的所有元素做帖子选题点匹配,匹配结果直接包含在返回数据中。
 
     参数
     -------
     account_name : 账号名,用于定位该账号的 pattern 库。
+    post_id : 帖子ID,用于加载帖子选题点并与 pattern 元素做匹配判断。
     derived_items : 已推导选题点列表,可为空。非空时每项为字典,需含 topic(或「已推导的选题点」)与 source_node(或「推导来源人设树节点」);为空时各 pattern 的条件概率取其自身 support。
     conditional_ratio_threshold : 条件概率阈值,仅返回条件概率 >= 该值的 pattern。
-    top_n : 返回条数上限,默认 20。
+    top_n : 返回条数上限,默认 100。
     context : 可选,Agent 工具上下文。
 
     返回
     -------
     ToolResult:
         - title: 结果标题。
-        - output: 可读的 pattern 列表文本(每行:pattern名称、条件概率)。
+        - output: 可读的 pattern 列表文本(每行:pattern名称、条件概率、帖子匹配情况)。
         - metadata: 含 account_name、conditional_ratio_threshold、top_n、count、items;
-          items 为列表,每项为 {"pattern名称": str, "条件概率": float}。
+          items 为列表,每项为 {"pattern名称": str, "条件概率": float,
+          "帖子选题点匹配": list[{"pattern元素": str, "帖子选题点": str, "匹配分数": float}] 或 "无匹配帖子选题点"}。
         - 出错时 error 为错误信息。
     """
     pattern_path = _pattern_file(account_name)
@@ -202,15 +247,49 @@ async def find_pattern(
     try:
         derived_list = _parse_derived_list(derived_items or [])
         items = get_patterns_by_conditional_ratio(
-            account_name, derived_list, conditional_ratio_threshold, top_n
+            account_name, derived_list, conditional_ratio_threshold, top_n, post_id
         )
+        # 批量收集所有 pattern 元素,统一做一次帖子选题点匹配
+        if items and post_id:
+            all_elements: list[str] = []
+            seen_elements: set[str] = set()
+            for item in items:
+                for elem in item["pattern名称"].split("+"):
+                    elem = elem.strip()
+                    if elem and elem not in seen_elements:
+                        all_elements.append(elem)
+                        seen_elements.add(elem)
+            matched_results = await _match_derivation_to_post_points(all_elements, account_name, post_id)
+            elem_match_map: dict[str, list] = {}
+            for m in matched_results:
+                elem_match_map.setdefault(m["推导选题点"], []).append({
+                    "帖子选题点": m["帖子选题点"],
+                    "匹配分数": m["匹配分数"],
+                })
+            for item in items:
+                pattern_matches = []
+                for elem in item["pattern名称"].split("+"):
+                    elem = elem.strip()
+                    for post_match in elem_match_map.get(elem, []):
+                        pattern_matches.append({
+                            "pattern元素": elem,
+                            "帖子选题点": post_match["帖子选题点"],
+                            "匹配分数": post_match["匹配分数"],
+                        })
+                item["帖子选题点匹配"] = pattern_matches if pattern_matches else "无匹配帖子选题点"
         if not items:
             output = f"未找到条件概率 >= {conditional_ratio_threshold} 的 pattern"
         else:
-            lines = [
-                f"- {x['pattern名称']}\t条件概率={x['条件概率']}"
-                for x in items
-            ]
+            lines = []
+            for x in items:
+                match_info = x.get("帖子选题点匹配", "未查询")
+                if isinstance(match_info, list):
+                    match_str = "、".join(
+                        f"{m['pattern元素']}→{m['帖子选题点']}({m['匹配分数']})" for m in match_info
+                    )
+                else:
+                    match_str = str(match_info)
+                lines.append(f"- {x['pattern名称']}\t条件概率={x['条件概率']}\t帖子匹配={match_str}")
             output = "\n".join(lines)
         return ToolResult(
             title=f"符合条件概率的 Pattern ({account_name}, 阈值={conditional_ratio_threshold})",
@@ -232,33 +311,36 @@ async def find_pattern(
 
 
 def main() -> None:
-    """本地测试:用家有大志账号、已推导选题点,查询符合条件概率阈值的 pattern。"""
+    """本地测试:用家有大志账号、已推导选题点,查询符合条件概率阈值的 pattern(含帖子匹配)。"""
     import asyncio
 
     account_name = "家有大志"
+    post_id = "68fb6a5c000000000302e5de"
     # 已推导选题点,每项:已推导的选题点 + 推导来源人设树节点
     derived_items = [
-        {"topic": "分享", "source_node": "分享"},
+        # {"topic": "分享", "source_node": "分享"},
         {"topic": "柴犬", "source_node": "动物角色"},
+        {"topic": "叙事结构", "source_node": "叙事逻辑"},
     ]
     conditional_ratio_threshold = 0.01
-    top_n = 10
+    top_n = 100
 
-    # 1)直接调用核心函数
+    # 1)直接调用核心函数(不含帖子匹配,仅验证排序逻辑)
     derived_list = _parse_derived_list(derived_items)
     items = get_patterns_by_conditional_ratio(
-        account_name, derived_list, conditional_ratio_threshold, top_n
+        account_name, derived_list, conditional_ratio_threshold, top_n, post_id
     )
     print(f"账号: {account_name}, 阈值: {conditional_ratio_threshold}, top_n: {top_n}")
     print(f"共 {len(items)} 条 pattern:\n")
     for x in items:
         print(f"  - {x['pattern名称']}\t条件概率={x['条件概率']}")
 
-    # 2)有 agent 时通过 tool 接口再跑一遍
+    # 2)有 agent 时通过 tool 接口再跑一遍(含帖子选题点匹配)
     if ToolResult is not None:
         async def run_tool():
             result = await find_pattern(
                 account_name=account_name,
+                post_id=post_id,
                 derived_items=derived_items,
                 conditional_ratio_threshold=conditional_ratio_threshold,
                 top_n=top_n,

+ 81 - 24
examples_how/overall_derivation/tools/find_tree_node.py

@@ -32,6 +32,15 @@ calc_node_conditional_ratio = _cond_mod.calc_node_conditional_ratio
 # 相对本文件:tools -> overall_derivation,input 在 overall_derivation 下
 _BASE_INPUT = Path(__file__).resolve().parent.parent / "input"
 
+# 加载 point_match(用于检查节点是否匹配帖子选题点)
+_point_match_spec = importlib.util.spec_from_file_location(
+    "point_match",
+    Path(__file__).resolve().parent / "point_match.py",
+)
+_point_match_mod = importlib.util.module_from_spec(_point_match_spec)
+_point_match_spec.loader.exec_module(_point_match_mod)
+_match_derivation_to_post_points = _point_match_mod.match_derivation_to_post_points
+
 
 def _tree_dir(account_name: str) -> Path:
     """人设树目录:../input/{account_name}/原始数据/tree/"""
@@ -167,29 +176,33 @@ def _parse_derived_list(derived_items: list[dict[str, str]]) -> list[tuple[str,
 # ---------------------------------------------------------------------------
 
 @tool(
-    description="获取指定账号人设树中的常量节点(全局常量、局部常量)。"
-    "功能:根据账号名查询该账号人设树中所有常量节点。"
-    "参数:account_name 为账号名。"
-    "返回:ToolResult,output 为可读的节点列表文本,metadata.items 为列表,每项含「节点名称」「概率」「常量类型」。"
+    description="获取指定账号人设树中的常量节点(全局常量、局部常量),并检查每个节点与帖子选题点的匹配情况。"
+    "功能:根据账号名查询该账号人设树中所有常量节点,同时对每个节点判断是否匹配帖子选题点,匹配结果直接包含在返回数据中。"
+    "参数:account_name 为账号名;post_id 为帖子ID,用于加载帖子选题点并做匹配判断。"
+    "返回:ToolResult,output 为可读的节点列表文本,metadata.items 为列表,每项含「节点名称」「概率」「常量类型」「帖子选题点匹配」(超过阈值的匹配列表,每项含帖子选题点与匹配分数;若无匹配则为字符串'无匹配帖子选题点')。"
 )
 async def find_tree_constant_nodes(
     account_name: str,
+    post_id: str,
     context: Optional[ToolContext] = None,
 ) -> ToolResult:
     """
-    获取人设树中的常量节点列表(全局常量与局部常量)。
+    获取人设树中的常量节点列表(全局常量与局部常量),并检查每个节点与帖子选题点的匹配情况
 
     参数
     -------
     account_name : 账号名,用于定位该账号的人设树数据。
+    post_id : 帖子ID,用于加载帖子选题点并与各常量节点做匹配判断。
     context : 可选,Agent 工具上下文。
 
     返回
     -------
     ToolResult:
         - title: 结果标题。
-        - output: 可读的节点列表文本(每行:节点名称、概率、常量类型)。
-        - metadata: 含 account_name、count、items;items 为列表,每项为 {"节点名称": str, "概率": 数值或 None, "常量类型": "全局常量"|"局部常量"}。
+        - output: 可读的节点列表文本(每行:节点名称、概率、常量类型、帖子匹配情况)。
+        - metadata: 含 account_name、count、items;items 为列表,每项为
+          {"节点名称": str, "概率": 数值或 None, "常量类型": "全局常量"|"局部常量",
+           "帖子选题点匹配": list[{"帖子选题点": str, "匹配分数": float}] 或 "无匹配帖子选题点"}。
         - 出错时 error 为错误信息。
     """
     tree_dir = _tree_dir(account_name)
@@ -201,10 +214,30 @@ async def find_tree_constant_nodes(
         )
     try:
         items = get_constant_nodes(account_name)
+        # 批量匹配所有节点与帖子选题点
+        if items and post_id:
+            node_names = [x["节点名称"] for x in items]
+            matched_results = await _match_derivation_to_post_points(node_names, account_name, post_id)
+            node_match_map: dict[str, list] = {}
+            for m in matched_results:
+                node_match_map.setdefault(m["推导选题点"], []).append({
+                    "帖子选题点": m["帖子选题点"],
+                    "匹配分数": m["匹配分数"],
+                })
+            for item in items:
+                matches = node_match_map.get(item["节点名称"], [])
+                item["帖子选题点匹配"] = matches if matches else "无匹配帖子选题点"
         if not items:
             output = "未找到常量节点"
         else:
-            lines = [f"- {x['节点名称']}\t概率={x['概率']}\t{x['常量类型']}" for x in items]
+            lines = []
+            for x in items:
+                match_info = x.get("帖子选题点匹配", "未查询")
+                if isinstance(match_info, list):
+                    match_str = "、".join(f"{m['帖子选题点']}({m['匹配分数']})" for m in match_info)
+                else:
+                    match_str = str(match_info)
+                lines.append(f"- {x['节点名称']}\t概率={x['概率']}\t{x['常量类型']}\t帖子匹配={match_str}")
             output = "\n".join(lines)
         return ToolResult(
             title=f"常量节点 ({account_name})",
@@ -220,36 +253,39 @@ async def find_tree_constant_nodes(
 
 
 @tool(
-    description="按条件概率从人设树中筛选节点,返回达到阈值且按条件概率排序的前 topN 条。"
-    "功能:根据账号与已推导选题点(可选),筛选人设树中条件概率不低于阈值的节点。"
-    "参数:account_name 为账号名;derived_items 为已推导选题点列表,每项含 topic(或已推导的选题点)与 source_node(或推导来源人设树节点),可为空,为空时条件概率使用节点自身的 _ratio;conditional_ratio_threshold 为条件概率阈值;top_n 为返回条数上限,默认 20。"
-    "返回:ToolResult,output 为可读的节点列表文本,metadata.items 为列表,每项含「节点名称」「条件概率」「父节点名称」。"
+    description="按条件概率从人设树中筛选节点,返回达到阈值且按条件概率排序的前 topN 条,并检查每个节点与帖子选题点的匹配情况。"
+    "功能:根据账号与已推导选题点(可选),筛选人设树中条件概率不低于阈值的节点,同时对每个节点判断是否匹配帖子选题点,匹配结果直接包含在返回数据中。"
+    "参数:account_name 为账号名;post_id 为帖子ID,用于加载帖子选题点并做匹配判断;derived_items 为已推导选题点列表,每项含 topic(或已推导的选题点)与 source_node(或推导来源人设树节点),可为空,为空时条件概率使用节点自身的 _ratio;conditional_ratio_threshold 为条件概率阈值;top_n 为返回条数上限,默认 100。"
+    "返回:ToolResult,output 为可读的节点列表文本,metadata.items 为列表,每项含「节点名称」「条件概率」「父节点名称」「帖子选题点匹配」(超过阈值的匹配列表,每项含帖子选题点与匹配分数;若无匹配则为字符串'无匹配帖子选题点')。"
 )
 async def find_tree_nodes_by_conditional_ratio(
     account_name: str,
+    post_id: str,
     derived_items: list[dict[str, str]],
     conditional_ratio_threshold: float,
-    top_n: int = 20,
+    top_n: int = 100,
     context: Optional[ToolContext] = None,
 ) -> ToolResult:
     """
-    按条件概率阈值从人设树筛选节点,返回最多 top_n 条(按条件概率降序)。
+    按条件概率阈值从人设树筛选节点,返回最多 top_n 条(按条件概率降序),并检查每个节点与帖子选题点的匹配情况
 
     参数
     -------
     account_name : 账号名,用于定位该账号的人设树数据。
+    post_id : 帖子ID,用于加载帖子选题点并与各节点做匹配判断。
     derived_items : 已推导选题点列表,可为空。非空时每项为字典,需含 topic(或「已推导的选题点」)与 source_node(或「推导来源人设树节点」);为空时各节点的条件概率取其自身 _ratio。
     conditional_ratio_threshold : 条件概率阈值,仅返回条件概率 >= 该值的节点。
-    top_n : 返回条数上限,默认 20。
+    top_n : 返回条数上限,默认 100。
     context : 可选,Agent 工具上下文。
 
     返回
     -------
     ToolResult:
         - title: 结果标题。
-        - output: 可读的节点列表文本(每行:节点名称、条件概率、父节点名称)。
+        - output: 可读的节点列表文本(每行:节点名称、条件概率、父节点名称、帖子匹配情况)。
         - metadata: 含 account_name、threshold、top_n、count、items;
-          items 为列表,每项为 {"节点名称": str, "条件概率": float, "父节点名称": str}。
+          items 为列表,每项为 {"节点名称": str, "条件概率": float, "父节点名称": str,
+          "帖子选题点匹配": list[{"帖子选题点": str, "匹配分数": float}] 或 "无匹配帖子选题点"}。
         - 出错时 error 为错误信息。
     """
     tree_dir = _tree_dir(account_name)
@@ -264,13 +300,32 @@ async def find_tree_nodes_by_conditional_ratio(
         items = get_nodes_by_conditional_ratio(
             account_name, derived_list, conditional_ratio_threshold, top_n
         )
+        # 批量匹配所有节点与帖子选题点
+        if items and post_id:
+            node_names = [x["节点名称"] for x in items]
+            matched_results = await _match_derivation_to_post_points(node_names, account_name, post_id)
+            node_match_map: dict[str, list] = {}
+            for m in matched_results:
+                node_match_map.setdefault(m["推导选题点"], []).append({
+                    "帖子选题点": m["帖子选题点"],
+                    "匹配分数": m["匹配分数"],
+                })
+            for item in items:
+                matches = node_match_map.get(item["节点名称"], [])
+                item["帖子选题点匹配"] = matches if matches else "无匹配帖子选题点"
         if not items:
             output = f"未找到条件概率 >= {conditional_ratio_threshold} 的节点"
         else:
-            lines = [
-                f"- {x['节点名称']}\t条件概率={x['条件概率']}\t父节点={x['父节点名称']}"
-                for x in items
-            ]
+            lines = []
+            for x in items:
+                match_info = x.get("帖子选题点匹配", "未查询")
+                if isinstance(match_info, list):
+                    match_str = "、".join(f"{m['帖子选题点']}({m['匹配分数']})" for m in match_info)
+                else:
+                    match_str = str(match_info)
+                lines.append(
+                    f"- {x['节点名称']}\t条件概率={x['条件概率']}\t父节点={x['父节点名称']}\t帖子匹配={match_str}"
+                )
             output = "\n".join(lines)
         return ToolResult(
             title=f"条件概率节点 ({account_name}, 阈值={conditional_ratio_threshold})",
@@ -296,13 +351,14 @@ def main() -> None:
     import asyncio
 
     account_name = "家有大志"
+    post_id = "68fb6a5c000000000302e5de"
     derived_items = [
         {"topic": "分享", "source_node": "分享"},
     ]
     conditional_ratio_threshold = 0.1
     top_n = 10
 
-    # 1)常量节点
+    # 1)常量节点(核心函数,无匹配)
     constant_nodes = get_constant_nodes(account_name)
     print(f"账号: {account_name} — 常量节点共 {len(constant_nodes)} 个(前 50 个):")
     for x in constant_nodes[:50]:
@@ -319,14 +375,15 @@ def main() -> None:
         print(f"  - {x['节点名称']}\t条件概率={x['条件概率']}\t父节点={x['父节点名称']}")
     print()
 
-    # 3)有 agent 时通过 tool 接口再跑一遍
+    # 3)有 agent 时通过 tool 接口再跑一遍(含帖子选题点匹配)
     if ToolResult is not None:
         async def run_tools():
-            r1 = await find_tree_constant_nodes(account_name)
+            r1 = await find_tree_constant_nodes(account_name, post_id=post_id)
             print("--- find_tree_constant_nodes ---")
             print(r1.output[:200] + "..." if len(r1.output) > 200 else r1.output)
             r2 = await find_tree_nodes_by_conditional_ratio(
                 account_name,
+                post_id=post_id,
                 derived_items=derived_items,
                 conditional_ratio_threshold=conditional_ratio_threshold,
                 top_n=top_n,