4
0

prompt_manager.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. """
  2. prompt_manager.py - Planner Prompt 构造器
  3. 该模块负责构造适合 LLM Planner 的 Prompt,使 LLM 能够生成标准化的 Plan JSON。
  4. 职责边界:
  5. - 构造高质量 Prompt
  6. - 管理 system instruction
  7. - 不调用 LLM
  8. - 不执行 capability
  9. 设计原则:
  10. - Prompt 必须清晰、无歧义
  11. - 必须明确告诉 LLM 角色和约束
  12. - 必须提供完整的输出格式规范
  13. - 必须考虑安全边界
  14. 使用方式:
  15. from prompt_manager import PlannerPromptManager
  16. manager = PlannerPromptManager()
  17. prompt = manager.build_plan_prompt(
  18. user_intent="降温",
  19. world_snapshot={"temperature": 32},
  20. available_tools=["adjust_fan", "speak"],
  21. domain_rules={"high_risk_actions": ["turn_off"]},
  22. )
  23. """
  24. from __future__ import annotations
  25. import json
  26. from typing import Any
  27. # =============================================================================
  28. # Prompt 模板常量
  29. # =============================================================================
  30. # Planner System Instruction
  31. PLANNER_SYSTEM_INSTRUCTION = """你是一个专业的 AI Agent Planner。
  32. 你的职责是根据用户意图和当前世界状态,生成标准化的行动计划(Plan)。
  33. ## 核心约束
  34. 1. 只生成 Plan,不执行动作
  35. - 你只输出 Plan JSON,不调用任何能力
  36. - 实际执行由 Executor 负责
  37. 2. 只能使用给定的能力
  38. - 必须从 available_tools 列表中选择
  39. - 禁止虚构或使用未提供的能力
  40. 3. 必须输出严格 JSON
  41. - 输出必须是一个可被 Plan.from_dict() 和 PlanStep.from_dict() 解析的标准 JSON 对象
  42. - 禁止输出 markdown
  43. - 禁止输出解释文字
  44. - 禁止输出额外前后缀
  45. - 禁止缺失必需字段
  46. - 禁止使用未定义字段替代标准字段
  47. 4. 安全优先原则
  48. - 高风险动作必须设置 requires_confirmation=true
  49. - 不确定时优先 ask_user 或 speak
  50. - 禁止直接执行危险动作
  51. 5. 世界状态冲突处理
  52. - 当世界状态显示可能存在冲突时
  53. - 优先询问用户确认(ask_user)
  54. - 不要自动执行可能重复或有风险的动作
  55. ## 工具类型说明
  56. - execute: 执行具体动作
  57. - ask_user: 询问用户确认
  58. - speak: 语音/文本输出
  59. - query_world: 查询世界状态
  60. - query_knowledge: 查询知识库
  61. - noop: 空操作
  62. ## 风险等级
  63. - low: 低风险,可自动执行
  64. - medium: 中风险,建议确认
  65. - high: 高风险,必须确认
  66. ## 输出格式要求
  67. 输出 JSON 必须包含以下字段:
  68. Plan 顶层字段:
  69. - plan_id: string (自动生成,唯一标识)
  70. - goal: string (计划目标描述)
  71. - reasoning: string (推理过程说明)
  72. - risk_level: "low" | "medium" | "high"
  73. - requires_confirmation: true | false
  74. - confirmation_message: string | null
  75. - steps: array (步骤数组)
  76. - status: "created" | "pending" | "executing" | "completed" | "failed"
  77. - source: string (计划来源,如 "llm")
  78. - metadata: object (元数据)
  79. 每个 step 必须包含以下字段:
  80. - step_id: number (步骤序号,从 1 开始)
  81. - action: string (能力名称)
  82. - tool_call_type: "execute" | "ask_user" | "speak" | "query_world" | "query_knowledge" | "noop"
  83. - parameters: object (执行参数)
  84. - preconditions: object (前置条件)
  85. - fallback: null | object (失败回退)
  86. - status: "pending" | "executing" | "completed" | "failed" | "skipped"
  87. - description: string (步骤描述)
  88. - requires_confirmation: true | false
  89. - confirmation_message: string | null
  90. - metadata: object (步骤元数据)
  91. 重要:直接输出 JSON 对象,不要包裹在任何代码块或前后缀中。"""
  92. """
  93. # 简单场景 Prompt 模板
  94. SIMPLE_SCENARIO_TEMPLATE = """## 用户意图
  95. {user_intent}
  96. ## 当前世界状态
  97. {world_snapshot}
  98. ## 可用能力
  99. {available_tools}
  100. {domain_rules_section}
  101. {recent_context_section}
  102. 请根据以上信息,生成一个标准 Plan JSON。"""
  103. # 无可用能力时的 Prompt
  104. NO_TOOLS_TEMPLATE = """## 用户意图
  105. {user_intent}
  106. ## 当前世界状态
  107. {world_snapshot}
  108. ## 警告:没有可用能力
  109. 你的系统目前没有任何可用能力。
  110. 请生成一个 SPEAK plan,告知用户当前无法满足其请求。
  111. 请生成一个标准 Plan JSON。"""
  112. # 意图不明确时的 Prompt
  113. UNCLEAR_INTENT_TEMPLATE = """## 用户意图
  114. {user_intent}
  115. ## 当前世界状态
  116. {world_snapshot}
  117. ## 可用能力
  118. {available_tools}
  119. ## 警告:用户意图不明确
  120. 用户请求不够明确,系统无法确定具体行动。
  121. 请生成一个 ASK_USER plan,询问用户澄清其意图。
  122. 请生成一个标准 Plan JSON。"""
  123. # =============================================================================
  124. # Planner Prompt 管理器
  125. # =============================================================================
  126. class PlannerPromptManager:
  127. """Planner Prompt 管理器
  128. 负责构造适合 LLM Planner 的 Prompt。
  129. 属性:
  130. system_instruction: 系统指令
  131. include_reasoning: 是否在输出中包含推理过程
  132. 示例:
  133. >>> manager = PlannerPromptManager()
  134. >>> prompt = manager.build_plan_prompt(
  135. ... user_intent="降温",
  136. ... world_snapshot={"temperature": 32},
  137. ... available_tools=["adjust_fan"],
  138. ... )
  139. """
  140. def __init__(
  141. self,
  142. system_instruction: str | None = None,
  143. include_reasoning: bool = True,
  144. ):
  145. """初始化 Prompt 管理器
  146. Args:
  147. system_instruction: 自定义系统指令,None 则使用默认指令
  148. include_reasoning: 是否包含推理过程
  149. """
  150. self.system_instruction = system_instruction or PLANNER_SYSTEM_INSTRUCTION
  151. self.include_reasoning = include_reasoning
  152. def build_plan_prompt(
  153. self,
  154. user_intent: str,
  155. world_snapshot: dict[str, Any],
  156. available_tools: list[str],
  157. domain_rules: dict[str, Any] | None = None,
  158. recent_context: dict[str, Any] | None = None,
  159. ) -> str:
  160. """构建 Plan Prompt
  161. 主要入口方法。根据输入构建完整的 prompt。
  162. 设计原则:
  163. - 专注于 prompt 构造,不做复杂的业务判断
  164. - 复杂场景判断由 planner.py 负责
  165. - 只保留必要的轻量分支:
  166. 1. 无可用工具
  167. 2. 意图明显不清晰
  168. 3. 默认普通 prompt
  169. Args:
  170. user_intent: 用户意图
  171. world_snapshot: 世界状态快照
  172. available_tools: 可用能力列表
  173. domain_rules: 领域规则
  174. recent_context: 最近上下文(传递给 LLM 参考)
  175. Returns:
  176. 完整的 prompt 字符串
  177. """
  178. domain_rules = domain_rules or {}
  179. recent_context = recent_context or {}
  180. # 轻量判断分支
  181. if not available_tools:
  182. scenario_prompt = self._build_no_tools_prompt(user_intent, world_snapshot)
  183. elif self._is_unclear_intent_simple(user_intent):
  184. scenario_prompt = self._build_unclear_intent_prompt(
  185. user_intent, world_snapshot, available_tools
  186. )
  187. else:
  188. scenario_prompt = self._build_default_prompt(
  189. user_intent, world_snapshot, available_tools, domain_rules, recent_context
  190. )
  191. # 组合完整 prompt
  192. return f"{self.system_instruction}\n\n{scenario_prompt}"
  193. def _is_unclear_intent_simple(self, user_intent: str) -> bool:
  194. """简单判断意图是否不清晰
  195. 轻量方法,只做最基础的检查。
  196. 复杂判断由 planner.py 负责。
  197. Args:
  198. user_intent: 用户意图
  199. Returns:
  200. 意图是否不清晰
  201. """
  202. # 意图过短
  203. if len(user_intent.strip()) < 2:
  204. return True
  205. return False
  206. def build_system_instruction(self) -> str:
  207. """获取系统指令
  208. Returns:
  209. 系统指令字符串
  210. """
  211. return self.system_instruction
  212. def _build_default_prompt(
  213. self,
  214. user_intent: str,
  215. world_snapshot: dict[str, Any],
  216. available_tools: list[str],
  217. domain_rules: dict[str, Any],
  218. recent_context: dict[str, Any],
  219. ) -> str:
  220. """构建默认 Prompt
  221. 统一处理大多数场景的 prompt 构造。
  222. Args:
  223. user_intent: 用户意图
  224. world_snapshot: 世界状态
  225. available_tools: 可用能力
  226. domain_rules: 领域规则
  227. recent_context: 最近上下文
  228. Returns:
  229. 场景 prompt 字符串
  230. """
  231. # 格式化世界状态
  232. if isinstance(world_snapshot, dict):
  233. world_str = json.dumps(world_snapshot, ensure_ascii=False, indent=2)
  234. else:
  235. world_str = str(world_snapshot)
  236. # 格式化可用工具
  237. tools_str = ", ".join(f'"{t}"' for t in available_tools)
  238. # 格式化领域规则
  239. if domain_rules:
  240. rules_str = f"\n\n## 领域规则\n{json.dumps(domain_rules, ensure_ascii=False, indent=2)}"
  241. else:
  242. rules_str = ""
  243. # 格式化最近上下文
  244. if recent_context:
  245. context_str = f"\n\n## 历史上下文\n{json.dumps(recent_context, ensure_ascii=False, indent=2)}"
  246. else:
  247. context_str = ""
  248. return SIMPLE_SCENARIO_TEMPLATE.format(
  249. user_intent=user_intent,
  250. world_snapshot=world_str,
  251. available_tools=tools_str,
  252. domain_rules_section=rules_str,
  253. recent_context_section=context_str,
  254. )
  255. def _build_no_tools_prompt(
  256. self,
  257. user_intent: str,
  258. world_snapshot: dict[str, Any],
  259. ) -> str:
  260. """构建无可用工具时的 Prompt"""
  261. if isinstance(world_snapshot, dict):
  262. world_str = json.dumps(world_snapshot, ensure_ascii=False, indent=2)
  263. else:
  264. world_str = str(world_snapshot)
  265. return NO_TOOLS_TEMPLATE.format(
  266. user_intent=user_intent,
  267. world_snapshot=world_str,
  268. )
  269. def _build_unclear_intent_prompt(
  270. self,
  271. user_intent: str,
  272. world_snapshot: dict[str, Any],
  273. available_tools: list[str],
  274. ) -> str:
  275. """构建意图不明确时的 Prompt"""
  276. if isinstance(world_snapshot, dict):
  277. world_str = json.dumps(world_snapshot, ensure_ascii=False, indent=2)
  278. else:
  279. world_str = str(world_snapshot)
  280. tools_str = ", ".join(f'"{t}"' for t in available_tools)
  281. return UNCLEAR_INTENT_TEMPLATE.format(
  282. user_intent=user_intent,
  283. world_snapshot=world_str,
  284. available_tools=tools_str,
  285. )
  286. # =============================================================================
  287. # 主程序入口(测试示例)
  288. # =============================================================================
  289. if __name__ == "__main__":
  290. print("=" * 70)
  291. print("Planner Prompt Manager 测试")
  292. print("=" * 70)
  293. manager = PlannerPromptManager()
  294. # 测试 1:简单场景
  295. print("\n[测试 1] 简单场景 - 降温")
  296. print("-" * 40)
  297. prompt = manager.build_plan_prompt(
  298. user_intent="打开风扇降温",
  299. world_snapshot={"temperature": 32, "humidity": 70},
  300. available_tools=["adjust_fan", "speak", "query"],
  301. domain_rules={"high_risk_actions": ["turn_off"]},
  302. )
  303. print(f"Prompt 长度: {len(prompt)} 字符")
  304. print(f"包含 system instruction: {'AI Agent Planner' in prompt}")
  305. print(f"包含 JSON 格式: {'plan_id' in prompt}")
  306. # 测试 2:意图不明确
  307. print("\n[测试 2] 意图不明确")
  308. print("-" * 40)
  309. prompt = manager.build_plan_prompt(
  310. user_intent="那个事情",
  311. world_snapshot={"status": "normal"},
  312. available_tools=["query", "speak"],
  313. )
  314. print(f"Prompt 长度: {len(prompt)} 字符")
  315. print(f"包含 ASK_USER 提示: {'询问用户' in prompt or 'ask_user' in prompt.lower()}")
  316. # 测试 3:无可用工具
  317. print("\n[测试 3] 无可用工具")
  318. print("-" * 40)
  319. prompt = manager.build_plan_prompt(
  320. user_intent="打开空调",
  321. world_snapshot={"temperature": 30},
  322. available_tools=[],
  323. )
  324. print(f"Prompt 长度: {len(prompt)} 字符")
  325. print(f"包含无可用能力提示: {'没有可用能力' in prompt}")
  326. # 测试 4:正常意图(含历史上下文)
  327. print("\n[测试 4] 正常意图")
  328. print("-" * 40)
  329. prompt = manager.build_plan_prompt(
  330. user_intent="先降温然后喂食",
  331. world_snapshot={"temperature": 32, "hunger_level": 80},
  332. available_tools=["adjust_fan", "feed", "speak"],
  333. domain_rules={"high_risk_actions": ["turn_off"], "medium_risk_actions": ["adjust"]},
  334. recent_context={"recent_actions": ["adjust_fan"]},
  335. )
  336. print(f"Prompt 长度: {len(prompt)} 字符")
  337. print(f"包含历史上下文: {'历史上下文' in prompt}")
  338. # 测试 5:打印完整 prompt 示例
  339. print("\n[测试 5] 完整 Prompt 示例")
  340. print("-" * 40)
  341. prompt = manager.build_plan_prompt(
  342. user_intent="打开风扇降温",
  343. world_snapshot={"temperature": 32, "humidity": 70},
  344. available_tools=["adjust_fan", "speak"],
  345. domain_rules={"high_risk_actions": ["turn_off"]},
  346. )
  347. # 只打印前 500 字符
  348. print(prompt[:500])
  349. print("...")
  350. # 测试 6:验证 system instruction 中没有 markdown 代码块模板
  351. print("\n[测试 6] 验证 JSON 输出约束")
  352. print("-" * 40)
  353. system_instr = manager.build_system_instruction()
  354. print(f"包含 markdown 代码块: {'```' in system_instr}")
  355. print(f"包含禁止输出 markdown: {'禁止输出 markdown' in system_instr}")
  356. print(f"包含禁止解释文字: {'禁止输出解释文字' in system_instr}")
  357. print("\n" + "=" * 70)
  358. print("Planner Prompt Manager 测试完成")
  359. print("=" * 70)