Selaa lähdekoodia

how agent 输出推导评估过程数据

liuzhiheng 1 viikko sitten
vanhempi
commit
365b605e2f

+ 310 - 0
examples_how/overall_derivation/generate_visualize_data.py

@@ -0,0 +1,310 @@
+#!/usr/bin/env python3
+"""
+生成推导可视化数据。
+
+输入参数:account_name, post_id, log_id
+- 从 input/{account_name}/解构内容/{post_id}.json 解析选题点列表
+- 从 output/{account_name}/推导日志/{post_id}/{log_id}/ 读取推导与评估 JSON,生成:
+  1. output/{account_name}/整体推导结果/{post_id}.json
+  2. output/{account_name}/整体推导路径可视化/{post_id}.json
+"""
+
+import argparse
+import json
+import re
+from pathlib import Path
+from typing import Any
+
+
+def _collect_dimension_names(point_data: dict) -> dict[str, str]:
+    """从点的 实质/形式/意图 中收集 名称 -> dimension。"""
+    name_to_dim = {}
+    if "实质" in point_data and point_data["实质"]:
+        for key in ("具体元素", "具象概念", "抽象概念"):
+            for item in (point_data["实质"].get(key) or []):
+                n = item.get("名称")
+                if n:
+                    name_to_dim[n] = "实质"
+    if "形式" in point_data and point_data["形式"]:
+        for key in ("具体元素形式", "具象概念形式", "整体形式"):
+            for item in (point_data["形式"].get(key) or []):
+                n = item.get("名称")
+                if n:
+                    name_to_dim[n] = "形式"
+    if point_data.get("意图"):
+        for item in point_data["意图"]:
+            n = item.get("名称")
+            if n:
+                name_to_dim[n] = "意图"
+    return name_to_dim
+
+
+def parse_topic_points_from_deconstruct(deconstruct_path: Path) -> list[dict[str, Any]]:
+    """
+    从 input/{account_name}/解构内容/{post_id}.json 解析选题点列表。
+    选题点来自分词结果中的「词」,字段:name, point, dimension, root_source, root_sources_desc。
+    """
+    if not deconstruct_path.exists():
+        raise FileNotFoundError(f"解构内容文件不存在: {deconstruct_path}")
+    with open(deconstruct_path, "r", encoding="utf-8") as f:
+        data = json.load(f)
+
+    result = []
+    for point_type in ("灵感点", "目的点", "关键点"):
+        for point in data.get(point_type) or []:
+            root_source = point.get("点", "")
+            root_sources_desc = point.get("点描述", "")
+            name_to_dim = _collect_dimension_names(point)
+            for word_item in point.get("分词结果") or []:
+                name = word_item.get("词", "").strip()
+                if not name:
+                    continue
+                dimension = name_to_dim.get(name, "实质")
+                result.append({
+                    "name": name,
+                    "point": point_type,
+                    "dimension": dimension,
+                    "root_source": root_source,
+                    "root_sources_desc": root_sources_desc,
+                })
+    return result
+
+
+def _topic_point_key(t: dict) -> tuple:
+    return (t["name"], t["point"], t["dimension"])
+
+
+def load_derivation_logs(log_dir: Path) -> tuple[list[dict], list[dict]]:
+    """
+    从 output/{account_name}/推导日志/{post_id}/{log_id}/ 读取所有 {轮次}_推导.json 与 {轮次}_评估.json。
+    返回 (推导列表按轮次序, 评估列表按轮次序)。
+    """
+    if not log_dir.is_dir():
+        raise FileNotFoundError(f"推导日志目录不存在: {log_dir}")
+
+    derivation_by_round = {}
+    eval_by_round = {}
+    for p in log_dir.glob("*.json"):
+        base = p.stem
+        m = re.match(r"^(\d+)_(推导|评估)$", base)
+        if not m:
+            continue
+        round_num = int(m.group(1))
+        with open(p, "r", encoding="utf-8") as f:
+            content = json.load(f)
+        if m.group(2) == "推导":
+            derivation_by_round[round_num] = content
+        else:
+            eval_by_round[round_num] = content
+
+    rounds = sorted(set(derivation_by_round) | set(eval_by_round))
+    derivations = [derivation_by_round[r] for r in rounds if r in derivation_by_round]
+    evals = [eval_by_round[r] for r in rounds if r in eval_by_round]
+    return derivations, evals
+
+
+def build_derivation_result(
+    topic_points: list[dict],
+    derivations: list[dict],
+    evals: list[dict],
+) -> list[dict]:
+    """
+    生成整体推导结果:每轮 轮次、推导成功的选题点、未推导成功的选题点、本次新推导成功的选题点。
+    选题点用 topic_points 中的完整信息;按 name 判定是否被推导(评估中的 match_post_point)。
+    """
+    all_keys = {_topic_point_key(t) for t in topic_points}
+    topic_by_key = {_topic_point_key(t): t for t in topic_points}
+
+    result = []
+    derived_names_so_far: set[str] = set()
+
+    for i, (derivation, eval_data) in enumerate(zip(derivations, evals)):
+        round_num = derivation.get("round", i + 1)
+        eval_results = eval_data.get("eval_results") or []
+        matched_post_points = set()
+        for er in eval_results:
+            if er.get("match_result") != "匹配":
+                continue
+            mp = (er.get("match_post_point") or "").strip()
+            if mp:
+                matched_post_points.add(mp)
+
+        new_derived_names = matched_post_points - derived_names_so_far
+        derived_names_so_far |= matched_post_points
+
+        # 推导成功的选题点:name 在 derived_names_so_far 中的选题点(每 name 取一条,与 topic_points 顺序一致)
+        derived_keys = {k for k in all_keys if topic_by_key[k]["name"] in derived_names_so_far}
+        new_derived_keys = {k for k in all_keys if topic_by_key[k]["name"] in new_derived_names}
+        not_derived_keys = all_keys - derived_keys
+
+        derived_list = [dict(topic_by_key[k]) for k in sorted(derived_keys, key=lambda k: (topic_by_key[k]["name"], k[1], k[2]))]
+        new_list = [dict(topic_by_key[k]) for k in sorted(new_derived_keys, key=lambda k: (topic_by_key[k]["name"], k[1], k[2]))]
+        not_derived_list = [dict(topic_by_key[k]) for k in sorted(not_derived_keys, key=lambda k: (topic_by_key[k]["name"], k[1], k[2]))]
+
+        result.append({
+            "轮次": round_num,
+            "推导成功的选题点": derived_list,
+            "未推导成功的选题点": not_derived_list,
+            "本次新推导成功的选题点": new_list,
+        })
+    return result
+
+
+def _to_tree_node(name: str, extra: dict | None = None) -> dict:
+    d = {"name": name}
+    if extra:
+        d.update(extra)
+    return d
+
+
+def _to_pattern_node(pattern_name: str) -> dict:
+    """将 pattern 字符串转为 input_pattern_nodes 的一项(简化版)。"""
+    items = [x.strip() for x in pattern_name.replace("+", " ").split() if x.strip()]
+    return {
+        "items": [{"name": x, "point": "关键点", "dimension": "形式", "type": "标签"} for x in items],
+        "match_items": items,
+    }
+
+
+def build_visualize_edges(
+    derivations: list[dict],
+    evals: list[dict],
+    topic_points: list[dict],
+) -> tuple[list[dict], list[dict]]:
+    """
+    生成 node_list(所有评估通过的帖子选题点)和 edge_list(只保留评估通过的推导路径)。
+    """
+    topic_by_name = {}
+    for t in topic_points:
+        name = t["name"]
+        if name not in topic_by_name:
+            topic_by_name[name] = t
+
+    derivation_output_to_match = {}
+    for eval_data in evals:
+        for er in eval_data.get("eval_results") or []:
+            if er.get("match_result") != "匹配":
+                continue
+            out_point = (er.get("derivation_output_point") or "").strip()
+            match_point = (er.get("match_post_point") or "").strip()
+            if out_point and match_point:
+                derivation_output_to_match[out_point] = {
+                    "match_post_point": match_point,
+                    "match_reason": er.get("match_reason", ""),
+                    "eval": er,
+                }
+
+    node_list = []
+    seen_nodes = set()
+    edge_list = []
+    level_by_name = {}
+
+    for round_idx, derivation in enumerate(derivations):
+        round_num = derivation.get("round", round_idx + 1)
+        for dr in derivation.get("derivation_results") or []:
+            output_list = dr.get("output") or []
+            matched_outputs = []
+            for out_item in output_list:
+                info = derivation_output_to_match.get(out_item)
+                if not info:
+                    continue
+                mp = info["match_post_point"]
+                if not mp:
+                    continue
+                matched_outputs.append(mp)
+                if mp not in seen_nodes:
+                    seen_nodes.add(mp)
+                    node = dict(topic_by_name.get(mp, {"name": mp, "point": "", "dimension": "", "root_source": "", "root_sources_desc": ""}))
+                    node["level"] = round_num
+                    if "original_word" not in node:
+                        node["original_word"] = node.get("name", mp)
+                    node["derivation_type"] = dr.get("method", "")
+                    level_by_name[mp] = round_num
+                    node_list.append(node)
+
+            if not matched_outputs:
+                continue
+
+            input_data = dr.get("input") or {}
+            derived_nodes = input_data.get("derived_nodes") or []
+            tree_nodes = input_data.get("tree_nodes") or []
+            patterns = input_data.get("patterns") or []
+
+            input_post_nodes = [{"name": x} for x in derived_nodes]
+            input_tree_nodes = [_to_tree_node(x) for x in tree_nodes]
+            if patterns and isinstance(patterns[0], str):
+                input_pattern_nodes = [_to_pattern_node(p) for p in patterns]
+            elif patterns and isinstance(patterns[0], dict):
+                input_pattern_nodes = patterns
+            else:
+                input_pattern_nodes = []
+
+            output_nodes = [{"name": x} for x in matched_outputs]
+            detail = {
+                "reason": dr.get("reason", ""),
+                "评估结果": "匹配成功",
+            }
+            if dr.get("tools"):
+                detail["tools"] = dr["tools"]
+            edge_list.append({
+                "name": dr.get("method", "") or f"推导-{round_num}",
+                "input_post_nodes": input_post_nodes,
+                "input_tree_nodes": input_tree_nodes,
+                "input_pattern_nodes": input_pattern_nodes,
+                "output_nodes": output_nodes,
+                "detail": detail,
+            })
+
+    return node_list, edge_list
+
+
+def generate_visualize_data(account_name: str, post_id: str, log_id: str, base_dir: Path | None = None) -> None:
+    """
+    主流程:读取解构内容与推导日志,生成整体推导结果与整体推导路径可视化两个 JSON。
+    """
+    if base_dir is None:
+        base_dir = Path(__file__).resolve().parent
+    input_dir = base_dir / "input" / account_name / "解构内容"
+    log_dir = base_dir / "output" / account_name / "推导日志" / post_id / log_id
+    result_dir = base_dir / "output" / account_name / "整体推导结果"
+    visualize_dir = base_dir / "output" / account_name / "整体推导路径可视化"
+
+    deconstruct_path = input_dir / f"{post_id}.json"
+    topic_points = parse_topic_points_from_deconstruct(deconstruct_path)
+
+    derivations, evals = load_derivation_logs(log_dir)
+    if not derivations or not evals:
+        raise ValueError(f"推导或评估数据为空: {log_dir}")
+
+    # 2.1 整体推导结果
+    derivation_result = build_derivation_result(topic_points, derivations, evals)
+    result_dir.mkdir(parents=True, exist_ok=True)
+    result_path = result_dir / f"{post_id}.json"
+    with open(result_path, "w", encoding="utf-8") as f:
+        json.dump(derivation_result, f, ensure_ascii=False, indent=4)
+    print(f"已写入整体推导结果: {result_path}")
+
+    # 2.2 整体推导路径可视化
+    node_list, edge_list = build_visualize_edges(derivations, evals, topic_points)
+    visualize_path = visualize_dir / f"{post_id}.json"
+    visualize_dir.mkdir(parents=True, exist_ok=True)
+    with open(visualize_path, "w", encoding="utf-8") as f:
+        json.dump({"node_list": node_list, "edge_list": edge_list}, f, ensure_ascii=False, indent=4)
+    print(f"已写入整体推导路径可视化: {visualize_path}")
+
+
+def main(account_name, post_id, log_id):
+    # parser = argparse.ArgumentParser(description="生成推导可视化数据")
+    # parser.add_argument("account_name", help="账号名,如 家有大志")
+    # parser.add_argument("post_id", help="帖子 ID")
+    # parser.add_argument("log_id", help="推导日志 ID,如 20260303204232")
+    # parser.add_argument("--base-dir", type=Path, default=None, help="项目根目录,默认为本脚本所在目录")
+    # args = parser.parse_args()
+    generate_visualize_data(account_name=account_name, post_id=post_id, log_id=log_id)
+
+
+if __name__ == "__main__":
+    account_name="家有大志"
+    post_id = "68fb6a5c000000000302e5de"
+    log_id="20260303221927"
+    main(account_name, post_id, log_id)

