English mirror
This English page is generated from the current Chinese documentation so every route, anchor, code sample, and language switch stays available on agently.tech. Human-edited English copy can replace this generated body page by page.
输出控制
很多模型应用最早翻车的地方,不是模型完全不会答,而是“看起来答了,代码却没法稳定消费”。今天有 risk 字段,明天叫 risk_level;数组有时是空的,有时混进解释性文本;字段缺了以后,业务系统只能靠异常兜底。
Output Control 解决的是这个问题:把模型回答变成有字段、有类型、有必填检查、有重试机会的数据。
先写一个结果契约
result = (
agent
.input("把这条工单整理成客服可以处理的结构:Ticket T-104 ...")
.output({
"status_summary": (str, "给客服看的状态摘要", True),
"risk_flags": [(str, "明确风险点", True)],
"next_actions": [(str, "下一步动作", True)],
"customer_reply": (str, "发给客户的回复", True),
})
.get_result()
)
data = result.get_data()这个 schema 里,每个叶子都可以写成 (type, description, ensure):
type约束字段类型。description告诉模型字段应该写什么。ensure=True表示字段要存在并可用。
ensure 不是默认值。它会被编译成必填路径检查,例如 status_summary、risk_flags[*]、next_actions[*]。
Agently 会检查什么
第一次读取结构化结果时,Agently 会跑一条固定流水线:
模型返回文本
↓
parse / repair
↓
strict output
↓
ensure_keys
↓
custom validate
↓
通过:返回 data
失败:在 retry 预算内重新请求几个细节值得注意:
- 缺 key、
None、空白字符串、空数组 wildcard 结果都会失败。 False和0是有效值,不会被当成空。- 所有失败共用一份 retry 预算,默认
max_retries=3。 - 预算耗尽后,默认抛异常;如果设置
raise_ensure_failure=False,会返回最近一次解析结果。
选择输出格式
省略 format 时,Agently 读取 prompt.default_output_format,全局默认是 json。这对业务系统最稳,也和旧消费者最兼容。
常用选择:
| 格式 | 适合 | 不适合 |
|---|---|---|
json | 嵌套对象、数组、外部系统互通、稳定机器契约 | 大段长文本或代码字段 |
auto | 已验证目标模型足够稳定,并接受 Agently 按 schema 选择格式 | 下游依赖固定原始输出形态 |
hybrid | 字符串长段落 + typed records 混合 | 所有字段都是紧凑机器数据 |
xml_field | 扁平纯字符串字段,或长文本字段需要明确边界 | 下游期待严格 XML 语义 |
yaml_literal | 团队明确偏好 YAML 文档 | 低遵循模型或缩进容易出错的场景 |
| 纯文本 | 只要一篇文章、一封邮件或一段 Markdown | 需要字段级读取或校验 |
新项目如果没有明确理由,先用默认 json。等目标模型在代表性任务上稳定后,再考虑 auto、hybrid 或 xml_field。
显式固定 JSON:
data = (
agent
.input("抽取发票字段。")
.output(
{
"vendor": (str, "供应商名称", True),
"line_items": [
{
"sku": (str, "SKU", True),
"amount": (float, "金额", True),
}
],
},
format="json",
)
.get_result()
.get_data()
)加一层业务校验
结构对了,不代表业务规则一定对。比如客服回复不能超过 280 字,可以加 .validate(...):
def reply_must_be_short(result, ctx):
reply = result.get("customer_reply", "")
if len(reply) > 280:
return {
"ok": False,
"reason": "customer_reply 超过 280 字",
"validator_name": "reply_length",
}
return True
data = (
agent
.input("把这条工单整理成客服回复:...")
.output({
"customer_reply": (str, "发给客户的回复", True),
})
.validate(reply_must_be_short)
.get_result()
.get_data()
)validate 在 strict output 和 ensure_keys 都通过之后运行。它看的对象是解析后的 canonical dict,而不是模型原始文本。
handler 可以返回:
| 返回 | 含义 |
|---|---|
True | 通过 |
False | 失败,并在预算内 retry |
{"ok": False, "reason": "..."} | 失败,并把原因写入错误或 retry event |
{"ok": False, "no_retry": True} | 失败但不重试 |
sync 和 async handler 都支持。
字段顺序也会影响效果
Output schema 是有序的。如果后面的结论依赖前面的证据,把支撑字段放在前面:
.output({
"facts": [(str, "从输入里提取的事实", True)],
"risk_reason": (str, "为什么有风险", True),
"risk_level": (str, "high / medium / low", True),
"customer_reply": (str, "面向客户的回复", True),
})让模型先产出事实和理由,再给等级和回复,通常比先要结论再补理由更稳。
复杂算术、长位数计算、加权聚合不要直接交给模型文本生成。让模型输出计算计划或参数,用工具执行,再把结果交给后续步骤。
什么时候用字段级流式
如果 UI 要在完整回答结束前看到某个字段,可以用 instant stream:
result = (
agent
.input("把工单整理成客服处理卡片:...")
.output({
"status_summary": (str, "一句话状态", True),
"risk_flags": [(str, "风险点", True)],
"customer_reply": (str, "客户回复", True),
})
.get_result()
)
for item in result.get_generator(type="instant"):
if item.delta:
print(item.path, item.delta)
final = result.get_data()instant 事件是 UI 临时状态。最终业务对象仍然从 get_data() / async_get_data() 读取,它不会重新请求模型。
什么时候不用 Output Control
如果输出只是一段自由文本,比如一篇完整文章、一封邮件、一个 Markdown 页面,不需要字段级读取,就不要强行写 schema:
result = agent.input("写一封 200 字以内的客户邮件。").get_result()
text = await result.async_get_text()只要下游代码需要按字段保存、分支、校验、展示或进入业务系统,再回到 .output(...)。
常见误用
- 只在 prompt 里写“请返回 JSON”,却不写
.output(...)。这样下游还是只能赌模型遵循格式。 - 把
ensure=True当默认值。它是必填检查。 - 一上来把
prompt.default_output_format改成auto,但没有用目标模型做代表性测试。 - 用关键词或子串匹配判断模型语义是否正确。语义质量需要更明确的规则或模型 judge,结构稳定只解决字段层问题。
- 流式 UI 使用
instant后,直接把临时 patch 当最终业务数据。最终保存仍然读get_data()。
下一步
- 了解 result 如何复用:模型响应
- 了解 schema 写法:Schema as Prompt
- 服务端和流式路径:Async First
- 给模型接外部能力:Actions 概览