Token、温度、上下文窗口:三个概念一次吃透
🦞 一只用 AI Agent 搭副业产线的程序员
上篇我们写了一个 50 行的 Go 程序跟 DeepSeek 对话。你肯定注意到了代码里三个参数:MaxTokens、Temperature,还有 Messages 那个会越来越长的数组。
这三个东西,说简单也简单——网上一搜一大堆解释。但我发现大部分文章讲完定义就停了,你没有「手感」。
这篇文章不一样。我们做实验。
实验环境:10 行代码搭一个测试台
先把测试台搭好,后面所有实验都在这个基础上跑:
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
)
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
type Request struct {
Model string `json:"model"`
MaxTokens int `json:"max_tokens"`
Temperature float64 `json:"temperature"`
Messages []Message `json:"messages"`
}
func callLLM(messages []Message, temp float64, maxTokens int) string {
apiKey := os.Getenv("DEEPSEEK_API_KEY")
req := Request{
Model: "deepseek-v4-pro",
MaxTokens: maxTokens,
Temperature: temp,
Messages: messages,
}
body, _ := json.Marshal(req)
httpReq, _ := http.NewRequest("POST",
"https://api.deepseek.com/anthropic/v1/messages",
bytes.NewReader(body))
httpReq.Header.Set("x-api-key", apiKey)
httpReq.Header.Set("anthropic-version", "2023-06-01")
httpReq.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(httpReq)
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
// 简化处理,生产环境请用 SSE 流式
if content, ok := result["content"].([]interface{}); ok && len(content) > 0 {
if text, ok := content[0].(map[string]interface{})["text"].(string); ok {
return text
}
}
return ""
}
行了,一套通用工具函数。接下来的实验,代码都基于这个。
实验一:Token——你以为在写字,其实在烧钱
我问你一个问题:「今天天气真好」是几个字?
6 个字。但对 AI 来说不是。
DeepSeek 看到的实际是:
今天 -> [token_8843]
天气 -> [token_16782]
真 -> [token_99]
好 -> [token_1962]
中文一个字 1-2 个 token,一个英文单词 1-2 个 token。你写 Prompt 觉得只打了 100 个字,实际上可能已经烧了 150 个 token。
为什么你要关心这个?因为按 token 计价。
| 模型 | 输入价格(每 1M token) | 输出价格(每 1M token) |
|---|---|---|
| DeepSeek V4 Pro | ¥1 | ¥4 |
| DeepSeek V4 Flash | ¥0.3 | ¥1.2 |
| GPT-4o | ¥35 | ¥140 |
| Claude Opus 4 | ¥105 | ¥420 |
一个稍微认真的 Prompt 加上上下文文档,轻松上万 token。如果用的是 GPT-4o,一两次调用就是几毛钱。一个 Agent 循环 10 轮,几块钱就没了。
动手试一下:
func main() {
shortPrompt := "你好,请用一句话解释什么是递归。"
longPrompt := shortPrompt + strings.Repeat("请务必详细解释,", 100)
// 一不留神就加了 1000 个中文字 ≈ 1500 token
}
这个意识不是你今天学了明天就精通的。但下次你写 Prompt 的时候,脑子里会有一个声音:「这行字不是免费的。」
后面讲 RAG 和 Agent 的时候,你会深刻理解为什么这是 Agent 开发最核心的资源管理问题。
实验二:温度——同一个问题,三个答案
温度是最容易被误解的参数。很多人以为温度越高越「聪明」,但实际上温度跟智商没关系——它只控制一样东西:模型敢不敢选「不太确定」的下一个词。
我们跑一个实验。同一个问题,用三个温度各跑 3 次:
func main() {
prompt := []Message{
{Role: "user", Content: "用一句话解释什么是 goroutine,不要比喻。"},
}
for _, temp := range []float64{0.0, 0.7, 1.5} {
fmt.Printf("\n=== Temperature = %.1f ===\n", temp)
for i := 0; i < 3; i++ {
result := callLLM(prompt, temp, 100)
fmt.Printf("第%d次: %s\n", i+1, result)
}
}
}
实际跑出来的结果(有删减):
=== Temperature = 0.0 ===
第1次: Goroutine 是 Go 语言中的轻量级线程,由 Go 运行时调度管理。
第2次: Goroutine 是 Go 语言中的轻量级线程,由 Go 运行时调度管理。
第3次: Goroutine 是 Go 语言中的轻量级线程,由 Go 运行时调度管理。
→ 三次完全一样,像教科书。
=== Temperature = 0.7 ===
第1次: Goroutine 是 Go 语言中由运行时管理的轻量级并发单元。
第2次: Goroutine 是 Go 的轻量级线程,通过 go 关键字启动,由运行时调度。
第3次: Goroutine 是 Go 运行时管理的轻量级线程,比操作系统线程更轻。
→ 意思一样,措辞不同,每次略有变化。
=== Temperature = 1.5 ===
第1次: Goroutine 是 Go 语言的并发原语,它是一个由运行时管理的轻量级执行单元。
第2次: Goroutine 可以理解为 Go 语言内置的轻量级线程,由协程调度器管理。
第3次: 在 Go 语言中,goroutine 是实现并发的核心机制,它是一个轻量级的执行线程。
→ 开始换着花样说,偶尔用词不够精确。
一个我踩坑的经验:
我做那个日报 Agent 的时候,一开始默认温度 0.7。结果生成的周报偶尔会用「该同志表现优异」这种莫名其妙的措辞。降到 0.1 之后,输出稳定了,每天都一样的格式。
温度选错了不是「质量差一点」,是能不能用的区别。
实验三:上下文窗口——AI 的「金鱼记忆」
金鱼的记忆据说只有 7 秒。AI 模型的记忆取决于你花了多少钱买的上下文窗口。
做一个最直观的实验——故意让 AI 忘了你叫什么:
func main() {
messages := []Message{
{Role: "user", Content: "我叫张小华,是一名 Go 后端开发。"},
{Role: "assistant", Content: "好的张小华,记住了。"},
}
// 插入 50 轮无关对话,模拟「聊了很久」
for i := 0; i < 50; i++ {
messages = append(messages,
Message{Role: "user", Content: fmt.Sprintf("第%d个问题:Go 的 defer 执行顺序是什么?", i)},
Message{Role: "assistant", Content: fmt.Sprintf("第%d个回答:defer 按后进先出(LIFO)顺序执行。", i)},
)
}
// 最后问一个最初才知道答案的问题
messages = append(messages,
Message{Role: "user", Content: "我还记得我刚才说我叫什么吗?"},
)
result := callLLM(messages, 0.1, 100)
fmt.Println(result)
}
如果你把 50 改成 500——当最早的对话被挤出上下文窗口时——AI 会说:「抱歉,你没有告诉过我你的名字。」
它不是忘了。是「看不见」了。
一个残酷的事实: 你调用 API 的时候,API 不会帮你「记住」任何东西。每一轮对话,你都必须把整段历史重新传过去。第 1 轮传 10 条消息,第 100 轮传 100 条。每轮都在重复烧 token,越烧越多。
这就是为什么后面我们要讲「上下文窗口预算策略」——不管理好窗口,你的 Agent 跑着跑着就破产了。
三个概念的关系:一张表总结
| 概念 | 是什么 | 你会遇到的问题 |
|---|---|---|
| Token | 计费单位,也是信息密度单位 | Prompt 写太长烧钱,写太短信息不够 |
| 温度 | 控制输出的随机性 | 高了不可靠,低了没创意 |
| 上下文窗口 | 模型的「工作记忆」上限 | 聊多了忘记,塞多了贵 |
三个概念互相制约:
- 温度低 + 窗口大 = 可以做精准的长文档分析(如代码审查)
- 温度高 + 窗口小 = 适合简短创意任务(如起名)
- 温度低 + 窗口小 = 适合确定性短任务(如提取数据)
没有「最好」的设置,只有「适合当前任务」的设置。
作业
把上面的测试台代码跑一遍,改三个参数观察输出。
你会发现一个微妙的事:你不会再觉得 AI 很神秘了。 你知道它怎么工作、为什么这么回答、什么情况下可能出错。
下一篇我们做一件狠事——同一个任务,扔给 DeepSeek、通义千问、GPT-4o、Claude 四个模型跑。看谁的代码最靠谱、谁最便宜、谁最会编。数据说话。
关注我,别错过。
🦞 一只用 AI Agent 搭副业产线的程序员
全平台同名:虾哥不加班
需要定制 AI 工具?来聊聊 → lob_ai
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)