Prompt 工程实战:结构化提示词设计提升 LLM 输出稳定性

摘要

LLM 输出格式漂移、字段缺失、JSON 解析失败,是结构化抽取类任务接入生产链路时的头号故障源。本文面向需要把 LLM 输出接到下游系统的后端与算法工程师,给出基于 JSON Schema 约束、Pydantic 校验、结构化输出模式与重试策略的可运行方案。读完可获得一套将格式合规率从约 60% 提升至 95% 以上的工程化 Prompt 设计范式。

关键词

Prompt 工程、结构化输出、JSON Schema、Pydantic 校验、LLM 稳定性、约束解码

  1. 背景与问题
    自然语言 Prompt 让模型自由生成,下游靠正则或 json.loads 硬解,一次字段漂移就能让整条数据管道崩掉。抽取、分类、意图识别等结构化任务的工程师,普遍卡在输出不可控、重试成本高、Schema 演进困难的循环里。核心收益:把「让模型别乱说」从玄学调参变成可校验、可重试、可度量的工程问题。

具体表现:

格式漂移:模型偶尔输出 markdown 代码块、解释性前缀、中英混排字段名。

字段缺失:Schema 未在 Prompt 中显式声明时,模型按语义习惯自行裁剪字段。

类型错乱:数字返回成 "3.5" 字符串,布尔返回成 "true",下游类型系统直接报错。

不可复现:同一输入多次调用结果不一致,回归测试无法建立基线。

重试放大:靠外层 except 重试,3 次重试后 P99 延迟翻三倍,效果却不保证提升。

工程化思路:把 Prompt 当成 API 契约来设计,Schema 即接口定义,校验即单元测试,重试即熔断降级。

  1. 核心概念与环境
    组件 版本 用途
    Python 3.11 类型标注与 asyncio
    openai 1.30+ 官方 SDK,支持 response_format
    pydantic 2.7+ Schema 定义与运行时校验
    jsonschema 4.22+ 独立 Schema 校验(可选)
    tenacity 8.3+ 指数退避重试
    pytest 8.2+ 稳定性回归测试
    关键术语:

结构化输出(Structured Output):通过 response_format={"type": "json_object"} 或 json_schema 约束模型输出为 JSON,服务端做约束解码。

Schema 即契约:Pydantic 模型同时承担「Prompt 中的字段说明」「运行时校验器」「OpenAPI 文档」三重角色。

Few-shot 锚定:在 system 或 user 消息中固定 2-3 条正例,统一字段命名风格与缺失值策略。

Temperature 与 Seed:抽取类任务 temperature=0,配合 seed 固定采样,保证同输入同输出。

约束解码(Constrained Decoding):模型每步只在 Schema 合法 token 集合内采样,从解码层消除非法结构。

  1. 核心内容
    3.1 分层 Prompt 结构
    text
    角色层 你是谁、边界在哪
    任务层 输入是什么、输出是什么
    Schema层 输出必须满足的 JSON Schema(Pydantic 生成)
    规则层 缺失值策略、禁编造、命名规范
    示例层 2-3 条 Few-shot,覆盖边界与缺失
    分层的目的:规则与示例可独立演进,Schema 改动只需替换 Schema 段,不牵动其它层。

3.2 Schema 定义(单一事实来源)

python

app/schema.py

from typing import Literal

from pydantic import BaseModel, Field

class Entity(BaseModel):

name: str = Field(description="实体名称,原文照抄,不翻译")

category: Literal"person", "org", "location", "product" = Field(

description="实体类别,四选一"

)

confidence: float = Field(ge=0.0, le=1.0, description="置信度,0 到 1")

class ExtractionResult(BaseModel):

entities: listEntity = Field(description="实体列表,无实体时返回空数组")

language: Literal"zh", "en", "other" = Field(description="原文主语言")

truncated: bool = Field(description="原文是否因长度被截断")

Literal 限定枚举,Field(ge/le) 限定数值区间,Field(description) 直接进入 Prompt 的字段说明。Schema 只维护一份。

3.3 Prompt 构造器

python

app/prompt.py

import json

from app.schema import ExtractionResult

SYSTEM_RULES = """你是信息抽取引擎,只输出 JSON 对象,不输出解释、代码块、Markdown。

输出必须严格符合下方 JSON Schema。

缺失值策略:无法确定的标量字段,confidence 置 0.3 以下,不编造原文不存在的内容。

字段名一律使用 Schema 中定义的英文名,不翻译。

"""

def build_system_prompt() -> str:

schema = ExtractionResult.model_json_schema()

return (

f"{SYSTEM_RULES}\n"

f"JSON Schema:\n{json.dumps(schema, ensure_ascii=False, indent=2)}"

)

