本系列共 10 阶段 ,面向有 Go 后端基础的开发者,全程用 Go 手写实现。
当前进度:阶段一 大模型基础 · 第 5 课
上一篇:Prompt 与消息角色
前面我们已经学习了 LLM 的基本原理、Token、上下文窗口和 Prompt。这一课继续学习一个非常重要的主题:如何控制大模型生成答案的方式。
在实际开发中,你可能会发现,同一个问题交给同一个模型,有时回答很严谨,有时回答很有创意,甚至每次表述都不一样。这与模型的生成机制和相关参数有关。
本课先理解原理,再用 Go 代码模拟参数配置,为后续真实 API 调用做准备。
一、本课学习目标
学完之后,你应该能够:
- 解释 Temperature 的作用,以及它为什么会影响回答的随机性。
- 理解 Temperature、Top-P、最大输出 Token 数之间的区别。
- 根据技术问答、JSON 输出、创意写作等场景选择合适的参数。
- 理解为什么降低 Temperature 不能彻底消除幻觉。
- 使用 Go 设计一份清晰的模型生成参数配置。
二、为什么需要生成参数?
假设我们正在开发一个 Go 技术助手,用户问:
请解释 Redis 缓存穿透,并给出 Go 后端的解决方案。
模型可能用不同的方式回答:
- 回答 A:直接给出定义、原因、解决方案和代码。
- 回答 B:先用生活中的例子解释,再分析技术原理。
- 回答 C:展开介绍缓存穿透、缓存击穿、缓存雪崩,以及多种架构设计。
这些回答可能都与问题有关,但它们的风格、详细程度和内容组织不同。
我们希望能够对模型的生成过程施加一定控制,而不是完全依赖默认设置。
常见的生成参数包括:
| 参数 | 主要作用 |
|---|---|
temperature |
调节生成时对不同候选 Token 的选择倾向 |
top_p |
限制采样时考虑的候选 Token 范围 |
max_tokens 或类似参数 |
限制本次生成的最大输出 Token 数 |
stop |
指定遇到某些停止标记时结束生成 |
注意:不同模型服务的参数名称、取值范围和支持情况可能不同。这里先学习通用概念,后续接入真实 API 时再以对应服务的官方文档为准。
三、Temperature:控制生成的随机性
1. 先理解模型如何选择下一个 Token
假设模型已经生成了下面这句话:
Go 语言适合用于......
接下来,模型可能考虑多个候选 Token。为了方便理解,我们使用一组虚构的示意概率:
| 候选 Token | 示意概率 |
|---|---|
| 后端开发 | 60% |
| 网络服务 | 25% |
| 游戏开发 | 10% |
| 其他内容 | 5% |
以上仅用于演示,不代表任何真实模型的概率分布。
如果模型总是选择概率最高的候选项,生成结果通常会更偏向高概率路径。
但大模型的生成并不一定每一步都选择最高概率的 Token。实际系统可以通过采样等方法,在多个候选项之间进行选择。
Temperature 就是影响这个选择过程的参数之一。
2. Temperature 的工作原理
在常见的采样实现中,模型会先得到候选 Token 的分数(通常称为 logits),然后通过 Temperature 调整这些分数的相对差异,再转换成概率分布。
可以用下面的流程理解:
text
模型计算候选 Token 的分数(logits)
↓
Temperature 调整分数差异,重新转换成概率分布
↓
按调整后的分布采样,选出下一个 Token
↓
把 Token 追加到上下文,继续生成下一个
简单记忆:
- 较低的 Temperature:分布通常更加集中,高概率候选项更占优势。
- 较高的 Temperature:分布通常更加平缓,低概率候选项也更有机会被选中。
这不是简单地给回答添加随机字符,而是影响模型在生成过程中选择后续 Token 的倾向。
3. 不同 Temperature 有什么区别?
下面用三个典型取值理解。它们是概念示例,并不代表所有模型都支持完全相同的范围。
| 典型取值 | 随机性 | 说明 |
|---|---|---|
0.0 |
低 | 更倾向于高概率候选项。适合严谨的技术解释、分类和结构化任务,但不保证每次输出绝对相同,也不保证答案正确。 |
0.7 |
中 | 在稳定性与表达变化之间取得一定平衡。可以作为一般对话的实验起点,但不是所有模型的最佳默认值。 |
1.5 |
高 | 在支持这一范围的模型上,生成内容可能更有变化,适合探索不同创意;也可能更容易偏离要求。 |
这里有两个必须记住的结论:
- Temperature 影响的是生成倾向,不是模型的知识水平。
- 降低 Temperature 不会让模型自动获得最新知识,也不能保证它不产生幻觉。
例如,模型不知道某个 Go 库的新版本 API。把 Temperature 从 0.7 调成 0,并不会凭空让它知道正确的 API。
要解决这个问题,通常需要提供可靠的资料、检索官方文档、进行结果验证,或者限制模型只能使用经过确认的信息。
四、Top-P:另一种控制候选范围的方法
Temperature 调整概率分布,Top-P 则主要控制采样时考虑的候选范围。
假设候选 Token 按概率从高到低排列:
| 候选项 | 概率 |
|---|---|
| A | 60% |
| B | 25% |
| C | 10% |
| D | 5% |
如果设置 top_p = 0.85,按照常见的 nucleus sampling(核采样)方式,系统会选取累计概率达到阈值所需的最小候选集合。
这里 A 的概率是 60%,A+B 的累计概率是 85%,因此示意情况下只保留 A 和 B:
| 候选项 | 概率 | 累计概率 | top_p = 0.85 是否纳入 |
|---|---|---|---|
| A | 60% | 60% | ✅ 纳入候选 |
| B | 25% | 85% | ✅ 纳入候选 |
| C | 10% | 95% | ❌ 排除 |
| D | 5% | 100% | ❌ 排除 |
这是简化示意:假设概率分布已经归一化,且未经过 Temperature 调整。实际采样过程通常会先结合其他生成设置,再决定最终候选集合。
有两个注意事项:
- Top-P 是基于候选项的累计概率来限制范围,不是"只选概率最高的前 P 个 Token"。
- Temperature 和 Top-P 都可能影响输出的多样性。实际开发中,建议先调整一个参数、观察效果,不要一开始同时调很多参数。
五、最大输出 Token 数:控制回答长度
假设你调用模型,希望它用 Go 解释 Redis 缓存穿透,并给出代码。
如果最大输出 Token 数太小,模型可能只输出定义,还没来得及给出代码就结束了。
例如:
- 设置较小的输出上限:回答可能不完整。
- 设置合理的输出上限:可以容纳原理、代码和解释。
- 设置过大的输出上限:不代表模型一定会生成更多内容;但可能增加可用输出预算或成本风险,具体取决于服务的计费和限制方式。
需要区分两个概念:
| 概念 | 含义 |
|---|---|
| 上下文窗口 | 一次请求能够处理的整体 Token 容量,通常涉及输入和输出 |
| 最大输出 Token 数 | 对本次生成结果长度设置的上限 |
举个例子:某模型的上下文窗口为 16K Token,输入已经占用 12K,最大输出设置为 8K。
不能因此认为它一定能生成 8K Token。输入和输出需要共同满足模型及服务的上下文限制,实际还可能存在额外的系统开销或服务端限制。
此外,Token 数不等于中文字符数,也不等于 Go 字符串的字节数。前面课程已经讲过这个区别。
六、其他常见生成参数
Stop:遇到指定标记时停止
某些 API 支持 stop 参数,用来指定一个或多个停止字符串。
例如,你希望模型生成某种特定格式,并在遇到特定标记后停止,就可以研究服务是否支持停止序列。
但要注意:
stop的具体行为依赖模型服务。- 不同接口可能有不同的限制。
- 对 JSON 输出,不能随意设置停止字符串,否则可能把 JSON 截断成无法解析的内容。
不要为了"让模型尽快结束"就随便加一个停止标记。首先要确保它不会破坏输出格式。
七、Go 后端如何设计生成参数?
目前我们还没有进入真实模型 API 调用阶段,所以先不假设任何厂商的请求格式。我们用 Go 定义一份自己的配置,理解后端如何管理这些参数。
1. 完整示例
新建 main.go:
go
package main
import (
"encoding/json"
"fmt"
)
type GenerationConfig struct {
Temperature float64 `json:"temperature"`
TopP float64 `json:"top_p"`
MaxOutputTokens int `json:"max_output_tokens"`
}
func main() {
// 技术问答场景的示例配置
config := GenerationConfig{
Temperature: 0.2,
TopP: 0.9,
MaxOutputTokens: 2048,
}
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
fmt.Println("序列化配置失败:", err)
return
}
fmt.Println(string(data))
}
运行:
bash
go run main.go
预期输出:
json
{
"temperature": 0.2,
"top_p": 0.9,
"max_output_tokens": 2048
}
这段代码可以直接运行,但要明确:它只是在 Go 中定义并序列化配置,没有调用真实的大模型。max_output_tokens 也是我们自己定义的字段名,不保证任何服务都接受这个字段。
2. 逐段理解代码
第一部分:定义配置结构体
go
type GenerationConfig struct {
Temperature float64 `json:"temperature"`
TopP float64 `json:"top_p"`
MaxOutputTokens int `json:"max_output_tokens"`
}
这里使用 struct 将多个相关参数组织在一起。
Temperature使用float64,因为它可能是小数。TopP使用float64,因为它通常也是小数。MaxOutputTokens使用int,因为输出 Token 上限是整数。- JSON Tag 指定序列化时的字段名称。
这和你熟悉的 Go 后端配置设计类似:将相关配置集中管理,而不是散落在业务代码中。
第二部分:根据业务场景创建配置
go
config := GenerationConfig{
Temperature: 0.2,
TopP: 0.9,
MaxOutputTokens: 2048,
}
这份配置是针对技术问答的一个实验起点,不是普遍适用的最佳参数。
真实项目中,你还需要考虑模型自身支持的参数、业务要求、输出格式以及调用成本。
第三部分:序列化
go
data, err := json.MarshalIndent(config, "", " ")
json.MarshalIndent 将 Go 结构体转换成格式化的 JSON。
当我们接入真实 API 时,可以在适配层将内部配置转换成服务要求的请求格式。但不能假设不同厂商的 API 字段完全相同。
3. 在实际项目中如何选择参数?
以你后续要开发的 Go AI 技术助手为例:
| 业务场景 | 参数选择思路 |
|---|---|
| Go 技术原理问答 | 可以先试较低 Temperature,重点保证回答结构清晰 |
| 生成严格 JSON | 优先使用 API 支持的结构化输出能力,并校验结果 |
| 生成代码 | 适度控制随机性,同时进行编译和测试验证 |
| 技术文档总结 | 以稳定、准确、完整为优先目标 |
| 创意文案生成 | 可以尝试提高 Temperature,比较不同结果 |
| Agent 调用工具 | 重点保证工具参数符合 Schema,并由 Go 后端校验;不能仅靠 Temperature 保证安全 |
特别注意:即使 Temperature 设置为 0,模型仍然可能给出错误的 Go 代码、错误的 API 名称或不正确的技术结论。
对于技术助手,真正可靠的设计应当是:
- 通过 Prompt 明确任务和输出要求。
- 必要时检索官方文档或可信知识库。
- 对 JSON 和工具参数进行程序校验。
- 对代码执行编译、测试等验证。
- 对工具调用实施权限控制。
模型参数是调节手段,不是可靠性保障机制。
八、本课总结
本课的核心知识可以归纳为:
- Temperature:影响生成概率分布的集中程度,进而影响采样的随机性。
- Top-P:通过累计概率阈值限制候选 Token 范围。
- 最大输出 Token 数:限制本次生成的长度,不代表模型一定会生成这么多 Token。
- Stop:在满足特定停止条件时结束生成,必须避免破坏输出格式。
- 工程实践:按场景选择参数、用实验验证效果,并在 Go 后端实施校验。
九、课后练习
练习 1:Temperature
同一个模型回答 Go 技术问题时,你将 Temperature 从 0.2 调整到 1.2。通常会有什么变化?为什么不能因此认定 1.2 的答案更准确?
参考思路:调高后概率分布更平缓,低概率候选也有机会被选中,表现为措辞更多变、展开路径更发散,同时偏离要求(跑题、编造)的概率也上升。它改变的是"怎么选词",不改变模型掌握的知识,所以和"准不准确"没有因果关系------提高 Temperature 只会让答案更难复现,便于对比实验,不能提升事实正确性。
练习 2:参数选择
你正在开发一个 Go 接口,需要模型返回以下 JSON:
json
{
"category": "redis",
"difficulty": "beginner",
"summary": "缓存穿透的解释"
}
你会重点关注哪些生成参数或 API 能力?为什么即使 Prompt 写得非常明确,也仍然需要 Go 后端校验?
参考思路 :优先用服务端的结构化输出 / JSON Schema 能力,而不是靠 Prompt 描述格式;Temperature 压低到接近 0;给输出留够
max_tokens避免截断;不要随意设stop,容易把 JSON 切断。后端校验不能省,因为 Prompt 是"请求配合",模型仍可能多带 ```json 围栏、漏字段、把枚举值写成同义词。校验放在json.Unmarshal之后,检查字段非空和枚举白名单。
练习 3:Token 限制
模型上下文窗口为 16K Token,当前请求的输入约为 12K Token。你把最大输出 Token 数设置为 8K,是否意味着模型一定可以完整输出 8K Token?请说明原因。
参考思路 :不一定。输入 12K + 输出 8K = 20K,已经超过 16K 窗口,服务端可能直接拒绝、也可能截断输出。
max_tokens是上限不是承诺,实际可用输出 ≈ 窗口 − 输入 − 系统开销。正确做法是让输入与输出之和留出余量,或先裁剪历史再调大输出上限。
下一篇:Go AI Agent 实战 06|JSON 结构化输出与 Go 后端校验