用 Python 构建基于反射循环的 AI 智能体
- 作者

- 姓名
- Nino
- 职业
- Senior Tech Editor
在生产环境中部署大语言模型(LLM)时,开发者面临着一个根本性的挑战:大语言模型会产生错误。这并不是偶尔发生,而是频繁发生。无论是细微的逻辑幻觉、格式错误的 JSON,还是违反业务规则的输出,概率性模型在本质上都存在不稳定性。在自动化关键业务流程时,如果仅仅依赖单次 LLM 调用,极易导致系统运行中断。
为了构建生产级别的应用,开发者必须在不确定的模型输出与确定性的软件系统之间架起一座桥梁。这就是智能体(Agent)设计模式大显身手的地方。在众多轻量且实用的模式中,反射循环(Reflection Loops) 是最行之有效的一种。反射循环是一种控制流模式,智能体运行任务,根据特定的质量标准评估输出,并决定是否结合建设性的批判意见进行重试。
在构建这些反射循环时,开发者通常会使用 n1n.ai 提供的 API 聚合服务来接入 Claude 3.5 Sonnet、OpenAI o3 以及 DeepSeek-V3 等主流模型。通过这种统一的访问方式,开发者可以轻松构建多模型反射流水线:例如使用低成本模型生成初始草稿,再使用高推理能力的模型进行深度批判。
反射循环的核心原理
反射循环的核心在于一个简单的认知不对称性:批判(Critique)在计算和语义上都比生成(Generation)更容易。
当我们要求 LLM 生成一个符合严格 Schema 的复杂 JSON 结构,并同时分析一段非结构化文本时,模型必须将注意力分配到多个任务上:解析输入、推理事实、构建 JSON 结构以及维持语法正确性。在单次执行中,模型极易遗漏边缘情况或丢失必需字段。
然而,如果我们将模型自己的输出重新输入给它,并问:“根据以下 Schema,这个 JSON 有什么问题?”,模型的注意力将完全集中在评估上。这种能力的不对称性正是反射循环能够发挥作用的根本原因。模型可以轻易在第二遍检查中发现它在第一遍生成时完全忽略的错误。
一个标准的反射循环包含三个核心组件:
- 生成器(Generator):负责执行主要任务的 LLM 提示词。
- 验证器(Validator):用于评估生成器输出的确定性或语义检查器。
- 批判器(Critiquer):负责解释验证失败原因并为下一次生成提供具体反馈的 LLM 调用(通常使用更具分析性的提示词或模型)。
用 Python 实现反射智能体
接下来,我们用 Python 实现一个通用的反射智能体类。该类将封装 API 调用,并协调生成、验证和批判阶段。
为了确保最大的灵活性并避免供应商锁定,我们将通过 n1n.ai 的统一接口调用模型。这使我们能够轻松在不同模型之间切换并进行基准测试。
首先,我们来实现核心的智能体类:
import json
from typing import Any, Callable
import httpx
MAX_ITERATIONS = 4
def call_llm(prompt: str, system: str = "") -> str:
# 在生产环境中,建议通过 n1n.ai 等统一网关路由调用
# 这样只需一个 API 密钥即可访问 Claude、DeepSeek 或 OpenAI 模型
response = httpx.post(
"https://api.example-llm.com/v1/messages",
headers={"x-api-key": "YOUR_KEY", "Content-Type": "application/json"},
json={
"model": "your-model",
"max_tokens": 1024,
"system": system,
"messages": [{"role": "user", "content": prompt}],
},
timeout=30,
)
response.raise_for_status()
return response.json()["content"][0]["text"]
class ReflectionAgent:
def __init__(
self,
task_prompt: str,
critique_prompt: str,
validator: Callable[[str], tuple[bool, str]],
):
self.task_prompt = task_prompt
self.critique_prompt = critique_prompt
self.validator = validator
def run(self, user_input: str) -> dict[str, Any]:
history: list[dict] = []
attempt = 0
while attempt < MAX_ITERATIONS:
attempt += 1
context = f"User input: {user_input}"
if history:
last = history[-1]
context += (
f"\n\nPrevious attempt:\n{last['output']}"
f"\nCritique:\n{last['critique']}"
)
# 1. 生成步骤
output = call_llm(f"{self.task_prompt}\n\n{context}")
# 2. 确定性验证步骤
ok, critique = self.validator(output)
history.append(
{"attempt": attempt, "output": output, "critique": critique, "passed": ok}
)
if ok:
return {
"success": True,
"output": output,
"iterations": attempt,
"history": history
}
# 3. 语义批判步骤(仅在验证失败时运行)
llm_critique = call_llm(
f"{self.critique_prompt}\n\nOutput to critique:\n{output}",
system="Be specific about what is wrong. Do not repeat the corrected version.",
)
history[-1]["critique"] = llm_critique
return {
"success": False,
"output": history[-1]["output"],
"iterations": attempt,
"history": history
}
智能体状态流解析
- 状态追踪:
history列表保存了每次尝试的完整轨迹,包括原始输出、验证结果和 LLM 批判内容。 - 上下文累积:在后续尝试中,智能体会将前一次的输出和批判内容附加到提示词中。这强迫模型正视自己之前的错误,并专注于纠正这些错误。
- 提前退出:一旦验证器返回
True,循环会立即退出,从而节省 Token 消耗并降低延迟。
结合确定性 Schema 验证
格式错误的 JSON 是结构化 LLM 输出任务中最常见的失效模式。虽然你可以让 LLM 去检查自己的 JSON 格式,但这样做效率极低。确定性验证器(如 JSON 解析器或 Schema 验证器)速度极快、100% 可靠且不消耗任何 Token。
我们可以使用 Python 的 jsonschema 库来强制执行严格的结构验证。通过将验证器与 LLM 逻辑分离,我们可以对其进行独立的单元测试。
import jsonschema
EXPECTED_SCHEMA = {
"type": "object",
"required": ["summary", "severity", "cve_ids"],
"properties": {
"summary": {"type": "string", "minLength": 10},
"severity": {"type": "string", "enum": ["low", "medium", "high", "critical"]},
"cve_ids": {
"type": "array",
"items": {"type": "string", "pattern": "^CVE-\\d{4}-\\d+$"},
},
},
}
def validate_security_report(text: str) -> tuple[bool, str]:
clean = text.strip()
# 如果 LLM 用 Markdown 代码块包裹了 JSON,进行剥离
if clean.startswith("```json"):
clean = "\n".join(clean.split("\n")[1:])
if clean.startswith("```"):
clean = "\n".join(clean.split("\n")[1:])
if clean.endswith("```"):
clean = "\n".join(clean.split("\n")[:-1])
clean = clean.strip()
try:
data = json.loads(clean)
except json.JSONDecodeError as exc:
return False, f"Invalid JSON: \{exc\}"
try:
jsonschema.validate(data, EXPECTED_SCHEMA)
except jsonschema.ValidationError as exc:
return False, f"Schema violation: \{exc.message\}"
return True, "OK"
以下是如何将该验证器注入到我们的 ReflectionAgent 中:
agent = ReflectionAgent(
task_prompt=(
"Analyze the following security advisory and return a JSON object "
"with keys 'summary' (string), 'severity' (low/medium/high/critical), "
"and 'cve_ids' (array of CVE strings). Return only the JSON, no prose."
),
critique_prompt=(
"Review this JSON output for accuracy, completeness, and schema compliance. "
"List each specific violation and explain why it fails."
),
validator=validate_security_report,
)
result = agent.run(
"CVE-2024-3094: backdoor found in XZ Utils 5.6.0 and 5.6.1 affecting liblzma, "
"allowing remote code execution on affected Linux distributions."
)
print(json.dumps(result, indent=2))
通过在 LLM 批判之前执行确定性的 Schema 检查,我们可以立即捕获基础的语法错误。如果 JSON 无效,返回给生成器的错误消息会非常精准(例如 Invalid JSON: Expecting ',' delimiter)。LLM 批判步骤则专门用于处理 Schema 无法表达的语义问题,例如验证提取出的 CVE ID 是否真正与输入文本对应。
生产环境防范机制:Token 预算与日志记录
在生产环境中运行反射循环时,如果不设置严格的限制,可能会导致延迟失控和 API 账单激增。如果模型陷入错误循环,它可能会在几秒钟内消耗数万个 Token。
为了防止这种情况,必须严格执行以下三条运营规则:
1. 强制迭代上限
千万不要将最大迭代次数(MAX_ITERATIONS)设得高于 4 或 5。如果 LLM 在四次尝试后仍无法输出有效内容,问题通常在于提示词设计、模型推理能力不足或验证规则本身存在逻辑漏洞。继续循环只会白白浪费 Token。
2. Token 与成本预算
实现一个预算防线,计算循环运行过程中的累积成本。如果累积成本超过了设定阈值,智能体必须立即中断并抛出异常。
COST_PER_1K_TOKENS = 0.003 # 根据具体模型定价调整
class BudgetedReflectionAgent(ReflectionAgent):
def __init__(self, *args, max_cost_usd: float = 0.10, **kwargs):
super().__init__(*args, **kwargs)
self.max_cost_usd = max_cost_usd
self._total_cost = 0.0
def _check_budget(self, prompt: str) -> None:
# 简单的 Token 估算启发式算法(1 个英文单词 ≈ 1.3 个 Token)
# 在生产环境中,应从 API 响应体中读取实际消耗的 Token 数
estimated_tokens = len(prompt.split()) * 1.3
projected_cost = (estimated_tokens / 1000) * COST_PER_1K_TOKENS
if self._total_cost + projected_cost > self.max_cost_usd:
raise RuntimeError(
f"Budget exceeded: $\{self._total_cost:.4f\} spent, "
f"limit $\{self.max_cost_usd\}"
)
self._total_cost += projected_cost
3. 结构化日志记录
将每一次迭代详细记录到中央数据库(如 SQLite 或 PostgreSQL)中。这使你能够分析失败率,找出哪些验证规则最常被违反,进而优化你的提示词。
一个简单的 SQLite 日志表设计如下:
CREATE TABLE reflection_logs (
task_id TEXT,
attempt INTEGER,
model TEXT,
input_tokens INTEGER,
output_tokens INTEGER,
passed BOOLEAN,
critique_text TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);
验证策略对比
选择合适的验证策略需要在延迟、成本和准确率之间进行权衡。下表总结了不同方法的优缺点:
| 验证策略 | 延迟 | 成本 | 准确率 | 适用场景 |
|---|---|---|---|---|
| 确定性验证 (Regex, JSON Schema) | 极低 (< 1ms) | 零成本 | 100% (语法层面) | JSON 结构、类型检查、必需字段验证 |
| 自我批判 (同模型 LLM) | 中等 (增加 1 次 LLM 调用) | 低-中等 | 中等 | 基础语义检查、语气调整 |
| 跨模型批判 (如 Claude 3.5 Sonnet) | 较高 (多模型调用开销) | 较高 | 极高 | 逻辑验证、事实准确性检查、安全审查 |
利用 n1n.ai 提供的多模型聚合能力,你可以轻松实现混合验证:使用快速且低成本的 DeepSeek-V3 进行初始生成,通过确定性 Schema 验证器建立第一道防线,仅在遇到复杂的逻辑错误时才调用 Claude 3.5 Sonnet 进行深度批判。
常见失效模式与应对策略
尽管反射循环非常强大,但如果不加以精心设计,它会以可预测的方式失效。
失效模式 1:错误自我强化(Self-Reinforcing Errors)
在第二次迭代中,模型往往会直接接受它在第一次迭代中做出的错误假设,将自己之前的输出视为真理。
- 应对策略:在系统提示词中明确指示:“请勿将上一次输出中的事实或假设视为已验证。所有结论必须直接从原始用户输入中重新推导。”
失效模式 2:模糊的批判提示词
如果你直接问 LLM:“这个输出正确吗?”,它几乎总是会给出敷衍的回答:“是的,输出是正确的。”
- 应对策略:强迫批判提示词具有对抗性。例如:“请列出该输出违反了系统提示词中的哪些具体约束。如果没有违反,请输出 'PASSED'。否则,请以列表形式详细指出每一处失败的具体原因。”
失效模式 3:主观的成功标准
反射循环需要清晰、客观的验证标准。如果你试图将反射循环应用于“语气”、“说服力”或“品牌一致性”等主观维度的优化,循环将不断波动且无法收敛。
- 应对策略:将反射循环限制在结构化、可验证的任务中(如代码生成、数据提取、数学计算、逻辑约束)。对于主观任务,应将输出路由给人工审核。
智能体工作流的安全加固
当智能体被允许根据其自我纠错后的输出执行实际操作(如数据库写入或 API 调用)时,反射循环必须与严格的沙箱机制相结合。
- 最小权限原则:确保智能体所使用的凭证仅拥有执行当前任务所需的最低权限。
- 输出净化:绝对不要在宿主机上直接执行 LLM 生成的代码。应使用隔离的 Docker 容器或 WebAssembly 运行时。
- 事务回滚:对于数据库写入操作,应在事务块内执行。如果反射循环在达到最大尝试次数后仍未通过最终验证,必须回滚该事务,以防止产生脏数据。
总结
实现反射循环是在不进行模型微调的情况下提高 LLM 应用可靠性最有效的方法之一。通过将确定性验证器与语义 LLM 批判相结合,你可以在错误扩散到下游系统之前将其捕获并自动修复。
对于构建高性能 AI 智能体的开发者而言,管理多个不同厂商的 LLM API 往往非常繁琐。使用 n1n.ai 这样的统一聚合平台可以大大简化这一过程,只需一个 API 密钥,即可无缝访问、评测和调度全球顶尖的语言模型。
Get a free API key at n1n.ai.