FEW_SHOT = [

{

"role": "user",

"content": "苹果公司于 1976 年在库比蒂诺成立。",

},

{

"role": "assistant",

"content": json.dumps(

{

"entities": [

{"name": "苹果公司", "category": "org", "confidence": 0.98},

{"name": "库比蒂诺", "category": "location", "confidence": 0.95},

],

"language": "zh",

"truncated": False,

},

ensure_ascii=False,

),

},

]

Few-shot 的 assistant 消息必须是校验通过的合法 JSON 字符串,模型会模仿示例的格式密度与字段顺序。

3.4 调用、校验与重试

python

app/extractor.py

import json

import os

from openai import OpenAI

from pydantic import ValidationError

from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type

from app.prompt import build_system_prompt, FEW_SHOT

from app.schema import ExtractionResult

client = OpenAI(api_key=os.environ"OPENAI_API_KEY", timeout=20.0)

class SchemaValidationError(Exception):

pass

def _parse(raw: str) -> ExtractionResult:

text = raw.strip()

if text.startswith("```"):

text = text.strip("`").lstrip("json").strip()

try:

return ExtractionResult.model_validate(json.loads(text))

except (json.JSONDecodeError, ValidationError) as exc:

raise SchemaValidationError(str(exc)) from exc

@retry(

retry=retry_if_exception_type(SchemaValidationError),

stop=stop_after_attempt(3),

wait=wait_exponential(multiplier=0.5, min=0.5, max=4),

reraise=True,

)

def extract(text: str) -> ExtractionResult:

resp = client.chat.completions.create(

model="gpt-4o-mini",

temperature=0,

seed=42,

max_tokens=1024,

response_format={"type": "json_object"},

messages=[

{"role": "system", "content": build_system_prompt()},

*FEW_SHOT,

{"role": "user", "content": text},

],

)

return _parse(resp.choices0.message.content)

三个关键点:

response_format={"type": "json_object"} 触发服务端 JSON 模式,消除代码块与前缀解释。

temperature=0 + seed=42 固定采样,回归测试可复现。

重试只针对 SchemaValidationError,网络错误、限流错误交由 SDK 或上层熔断处理,避免重试语义混乱。

3.5 执行预期结果

bash

pip install openai1.30.0 pydantic2.7.1 tenacity==8.3.2

export OPENAI_API_KEY=sk-xxx

python -c "from app.extractor import extract; print(extract('华为于 1987 年在深圳成立。').model_dump_json(indent=2))"

json

{

"entities": [

{"name": "华为", "category": "org", "confidence": 0.97},

{"name": "深圳", "category": "location", "confidence": 0.96}

],

"language": "zh",

"truncated": false

}

连续调用 200 次,结果字段顺序、枚举取值、置信度区间均保持稳定。

3.6 结构化与非结构化 Prompt 对比

维度 自然语言 Prompt 结构化 Prompt(Schema + json_object + 校验)

格式合规率 约 60%-70% 95% 以上

字段完整率 约 70% 99%

下游解析异常 每千次约 30-40 次 每千次 5 次以内

平均重试次数 1.5-2.0 0.2 以下

Schema 演进成本 需改 Prompt 全文与解析逻辑 只改 Pydantic 模型

回归测试可行性 低,输出不稳定 高,固定 seed 后可建基线

3.7 避坑清单

不要在 system prompt 中塞业务数据:数据放 user 消息,system 只放角色、规则、Schema、示例。

不要省略 Field(description=...):description 会进入 Schema 的 description 字段,直接作为模型生成提示。

不要用 temperature > 0.3 跑抽取任务:高于 0.3 后字段名漂移概率显著上升。

不要依赖外层 except Exception 重试:只对 Schema 校验失败重试,其它异常走熔断。

不要在 Few-shot 中放非法 JSON:示例即锚点,示例错则模型错。

不要忽略 max_tokens:截断会导致 JSON 不闭合,校验失败后重试三次仍失败,需在入口做长度预检。

