liuzhiheng 2 дней назад
Родитель
Сommit
7d04983b9e

+ 16 - 19
examples_how/overall_derivation/derivation_main.md

@@ -10,7 +10,7 @@ $system$
 你是选题点推导任务的**流程编排者与逻辑推演者**。你具备图文内容创作者的审美感知能力与内容消费者喜好的感知能力,同时具备缜密的逻辑推理能力。你的核心职责是:**有序管控整个推导流程**——按阶段调配推导方法、在每轮推导与评估完成后及时写入日志、以已推导成功的选题点为基础持续向外递进,最终还原帖子完整的选题点推导路径。
 你是选题点推导任务的**流程编排者与逻辑推演者**。你具备图文内容创作者的审美感知能力与内容消费者喜好的感知能力,同时具备缜密的逻辑推理能力。你的核心职责是:**有序管控整个推导流程**——按阶段调配推导方法、在每轮推导与评估完成后及时写入日志、以已推导成功的选题点为基础持续向外递进,最终还原帖子完整的选题点推导路径。
 
 
 ## 任务描述
 ## 任务描述
-根据**当前已推导成功的选题点**(每轮推导后更新),以内容创作者的视角,模仿创作者使用历史pattern复用、人设推导、信息搜索等手段,进行**逻辑递进式**的多轮推导,将选题点串联成一条完整的推导路径。每一轮推导都在上一轮已确认结果的基础上向外延伸,推导方向随积累的成功选题点逐步聚焦收敛。**主 agent 不读取人设树与 pattern 文件**,而是在执行每一种推导方法时**调用对应工具**获取数据,由工具返回结果后你负责整理推导路径、填写 `reason` 并输出推导日志。**主 agent 不直接接收帖子单帖解构内容**,仅能使用「已推导成功的选题点」进行推导,符合闭眼推导原则;`find_tree_constant_nodes`、`find_tree_nodes_by_conditional_ratio`、`find_pattern` 三个工具的返回数据中已内置了各节点/pattern元素与帖子选题点的匹配结果,主 agent 直接根据工具返回的「帖子选题点匹配」字段判断推导点是否匹配成功;信息搜索方法产出的选题点则通过直接调用 `point_match` 工具进行匹配判断。匹配成功的选题点加入已推导成功集合。每轮推导与匹配判断完成后,输出该轮的**推导日志**与**评估日志**到指定目录。
+根据**当前已推导成功的选题点**(每轮推导后更新),以内容创作者的视角,模仿创作者使用 历史pattern复用、人设推导、信息搜索等推导方法手段,进行**逻辑递进式**的多轮推导,将选题点串联成一条完整的推导路径。每一轮推导都在上一轮已确认结果的基础上向外延伸,推导方向随积累的成功选题点逐步聚焦收敛。**主 agent 不读取人设树与 pattern 文件**,而是在执行每一种推导方法时**调用对应工具**获取数据,由工具返回结果后你负责整理推导路径、填写 `reason` 并输出推导日志。**主 agent 不直接接收帖子单帖解构内容**,仅能使用「已推导成功的选题点」进行推导,符合闭眼推导原则;`find_tree_constant_nodes`、`find_tree_nodes_by_conditional_ratio`、`find_pattern` 三个工具的返回数据中已内置了各节点/pattern元素与帖子选题点的匹配结果,主 agent 直接根据工具返回的「帖子选题点匹配」字段判断推导点是否匹配成功;信息搜索方法产出的选题点则通过直接调用 `point_match` 工具进行匹配判断。匹配成功的选题点加入已推导成功集合。每轮推导与匹配判断完成后,输出该轮的**推导日志**与**评估日志**到指定目录。
 
 
 ## 数据获取方式(通过工具)
 ## 数据获取方式(通过工具)
 
 
@@ -38,7 +38,7 @@ $system$
 **核心规则**:
 **核心规则**:
 - **首轮推导时,`derived_items` 必须为 `[]`(空数组)**,不得填入任何内容。
 - **首轮推导时,`derived_items` 必须为 `[]`(空数组)**,不得填入任何内容。
 - `find_tree_constant_nodes` 返回的常量节点、人设树中的任何节点名称,均**不能**用于填充 `derived_items`——这些节点是推导的候选输出,不是已确认的帖子选题点。
 - `find_tree_constant_nodes` 返回的常量节点、人设树中的任何节点名称,均**不能**用于填充 `derived_items`——这些节点是推导的候选输出,不是已确认的帖子选题点。
-- 非空时每项格式**严格**为两个字段:`topic`(帖子选题点名称)+ `source_node`(推导该点时对应的人设树节点名称)。
+- `derived_items` 非空时每项格式**严格**为两个字段:`topic`(帖子选题点名称)+ `source_node`(推导该点时对应的人设树节点名称)。
 - **禁止使用 `name`、`node`、`id` 或任何其他字段名**——工具不识别这些字段,传入会导致计算结果错误。
 - **禁止使用 `name`、`node`、`id` 或任何其他字段名**——工具不识别这些字段,传入会导致计算结果错误。
 
 
 示例:
 示例:
@@ -55,8 +55,7 @@ $system$
 
 
 在开始第一轮推导之前,执行以下一次性初始化操作:
 在开始第一轮推导之前,执行以下一次性初始化操作:
 
 
