search_person_tree.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. """
  2. 人设树常量点检索工具 - 根据人设名称提取所有常量点
  3. 用于 Agent 执行时自主提取人设树中的常量点。
  4. """
  5. import json
  6. from pathlib import Path
  7. from typing import Any, Dict, List
  8. from agent.tools import tool, ToolResult
  9. # 人设数据基础路径
  10. PERSONA_DATA_BASE_PATH = Path(__file__).parent.parent / "data"
  11. def _extract_constant_points(tree_data: Dict[str, Any], dimension: str, path_prefix: str = "") -> List[Dict[str, Any]]:
  12. """
  13. 递归提取树中所有常量点
  14. Args:
  15. tree_data: 树节点数据
  16. dimension: 维度类型(形式/实质/意图)
  17. path_prefix: 当前路径前缀
  18. Returns:
  19. 常量点列表
  20. """
  21. constant_points = []
  22. for node_name, node_data in tree_data.items():
  23. # 跳过元数据字段
  24. if node_name.startswith("_"):
  25. continue
  26. # 构建当前节点的路径
  27. current_path = f"{path_prefix}>{node_name}" if path_prefix else node_name
  28. # 检查是否为常量点:_type 为 "ID" 且 _is_constant 为 true
  29. if isinstance(node_data, dict):
  30. node_type = node_data.get("_type")
  31. is_constant = node_data.get("_is_constant", False)
  32. if node_type == "ID" and is_constant:
  33. constant_points.append({
  34. "点名称": node_name,
  35. "维度": dimension,
  36. "路径": current_path,
  37. "权重": node_data.get("_persona_weight_score", 0),
  38. "帖子数量": node_data.get("_post_count", 0)
  39. })
  40. # 递归处理子节点
  41. if "children" in node_data:
  42. child_points = _extract_constant_points(
  43. node_data["children"],
  44. dimension,
  45. current_path
  46. )
  47. constant_points.extend(child_points)
  48. return constant_points
  49. def _load_persona_tree(persona_name: str) -> Dict[str, List[Dict[str, Any]]]:
  50. """
  51. 加载人设树数据并提取所有常量点
  52. Args:
  53. persona_name: 人设名称,如 "家有大志"
  54. Returns:
  55. 包含三个维度常量点的字典
  56. """
  57. persona_dir = PERSONA_DATA_BASE_PATH / persona_name / "tree"
  58. if not persona_dir.exists():
  59. return {
  60. "found": False,
  61. "persona_name": persona_name,
  62. "error": f"人设目录不存在: {persona_dir}"
  63. }
  64. tree_files = {
  65. "形式": persona_dir / "形式_point_tree_how.json",
  66. "实质": persona_dir / "实质_point_tree_how.json",
  67. "意图": persona_dir / "意图_point_tree_how.json"
  68. }
  69. all_constant_points = {
  70. "形式": [],
  71. "实质": [],
  72. "意图": []
  73. }
  74. missing_files = []
  75. for dimension, tree_file in tree_files.items():
  76. if not tree_file.exists():
  77. missing_files.append(f"{dimension}_point_tree_how.json")
  78. continue
  79. try:
  80. with open(tree_file, 'r', encoding='utf-8') as f:
  81. tree_data = json.load(f)
  82. # 提取该维度的所有常量点
  83. constant_points = _extract_constant_points(tree_data, dimension)
  84. all_constant_points[dimension] = constant_points
  85. except Exception as e:
  86. return {
  87. "found": False,
  88. "persona_name": persona_name,
  89. "error": f"读取文件 {tree_file.name} 失败: {str(e)}"
  90. }
  91. if missing_files:
  92. return {
  93. "found": False,
  94. "persona_name": persona_name,
  95. "error": f"缺失文件: {', '.join(missing_files)}"
  96. }
  97. return {
  98. "found": True,
  99. "persona_name": persona_name,
  100. "constant_points": all_constant_points
  101. }
  102. @tool(
  103. description="根据人设名称检索该人设树中的所有常量点(_is_constant=true的点)。",
  104. display={
  105. "zh": {
  106. "name": "人设常量点检索",
  107. "params": {
  108. "persona_name": "人设名称(如:家有大志)",
  109. },
  110. },
  111. },
  112. )
  113. async def search_person_tree_constants(persona_name: str) -> ToolResult:
  114. """
  115. 根据人设名称检索该人设树中的所有常量点。
  116. 常量点是指人设树中 _type 为 "ID" 且 _is_constant 为 true 的节点。
  117. 这些点代表了该人设的核心特征和固定属性。
  118. Args:
  119. persona_name: 人设名称,如 "家有大志"
  120. Returns:
  121. ToolResult: 包含三个维度(形式、实质、意图)的常量点列表
  122. """
  123. if not persona_name:
  124. return ToolResult(
  125. title="人设常量点检索失败",
  126. output="",
  127. error="请提供人设名称",
  128. )
  129. try:
  130. result = _load_persona_tree(persona_name)
  131. except Exception as e:
  132. return ToolResult(
  133. title="人设常量点检索失败",
  134. output="",
  135. error=f"检索异常: {str(e)}",
  136. )
  137. if not result.get("found"):
  138. return ToolResult(
  139. title="人设常量点检索失败",
  140. output="",
  141. error=result.get("error", "未知错误"),
  142. )
  143. constant_points = result["constant_points"]
  144. # 统计信息
  145. total_count = sum(len(points) for points in constant_points.values())
  146. # 格式化输出
  147. output_data = {
  148. "人设名称": persona_name,
  149. "常量点总数": total_count,
  150. "形式常量点": constant_points["形式"],
  151. "实质常量点": constant_points["实质"],
  152. "意图常量点": constant_points["意图"],
  153. "统计": {
  154. "形式点数": len(constant_points["形式"]),
  155. "实质点数": len(constant_points["实质"]),
  156. "意图点数": len(constant_points["意图"])
  157. }
  158. }
  159. output = json.dumps(output_data, ensure_ascii=False, indent=2)
  160. return ToolResult(
  161. title=f"人设常量点检索 - {persona_name}",
  162. output=output,
  163. long_term_memory=f"检索到 {total_count} 个常量点(形式:{len(constant_points['形式'])},实质:{len(constant_points['实质'])},意图:{len(constant_points['意图'])})",
  164. )