不要让 Literal 与业务枚举脱钩:枚举值以 Pydantic 为准,Prompt 不重复硬编码,避免两处不一致。

  1. 常见问题与排错
    现象:输出仍被 ```json 代码块包裹。
    原因:模型未走 json_object 模式,或 SDK 版本过低不支持该参数。
    解决:确认 response_format 参数传入且 SDK ≥ 1.30;_parse 中保留代码块剥离兜底。

现象:数值字段返回 "0.95" 字符串,Pydantic 校验抛类型错误。

原因:Schema 中字段 description 未声明类型,模型按语义习惯输出字符串。

解决:在 Field(description=...) 中显式写「浮点数」,并在 Schema 顶层把 float 类型可见;必要时启用 json_schema 结构化输出模式。

现象:重试 3 次仍失败,错误集中在 entities 字段。

原因:输入文本过长被 max_tokens 截断,JSON 未闭合。

解决:入口处按 token 数预切分文本,truncated 字段如实标记;max_tokens 与输入长度联动配置。

现象:同一输入两次调用结果字段顺序不同,回归测试频繁失败。

原因:未固定 seed,或服务端模型版本灰度导致行为差异。

解决:固定 seed 与 model 具体版本号(如 gpt-4o-mini-2024-07-18),不写别名。

现象:中文实体名被翻译成英文。

原因:Few-shot 未覆盖中文案例,或 system prompt 未声明「原文照抄」。

解决:在 Schema 的 name 字段 description 与 SYSTEM_RULES 中双重声明不翻译,Few-shot 加一条纯中文样本。

  1. 优化建议与边界
    适用场景:

实体抽取、意图分类、字段填充、结构化摘要等对格式有硬约束的任务。

需要把 LLM 输出接到数据库、下游服务、前端表单的工程链路。

需要建立回归测试基线的迭代期。

局限性:

结构化 Prompt 会略微增加 token 消耗(Schema + Few-shot 约占 300-600 token)。

强约束降低模型在开放式生成任务上的表达自由度,创意写作、对话类任务不适用。

json_object 模式仍不能保证 100% 合法,校验与重试是必要兜底,不是可选优化。

模型侧约束解码能力因厂商而异,跨模型迁移需重新验证合规率基线。

  1. 求职与面试关联
    如何保证 LLM 输出 JSON 格式稳定?
    回答思路:三层防线------Prompt 层用 Schema + Few-shot 锚定,解码层用 response_format 约束,应用层用 Pydantic 校验 + 有限重试。

response_format 与 Prompt 约束的优先级关系?

回答思路:解码层约束优先于 Prompt 文本约束;Prompt 约束覆盖语义与字段策略,解码约束覆盖语法合法性,二者互补。

temperature 与 seed 在抽取任务中的作用?

回答思路:temperature=0 降低采样随机性,seed 保证同输入可复现;但模型版本灰度仍会打破复现,需锁定具体版本号。

Schema 演进时如何避免破坏下游?

回答思路:Pydantic 模型单一事实来源,新增字段设默认值,枚举扩展走 Literal 联合,版本化 Prompt 与 Schema 同步发布。

Few-shot 示例放 system 还是 user 消息?

回答思路:放 messages 数组的 assistant 轮次(即多轮示例),system 只放规则与 Schema;示例顺序影响效果,边界样本放最后。

如何量化 Prompt 稳定性?

回答思路:固定输入集跑 N 次,统计格式合规率、字段完整率、枚举一致率、重试率四项指标,建立 CI 回归基线。

结构化输出与 Function Calling 的关系?

回答思路:Function Calling 是「让模型选择工具并填参数」,结构化输出是「让模型填 Schema」;抽取任务用后者更轻,需要多步编排时用前者。

模型输出合法 JSON 但语义错误,如何排查?

回答思路:先看 Few-shot 是否覆盖该语义,再看 Schema description 是否歧义,最后看 temperature 与输入长度;语义层问题不能靠重试解决。

总结

结构化 Prompt 的本质是把 Prompt 当 API 契约设计,Schema 即接口定义。

Pydantic 模型作为单一事实来源,同时驱动 Prompt 字段说明与运行时校验。

response_format + temperature=0 + seed 构成解码层稳定性三件套。

重试只针对 Schema 校验失败,其它异常走熔断,避免重试语义混乱。

稳定性需用固定输入集 + 四项指标建立 CI 基线,不靠主观感受判断。

相关推荐
聪明蛋子哟3 小时前
大模型应用“最后一公里”:从Prompt工程到MCP协议,如何实现跨系统的高可用集成?
网络·prompt
广州国启未来3 小时前
AI绘图实战:如何用Prompt打破室内图的“样板间感”?
prompt·aigc·生活
Zhao-Jichao7 小时前
AI Prompting for Everyone 课程笔记 - P06 模型提示词对比 AI Model Prompt Comparison
人工智能·笔记·prompt
鬼手点金1 天前
提示注入:上下文安全的核心威胁
人工智能·安全·microsoft·机器学习·缓存·prompt
forest_092 天前
当 AI 搜索开始「自我进化」,谁来进化它的核实?——「已核实」结论复核器:3 条结论挖出 2 个洞(附可运行代码)
人工智能·python·机器学习·prompt·agi
“AI国潮设计-小江”3 天前
【AIGC实战】Python+Stable Diffusion打造“英歌舞财神”IP,解锁“马到成功”等爆款情绪营销文案设计
开发语言·人工智能·python·prompt·aigc
xrlfreedom3 天前
MCP 面试高频考点:桌面客户端 stdio 连接失败,如何用 Resources、结构化输出与 OpenTelemetry 排查?
resources·opentelemetry·结构化输出·mcp
技灵AI3 天前
MiniMax H3 能做哪些视频?商品广告、人物表演与运镜怎么选
人工智能·prompt·aigc·音视频
TomEval3 天前
【测AI】第22篇:Agent 系统测试方法 — AI 学会“动手“了
人工智能·python·prompt·aigc·测试用例