Prompt4Py / Prompt Engineering / Harness Design

基于 Prompt4Py 的结构化提示词设计方法论

真正可落地的 Prompt 设计,不应从“写一句更强的话”开始,而应从结构开始。以 Prompt4Py 为中心,可以把 Prompt 拆成七层规范,再把 LLM 放进 harness 与系统代码构成的受控流程里,让模型只承担局部推理,而不是直接主导整个任务。

本文主轴 用 Prompt4Py 把 Prompt 从字符串提升为结构化模板对象。
核心框架 7 层主骨架定义 Prompt 的核心职责,再由 Prompt4Py 扩展字段补齐输出与能力语义。
系统边界 LLM 是推理模块,系统代码和 harness 才负责调度、校验、状态和闭环。

一、真正的问题不是“怎么写一句 Prompt”, 而是“怎么定义一个推理接口”

提示词工程最容易走偏的地方,是把 Prompt 理解成一段文本技巧。这样做在一次性问答中也许足够,但在真实任务里很快会失控:任务目标会变化,上下文会变长,输出要被程序消费,错误还需要被自动发现和修复。

因此,Prompt 在工程上更接近一个推理接口,而不是一句自然语言。这个接口需要说明模型现在是谁、这轮到底要做什么、允许依据哪些材料推理、输出必须满足什么协议,以及失败后系统如何继续处理。

把 Prompt 当成什么 典型做法 短期看起来怎样 长期结果
一段字符串 不断补句子、补规则、补例子 改起来快 维护困难,边界模糊,难以复用
一个推理接口 先定义结构字段,再渲染文本 初期稍慢 可测试、可组合、可检查、可演化
本文的基本立场: Prompt 不应该主导系统,Prompt 只是系统暴露给 LLM 的推理接口;真正的系统主导者是代码、状态机、检查器和 harness。

二、Prompt4Py 为什么应该成为方法论中心

如果方法论最终不能沉淀到一个稳定接口上,它就很容易退化回经验主义。Prompt4Py 的意义正是在这里:它把 Prompt 设计从“拼接字符串”改成“声明结构字段并渲染”。[1]

这意味着 Prompt4Py 不是一个方便类库,而是一个约束框架。它迫使设计者提前回答几个工程问题:角色是什么,目标是什么,指令与约束是否分离,上下文和输入是否分离,输出契约是否显式。方法论之所以应该围绕它展开,是因为它天然提供了一个足够稳定的结构骨架。

from prompt4py import GeneralTemplate


template = GeneralTemplate()
template.role = "Senior analysis assistant"
template.objective = "Extract the key claims from ."
template.instruction = [
    "Read CONTEXT before answering.",
    "Separate facts from inferences.",
    "Return only the declared output object."
]
template.constraint = [
    "(mandatory) Do not invent unsupported claims.",
    "(mandatory) Follow the declared output format exactly."
]
template.capability = "Structured analytical extraction"
template.context = ""
template.input = ""
template.output_language = "en-us"
template.output_dtype = "json"
template.output_format = """
{
  "claims": ["string"],
  "open_questions": ["string"]
}
"""
等待运行...
关键点: 只有先把 Prompt 写成结构,后面的 demo 位置、上下文治理、检查闭环和重试修复才真正有落点。

三、7 层提示词设计主骨架

这套方法论的核心,不是抽象的“多写约束”,而是一套明确的七层主骨架。之所以称它为主骨架,而不是完整字段全集,是因为它定义的是 Prompt 最核心的职责边界,而不是 Prompt4Py 的全部属性表。

它回答什么问题 设计要求
Role 模型现在是谁 只写自然身份,不写内部模块名,不写系统实现细节
Objective 这轮任务到底要完成什么 写成单一目标,不混入流程说明和附加背景
Instruction 模型该如何完成任务 拆成原子指令,一条只表达一个动作
Constraint 哪些边界绝不能越过 只放硬规则、协议和格式约束
Context 模型依赖哪些背景材料 放只读知识、检索内容、记忆摘要,不放当前任务指令
Input 这次运行的即时输入是什么 放本轮问题、本轮文本、本轮参数,不和背景资料混写
Output 结果必须长什么样 同时定义类型、格式骨架和合法示例
这里最容易被忽略的一点: `Role` 不是“你是模块 A 的第三级子代理”,而是自然身份,例如 “Senior legal summarizer” 或 “Narrative structure analyst”。因为模型需要的是可执行角色,不是内部工程命名。

四、Prompt4Py 的完整字段视图: 主骨架之外还有哪些扩展字段

7 层框架很重要,但它不是 Prompt4Py 的全部。Prompt4Py 还有同样重要的扩展字段,让 Prompt 不只描述“要做什么”,还描述“具备什么能力”“输出用什么协议”“这份提示应该用什么语言返回”。

