50 行 Go 代码,让你看懂大模型到底是怎么「说话」的
🦞 一只用 AI Agent 搭副业产线的程序员
上篇文章结尾我说:下一篇用 50 行 Go 代码让你理解大语言模型怎么工作。
不是标题党。往下看。
省流版
| 概念 | 一句话 |
|---|---|
| LLM 的本质 | 一个「下一个词预测器」——你给它一段文字,它猜下一个字该写什么 |
| Token | 大模型读的不是「字」,是被切成小块的文本片段 |
| 温度 | 控制模型「敢不敢乱说」的旋钮——越低越保守,越高越敢编 |
| 上下文窗口 | 模型的「短期记忆」——超过了就忘了 |
| 这篇文章会给你什么 | 一个能跑的 Go 程序 + 三个概念的直观理解 |
别被数学公式吓住
网上 90% 讲大模型的文章开篇就是 Transformer 架构图、注意力机制公式、多头注意力。看完跟没看一样——你记住了「Q、K、V」三个字母,但完全不知道这跟你调 API 有什么关系。
一个 8 年程序员告诉你:你不需要懂 Transformer 数学,你只需要懂大模型「怎么想」。
剩下那 10% 讲清楚了的文章,都指向一个结论——
大语言模型就是一个「下一个词预测器」(Next Token Predictor)。
你输入「今天天气真」,模型算了一下,觉得接下来最可能出现的字是「好」。于是输出「好」。然后把「今天天气真好」再喂回去,模型继续猜下一个字可能是「,」。循环往复,直到输出一个结束符,一句话就写完了。
这就是 LLM 的全部秘密。
用代码把这件事跑一遍,你比读 10 篇科普文章都有用。
50 行 Go 代码:跟 DeepSeek 对话
先跑起来。用 DeepSeek 的 Anthropic 兼容端点,因为国内访问快、免费额度够你跑实验:
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
)
const baseURL = "https://api.deepseek.com/anthropic"
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"`
System string `json:"system"`
Messages []Message `json:"messages"`
Stream bool `json:"stream"`
}
func main() {
apiKey := os.Getenv("DEEPSEEK_API_KEY")
req := Request{
Model: "deepseek-v4-pro",
MaxTokens: 200,
Temperature: 0.1, // 先设低一点
System: "你是一个程序员助手,回答简洁准确。",
Messages: []Message{
{Role: "user", Content: "用一句话解释 goroutine"},
},
Stream: true,
}
body, _ := json.Marshal(req)
httpReq, _ := http.NewRequest("POST", baseURL+"/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()
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "data: ") {
data := line[6:]
if data == "[DONE]" {
break
}
var event map[string]interface{}
json.Unmarshal([]byte(data), &event)
if delta, ok := event["delta"].(map[string]interface{}); ok {
if text, ok := delta["text"].(string); ok {
fmt.Print(text)
}
}
}
}
}
就这么点代码。一个 struct 定义请求、一个 HTTP 调用、一个 SSE 流解析——没了。
你会发现三件事:
- 你没传「怎么思考」:代码里没有告诉模型该选什么词、怎么推理。模型自己会。
- 你只控制了三个参数:
Model(用哪个模型)、MaxTokens(最多输出多少)、Temperature(随机性旋钮)。 - 除此以外你什么都控制不了:模型输出的每个字都是它自己「算」出来的,你只能通过 Prompt 引导它。
这就是 LLM 的调用本质:输入文本 + 参数 → 输出文本。跟你调一个数据库、调一个 Redis 没有本质区别——输入一个查询,返回一个结果。只不过这个「数据库」算的不是索引,是概率。
第一个概念:Token——大模型不读「字」
你把「解释 goroutine」发给了 DeepSeek。但模型接收到的不是这 5 个字。
它看到的是:
解释 -> [token_12345]
gor -> [token_67890]
outine -> [token_24680]
Token 是模型的最小输入单位。一个中文字通常 1-2 个 token,英文字母 1 个 token。模型不认识「字」——它只认识 token ID。
这为什么重要?
因为模型按 token 计价,也按 token 限制输入输出。你写 Prompt 的时候觉得自己只写了一句话,实际上可能已经烧了 200 个 token。
写一段 Go 代码,粗略估算 token 数:
func countTokens(text string) int {
// 简单估算:英文按空格分词,中文按字符
// 实际 tokenizer 更复杂,但这个粗略换算够用了
chineseChars := 0
for _, r := range text {
if r >= 0x4E00 && r <= 0x9FFF {
chineseChars++
}
}
englishWords := len(strings.Fields(text))
// 中文约 1.5 字符/token,英文约 1 词/token
return chineseChars*2/3 + englishWords
}
你不一定要算得很准。但你得知道:你的 Prompt 不是免费的。每次调用都在花钱。 这就是为什么后面讲 RAG 和 Agent 的时候,我会反复强调「上下文窗口预算」——你不是在聊天,你是在花钱跟模型聊天。
第二个概念:Temperature——模型的「创作欲」
把上面代码里的 Temperature 分别设成 0.1、0.8、1.5,调三次同一个问题:「用一句话解释 goroutine」。
Temperature = 0.1 (保守):
"Goroutine 是 Go 语言中由运行时管理的轻量级线程,通过 go 关键字启动。"
→ 像教科书。每次跑结果几乎一样。
Temperature = 0.8 (适中):
"Goroutine 就像一个可以随手开的小工具,Go 帮你调度,你只管写逻辑就行。"
→ 有人味了。每次跑略有不同。
Temperature = 1.5 (狂野):
"Goroutine 是 Go 语言给你的魔法棒——轻轻一挥,成千上万个并发任务像蚂蚁一样工作起来。"
→ 开始写诗了。每次结果差异很大。
温度到底在控制什么?它在控制模型选词时「敢不敢选概率低的那个」。
模型每步都会给所有可能的词打分。温度低 → 模型只看高分词,选最稳的那个。温度高 → 模型开始考虑中低分词,输出变得有创意但不可控。
简单记:
| 温度范围 | 效果 | 适合 |
|---|---|---|
| 0.0 - 0.3 | 确定性输出,每次一样 | 代码生成、数据提取、翻译 |
| 0.3 - 0.8 | 有变化但可控 | 日常对话、内容润色 |
| 0.8 - 1.2 | 创意但不可靠 | 脑暴、写小说、起名 |
| 1.2+ | 胡说概率显著上升 | 不推荐生产使用 |
一个踩过的坑:我一开始把所有任务的温度都设成 0.7。后来发现代码生成在 0.7 下偶尔会发明不存在的 API——温度一降到 0.1 就好了。不是模型不行,是我参数不对。
第三个概念:上下文窗口——模型的「短期记忆」
把上面代码里的 Messages 改成多轮对话:
Messages: []Message{
{Role: "user", Content: "我叫小王,我是后端开发。"},
{Role: "assistant", Content: "好的小王,了解了。"},
{Role: "user", Content: "我刚才说我叫什么?"},
}
模型能答出来「小王」。因为「我叫小王」这段对话还在上下文窗口里。
现在想象你跟模型聊了 100 轮。前面的对话超出了上下文窗口——模型就「忘」了。它不是没记住,是看不见。
不同模型的上下文窗口:
| 模型 | 上下文窗口 | 约等于 |
|---|---|---|
| DeepSeek V4 | 128K tokens | ~10 万字,一本中篇小说 |
| Claude 4 | 200K tokens | ~15 万字 |
| GPT-4o | 128K tokens | ~10 万字 |
10 万字听起来很多。但如果你在做 RAG——把相关文档都塞进 Prompt 让模型参考——随随便便就能烧掉 5 万 token。
这就是为什么「上下文窗口管理」是 Agent 开发的核心问题。 不是模型不够聪明,是它的短期记忆有限。
整件事最反直觉的一点
你会觉得 LLM 「懂」你在说什么。
但它不懂。它做的事就是:拿到你输入的 token 序列 → 算一遍 → 输出下一个最可能的 token → 再算一遍 → 再输出。每一步都没有「理解」,只有概率计算。
那为什么看起来这么聪明?
因为它在训练时读了几万亿字的文本。你问的问题、你要的格式、你想的表达方式——它都「见过」。它的聪明不是来自思考,是来自「见过足够多」。
理解这一点不是为了让你鄙视 LLM,是为了让你更好地用它。
- 你不理解 LLM,就会给它提不合理的期望——「帮我想一个从来没人做过的新产品」
- 你理解了 LLM,就会知道它擅长的是——「帮我把我已经有的想法,用更好的方式表达出来」
你现在知道的东西,超过 80% 自称「懂 AI」的人
三个概念:Token、Temperature、Context Window。
你会发现所有 AI 对话的 Bug——为什么有时答非所问、为什么有时胡编乱造、为什么有时不记得你刚才说了什么——都在这三个概念里。
下一篇我会把这三个概念做成对照实验:同一个 Prompt,调不同温度、换不同模型,让你直观看到输出的差异。数据说话,不是玄学。
关注我,别错过。
🦞 一只用 AI Agent 搭副业产线的程序员
全平台同名:虾哥不加班
50 行源码可运行:保存为
main.go,设好DEEPSEEK_API_KEY,go run main.go即可
更多推荐
所有评论(0)