orchestrator.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. """使用多 Agent 模式的主系统编排逻辑"""
  2. from datetime import datetime
  3. from typing import Dict, Any, List, Optional
  4. from models import ContentNode, ContentLevel, ColumnPlan, ReviewResult
  5. from agents import (
  6. PlannerAgent,
  7. WriterAgent,
  8. ReflectionWriterAgent,
  9. ReviewerAgent,
  10. RevisionAgent
  11. )
  12. from config import get_settings, get_word_count
  13. class ColumnWriterOrchestrator:
  14. """
  15. 提供多 Agent 模式的专栏写作系统
  16. 架构设计:
  17. 1. PlannerAgent → PlanAndSolveAgent(任务分解和规划)
  18. 2. WriterAgent → ReActAgent(推理和工具调用)
  19. 3. 评审+修改 → ReflectionAgent(自我反思优化)
  20. """
  21. def __init__(self, use_reflection_mode: bool = False):
  22. """
  23. 初始化编排器
  24. Args:
  25. use_reflection_mode: 是否使用 ReflectionAgent 模式
  26. - True: 使用 ReflectionAgent(自动评审和优化)
  27. - False: 使用 ReActAgent + 独立评审流程
  28. """
  29. self.settings = get_settings()
  30. self.use_reflection_mode = use_reflection_mode
  31. # 创建各个 Agent
  32. print("\n 初始化专栏写作系统...")
  33. print(f" 模式选择: {'ReflectionAgent(自我反思)' if use_reflection_mode else 'ReActAgent(推理行动)+ 评审'}")
  34. # 规划 Agent - 使用 PlanAndSolveAgent
  35. self.planner = PlannerAgent()
  36. # 写作 Agent - 根据模式选择
  37. if use_reflection_mode:
  38. self.writer = ReflectionWriterAgent()
  39. print(" WriterAgent: ReflectionAgent(内置评审优化)")
  40. self.reviewer = None
  41. self.revision_agent = None
  42. else:
  43. self.writer = WriterAgent(enable_search=self.settings.enable_search)
  44. print(" WriterAgent: ReActAgent(推理-行动-搜索)")
  45. # 评审和修改 Agent(仅 ReAct 模式下可用)
  46. if self.settings.enable_review:
  47. self.reviewer = ReviewerAgent()
  48. self.revision_agent = RevisionAgent()
  49. print(f" ReviewerAgent: 已启用(通过阈值: {self.settings.approval_threshold})")
  50. print(f" RevisionAgent: 已启用(最大修改次数: {self.settings.max_revisions})")
  51. else:
  52. self.reviewer = None
  53. self.revision_agent = None
  54. print(" ReviewerAgent: 已禁用")
  55. # 统计信息
  56. self.stats = {
  57. 'total_generations': 0,
  58. 'total_reviews': 0,
  59. 'total_revisions': 0,
  60. 'total_rewrites': 0,
  61. 'approved_first_try': 0,
  62. 'start_time': None,
  63. 'end_time': None
  64. }
  65. print("▸ 系统初始化完成\n")
  66. def create_column(self, main_topic: str) -> Dict[str, Any]:
  67. """
  68. 创建完整专栏
  69. Args:
  70. main_topic: 专栏主题
  71. Returns:
  72. 包含专栏完整信息的字典
  73. """
  74. self.stats['start_time'] = datetime.now()
  75. print(f"\n{'='*70}")
  76. print(f"▸ 开始创建专栏:{main_topic}")
  77. print(f"{'='*70}\n")
  78. # Step 1: 规划专栏结构(使用 PlanAndSolveAgent)
  79. print("▸ 第一步:规划专栏结构(PlanAndSolveAgent)")
  80. print("-" * 70)
  81. column_plan = self.planner.plan_column(main_topic)
  82. print(f" 标题:{column_plan.column_title}")
  83. print(f" 话题数:{column_plan.get_topic_count()} 个")
  84. print(f" 目标读者:{column_plan.target_audience}\n")
  85. # Step 2: 为每个子话题创建内容树
  86. mode_name = "ReflectionAgent" if self.use_reflection_mode else "ReActAgent"
  87. print(f"▸️ 第二步:撰写专栏文章({mode_name})")
  88. print("-" * 70)
  89. content_trees = self._write_topics_sequential(column_plan)
  90. # Step 3: 组装完整专栏
  91. print("\n▸ 第三步:组装专栏内容")
  92. print("-" * 70)
  93. full_column = self._assemble_column(column_plan, content_trees)
  94. self.stats['end_time'] = datetime.now()
  95. duration = (self.stats['end_time'] - self.stats['start_time']).total_seconds()
  96. print(f"\n{'='*70}")
  97. print(f"▸ 专栏创建完成!耗时 {duration:.1f} 秒")
  98. print(f"{'='*70}\n")
  99. # 添加统计信息
  100. full_column['creation_stats'] = self.stats
  101. full_column['agent_modes'] = {
  102. 'planner': 'PlanAndSolveAgent',
  103. 'writer': 'ReflectionAgent' if self.use_reflection_mode else 'ReActAgent',
  104. 'reviewer': 'ReviewerAgent' if (self.reviewer and not self.use_reflection_mode) else None,
  105. 'revision': 'RevisionAgent' if (self.revision_agent and not self.use_reflection_mode) else None
  106. }
  107. return full_column
  108. def _write_topics_sequential(self, column_plan: ColumnPlan) -> List[ContentNode]:
  109. """顺序写作各个话题"""
  110. content_trees = []
  111. for idx, topic in enumerate(column_plan.topics, 1):
  112. print(f"\n{'─'*70}")
  113. print(f"▸ 正在写作第 {idx}/{column_plan.get_topic_count()} 个话题")
  114. print(f" 话题:{topic['title']}")
  115. print(f"{'─'*70}")
  116. tree = self._write_topic_tree(topic, column_plan)
  117. content_trees.append(tree)
  118. # 显示进度
  119. progress = idx / column_plan.get_topic_count() * 100
  120. print(f"\n▸ 总体进度:{progress:.0f}% ({idx}/{column_plan.get_topic_count()})")
  121. return content_trees
  122. def _write_topic_tree(
  123. self,
  124. topic: Dict[str, Any],
  125. column_context: ColumnPlan
  126. ) -> ContentNode:
  127. """递归写作话题树"""
  128. root = ContentNode(
  129. id=topic['id'],
  130. title=topic['title'],
  131. level=ContentLevel.TOPIC,
  132. description=topic['description']
  133. )
  134. context = {
  135. 'column_title': column_context.column_title,
  136. 'column_description': column_context.column_description,
  137. 'target_audience': column_context.target_audience,
  138. 'current_topic': topic
  139. }
  140. self._recursive_write(root, context, level=1)
  141. return root
  142. def _recursive_write(
  143. self,
  144. node: ContentNode,
  145. context: Dict[str, Any],
  146. level: int
  147. ):
  148. """递归写作核心逻辑"""
  149. if level > self.settings.max_depth:
  150. indent = " " * level
  151. print(f"{indent}▸️ 达到最大深度 {self.settings.max_depth},停止展开")
  152. return
  153. indent = " " * level
  154. print(f"\n{indent}{'┈'*40}")
  155. print(f"{indent}▸ Level {level}: {node.title}")
  156. print(f"{indent}{'┈'*40}")
  157. if self.use_reflection_mode:
  158. # 模式1: 使用 ReflectionAgent(内置评审优化)
  159. self._write_with_reflection(node, context, level, indent)
  160. else:
  161. # 模式2: 使用 ReActAgent(推理-行动)
  162. self._write_with_react(node, context, level, indent)
  163. def _write_with_reflection(
  164. self,
  165. node: ContentNode,
  166. context: Dict[str, Any],
  167. level: int,
  168. indent: str
  169. ):
  170. """使用 ReflectionAgent 模式写作"""
  171. print(f"{indent}▸️ 使用 ReflectionAgent 生成并优化内容...")
  172. content_data = self.writer.generate_and_refine_content(node, context, level)
  173. self.stats['total_generations'] += 1
  174. # ReflectionAgent 已经完成了自我评审和优化
  175. node.content = content_data['content']
  176. node.metadata = content_data.get('metadata', {})
  177. node.metadata['agent_mode'] = 'ReflectionAgent'
  178. node.metadata['auto_refined'] = True
  179. word_count = content_data.get('word_count', len(content_data['content']))
  180. print(f"{indent} 字数:{word_count}")
  181. print(f"{indent}▸ 内容已通过自我反思优化")
  182. # 处理子节点
  183. self._process_children(node, content_data, context, level, indent)
  184. def _write_with_react(
  185. self,
  186. node: ContentNode,
  187. context: Dict[str, Any],
  188. level: int,
  189. indent: str
  190. ):
  191. """使用 ReActAgent 模式写作(可选评审)"""
  192. print(f"{indent}▸️ 使用 ReActAgent 生成内容(推理-行动)...")
  193. content_data = self.writer.generate_content(node, context, level)
  194. self.stats['total_generations'] += 1
  195. current_content = content_data['content']
  196. word_count = content_data.get('word_count', len(current_content))
  197. print(f"{indent} 字数:{word_count}")
  198. print(f"{indent}▸ ReActAgent 完成推理和行动")
  199. # 如果启用评审,进行评审和可能的修改
  200. if self.reviewer and self.settings.enable_review:
  201. current_content, review_metadata = self._review_and_revise(
  202. node, current_content, content_data, level, indent
  203. )
  204. content_data['content'] = current_content
  205. content_data['metadata'] = {**content_data.get('metadata', {}), **review_metadata}
  206. node.content = current_content
  207. node.metadata = content_data.get('metadata', {})
  208. node.metadata['agent_mode'] = 'ReActAgent'
  209. # 处理子节点
  210. self._process_children(node, content_data, context, level, indent)
  211. def _review_and_revise(
  212. self,
  213. node: ContentNode,
  214. content: str,
  215. content_data: Dict[str, Any],
  216. level: int,
  217. indent: str
  218. ) -> tuple:
  219. """
  220. 评审并根据需要修改内容
  221. Args:
  222. node: 当前节点
  223. content: 当前内容
  224. content_data: 完整的内容数据
  225. level: 层级
  226. indent: 缩进
  227. Returns:
  228. (最终内容, 评审元数据)
  229. """
  230. target_word_count = get_word_count(level)
  231. key_points = content_data.get('metadata', {}).get('keywords', [])
  232. if not key_points:
  233. key_points = [node.title, node.description]
  234. revision_count = 0
  235. final_content = content
  236. review_history = []
  237. while revision_count <= self.settings.max_revisions:
  238. # 评审
  239. print(f"{indent}▸ 开始评审(第 {revision_count + 1} 轮)...")
  240. review_result = self.reviewer.review_content(
  241. content=final_content,
  242. level=level,
  243. target_word_count=target_word_count,
  244. key_points=key_points
  245. )
  246. self.stats['total_reviews'] += 1
  247. review_history.append({
  248. 'round': revision_count + 1,
  249. 'score': review_result.score,
  250. 'grade': review_result.grade,
  251. 'needs_revision': review_result.needs_revision
  252. })
  253. print(f"{indent} 评审结果: {review_result.score}/100 ({review_result.grade})")
  254. # 检查是否通过评审
  255. if review_result.score >= self.settings.approval_threshold:
  256. print(f"{indent}▸ 内容通过评审!")
  257. if revision_count == 0:
  258. self.stats['approved_first_try'] += 1
  259. break
  260. # 检查是否还能修改
  261. if revision_count >= self.settings.max_revisions:
  262. print(f"{indent}▸️ 达到最大修改次数 ({self.settings.max_revisions}),使用当前版本")
  263. break
  264. # 检查是否需要重写(分数太低)
  265. if review_result.score < self.settings.revision_threshold:
  266. print(f"{indent}▸️ 分数过低 ({review_result.score} < {self.settings.revision_threshold}),需要重写")
  267. self.stats['total_rewrites'] += 1
  268. # 重新生成内容
  269. new_content_data = self.writer.generate_content(
  270. node,
  271. {'review_feedback': review_result.reviewer_notes},
  272. level,
  273. additional_requirements=f"请注意避免以下问题: {review_result.reviewer_notes}"
  274. )
  275. self.stats['total_generations'] += 1
  276. final_content = new_content_data['content']
  277. else:
  278. # 修改内容
  279. print(f"{indent}▸ 根据评审意见修改内容...")
  280. revised_data = self.revision_agent.revise_content(
  281. original_content=final_content,
  282. review_result=review_result,
  283. target_word_count=target_word_count
  284. )
  285. self.stats['total_revisions'] += 1
  286. final_content = revised_data.get('revised_content', final_content)
  287. revision_count += 1
  288. # 构建评审元数据
  289. final_review = review_history[-1] if review_history else {}
  290. review_metadata = {
  291. 'review_score': final_review.get('score'),
  292. 'review_grade': final_review.get('grade'),
  293. 'review_rounds': len(review_history),
  294. 'review_history': review_history,
  295. 'reviewed': True
  296. }
  297. return final_content, review_metadata
  298. def _process_children(
  299. self,
  300. node: ContentNode,
  301. content_data: Dict[str, Any],
  302. context: Dict[str, Any],
  303. level: int,
  304. indent: str
  305. ):
  306. """处理子节点"""
  307. if content_data.get('needs_expansion') and level < self.settings.max_depth:
  308. subsections = content_data.get('subsections', [])
  309. if subsections:
  310. print(f"{indent}▸ 需要展开 {len(subsections)} 个子节点")
  311. for subsection in subsections:
  312. child = ContentNode(
  313. id=subsection['id'],
  314. title=subsection['title'],
  315. level=ContentLevel(level + 1),
  316. description=subsection['description']
  317. )
  318. node.add_child(child)
  319. # 递归写作子节点
  320. self._recursive_write(child, context, level + 1)
  321. def _assemble_column(
  322. self,
  323. plan: ColumnPlan,
  324. trees: List[ContentNode]
  325. ) -> Dict[str, Any]:
  326. """组装完整专栏"""
  327. articles = []
  328. for tree in trees:
  329. article_content = self._tree_to_markdown(tree)
  330. articles.append({
  331. 'id': tree.id,
  332. 'title': tree.title,
  333. 'content': article_content,
  334. 'metadata': tree.metadata,
  335. 'word_count': tree.count_words()
  336. })
  337. return {
  338. 'column_info': {
  339. 'title': plan.column_title,
  340. 'description': plan.column_description,
  341. 'target_audience': plan.target_audience,
  342. 'topic_count': plan.get_topic_count()
  343. },
  344. 'articles': articles,
  345. 'statistics': self._calculate_statistics(trees)
  346. }
  347. def _tree_to_markdown(self, node: ContentNode, depth: int = 0) -> str:
  348. """将内容树转换为markdown"""
  349. markdown = []
  350. heading_level = "#" * (depth + 1)
  351. markdown.append(f"{heading_level} {node.title}\n")
  352. if node.content:
  353. markdown.append(node.content)
  354. markdown.append("\n")
  355. for child in node.children:
  356. child_md = self._tree_to_markdown(child, depth + 1)
  357. markdown.append(child_md)
  358. return "\n".join(markdown)
  359. def _calculate_statistics(self, trees: List[ContentNode]) -> Dict[str, Any]:
  360. """计算统计信息"""
  361. total_words = 0
  362. total_nodes = 0
  363. def count_tree(node: ContentNode):
  364. nonlocal total_words, total_nodes
  365. total_nodes += 1
  366. total_words += len(node.content) if node.content else 0
  367. for child in node.children:
  368. count_tree(child)
  369. for tree in trees:
  370. count_tree(tree)
  371. return {
  372. 'total_articles': len(trees),
  373. 'total_nodes': total_nodes,
  374. 'total_words': total_words,
  375. 'avg_words_per_article': total_words // len(trees) if trees else 0
  376. }