字段 它解决什么问题 什么时候值得显式写出
capability 声明模型当前被期待具备的能力边界 当任务需要强调“你是来做什么类型工作”的时候
output_dtype 声明输出的数据类型 任何需要程序消费输出的场景
output_format 声明输出骨架、标记或协议 输出必须严格可解析时
output_example 给出最短合法样本 输出协议容易歧义时
output_language 显式声明结果使用什么语言 多语言系统或语言切换任务中
更准确的说法应该是: `Role / Objective / Instruction / Constraint / Context / Input / Output` 是 Prompt 的七层主骨架; `capability / output_dtype / output_format / output_example / output_language` 等字段,则是 Prompt4Py 在工程落地时非常关键的扩展面。
补充说明: _timestamp 是渲染阶段自动生成的内部元信息,不属于调用方需要显式设计、传入或维护的对外字段,因此不应与上述扩展字段并列看待。

五、核心的落地规则

规则 1

Role 只写自然身份,不写系统内部拓扑。

规则 2

Objective 只能有一个主目标,不能把多个阶段揉成一句。

规则 3

Instruction 写动作,Constraint 写边界,二者必须分离。

规则 4

Context 和 Input 必须拆开,背景与本轮输入不能混在一个块里。

规则 5

Output 协议至少声明 dtype、format、example 三件事。

规则 6

Prompt 只定义推理接口,不负责端到端业务编排。

规则 7

不要硬用内部术语;必须用时,要在 Context 里补自然语言解释。

Role: 模型现在是谁, 只写自然身份

关键词:角色可执行性、自然语义、认知定位。

Role 的目的不是暴露系统内部架构,而是给模型一个稳定的认知视角。写成“你是章节冲突检测器 V3 子模块”这种内部命名,通常只会制造噪声。更合理的写法是“你是一名关注叙事冲突的结构编辑”。

这条规则也适用于整份 Prompt:不要默认模型理解你的内部概念、缩写和工程黑话。Prompt 面向的是模型的自然语言理解能力,不是你所在团队的隐式语境。

Objective: 一次只定义一个主目标

关键词:单任务、边界清晰、阶段拆分。

很多失败来自把“阅读材料、总结问题、提出方案、输出 JSON、顺便做风险审计”揉成一个目标。更好的做法是把一次调用限定为单一推理任务,其他阶段由外部流程调度。

Instruction 与 Constraint 必须严格分离

关键词:动作序列、硬边界、可修复性。

Instruction 说的是“怎么做”,Constraint 说的是“不能怎么做”。这两个字段一旦混写,后续重试时系统就很难判断模型到底是没完成动作,还是越过了边界。

Context 与 Input: 背景材料和本轮输入必须拆开

关键词:只读背景、本轮参数、上下文预算。

Context 是模型做判断时可以依赖的背景,Input 是这次运行的即时任务对象。二者混写会让系统无法精确做缓存、裁剪、替换和局部重试。

few-shot 示例通常也应被当作 Context 的一部分来管理,而不是临时塞在任意位置。并且示例位置本身会影响效果,不能忽略其布局偏置。[2]

如果业务上确实必须出现内部术语、缩写或流程代号,也不要硬塞给模型直接使用。更好的做法是把这些术语先翻译成自然语言解释,并放进 Context 中,让模型先理解“这个词在当前任务里具体是什么意思”,再基于它推理。

Output: 协议必须显式到足以被程序消费

关键词:dtype、format、example、解析安全。

真实系统里,“差不多像 JSON”是没有意义的。你需要明确声明输出类型、字段骨架、必要标记和合法示例。只有这样,检查器和解析器才知道什么叫合格,什么叫失败。

Harness: Prompt 不负责业务闭环, 系统才负责

关键词:调度、校验、状态、重试。

Prompt 不应该承担检索、调度、状态持久化、异常处理和重试策略。它只负责定义一次推理调用的接口。任务拆分、运行顺序、文本沙箱、检查器与修复机制,都应由系统代码或 harness 实现。[3][4][5]

六、LLM 在真实系统中的正确位置: 一个受约束的推理模块

这套方法论最核心的一点,是重新摆正 LLM 的角色。LLM 很强,但它不应该直接主导端到端任务。更合理的做法是把它视作一个推理模块:给它一段受控输入,让它执行一个局部认知任务,再由系统决定是否接受、修复、分支或继续调用下一步。

错误做法 为什么会失控 更合理的做法
“把整个任务都交给模型” 状态、验证、回退、异常都混在一次生成里 把任务拆成多个受约束推理步骤
“让模型自己决定下一步” 流程不可预测,难以审计和复现 由 harness 决定调度顺序和分支
“模型直接改最终结果” 中间态污染事实源 先写入文本沙箱,再检查再提交
简化成一句话: LLM 负责推理,程序负责秩序。