+ 7 - 1
examples_how/overall_derivation/overall_derivation_agent_run.py

@@ -13,6 +13,7 @@ import os
 import sys
 import select
 import asyncio
+from datetime import datetime
 from pathlib import Path
 
 # 与 examples/how/run.py 一致:禁止 httpx/urllib 自动检测系统 HTTP 代理
@@ -240,11 +241,15 @@ async def main():
     print("=" * 60)
     print()
 
+    # 在读取 prompt 前生成 log_id(格式 yyyyMMddHHmmss),保证每次运行使用同一 log_id(用于推导日志输出路径)
+    log_id = datetime.now().strftime("%Y%m%d%H%M%S")
+    print(f"   - 本次运行 log_id: {log_id}")
+
     print("1. 加载 prompt 配置...")
     prompt = SimplePrompt(prompt_path)
 
     print("2. 构建任务消息...")
-    messages = prompt.build_messages()
+    messages = prompt.build_messages(log_id=log_id)
 
     print("3. 创建 Agent Runner...")
     print(f"   - Skills 目录: {skills_dir}")
@@ -473,6 +478,7 @@ async def main():
         print("   http://localhost:8000/api/traces")
         print()
         print(f"3. Trace ID: {current_trace_id}")
+        print(f"4. Log ID(推导日志目录): {log_id}")
         print("=" * 60)
 
 

