Agent 工作流的四个小聪明:万能 Prompt、God Agent 与无限重试
Agent 失控往往始于几个看似省事的设计:Prompt 越堆越长、一个 Agent 包办所有工具、用正则硬拆 JSON、失败后无限重试。下面逐项说明怎么把它们收回到可验证的状态机里。
Agent 架构里最常见的四种“小聪明”反模式
把规则全塞进 Prompt,并不能得到稳定的 Agent。模型负责推理,权限、状态转换和重试上限仍应由代码控制。
flowchart TD
Submittion[接收任务请求] --> AgentState{Agent 状态机校验}
AgentState -- 反模式1: 提示词无限膨胀 --> Fail1[上下文污染/关键指令遗忘]
AgentState -- 反模式2: 单 Agent 硬干所有活 --> Fail2[工具调用死循环/逻辑死锁]
AgentState -- 反模式3: 裸用 JSON 正则解析 --> Fail3[提取报错/字段缺失导致 Crash]
AgentState -- 优化方案: 状态机降级与结构化输出 --> SafeExec[防幻觉沙箱与重试调度]
SafeExec -- 工具调用异常 --> Fallback[触发安全降级/返回兜底数据]
SafeExec -- 格式校验通过 --> Success[输出稳定结构化结果]
1. “万能指令”大杂烩(Prompt Bloating)
企图在一份 System Prompt 中涵盖角色设定、输出格式、违禁词列表、数十个 Few-Shot 示例以及复杂的分支条件。其结果是模型出现“首尾遗忘”(Lost in the Middle),最关键的约束反而被忽略。
2. 单 Agent 强行打全场(God-Agent Pattern)
让一个 Agent 同时扮演意图识别、数据库查询、文本撰写与格式校对的角色。Agent 在反复自我修正(Self-Reflection)中极其容易陷入无限递归循环,耗尽 Token 额度。
3. 硬编码正则表达式解析 JSON(Fragile Parsing)
依赖大模型输出 Markdown 代码块,然后用正则去匹配 json.loads()。一旦模型输出了非转义双引号或尾部逗号,整条业务链路立刻崩溃。
4. 缺乏止损机制的盲目重试(Infinite Retry Loop)
当 Agent 调用外部 API 失败时,无脑重新将报错信息丢回给模型。如果 API 本身是参数非法(400 Bad Request),大模型会在同一个错误里重试直到超时。
健壮的 Agent 状态机与 Prompt 管理器实现
下面使用 Python 构建一个具备状态管理、JSON 模式校验、幻觉防御以及重试降级的 Agent 状态机。
import json
import logging
import re
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Callable, Dict, List, Optional
logging.basicConfig(level=logging.INFO, format="%(asctime)s - [%(levelname)s] - %(message)s")
logger = logging.getLogger("RobustAgentEngine")
class AgentState(Enum):
IDLE = "IDLE"
THINKING = "THINKING"
EXECUTING_TOOL = "EXECUTING_TOOL"
RETRYING = "RETRYING"
COMPLETED = "COMPLETED"
FAILED = "FAILED"
@dataclass
class ToolDefinition:
name: str
description: str
func: Callable[[Dict[str, Any]], Dict[str, Any]]
class StructuralPromptTemplate:
"""结构化 Prompt 管理器:将角色、上下文与格式要求分离,避免 Prompt 膨胀"""
@staticmethod
def build_system_prompt(role_desc: str, constraints: List[str]) -> str:
formatted_constraints = "\n".join([f"- {c}" for c in constraints])
return (
f"### 核心角色\n{role_desc}\n\n"
f"### 严格执行规则\n{formatted_constraints}\n\n"
f"### 输出要求\n必须严格使用 JSON 格式返回,不要包含 markdown 标记或额外的思考前缀。"
)
class RobustAgentRunner:
def __init__(self, name: str, system_prompt: str, max_retries: int = 3):
self.name = name
self.system_prompt = system_prompt
self.max_retries = max_retries
self.tools: Dict[str, ToolDefinition] = {}
self.state = AgentState.IDLE
def register_tool(self, tool: ToolDefinition) -> None:
self.tools[tool.name] = tool
def _clean_and_parse_json(self, raw_text: str) -> Dict[str, Any]:
"""增强版 JSON 解析器:自动清洗模型常见的包裹符号与非法字符"""
cleaned = raw_text.strip()
# 去除 markdown 代码块标记
cleaned = re.sub(r"^```json\s*", "", cleaned, flags=re.MULTILINE)
cleaned = re.sub(r"^```\s*", "", cleaned, flags=re.MULTILINE)
cleaned = re.sub(r"```$", "", cleaned, flags=re.MULTILINE)
cleaned = cleaned.strip()
try:
return json.loads(cleaned)
except json.JSONDecodeError as err:
logger.warning(f"直接解析 JSON 失败: {err},尝试修复常见格式问题...")
# 尝试提取第一个 { 到最后一个 }
match = re.search(r"\{.*\}", cleaned, re.DOTALL)
if match:
return json.loads(match.group(0))
raise ValueError(f"无法将模型输出解析为有效 JSON: {raw_text}")
async def _mock_llm_call(self, prompt: str, retry_count: int) -> str:
"""模拟大模型 API 响应:演示解析失败与自我修正"""
await asyncio.sleep(0.2)
if retry_count == 0 and "fail_mock" in prompt:
# 故意吐出不规范的 Markdown 包裹 JSON
return "```json\n{\n \"action\": \"fetch_weather\",\n \"params\": {\"city\": \"Hangzhou\"}\n} \n```"
elif retry_count > 0:
# 重试时吐出标准 JSON
return '{"action": "fetch_weather", "params": {"city": "Hangzhou"}, "final_answer": "杭州今天晴朗,温度适合户外散步。"}'
return '{"action": "none", "final_answer": "任务已完成"}'
async def run_task(self, user_input: str) -> Dict[str, Any]:
self.state = AgentState.THINKING
retries = 0
last_error = ""
while retries <= self.max_retries:
try:
current_prompt = f"{self.system_prompt}\n\n【用户请求】: {user_input}"
if last_error:
current_prompt += f"\n\n【上次解析失败提醒】: {last_error},请务必返回合法的 JSON!"
logger.info(f"[{self.name}] 正在执行推理 (尝试第 {retries + 1} 次)...")
raw_response = await self._mock_llm_call(user_input, retries)
parsed_response = self._clean_and_parse_json(raw_response)
# 检查工具调用需求
action = parsed_response.get("action")
if action and action in self.tools:
self.state = AgentState.EXECUTING_TOOL
logger.info(f"执行注册工具: {action}")
tool_res = self.tools[action].func(parsed_response.get("params", {}))
parsed_response["tool_result"] = tool_res
self.state = AgentState.COMPLETED
return {
"status": "success",
"data": parsed_response,
"retries_used": retries,
}
except Exception as err:
retries += 1
last_error = str(err)
self.state = AgentState.RETRYING
logger.warning(f"执行轮次异常 ({err}),准备重试...")
if retries > self.max_retries:
break
self.state = AgentState.FAILED
# 降级输出兜底结果
return {
"status": "fallback",
"error": f"超过最大重试次数: {last_error}",
"fallback_answer": "抱歉,由于指令解析连续异常,已为您记录日志并切至人工保底流程。",
}
# 工具函数示范
def fetch_weather_mock(params: Dict[str, Any]) -> Dict[str, Any]:
city = params.get("city", "未知")
return {"temperature": "22℃", "condition": "Sunny", "city": city}
import asyncio
async def main():
system_prompt = StructuralPromptTemplate.build_system_prompt(
role_desc="你是一个生活小助手Agent,负责响应用户的生活问询。",
constraints=[
"严禁输出 Markdown 思考代码块以外的多余文字",
"如果需要查询天气,必须把 action 设为 fetch_weather",
],
)
agent = RobustAgentRunner(name="LifeAssistant", system_prompt=system_prompt, max_retries=2)
agent.register_tool(
ToolDefinition(
name="fetch_weather",
description="获取城市天气",
func=fetch_weather_mock,
)
)
print("=== 测试场景 1: 带有格式瑕疵但可自我修复的 Agent 交互 ===")
res1 = await agent.run_task("帮我查查杭州的天气 fail_mock")
print(f"最终结果: {json.dumps(res1, ensure_ascii=False, indent=2)}")
if __name__ == "__main__":
asyncio.run(main())
面向生产环境的 Agent 调试法则
要在生产环境跑稳 Agent 工作流,需时刻记住三条原则:
- 职责单一化:不要让单 Agent 处理超过 3 个决策工具。通过 Router Agent 分发给专门的 Worker Agent。
- 结构优先于提示:优先使用 OpenAI Function Calling / Structured Outputs(如 Pydantic 模式导出),而不是在 Prompt 里苦苦哀求模型“请返回 JSON”。
- 设置确定性的终止条件:在 Agent 循环中加入最大步数限制(Max Steps)和重复 Tool Call 检测。如果同一个工具带相同参数连续跑了 2 次,强制终止并抛出人工拦截信号。

1298

被折叠的 条评论
为什么被折叠?



