1. 引言
在 AI 应用开发中,提示词(Prompt)早已不只是“问一句、答一句”的简单输入。随着大语言模型能力的提升,如何通过精心设计的提示词来编排复杂的任务流程,已成为构建高效 AI 工作流的关键。本文将带你从最基础的单轮对话出发,逐步深入到多轮对话工作流的构建,揭示提示词工程在其中的核心作用。
文章摘要
从一句提示词到一套智能工作流,提示词工程如何驾驭LLM完成复杂任务?本文以状态管理为线索,系统梳理从单轮对话到多轮AI 工作流的演进路径。通过结构化提示词模板、对话记忆机制与意图路由等实战技巧,手把手教你构建可维护的多轮问答系统。适合对话系统、智能客服与自动化编排场景的开发者阅读。
2. 单轮对话:一切的基础
单轮对话是 AI 交互的最小单元:用户输入一条指令,模型返回一次回答。看似简单,但一个高质量的单轮交互,背后往往需要结构化的提示词设计。
2.1 结构化提示词模板
一个典型的单轮提示词通常包含以下要素:
- 角色设定:定义 AI 的身份(如“你是一位资深 Python 开发者”)。
- 任务描述:明确要完成的具体任务(如“请为以下函数编写单元测试”)。
- 输入数据:提供待处理的内容。
- 输出格式:指定返回结果的格式(如 JSON、Markdown 表格)。
- 约束条件:限制回答的长度、风格或禁止事项。
# 角色 你是一位资深 Python 开发者。 # 任务 为以下函数编写单元测试,覆盖正常输入和边界情况。 # 输入函数 def divide(a, b): return a / b # 输出格式 请用 pytest 风格,返回完整的测试代码。 # 约束 - 不要使用第三方 mock 库。 - 每个测试用例用 `def test_xxx():` 命名。2.2 单轮对话的局限性
单轮对话虽然简单直接,但在复杂任务中暴露出明显不足:
- 上下文有限:无法利用历史对话中的信息。
- 无法纠错:一次回答出错,需要用户重新组织整个问题。
- 缺乏状态:无法记住用户偏好或中间结果。
这些局限性催生了多轮对话工作流的需求。
下面从多个维度对比单轮对话与多轮对话工作流的差异:
| 对比维度 | 单轮对话工作流 | 多轮对话工作流 |
|---|---|---|
| 上下文能力 | 每次请求独立,无法利用历史信息 | 维护对话状态,可引用历史上下文 |
| 状态管理 | 无状态,每次从零开始 | 有状态,通过状态对象记录进度与已收集信息 |
| 错误处理 | 出错需用户重新组织完整问题 | 可在后续轮次中追问、纠正或重试 |
| 适用场景 | 简单问答、一次性翻译、单步工具调用 | 信息收集、多步流程、客服对话、复杂任务编排 |
| 实现复杂度 | 低,只需单次提示词模板 | 较高,需设计状态机、记忆模块与分支逻辑 |
| 用户体验 | 简洁但缺乏连贯性 | 连贯自然,能逐步引导用户完成复杂流程 |
3. 多轮对话:状态与记忆的引入
多轮对话的核心在于状态管理和上下文记忆。AI 需要记住之前说过的话,并根据当前状态决定下一步行动。
3.1 对话状态的设计
在多轮工作流中,我们通常用一个结构化的“状态对象”来记录对话进展:
classConversationState:def__init__(self):self.history=[]# 对话历史self.current_step=0# 当前步骤self.collected_info={}# 已收集的信息self.pending_questions=[]# 待追问的问题每次模型回答后,后端解析回答并更新状态,再根据状态决定下一轮提示词的内容。
3.2 多轮提示词模板示例
以下是一个“信息收集型”多轮工作流的提示词模板:
# 系统角色 你是一位友好的客服助手,负责收集用户的订单问题。 # 当前对话状态 - 已收集信息:{collected_info} - 当前步骤:{current_step} - 待确认项:{pending_items} # 对话历史(最近3轮) {recent_history} # 指令 请根据当前状态,向用户提出下一个问题,或确认已收集的信息。 如果所有信息已收集完毕,请总结并告知用户。通过动态填充{collected_info}、{current_step}等变量,模型就能“记住”当前进度,并做出符合流程的回应。
4. 实战:构建一个多轮问答工作流
下面我们用一个具体的例子——智能客服工单系统,来演示如何用提示词构建多轮工作流。
4.1 工作流定义
4.2 核心代码实现
importjsonfromopenaiimportOpenAI client=OpenAI()defbuild_prompt(state):"""根据当前状态构建提示词"""# 构建系统提示词,将当前对话状态序列化为结构化指令system_prompt=f""" 你是一个智能客服助手。请根据当前对话状态,向用户提出下一个问题。 当前状态: - 问题类型:{state.get('issue_type','未确定')}- 已收集信息:{json.dumps(state.get('collected', {}), ensure_ascii=False)} - 当前步骤:{state['step']}对话历史:{format_history(state['history'])}请用中文回复,每次只问一个问题。 """returnsystem_promptdefrun_workflow():# 初始化对话状态:定义步骤流转、问题类型、已收集信息和历史记录state={"step":"ask_type",# 当前对话阶段,控制流程走向"issue_type":None,# 用户问题类型,用于分支路由"collected":{},# 已收集的关键信息,逐步累积"history":[]# 完整对话历史,供提示词构建上下文}# 循环控制:只要未到达"done"状态,就持续进行多轮对话whilestate["step"]!="done":# 根据当前状态动态构建提示词,将状态信息注入 LLM 上下文prompt=build_prompt(state)# 调用大模型生成回复response=client.chat.completions.create(model="gpt-4",messages=[{"role":"system","content":prompt}])reply=response.choices[0].message.contentprint(f"AI:{reply}")# 获取用户输入并追加到历史记录,维持对话记忆user_input=input("用户: ")state["history"].append({"role":"user","content":user_input})# 解析用户输入并更新状态(简化版)# 根据当前步骤匹配用户意图,驱动状态机向下一个阶段流转ifstate["step"]=="ask_type":# 根据用户回复中的关键词判断问题类型,决定下一步分支if"技术"inuser_input:state["issue_type"]="技术"state["step"]="ask_version"# 切换到版本询问阶段elif"账单"inuser_input:state["issue_type"]="账单"state["step"]="ask_order"# 切换到订单询问阶段elifstate["step"]=="ask_version":# 收集版本信息后,推进到日志收集阶段state["collected"]["version"]=user_input state["step"]="ask_log"elifstate["step"]=="ask_log":# 收集日志信息后,标记对话完成,循环条件将不满足而退出state["collected"]["log"]=user_input state["step"]="done"# ... 其他步骤类似if__name__=="__main__":run_workflow()4.3 运行效果示例
AI: 您好!请问您遇到的是技术问题还是账单问题? 用户: 技术问题 AI: 好的,请问您使用的系统版本是多少? 用户: Windows 11 专业版 23H2 AI: 收到。请提供相关的错误日志或截图。 用户: 错误代码 0x80070005 AI: 感谢您提供的信息。我已为您生成工单,工单编号 TK-2024-001,预计24小时内会有工程师联系您。5. 进阶技巧:提示词编排与分支逻辑
在实际生产环境中,多轮工作流往往需要更复杂的分支逻辑和条件跳转。
5.1 基于意图的路由
在每一轮对话中,先让模型输出一个结构化的“意图标签”,后端根据标签决定下一步:
请分析用户输入,输出以下 JSON 格式: { "intent": "ask_question | provide_info | request_escalation | end_conversation", "extracted_info": { ... }, "next_step": "step_name" }5.2 错误恢复与重试
当模型输出不符合预期时(如 JSON 解析失败),工作流应具备重试机制:
max_retries=3forattemptinrange(max_retries):try:response=call_llm(prompt)parsed=json.loads(response)breakexceptjson.JSONDecodeError:prompt+="\n【注意】请严格输出 JSON 格式,不要包含其他文字。"else:# 降级处理:使用默认分支parsed={"intent":"ask_question","next_step":"default"}下面是一个更完整的实战示例,包含状态回滚、重试提示词优化和用户引导逻辑:
importjsonfromtypingimportOptional,Dict,AnyclassRetryableWorkflow:"""带错误恢复与重试的多轮对话工作流"""def__init__(self,max_retries:int=3):self.max_retries=max_retries self.state={"history":[],"last_valid_state":None}def_build_retry_prompt(self,original_prompt:str,error_detail:str,attempt:int)->str:"""根据重试次数动态构建提示词"""base_instruction=("你是一个严格输出 JSON 的助手。请只输出一个 JSON 对象,""不要包含任何 Markdown 标记、代码块围栏或额外说明文字。")ifattempt==1:# 第一次重试:温和提醒returnf"{original_prompt}\n\n【提示】{base_instruction}"elifattempt==2:# 第二次重试:给出明确格式要求return(f"{original_prompt}\n\n"f"【重要】上次输出格式错误({error_detail})。\n"f"请严格按照以下格式输出,不要添加任何其他内容:\n"f'{{"intent": "意图名称", "params": {{}}, "next_step": "下一步"}}')else:# 最后一次重试:使用 few-shot 示例return(f"{original_prompt}\n\n"f"【最后一次尝试】请参考以下示例输出:\n"f'正确示例:{{"intent": "ask_question", "params": {{"question": "你好"}}, "next_step": "respond"}}\n'f'正确示例:{{"intent": "search_knowledge", "params": {{"keyword": "Python"}}, "next_step": "search"}}\n'f"请只输出一个 JSON 对象:")def_rollback_state(self)->None:"""回滚到上一个有效状态"""ifself.state["last_valid_state"]isnotNone:self.state["history"]=self.state["last_valid_state"]["history"]print("[状态回滚] 已恢复到上一个有效对话状态")else:print("[状态回滚] 无有效历史状态,保持当前状态")def_handle_fallback(self,user_input:str)->Dict[str,Any]:"""优雅降级:当所有重试都失败时,使用规则解析"""print("[降级处理] 模型输出持续异常,切换到规则解析模式")# 简单的规则匹配作为兜底ifany(kwinuser_inputforkwin["你好","嗨","hello"]):return{"intent":"greeting","params":{},"next_step":"respond"}elifuser_input.endswith("?"):return{"intent":"ask_question","params":{"question":user_input},"next_step":"respond"}else:return{"intent":"unknown","params":{"raw_input":user_input},"next_step":"clarify"}def_guide_user_retry(self,attempt:int)->str:"""生成引导用户重新输入的提示"""guides=["请重新描述您的问题,我会更仔细地处理。","您可以换一种方式表达,或者直接说“帮助”查看可用功能。","看起来遇到了解析问题,请用简单的句子重新描述您的需求。"]returnguides[min(attempt-1,len(guides)-1)]defprocess_message(self,user_input:str)->Dict[str,Any]:"""处理用户消息,带重试与降级"""prompt=self._build_base_prompt(user_input)forattemptinrange(1,self.max_retries+1):try:response=call_llm(prompt)parsed=json.loads(response)# 校验必要字段if"intent"notinparsedor"next_step"notinparsed:raiseValueError("缺少必要字段")# 成功:保存当前状态作为有效状态快照self.state["last_valid_state"]={"history":self.state["history"].copy()}self.state["history"].append({"role":"assistant","parsed":parsed})returnparsedexcept(json.JSONDecodeError,ValueError)ase:error_detail=str(e)print(f"[重试{attempt}/{self.max_retries}] 解析失败:{error_detail}")ifattempt<self.max_retries:# 更新提示词,并引导用户重新输入prompt=self._build_retry_prompt(prompt,error_detail,attempt)user_guide=self._guide_user_retry(attempt)print(f"[引导用户]{user_guide}")else:# 所有重试耗尽:回滚状态 + 降级处理self._rollback_state()fallback_result=self._handle_fallback(user_input)self.state["history"].append({"role":"assistant","parsed":fallback_result,"fallback":True})returnfallback_resultdef_build_base_prompt(self,user_input:str)->str:"""构建基础提示词(示例)"""return(f"对话历史:{json.dumps(self.state['history'],ensure_ascii=False)}\n"f"用户输入:{user_input}\n""请分析用户意图并输出 JSON。")# 使用示例defcall_llm(prompt:str)->str:"""模拟 LLM 调用(可能返回非法 JSON)"""importrandom# 模拟:前两次返回非法 JSON,第三次成功ifrandom.random()<0.6:return"好的,我来回答你的问题。"# 非 JSON 输出return'{"intent": "ask_question", "params": {"question": "示例"}, "next_step": "respond"}'# 运行演示workflow=RetryableWorkflow(max_retries=3)result=workflow.process_message("Python 怎么学?")print(f"最终解析结果:{result}")这个示例的核心改进点:
- 动态重试提示词:根据重试次数逐步增强约束(温和提醒 → 格式要求 → few-shot 示例)
- 状态回滚:
_rollback_state()在重试耗尽后恢复到上一个有效对话状态,避免污染后续对话 - 用户引导:
_guide_user_retry()在每次重试时给出不同的引导文案,提升用户体验 - 优雅降级:
_handle_fallback()使用规则匹配作为兜底方案,确保系统不崩溃 - 字段校验:不仅检查 JSON 合法性,还校验必要字段是否存在
6. 总结与展望
从单轮到多轮对话,提示词工程的核心演进路径是:
- 从静态到动态:提示词不再是固定文本,而是根据状态实时组装。
- 从无状态到有状态:引入对话状态对象,让 AI 拥有“短期记忆”。
- 从线性到分支:通过意图识别和条件跳转,实现复杂业务流程。
未来,随着 Agent 框架和工具调用能力的成熟,提示词工作流将进一步与外部 API、数据库、代码执行引擎深度融合,构建出真正自主决策的 AI 系统。
希望本文能帮助你理解提示词在构建 AI 工作流中的关键作用,并动手实践自己的多轮对话应用。