plan_agent.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. """Step 3: Plan-and-Solve 股票多维度分析
  2. 来自 hello-agents 教程第4章 Plan-and-Solve 范式:
  3. Planner: 将复杂分析问题分解为有序步骤
  4. Executor: 逐步执行,积累上下文,最终综合生成报告
  5. """
  6. import ast
  7. from llm_client import HelloAgentsLLM
  8. from tools import (
  9. get_realtime_quote, get_historical_data, get_financial_data,
  10. calc_indicators, get_news
  11. )
  12. PLANNER_PROMPT = """你是一个顶级的股票分析规划专家。用户会提出一个股票分析请求,你的任务是将它分解成一个由多个独立步骤组成的分析计划。
  13. 每个步骤应该聚焦一个分析维度,按从数据收集到综合分析的逻辑顺序排列。
  14. 可用数据维度: 实时行情、历史K线、技术指标(MA/MACD/RSI/布林带)、财务数据、新闻舆情。
  15. 问题: {question}
  16. 请严格按照以下格式输出计划,```python与```作为前后缀是必要的:
  17. ```python
  18. ["步骤1: 具体行动描述", "步骤2: 具体行动描述", ...]
  19. ```
  20. 示例:
  21. ```python
  22. ["获取600519的实时行情和60天历史K线", "计算技术指标评估趋势和动能", "获取财务数据评估估值", "获取新闻舆情评估市场情绪", "综合所有数据输出完整分析报告"]
  23. ```
  24. """
  25. EXECUTOR_PROMPT = """你是一位专业的股票分析师。你正在按预定计划逐步分析一只股票。
  26. ## 完整计划:
  27. {plan}
  28. ## 已完成步骤的结果:
  29. {history}
  30. ## 当前步骤:
  31. {current_step}
  32. ## 可用工具
  33. - GetRealtimeQuote: 获取实时行情。输入: 股票代码
  34. - GetHistoricalData: 获取历史K线。输入格式: "代码|daily|天数"
  35. - CalcIndicators: 计算技术指标。输入格式: "代码|daily|天数"
  36. - GetFinancialData: 获取财务数据。输入: 股票代码
  37. - GetNews: 获取新闻舆情。输入: 股票代码
  38. 请执行当前步骤。如果需要获取数据,请在回复中明确指定要调用的工具和参数,格式为:
  39. [[TOOL:工具名:参数]]
  40. 示例:
  41. [[TOOL:GetRealtimeQuote:600519]]
  42. [[TOOL:GetHistoricalData:600519|daily|60]]
  43. 如果当前步骤是综合分析(不需要获取新数据),请直接基于已有结果给出分析。
  44. 如果这是最后一步,请输出完整的综合分析报告,包含: 基本概况、技术面、基本面、消息面、风险提示、投资建议。
  45. 现在请执行当前步骤。"""
  46. class Planner:
  47. def __init__(self, llm_client: HelloAgentsLLM):
  48. self.llm_client = llm_client
  49. def plan(self, question: str) -> list:
  50. prompt = PLANNER_PROMPT.format(question=question)
  51. messages = [{"role": "user", "content": prompt}]
  52. print("\n [规划中...]")
  53. response = self.llm_client.think(messages=messages) or ""
  54. try:
  55. plan_str = response.split("```python")[1].split("```")[0].strip()
  56. plan = ast.literal_eval(plan_str)
  57. if isinstance(plan, list) and len(plan) > 0:
  58. return plan
  59. except (ValueError, SyntaxError, IndexError) as e:
  60. print(f" [规划解析失败: {e}]")
  61. # 回退:默认分析计划
  62. return [
  63. "获取实时行情和60天历史K线数据",
  64. "计算技术指标(MACD/RSI/布林带/均线)",
  65. "获取财务数据评估基本面和估值",
  66. "获取新闻舆情了解市场情绪",
  67. "综合所有数据生成完整分析报告"
  68. ]
  69. class Executor:
  70. def __init__(self, llm_client: HelloAgentsLLM):
  71. self.llm_client = llm_client
  72. # 工具映射
  73. self.tools = {
  74. "GetRealtimeQuote": get_realtime_quote,
  75. "GetHistoricalData": get_historical_data,
  76. "CalcIndicators": calc_indicators,
  77. "GetFinancialData": get_financial_data,
  78. "GetNews": get_news,
  79. }
  80. def execute(self, question: str, plan: list) -> str:
  81. import re
  82. history = ""
  83. final_result = ""
  84. print(f"\n [计划共 {len(plan)} 步]")
  85. for i, step in enumerate(plan, 1):
  86. print(f"\n{'='*50}")
  87. print(f" 步骤 {i}/{len(plan)}: {step}")
  88. print(f"{'='*50}")
  89. prompt = EXECUTOR_PROMPT.format(
  90. plan="\n".join([f"{j}. {s}" for j, s in enumerate(plan, 1)]),
  91. history=history if history else "(尚无已完成步骤)",
  92. current_step=step,
  93. )
  94. messages = [{"role": "user", "content": prompt}]
  95. response = self.llm_client.think(messages=messages) or ""
  96. print(f" [LLM 响应]\n{response[:500]}{'...' if len(response)>500 else ''}")
  97. # 解析工具调用 [[TOOL:Name:args]]
  98. tool_pattern = re.findall(r"\[\[TOOL:(\w+):(.*?)\]\]", response)
  99. tool_results = []
  100. for tool_name, tool_args in tool_pattern:
  101. func = self.tools.get(tool_name)
  102. if func:
  103. result = func(tool_args.strip())
  104. tool_results.append(f"[{tool_name}结果]\n{result}")
  105. print(f" [工具] {tool_name} 执行完成")
  106. # 如果有工具调用,让 LLM 基于工具结果再回答一次
  107. if tool_results:
  108. followup = f"工具执行结果:\n\n" + "\n\n".join(tool_results)
  109. followup += f"\n\n请基于以上数据完成当前步骤: {step}"
  110. messages.append({"role": "assistant", "content": response})
  111. messages.append({"role": "user", "content": followup})
  112. final_response = self.llm_client.think(messages=messages) or ""
  113. step_result = final_response
  114. else:
  115. step_result = response
  116. print(f" [步骤 {i} 结果]\n{step_result[:300]}{'...' if len(step_result)>300 else ''}")
  117. history += f"\n--- 步骤{i}: {step} ---\n{step_result}\n"
  118. final_result = step_result
  119. return final_result
  120. class PlanAndSolveStockAgent:
  121. """Plan-and-Solve 股票分析 Agent"""
  122. def __init__(self, llm_client: HelloAgentsLLM):
  123. self.llm_client = llm_client
  124. self.planner = Planner(llm_client)
  125. self.executor = Executor(llm_client)
  126. def run(self, question: str):
  127. print(f"\n{'='*60}")
  128. print(f" Plan-and-Solve 模式")
  129. print(f" 问题: {question}")
  130. print(f"{'='*60}")
  131. # 1. 规划
  132. plan = self.planner.plan(question)
  133. print(f"\n 分析计划:")
  134. for i, step in enumerate(plan, 1):
  135. print(f" {i}. {step}")
  136. # 2. 执行
  137. final_answer = self.executor.execute(question, plan)
  138. print(f"\n{'='*60}")
  139. print(f" 分析完成")
  140. print(f"{'='*60}")
  141. return final_answer