agents.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. """核心 Agent 实现"""
  2. import json
  3. import os
  4. from typing import Dict, Any, Optional
  5. from hello_agents import SimpleAgent, HelloAgentsLLM
  6. from hello_agents.tools import MCPTool
  7. from models import ColumnPlan, ReviewResult, ContentNode
  8. from prompts import (
  9. PLANNER_PROMPT,
  10. WRITER_PROMPT,
  11. REVIEWER_PROMPT,
  12. REVISION_PROMPT,
  13. get_structure_requirements
  14. )
  15. from config import get_settings, get_word_count
  16. class LLMService:
  17. """LLM 服务单例"""
  18. _instance: Optional[HelloAgentsLLM] = None
  19. @classmethod
  20. def get_llm(cls) -> HelloAgentsLLM:
  21. """获取 LLM 实例(单例模式)"""
  22. if cls._instance is None:
  23. cls._instance = HelloAgentsLLM()
  24. print(f"✅ LLM服务初始化成功")
  25. print(f" 提供商: {cls._instance.provider}")
  26. print(f" 模型: {cls._instance.model}")
  27. return cls._instance
  28. class PlannerAgent:
  29. """规划 Agent - 负责生成专栏大纲"""
  30. def __init__(self):
  31. self.llm = LLMService.get_llm()
  32. self.agent = SimpleAgent(
  33. name="专栏规划专家",
  34. llm=self.llm,
  35. system_prompt="你是一位经验丰富的专栏策划专家,擅长将大话题拆解为结构清晰的专栏大纲。"
  36. )
  37. def plan_column(self, main_topic: str) -> ColumnPlan:
  38. """
  39. 规划专栏大纲
  40. Args:
  41. main_topic: 专栏主题
  42. Returns:
  43. ColumnPlan 实例
  44. """
  45. print(f"\n📋 规划 Agent 开始规划专栏...")
  46. print(f" 主题: {main_topic}")
  47. prompt = PLANNER_PROMPT.format(topic=main_topic)
  48. response = self.agent.run(prompt)
  49. # 解析 JSON 响应
  50. plan_data = self._extract_json(response)
  51. plan = ColumnPlan.from_dict(plan_data)
  52. print(f"✅ 规划完成")
  53. print(f" 专栏标题: {plan.column_title}")
  54. print(f" 话题数量: {plan.get_topic_count()}")
  55. return plan
  56. def _extract_json(self, response: str) -> Dict[str, Any]:
  57. """从响应中提取 JSON"""
  58. try:
  59. # 尝试直接解析
  60. if response.strip().startswith('{'):
  61. return json.loads(response)
  62. # 查找 JSON 代码块
  63. if "```json" in response:
  64. json_start = response.find("```json") + 7
  65. json_end = response.find("```", json_start)
  66. json_str = response[json_start:json_end].strip()
  67. elif "```" in response:
  68. json_start = response.find("```") + 3
  69. json_end = response.find("```", json_start)
  70. json_str = response[json_start:json_end].strip()
  71. elif "{" in response and "}" in response:
  72. json_start = response.find("{")
  73. json_end = response.rfind("}") + 1
  74. json_str = response[json_start:json_end]
  75. else:
  76. raise ValueError("响应中未找到 JSON 数据")
  77. return json.loads(json_str)
  78. except Exception as e:
  79. print(f"⚠️ JSON 解析失败: {e}")
  80. print(f" 响应内容: {response[:500]}...")
  81. raise
  82. class WriterAgent:
  83. """写作 Agent - 负责生成和修改内容"""
  84. def __init__(self, enable_search: bool = True):
  85. """
  86. 初始化写作 Agent
  87. Args:
  88. enable_search: 是否启用搜索功能
  89. """
  90. self.llm = LLMService.get_llm()
  91. self.enable_search = enable_search
  92. # 根据是否启用搜索调整提示词
  93. if enable_search:
  94. system_prompt = """你是一位专业的内容创作者,擅长按照树形结构递归地撰写文章内容。
  95. 🔍 你可以使用搜索工具获取最新信息:
  96. - web_search: 搜索最新资讯、技术文档、代码示例等
  97. - search_recent_info: 搜索最新动态和趋势
  98. - search_code_examples: 搜索代码示例和教程
  99. - verify_facts: 验证事实的准确性
  100. 当你需要最新信息、技术细节、代码示例或验证事实时,请主动使用搜索工具。"""
  101. else:
  102. system_prompt = "你是一位专业的内容创作者,擅长按照树形结构递归地撰写文章内容。"
  103. self.agent = SimpleAgent(
  104. name="内容创作专家",
  105. llm=self.llm,
  106. system_prompt=system_prompt
  107. )
  108. # 添加搜索工具(如果启用)
  109. if enable_search:
  110. self._setup_search_tool()
  111. def _setup_search_tool(self):
  112. """设置搜索工具(使用 MCPTool)"""
  113. settings = get_settings()
  114. # 检查是否配置了搜索 API
  115. has_search_api = bool(settings.tavily_api_key or settings.serpapi_api_key)
  116. if not has_search_api:
  117. print("⚠️ 未配置搜索 API Key,搜索功能将不可用")
  118. print(" 请在 .env 文件中配置 TAVILY_API_KEY 或 SERPAPI_API_KEY")
  119. return
  120. try:
  121. # 准备环境变量
  122. env = {}
  123. if settings.tavily_api_key:
  124. env["TAVILY_API_KEY"] = settings.tavily_api_key
  125. if settings.serpapi_api_key:
  126. env["SERPAPI_API_KEY"] = settings.serpapi_api_key
  127. # 创建搜索 MCP 工具
  128. search_tool = MCPTool(
  129. name="search",
  130. description="联网搜索工具,提供最新信息、代码示例、事实验证等功能",
  131. server_command=["python", "search_mcp_server.py"],
  132. env=env,
  133. auto_expand=True # 自动展开所有子工具
  134. )
  135. self.agent.add_tool(search_tool)
  136. print("✅ 搜索工具已添加到 WriterAgent")
  137. print(f" 可用工具数量: {len(self.agent.list_tools())}")
  138. except Exception as e:
  139. print(f"⚠️ 添加搜索工具失败: {e}")
  140. print(" WriterAgent 将在没有搜索功能的情况下运行")
  141. def generate_content(
  142. self,
  143. node: ContentNode,
  144. context: Dict[str, Any],
  145. level: int,
  146. additional_requirements: str = ""
  147. ) -> Dict[str, Any]:
  148. """
  149. 生成内容
  150. Args:
  151. node: 当前节点
  152. context: 写作上下文
  153. level: 当前层级
  154. additional_requirements: 额外要求
  155. Returns:
  156. 生成的内容数据
  157. """
  158. structure_requirements = get_structure_requirements(level)
  159. word_count = get_word_count(level)
  160. prompt = WRITER_PROMPT.format(
  161. level=level,
  162. topic_title=node.title,
  163. description=node.description,
  164. word_count=word_count,
  165. context=json.dumps(context, ensure_ascii=False, indent=2),
  166. structure_requirements=structure_requirements,
  167. additional_requirements=additional_requirements
  168. )
  169. response = self.agent.run(prompt)
  170. content_data = self._extract_json(response)
  171. return content_data
  172. def revise_content(
  173. self,
  174. original_content: str,
  175. review_result: ReviewResult,
  176. level: int
  177. ) -> Dict[str, Any]:
  178. """
  179. 根据评审意见修改内容
  180. Args:
  181. original_content: 原始内容
  182. review_result: 评审结果
  183. level: 层级
  184. Returns:
  185. 修改后的内容数据
  186. """
  187. # 格式化评审信息
  188. strengths = "\n".join([f"- {s}" for s in review_result.detailed_feedback.get('strengths', [])])
  189. issues = []
  190. for issue in review_result.detailed_feedback.get('issues', []):
  191. issues.append(
  192. f"[{issue.get('severity', '未知')}] {issue.get('location', '未知位置')}\n"
  193. f"问题:{issue.get('problem', '')}\n"
  194. f"建议:{issue.get('suggestion', '')}\n"
  195. f"影响:{issue.get('impact', '')}"
  196. )
  197. issues_text = "\n\n".join(issues)
  198. priority_changes = "\n\n".join([
  199. f"{i+1}. {change.get('section', '')} - {change.get('action', '')}\n {change.get('detail', '')}"
  200. for i, change in enumerate(review_result.revision_plan.get('priority_changes', []))
  201. ])
  202. minor_improvements = "\n".join([
  203. f"- {change.get('section', '')}: {change.get('detail', '')}"
  204. for change in review_result.revision_plan.get('minor_improvements', [])
  205. ])
  206. word_count = get_word_count(level)
  207. current_word_count = len(original_content)
  208. word_count_range = f"{int(word_count * 0.9)}-{int(word_count * 1.1)}"
  209. # 计算字数调整
  210. if current_word_count < word_count * 0.9:
  211. word_count_adjustment = f"需要增加约 {int(word_count * 0.9 - current_word_count)} 字"
  212. elif current_word_count > word_count * 1.1:
  213. word_count_adjustment = f"需要精简约 {int(current_word_count - word_count * 1.1)} 字"
  214. else:
  215. word_count_adjustment = "字数合适,保持当前水平"
  216. prompt = REVISION_PROMPT.format(
  217. original_content=original_content,
  218. score=review_result.score,
  219. grade=review_result.grade,
  220. strengths=strengths,
  221. issues=issues_text,
  222. reviewer_notes=review_result.reviewer_notes,
  223. priority_changes=priority_changes,
  224. minor_improvements=minor_improvements,
  225. word_count_range=word_count_range,
  226. current_word_count=current_word_count,
  227. word_count_adjustment=word_count_adjustment
  228. )
  229. response = self.agent.run(prompt)
  230. revised_data = self._extract_json(response)
  231. return revised_data
  232. def _extract_json(self, response: str) -> Dict[str, Any]:
  233. """从响应中提取 JSON"""
  234. try:
  235. if response.strip().startswith('{'):
  236. return json.loads(response)
  237. if "```json" in response:
  238. json_start = response.find("```json") + 7
  239. json_end = response.find("```", json_start)
  240. json_str = response[json_start:json_end].strip()
  241. elif "```" in response:
  242. json_start = response.find("```") + 3
  243. json_end = response.find("```", json_start)
  244. json_str = response[json_start:json_end].strip()
  245. elif "{" in response and "}" in response:
  246. json_start = response.find("{")
  247. json_end = response.rfind("}") + 1
  248. json_str = response[json_start:json_end]
  249. else:
  250. raise ValueError("响应中未找到 JSON 数据")
  251. return json.loads(json_str)
  252. except Exception as e:
  253. print(f"⚠️ JSON 解析失败: {e}")
  254. raise
  255. class ReviewerAgent:
  256. """评审 Agent - 负责评审内容质量"""
  257. def __init__(self):
  258. self.llm = LLMService.get_llm()
  259. self.agent = SimpleAgent(
  260. name="内容评审专家",
  261. llm=self.llm,
  262. system_prompt="你是一位严格而专业的内容评审专家,擅长评审文章质量并提供详细的、可操作的修改建议。"
  263. )
  264. def review_content(
  265. self,
  266. content: str,
  267. level: int,
  268. requirements: Dict[str, Any]
  269. ) -> ReviewResult:
  270. """
  271. 评审内容
  272. Args:
  273. content: 待评审内容
  274. level: 层级
  275. requirements: 要求(包括字数、要点等)
  276. Returns:
  277. ReviewResult 实例
  278. """
  279. target_word_count = requirements.get('word_count', get_word_count(level))
  280. key_points = requirements.get('key_points', [])
  281. prompt = REVIEWER_PROMPT.format(
  282. level=level,
  283. target_word_count=target_word_count,
  284. key_points=json.dumps(key_points, ensure_ascii=False),
  285. content=content
  286. )
  287. response = self.agent.run(prompt)
  288. review_data = self._extract_json(response)
  289. review_result = ReviewResult.from_dict(review_data)
  290. return review_result
  291. def _extract_json(self, response: str) -> Dict[str, Any]:
  292. """从响应中提取 JSON"""
  293. try:
  294. if response.strip().startswith('{'):
  295. return json.loads(response)
  296. if "```json" in response:
  297. json_start = response.find("```json") + 7
  298. json_end = response.find("```", json_start)
  299. json_str = response[json_start:json_end].strip()
  300. elif "```" in response:
  301. json_start = response.find("```") + 3
  302. json_end = response.find("```", json_start)
  303. json_str = response[json_start:json_end].strip()
  304. elif "{" in response and "}" in response:
  305. json_start = response.find("{")
  306. json_end = response.rfind("}") + 1
  307. json_str = response[json_start:json_end]
  308. else:
  309. raise ValueError("响应中未找到 JSON 数据")
  310. return json.loads(json_str)
  311. except Exception as e:
  312. print(f"⚠️ JSON 解析失败: {e}")
  313. raise