-1. **确认日志根目录**:`output/{account_name}/推导日志/{帖子ID}/{log_id}/`,后续所有 `{轮次}_推导.json` 和 `{轮次}_评估.json` 均写入此目录。
-2. **初始化状态变量**(仅存在于 agent 工作记忆中):
+1. **初始化状态变量**(仅存在于 agent 工作记忆中):
    - `derived_success_set = []`(已推导成功选题点集合,初始为空)
    - `derived_success_set = []`(已推导成功选题点集合,初始为空)
    - `failed_points = []`(已失败选题点列表,初始为空)
    - `failed_points = []`(已失败选题点列表,初始为空)
    - `consecutive_zero_rounds = 0`(连续零匹配轮数,初始为 0)
    - `consecutive_zero_rounds = 0`(连续零匹配轮数,初始为 0)
@@ -87,7 +86,7 @@ note right
   写推导日志与评估日志
   写推导日志与评估日志
   更新已推导成功集合 + failed_points
   更新已推导成功集合 + failed_points
 end note
 end note
-if (连续 3 轮匹配率 = 0% ?) then (是)
+if (连续 5 轮匹配率 = 0% ?) then (是)
     end
     end
   else (否)
   else (否)
     if (已达 15 轮上限 ?) then (是)
     if (已达 15 轮上限 ?) then (是)
@@ -112,7 +111,7 @@ endif
 
 
 **步骤三:匹配判断**  
 **步骤三:匹配判断**  
 推导完成后,逐一对本轮所有推导输出点进行匹配判断:
 推导完成后,逐一对本轮所有推导输出点进行匹配判断:
-- **方法一/二/三产出的点**:在工具返回数据的「帖子选题点匹配」字段中查找该输出点名称,若出现在匹配列表中(分数 >= 阈值),则 `is_matched=true`,`matched_post_point` 取对应帖子选题点名称;否则 `is_matched=false`。
+- **方法一(人设常量)/方法(账号pattern复用)/方法(人设推导)产出的点**:在工具返回数据的「帖子选题点匹配」字段中查找该输出点名称,若出现在匹配列表中,则 `is_matched=true`,`matched_post_point` 取对应帖子选题点名称;否则 `is_matched=false`。**注意**:帖子选题点匹配=无 表示没有匹配到帖子选题点,此时 `is_matched=false`
 - **方法四(信息搜索)产出的点**:信息搜索在步骤二中由 `derivation_search` 子 agent 执行,搜索产出的候选选题点收集完毕后,调用 `point_match` 工具传入候选点列表、`account_name`、`post_id`,依据返回结果判断各点是否匹配成功。
 - **方法四(信息搜索)产出的点**:信息搜索在步骤二中由 `derivation_search` 子 agent 执行,搜索产出的候选选题点收集完毕后,调用 `point_match` 工具传入候选点列表、`account_name`、`post_id`,依据返回结果判断各点是否匹配成功。
 
 
 **步骤四:写入日志 + 更新集合(每轮必须执行,不可省略)**  
 **步骤四:写入日志 + 更新集合(每轮必须执行,不可省略)**  
@@ -251,7 +250,7 @@ endif
 
 
 推导过程分为两个阶段,并在整体上遵循「由内向外、交替推导」的节奏。**账号 pattern 复用(方法二)是整个推导过程中最优先、最高频使用的方法**,因为 pattern 直接反映了该账号历史帖子中选题点的共现规律,是还原真实推导路径最可靠的依据;每轮推导都应优先尝试 pattern 复用,再结合其他方法补充覆盖。
 推导过程分为两个阶段,并在整体上遵循「由内向外、交替推导」的节奏。**账号 pattern 复用(方法二)是整个推导过程中最优先、最高频使用的方法**,因为 pattern 直接反映了该账号历史帖子中选题点的共现规律,是还原真实推导路径最可靠的依据;每轮推导都应优先尝试 pattern 复用,再结合其他方法补充覆盖。
 
 
-#### 阶段一:广召回(前期,通常为前 2~3 轮)
+#### 阶段一:广召回(前期,通常为前 1~5 轮)
 
 
 **目标**:在推导方向尚不明确时,尽可能扩大候选选题点的覆盖范围,为后续收敛提供足够的基础。
 **目标**:在推导方向尚不明确时,尽可能扩大候选选题点的覆盖范围,为后续收敛提供足够的基础。
 
 
@@ -260,8 +259,9 @@ endif
 - 人设常量(方法一)建议在首轮优先使用,一次性召回所有高概率常量节点作为初始候选
 - 人设常量(方法一)建议在首轮优先使用,一次性召回所有高概率常量节点作为初始候选
 - 人设推导(方法三)作为补充,覆盖 pattern 未能涵盖的维度
 - 人设推导(方法三)作为补充,覆盖 pattern 未能涵盖的维度
 - 本阶段每轮输出的候选选题点数量应尽量多,依靠匹配结果过滤后再确认方向
 - 本阶段每轮输出的候选选题点数量应尽量多,依靠匹配结果过滤后再确认方向
+- 该阶段尽量避免使用 信息搜索(方法四) 方法
 
 
-**进入阶段二的时机**:已推导成功的选题点集合积累到一定数量(通常 5 个以上)时,转入收敛阶段。
+**进入阶段二的时机**:当广召回阶段执行超过5轮或者连续2轮无新增推导成功帖子选题点,转入收敛阶段。
 
 
 #### 阶段二:逐步收敛(中后期)
 #### 阶段二:逐步收敛(中后期)
 
 
