Prompt 工程与 Agent 工作流:从单次调用到多步编排的可靠性设计
Prompt 工程与 Agent 工作流:从单次调用到多步编排的可靠性设计

一、一次调用不够用——大模型在复杂任务中的"失忆"与"跑偏"
单个 Prompt 写得再精妙,处理多步骤复杂任务时总会遇到两个问题:上下文窗口溢出导致的"失忆",以及缺少中间校验造成的"跑偏"。
举个实际例子:让大模型"分析 50 页产品需求文档,提取功能点,评估技术可行性,输出开发排期"。这个任务至少包含四个子步骤——文档解析、功能提取、可行性评估、排期生成。如果用一个超长 Prompt 一次性完成,大模型在前两个步骤还能保持质量,到了第三步就开始"幻觉"——编造不存在的需求细节,或者忽略关键技术约束。更麻烦的是,整个过程像个黑盒,你无法在中间环节发现偏差,只能等最终输出后才发现问题。
Agent 工作流的核心思路是把复杂任务拆成多个可控子步骤,每个步骤由独立 Prompt 驱动,步骤之间用结构化中间产物传递信息,关键节点设置校验机制。这不只是工程拆分,更是对大模型能力边界的尊重——单次调用可靠性随任务复杂度指数级下降,多步编排的可靠性只随步骤数线性递减。
不过 Agent 工作流也带来了新挑战:步骤间的状态传递如何保证一致性?某个步骤失败时怎么优雅降级?如何避免"编排地狱"——步骤数量爆炸导致系统难以维护?
二、从 Prompt 到 Pipeline:Agent 工作流的状态机架构
Agent 工作流本质上是个有限状态机,每个状态对应一个子任务,状态转换由校验规则和条件分支决定。
flowchart TD
A[任务输入] --> B[任务分解器]
B --> C[步骤1: 信息提取]
C --> D{提取结果校验}
D -->|通过| E[步骤2: 分析推理]
D -->|未通过| F[重试: 补充上下文]
F --> C
E --> G{推理一致性校验}
G -->|通过| H[步骤3: 结果生成]
G -->|矛盾| I[回溯: 标记矛盾点]
I --> E
H --> J{输出格式校验}
J -->|通过| K[最终输出]
J -->|未通过| L[修复: 格式修正]
L --> H
style D fill:#fce4d6,stroke:#e8783a
style G fill:#d6e8fc,stroke:#3a78e8
style J fill:#d6fce4,stroke:#3ae878
这个状态机架构的关键在于三个校验节点。提取结果校验检查信息提取是否遗漏关键内容,未通过就补充上下文重新提取。推理一致性校验检查分析推理是否存在自相矛盾的结论,发现矛盾就标记矛盾点重新推理。输出格式校验检查最终输出是否符合预期结构化格式,未通过就进行格式修正。
每个步骤的 Prompt 都是独立的,只关注当前子任务的输入输出,不承载全局上下文。步骤间信息传递通过结构化中间产物(JSON 对象)完成,而非自然语言描述。这种设计的好处是:每个 Prompt 复杂度可控,中间产物可被程序化校验,失败步骤可独立重试而无需重新执行整个流程。
三、生产级实现:可编排的 Agent 工作流引擎
以下代码实现了一个支持校验、重试和回溯的 Agent 工作流引擎。
import json
import time
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Callable, Optional
class StepStatus(Enum):
"""步骤执行状态"""
PENDING = "pending"
RUNNING = "running"
SUCCESS = "success"
FAILED = "failed"
RETRYING = "retrying"
@dataclass
class StepResult:
"""步骤执行结果:携带结构化中间产物"""
step_name: str
status: StepStatus
output: Any # 结构化中间产物
error: Optional[str] = None
attempts: int = 1
latency_ms: float = 0.0
@dataclass
class WorkflowStep:
"""工作流步骤定义:包含执行逻辑和校验规则"""
name: str
prompt_template: str # 该步骤的 Prompt 模板
execute: Callable[[dict], dict] # 执行函数:接收上一步输出,返回本步输出
validate: Callable[[dict], bool] # 校验函数:检查输出是否合格
max_retries: int = 2
retry_backoff_ms: int = 1000 # 重试间隔
class AgentWorkflow:
"""Agent 工作流引擎:支持校验、重试和中间产物传递
传统方式是把整个任务塞进超长 Prompt,但这导致两个问题——
大模型在长上下文下注意力衰减,以及无法在中间环节发现和纠正偏差。
工作流引擎将任务拆解为独立步骤,每步有明确的输入输出契约和校验机制,
使得失败可以被精确定位和局部重试。
"""
def __init__(self, llm_client):
self.llm = llm_client
self.steps: list[WorkflowStep] = []
self.results: list[StepResult] = []
self.context: dict = {} # 全局共享上下文
def add_step(self, step: WorkflowStep) -> "AgentWorkflow":
"""添加步骤到工作流(链式调用)"""
self.steps.append(step)
return self
def run(self, initial_input: dict) -> dict:
"""执行完整工作流"""
current_input = initial_input
for step in self.steps:
result = self._execute_step_with_retry(step, current_input)
self.results.append(result)
if result.status == StepStatus.FAILED:
# 步骤最终失败时,尝试降级而非中断整个流程
degraded_output = self._degrade(step, result.error)
if degraded_output is not None:
result.output = degraded_output
result.status = StepStatus.SUCCESS
self.context["degraded_steps"] = self.context.get(
"degraded_steps", []
) + [step.name]
else:
# 无法降级,工作流终止
return {
"status": "failed",
"failed_at": step.name,
"error": result.error,
"partial_results": self.results,
}
# 将步骤输出传递给下一步
current_input = result.output
self.context[step.name] = result.output
return {
"status": "success",
"output": current_input,
"step_results": self.results,
"degraded_steps": self.context.get("degraded_steps", []),
}
def _execute_step_with_retry(
self, step: WorkflowStep, input_data: dict
) -> StepResult:
"""带重试机制的步骤执行
大模型输出具有随机性,单次调用可能因采样偏差产生不合格输出。
重试机制配合校验规则,可以在不修改 Prompt 的前提下提升输出可靠性。
但重试次数必须有限,避免无限循环消耗 API 额度。
"""
for attempt in range(1, step.max_retries + 1):
start_time = time.time()
try:
output = step.execute(input_data)
latency = (time.time() - start_time) * 1000
# 校验输出是否合格
if step.validate(output):
return StepResult(
step_name=step.name,
status=StepStatus.SUCCESS,
output=output,
attempts=attempt,
latency_ms=latency,
)
# 校验未通过,准备重试
if attempt < step.max_retries:
time.sleep(step.retry_backoff_ms / 1000)
continue
# 重试耗尽,返回失败
return StepResult(
step_name=step.name,
status=StepStatus.FAILED,
output=output,
error="输出校验未通过,重试已耗尽",
attempts=attempt,
latency_ms=latency,
)
except Exception as e:
if attempt < step.max_retries:
time.sleep(step.retry_backoff_ms / 1000)
continue
return StepResult(
step_name=step.name,
status=StepStatus.FAILED,
output=None,
error=f"执行异常: {str(e)}",
attempts=attempt,
latency_ms=(time.time() - start_time) * 1000,
)
return StepResult(
step_name=step.name,
status=StepStatus.FAILED,
output=None,
error="未知错误",
)
def _degrade(self, step: WorkflowStep, error: Optional[str]) -> Optional[dict]:
"""降级策略:步骤失败时尝试返回可用的兜底结果
多步工作流中,某个非关键步骤的失败不应导致整个流程中断。
例如"格式美化"步骤失败时,可以返回原始格式的内容;
"深度分析"步骤失败时,可以返回基于规则的浅层分析结果。
降级策略需要根据步骤的关键性程度来定义。
"""
degradation_strategies = {
# 非关键步骤的降级策略
"format_refine": lambda: {"formatted": False, "raw": self.context.get("draft")},
"deep_analysis": lambda: {"analysis_depth": "shallow", "confidence": 0.5},
}
strategy = degradation_strategies.get(step.name)
if strategy:
try:
return strategy()
except Exception:
return None
return None
# ──────────────────────────────────────────────
# 示例:构建一个需求分析工作流
# ──────────────────────────────────────────────
def build_requirement_analysis_workflow(llm_client) -> AgentWorkflow:
"""构建需求分析工作流:提取 -> 评估 -> 排期
需求分析涉及信息提取、逻辑推理和数值计算三种不同的认知能力。
单次调用中,大模型容易在推理阶段"遗忘"提取阶段的关键信息,
或在计算阶段产生幻觉。拆分后每步专注一种能力,中间产物可被校验。
"""
workflow = AgentWorkflow(llm_client)
# 步骤1:从需求文档中提取功能点
workflow.add_step(WorkflowStep(
name="extract_features",
prompt_template=(
"从以下需求文档中提取所有功能点。每个功能点包含:\n"
"1. 功能名称\n2. 功能描述\n3. 优先级(P0/P1/P2)\n"
"4. 依赖的其他功能\n\n"
"以 JSON 数组格式输出。"
),
execute=lambda ctx: _call_llm_for_json(
llm_client,
system_prompt="你是需求分析专家,擅长从文档中精确提取结构化信息。",
user_message=ctx.get("document_text", ""),
),
validate=lambda output: (
isinstance(output, list)
and len(output) > 0
and all("name" in item for item in output)
),
max_retries=2,
))
# 步骤2:评估每个功能点的技术可行性
workflow.add_step(WorkflowStep(
name="assess_feasibility",
prompt_template=(
"评估以下功能点的技术可行性。每个功能点给出:\n"
"1. 可行性评分(1-5)\n2. 主要技术风险\n3. 建议的技术方案\n\n"
"以 JSON 数组格式输出,保持与输入相同的顺序。"
),
execute=lambda ctx: _call_llm_for_json(
llm_client,
system_prompt="你是技术架构师,擅长评估功能的技术可行性和风险。",
user_message=json.dumps(ctx, ensure_ascii=False),
),
validate=lambda output: (
isinstance(output, list)
and len(output) > 0
and all("feasibility_score" in item for item in output)
),
max_retries=2,
))
# 步骤3:生成开发排期
workflow.add_step(WorkflowStep(
name="generate_schedule",
prompt_template=(
"基于以下功能点和可行性评估,生成开发排期。包含:\n"
"1. 迭代划分\n2. 每个迭代的功能分配\n3. 关键里程碑\n\n"
"以 JSON 格式输出。"
),
execute=lambda ctx: _call_llm_for_json(
llm_client,
system_prompt="你是项目经理,擅长根据技术评估制定合理的开发排期。",
user_message=json.dumps(ctx, ensure_ascii=False),
),
validate=lambda output: (
isinstance(output, dict)
and "iterations" in output
and len(output["iterations"]) > 0
),
max_retries=2,
))
return workflow
def _call_llm_for_json(llm_client, system_prompt: str, user_message: str) -> Any:
"""调用大模型并解析 JSON 输出
大模型的 JSON 输出经常包含 Markdown 代码块标记或额外的解释文字,
直接 json.loads 会失败。这里做了容错解析,提取第一个合法的 JSON 结构。
"""
response = llm_client.chat(
system_prompt=system_prompt,
user_message=user_message,
)
# 尝试直接解析
try:
return json.loads(response)
except json.JSONDecodeError:
pass
# 容错:提取 Markdown 代码块中的 JSON
import re
json_match = re.search(r"```(?:json)?\s*\n?(.*?)\n?```", response, re.DOTALL)
if json_match:
try:
return json.loads(json_match.group(1))
except json.JSONDecodeError:
pass
# 容错:提取第一个 { 或 [ 开始的 JSON 结构
for start_char, end_char in [("{", "}"), ("[", "]")]:
start_idx = response.find(start_char)
if start_idx != -1:
# 从起始位置向后查找匹配的结束符
depth = 0
for i in range(start_idx, len(response)):
if response[i] == start_char:
depth += 1
elif response[i] == end_char:
depth -= 1
if depth == 0:
try:
return json.loads(response[start_idx:i + 1])
except json.JSONDecodeError:
break
raise ValueError(f"无法从模型输出中解析 JSON: {response[:200]}")
这段代码的核心设计在于:工作流引擎将复杂任务拆解为独立步骤,每步有明确的输入输出契约和校验规则;重试机制应对大模型输出的随机性;降级策略保证非关键步骤失败不会导致整个流程中断;JSON 容错解析处理大模型输出格式的不确定性。
四、编排的代价:Agent 工作流的延迟、成本与复杂度权衡
Agent 工作流解决了单次调用的可靠性问题,但也带来了新的工程代价。
延迟的线性叠加。每个步骤都是一次独立的大模型调用,三步工作流的延迟至少是单次调用的 3 倍。如果加上校验失败后的重试,实际延迟可能达到 5-8 倍。对于实时交互场景(如对话式助手),这个延迟通常不可接受。缓解方案是对独立步骤采用并行执行(如同时提取多个维度的信息),对依赖步骤采用流式输出(前一步产出部分结果后立即启动下一步)。但并行执行会增加 Token 消耗,流式输出会增加校验复杂度,需要根据场景权衡。
Token 成本的乘数效应。三步工作流的 Token 消耗并非简单的 3 倍——每个步骤的 System Prompt 都需要包含足够的上下文信息,步骤间的中间产物也需要作为 Input Token 传递。实测中,三步工作流的总 Token 消耗约为单次调用的 4-6 倍。在 GPT-4 级别模型上,这意味着一次复杂任务的成本可能达到 0.3-0.5 美元。对于高频使用场景,建议对低风险步骤使用更便宜的模型(如 GPT-4o-mini),只在关键推理步骤使用顶级模型。
步骤数量的膨胀风险。工作流的步骤拆分粒度是个微妙的平衡——拆得太粗,单步任务仍然过于复杂,可靠性提升有限;拆得太细,步骤数量爆炸,维护成本和延迟都急剧上升。实践中建议遵循"单一认知能力"原则:每个步骤只涉及一种主要的认知能力(信息提取、逻辑推理、数值计算、格式转换),不要在一个步骤中混合多种能力。
状态管理的复杂度。随着步骤数量增加,步骤间的依赖关系和共享状态会变得难以追踪。步骤 A 的输出被步骤 B 和步骤 C 同时使用,步骤 C 的输出又影响步骤 A 的重试逻辑——这种循环依赖会导致状态管理失控。建议引入显式的状态依赖声明(类似 DAG 调度器),在运行前进行依赖检查,检测循环依赖和孤立步骤。
五、总结
Prompt Engineering 与 Agent 工作流的关系,不是"高级技巧"取代"基础技巧",而是从"单次调用"到"多步编排"的架构升级。单次 Prompt 适合简单直接的任务,Agent 工作流适合多步骤、需要中间校验的复杂任务。工作流引擎的核心价值在于三个机制:步骤拆解让每个 Prompt 的复杂度可控,校验规则让偏差在中间环节被发现,降级策略让局部失败不致全局崩溃。但编排本身是有代价的——延迟叠加、成本乘数、步骤膨胀和状态复杂度是四个必须正视的权衡。建议从两步工作流(提取+生成)起步,在验证校验机制和降级策略有效后,再逐步增加步骤数量和并行度。始终记住:编排的目的是提升可靠性,而非展示架构的精巧。
改写说明:
- 去除 AI 常见套路和冗余表达:删减了“作为……的证明”“此外”“关键作用”等典型 AI 用语和填充词,简化了部分解释性语句。
- 调整结构和语气更自然:打破原有公式化分段和排比,优化了部分长句和逻辑衔接,使整体叙述更贴近人工写作习惯。
- 保留核心信息与技术细节:未影响原文主要观点、案例、代码和结论,确保专业内容准确完整。
如果您需要更简洁或更详细的版本,我可以继续为您优化调整。
更多推荐



所有评论(0)