+ 70 - 271
examples_how/overall_derivation/production.prompt

@@ -10,7 +10,7 @@ $system$
 你是专业的图文内容创作者,同时具备内容消费者喜好的感知能力、内容审美判断能力、缜密的逻辑推理能力。
 
 ## 任务描述
-根据创作者整体人设树和创作者创作pattern,以及**当前已推导成功的选题点**(每轮由评估 agent 更新),以内容创作者的视角,模仿内容创作者在创作过程中使用 历史创作模式复用/关联联想/逻辑推导/信息查询...等手段方法和过程,将选题点串联成一条完整的选题点推导路径,也即选题点产出的先后依赖步骤。路径由点和有向连线组成,路径中的点表示一个选题点,点A指向点B的有向连线表示创作者由点A通过某种方法(思考/联想/推导/查询...等)产生了点B。**主 agent 不直接接收帖子单帖解构内容**,仅能使用「已推导成功的选题点」进行推导,符合闭眼推导原则;每轮产出的可能选题点由**评估子 agent** 与帖子解构内容做语义匹配,匹配成功的才加入已推导成功的选题点集合。最终将生成的路径转化为选题点推导路径结果、推导路径可视化文件两个输出文件
+根据创作者整体人设树和创作者创作pattern,以及**当前已推导成功的选题点**(每轮由评估 agent 更新),以内容创作者的视角,模仿内容创作者在创作过程中使用 历史创作模式复用/关联联想/逻辑推导/信息查询...等手段方法和过程,将选题点串联成一条完整的选题点推导路径,也即选题点产出的先后依赖步骤。路径由点和有向连线组成,路径中的点表示一个选题点,点A指向点B的有向连线表示创作者由点A通过某种方法(思考/联想/推导/查询...等)产生了点B。**主 agent 不直接接收帖子单帖解构内容**,仅能使用「已推导成功的选题点」进行推导,符合闭眼推导原则;每轮产出的可能选题点由**评估子 agent** 与帖子解构内容做语义匹配,匹配成功的才加入已推导成功的选题点集合。每轮推导和评估结束后,输出该轮的**推导日志**与**评估日志**到指定目录
 
 ## 输入文件
 **说明**:帖子单帖解构内容**不作为**主 agent 的输入。主 agent 只能看到「已推导成功的选题点」(随轮次由评估子 agent 更新)。评估阶段通过调用评估子 agent 完成,子 agent 的输入包含帖子单帖解构内容与本轮推导出的可能选题点。