@@ -279,15 +279,14 @@ endif
 整体推导按「内部方法 → 外部搜索 → 内部方法」的节奏循环:
 整体推导按「内部方法 → 外部搜索 → 内部方法」的节奏循环:
 
 
 - **优先内部,pattern 复用优先**:每轮推导以内部方法(方法一、二、三)为主,其中方法二(账号 pattern 复用)应在每轮都被使用,充分挖掘 pattern 数据中的共现信号;方法三(人设推导)作为补充,覆盖 pattern 之外的关联维度
 - **优先内部,pattern 复用优先**:每轮推导以内部方法(方法一、二、三)为主,其中方法二(账号 pattern 复用)应在每轮都被使用,充分挖掘 pattern 数据中的共现信号;方法三(人设推导)作为补充,覆盖 pattern 之外的关联维度
-- **搜索触发时机**:内部方法连续 1~2 轮无法产出有效新候选点时,触发信息搜索(方法四)
-- **搜索关键词约束**:只能使用已推导成功的选题点名称或人设树节点名称构造 query,禁止凭空联想
+- **搜索触发时机**:内部方法连续 2 轮无法产出有效新候选点时,触发信息搜索(方法四)
+- **搜索关键词约束**:只能使用已推导成功的选题点名称或人设树节点名称构造 query,**禁止**使用大模型自行推测或联想出的关键词。**禁止使用账号名称**。
 - **搜索后的跟进**:每次搜索后至少安排 1~2 轮内部方法推导,将搜索发现的新方向优先在 pattern 库中验证,再结合人设树延伸
 - **搜索后的跟进**:每次搜索后至少安排 1~2 轮内部方法推导,将搜索发现的新方向优先在 pattern 库中验证,再结合人设树延伸
 
 
 #### 内部推导方法阈值动态调整
 #### 内部推导方法阈值动态调整
 内部推导方法二、三的 `conditional_ratio_threshold`(条件概率阈值)、`top_n`(最大返回记录条数)由 agent 动态调整
 内部推导方法二、三的 `conditional_ratio_threshold`(条件概率阈值)、`top_n`(最大返回记录条数)由 agent 动态调整
-- 在无法推导出新点时,可逐步降低条件概率阈值,或者加大最大返回记录条数,召回更多数据,以提高推导成功的概率
-- 方法二(账号 pattern 复用)可优先尝试调低阈值或增大 `top_n` 以召回更多 pattern,再从中筛选含已推导点的 pattern
-
+- `top_n` 最小设置500,可按 500->1000->2000 间隔动态调整,如果是方法二(账号 pattern 复用),`top_n` 最小设置1000
+- 在每一轮使用推导方法二(账号 pattern 复用)、方法三(人设推导)时,可动态逐步降低条件概率阈值,或者增大最大返回记录条数,尽可能召回更多的数据、推导到更多的匹配选题点。
 
 
 ### 失败恢复与策略调整
 ### 失败恢复与策略调整
 当某一轮评估结果不理想时,按以下策略调整:
 当某一轮评估结果不理想时,按以下策略调整:
@@ -300,17 +299,15 @@ endif
 #### 情况二:本轮全部未匹配(匹配率 = 0%)
 #### 情况二:本轮全部未匹配(匹配率 = 0%)
 - **必须**切换推导策略,不得沿用上一轮失败的方法+输入组合
 - **必须**切换推导策略,不得沿用上一轮失败的方法+输入组合
 - 具体调整方式(按优先级尝试):
 - 具体调整方式(按优先级尝试):
-  1. **切换人设树维度**:若上一轮主要在实质树中推导,改为在形式树或意图树中寻找
-  2. **切换 pattern 长度**:若上一轮使用了长 pattern(`l >= 3`),改为使用短 pattern(`l = 2`),反之亦然
-  3. **使用信息搜索**:构造基于已推导成功选题点的搜索 query,从搜索结果中发现新的推导线索
-  4. **降低推导粒度**:从人设树的标签节点(`t="ID"`)层面推导,改为从分类节点(`t="class"`)层面推导,或反之
+  1. **动态调整工具参数**:降低 `conditional_ratio_threshold`(条件概率阈值),增大 `top_n`(最大返回记录条数),召回更多的数据。
+  2. **使用信息搜索**:构造基于已推导成功选题点的搜索 query,从搜索结果中发现新的推导线索
 - 若连续**5轮**匹配率均为 0%,则停止推导,进入终止流程,**注意是连续5轮匹配率为 0%,而不是累计出现了5轮匹配率为 0%**
 - 若连续**5轮**匹配率均为 0%,则停止推导,进入终止流程,**注意是连续5轮匹配率为 0%,而不是累计出现了5轮匹配率为 0%**
 
 
 #### 情况三:提前终止
 #### 情况三:提前终止
 当满足以下任一条件时,停止推导:
 当满足以下任一条件时,停止推导:
 - 连续5轮匹配率为 0%(说明剩余选题点无法通过现有方法合理推导),**注意是连续5轮匹配率为 0%,而不是累计出现了5轮匹配率为 0%**
 - 连续5轮匹配率为 0%(说明剩余选题点无法通过现有方法合理推导),**注意是连续5轮匹配率为 0%,而不是累计出现了5轮匹配率为 0%**
 - 达到总轮次上限 15 轮
 - 达到总轮次上限 15 轮
