你有没有遇到过这种情况:同一个提示词,温度已经调到 0,输出结果还是会偶尔飘一下;再把 top_p 往下压,文本突然就稳定了很多。大模型的打分、采样,以及后来常听说的 Pi Agent 这类智能体,表面上是几套独立的概念,实际上串在同一条逻辑线上。只有把“模型到底怎么选下一个词”这件事想清楚,后面调参数、调试 Agent 才不会靠猜。这篇文章我会结合自己实际跑过的项目,把 token 打分与采样机制拆开讲,再延伸到 Pi Agent 这类智能代理的核心原理,以及工程落地时到底该怎么调、怎么避坑。
我默认读者至少部署过或者调用过大模型 API,对 temperature、top_p 有模糊印象但没有系统理解。如果你正准备写一个偏向稳定性、工具调用的应用,这篇文章会更适合你。内容不会太上头讲数学,但关键公式和计算逻辑我会保留,因为后面排错的时候靠的就是这些细节。
1. 先从“打分”说起:模型脑子里其实是一张词表账单
1.1 每个候选词都有一个 logit 分数
大模型在生成下一个 token 时,做的事情本质上是一次“全词表打分”。假设词表大小是 32K 或者 128K,那么模型最后会输出一个同样长度的向量,每一个位置对应词表里某个 token 的原始得分,这个得分在深度学习里叫 logit。它可以是正的,也可以是负的,数值大小没有任何绝对意义,只有相对比较才有意义。
我举个例子。比如输入“今天的天气很”,模型可能给“好”打了 5.2 分,给“不”打了 3.1 分,给“热”打了 0.8 分,给“, ”打了 -0.5 分。注意这里不是概率,只是分数。真正的概率分布要通过 softmax 才能得到。
softmax 做的事情就是把这一堆分数压缩成总和为 1 的概率:分数最高的那个 token,概率未必就压倒性大,具体要看分数之间的差距。如果最高分和次高分只差 0.1,那么两者概率差不多;如果差了 5.0,那么概率会非常集中。这个“分数差距”非常重要,因为它决定了采样的随机性还有多大空间。
每一层 Transformer 计算到最后,都会接一个 lm_head 线性层,把最后一个隐状态映射到词表大小。这个映射就是打分的全过程。很多人以为模型“想好了一句话再开始说”,其实不是,它是一个 token 一个 token 现算现卖的。
1.2 softmax 之后才是概率,但不是每个概率都适合直接用
softmax 的公式写出来很简洁:
p_i = exp(logit_i / T) / sum_j(exp(logit_j / T))这里 T 就是 temperature。当 T=1 时,就是标准 softmax。当 T 大于 1,logit 会被缩小,概率分布变得更平缓;当 T 小于 1,logit 会被放大,高概率 token 的胜率更高。
一个很常见的误解是:temperature=0 就是“随机性最小”。严格来说,T=0 时公式里除以 0 没有意义,所以几乎所有推理框架在 T=0 时会直接取 logit 最大的 token,也就是贪心解码,等价于 argmax。我遇到过有人把 T 设成 0.0,然后问“为什么有时候还会随机”,这多半是因为框架内部把 0.0 解释成了“不指定”,悄悄换成了默认值。你在代码里真要关闭采样,最好显式把 do_sample=False 或者直接调用 greedy 接口,不要只丢一个 temperature=0。
另一个容易踩的点:top_p、top_k 影响的是“截断哪些 token 参与重算”,temperature 影响的是“截断前每个 token 的权重”。它们的执行顺序在大部分框架里是先缩放 logits,再做 top_k/top_p 过滤,最后 softmax。这个顺序影响实测效果,后面我单独讲。
2. 采样策略不是玄学:temperature、top_k、top_p 各自管哪一块
2.1 temperature 管“胆子大小”,但挡不住低概率 token
temperature 的本质是调节概率分布的形状。T 高,尾部 token 被选中的概率变大,输出更有“发散感”;T 低,头部 token 的概率被拉大,输出更保守。
但只调 temperature 有一个问题:即便温度调得很低,只要次高和最高的概率没有拉开差距,模型依然会在几个近义词之间抖动。你让模型生成“今天天气很__”,候选的“好”“不错”“晴朗”“棒”如果分数接近,输出就容易不稳定。这时候需要 top_p 或 top_k 把这一撮候选词之外的全部砍掉。
我自己的习惯是:写代码、做 JSON 抽取、跑 Agent 工具调用,temperature 直接给到 0.1 到 0.3,同时 top_p 给 0.9 左右;做营销文案、故事草稿,temperature 给 0.8 到 1.2,top_p 给 0.95。不要只调一个。
2.2 top_k 像是一个固定名额的投票池
top_k 表示只保留概率最高的 K 个 token,然后把其余 token 全部排除,再重新归一化概率。K 是一个绝对值,比如 50,代表每一轮解码只在得分最高的 50 个候选词里面选。
它的优点是简单、可控。缺点是词在不同位置的分布状态差别太大:有些位置模型非常确定,前 5 个候选就占了 99% 概率;有些位置模型很犹豫,前 100 个候选都差不多。用一个固定 K,要么在确定的位置引入了不必要的噪声,要么在犹豫的位置把合格候选误杀了。
我在做中文内容生成时对 top_k 比较谨慎,因为中文词表切分方式和英文不一样,一个汉字或词组在词表里的地位很不平均。固定 K 很容易让一些生僻字偶尔冒出来,英语环境里这种现象会轻一些。
2.3 top_p 是“看菜下饭”的核采样
top_p 又叫 nucleus sampling,它的逻辑比 top_k 更合理:从概率最高的 token 开始往下累加,直到累计概率刚好超过 p 就停,然后只在这些 token 里重新归一化。
比如 p=0.9,那么无论最后剩下 20 个还是 200 个 token,都是“足以覆盖 90% 可能性的那一组”。在模型确定的位置,截断名单会很短;在不确定的位置,截断名单会变长。这个适应性正是 top_p 比 top_k 更稳的原因。
实际使用中我一般把 top_p 当作“保险丝”,给 0.85 到 0.95 之间。它不负责“让结果更有创意”,只负责“把明显不合理的低概率垃圾选项排除掉”。
2.4 惩罚项:解决“车轱辘话来回说”的常用手段
除了 temperature、top_k、top_p,现在主流框架还会暴露 repetition_penalty、frequency_penalty、presence_penalty 这几个参数。它们的作用维度不一样:
- repetition_penalty:对已经出现过的 token 做额外降权,降权力度只看“出现过没有”,出现次数越多降得越狠。
- frequency_penalty:按 token 出现次数比例进行惩罚,次数越多,惩罚越大。
- presence_penalty:不管出现几次,只要出现就惩罚一次,偏向鼓励引入新主题。
我在摘要生成任务里遇到过特别典型的重复问题:模型第一段写了“公司今年营收增长”,第二段又写“公司今年营收增长”,不是 bug,而是解码时这些 token 的联合概率确实很高。这时候把 frequency_penalty 调到 0.5 左右,比调低 temperature 更有效,因为问题不在随机性,而在局部高概率循环。
下面的对比表是我自己整理的一个快速选参参考,适合大多数中小模型:
| 场景 | temperature | top_p | frequency_penalty | presence_penalty |
|---|---|---|---|---|
| 代码生成 | 0.1-0.3 | 0.9 | 0 | 0 |
| 结构化 JSON 抽取 | 0-0.2 | 0.95 | 0 | 0 |
| 对话助手 | 0.7 | 0.9 | 0.3 | 0.3 |
| 创意写作 | 0.9-1.2 | 0.95 | 0.5 | 0.4 |
| 摘要生成 | 0.3 | 0.9 | 0.5 | 0 |
这套组合不是金科玉律,但它能覆盖我 80% 的调参场景。剩下 20% 的情况,需要根据模型体量、微调数据和任务目标微调。
3. 实战中采样参数为什么必须联动着调
3.1 先调 temperature,还是先调 top_p?
我的经验是:先确定任务的“确定性需求”,再定 temperature,然后用 top_p 补一刀。
如果任务要求稳定输出,比如代码补全、SQL 生成、实体抽取,temperature 直接降到 0.3 以下。如果任务要求多样性,比如批量生成不同风格的标题,temperature 提到 0.8 以上。接着再看输出里有没有“看起来不合常理但概率不低”的词。如果任务稳定但偶发怪词,那就是 top_p 太大,压到 0.85 一般能解决;如果任务发散但输出太干巴,把 top_p 调到 0.98 以上,让更多尾部词参与竞争。
有一个容易忽略的点:temperature 升高后,top_p 的截断效果也会变化。因为温度改变了 logits 的相对差距,同一组 token 排序可能不变,但累计概率的分布会变。这意味着 temperature 和 top_p 不是各自独立生效,你改了一个,另一个的“表现”也会跟着变。调参时尽量一次只动一个变量,否则很难定位是谁引起了变化。
3.2 采样参数对 Agent 工具调用的影响,比想象中大
如果你只是做聊天问答,采样参数影响的是措辞和风格;但如果你在做 Agent 工具调用,采样参数直接影响“能不能正确触发一个函数”。
一个很常见的故障是:模型该调用 search_products 工具,结果却输出了“search_products”字符串,或者自己编了个 searchProducts。这种情况大多不是模型能力不够,而是解码策略给了模型太多自由,让它从“最可能的 token 序列”里滑到了“第二可能的 token 序列”。
我在跑 Pi Agent 这类能自动规划、自动执行代码的智能体时,第一个动作就是把采样参数拉回确定性区间。之前的项目里,temperature 从 0.7 改成 0.2 之后,工具调用失败率直接降了大约 40%,代价是生成内容确实更“死板”,但在 Agent 场景里“死板”不是缺点。
3.3 利用 logprob 判断模型置信度
很多推理框架会额外返回每个 token 的 logprob。这个值很有用,它能告诉你模型在对当前内容“犹豫不决”。
比如模型生成“import os”之后,下一个 token 的 logprob 很高,说明这一步模型很有把握;如果某个 token 的 logprob 和次高 token 差得很少,说明这里是个容易出错的点。工程上可以用这个信息做两件事:
- 记录低置信度 token 的位置,后期人工审查。
- 在 Agent 循环里,如果连续多个 token 置信度都很低,就提前跳出,重新规划提示词或让模型生成多个候选再投票。
不要只看最终文本“看起来像不像”,logprob 是解码阶段直接暴露出来的内部信号,比人工抽检客观得多。
4. Pi Agent 核心原理:从“模型聊天”到“模型干活”
4.1 什么是 Agent?为什么不能直接靠提示词解决?
把大模型当作聊天机器人用,交互模式是“输入问题 -> 输出答案”;把大模型当作 Agent 用,交互模式则是“输入目标 -> 计划拆分 -> 调用工具 -> 观察结果 -> 调整计划 -> 完成目标”。Pi Agent 就是这一类智能代理中的一个典型实现,尤其适合代码分析、批量文件操作这类需要多步骤执行的任务。
为什么普通提示词工程做不到?因为真实任务往往有时间线、外部状态和反馈闭环。你让模型“把项目里的所有 TODO 整理成 Excel”,模型在一轮对话里无法真正读取文件、遍历目录、处理格式。Agent 的价值是给模型装了“手”:它能执行命令、读文件、写文件、运行测试,然后根据执行结果决定下一步动作。
Pi Agent 的逻辑核心并不复杂,我理解下来就是一个带状态机的循环:
1. 解析任务目标 2. 生成一个执行计划 3. 选择一个工具并构建参数 4. 执行工具,拿到观察结果 5. 判断结果是否符合预期 6. 不符合则修订计划,回到第 3 步;符合则继续下一步 7. 所有步骤完成后汇总输出这个循环一旦抽象出来,你会发现市面上大多数 coding agent 都是同一套骨架。区别在于每个模块的工程实现细节。
4.2 Pi Agent 的工具调用设计:让模型“在一个受限选项里做选择”
Agent 里最关键的一步不是最终文本生成,而是“决策”。模型每次决定调用哪个工具、传入什么参数,本质上还是一个 token 级别的解码问题,但它被强行约束在了一个结构化空间里。
实现上通常有两种约束方式。第一种是传统的 function calling 路线,模型输出 JSON,里面包含 function name 和 arguments,框架解析后执行。第二种是更严格的 constrained decoding,在解码阶段直接通过 mask 掉非法 token,保证模型不可能输出一个不在预定义 schema 里的 JSON。
Pi Agent 这类偏工程向的框架,我更推荐用第二种思路去理解。因为它不是让模型自由发挥,而是把模型的行为空间限制在一个很小的集合里,采样参数在这种模式下才能发挥真正的价值。如果模型可以随意输出,那温度调多低都救不回来。
正因为如此,采样参数在 Agent 里有更直接的影响:temperature 决定“策略探不探索”,top_p 决定“工具名会不会被截断”,penalty 决定“计划步骤会不会重复”。我之前遇到的一个教训是给 Agent 开了 frequency_penalty,结果它把某个必要函数调用当成“重复内容”给惩罚掉了,导致执行链断裂。所以 Agent 场景里惩罚项默认关掉,除非你明显看到循环重复。
4.3 记忆与反思:Agent 比 RAG 多出来的那一层
很多人会把 Agent 和 RAG 混在一起,实际上两者的定位不同。RAG 解决的是“模型不知道什么”,把外部知识塞进上下文;Agent 解决的是“模型不能做什么”,把外部工具接入决策循环。Pi Agent 的上下文管理更接近一个工作区:它不只存放检索回来的资料,还存放中间执行结果、报错信息、上一步的状态。
“反思”模块是 Agent 强大和危险并存的原因。强大在于,当工具执行失败时,模型能读取错误信息,修正计划重试;危险在于,如果采样参数过于发散,模型会在失败后脑补一个根本不存在的解决方案,越改越偏。
我的工程经验是:给 Agent 设置“重试上限”与“失败回退”规则。比如同一步骤连续失败 3 次就强制切换策略,不依赖模型自动判断是否该停下来。这是把“模型自由决策”和“流程硬约束”结合起来,不能只靠其中一边。
5. 采样和打分如何反向影响 Agent 的每一步决策
5.1 温度越低,Agent 越“乖”,但也越容易错过替代方案
在 Agent 执行路径上,低温度可以保证每一步决策都选择概率最高的动作,不易出幻觉,尤其适合工具调用。但代价是模型几乎不会探索“另一种做法”。
举个例子,如果第一步计划应该是“读取配置文件”,但当前目录下没有这个文件,模型大概率会直接报错退出;如果温度稍高,它可能会尝试列出目录、搜索文件、查看 README,从而找到正确路径。这种探索能力对复杂任务有价值,但你也要承担它突然偏离目标的风险。
我的选择是分阶段控制:规划阶段用稍高温度,执行阶段用最低温度。规划阶段需要发散,把所有可能的路径想全面;执行阶段需要收敛,避免对工具参数做无中生有的替换。
5.2 用 top_p 抑制工具名的“近义词漂移”
工具调用中一个隐蔽的问题:模型意思对了,但表达不对。比如定义好的工具名是 list_files,模型偏要输出 listFile 或者 listfiles。这种问题在开了 top_p 过大时更容易出现,因为候选集中混入了一些表面合理但 schema 不匹配的 token。
解决思路不只是调低 top_p,还要在提示词里把工具名、函数签名、参数类型写得足够“死”。在 Pi Agent 这类框架里,工具描述就是给模型看的“API 文档”,描述模糊,模型就只能猜。参数值也一样,如果某个参数只允许字符串枚举值,那就明确写出来,同时把这几个枚举值本身也放进解码约束条件里。
5.3 结构化输出时,采样参数要让位于约束解码
如果你在做一个数据抽取 Agent,最终结果要落成 JSON,那么单纯调小 temperature 还不够。更可靠的做法是使用约束解码或者严格的 JSON schema 校验。约束解码意味着在每一轮生成 token 时,根据当前已生成内容和目标 schema,直接屏蔽掉非法 token。
这个方法对采样参数几乎是免疫的,因为模型没有机会输出 schema 之外的文本。它解决的是“大模型不会稳定地输出合法 JSON”的问题,比任何咒语式提示词都靠谱。代价是实现成本高一些,而且不是所有推理框架都支持。如果框架不支持,退而求其次的做法是:temperature=0.1 + top_p=0.9 + 生成后做 schema 校验,校验失败就重新生成,但重试次数控制在 3 次以内。
6. 综合落地:一条可复用的 Agent + 采样配置路径
6.1 第一步:把任务拆成“决策型”和“生成型”
在给 Pi Agent 配置采样参数之前,我会先做一个任务拆分。写入文件的代码、查询数据库的 SQL、调用函数的参数属于决策型,这些内容必须精确;解释性注释、错误分析报告、最终总结属于生成型,这些内容可以有风格变化。
理想做法是:决策型步骤用低温度 + 结构化输出;生成型步骤用默认温度 + 较长输出限制。如果整个 Agent 只允许一套采样参数,我建议无脑选低温度,因为生成型文字可以后期润色,决策型错误却会导致整个任务失败。
6.2 第二步:给循环加上“质量门禁”
Agent 只有循环还不够,循环的退出条件必须设计清楚。我通常设置三层门禁:
- 第一层是工具执行层:命令返回非零退出码就算失败,直接进反思。
- 第二层是结构化校验层:JSON 解析失败、字段缺失、枚举值非法,都算失败。
- 第三层是任务完成度层:由模型自己判断目标是否达成,但为了防止它自欺欺人,我还会额外要求它给出完成证据,比如文件路径、关键输出摘要、测试结果。
这三层门禁和采样参数没有直接关系,但没有门禁,单纯调参数无法保证系统质量。采样决定“模型有没有自由”,门禁决定“模型乱来之后系统会不会兜底”。
6.3 第三步:建立可量化的调参基线
调参不能靠感觉,尤其是 Agent 场景。我的习惯是固定一组测试任务,跑 20 轮,记录三个指标:
- 工具调用成功率:模型输出的调用是否被框架成功解析并执行。
- 重试次数:同一子任务被循环捞回几次才算成功。
- 完成率:最终目标是否达成,结果是否可以验收。
然后改一个参数,再跑同一组任务,对比指标。不要同时改 temperature 和 top_p,否则出了问题你不知道是谁的责任。我遇到过一个项目,修改后工具调用成功率从 80% 掉到 50%,排查半天发现是 temperature 从 0.2 调到了 0.4,top_p 没动。这个幅度看起来很小,但对 Agent 的稳定性影响就是这么大。
6.4 第四步:必要时退回到“多候选 + 投票”
如果某个步骤要求特别精确,但模型单个候选不稳定,可以用“多次采样再选”来解决。做法是让模型针对同一个决策点生成 N 个候选结果,每个用较高的 temperature 采样,最后拿 N 个结果做投票或者按 logprob 总和排序,取最一致的答案。
这个方法比单纯把 temperature 降到极低更有效。因为 temperature=0 只是贪心地选择第一步最可能的 token,但“第一步最可能”不等于“完整序列最合理”。多个候选取一致,能抵消解码路径上的偶然偏移。
代价是推理成本乘以 N,所以通常只在关键路径上使用。我在关键的文件操作和代码生成环节用 3 个候选投票,非关键步骤直接单次低温度生成。
7. 最后分享两个容易被忽略的细节
7.1 “打分”不只是解码阶段的事,评估阶段也重要
项目标题里提到的“打分”,在 Agent 工程化中还可以理解成“对任务结果的评估”。Pi Agent 这类框架里经常内置一个模型打分的环节:让大模型评估自己或者别的新能手的输出,给质量打分。
这一步同样受采样参数影响。给分的时候如果把 temperature 调得太高,模型可能因为表达差异给出不稳定分数。我的建议是评估任务一律用低温度,同时要求模型先给出评分理由,再给分数,顺序不能反。先给理由会让模型被迫思考,先给分数则容易跟着锚定走。
7.2 Agent 的上下文窗口不是免费的,采样也不会帮你省资源
最后提醒一句:Agent 和多轮对话不同,每次工具返回结果都会塞进上下文,时间一长成本会涨得很快。采样参数再合理,也解决不了上下文膨胀。工程上要做的不是放任模型把整个文件读进来,而是给 Agent 加文件裁剪、行号过滤、摘要前置这些约束,让它只看到和自己当前任务相关的部分。
我当时第一次把 Pi Agent 用在代码仓库分析上,就吃过这套亏:Agent 把一个大文件反复读取,单次任务的 token 消耗翻了快五倍。后来设置了最大读取行数和按关键字过滤,成本立刻降下来,稳定性反而更好了。这个优化和模型本身的能力无关,但它是能让 Agent 真正跑在业务里的关键一步。
大模型打分与采样机制,其实没有多高深,它就是“词表上每个候选 token 的分数 -> 概率化 -> 按策略选择”这么一条链路。把这条链路想清楚,再看 Pi Agent 这类智能体,会发现很多设计都是顺理成章的:Agent 要做决策,所以必须把自由度收紧;Agent 要执行多步,所以需要循环和门禁;Agent 要可靠,所以需要反复评估和策略兜底。我在实际项目中最大的体会是,不要把这些东西割裂地看,采样参数不是某一步的微调,而是整个系统稳定性的基石。少改参数多测指标,比追任何新框架都管用。