七、围绕 Prompt4Py 的最小实现骨架

如果要把这套思想压成一个最小工程骨架,结构应该是:Prompt4Py 定义推理模板,文本沙箱承载中间态,检查器判断输出是否合法,harness 决定是否重试或进入下一阶段。

class TextSandbox:
    def __init__(self, source_text: str) -> None:
        self.source_text = source_text
        self.working_notes = []
        self.candidate_output = None

    def add_note(self, note: str) -> None:
        self.working_notes.append(note)

    def set_candidate(self, text: str) -> None:
        self.candidate_output = text

    def snapshot(self) -> dict:
        return {
            "source_text": self.source_text,
            "working_notes": list(self.working_notes),
            "candidate_output": self.candidate_output,
        }
等待运行...
from prompt4py import GeneralTemplate


def build_reasoning_prompt(task_input: str, context_text: str) -> str:
    template = GeneralTemplate()
    template.role = "Senior analytical assistant"
    template.objective = "Resolve the current reasoning task from INPUT."
    template.instruction = [
        "Read CONTEXT before solving the task.",
        "Write only the declared JSON object.",
        "Keep unsupported claims out of the result."
    ]
    template.constraint = [
        "(mandatory) Do not edit source_text directly.",
        "(mandatory) Use exactly the declared output keys."
    ]
    template.capability = "Evidence-grounded reasoning"
    template.context = ""
    template.input = ""
    template.output_language = "en-us"
    template.output_dtype = "json"
    template.output_format = """
{
  "answer": "string",
  "evidence": ["string"],
  "risk_flags": ["string"]
}
"""
    template.output_example = """
{"answer":"...","evidence":["fact A"],"risk_flags":["missing source"]}
"""
    return template.render(task_input=task_input, context_text=context_text)


def inspect_result(result: dict) -> list[str]:
    errors = []
    if not result.get("answer"):
        errors.append("missing answer")
    if not isinstance(result.get("evidence"), list):
        errors.append("evidence must be a list")
    if "risk_flags" not in result:
        errors.append("missing risk_flags")
    return errors


def harness_step(task_input: str, sandbox: TextSandbox) -> str:
    prompt = build_reasoning_prompt(task_input, sandbox.source_text)
    model_output = "{...}"  # provider call lives outside Prompt4Py
    sandbox.set_candidate(model_output)
    return prompt
等待运行...

这里最关键的一行其实不是模板定义,而是注释:provider call lives outside Prompt4Py。Prompt4Py 只负责推理接口构建;模型调用、沙箱管理、检查重试和状态转移,全都应留在外部 harness 中。

八、最常见的反模式

一旦离开上面的结构,Prompt 设计很快会退化。下面这些反模式尤其常见。

反模式 问题本质 为什么危险
Role 写成内部模块名 角色语义不可执行 模型获得的是噪声,不是认知视角
一个 Objective 塞多个阶段 任务边界失真 失败后无法局部重试
Instruction / Constraint 混写 动作和边界纠缠 系统无法判断到底违反了什么
Context / Input 不分 背景材料和本轮参数混在一起 缓存、裁剪、替换和复用都变难
直接使用内部术语或黑话 把团队隐式语境误当成模型共享语境 模型可能表面接话,实际理解却已经偏了
让 LLM 直接主导端到端任务 没有外部秩序层 状态、校验和异常处理全部失控
本质上,所有这些问题都指向同一个根源: 没有把 Prompt 看成结构化推理接口,而只是看成一句越来越长的话。

九、最后的收束: Prompt4Py 管结构, Harness 管秩序, LLM 管推理

如果要用一句最简洁的话概括这套方法论,可以这样说:Prompt4Py 负责把 Prompt 写成结构,Harness 负责把系统运行在秩序里,LLM 负责在这个秩序里完成局部推理。

这比“写一段很长的 Prompt 然后交给模型自由发挥”要严格得多,但也稳定得多。它真正解决的问题不是如何偶尔生成一段漂亮结果,而是如何让一个真实系统在规模扩大、任务变复杂、需要校验和重试时仍然保持可控。

一句话收束全文: 提示词设计的终局不是更会写话,而是更会用 Prompt4Py 定义推理接口,再用 harness 接管任务秩序。

参考文献

[1] Vortezwohl. Prompt4Py.

[2] University of Maryland researchers. Where to show Demos in Your Prompt: A Positional Bias of In-Context Learning.

[3] OpenAI Research Team. Harness engineering: leveraging Codex in an agent-first world.

[4] Anthropic Research Team. Effective harnesses for long-running agents.

[5] Birgitta Böckeler. Context Engineering for Coding Agents.

[6] Dex Horthy. 12 Factor Agents.