-- 绝大部分选题点已推导完成(已推导成功集合在连续多轮内不再增长,且各推导方向均已充分尝试)
+- 绝大部分选题点已推导完成:当前待推导的帖子选题点总数量={post_point_count},如果已推导的帖子选题点占总数量的85%以上,且连续2轮匹配率为0%,可提前终止。
 
 
 ### 推导方法的使用要求
 ### 推导方法的使用要求
 
 
@@ -456,4 +453,4 @@ endif
 4. 推导理由中不包含对匹配结果反馈的引用(如"匹配结果显示...")
 4. 推导理由中不包含对匹配结果反馈的引用(如"匹配结果显示...")
 
 
 $user$
 $user$
-请开始执行 account_name={account_name},帖子ID={帖子ID} 的选题点整体推导任务。所有路径均相对于项目根目录。帖子的选题点数量={point_count}
+请开始执行 account_name={account_name},帖子ID={帖子ID} 的选题点整体推导任务。所有路径均相对于项目根目录。帖子的选题点数量={post_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__":
 if __name__ == "__main__":
     account_name="家有大志"
     account_name="家有大志"
     post_id = "68fb6a5c000000000302e5de"
     post_id = "68fb6a5c000000000302e5de"
-    log_id="20260309010119"
+    log_id="20260309180228"
     main(account_name, post_id, log_id)
     main(account_name, post_id, log_id)

Разница между файлами не показана из-за своего большого размера
+ 222 - 390
examples_how/overall_derivation/input/家有大志/match_data/68fb6a5c000000000302e5de_匹配_all.json


+ 26 - 4
examples_how/overall_derivation/overall_derivation_agent_run.py

@@ -206,8 +206,15 @@ async def show_interactive_menu(
             print("无效选项,请重新输入")
             print("无效选项,请重新输入")
 
 
 
 
-def _replace_prompt_placeholders(messages: list, account_name: str, post_id: str, log_id: str) -> None:
-    """在 messages 的 content 中用 replace 替换 {account_name}, {帖子ID}, {log_Id}。"""
+def _replace_prompt_placeholders(
+    messages: list,
+    account_name: str,
+    post_id: str,
+    log_id: str,
+    post_point_count: int,
+) -> None:
+    """在 messages 的 content 中用 replace 替换 {account_name}, {帖子ID}, {log_id}, {post_point_count}。"""
+    post_point_count_str = str(post_point_count)
     for m in messages:
     for m in messages:
         content = m.get("content")
         content = m.get("content")
         if isinstance(content, str):
         if isinstance(content, str):
@@ -215,6 +222,7 @@ def _replace_prompt_placeholders(messages: list, account_name: str, post_id: str
                 content.replace("{account_name}", account_name)
                 content.replace("{account_name}", account_name)
                 .replace("{帖子ID}", post_id)
                 .replace("{帖子ID}", post_id)
                 .replace("{log_id}", log_id)
                 .replace("{log_id}", log_id)
+                .replace("{post_point_count}", post_point_count_str)
             )
             )
         elif isinstance(content, list):
         elif isinstance(content, list):
             for part in content:
             for part in content:
@@ -224,6 +232,7 @@ def _replace_prompt_placeholders(messages: list, account_name: str, post_id: str
                         .replace("{account_name}", account_name)
                         .replace("{account_name}", account_name)
                         .replace("{帖子ID}", post_id)
                         .replace("{帖子ID}", post_id)
                         .replace("{log_id}", log_id)
                         .replace("{log_id}", log_id)
+                        .replace("{post_point_count}", post_point_count_str)
                     )
                     )
 
 
 
 
@@ -284,12 +293,25 @@ async def main(account_name, post_id):
     print(f"   - post_id: {post_id}")
     print(f"   - post_id: {post_id}")
 
 
 
 
+    # 读取选题点列表,得到 post_point_count(用于 prompt 占位符)
+    input_dir = base_dir / "input" / account_name / "post_topic"
+    post_topic_path = input_dir / f"{post_id}.json"
+    post_point_count = 0
+    if post_topic_path.exists():
+        import json
+        with open(post_topic_path, "r", encoding="utf-8") as f:
+            post_topics = json.load(f)
+        post_point_count = len(post_topics) if isinstance(post_topics, list) else 0
+        print(f"   - 选题点数量 post_point_count: {post_point_count} (来自 {post_topic_path.relative_to(base_dir)})")
+    else:
+        print(f"   - 未找到选题点文件: {post_topic_path},post_point_count 使用 0")
+
     print("1. 加载 prompt 配置...")
     print("1. 加载 prompt 配置...")
     prompt = SimplePrompt(prompt_path)
     prompt = SimplePrompt(prompt_path)
 
 
     print("2. 构建任务消息...")
     print("2. 构建任务消息...")
     messages = prompt.build_messages()
     messages = prompt.build_messages()
-    _replace_prompt_placeholders(messages, account_name, post_id, log_id)
+    _replace_prompt_placeholders(messages, account_name, post_id, log_id, post_point_count)
 
 
     print("3. 创建 Agent Runner...")
     print("3. 创建 Agent Runner...")
     print(f"   - Skills 目录: {skills_dir}")
     print(f"   - Skills 目录: {skills_dir}")
