reflect.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. """
  2. reflect 工具 — 轻量反思
  3. 用普通模型快速评估当前搜索结果,输出简短思路和下一步建议。
  4. 继承调用者的完整对话历史作为上下文。
  5. """
  6. import logging
  7. import os
  8. from typing import Any, Dict, List, Optional
  9. from agent.llm.qwen import qwen_llm_call
  10. from agent.tools import tool
  11. from agent.tools.models import ToolContext, ToolResult
  12. logger = logging.getLogger(__name__)
  13. # 反思模型配置(普通模式,非 thinking)
  14. REFLECT_MODEL = os.getenv("REFLECT_MODEL", "qwen-plus")
  15. REFLECT_SYSTEM_PROMPT = """你是调研助手。根据对话历史和当前搜索结果,简要回答:
  16. 1. 本轮搜到了什么有价值的信息?缺了什么?
  17. 2. 下一轮搜什么?给出 1-3 个具体 query 词
  18. 3. 是否需要换搜索渠道或角度?
  19. 要求:直接输出思路,不要分析框架,不要长篇大论。3-5 句话即可。"""
  20. REFLECT_USER_TEMPLATE = """需求:{question}
  21. 本轮搜索结果:
  22. {findings}
  23. 简要反思,给出下一步 query。"""
  24. async def _fetch_caller_history(context: Optional[Dict[str, Any]]) -> List[Dict]:
  25. """从 context 中获取调用者的历史消息队列"""
  26. if not context:
  27. return []
  28. store = context.get("store")
  29. trace_id = context.get("trace_id")
  30. if not store or not trace_id:
  31. return []
  32. try:
  33. trace = await store.get_trace(trace_id)
  34. if not trace:
  35. return []
  36. messages = await store.get_main_path_messages(
  37. trace_id, trace.head_sequence
  38. )
  39. # 转为 LLM 消息格式
  40. history = []
  41. for msg in messages:
  42. llm_dict = msg.to_llm_dict()
  43. if llm_dict:
  44. history.append(llm_dict)
  45. logger.info(f"reflect: 获取到 {len(history)} 条历史消息")
  46. return history
  47. except Exception as e:
  48. logger.warning(f"reflect: 获取历史消息失败: {e}")
  49. return []
  50. @tool(
  51. description="轻量反思:快速评估本轮搜索结果,输出简短思路和下一步 query 建议",
  52. hidden_params=["context"],
  53. )
  54. async def reflect(
  55. question: str,
  56. findings: str,
  57. context: Optional[ToolContext] = None,
  58. ) -> ToolResult:
  59. """
  60. 对本轮搜索结果进行轻量反思,给出下一步搜索思路。
  61. Args:
  62. question: 原始调研问题/需求描述
  63. findings: 本轮搜索结果摘要
  64. """
  65. # 获取调用者的历史消息
  66. # context 可能是 ToolContextImpl(有 .context 属性)或直接是 dict
  67. if context is None:
  68. caller_context = None
  69. elif isinstance(context, dict):
  70. caller_context = context
  71. else:
  72. caller_context = getattr(context, 'context', None)
  73. caller_history = await _fetch_caller_history(caller_context)
  74. # 构建消息:system + 调用者历史 + 反思请求
  75. messages = [{"role": "system", "content": REFLECT_SYSTEM_PROMPT}]
  76. if caller_history:
  77. messages.extend(caller_history)
  78. messages.append({
  79. "role": "user",
  80. "content": REFLECT_USER_TEMPLATE.format(
  81. question=question,
  82. findings=findings,
  83. ),
  84. })
  85. try:
  86. result = await qwen_llm_call(
  87. messages=messages,
  88. model=REFLECT_MODEL,
  89. temperature=0.2,
  90. )
  91. content = result["content"]
  92. msg_count = len(caller_history)
  93. cost = result.get("cost", 0.0)
  94. reasoning_tokens = result.get("reasoning_tokens", 0)
  95. return ToolResult(
  96. title=f"反思完成 (model: {REFLECT_MODEL}, 继承 {msg_count} 条历史)",
  97. output=content,
  98. tool_usage={
  99. "model": REFLECT_MODEL,
  100. "prompt_tokens": result.get("prompt_tokens", 0),
  101. "completion_tokens": result.get("completion_tokens", 0),
  102. "reasoning_tokens": reasoning_tokens,
  103. "cost": cost,
  104. },
  105. )
  106. except Exception as e:
  107. return ToolResult(
  108. title="reflect 失败",
  109. output=f"调用反思模型出错: {str(e)}",
  110. )