每一个参数背后都是一种权衡
先把后面要反复出现的词理清
这篇里很多参数的解释要用到下面这些概念,先一次性讲清楚,免得后面来回切窗口:
- token:模型看到的最小单位。中文里常对应 1 个字或 1~2 个字,英文里常对应一个常用单词或前缀/后缀片段。例如
"hello"是 1 个 token,"reasoning"可能被切成"reason"+"ing"两个 token - tokenizer:把字符串切成 token 序列的工具,每个模型自带一套。OpenAI 系列用
tiktoken,开源模型多数自带在 HuggingFace 仓库里 - logits:模型每生成一个 token 时输出的"原始打分"——一个长度等于词表大小的向量,每个位置是对应 token 的分数。分数越高,被选中的可能越大
- softmax:把任意一组实数转换成"和为 1 的非负数"(也就是概率分布)的函数:
softmax(x_i) = exp(x_i) / Σ exp(x_j)。它把 logits 变成实际可以拿来采样的概率 - 采样(sampling):从概率分布里抽出一个 token 的过程。最简单的方式是按概率随机抽,复杂方式会先裁剪分布(top-k、top-p)再抽
- SSE(Server-Sent Events):一种 HTTP 长连接协议,服务器可以持续往客户端推数据。流式输出(
stream=True)就是基于它做的 - JSON Schema:一种描述 JSON 结构的标准(字段类型、必填项、嵌套结构),让程序可以校验一个 JSON 是不是符合预期形状
- Pydantic:Python 里最流行的数据校验库,用类型注解定义一个数据模型,自动生成 JSON Schema、做反序列化、报详细错误
下面所有参数的"控制什么"都会落到这几个概念上。
起点:一个最简调用里有哪些默认参数
新手第一次调 API 经常写成这样:
from openai import OpenAI
client = OpenAI(api_key="...", base_url="...")
resp = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": "你好"}],
)
只传 model 和 messages 就能拿到回复。但 chat.completions.create 总共支持 20 多个参数,没传的部分都用默认值。这些默认值常常不是你真正想要的——比如 temperature=1 在抽取任务里会让结果不稳定,没设 max_tokens 时模型可能突然写出 3000 字的长回复,对应的 API 调用成本会显著上升。
下面把这些参数按它们实际控制模型的哪一步分组讲。
模型与上下文:写错就直接报错
model——指定调哪个后端模型。这是字符串完全匹配,DeepSeek 是 "deepseek-chat" / "deepseek-reasoner",OpenAI 是 "gpt-4o" / "gpt-4o-mini" / "o1" 等。写错不会 fallback,直接 404 或 model not found。
messages——对话历史的列表。每条消息是 {"role": ..., "content": ...},role 可以是 system(系统提示,定义模型角色)、user(用户输入)、assistant(模型之前的回复)、tool(工具调用结果,下面会讲)。模型把整个列表拼成一个长序列送进去,所以 messages 越长,每次调用计费越贵、首 token 延迟越高。
这两个参数前面主线讲过,不展开。下面是真正影响输出行为的部分。
采样:控制模型怎么从概率分布里挑下一个 token
模型每生成一个 token,本质上是一个三步过程:
- 前向传播得到 logits(一个长度 ≈ 50000~200000 的向量,词表多大就多长)
- 把 logits 过 softmax 得到概率分布
- 从分布里按规则采样一个 token
下面三个参数控制第 2 步和第 3 步。
temperature(OpenAI / DeepSeek 默认 1,范围因供应商而异)
控制采样的"锐度"。它把 logits 在 softmax 之前除以一个温度系数 T:
softmax(logits / T)
T 越小,logits 被放大,softmax 输出越接近 one-hot(one-hot 指只有一个位置接近 1、其余接近 0 的向量),模型几乎总是选概率最高的那个 token——输出确定但单调。T 越大,logits 被压平,分布更接近均匀,低概率 token 也有被选中的机会——输出更发散但容易偏离语义。
数学上 T 还有两个极限:
- T → 0+——分布退化成 one-hot,等同于直接 argmax(贪心选最高分 token)。T = 0 在实现里通常被特例处理(除以 0 会爆),框架内部直接走贪心
- T → ∞——分布变成完全均匀,每个 token 概率都是
1/V(V 是词表大小),输出接近随机字符流
直观对应:
temperature=0——每次都贪心选最高概率 token(贪心指每一步都选当前最优,不考虑长远)。同一输入基本是同一输出temperature=1——按模型输出的原始概率分布采样,不做缩放temperature>1——拉平分布,低概率 token 选中机会上升
按业务场景的取值经验:
- 信息抽取、分类、Function Calling:设
0(要求确定性) - 代码生成:
0到0.3 - 日常对话、摘要:
0.5到0.7 - 创意写作、头脑风暴:
0.8到1.2 >1.5实战中很少用,输出容易偏离语义
各家 API 的实际允许范围并不统一。常见服务商对比:
| 服务商 | temperature 范围 | 默认值 | 备注 |
|---|---|---|---|
| OpenAI(GPT-3.5 / 4 / 4o) | 0 ~ 2 | 1 | 超 2 直接返回 400 Bad Request |
| DeepSeek | 0 ~ 2 | 1 | 完全兼容 OpenAI 规范 |
| Qwen / 通义千问 | 0 ~ 2 | 1 | DashScope 也是这个范围 |
| Google Gemini(1.5+ / 2.x) | 0 ~ 2 | 1.0 | |
| Mistral La Plateforme | 0 ~ 1.5 | 0.7 | 上限略低 |
| Anthropic Claude | 0 ~ 1 | 1.0 | 设计上拒绝 >1,Anthropic 认为高温对实际应用没意义 |
| OpenAI 推理模型(o1 / o3 系列) | 不支持 | — | 推理模型有自己的内部采样策略,外部传 temperature 会报错 |
| HuggingFace transformers(自托管) | 任意正数 | 1.0 | 研究场景可设到 5、10,但输出基本是噪声 |
跨供应商切换时要小心——同一段调 OpenAI 的 temperature=1.2 代码搬到 Claude 上会报 temperature must be between 0 and 1,必须按目标 API 的上限做兼容。
temperature=0 在某些实现里仍有微小随机性(GPU 浮点不严格确定),如果完全要可复现,配合下面的 seed,并记录返回的 system_fingerprint(只要 fingerprint 没变,同 seed + 同输入产出基本一致;fingerprint 变了说明后端模型迭代了,结果会变化)。
top_p(0~1,默认 1)
另一种裁剪分布的方式,叫 nucleus sampling(核采样)。top_p=0.9 的意思是:
先把所有 token 按概率从高到低排序,取累积概率达到 0.9 的最小集合,只在这个集合里采样。
也就是说,低概率的"长尾"token 直接被砍掉,只在"高概率核"里采样。
它跟 temperature 是两个独立维度——temperature 调整分布的形状,top_p 切掉分布的尾巴。两者可以同时用,但工程上调其中一个即可。OpenAI 官方文档也建议二选一。多数情况调 temperature,top_p 留默认 1。
seed(整数,可选)
固定采样的随机种子。同样输入 + 同样 seed 期望产生同样输出。
它不是"严格保证"——GPU 上的浮点运算非确定性、模型版本更新、底层 kernel 优化都可能让同一 seed 跑出微小差异。它的真实定位是"尽力而为的可复现",对调试和单元测试有用,对生产端的强一致性需求别依赖它。
返回结果里会带 system_fingerprint 字段,只要 fingerprint 不变,相同 seed 输出基本一致;fingerprint 变了(后端模型版本更新)意味着结果会变,要重新验证。
长度控制:限制模型最多生成多少
max_tokens / max_completion_tokens
限制单次调用输出的 token 数上限。默认值通常很大(几千到一两万),不设的话有两个风险:
- 模型被诱导写超长内容,导致单次调用成本失控
- 触达模型上下文上限被强制截断在奇怪位置
工程上建议总是设一个合理上限。例如抽取任务设 max_tokens=500 通常足够,对话设 2000,长文摘要设 4000。
注意:
- 它限制的是 输出,不是输入。输入太长要看模型的
max_position_embeddings(一般和"context window"对应) - 如果输出真的被截断,最后一个 token 可能是不完整的中文字符(
utf-8多字节没切完整)或半句话。要在客户端加防御逻辑——例如检查finish_reason == "length"时再追加一次调用让模型续写 - 较新的 OpenAI o 系列把参数名改成了
max_completion_tokens,语义一样。原因是 o 系列内部有"思考 token",原max_tokens的含义模糊了
流式:把输出按 token 推回客户端
stream(布尔,默认 False)
打开后服务端会持续推送 token,而不是等全部生成完再一次性返回。底层是 HTTP 长连接 + SSE 协议。
收到的是一连串 chunk,每个 chunk 含一小段新 token:
stream = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": "讲个故事"}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
关键事实:流式和非流式在计费上完全一致——区别只在用户感知到的延迟和可中断性。生产里如果是聊天界面建议开流式,首字延迟从几秒降到几百毫秒。
stream_options(对象,可选)
最常用的是 {"include_usage": True}——让流式模式下最后一个 chunk 携带 token 用量统计。默认流式不返回 usage,做成本监控时建议加上:
stream = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
stream=True,
stream_options={"include_usage": True},
)
usage = None
for chunk in stream:
if chunk.usage:
usage = chunk.usage # 最后一个 chunk 才有
delta = chunk.choices[0].delta.content if chunk.choices else None
if delta:
print(delta, end="", flush=True)
print(f"\n[usage] input={usage.prompt_tokens} output={usage.completion_tokens}")
结构化输出:强制 JSON / 强制 schema
response_format
让模型输出符合特定结构而不是自由文本。
{"type": "text"}——默认,纯文本{"type": "json_object"}——保证输出是合法 JSON。但不保证字段结构正确,只是"语法上 parse 得过"。Prompt 里必须出现 "JSON" 这个词,否则模型可能不知道要走 JSON 模式{"type": "json_schema", "json_schema": {...}}(新版 OpenAI 支持)——按提供的 JSON Schema 严格生成。OpenAI 的 SDK 还可以直接传 Pydantic 模型,由 SDK 自动转 schema:
from pydantic import BaseModel
from openai import OpenAI
class Person(BaseModel):
name: str
age: int
email: str | None
client = OpenAI()
resp = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[
{"role": "system", "content": "提取人物信息"},
{"role": "user", "content": "张三今年 28 岁,邮箱 [email protected]"},
],
response_format=Person,
)
person = resp.choices[0].message.parsed
print(person.name, person.age) # 张三 28
.parse 是 SDK 的便捷方法,会自动把模型输出反序列化成 Pydantic 实例。出错(不符合 schema)时抛异常。
非 OpenAI 后端(DeepSeek 等)目前多数只支持 json_object,不支持 json_schema。要做严格 schema 校验只能在客户端用 instructor 库做重试 + 验证。
工具调用:让模型决定调哪个函数
tools
把可以被模型调用的函数列表传进去。每个函数声明遵循 JSON Schema:
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询某个城市的当前天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名"},
},
"required": ["city"],
},
},
},
]
resp = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": "北京现在天气怎么样?"}],
tools=tools,
)
msg = resp.choices[0].message
if msg.tool_calls:
call = msg.tool_calls[0]
print(call.function.name) # get_weather
print(call.function.arguments) # {"city": "北京"}
模型决定调哪个工具、用什么参数。客户端拿到 tool_calls 后实际执行函数,再把结果作为 {"role": "tool", ...} 消息追加回去继续对话。第 07 篇主线展开了完整流程。
tool_choice
控制模型对工具的调用策略:
"auto"(默认)——模型自己决定是否调用工具"none"——禁止调用任何工具,强制走纯文本回答"required"——必须调用某个工具{"type": "function", "function": {"name": "xxx"}}——强制调用指定的某个函数
强制某工具的典型场景:让模型每次必须先调用"意图分类工具",根据返回再决定下一步——把 LLM 当作分类器使用。
惩罚与偏置:调整 token 的出现频率
frequency_penalty(-2~2,默认 0)
对已经在输出里出现过的 token 按出现次数施加惩罚。每出现一次就累加一次惩罚到 logits 上。正值让模型避免重复(防止"非常非常非常..."这种),负值鼓励重复。
presence_penalty(-2~2,默认 0)
类似 frequency_penalty 但不按次数累加,只看是否出现过。正值鼓励引入新话题,负值让模型专注已有话题。
工程上:绝大多数场景这两个都留 0。它们对输出的影响微妙、不直观,调整不当反而比默认更差。要防重复优先改 Prompt("避免重复用词")或降 temperature;要让回答更发散直接调 temperature。
logit_bias(字典,可选)
对特定 token 的 logit 加偏置,强制提升或压制某些 token 被生成的概率:
# 假设我们用 tiktoken 拿到了 "抱歉" 和 "对不起" 的 token id
import tiktoken
enc = tiktoken.encoding_for_model("gpt-4o")
ban_ids = enc.encode("抱歉") + enc.encode("对不起")
resp = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "讲一下今天的新闻"}],
logit_bias={str(tid): -100 for tid in ban_ids}, # -100 约等于禁止
)
+100 约等于强制选中,-100 约等于禁止生成。中间值(±1 到 ±20)是软调节。
实战中很少用——前提是你能拿到 token id(多 token 词要拆开),而且不同模型 tokenizer 不一样、id 各不通用。它的定位是"Prompt 调不动时的最后手段"。
调试用的低层参数
logprobs / top_logprobs
让 API 返回每个生成 token 的对数概率(logprob 是 log probability 的缩写:log(p),p 是概率。因为概率经常很小,取对数后数值更稳定)。
top_logprobs=N 进一步返回每一步前 N 个候选 token 及其概率,能看到"模型当时还考虑过哪些选项":
resp = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "今天是星期"}],
logprobs=True,
top_logprobs=5,
max_tokens=3,
)
for token_info in resp.choices[0].logprobs.content:
print(f"实际生成: {token_info.token!r} logprob={token_info.logprob:.3f}")
for alt in token_info.top_logprobs:
prob = math.exp(alt.logprob) * 100
print(f" 候选 {alt.token!r} {prob:.1f}%")
实战价值:
- 置信度估计:对分类任务,看模型选出 "yes" 的概率有多高(80% 还是 51%)
- 理解模型为什么这么输出:debug 输出"不对劲"时看候选有没有合理的
- 模型对比研究:同样 prompt 在不同模型上看分布形状
非研究/调试场景几乎不用,因为它增加返回数据量、还要客户端处理。
n(整数,默认 1)
一次请求让模型生成 N 个独立的回答(采样多次)。常用于"多路采样取最好"——把同一 prompt 跑 5 次,让另一个模型或评分函数挑最优的那个。
注意:输出 token 计费是 N 倍(输入只算 1 倍,因为只发一次)。推理模型(o 系列)不支持 n。
stop(字符串或字符串列表)
命中这些字符串时立即停止生成。例:
resp = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": "列举 3 个 Python web 框架,每个一行,结束用 END。"}],
stop=["END"],
)
最多支持 4 个 stop 字符串。
推理模型(o 系列 / DeepSeek-R1)的特殊行为
新一代推理模型在采样和参数上跟普通 chat 模型有几个明显不同:
- 不支持
temperature/top_p/frequency_penalty/presence_penalty/logprobs——推理模型有自己的内部采样策略,外部参数无效或报错 - 不支持
n——同上,跟内部多路推理冲突 - 新增
reasoning_effort("low"/"medium"/"high")——控制模型内部思考链的深度。高档位会推理更久、更准、更贵。简单任务用 low 省钱 - 输出会包含
reasoning_content字段(思考过程),但通常不计入最终回复给用户的部分
如果代码里给 o 系列传了 temperature,会直接报错而不是被忽略。生产里如果同时支持普通 chat 模型和推理模型,要做模型判断分别构造参数。
把参数串成场景化的 preset
参数太多,每个调用都从头选很容易不一致。工程上把参数组合按场景固化成 preset 是更稳的做法:
PRESETS = {
"extract": {
"temperature": 0,
"max_tokens": 800,
"response_format": {"type": "json_object"},
},
"chat": {
"temperature": 0.7,
"max_tokens": 2000,
"stream": True,
"stream_options": {"include_usage": True},
},
"creative": {
"temperature": 1.0,
"max_tokens": 3000,
"stream": True,
},
"code": {
"temperature": 0.2,
"max_tokens": 4000,
},
"classify": {
"temperature": 0,
"max_tokens": 50,
"logprobs": True, # 拿到分类置信度
"top_logprobs": 5,
},
}
def chat(messages, scenario: str = "chat", **overrides):
"""按场景挑参数组合,允许临时 override"""
params = {**PRESETS[scenario], **overrides}
return client.chat.completions.create(
model="deepseek-chat",
messages=messages,
**params,
)
# 用法
chat([{"role": "user", "content": "提取这段里的人名"}], scenario="extract")
chat([{"role": "user", "content": "写首关于秋天的诗"}], scenario="creative")
好处:业务代码里不再到处零散调参数,参数集中管理;调优时改一处影响所有同场景调用。
一个常见误解:参数能完全控制行为吗
不能。参数能调的是采样策略和输出长度/格式约束,但模型本身的"判断、推理、知识"不在这里。看到回答不好就调 temperature 是新手常犯的错——多数时候问题在 Prompt、上下文、模型选择,跟参数无关。
排查顺序应该是:
- 输出乱码 / 不合 JSON 格式 → 检查
response_format和 Prompt - 同样输入结果不稳 → 调
temperature到 0 + 设seed - 输出太长被截断 → 调
max_tokens或改 Prompt 让模型简洁 - 输出太短或答非所问 → 改 Prompt、换模型、加 few-shot 示例
- 工具调用不准 → 检查 tool description、
tool_choice、模型支持度 - 重复词太多 → 改 Prompt("避免重复");仍未改善再考虑
frequency_penalty
参数是工程上的最后一公里调节,不是模型能力本身的开关。
相关阅读
- OpenAI Chat Completions API Reference
- DeepSeek API 参数文档
- What is temperature, really?(图示)
- Reasoning models guide(OpenAI 推理模型)
版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。
(采用 CC BY-NC-SA 4.0 许可协议进行授权)
本文标题:番外 2:API 参数全解,temperature 到 logprobs
本文链接:https://www.sshipanoo.com/blog/ai/ai-for-python/番外02-API参数详解/
本文最后一次更新为 天前,文章中的某些内容可能已过时!