内容概要
- 大模型有四个"命运操纵者":
Temperature、Top-p、max_tokens和stop。前两个掌控创造性,后两个决定------回答何时结束。 - 你以为
max_tokens只是"限字数"?错,它只管生成部分,而且设不好会让回答"断气"。 - 至于
stop------很多人觉得它"没用",于是干脆不设。但在结构化输出场景下,不设 stop 等于埋雷。 - 更棘手的是:
max_tokens这把"安全刀"切下去,回答可能被生生截断。生产级应用里,光靠参数不够,你还需要一道 "后处理"保险。
一、max_tokens(最大生成长度)
1. 控制范围
控制模型生成的新 token 数量(而非 prompt + response 的总长度)。
示例:
- 输入 prompt:
"介绍一下北京"(4 个 token) max_tokens = 50- 模型输出:最多 50 个 token(约 30--40 个汉字)
- 总上下文 = 4 + 50 = 54 tokens
2. 常见误区
| 误区 | 真相 |
|---|---|
| 控制总长度(prompt + response) | ❌ 只控制生成部分 |
| 控制字符数 | ❌ 控制 token 数(1 token ≠ 1 字符) |
| 设越大越好 | ❌ 浪费成本、可能跑题、增加延迟 |
| 设太小模型会自动停 | ❌ 会直接截断,可能停在半句话 |
3. Token 与字符换算(粗略)
| 语言 | 1 token ≈ | 示例 |
|---|---|---|
| 英文 | 0.75 个词(4 字符) | "Hello world" → 2 tokens |
| 中文 | 0.5--1 个汉字 | "你好世界" → 3--4 tokens |
| 代码 | 视语言而定 | 空格、缩进都算 |
实测(GPT-4 级别):
"你好"→ 2 tokens"你好,我是助手"→ 6--7 tokens- 100 字中文 → 约 50--80 tokens
4. 设置策略
| 任务类型 | 建议 max_tokens | 理由 |
|---|---|---|
| 分类/判断 | 5--20 | 只需输出标签或短答案 |
| 短问答 | 50--150 | "什么是 X?" 类问题 |
| 摘要/翻译 | 200--500 | 取决于原文长度 |
| 代码生成 | 500--2000 | 函数或完整脚本 |
| 长文写作 | 1000--4000 | 故事、报告、论文段落 |
| 无限制对话 | 不设或设很大 | 成本风险高 |
5. 截断问题及解决
问题示例:
makefile
输入:"讲一个关于猫的笑话"
max_tokens = 10
输出:"有一只猫,它走" ← 话没说完
解决方案:
- 设大一点 + 用
stop提前终止(推荐) - 后处理检测:若输出不以标点/换行结尾,补提示或重试
- 检查
finish_reason:"stop":正常结束(遇到 stop token)"length":被max_tokens截断(需警惕)
二、stop(停止符)
1. 作用
告诉模型:看到这些字符/token 时立即停止生成(停止符本身不包含在输出中)。
示例:
swift
prompt: "列出三个水果:"
stop = ["\n", "。"]
生成过程:
"苹果" → "、" → "香蕉" → "、" → "橙子" → "\n" → 立即停止
输出:"苹果、香蕉、橙子"
2. 常见 stop 设置
| 场景 | stop 设置 | 原因 |
|---|---|---|
| 单行回答 | ["\n"] |
换行表示结束 |
| 中文对话 | ["\n\n", "。\n", "?"] |
双换行或标点+换行 |
| 分点列举 | ["\n\n", "\n数字"] |
防止继续列更多点 |
| 代码生成 | ["\n```", "\ndef ", "\nclass "] |
函数/类边界 |
| 多轮对话 | ["\n用户:", "\nUser:"] |
防止模型扮演用户 |
| 结构化输出(JSON) | ["}"] 或 ["\n}"] |
完整 JSON 后停止 |
3. 特殊技巧
技巧 1:多层 stop
python
stop = ["\n\n", "。", "!", "?", "\n---"]
技巧 2:用 stop 强制格式
python
prompt = """请回答以下问题,每个答案占一行:
1. 首都是哪里?
2. 人口多少?
"""
stop = ["\n\n"]
技巧 3:防止模型继续说话
python
prompt = """用户:你好
助手:您好!
用户:介绍一下你自己
助手:"""
stop = ["\n用户:", "\nUser:"]
4. 坑与注意
| 坑 | 说明 | 解决 |
|---|---|---|
| stop 不精确匹配 | 只匹配 token 边界 | 用常见字符,避免生僻组合 |
| 中英文标点差异 | "." 和 "。" 不同 |
明确指定所有变体 |
| stop 太长无效 | 某些 API 限制长度 | 查看文档 |
| stop 冲突 | 多个同时出现 | 取第一个匹配 |
| 忘记设 stop | 模型可能一直生成 | 必须设 max_tokens 兜底 |
三、max_tokens 与 stop 的配合策略
黄金法则
用 stop 定义"自然结束",用 max_tokens 定义"硬上限"。
python
{
"max_tokens": 500, # 硬上限
"stop": ["\n\n", "。"] # 自然结束条件
}
工作流程
ini
生成 token → 检查 stop → 匹配 → 立即停止(finish_reason="stop")
↓ 不匹配
检查长度 → 达到 max_tokens → 强制停止(finish_reason="length")
↓ 未达到
继续生成
实际例子
python
prompt = "请用三句话总结这篇文章:..."
max_tokens = 200
stop = ["\n\n", "。\n"]
# 好情况:100 token 时遇到 "。",正常结束
# 坏情况:200 token 时还没结束,被截断(需检查 finish_reason)
四、实战建议
调试 Checklist
-
先设 stop,后调 max_tokens
- stop 定义结束模式
max_tokens = stop 预期长度 × 1.5~2
-
检查
finish_reasonpythonif response.choices[0].finish_reason == "length": print("警告:被截断") -
中文场景特别注意
- 用
"。"、"?"、"!"而非"." - 注意全角/半角差异
- 用
-
动态调整
- 频繁截断 → 增加
max_tokens - 过早停止 → 调整
stop条件
- 频繁截断 → 增加
代码模板
python
def safe_generate(prompt, expected_tokens=100):
response = model.generate(
prompt=prompt,
max_tokens=expected_tokens * 2,
stop=["\n\n", "。", "!", "?"],
)
if response.finish_reason == "length":
return response.text + "...(截断)"
return response.text
一句话总结
max_tokens是安全网(硬上限),stop是智能闸门(自然结束)。两者必须配合使用:用
stop定义"什么时候该停",用max_tokens定义"无论如何不能超过多少"。
思考与问题
Q1:设定 stop = ["\n\n", "。\n"],如何确保回答内容不被意外截断?
A1:你无法 100% 保证,但可以通过设计最大化避免。
一、为什么会截断?
stop 是贪婪匹配 ,一旦匹配就立即停止,不会"等说完这句话"。
二、解决方案(6 种策略)
| 策略 | 做法 | 适用场景 |
|---|---|---|
| 1. 更宽松的 stop | 只用 ["\n\n"] |
避免句号+换行触发 |
| 2. 多个 stop + 优先级 | 长模式优先 | 章节、代码块结束 |
| 3. 后处理智能截断 | 生成后按段落切分 | 最安全 |
4. 以 max_tokens 为主 |
不设或宽松 stop | 长度控制优先 |
| 5. 结构化 + 特殊标记 | 模型输出 [END] |
模型自决结束 |
| 6. 多轮生成 + 完整性检查 | 不完整则继续 | 确保完整 |
三、推荐配置
python
config = {
"max_tokens": 800,
"stop": ["\n\n\n", "```\n\n"], # 只设强边界
}
# 后处理
def post_process(text):
if not text.endswith(('。', '!', '?')):
text = text.rsplit('。', 1)[0] + '。'
return text
四、核心结论
stop工作在 token 级别,不理解语义。
原则 :stop 设得越宽松越安全,用max_tokens设硬上限,后处理比 stop 更可靠。
Q2:后处理是在模型端还是调用端处理?
A2:在模型调用端(客户端)处理。
一、职责分离
| 位置 | 职责 | 典型操作 |
|---|---|---|
| 模型端 | 生成文本、应用 stop | 机械字符串匹配 |
| 调用端 | 后处理 | 句子切分、补全、格式化 |
二、调用流程
arduino
调用端 模型端
│ │
├─ 发送请求 ─────────────→│
│ ├─ 生成 tokens
│ ├─ 检查 stop
│←────────────────────────┤ 返回原始输出
├─ 后处理(你的代码) │
├─ 展示给用户 │
三、代码示例
python
response = client.chat.completions.create(...)
raw = response.choices[0].message.content
final = post_process(raw) # 调用端后处理
四、为什么不在模型端做?
- 模型端是通用的,无法预知各应用需求
- 职责单一:只负责生成
- 调用端更灵活,可按需定制
Q3:是否可以认为 stop 用处不大,不设为好?
A3:不能。stop 是场景特定的工具。
一、必须使用 stop 的场景
| 场景 | 原因 |
|---|---|
| 多轮对话/角色扮演 | 防止模型扮演用户 |
| 结构化输出(JSON/代码) | 定义边界 |
| 防止无限生成 | 配合 max_tokens 优雅停止 |
二、不建议使用 stop 的场景
- 开放式问答
- 创意写作
- 摘要生成
三、决策树
arduino
是否需要防止模型越界?
├─ 是 → 必须设 stop
└─ 否 → 是否有明确结构边界?
├─ 是 → 建议设 stop
└─ 否 → 是否可能无限重复?
├─ 是 → 建议设 stop
└─ 否 → 可不设 stop
四、推荐配置
| 场景 | 配置 |
|---|---|
| 通用 | max_tokens=500, stop=None |
| 多轮对话 | max_tokens=300, stop=["\n用户:"] |
| 结构化输出 | max_tokens=1000, stop=["}", "]"] |
| 长文本 | max_tokens=2000, stop=["\n\n\n"] |
结论 :
stop不是"用处不大",而是"场景特定"。不确定时设
stop=None,发现多余内容再加stop。
Q4:stop=None 是默认值吗?
A4:是的。
官方说明
stop参数 optional,默认null- Python 中
stop=None等同于不传
三种等价写法
python
# 1. 不传
response = client.chat.completions.create(...)
# 2. 显式 None
response = client.chat.completions.create(..., stop=None)
# 3. 空列表(部分 API)
response = client.chat.completions.create(..., stop=[])
stop=None 时的行为
| 行为 | 说明 |
|---|---|
| 自然结束 | 模型完成一个完整回答后停止 |
finish_reason |
返回 "stop" |
| 无额外停止条件 | 不会因特定字符串提前停止 |
仍有 max_tokens 保护 |
达到上限时以 "length" 结束 |
Q5:这样理解对吗:stop=None 时模型大多自然结束;若达到 max_tokens 则以 "length" 结束,此时需在调用端后处理以避免截断?
A5:完全正确。这是生产环境的标准做法。
完整处理流程
python
def generate_with_handling(prompt, max_tokens=500):
response = client.chat.completions.create(
...,
max_tokens=max_tokens,
stop=None,
)
text = response.choices[0].message.content
finish_reason = response.choices[0].finish_reason
if finish_reason == "stop":
return text
elif finish_reason == "length":
return post_process_truncated(text)
更优雅的方案:避免截断
- 设置宽松
max_tokens(预期 × 2) - 动态重试:被截断后增加 tokens 重试
- 分段生成:长文本分多次生成
生产级最佳实践
python
class RobustLLMClient:
def generate(self, prompt, expected_tokens=200):
max_tokens = min(expected_tokens * 2, 2000)
response = self.client.chat.completions.create(
...,
max_tokens=max_tokens,
stop=None,
)
if response.choices[0].finish_reason == "length":
return self._repair_truncated(response.text)
return response.text
最终总结
| 情况 | 行为 | 处理方式 |
|---|---|---|
stop=None + 未达上限 |
模型自然结束 | ✅ 直接使用 |
stop=None + 达到上限 |
被截断 | ⚠️ 调用端后处理 |
stop 有值 |
遇到特定字符串停止 | 可能语义完整,也可能过早停止 |
一句话总结 :
stop=None让模型自由发挥,max_tokens兜底保护,finish_reason判断是否截断,调用端后处理修复问题。这是最稳健的生产级策略。