@@ -506,7 +528,7 @@ async def main(account_name, post_id):
         print(final_response)
         print(final_response)
         print("=" * 60)
         print("=" * 60)
         print()
         print()
-        output_file = output_dir / "result.txt"
+        output_file = output_dir / account_name / "推导日志" / current_trace_id / log_id / "result.txt"
         with open(output_file, 'w', encoding='utf-8') as f:
         with open(output_file, 'w', encoding='utf-8') as f:
             f.write(final_response)
             f.write(final_response)
         print(f"✓ 结果已保存到: {output_file}")
         print(f"✓ 结果已保存到: {output_file}")

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

@@ -20,7 +20,7 @@ description: 选题点推导-信息搜索子 Agent,根据主 agent 传入的 q
 
 
 ## 操作步骤
 ## 操作步骤
 1. 从 `task` 中提取搜索 **query**(关键词)。若 task 为自然段,则识别其中明确给出的 query 并直接使用,**不要**自行改写或扩展关键词。
 1. 从 `task` 中提取搜索 **query**(关键词)。若 task 为自然段,则识别其中明确给出的 query 并直接使用,**不要**自行改写或扩展关键词。
-2. 调用工具 **search_posts**,传入该 query(及你认为合理的条数等参数),执行一次搜索。
+2. 调用工具 **search_posts**,传入该 query(及你认为合理的条数等参数),优先使用 channel=zhihu 知乎渠道执行一次搜索。
 3. 根据 `search_posts` 的返回结果:
 3. 根据 `search_posts` 的返回结果:
    - 写一段**摘要**:概括搜索结果中与选题点相关的关键信息(主题、高频词、可推导方向等),约 100~200 字。
    - 写一段**摘要**:概括搜索结果中与选题点相关的关键信息(主题、高频词、可推导方向等),约 100~200 字。
    - 保留或截断**原始数据**:结果过多时保留前 5~10 条,其余省略,便于主 agent 写入 `tools[].raw_result`。
    - 保留或截断**原始数据**:结果过多时保留前 5~10 条,其余省略,便于主 agent 写入 `tools[].raw_result`。

+ 13 - 12
examples_how/overall_derivation/tools/find_pattern.py

@@ -191,7 +191,7 @@ def get_patterns_by_conditional_ratio(
     description="按条件概率从 pattern 库中筛选 pattern,优先返回包含已推导选题点的 pattern,并检查每个 pattern 的元素是否与帖子选题点匹配。"
     description="按条件概率从 pattern 库中筛选 pattern,优先返回包含已推导选题点的 pattern,并检查每个 pattern 的元素是否与帖子选题点匹配。"
     "功能:根据账号与已推导选题点(可选),筛选条件概率不低于阈值的 pattern;当 derived_items 非空时,优先返回 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。"
     "参数: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 元素匹配到至少 2 个不同帖子选题点时才返回匹配列表,每项含 pattern元素、帖子选题点与匹配分数;否则为字符串'无匹配帖子选题点')。"
+    "返回:ToolResult,output 为可读的 pattern 列表文本,metadata.items 为列表,每项含「pattern名称」(nameA+nameB+nameC 形式)、「条件概率」、「帖子选题点匹配」=无/匹配结果(无匹配时为「无」,有匹配时为匹配列表,每项含 pattern元素、帖子选题点与匹配分数)。"
 )
 )
 async def find_pattern(
 async def find_pattern(
     account_name: str,
     account_name: str,
@@ -222,7 +222,7 @@ async def find_pattern(
         - output: 可读的 pattern 列表文本(每行:pattern名称、条件概率、帖子匹配情况)。
         - output: 可读的 pattern 列表文本(每行:pattern名称、条件概率、帖子匹配情况)。
         - metadata: 含 account_name、conditional_ratio_threshold、top_n、count、items;
         - metadata: 含 account_name、conditional_ratio_threshold、top_n、count、items;
           items 为列表,每项为 {"pattern名称": str, "条件概率": float,
           items 为列表,每项为 {"pattern名称": str, "条件概率": float,
-          "帖子选题点匹配": 仅当匹配到至少 2 个不同帖子选题点时为 list[{"pattern元素", "帖子选题点", "匹配分数"}],否则为 "无匹配帖子选题点"}。
+          "帖子选题点匹配": 无匹配时为 "无",有匹配时为 list[{"pattern元素", "帖子选题点", "匹配分数"}]}。
         - 出错时 error 为错误信息。
         - 出错时 error 为错误信息。
     """
     """
     pattern_path = _pattern_file(account_name)
     pattern_path = _pattern_file(account_name)
@@ -264,24 +264,24 @@ async def find_pattern(
                             "帖子选题点": post_match["帖子选题点"],
                             "帖子选题点": post_match["帖子选题点"],
                             "匹配分数": post_match["匹配分数"],
                             "匹配分数": post_match["匹配分数"],
                         })
                         })
-                # 仅当 pattern 元素匹配到至少 2 个不同帖子选题点时才返回匹配信息,否则返回无匹配
+                # 仅当 pattern 元素匹配到至少 2 个不同帖子选题点时才返回匹配信息,否则为无
                 distinct_post_points = len({m["帖子选题点"] for m in pattern_matches})
                 distinct_post_points = len({m["帖子选题点"] for m in pattern_matches})
                 item["帖子选题点匹配"] = (
                 item["帖子选题点匹配"] = (
-                    pattern_matches if distinct_post_points >= 2 else "无匹配帖子选题点"
+                    pattern_matches if distinct_post_points >= 2 else "无"
                 )
                 )
         if not items:
         if not items:
             output = f"未找到条件概率 >= {conditional_ratio_threshold} 的 pattern"
             output = f"未找到条件概率 >= {conditional_ratio_threshold} 的 pattern"
         else:
         else:
             lines = []
             lines = []
             for x in items:
             for x in items:
