agents_advanced.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752
  1. """核心 Agent"""
  2. import json
  3. import os
  4. from typing import Dict, Any, Optional, List
  5. from hello_agents import (
  6. HelloAgentsLLM,
  7. ReActAgent,
  8. ReflectionAgent,
  9. PlanAndSolveAgent
  10. )
  11. from hello_agents.tools import MCPTool, ToolRegistry
  12. from models import ColumnPlan, ReviewResult, ContentNode, ContentLevel
  13. from prompts import get_structure_requirements, get_react_writer_prompt, get_reflection_writer_prompts, get_planner_prompts
  14. from config import get_settings, get_word_count
  15. import re # Added for JSON parsing
  16. settings = get_settings()
  17. class LLMService:
  18. """LLM 服务单例"""
  19. _instance: Optional[HelloAgentsLLM] = None
  20. @classmethod
  21. def get_llm(cls) -> HelloAgentsLLM:
  22. """获取 LLM 实例(单例模式)"""
  23. if cls._instance is None:
  24. cls._instance = HelloAgentsLLM()
  25. print(f"✅ LLM服务初始化成功")
  26. print(f" 提供商: {cls._instance.provider}")
  27. print(f" 模型: {cls._instance.model}")
  28. return cls._instance
  29. class AdvancedPlannerAgent:
  30. """
  31. 使用 PlanAndSolveAgent 模式
  32. PlanAndSolveAgent 将任务分解为子任务并逐步执行,非常适合专栏规划场景:
  33. 1. 分析主题(理解用户需求)
  34. 2. 规划子话题(分解任务)
  35. 3. 组织结构(逐步执行)
  36. """
  37. def __init__(self):
  38. self.llm = LLMService.get_llm()
  39. # 自定义 PlanAndSolve 提示词
  40. planner_prompts = {
  41. "planner": """
  42. 你是一位经验丰富的专栏策划专家。请将以下专栏主题分解为清晰的子话题规划步骤。
  43. 主题: {question}
  44. 请按以下格式输出规划步骤:
  45. ```python
  46. [
  47. "步骤1: 分析主题的核心概念和目标读者",
  48. "步骤2: 确定知识体系的整体框架",
  49. "步骤3: 规划5-10个子话题,确保逻辑递进",
  50. "步骤4: 为每个子话题设定学习目标和要点",
  51. "步骤5: 组装完整的专栏大纲"
  52. ]
  53. ```
  54. """,
  55. "executor": """
  56. 你是专栏规划执行专家。请按照规划步骤执行专栏大纲的生成。
  57. # 原始主题: {question}
  58. # 规划步骤: {plan}
  59. # 已完成步骤: {history}
  60. # 当前步骤: {current_step}
  61. 请执行当前步骤。如果这是最后一步,请输出完整的 JSON 格式专栏大纲:
  62. ```json
  63. {{
  64. "column_title": "专栏总标题",
  65. "column_description": "专栏简介(100-200字)",
  66. "target_audience": "目标读者群体",
  67. "topics": [
  68. {{
  69. "id": "topic_001",
  70. "title": "子话题标题",
  71. "description": "子话题简介(50-100字)",
  72. "estimated_words": 2500,
  73. "key_points": ["要点1", "要点2", "要点3"],
  74. "prerequisites": ["前置知识1", "前置知识2"]
  75. }}
  76. ]
  77. }}
  78. ```
  79. 如果不是最后一步,请输出当前步骤的分析结果。
  80. """
  81. }
  82. self.agent = PlanAndSolveAgent(
  83. name="专栏规划专家",
  84. llm=self.llm,
  85. custom_prompts=planner_prompts
  86. )
  87. def plan_column(self, main_topic: str) -> ColumnPlan:
  88. """
  89. 规划专栏大纲
  90. Args:
  91. main_topic: 专栏主题
  92. Returns:
  93. ColumnPlan 实例
  94. """
  95. print(f"\n📋 PlanAndSolve Agent 开始规划专栏...")
  96. print(f" 使用模式: 任务分解 → 逐步执行")
  97. print(f" 主题: {main_topic}")
  98. response = self.agent.run(main_topic)
  99. # 解析 JSON 响应
  100. plan_data = self._extract_json(response)
  101. plan = ColumnPlan.from_dict(plan_data)
  102. print(f"✅ 规划完成")
  103. print(f" 专栏标题: {plan.column_title}")
  104. print(f" 话题数量: {plan.get_topic_count()}")
  105. return plan
  106. def _extract_json(self, response: str) -> Dict[str, Any]:
  107. """从响应中提取 JSON"""
  108. try:
  109. if response.strip().startswith('{'):
  110. return json.loads(response)
  111. if "```json" in response:
  112. json_start = response.find("```json") + 7
  113. json_end = response.find("```", json_start)
  114. json_str = response[json_start:json_end].strip()
  115. elif "```" in response:
  116. json_start = response.find("```") + 3
  117. json_end = response.find("```", json_start)
  118. json_str = response[json_start:json_end].strip()
  119. elif "{" in response and "}" in response:
  120. json_start = response.find("{")
  121. json_end = response.rfind("}") + 1
  122. json_str = response[json_start:json_end]
  123. else:
  124. raise ValueError("响应中未找到 JSON 数据")
  125. return json.loads(json_str)
  126. except Exception as e:
  127. print(f"⚠️ JSON 解析失败: {e}")
  128. print(f" 响应内容: {response[:500]}...")
  129. raise
  130. class ReActAgentWrapper:
  131. """
  132. ReActAgent 包装器,用于捕获历史信息和处理错误
  133. """
  134. def __init__(self, agent: ReActAgent):
  135. self.agent = agent
  136. self.last_history = [] # 保存最后一次运行的历史
  137. self.last_response = None
  138. def run(self, question: str):
  139. """运行 Agent 并捕获历史信息"""
  140. try:
  141. # 尝试访问 agent 的 history 属性(如果存在)
  142. if hasattr(self.agent, 'history'):
  143. original_history = self.agent.history.copy() if self.agent.history else []
  144. else:
  145. original_history = []
  146. response = self.agent.run(question)
  147. self.last_response = response
  148. # 尝试获取最终的历史信息
  149. if hasattr(self.agent, 'history'):
  150. self.last_history = self.agent.history.copy() if self.agent.history else []
  151. else:
  152. self.last_history = original_history
  153. return response
  154. except Exception as e:
  155. # 即使出错也尝试保存历史
  156. if hasattr(self.agent, 'history'):
  157. self.last_history = self.agent.history.copy() if self.agent.history else []
  158. raise
  159. class AdvancedWriterAgent:
  160. """
  161. 写作 Agent - 使用 ReActAgent 模式
  162. ReActAgent 结合推理(Reasoning)和行动(Acting),非常适合需要工具调用的写作场景:
  163. 1. 分析写作需求(推理)
  164. 2. 决定是否需要搜索(推理)
  165. 3. 调用搜索工具(行动)
  166. 4. 整合信息写作(行动)
  167. """
  168. def __init__(self, enable_search: bool = True):
  169. """
  170. 初始化写作 Agent
  171. Args:
  172. enable_search: 是否启用搜索功能
  173. """
  174. self.llm = LLMService.get_llm()
  175. self.enable_search = enable_search
  176. # 创建工具注册表
  177. self.tool_registry = ToolRegistry()
  178. # 添加搜索工具(如果启用)
  179. if enable_search:
  180. self._setup_search_tool()
  181. # 自定义 ReAct 提示词(参考示例代码的简洁格式)
  182. react_prompt = get_react_writer_prompt() # 从 prompts.py 获取
  183. # 创建 ReActAgent 并用包装器包装
  184. react_agent = ReActAgent(
  185. name="内容创作专家",
  186. llm=self.llm,
  187. tool_registry=self.tool_registry,
  188. custom_prompt=react_prompt,
  189. max_steps=10 # 增加到 10 步,给 Agent 更多机会完成任务
  190. )
  191. self.agent = ReActAgentWrapper(react_agent)
  192. def _setup_search_tool(self):
  193. """设置搜索工具(使用 MCPTool)"""
  194. settings = get_settings()
  195. # 检查是否配置了搜索 API
  196. has_search_api = bool(settings.tavily_api_key or settings.serpapi_api_key)
  197. if not has_search_api:
  198. print("⚠️ 未配置搜索 API Key,WriterAgent 将使用 ReAct 模式但无搜索能力")
  199. return
  200. try:
  201. # 准备环境变量
  202. env = {}
  203. if settings.tavily_api_key:
  204. env["TAVILY_API_KEY"] = settings.tavily_api_key
  205. if settings.serpapi_api_key:
  206. env["SERPAPI_API_KEY"] = settings.serpapi_api_key
  207. # 创建搜索 MCP 工具
  208. search_tool = MCPTool(
  209. name="search",
  210. description="联网搜索工具,提供 web_search, search_recent_info, search_code_examples, verify_facts 等功能",
  211. server_command=["python", "search_mcp_server.py"],
  212. env=env,
  213. auto_expand=True
  214. )
  215. # 将 MCP 工具的所有子工具注册到 ToolRegistry
  216. # 注意:这里我们需要手动注册,因为 ReActAgent 使用 ToolRegistry
  217. self._register_search_functions()
  218. print("✅ 搜索工具已添加到 ReActAgent")
  219. except Exception as e:
  220. print(f"⚠️ 添加搜索工具失败: {e}")
  221. def _register_search_functions(self):
  222. """注册搜索函数到 ToolRegistry"""
  223. # 这里注册模拟的搜索函数(实际项目中应该调用 MCP 服务器)
  224. def web_search(query: str) -> str:
  225. """网页搜索"""
  226. return f"[模拟搜索结果] 关于 '{query}' 的搜索结果..."
  227. def search_recent_info(topic: str) -> str:
  228. """搜索最新信息"""
  229. return f"[模拟最新信息] 关于 '{topic}' 的最新动态..."
  230. def search_code_examples(technology: str, task: str) -> str:
  231. """搜索代码示例"""
  232. return f"[模拟代码示例] {technology} 实现 {task} 的示例代码..."
  233. def verify_facts(statement: str) -> str:
  234. """验证事实"""
  235. return f"[模拟验证结果] 关于 '{statement}' 的验证信息..."
  236. self.tool_registry.register_function(
  237. "web_search",
  238. "通用网页搜索,获取最新资讯和资料",
  239. web_search
  240. )
  241. self.tool_registry.register_function(
  242. "search_recent_info",
  243. "搜索最新信息和动态",
  244. search_recent_info
  245. )
  246. self.tool_registry.register_function(
  247. "search_code_examples",
  248. "搜索代码示例和教程",
  249. search_code_examples
  250. )
  251. self.tool_registry.register_function(
  252. "verify_facts",
  253. "验证事实准确性",
  254. verify_facts
  255. )
  256. def generate_content(
  257. self,
  258. node: ContentNode,
  259. context: Dict[str, Any],
  260. level: int,
  261. additional_requirements: str = ""
  262. ) -> Dict[str, Any]:
  263. """
  264. 生成内容(使用 ReAct 模式)
  265. Args:
  266. node: 当前节点
  267. context: 写作上下文
  268. level: 当前层级
  269. additional_requirements: 额外要求
  270. Returns:
  271. 生成的内容数据
  272. """
  273. structure_requirements = get_structure_requirements(level)
  274. word_count = get_word_count(level)
  275. # 构建写作任务描述(简化格式,参考示例代码)
  276. task_description = f"""
  277. 请撰写一篇技术专栏文章。
  278. 层级: Level {level}/3
  279. 话题: {node.title}
  280. 描述: {node.description}
  281. 要求字数: {word_count} 字(允许误差±10%)
  282. 上下文信息:
  283. {json.dumps(context, ensure_ascii=False, indent=2)}
  284. 结构要求:
  285. {structure_requirements}
  286. 额外要求:
  287. {additional_requirements if additional_requirements else "无"}
  288. 重要提示:
  289. - 完成写作后,必须使用 `Finish[JSON内容]` 格式输出结果
  290. - JSON 中的 `level` 字段必须是 {level}
  291. - `content` 字段必须包含完整的文章正文(Markdown格式)
  292. - 文章必须包含:引言、主体内容(3-5个小节)、实践案例、总结
  293. """
  294. try:
  295. response = self.agent.run(task_description)
  296. # 调试:打印原始响应
  297. print(f"\n{'='*70}")
  298. print("📋 ReActAgent 原始响应:")
  299. print(f"{'='*70}")
  300. print(response[:1000] if len(response) > 1000 else response)
  301. print(f"{'='*70}\n")
  302. # 检查是否是错误消息
  303. if response and ("无法在限定步数内完成" in response or "抱歉" in response):
  304. print("⚠️ ReActAgent 达到最大步数限制或无法完成任务")
  305. print(f" 已收集的历史信息: {len(self.agent.last_history)} 条")
  306. # 如果达到步数限制,基于历史信息生成内容
  307. return self._generate_content_with_history(
  308. node, context, level, structure_requirements, word_count,
  309. self.agent.last_history, task_description
  310. )
  311. content_data = self._extract_json(response)
  312. return content_data
  313. except Exception as e:
  314. print(f"⚠️ ReActAgent 执行失败: {e}")
  315. print(f" 已收集的历史信息: {len(self.agent.last_history)} 条")
  316. print(" 尝试基于历史信息生成内容...")
  317. return self._generate_content_with_history(
  318. node, context, level, structure_requirements, word_count,
  319. self.agent.last_history, task_description
  320. )
  321. def _generate_content_with_history(
  322. self,
  323. node: ContentNode,
  324. context: Dict[str, Any],
  325. level: int,
  326. structure_requirements: str,
  327. word_count: int,
  328. history: List[str],
  329. original_task: str
  330. ) -> Dict[str, Any]:
  331. """
  332. 当 ReActAgent 失败时,基于历史信息使用 SimpleAgent 生成内容
  333. Args:
  334. history: ReActAgent 收集的历史信息(Thought、Action、Observation)
  335. """
  336. from hello_agents import SimpleAgent
  337. fallback_agent = SimpleAgent(
  338. name="内容创作专家(备用)",
  339. llm=self.llm,
  340. system_prompt="你是一位专业的内容创作者,擅长撰写技术专栏文章。"
  341. )
  342. # 构建包含历史信息的任务描述
  343. history_summary = ""
  344. if history:
  345. history_summary = "\n\n## 已撰写的部分历史:\n"
  346. for i, item in enumerate(history[-10:], 1): # 只取最后10条历史
  347. history_summary += f"{i}. {item}\n"
  348. history_summary += "\n请基于以上信息继续完成写作任务。\n"
  349. task = f"""
  350. 请撰写一篇技术专栏文章。
  351. 话题: {node.title}
  352. 描述: {node.description}
  353. 要求字数: {word_count} 字
  354. 结构要求:
  355. {structure_requirements}
  356. {history_summary}
  357. 请直接输出 JSON 格式的内容:
  358. {{
  359. "title": "{node.title}",
  360. "level": {level},
  361. "content": "完整的文章正文(markdown格式,包含引言、主体、案例、总结)",
  362. "word_count": 实际字数,
  363. "needs_expansion": false,
  364. "subsections": [],
  365. "metadata": {{}}
  366. }}
  367. """
  368. print(f"📝 使用 SimpleAgent 基于历史信息生成内容...")
  369. response = fallback_agent.run(task)
  370. return self._extract_json(response)
  371. def revise_content(
  372. self,
  373. original_content: str,
  374. review_result: ReviewResult,
  375. level: int
  376. ) -> Dict[str, Any]:
  377. """
  378. 根据评审意见修改内容
  379. Args:
  380. original_content: 原始内容
  381. review_result: 评审结果
  382. level: 层级
  383. Returns:
  384. 修改后的内容数据
  385. """
  386. # 构建修改任务
  387. task_description = f"""
  388. ## 修改任务
  389. **原始内容**:
  390. {original_content[:500]}...
  391. **评审分数**: {review_result.score}/100
  392. **评审等级**: {review_result.grade}
  393. **主要问题**:
  394. {json.dumps(review_result.detailed_feedback.get('issues', [])[:3], ensure_ascii=False, indent=2)}
  395. **修改建议**:
  396. {json.dumps(review_result.revision_plan.get('priority_changes', []), ensure_ascii=False, indent=2)}
  397. 请使用 ReAct 模式完成修改:
  398. 1. 思考评审意见的核心要求
  399. 2. 决定是否需要搜索新信息
  400. 3. 修改内容
  401. 4. 使用 Finish[修改后的JSON内容] 输出结果
  402. """
  403. response = self.agent.run(task_description)
  404. revised_data = self._extract_json(response)
  405. return revised_data
  406. def _extract_json(self, response: str) -> Dict[str, Any]:
  407. """
  408. 从响应中提取 JSON(支持多种格式,包括 Finish[...] 格式)
  409. 增强的 JSON 解析,能够处理包含复杂字符串的 JSON
  410. """
  411. import re
  412. import json.encoder
  413. def extract_json_with_retry(json_str: str) -> Dict[str, Any]:
  414. """尝试多种方式解析 JSON"""
  415. # 方法1: 直接解析
  416. try:
  417. return json.loads(json_str)
  418. except json.JSONDecodeError:
  419. pass
  420. # 方法2: 尝试修复常见的 JSON 问题
  421. # 修复未转义的换行符
  422. fixed = json_str.replace('\n', '\\n').replace('\r', '\\r').replace('\t', '\\t')
  423. try:
  424. return json.loads(fixed)
  425. except json.JSONDecodeError:
  426. pass
  427. # 方法3: 尝试提取并重新构建 JSON
  428. # 提取各个字段
  429. title_match = re.search(r'"title"\s*:\s*"([^"]*)"', json_str)
  430. level_match = re.search(r'"level"\s*:\s*(\d+)', json_str)
  431. word_count_match = re.search(r'"word_count"\s*:\s*(\d+)', json_str)
  432. needs_expansion_match = re.search(r'"needs_expansion"\s*:\s*(true|false)', json_str)
  433. # 提取 content(可能跨多行)
  434. content_match = re.search(r'"content"\s*:\s*"(.*?)"(?=\s*[,}])', json_str, re.DOTALL)
  435. if not content_match:
  436. # 尝试另一种格式
  437. content_match = re.search(r'"content"\s*:\s*"([^"]*(?:\\.[^"]*)*)"', json_str, re.DOTALL)
  438. result = {}
  439. if title_match:
  440. result['title'] = title_match.group(1)
  441. if level_match:
  442. result['level'] = int(level_match.group(1))
  443. if content_match:
  444. # 处理转义字符
  445. content = content_match.group(1)
  446. content = content.replace('\\n', '\n').replace('\\r', '\r').replace('\\t', '\t')
  447. result['content'] = content
  448. if word_count_match:
  449. result['word_count'] = int(word_count_match.group(1))
  450. else:
  451. result['word_count'] = len(result.get('content', ''))
  452. if needs_expansion_match:
  453. result['needs_expansion'] = needs_expansion_match.group(1) == 'true'
  454. else:
  455. result['needs_expansion'] = False
  456. result['subsections'] = []
  457. result['metadata'] = {}
  458. return result
  459. try:
  460. # 方法1: 尝试从 Finish[...] 格式中提取(ReAct 标准格式)
  461. finish_match = re.search(r"Finish\[(.*?)\]", response, re.DOTALL)
  462. if finish_match:
  463. finish_content = finish_match.group(1).strip()
  464. print(f"🔍 找到 Finish 格式,内容长度: {len(finish_content)}")
  465. return extract_json_with_retry(finish_content)
  466. # 方法2: 直接是 JSON 对象
  467. if response.strip().startswith('{'):
  468. return extract_json_with_retry(response.strip())
  469. # 方法3: Markdown 代码块中的 JSON
  470. if "```json" in response:
  471. json_start = response.find("```json") + 7
  472. json_end = response.find("```", json_start)
  473. json_str = response[json_start:json_end].strip()
  474. return extract_json_with_retry(json_str)
  475. # 方法4: 普通代码块中的 JSON
  476. if "```" in response:
  477. json_start = response.find("```") + 3
  478. json_end = response.find("```", json_start)
  479. json_str = response[json_start:json_end].strip()
  480. if json_str.startswith("json"):
  481. json_str = json_str[4:].strip()
  482. return extract_json_with_retry(json_str)
  483. # 方法5: 尝试提取第一个完整的 JSON 对象(使用更宽松的正则)
  484. # 匹配从第一个 { 到最后一个 } 之间的内容
  485. brace_start = response.find('{')
  486. if brace_start != -1:
  487. brace_count = 0
  488. brace_end = brace_start
  489. for i in range(brace_start, len(response)):
  490. if response[i] == '{':
  491. brace_count += 1
  492. elif response[i] == '}':
  493. brace_count -= 1
  494. if brace_count == 0:
  495. brace_end = i + 1
  496. break
  497. if brace_end > brace_start:
  498. json_str = response[brace_start:brace_end]
  499. return extract_json_with_retry(json_str)
  500. # 如果都失败了,抛出错误并显示响应内容
  501. print(f"⚠️ 无法从响应中提取 JSON")
  502. print(f" 响应完整内容(前2000字符):\n{response[:2000]}")
  503. raise ValueError("响应中未找到有效的 JSON 数据")
  504. except Exception as e:
  505. print(f"⚠️ 提取 JSON 时发生错误: {e}")
  506. print(f" 响应内容(前1000字符): {response[:1000]}")
  507. raise
  508. class AdvancedReflectionWriterAgent:
  509. """
  510. 反思写作 Agent - 使用 ReflectionAgent 模式
  511. ReflectionAgent 通过自我反思和迭代优化来改进输出,将评审和修改整合为一个 Agent:
  512. 1. 生成初稿
  513. 2. 自我评审(反思)
  514. 3. 根据反思修改(优化)
  515. 4. 达到质量标准
  516. """
  517. def __init__(self):
  518. self.llm = LLMService.get_llm()
  519. # 自定义 Reflection 提示词
  520. reflection_prompts = {
  521. "initial": """
  522. 你是一位专业的内容创作者。请撰写以下内容的初稿:
  523. {task}
  524. 请输出完整的 JSON 格式内容。
  525. """,
  526. "reflect": """
  527. 你是一位严格的内容评审专家。请评审以下内容:
  528. # 写作任务: {task}
  529. # 内容初稿: {content}
  530. 请从以下维度评审:
  531. 1. **内容质量** (40分): 准确性、完整性、深度、原创性
  532. 2. **结构逻辑** (30分): 层次清晰、逻辑连贯、过渡自然
  533. 3. **语言表达** (20分): 易读性、专业性、准确性
  534. 4. **格式规范** (10分): 字数达标、格式正确、排版美观
  535. 如果内容质量很好(85分以上),请回答"无需改进"。
  536. 否则,请详细指出问题并提供具体的修改建议。
  537. """,
  538. "refine": """
  539. 请根据评审意见优化你的内容:
  540. # 原始任务: {task}
  541. # 当前内容: {last_attempt}
  542. # 评审意见: {feedback}
  543. 请输出优化后的完整 JSON 格式内容。
  544. """
  545. }
  546. self.agent = ReflectionAgent(
  547. name="反思写作专家",
  548. llm=self.llm,
  549. custom_prompts=reflection_prompts,
  550. max_iterations=2 # 最多反思 2 次
  551. )
  552. def generate_and_refine_content(
  553. self,
  554. node: ContentNode,
  555. context: Dict[str, Any],
  556. level: int
  557. ) -> Dict[str, Any]:
  558. """
  559. 生成并反思优化内容
  560. Args:
  561. node: 当前节点
  562. context: 写作上下文
  563. level: 当前层级
  564. Returns:
  565. 优化后的内容数据
  566. """
  567. print(f"\n🔄 ReflectionAgent 开始写作并自我反思...")
  568. print(f" 使用模式: 初稿 → 自我评审 → 优化")
  569. structure_requirements = get_structure_requirements(level)
  570. word_count = get_word_count(level)
  571. task_description = f"""
  572. ## 写作任务
  573. **层级**: Level {level}/3
  574. **话题**: {node.title}
  575. **描述**: {node.description}
  576. **要求字数**: {word_count} 字(允许误差±10%)
  577. **结构要求**:
  578. {structure_requirements}
  579. **上下文**:
  580. {json.dumps(context, ensure_ascii=False, indent=2)}
  581. 请输出完整的 JSON 格式内容:
  582. ```json
  583. {{
  584. "title": "章节标题",
  585. "level": {level},
  586. "content": "正文内容(markdown格式)",
  587. "word_count": 实际字数,
  588. "needs_expansion": true/false,
  589. "subsections": [...],
  590. "metadata": {{...}}
  591. }}
  592. ```
  593. """
  594. response = self.agent.run(task_description)
  595. content_data = self._extract_json(response)
  596. print(f"✅ ReflectionAgent 完成反思优化")
  597. return content_data
  598. def _extract_json(self, response: str) -> Dict[str, Any]:
  599. """从响应中提取 JSON"""
  600. try:
  601. if response.strip().startswith('{'):
  602. return json.loads(response)
  603. if "```json" in response:
  604. json_start = response.find("```json") + 7
  605. json_end = response.find("```", json_start)
  606. json_str = response[json_start:json_end].strip()
  607. elif "```" in response:
  608. json_start = response.find("```") + 3
  609. json_end = response.find("```", json_start)
  610. json_str = response[json_start:json_end].strip()
  611. elif "{" in response and "}" in response:
  612. json_start = response.find("{")
  613. json_end = response.rfind("}") + 1
  614. json_str = response[json_start:json_end]
  615. else:
  616. raise ValueError("响应中未找到 JSON 数据")
  617. return json.loads(json_str)
  618. except Exception as e:
  619. print(f"⚠️ JSON 解析失败: {e}")
  620. raise