
同一句提示词连续问三遍,大模型为什么会给出三种不同的回答?
我最开始把这种现象简单理解成"模型会随机说话"。但继续往下学才发现,随机并不等于毫无规律。模型每生成一个 Token,都会先得到一组候选分数,再按照采样规则选出下一个 Token。Temperature、Top K 和 Top P,控制的正是这个过程。
这篇文章不把参数当成几个孤立的旋钮,而是从一次 Token 选择出发,看看它们分别改变了什么,最后再用一个 LangChain.js 小实验观察低温与高温输出的差别。
一、模型不是在"想一句完整的话"
假设上下文是"你好",模型正在预测下一个 Token。为了方便理解,我们先虚构一组概率:
| 候选 Token | 吗 | 啊 | 呀 | 呢 | 哦 |
|---|---|---|---|---|---|
| 概率 | 60% | 18% | 12% | 7% | 3% |
"吗"的概率最高,但这不代表它每次都必然被选中。只要生成过程采用采样,其他候选仍然有机会出现,只是机会不同。
这里说的是 Token,不一定是一个完整汉字或单词。它也可能是词的一部分、标点或者空格。模型每选出一个 Token,就会把它加入上下文,再继续预测下一个,直到生成结束。

理解这一点后,三个参数的分工就清楚多了:
Temperature改变候选之间的概率差距;Top K按候选数量缩小范围;Top P按累计概率缩小范围。
二、Temperature:改变概率分布的"形状"
模型先产生一组原始分数,也就是 logits。Temperature 会参与 softmax 计算:
text
p_i(T) = exp(z_i / T) / Σ_j exp(z_j / T)
不必被公式吓到。可以把它拆成三步:
z_i是第i个候选 Token 的原始分数;- 每个分数先除以温度
T; - softmax 再把这些分数转换成总和为 1 的概率。
当 T 是较小的正数时,原本的分数差距会被放大,概率分布变得更"尖":高分候选更占优势,输出通常更集中。
当 T 较大时,分数差距被缩小,概率分布变得更"平":更多候选有机会被抽中,输出通常更发散。
因此,Temperature 不是"把某个低概率词直接选出来",而是先重塑整组概率,再交给采样过程选择。
还要注意两个边界:
- Temperature 的合法范围由模型供应商决定,不能笼统地写成永远是
0~1; temperature: 0如何处理也取决于具体 API,有的服务会走近似确定性的路径,有的服务不接受 0。
所以,调参前先看自己使用的模型文档,比记住一个固定范围更可靠。
三、Top K:只留下前 K 名候选
Top K 的规则很直接:先按概率从高到低排序,只保留前 K 个候选,其余候选本轮不参与采样。
还是用前面的概率举例。若设置 topK = 3,候选集只剩下:
text
吗 60% 啊 18% 呀 12%
"呢"和"哦"会被排除。随后,系统会对留下的候选重新归一化,再进行采样。
Top K 的优点是候选数量稳定,容易理解;局限也很明显:无论当前概率是特别集中还是特别分散,它都机械地保留 K 个。
更重要的是,并非每个模型 API 都提供 Top K。参数是否存在、叫 topK 还是 top_k、应该写在构造参数还是透传字段里,都要以供应商和 SDK 文档为准。
四、Top P:让候选数量跟着概率变化
Top P 也叫 nucleus sampling(核采样)。它不固定候选数量,而是从最高概率开始累加,直到累计概率达到阈值 P。
假设设置 topP = 0.85:
text
吗 60% 累计 60%
吗 + 啊 累计 78%
吗 + 啊 + 呀 累计 90% -> 达到 85%,停止
这一轮会留下 3 个候选。如果下一轮的概率更集中,也许只需 1~2 个候选就能达到 85%;如果概率更分散,则可能保留更多候选。
这也是 Top P 在许多通用大模型 API 中更常见的原因:候选集能随当前上下文动态变化。