-                match_info = x.get("帖子选题点匹配", "未查询")
+                match_info = x.get("帖子选题点匹配", "")
                 if isinstance(match_info, list):
                 if isinstance(match_info, list):
                     match_str = "、".join(
                     match_str = "、".join(
                         f"{m['pattern元素']}→{m['帖子选题点']}({m['匹配分数']})" for m in match_info
                         f"{m['pattern元素']}→{m['帖子选题点']}({m['匹配分数']})" for m in match_info
                     )
                     )
                 else:
                 else:
                     match_str = str(match_info)
                     match_str = str(match_info)
-                lines.append(f"- {x['pattern名称']}\t条件概率={x['条件概率']}\t帖子匹配={match_str}")
+                lines.append(f"- {x['pattern名称']}\t条件概率={x['条件概率']}\t帖子选题点匹配={match_str}")
             output = "\n".join(lines)
             output = "\n".join(lines)
         return ToolResult(
         return ToolResult(
             title=f"符合条件概率的 Pattern ({account_name}, 阈值={conditional_ratio_threshold})",
             title=f"符合条件概率的 Pattern ({account_name}, 阈值={conditional_ratio_threshold})",
@@ -308,12 +308,13 @@ def main() -> None:
     account_name = "家有大志"
     account_name = "家有大志"
     post_id = "68fb6a5c000000000302e5de"
     post_id = "68fb6a5c000000000302e5de"
     # 已推导选题点,每项:已推导的选题点 + 推导来源人设树节点
     # 已推导选题点,每项:已推导的选题点 + 推导来源人设树节点
-    derived_items = [
-        {"topic": "分享", "source_node": "分享"},
-        {"topic": "植入方式", "source_node": "植入方式"},
-        {"topic": "叙事结构", "source_node": "叙事结构"},
-    ]
-    conditional_ratio_threshold = 0.01
+    # derived_items = [
+    #     {"topic": "分享", "source_node": "分享"},
+    #     {"topic": "植入方式", "source_node": "植入方式"},
+    #     {"topic": "叙事结构", "source_node": "叙事结构"},
+    # ]
+    derived_items = derived_items = [{"source_node":"分享","topic":"分享"},{"source_node":"叙事结构","topic":"叙事结构"},{"source_node":"图片文字","topic":"图片文字"},{"source_node":"补充说明式","topic":"补充说明式"},{"source_node":"幽默化标题","topic":"幽默化标题"},{"source_node":"标题","topic":"标题"}]
+    conditional_ratio_threshold = 0.1
     top_n = 2000
     top_n = 2000
 
 
     # 1)直接调用核心函数(不含帖子匹配,仅验证排序逻辑)
     # 1)直接调用核心函数(不含帖子匹配,仅验证排序逻辑)

+ 33 - 32
examples_how/overall_derivation/tools/find_tree_node.py

@@ -167,7 +167,7 @@ def _parse_derived_list(derived_items: list[dict[str, str]]) -> list[tuple[str,
     description="获取指定账号人设树中的常量节点(全局常量、局部常量),并检查每个节点与帖子选题点的匹配情况。"
     description="获取指定账号人设树中的常量节点(全局常量、局部常量),并检查每个节点与帖子选题点的匹配情况。"
     "功能:根据账号名查询该账号人设树中所有常量节点,同时对每个节点判断是否匹配帖子选题点,匹配结果直接包含在返回数据中。"
     "功能:根据账号名查询该账号人设树中所有常量节点,同时对每个节点判断是否匹配帖子选题点,匹配结果直接包含在返回数据中。"
     "参数:account_name 为账号名;post_id 为帖子ID,用于加载帖子选题点并做匹配判断。"
     "参数:account_name 为账号名;post_id 为帖子ID,用于加载帖子选题点并做匹配判断。"
-    "返回:ToolResult,output 为可读的节点列表文本,metadata.items 为列表,每项含「节点名称」「概率」「常量类型」「帖子选题点匹配」(超过阈值的匹配列表,每项含帖子选题点与匹配分数;若无匹配则为字符串'无匹配帖子选题点')。"
+    "返回:ToolResult,output 为可读的节点列表文本,metadata.items 为列表,每项含「节点名称」「概率」「常量类型」「帖子选题点匹配」=无/匹配结果(无匹配时为「无」,有匹配时为匹配列表,每项含帖子选题点与匹配分数)。"
 )
 )
 async def find_tree_constant_nodes(
 async def find_tree_constant_nodes(
     account_name: str,
     account_name: str,
@@ -190,7 +190,7 @@ async def find_tree_constant_nodes(
         - output: 可读的节点列表文本(每行:节点名称、概率、常量类型、帖子匹配情况)。
         - output: 可读的节点列表文本(每行:节点名称、概率、常量类型、帖子匹配情况)。
         - metadata: 含 account_name、count、items;items 为列表,每项为
         - metadata: 含 account_name、count、items;items 为列表,每项为
           {"节点名称": str, "概率": 数值或 None, "常量类型": "全局常量"|"局部常量",
           {"节点名称": str, "概率": 数值或 None, "常量类型": "全局常量"|"局部常量",
-           "帖子选题点匹配": list[{"帖子选题点": str, "匹配分数": float}] 或 "无匹配帖子选题点"}。
+           "帖子选题点匹配": 无匹配时为 "无",有匹配时为 list[{"帖子选题点": str, "匹配分数": float}]}。
         - 出错时 error 为错误信息。
         - 出错时 error 为错误信息。
     """
     """
     tree_dir = _tree_dir(account_name)
     tree_dir = _tree_dir(account_name)
@@ -214,18 +214,18 @@ async def find_tree_constant_nodes(
                 })
                 })
             for item in items:
             for item in items:
                 matches = node_match_map.get(item["节点名称"], [])
                 matches = node_match_map.get(item["节点名称"], [])
-                item["帖子选题点匹配"] = matches if matches else "无匹配帖子选题点"
+                item["帖子选题点匹配"] = matches if matches else "无"
         if not items:
         if not items:
             output = "未找到常量节点"
             output = "未找到常量节点"
         else:
         else:
             lines = []
             lines = []
             for x in items:
             for x in items:
-                match_info = x.get("帖子选题点匹配", "未查询")
+                match_info = x.get("帖子选题点匹配", "")
                 if isinstance(match_info, list):
                 if isinstance(match_info, list):
                     match_str = "、".join(f"{m['帖子选题点']}({m['匹配分数']})" for m in match_info)
                     match_str = "、".join(f"{m['帖子选题点']}({m['匹配分数']})" for m in match_info)
                 else:
                 else:
                     match_str = str(match_info)
                     match_str = str(match_info)
-                lines.append(f"- {x['节点名称']}\t概率={x['概率']}\t{x['常量类型']}\t帖子匹配={match_str}")
+                lines.append(f"- {x['节点名称']}\t概率={x['概率']}\t{x['常量类型']}\t帖子选题点匹配={match_str}")
             output = "\n".join(lines)
             output = "\n".join(lines)
         return ToolResult(
         return ToolResult(
             title=f"常量节点 ({account_name})",
             title=f"常量节点 ({account_name})",
@@ -244,7 +244,7 @@ async def find_tree_constant_nodes(
     description="按条件概率从人设树中筛选节点,返回达到阈值且按条件概率排序的前 topN 条,并检查每个节点与帖子选题点的匹配情况。"
     description="按条件概率从人设树中筛选节点,返回达到阈值且按条件概率排序的前 topN 条,并检查每个节点与帖子选题点的匹配情况。"
     "功能:根据账号与已推导选题点(可选),筛选人设树中条件概率不低于阈值的节点,同时对每个节点判断是否匹配帖子选题点,匹配结果直接包含在返回数据中。"
     "功能:根据账号与已推导选题点(可选),筛选人设树中条件概率不低于阈值的节点,同时对每个节点判断是否匹配帖子选题点,匹配结果直接包含在返回数据中。"
     "参数:account_name 为账号名;post_id 为帖子ID,用于加载帖子选题点并做匹配判断;derived_items 为已推导选题点列表,每项含 topic(或已推导的选题点)与 source_node(或推导来源人设树节点),可为空,为空时条件概率使用节点自身的 _ratio;conditional_ratio_threshold 为条件概率阈值;top_n 为返回条数上限,默认 100。"
     "参数:account_name 为账号名;post_id 为帖子ID,用于加载帖子选题点并做匹配判断;derived_items 为已推导选题点列表,每项含 topic(或已推导的选题点)与 source_node(或推导来源人设树节点),可为空,为空时条件概率使用节点自身的 _ratio;conditional_ratio_threshold 为条件概率阈值;top_n 为返回条数上限,默认 100。"
-    "返回:ToolResult,output 为可读的节点列表文本,metadata.items 为列表,每项含「节点名称」「条件概率」「父节点名称」「帖子选题点匹配」(超过阈值的匹配列表,每项含帖子选题点与匹配分数;若无匹配则为字符串'无匹配帖子选题点')。"
+    "返回:ToolResult,output 为可读的节点列表文本,metadata.items 为列表,每项含「节点名称」「条件概率」「父节点名称」「帖子选题点匹配」=无/匹配结果(无匹配时为「无」,有匹配时为匹配列表,每项含帖子选题点与匹配分数)。"
 )
 )
 async def find_tree_nodes_by_conditional_ratio(
 async def find_tree_nodes_by_conditional_ratio(
     account_name: str,
     account_name: str,
@@ -273,7 +273,7 @@ async def find_tree_nodes_by_conditional_ratio(
         - output: 可读的节点列表文本(每行:节点名称、条件概率、父节点名称、帖子匹配情况)。
         - output: 可读的节点列表文本(每行:节点名称、条件概率、父节点名称、帖子匹配情况)。
         - metadata: 含 account_name、threshold、top_n、count、items;
         - metadata: 含 account_name、threshold、top_n、count、items;
           items 为列表,每项为 {"节点名称": str, "条件概率": float, "父节点名称": str,
           items 为列表,每项为 {"节点名称": str, "条件概率": float, "父节点名称": str,
-          "帖子选题点匹配": list[{"帖子选题点": str, "匹配分数": float}] 或 "无匹配帖子选题点"}。
+          "帖子选题点匹配": 无匹配时为 "无",有匹配时为 list[{"帖子选题点": str, "匹配分数": float}]}。
         - 出错时 error 为错误信息。
         - 出错时 error 为错误信息。
     """
     """
     tree_dir = _tree_dir(account_name)
     tree_dir = _tree_dir(account_name)
@@ -300,19 +300,19 @@ async def find_tree_nodes_by_conditional_ratio(
                 })
                 })
             for item in items:
             for item in items:
                 matches = node_match_map.get(item["节点名称"], [])
                 matches = node_match_map.get(item["节点名称"], [])
-                item["帖子选题点匹配"] = matches if matches else "无匹配帖子选题点"
+                item["帖子选题点匹配"] = matches if matches else "无"
         if not items:
         if not items:
             output = f"未找到条件概率 >= {conditional_ratio_threshold} 的节点"
             output = f"未找到条件概率 >= {conditional_ratio_threshold} 的节点"
         else:
         else:
             lines = []
             lines = []
             for x in items:
             for x in items:
-                match_info = x.get("帖子选题点匹配", "未查询")
+                match_info = x.get("帖子选题点匹配", "")
                 if isinstance(match_info, list):
                 if isinstance(match_info, list):
                     match_str = "、".join(f"{m['帖子选题点']}({m['匹配分数']})" for m in match_info)
                     match_str = "、".join(f"{m['帖子选题点']}({m['匹配分数']})" for m in match_info)
                 else:
                 else:
                     match_str = str(match_info)
                     match_str = str(match_info)
                 lines.append(
                 lines.append(
-                    f"- {x['节点名称']}\t条件概率={x['条件概率']}\t父节点={x['父节点名称']}\t帖子匹配={match_str}"
+                    f"- {x['节点名称']}\t条件概率={x['条件概率']}\t父节点={x['父节点名称']}\t帖子选题点匹配={match_str}"
                 )
                 )
             output = "\n".join(lines)
             output = "\n".join(lines)
         return ToolResult(
         return ToolResult(
@@ -339,29 +339,30 @@ def main() -> None:
 
 
     account_name = "家有大志"
     account_name = "家有大志"
     post_id = "68fb6a5c000000000302e5de"
     post_id = "68fb6a5c000000000302e5de"
-    derived_items = [
-        {"topic": "分享", "source_node": "分享"},
-        {"topic": "叙事结构", "source_node": "叙事结构"},
-    ]
+    # derived_items = [
+    #     {"topic": "分享", "source_node": "分享"},
+    #     {"topic": "叙事结构", "source_node": "叙事结构"},
+    # ]
+    derived_items = [{"source_node":"分享","topic":"分享"},{"source_node":"叙事结构","topic":"叙事结构"},{"source_node":"图片文字","topic":"图片文字"},{"source_node":"补充说明式","topic":"补充说明式"},{"source_node":"幽默化标题","topic":"幽默化标题"},{"source_node":"标题","topic":"标题"}]
     conditional_ratio_threshold = 0.1
     conditional_ratio_threshold = 0.1
-    top_n = 100
-
-    # 1)常量节点(核心函数,无匹配)
-    constant_nodes = get_constant_nodes(account_name)
-    print(f"账号: {account_name} — 常量节点共 {len(constant_nodes)} 个(前 50 个):")
-    for x in constant_nodes[:50]:
-        print(f"  - {x['节点名称']}\t概率={x['概率']}\t{x['常量类型']}")
-    print()
-
-    # 2)条件概率节点(核心函数)
-    derived_list = _parse_derived_list(derived_items)
-    ratio_nodes = get_nodes_by_conditional_ratio(
-        account_name, derived_list, conditional_ratio_threshold, top_n
-    )
-    print(f"条件概率节点 阈值={conditional_ratio_threshold}, top_n={top_n}, 共 {len(ratio_nodes)} 个:")
-    for x in ratio_nodes:
-        print(f"  - {x['节点名称']}\t条件概率={x['条件概率']}\t父节点={x['父节点名称']}")
-    print()
+    top_n = 1000
+
+    # # 1)常量节点(核心函数,无匹配)
+    # constant_nodes = get_constant_nodes(account_name)
+    # print(f"账号: {account_name} — 常量节点共 {len(constant_nodes)} 个(前 50 个):")
+    # for x in constant_nodes[:50]:
+    #     print(f"  - {x['节点名称']}\t概率={x['概率']}\t{x['常量类型']}")
+    # print()
+    #
+    # # 2)条件概率节点(核心函数)
+    # derived_list = _parse_derived_list(derived_items)
+    # ratio_nodes = get_nodes_by_conditional_ratio(
+    #     account_name, derived_list, conditional_ratio_threshold, top_n
+    # )
+    # print(f"条件概率节点 阈值={conditional_ratio_threshold}, top_n={top_n}, 共 {len(ratio_nodes)} 个:")
+    # for x in ratio_nodes:
+    #     print(f"  - {x['节点名称']}\t条件概率={x['条件概率']}\t父节点={x['父节点名称']}")
+    # print()
 
 
     # 3)有 agent 时通过 tool 接口再跑一遍(含帖子选题点匹配)
     # 3)有 agent 时通过 tool 接口再跑一遍(含帖子选题点匹配)
     if ToolResult is not None:
     if ToolResult is not None:

Некоторые файлы не были показаны из-за большого количества измененных файлов