title: '输出质量:结构化输出、思维链推理' description: '结构化输出(循环+重试+校验)和思维链推理(循环+自检)的实战案例。' 输出质量 结构化输出(Structured Output) 8.1 架构 通过"生成 → 校验 → 提取"三步循环,确保 LLM 输出符合预期的 JSON 格式: 结构化输出:生成 → 解析校验 → 提取输出,校验失败则重试 8.2 核心思路 LLM 的输出是自由文本,但下游系统往往需要结构化数据(JSON、表格、特定格式)。核心挑战是:LLM 可能输出格式不对的内容。解决方案:生成 → 解析校验 → 不对就重来。 8.
title: '输出质量:结构化输出、思维链推理' description: '结构化输出(循环+重试+校验)和思维链推理(循环+自检)的实战案例。'
::: info 难度:中级 | 模式:循环 + 重试 + 校验 | 关键词:JSON 解析、格式验证、可靠输出
:::
通过"生成 → 校验 → 提取"三步循环,确保 LLM 输出符合预期的 JSON 格式:
结构化输出:生成 → 解析校验 → 提取输出,校验失败则重试
LLM 的输出是自由文本,但下游系统往往需要结构化数据(JSON、表格、特定格式)。核心挑战是:LLM 可能输出格式不对的内容。解决方案:生成 → 解析校验 → 不对就重来。
实现分为三步:GenerateJSON 生成、ValidateJSON 解析校验(带 max_retries)、CheckResult 提取输出或决策重试。注意双层重试机制 —— 节点内重试解析,Flow 层重试生成:
import json import re from pocketflow import Node, Flow class GenerateJSON(Node): def prep(self, shared): return shared["task"] def exec(self, task): prompt = f"""请为以下任务生成严格的 JSON 格式结果: {task} 输出格式:{{"name": "...", "score": 0-100, "reason": "..."}} 只输出 JSON,不要其他文字。""" return call_llm(prompt) def post(self, shared, prep_res, exec_res): shared["raw_output"] = exec_res class ValidateJSON(Node): """解析并校验 JSON 格式,利用 max_retries 自动重试解析""" def prep(self, shared): return shared["raw_output"] def exec(self, raw): # 提取 JSON 部分 match = re.search(r'\{.*\}', raw, re.DOTALL) if not match: raise ValueError("输出中未找到 JSON") data = json.loads(match.group()) # 校验必需字段和类型 assert "name" in data, "缺少 name 字段" assert "score" in data, "缺少 score 字段" assert isinstance(data["score"], (int, float)), "score 必须是数字" assert 0 <= data["score"] <= 100, "score 必须在 0-100 之间" return data def exec_fallback(self, prep_res, exc): # 解析失败,返回 None 触发重新生成 print(f"解析失败:{exc}") return None def post(self, shared, prep_res, exec_res): shared["result"] = exec_res # 写入解析结果(成功为 dict,失败为 None) class CheckResult(Node): def prep(self, shared): return shared.get("result") def post(self, shared, prep_res, exec_res): if shared.get("result") is None: shared["retry_count"] = shared.get("retry_count", 0) + 1 if shared["retry_count"] >= 3: return "give_up" return "retry" # 解析失败,让 LLM 重新生成 return "done" # 构建 Flow generate = GenerateJSON() validate = ValidateJSON(max_retries=2) # 解析本身可重试 2 次 check = CheckResult() output = Node() # 占位输出节点 generate >> validate >> check check - "retry" >> generate # 格式不对,重新生成 check - "done" >> output # 格式正确,输出结果 check - "give_up" >> output # 多次失败,放弃 flow = Flow(start=generate) flow.run({"task": "评估候选人张三的 Python 编程能力"})
::: tip 学习要点
ValidateJSON(max_retries=2) 在节点内重试解析,check - "retry" >> generate 在 Flow 层重试生成None 让后续节点决策::: info 难度:进阶 | 模式:循环 + 自检 | 关键词:分步推理、自我验证、复杂问题求解
:::
思维链:循环推理 + 思考历史存储
分步推理 + 自我验证的循环架构 —— 每推一步就校验,错了则回退重推。Verify 有三条出路:步骤有误时 "error" 回退重推;步骤正确但未完成时 "continue" 继续下一步;全部完成时 "ok" 进入 Conclude 输出答案。
复杂问题(数学、逻辑、多步规划)直接让 LLM 一步回答容易出错。解决方案:分步推理,每步验证。
三个节点各司其职:StepReason 推理一步、Verify 校验正确性、Conclude 整合答案。shared["steps"] 列表记录完整推理链,验证失败时 pop() 回退:
from pocketflow import Node, Flow class StepReason(Node): def prep(self, shared): return { "question": shared["question"], "steps": shared.get("steps", []), } def exec(self, data): prompt = f"""问题:{data['question']} 已有推理步骤:{data['steps']} 请继续推理下一步,输出格式: STEP: [推理过程] ANSWER: [如果已得出最终答案]""" return call_llm(prompt) def post(self, shared, prep_res, exec_res): shared.setdefault("steps", []).append(exec_res) shared["latest_step"] = exec_res class Verify(Node): def prep(self, shared): return { "steps": shared["steps"], "latest_step": shared["latest_step"], } def exec(self, data): prompt = f"请验证以下推理是否正确:\n{data['steps']}\n如果有错误请指出。" return call_llm(prompt) def post(self, shared, prep_res, exec_res): if "错误" in exec_res: shared["steps"].pop() # 移除错误的步骤 return "error" # 回退重推 if "ANSWER" in shared.get("latest_step", ""): return "ok" # 已得出答案 return "continue" # 正确但未完成 class Conclude(Node): def prep(self, shared): return shared["steps"] def exec(self, steps): prompt = f"基于以下推理步骤,给出最终答案:\n{steps}" return call_llm(prompt) def post(self, shared, prep_res, exec_res): shared["answer"] = exec_res # 构建 Flow step_reason = StepReason() verify = Verify() conclude = Conclude() step_reason >> verify verify - "error" >> step_reason # 发现错误,重推 verify - "continue" >> step_reason # 继续推理下一步 verify - "ok" >> conclude # 验证通过,输出 flow = Flow(start=step_reason) flow.run({"question": "一个水池有 A、B 两个进水管,A 管 4 小时注满,B 管 6 小时注满,同时开两管几小时注满?"})
::: tip 学习要点
shared["steps"] 列表记录完整推理链,验证失败时 pop() 回退