五、三个参数到底是什么关系?
| 参数 | 改变什么 | 直觉效果 | 注意事项 |
|---|---|---|---|
| Temperature | 整组概率的相对差距 | 低温更集中,高温更发散 | 范围和 0 的处理由供应商决定 |
| Top K | 候选集的最大数量 | 只在前 K 名中选择 | 很多 OpenAI 兼容接口并不支持 |
| Top P | 候选集的累计概率范围 | 按当前分布动态保留候选 | P 越小,候选范围通常越窄 |
它们不是互相替代的同一个参数,但也没有"Temperature 和 Top K 绝对不能同时很大或同时很小"这样的硬规则。
实际系统常见的处理顺序是:先用 Temperature 改变概率分布,再按 Top K 或 Top P 过滤候选,最后采样。但具体服务可能只暴露其中一部分参数,也可能规定不要同时修改多个采样参数。
最稳妥的做法是:选择一个保守基线,每次只改变一个参数,用固定评测集观察结果。
六、不同任务可以从什么参数开始?
下面不是"标准答案",只是便于启动实验的参考值。不同模型即使接收同名参数,表现也可能不同。
| 任务 | Temperature 起点 | Top P 起点 | 观察重点 |
|---|---|---|---|
| 代码生成、结构化抽取 | 0.1~0.3 | 0.80~0.90 | 格式稳定、约束遵守、可执行性 |
| 基于资料的事实问答 | 0.2~0.4 | 0.85~0.95 | 引用一致、答案完整、拒答边界 |
| 普通文案与总结 | 0.5~0.7 | 0.90~0.98 | 可读性、重复度、信息保真 |
| 创意写作与角色表达 | 0.8~1.0 | 0.95~1.00 | 新颖度、连贯性、跑题率 |
如果供应商建议只调 Temperature 或 Top P,就先遵循它的建议。一次同时改两个变量,会让你很难判断究竟是哪一个参数带来了变化。
七、用 LangChain.js 做一次对比实验
原学习示例使用了两套 ChatOpenAI 配置:低温负责"严谨",高温负责"创意"。这个实验思路很好,但本地安装的 @langchain/openai@1.5.5 有一个兼容性细节需要修正。
在这个版本中,ChatOpenAI 的顶层构造参数会读取 temperature 和 topP,却不会读取顶层的 topK。也就是说,下面这种写法虽然 JavaScript 不会报错,topK 实际上不会进入请求:
javascript
new ChatOpenAI({
temperature: 0.8,
topK: 4,
});
如果模型供应商支持 Top K,需要按照它的 SDK 或透传参数文档配置,不能默认所有 OpenAI 兼容接口都支持。为了让本文示例与当前本地依赖匹配,下面改用已支持的 temperature + topP。
js
import "dotenv/config";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { PromptTemplate } from "@langchain/core/prompts";
import { ChatOpenAI } from "@langchain/openai";
const modelName = process.env.DEEPSEEK_MODEL;
if (!process.env.DEEPSEEK_API_KEY || !process.env.DEEPSEEK_API_URL || !modelName) {
throw new Error(
"请先配置 DEEPSEEK_API_KEY、DEEPSEEK_API_URL 和 DEEPSEEK_MODEL",
);
}
function createModel({ temperature, topP }) {
return new ChatOpenAI({
model: modelName,
temperature,
topP,
maxTokens: 600,
apiKey: process.env.DEEPSEEK_API_KEY,
configuration: {
baseURL: process.env.DEEPSEEK_API_URL,
},
});
}
const storyPrompt = PromptTemplate.fromTemplate(`
请写一篇短篇散文,主题:{theme}
风格温柔治愈,篇幅 200 字左右,不要分段,文字细腻、有画面感。
`);
const outputParser = new StringOutputParser();
const preciseChain = storyPrompt
.pipe(createModel({ temperature: 0.2, topP: 0.8 }))
.pipe(outputParser);
const creativeChain = storyPrompt
.pipe(createModel({ temperature: 0.9, topP: 0.95 }))
.pipe(outputParser);
async function runChain(label, chain, theme, rounds = 3) {
console.log(`\n=== ${label} ===`);
for (let round = 1; round <= rounds; round += 1) {
const text = await chain.invoke({ theme });
console.log(`\n第 ${round} 次:\n${text}`);
}
}
async function main() {
const theme = "秋日山野晚风";
await runChain("低温模式", preciseChain, theme);
await runChain("高温模式", creativeChain, theme);
}
main().catch((error) => {
console.error("调用失败:", error);
process.exitCode = 1;
});
运行前需要配置三项环境变量:DEEPSEEK_API_KEY 使用自己的密钥,DEEPSEEK_API_URL 使用服务商提供的兼容接口地址,DEEPSEEK_MODEL 使用服务商实际支持的模型名。不要把未经确认的模型名称写死在代码里,也不要把真实密钥提交到仓库。
安装依赖后运行:
bash
pnpm install
node main.mjs
示例让每条链连续生成 3 次。观察时不要只比较"哪篇更好",还可以记录:
- 相同意象和句式的重复程度;
- 用词是否更大胆;
- 是否偏离"秋日山野晚风"的主题;
- 多次输出之间的差异有多大。
这才是调节随机性的意义:把感觉变成可以重复观察的实验。
八、降低 Temperature 能解决幻觉吗?
不能。
降低 Temperature 往往能让输出更稳定,却不能让模型突然知道它原本不知道的事实。一个错误答案也可能以非常稳定、非常自信的方式重复出现。
如果业务关心事实可靠性,还需要组合其他手段:
- 用 RAG 为回答提供可追溯资料;
- 用工具调用查询数据库或实时接口;
- 用结构化输出和业务规则约束结果;
- 建立包含正确答案、拒答场景和边界样本的评测集;
- 对高风险任务增加人工复核。
Temperature 管的是"怎么抽样",不是"知识从哪里来"。把这两个问题分开,才能避免对参数产生不切实际的期待。
九、我的调参清单
以后再遇到需要控制大模型随机性的需求,我会按下面五步做:
- 先定义任务目标:要稳定、完整、有创意,还是兼顾几项?
- 选择保守基线:先使用供应商默认值或较低 Temperature。
- 一次只改一个变量:否则无法判断变化来自哪里。
- 同一输入重复采样:单次输出不能代表参数的整体效果。
- 用指标评估质量:同时看正确率、格式通过率、重复度、跑题率和人工偏好。
总结
大模型的生成不是从词库里随便抓一个词,而是在每一步计算候选分数、形成概率分布,再按照采样规则选出下一个 Token。
- Temperature 重塑概率差距;
- Top K 用固定数量限制候选;
- Top P 用累计概率动态限制候选。
参数没有脱离模型、任务和评测集的"万能搭配"。先理解每个旋钮到底改了什么,再通过重复实验观察效果,才是开发者更有效、也更靠谱地控制 AI 随机性的方式。