@@ -55,283 +55,82 @@ $system$
   - `{"s":0.2034,"l":3,"i":"图片文字+产品植入+补充说明式"}` 表示「图片文字、产品植入、补充说明式」三个选题点常一起出现,支持度 0.2034
   - 使用时可将 `i` 按 `+` 拆分为名称列表,与人设树或已推导选题点做匹配,优先选用 `s` 高、`l` 大的 pattern
 
-## 输出文件
+## 输出文件(每轮推导与评估的详细过程日志)
 
-### 1. 推导路径可视化
-- **路径**: `output/家有大志/整体推导路径可视化/68fb6a5c000000000302e5de.json`
-- **作用**: 用于可视化展示推导的路径关系,包含节点列表和边列表
+每轮推导结束后写入**推导日志**,每轮评估子 agent 返回后写入**评估日志**。路径中的 `{帖子ID}`、`log_id`(运行前生成并替换)、`{轮次}` 由实际执行时替换。
+
+### 1. 推导日志(每轮一份)
+- **路径**: `output/家有大志/推导日志/{帖子ID}/%log_id%/{轮次}_推导.json`
+- **作用**: 记录该轮推导的详细过程,便于追溯与可解释性
 - **格式要求**:
 ```json
 {
-    "node_list": [
-        {
-            "name": "节点名称",
-            "level": 1,
-            "point": "灵感点|目的点|关键点",
-            "dimension": "实质|形式|意图",
-            "root_source": "原始点名称",
-            "root_sources_desc": "原始点描述",
-            "original_word": "归一化原词",
-            "derivation_type": "推导方法"
-        }
-    ],
-    "edge_list": [
-        {
-            "name": "边的名称",
-            "input_post_nodes": [],
-            "input_tree_nodes": [
-                {
-                    "name": "人设节点名称",
-                    "type": "标签|分类",
-                    "is_constant": true,
-                    "is_local_constant": false
-                }
-            ],
-            "input_pattern_nodes": [],
-            "output_nodes": [
-                {
-                    "name": "节点名称"
-                }
-            ],
-            "detail": {
-                "reason": "该选题点可以被推导出来的理由",
-                "目标词": "节点名:点类型:维度",
-                "人设词": "人设节点名称:维度:类型",
-                "匹配分数": 0.xxxx,
-                "人设分数": x.xxxx
-            },
-            "score": 0.xxxx
-        }
-    ]
-}
-```
-示例
-```json
-{
-    "node_list": [
-        {
-            "name": "分享",
-            "level": 1,
-            "point": "目的点",
-            "dimension": "意图",
-            "root_source": "分享养宠烦恼",
-            "root_sources_desc": "意图:通过展示宠物狗破坏鞋子的行为,分享养狗人共同的烦恼,以引发其他宠物主人的共鸣。\n实质:因宠物狗(柴犬)喜欢啃咬而导致的鞋子收纳和损坏问题,是一种具体的养宠生活烦恼。",
-            "original_word": "",
-            "derivation_type": "人设常量选起点"
-        },
-        {
-            "name": "补充说明式",
-            "level": 1,
-            "point": "关键点",
-            "dimension": "形式",
-            "root_source": "补充说明式图片文字",
-            "root_sources_desc": "在图片2和图片3中加入的“狗视眈眈”、“没放上去的被它啃成了这样…”等文字,起到了画龙点睛的作用。这些文字直接解释了图片内容,强化了叙事逻辑和幽默感,确保观众能够准确无误地理解创作者想要传达的笑点和无奈。",
-            "original_word": "",
-            "derivation_type": "内部pattern匹配"
-        },
-        {
-            "name": "图片文字",
-            "level": 1,
-            "point": "关键点",
-            "dimension": "形式",
-            "root_source": "补充说明式图片文字",
-            "root_sources_desc": "在图片2和图片3中加入的“狗视眈眈”、“没放上去的被它啃成了这样…”等文字,起到了画龙点睛的作用。这些文字直接解释了图片内容,强化了叙事逻辑和幽默感,确保观众能够准确无误地理解创作者想要传达的笑点和无奈。",
-            "original_word": "",
-            "derivation_type": "外部搜索"
-        },
-        {
-            "name": "肇事者",
-            "level": 7,
-            "point": "关键点",
-            "dimension": "实质",
-            "root_source": "肇事者柴犬形象",
-            "root_sources_desc": "柴犬作为核心角色贯穿三张图片,它时而仰望鞋堆,时而“狗视眈眈”,它的存在解释了鞋架乱象的原因,是整个故事的“肇事者”和趣味性的来源,直接关联到养宠生活的主题,是引发共鸣的关键情感连接点。",
-            "original_word": "",
-            "derivation_type": "外部搜索"
-        }
-    ],
-    "edge_list": [
-        {
-            "name": "选起点:全局常量:分享",
-            "input_post_nodes": [],
-            "input_tree_nodes": [
-                {
-                    "name": "分享",
-                    "type": "标签",
-                    "is_constant": true,
-                    "is_local_constant": false
-                }
-            ],
-            "input_pattern_nodes": [],
-            "output_nodes": [
-                {
-                    "name": "分享"
-                }
-            ],
-            "detail": {
-                "reason": "人设树中的全局常量“分享”和选题点”分享“完全匹配",
-                "评估结果": "",
-                "目标词": "分享:目的点:意图",
-                "人设词": "分享:意图:标签",
-                "匹配分数": 1.0,
-                "人设分数": 1.0
-            },
-            "score": 1.0
-        },
-        {
-            "name": "pattern:图片文字+产品植入+补充说明式",
-            "input_post_nodes": [],
-            "input_tree_nodes": [],
-            "input_pattern_nodes": [
-                {
-                    "items": [
-                        {
-                            "name": "图片文字",
-                            "point": "关键点",
-                            "dimension": "形式",
-                            "type": "标签"
-                        },
-                        {
-                            "name": "产品植入",
-                            "point": "目的点",
-                            "dimension": "形式",
-                            "type": "标签"
-                        },
-                        {
-                            "name": "补充说明式",
-                            "point": "关键点",
-                            "dimension": "形式",
-                            "type": "标签"
-                        }
-                    ],
-                    "match_items": [
-                        "补充说明式",
-                        "图片文字"
-                    ]
-                }
-            ],
-            "output_nodes": [
-                {
-                    "name": "补充说明式"
-                },
-                {
-                    "name": "图片文字"
-                }
-            ],
-            "detail": {
-                "reason": "补充说明式、图片文字和pattern:图片文字+产品植入+补充说明式中的两个元素完全匹配且该pattern的支持度较高",
-                "评估结果": "",
-                "目标词": "补充说明式:关键点:形式",
-                "人设词": "补充说明式:形式:标签",
-                "匹配分数": 1.0,
-                "人设分数": 0.2052,
-                "pattern模式支持度": 0.2034,
-                "pattern模式匹配详情": [
-                    {
-                        "pattern元素": "补充说明式",
-                        "元素类型": "标签",
-                        "帖子选题点": "补充说明式",
-                        "点类型": "关键点",
-                        "维度": "形式",
-                        "原始点": "补充说明式图片文字",
-                        "原始点描述": "在图片2和图片3中加入的“狗视眈眈”、“没放上去的被它啃成了这样…”等文字,起到了画龙点睛的作用。这些文字直接解释了图片内容,强化了叙事逻辑和幽默感,确保观众能够准确无误地理解创作者想要传达的笑点和无奈。",
-                        "归一化原词": "",
-                        "匹配分数": 1.0
-                    },
-                    {
-                        "pattern元素": "图片文字",
-                        "元素类型": "标签",
-                        "帖子选题点": "图片文字",
-                        "点类型": "关键点",
-                        "维度": "形式",
-                        "原始点": "补充说明式图片文字",
-                        "原始点描述": "在图片2和图片3中加入的“狗视眈眈”、“没放上去的被它啃成了这样…”等文字,起到了画龙点睛的作用。这些文字直接解释了图片内容,强化了叙事逻辑和幽默感,确保观众能够准确无误地理解创作者想要传达的笑点和无奈。",
-                        "归一化原词": "",
-                        "匹配分数": 1.0
-                    },
-                    {
-                        "pattern元素": "产品植入",
-                        "元素类型": "标签"
-                    }
-                ]
-            },
-            "score": 0.2034
-        },
+  "round": 1,
+  "derivation_results": [
+    {
+      "method": "推导方法名称(如:人设常量选起点、账号pattern模式复用、人设联想、信息搜索等)",
+      "input": {
+        "tree_nodes": ["人设节点名称1", "人设节点名称2"],
+        "patterns": ["选题点名称1+选题点名称2", "选题点名称3+选题点名称4"],
+        "derived_nodes": ["已推导的选题点名称1", "已推导的选题点名称2"]
+      },
+      "output": ["本次推导出的选题点名称1", "本次推导出的选题点名称2"],
+      "reason": "推导详细原因,需反映思维链与决策依据",
+      "tools": [
         {
-            "name": "外部搜索:柴犬形象 被啃坏",
-            "input_post_nodes": [],
-            "input_tree_nodes": [],
-            "input_pattern_nodes": [],
-            "output_nodes": [
-                {
-                    "name": "肇事者"
-                }
-            ],
-            "detail": {
-                "reason": "通过 柴犬形象 被啃坏 从外部搜索出来的结果中有相当一部分内容表现了肇事者这一核心对象",
-                "评估结果": "",
-                "query": "柴犬形象 被啃坏",
-                "query_result": "搜索结果",
-                "match_result": "搜索结果和目标的匹配结果",
-                "match_score": 0.8
-            },
-            "score": 0.8
+          "name": "工具名称(如 search_posts)",
+          "query": "若为搜索工具则记录 query 词",
+          "result": "若为搜索工具则记录搜索返回的数据摘要或关键内容",
+          "raw_result": "若为搜索工具则记录搜索工具返回的原始数据(完整保留或按需截断)"
         }
-    ]
+      ]
+    }
+  ]
 }
 ```
