前面三根缰绳管的是"模型能看到什么"(上下文管理)、"输出是否可信"(验证闭环)、"经验能否复用"(知识沉淀)。第四根缰绳回答的问题最简单,也最容易被忽略:模型的输出格式对吗?
在一个生产级 Agent 系统中,模型的输出往往不是给最终用户看的自然语言,而是给下游系统吃的结构化数据——JSON、函数调用参数、数据库查询语句、API 请求体。这些输出如果不符合预期格式,下游系统就会报错,甚至静默地做错事。
输出约束不是单一的技术,而是从 Prompt 到代码的三层防线:
在 Prompt 中明确告诉模型输出的格式要求。这是最基础也最重要的约束:
// 弱约束:"请以 JSON 格式返回"
// 强约束:
"你的回答必须是一个 JSON 对象,格式如下:
{
\"action\": \"工具名称\", // 必填,从可用工具列表中选择
\"params\": { // 必填,根据所选工具的参数 Schema
\"param1\": \"值\" // 每个参数必须符合类型要求
},
\"reasoning\": \"选择理由\" // 必填,不超过 100 字
}
请确保:
1. 输出是合法的 JSON
2. 所有必填字段都已填写
3. 参数类型与 Schema 一致
4. 不需要 Markdown 包裹(直接输出 JSON)" Prompt 约束的关键原则:越具体越好。"返回 JSON"不如"返回一个包含 id、name、status 三个字段的 JSON 对象"。"不要添加 Markdown"不如"直接输出纯 JSON,不要用 ```json 包裹"。
Prompt 约束靠模型的"理解",Schema 约束靠系统的"强制"。当 API 支持结构化输出(Structured Output / JSON Mode)时,优先使用:
// 用 JSON Schema 约束模型输出(以 OpenAI API 为例)
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [/* ... */],
response_format: {
type: "json_schema",
json_schema: {
name: "tool_call",
strict: true,
schema: {
type: "object",
properties: {
action: { type: "string", enum: ["query", "update", "delete"] },
params: { type: "object" },
reasoning: { type: "string", maxLength: 100 }
},
required: ["action", "params", "reasoning"],
additionalProperties: false
}
}
}
}); Schema 约束的优势:模型输出的 JSON 保证符合 Schema。如果模型生成的 JSON 不符合 Schema,API 会自动重试或报错,不会把非法输出传递到下游。
即使有了 Prompt 约束和 Schema 约束,代码层面仍然需要做最终的兜底:
输出约束不只是让模型输出合法的 JSON。在更多场景中,约束的含义更广泛:
模型可能生成过长的回复。在某些场景下(如 Tool 描述的返回、摘要生成),需要限制输出长度:
模型回复的语气和风格应该与应用场景匹配:
最严格的约束——模型不能说的内容:
输出约束最常受到的批评是"限制了模型的创造力"。这个批评本身没错——约束确实限制了模型"能说的范围"。但问题不在于"是否限制",而在于"在什么地方限制":
在不同场景下,约束的强度不同:
┌────────────────────────────────────────────┐
│ 松约束区域(创造力优先) │
│ ├─ 面向用户的对话回复 │
│ ├─ 创意内容生成 │
│ └─ 探索性分析 │
│ 策略:风格约束为主,格式约束为辅 │
├────────────────────────────────────────────┤
│ 紧约束区域(精确性优先) │
│ ├─ 工具调用参数 │
│ ├─ API 请求构建 │
│ ├─ 数据库查询生成 │
│ └─ 数据格式转换 │
│ 策略:Prompt + Schema + 代码三层全开 │
├────────────────────────────────────────────┤
│ 零容忍区域(安全优先) │
│ ├─ 权限相关操作 │
│ ├─ 财务/法律相关输出 │
│ └─ 涉及用户隐私的回复 │
│ 策略:代码层面的强制约束 + 人工审批 │
└────────────────────────────────────────────┘ 关键是在同一个 Agent 系统中,根据不同的输出类型动态调整约束强度。一个写客服回复的 Agent 和一个写 SQL 查询的 Agent,不应该使用相同的约束策略。
输出约束在工程上需要与 Agent 的框架深度集成:
// 输出约束管理器的核心结构
class OutputConstraintManager:
def getConstraints(self, context):
# 根据输出目标选择约束策略
if context.target == "tool_call":
return ToolCallConstraints(context.tool)
elif context.target == "user_reply":
return ReplyConstraints(context.scenario)
elif context.target == "data_extraction":
return SchemaConstraints(context.schema)
def apply(self, output, constraints):
# 应用约束(Prompt + Schema 已在 API 层面处理)
# 代码层面做最终校验
if constraints.validate(output):
return output
else:
return constraints.fallback() // 降级输出 到此,Harness 的四根缰绳全部到位:
Harness —— 控制大模型的第一根缰绳
┌────────────────────────────────────────────┐
│ ① 上下文管理 │
│ 问题:模型能看到什么 │
│ 答案:分层管理,把最重要的信息放在最前面 │
├────────────────────────────────────────────┤
│ ② 验证闭环 │
│ 问题:模型的输出是否可信 │
│ 答案:格式→语义→安全三层验证,失败走修正路径 │
├────────────────────────────────────────────┤
│ ③ 知识沉淀 │
│ 问题:经验能否复用 │
│ 答案:捕获→结构化→存储→索引→复用 │
├────────────────────────────────────────────┤
│ ④ 输出约束 │
│ 问题:输出格式对吗 │
│ 答案:Prompt→Schema→代码三层约束,兜底降级 │
└────────────────────────────────────────────┘
四根缰绳合在一起,不是为了"控制"模型,
而是为了让人与模型协作时,
系统是可预测、可维护、可改进的。 Harness 不是框架,不是工具库,是一套方法论。它的核心不是"阻止模型做错",而是让正确的行为更容易发生。四根缰绳分别管了模型的输入、处理、输出、成长——它们不是技术债,而是 Agent 系统走向生产环境的必修课。
💬 评论