同一个问题问两遍得到不同答案,这不是缺陷,是解码策略的必然结果。但在 Agent 里,这种不确定性会以最讨厌的方式暴露出来:工具参数填错、JSON 少一个大括号、同一个任务两次跑出不同的步骤数。这一章讲清每个采样参数到底在动什么,以及哪些地方必须确定、哪些地方必须不确定。
编辑部注 · 读完请务必记住一条工程纪律:需要结构化输出的地方,temperature 应该是 0(或极低),而不是「调低一点」。
模型输出不是一个答案,而是一个概率分布。每一步它给出词表上每个 token 的概率,然后由解码策略从中挑一个出来。所谓「温度」「top-p」,都是在改这个挑选过程。理解这一点,你就能判断哪些不确定性是可修的、哪些是不可修的。
一、模型实际吐出的是什么
在最后一层之后,模型给出的是 logits——词表大小(通常 10 万量级)的一个向量,每个位置一个分数。经过 softmax 变成概率分布。
一个必须建立的直觉:模型从来没有「想好了一个答案」,它只是不断地在每一步给出一个分布,然后从里面抽一个。 所谓「生成」,是抽样的累加。
| 参数 | 作用位置 | 在做什么 | 调高 / 调低的后果 |
|---|---|---|---|
temperature | softmax 之前 | 给 logits 除以 T:T<1 让分布更尖锐,T>1 让分布更平坦 | 调低 → 更确定、更保守、也更容易陷入重复;调高 → 更发散、更有创意,也更容易跑偏 |
top_p(核采样) | softmax 之后 | 按概率从高到低累加,只保留累计到 p 的那批候选,其余丢弃 | 调低 → 候选集变小,输出收敛;调高 → 保留更多长尾可能 |
top_k | softmax 之后 | 固定只保留概率最高的 k 个候选 | 与 top_p 作用类似;但 k 固定,遇到分布极尖或极平时表现不一致 |
repetition_penalty | logits 修正 | 对已出现过的 token 降低其分数 | 轻微使用可缓解复读;过强会破坏专有名词与代码标识符的重复出现 |
seed | 随机数源 | 固定采样用的随机序列 | 同一 seed + 同一请求 + 同一后端版本 → 可复现;换后端可能失效 |
二、temperature 与 top_p 的区别
这两个参数经常被混着用,但它们动的是分布的两个不同阶段。
三、Agent 场景下的确定性纪律
这是本章最有工程价值的一节。把 Agent 里所有调用模型的地方按「要不要确定性」分两类:
| 调用类型 | 推荐设置 | 原因 |
|---|---|---|
| 工具调用 / 参数生成 | temperature 0,top_p 尽可能低 | 参数写错就执行失败,没有「创意」的余地 |
| 结构化输出(JSON / YAML) | temperature 0 + 强制 schema | 格式错误直接导致解析异常,会引发重试与成本翻倍 |
| 意图分类 / 路由 | temperature 0 | 同一句话应该稳定路由到同一条链路,便于复现问题 |
| 代码生成 | temperature 0 ~ 0.2 | 可验证(能跑通),不需要多样性;低温度减少无意义的变体 |
| 方案探索 / 头脑风暴 | temperature 0.7 ~ 1.0 | 此时多样性本身就是产出,需要多个不同的候选 |
| 文案润色 | temperature 0.5 左右 | 既要稳定风格,又要避免套话 |
即使温度设为 0(即每步都取概率最高的 token,greedy 解码),同一请求在不同条件下仍可能得到不同结果:
- 浮点非确定性:批处理中不同 token 与其他请求拼在同一次前向里,累加顺序变化会导致极小的数值差异,在分布接近的两个 token 之间翻转;
- 后端版本变化:推理框架升级、量化版本更换,都会改变 logits 的微小差异;
- 前缀缓存命中与否:不同计算路径同样可能带来微小数值差异。
所以正确的表述是:temperature=0 让结果「几乎确定」,但工程上必须假设它可能变化——不能把正确性建立在「它一定一样」上面。
四、让输出可控的三种手段
既然模型输出本质是概率性的,工程上就要用「约束」而不是「祈祷」。按强度从弱到强:
第一层:低温度。 最简单,但只是降低概率,不提供任何保证。
第二层:JSON Schema / 结构化输出约束。 在解码时限制每个位置只能取合法 token(例如进入字符串状态就只能取字符串字符)。这是真正意义上的强制,失败率极低。大多数现代 API 都支持 response_format 或 tool_calls 这类约束通道。
第三层:后置校验 + 有限重试。 无论前面怎么做,都要在拿到结果后做一次校验(能否解析、字段是否齐全、取值范围是否合法)。校验失败时的重试必须带「上一次错在哪」的信息,否则只是重复同样的错误。
import json
from typing import Any
TOOL_SCHEMA = {
"type": "object",
"properties": {
"city": {"type": "string"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
"days": {"type": "integer", "minimum": 1, "maximum": 7},
},
"required": ["city", "unit", "days"],
"additionalProperties": False,
}
def call_model(messages, temperature: float):
"""占位:真实调用里把 temperature 和 response_format 一起传下去"""
raise NotImplementedError
def validated_tool_call(messages, retries: int = 2) -> dict[str, Any] | None:
"""
三层防线的落地:
1. temperature=0 + 结构化输出约束(由 API 保证格式)
2. 本地 schema 校验(参数完整性、取值范围)
3. 带错误信息的有限重试(而不是盲目重试)
"""
local = list(messages)
for attempt in range(retries + 1):
raw = call_model(local, temperature=0)
try:
args = json.loads(raw) if isinstance(raw, str) else raw
except json.JSONDecodeError as e:
local.append({"role": "user",
"content": f"上一次输出不是合法 JSON({e}),请只输出 JSON 对象。"})
continue
err = validate(args, TOOL_SCHEMA)
if err is None:
return args
# 关键:把具体错在哪回喂,模型才能真正修正
local.append({"role": "user",
"content": f"参数校验失败:{err}。请修正后重新输出完整参数。"})
return None # 明确失败,交由上层决定是降级还是转人工
def validate(obj: dict, schema: dict) -> str | None:
"""极简校验:只示意逻辑,生产环境请用 pydantic / jsonschema"""
for key in schema.get("required", []):
if key not in obj:
return f"缺少必填字段 {key}"
if obj.get("unit") not in schema["properties"]["unit"]["enum"]:
return f"unit 取值非法:{obj.get('unit')}"
if not isinstance(obj.get("days"), int) or not 1 <= obj["days"] <= 7:
return f"days 必须是 1-7 的整数,当前为 {obj.get('days')}"
return None- 拿一个真实的任务,把 temperature 设成 0、0.7、1.2 各跑 10 次,记录参数完全一致的次数;
- 统计「重试时把错误信息回喂」与「不带信息重试」的成功率差异——通常会差 20 个百分点以上;
- 写一句话说明:为什么在 Agent 里,
temperature=0之外还必须做校验?
五、自测
六、小结
- 模型输出的是分布,不是答案;解码策略决定怎么从分布里挑。
- 温度改「多平」,top-p 改「多宽」;两者叠加时要注意极端组合。
- Agent 里凡是需要结构化结果的地方,一律低温 + 强约束 + 校验 + 带反馈的有限重试。
- temperature=0 只是「几乎确定」,工程上不能依赖「一定一致」。
不确定性本身不是问题,把不确定性当成确定性来用才是问题。本刊编辑部
到这里,模型原理部分还差最后一块拼图:既然算力与显存都这么贵,工业界到底用什么手段把成本打下来?
◇ 面试官会怎么问
共 3 条 · 先自己答一遍,再展开对照
Temperature 和 top_p 分别在调什么?Agent 场景你会怎么设?
Agent 场景的经验是一条原则:凡是输出要被程序解析的地方(工具调用、结构化参数、JSON),尽量低温甚至贪婪解码;需要发散的地方(头脑风暴、候选方案生成)才提高温度。
要注意:即使 temperature = 0,由于并行归约的浮点非确定性、批处理组合变化等原因,输出也不保证逐字一致——所以兜底必须是校验和重试,而不是"假设它稳定"。
追问链
- 那参数校验失败了你怎么办?
- 为什么 temperature=0 仍然可能两次结果不同?
同一个问题问两次答案不同,这在 Agent 里会变成什么问题?
对应的工程手段是:结构化输出 + Schema 校验、解码参数按环节分级设置、把关键决策点做成确定性代码而不是交给模型自由发挥、评测时固定随机种子并多次重复取分布。
top_k 和 top_p 能同时用吗?会有什么问题?
更实用的建议是:固定一个主参数(OpenAI 系通常调 top_p,部分开源模型偏 top_k),另一个设为默认值,避免两个旋钮互相干扰导致调参不可解释。另外,把"温度"和"惩罚项"(presence / frequency penalty)混着调最容易出现"越调越怪"的情况。
结论先行(一句话给出取舍)→ 机制(为什么是这样,涉及哪条链路)→ 代价或边界(这么做放弃了什么)→ 你的实践或数字(真实场景里怎么落地)。 只讲机制不讲代价,是背题;只讲取舍没有数字,是空谈。