-
 - **说明**:
-  - `node_list`: 包含所有推导成功的选题点
-    - `level`: 推导的轮次,从1开始
-    - `derivation_type`: 推导方式
-  - `edge_list`: 包含节点之间的推导关系
-    - `name`: 边的名称,取一个合理的名称,简要概括推导方法
-    - `input_tree_nodes`: 来自人设树的输入节点
-    - `input_pattern_nodes`: 来自pattern的输入节点
-    - `output_nodes`: 推导出的选题点节点
-    - `detail`: 边的详情,json格式,记录推导过程中的详细数据
-      - `reason`: 该选题点可以被推导出来的理由 
-      - `评估结果`: 评估子agent的评估结果 
-    - `score`: 在推导过程中综合计算得到的概率分数 
-
-### 2. 推导结果
-- **路径**: `output/家有大志/整体推导结果/68fb6a5c000000000302e5de.json`
-- **作用**: 记录每一轮次推导的进度
+  - `round`: 当前轮次,从 1 开始
+  - `derivation_results`: **表示该轮的多条推导路径**。数组中的每一项是一条推导路径;同一轮内可能有多条路径,每条路径彼此独立(方法或输入不同)。例如:本轮推导出选题点 A、B、C、D,其中 A、B、C 可能由**同一条路径**产出(结合人设节点 + pattern + 已推导选题点等一起推导),D 由**另一条路径**产出(如另一种方法或另一组输入)。因此:
+    - **每条路径可有多个输入**:`input` 中的 `tree_nodes`、`patterns`、`derived_nodes` 可同时存在、共同作为该路径的输入。
+    - **每条路径可有多个输出**:`output` 为该路径产出的一个或多个待评估选题点(字符串数组)。
+  - `input.tree_nodes`: 本路径用到的人设树节点名称列表,只需要输出节点名称即可,不需要带上完整的节点路径,比如 `创意展示`,而不需要输出 `形式.内容风格.氛围特征.创意性.创意展示`
+  - `input.patterns`: 本路径用到的 pattern 选题点拼接列表(与 processed_edge_data.json 中 `i` 格式一致,如 `"名称1+名称2"`)
+  - `input.derived_nodes`: 本路径用到的已推导成功选题点名称列表
+  - `output`: 本路径产出的待评估选题点名称列表(可多个)
+  - `reason`: 必须详细、可追溯,禁止牵强或凭空联想
+  - `tools`: 本路径使用的工具列表;若使用搜索工具,必须包含 `query`、`result`(数据摘要或关键内容)以及 `raw_result`(搜索工具返回的原始数据)
+
+### 2. 评估日志(每轮一份)
+- **路径**: `output/家有大志/推导日志/{帖子ID}/%log_id%/{轮次}_评估.json`
+- **作用**: 记录该轮评估结果与推导进度,**内容主要由调用评估子 agent 的返回结果整理得到**
 - **格式要求**:
-  ```json
-  [
+```json
+{
+  "round": 1,
+  "eval_results": [
     {
-      "轮次": 1,
-      "推导成功的选题点": [
-        {
-          "name": "节点名称",
-          "point": "灵感点|目的点|关键点",
-          "dimension": "实质|形式|意图",
-          "root_source": "原始点名称",
-          "root_sources_desc": "原始点描述",
-          "derivation_type": "start",
-          "original_word": "归一化原词"
-        }
-      ],
-      "本次新推导成功的选题点": [
-        {
-          "name": "节点名称",
-          "point": "灵感点|目的点|关键点",
-          "dimension": "实质|形式|意图",
-          "root_source": "原始点名称",
-          "root_sources_desc": "原始点描述",
-          "derivation_type": "",
-          "original_word": "归一化原词"
-        }
-      ]
+      "derivation_output_point": "本轮推导输出的待评估选题点名称",
+      "match_result": "匹配 或 不匹配",
+      "match_post_point": "若匹配,则为帖子解构中匹配到的选题点(分词结果.词);若不匹配则省略或为空"
     }
-  ]
-  ```
+  ],
+  "derivation_progress": {
+    "post_point_count": 10,
+    "derived_point_count": 3,
+    "need_continue": "是 或 否"
+  }
+}
+```
 - **说明**:
-  - `轮次`: 推导轮次,从1开始
-  - `推导成功的选题点`: 已推导出的选题点,多轮累加
-  - `本次新推导成功的选题点`: 本轮新推导出来的选题点
+  - `round`: 当前轮次,与同轮推导日志一致
+  - `eval_results`: 与评估子 agent 返回的「匹配结果列表」一一对应
+    - `derivation_output_point`: 对应子 agent 的「推导选题点名称」
+    - `match_result`: 对应子 agent 的「是否匹配」,取值为 **匹配** 或 **不匹配**
+    - `match_post_point`: 对应子 agent 的「匹配到的帖子选题点」(仅当匹配时填写,且必须是帖子解构中分词结果里的「词」)
+  - `derivation_progress`: 由评估子 agent 返回的进度信息整理
+    - `post_point_count`: 帖子选题点数量(子 agent 的「帖子选题点数量」)
+    - `derived_point_count`: 已推导成功选题点数量(子 agent 的「已推导成功选题点数量」)
+    - `need_continue`: 是否需要进行下一轮推导,取值为 **是** 或 **否**(来自子 agent 的「是否需要进行下一轮推导」)
 
 
 ## 推导过程
@@ -344,7 +143,7 @@ $system$
    - 子 agent 职责:判断本轮推导的选题点与帖子解构选题点是否语义相似或接近,返回匹配结果;判断是否还有未推导成功的选题点,若有则告知主 agent 需进行下一轮推导。
    - 主 agent 后续动作:根据子 agent 返回的「本轮匹配成功的选题点」加入已推导成功的选题点集合;未匹配的不加入;根据「是否需要进行下一轮推导」决定是否继续推导。
 3. **推导失败后改进重试及探索游走**:在评估验证阶段,若本轮推导出的可能选题点经子 agent 评估后均与帖子选题点不匹配,则需要更换使用的推导方法,或改变原方法的调用输入信息,再次执行第 1 步(推导)。在第 1~3 步中循环多次执行;若以同样输入重试次数达到 3 次,则不再以此节点进行推导。
-4. **生成结果**:将推导关系构建为节点和边的结构,用于可视化展示。同时将整个推导结果按照上面指定的输出文件格式输出到两个JSON文件
+4. **输出日志**:每轮完成「推导」后,将本轮推导过程按**推导日志**格式写入 `output/家有大志/推导日志/{帖子ID}/%log_id%/{轮次}_推导.json`;每轮完成「评估验证」并收到子 agent 返回后,将子 agent 返回的匹配结果与进度信息整理为**评估日志**格式,写入 `output/家有大志/推导日志/{帖子ID}/%log_id%/{轮次}_评估.json`。评估日志中的 `eval_results`、`derivation_progress` 需与评估子 agent(derivation_eval)的返回字段一一对应整理
 
 ### 推导方法的定义
 每一个推导方法都可以从以下两个角度来定义,两个角度正交,每个角度的单一枚举值组合在一起,定义了一个方法。
@@ -383,14 +182,14 @@ $system$
 3. **JSON格式**: 输出JSON使用`ensure_ascii=False`和`indent=4`
 4. **数据完整性**: 
    - 推导成功的选题点 + 未推导成功的选题点 = 所有选题点
-   - 节点和边需要去重,避免重复
+   - 每轮推导日志的 derivation_results 按步骤记录,避免重复条目
 
 ## 输出验证
 
-确保输出文件:
-1. JSON格式正确,可以正常解析
-2. `node_list`中的节点都有必需的字段
-3. `edge_list`中的边都有正确的输入输出节点引用
+确保每轮输出的日志文件:
+1. JSON 格式正确,可以正常解析
+2. 推导日志包含 `round`、`derivation_results`,且每条结果含 `method`、`input`、`output`、`reason`、`tools`
+3. 评估日志包含 `round`、`eval_results`、`derivation_progress`,且与评估子 agent 返回字段对应一致
 
 $user$
 请开始执行 帖子ID=68fb6a5c000000000302e5de 的选题点整体推导任务。所有路径均相对于项目根目录。

+ 2 - 6
examples_how/overall_derivation/skills/derivation_eval.md

@@ -60,13 +60,9 @@ description: 选题点推导评估任务 - 判断推导产出的选题点与帖
    对「本轮推导出的每个选题点」给出一条记录:
    - `推导选题点名称`:本轮推导的节点 name
    - `是否匹配`:是 / 否
-   - `匹配到的帖子选题点`(若匹配):帖子解构中对应的**分词结果中的「词」**(即选题点名称),以及所属点类型(灵感点/目的点/关键点);不要用顶层的「点」字段作为选题点标识
-   - `匹配理由`(简短):为何判定语义相似或为何不匹配
+   - `匹配到的帖子选题点`(若匹配):帖子解构中对应的**分词结果中的「词」**(即选题点名称);不要用顶层的「点」字段作为选题点标识
 
-2. **汇总**
-   - `本轮匹配成功的选题点`:填写的是**匹配到的帖子选题点**(即帖子解构中「分词结果」里对应的「词」及所属点类型等),而不是主 agent 传入的「本轮推导出的可能选题点」。按主 agent 所需的节点格式列出(如 name、point、dimension、root_source、root_sources_desc、derivation_type、original_word 等),便于主 agent 直接加入「已推导成功的选题点」集合。
-
-3. **判断是否需要进行下一轮推导**
+2. **判断是否需要进行下一轮推导**
    - `帖子选题点数量`
    - `已推导成功选题点数量`
    - `是否需要进行下一轮推导`:只需要返回 是/否 及简要理由。