本系列共 10 阶段 ,面向有 Go 后端基础的开发者,全程用 Go 手写实现。
当前进度:阶段一 大模型基础 · 第 2 课
上一篇:AI 应用与传统后端的本质区别
上一课我们理清了传统后端与 AI 应用的区别,这一课开始深入 LLM 本身。
你不需要先学复杂的机器学习数学。我们从 Go 后端工程师的视角出发,先理解模型接收什么、如何生成回答、为什么会出错,以及这些原理如何影响实际的项目设计。
一、本课学习目标
学完本课,你应该能够:
- 解释什么是大语言模型,以及它与普通程序的区别。
- 理解模型为什么可以根据上下文生成回答。
- 理解 Token、概率预测和逐步生成的基本关系。
- 说清楚模型参数、上下文和训练数据不是同一个概念。
- 理解模型为什么会产生幻觉,以及后端应该如何应对。
二、什么是大语言模型?
LLM 是 Large Language Model 的缩写,中文叫大语言模型。
简单来说,它是一种通过大量数据训练、能够根据输入上下文生成文本的模型。
例如,你输入:
Go 语言中,defer 的主要作用是......
模型可能生成:
延迟执行函数调用,通常用于释放资源、关闭文件、解锁互斥锁等场景。
它不是简单地在数据库中搜索一条固定答案,而是根据当前上下文和训练过程中学到的语言、知识及模式,计算接下来生成什么内容更合适。
这里有一个需要注意的区别:
- 训练阶段:模型通过训练数据调整内部参数,学习语言模式及其他统计规律。
- 推理阶段:模型接收当前输入,利用已经训练好的参数生成输出。
当你调用一个在线模型 API 时,通常是在进行推理,而不是重新训练模型。
训练与推理的区别
| 对比项 | 训练(Training) | 推理(Inference) |
|---|---|---|
| 输入 | 大规模训练数据 | 你的 Prompt 与上下文 |
| 做什么 | 通过优化算法调整模型参数 | 使用已训练好的参数计算并生成回答 |
| 谁来完成 | 通常由模型提供方完成 | 你每次调用 API 时都在发生 |
| 成本形态 | 一次性的大额投入 | 按调用量和 Token 计费 |
对于 Go 开发者而言,我们首先要掌握的是推理阶段:如何构造请求、组织上下文、处理响应,以及如何让模型安全地参与业务流程。
三、模型究竟是如何生成文字的?
这是本课最重要的知识点。
我们先用一个简化例子说明。
假设输入:
今天的天气非常
模型需要预测接下来可能出现什么内容。
它可能给候选词对应的 Token 分配不同概率:
| 候选内容 | 示意概率 |
|---|---|
| 好 | 0.45 |
| 热 | 0.25 |
| 冷 | 0.15 |
| 糟糕 | 0.10 |
| 其他 | 0.05 |
注意:这些概率是为了讲解而构造的示意数据,不是任何真实模型的测量结果。
模型可能选择"好",得到:
今天的天气非常好
然后继续预测下一个 Token,例如",",再预测后面的内容。
因此,文本生成可以粗略理解为:
text
输入上下文
↓
计算下一个 Token 的概率分布
↓
根据解码策略选择一个 Token
↓
将它加入当前上下文
↓
再次计算下一个 Token
↓
不断重复,直到满足停止条件
这就是为什么模型能够生成长篇回答:它不是一次性直接输出整篇文章,而是在生成过程中不断预测后续 Token。
严格来说,模型内部会经过多层神经网络计算,概率分布也不是简单地从词典中查表得来。上面的流程只是帮助我们建立直观认识。
四、什么是 Token?为什么模型不直接处理文字?
上一课提到过 Token,这里进一步理解它。
Token 是模型处理文本时使用的基本单位。它可能对应一个完整的单词、汉字、词语的一部分,或者标点符号。
例如,下面的文字:
text
Go is powerful.
可能被拆分为:
text
["Go", " is", " powerful", "."]
这只是一个示意拆分,实际结果取决于模型使用的 Tokenizer(分词器)。
中文也一样。例如:
text
我正在学习 Go
可能被拆分为多个 Token,但不能简单认为一个汉字就一定对应一个 Token。
Token 在模型中扮演什么角色?
模型通常先把文本转换为 Token ID,再将这些 ID 转换成模型内部可以处理的数值表示,经过神经网络计算后生成输出 Token,最后将其还原为文字:
text
原始文本「我正在学习 Go」
↓
Tokenizer ------ 把文本切分成 Token,并转换为 Token ID
↓
模型计算 ------ 利用上下文与模型参数计算后续 Token
↓
输出 Token ------ 经过解码后转换为可阅读的文本
这对实际开发有什么影响?
第一,Token 数量不等于字符数量。 不能简单地用字符串长度来准确计算模型输入成本。
第二,Token 会影响上下文容量。 用户问题、系统提示词、历史对话以及模型输出都可能占用上下文空间。
第三,Token 可能影响费用。 很多模型服务会分别计算输入 Token 和输出 Token 的用量,具体计费规则以服务商为准。
后续我们会专门学习 Token 与上下文窗口,不必现在就掌握具体的计算方法。
五、为什么模型有时能回答问题,有时却会犯错?
我们先理解三个核心原因。
1. 模型学习的是参数化模式,而不是一个绝对正确的知识数据库
模型训练时,会通过大量样本调整内部参数,让它能够预测和生成符合上下文的内容。
它学到的能力可能包括:
- 语言表达和语法结构。
- 常见事实及概念之间的关联。
- 代码结构和编程模式。
- 根据上下文进行一定程度的推理与归纳。
但模型参数不是一份可以逐条查询、保证绝对准确的数据库。
例如,你询问某个 Go 库最新版本的 API,如果模型缺少相关信息,就可能给出过时的接口,甚至生成一个看起来合理、实际上不存在的方法。
2. 生成合理的文字,不等于保证事实正确
模型的目标是根据上下文生成最合适的后续内容,但这种生成能力并不自动保证事实准确。
例如:
请介绍 Go 标准库中的
http.NewAgent()方法。
如果这个方法实际上不存在,模型仍可能生成一段看似专业的解释。
这类现象通常称为模型幻觉(Hallucination)。
应对方法不是单纯地要求模型"不要犯错",而是采用工程措施:
- 让模型使用可靠的资料。
- 对代码和结构化输出进行验证。
- 对关键事实提供可核查的来源。
- 对高风险操作增加权限检查与人工确认。
- 无法确定时允许系统明确返回不确定结果。
3. 模型不一定拥有你需要的实时信息
假设你今天在 Blogo 中新增了一篇文章:
text
标题:Go Context 在生产环境中的实践
如果没有把这篇文章提供给模型,或者没有为模型配置相应的检索工具,模型通常不会自动知道你刚刚写了什么。
解决方案取决于需求:
| 需求 | 可以考虑的方案 |
|---|---|
| 让模型了解当前问题的背景 | Prompt 与上下文 |
| 查询某一条确定的业务记录 | Go 工具调用数据库或业务 API |
| 从大量文档中检索相关内容 | RAG |
| 获取最新外部信息 | 接入可靠的搜索或数据工具 |
| 让模型适应特定领域的表达与任务 | 根据需要评估提示词优化、微调等方案 |
这里要记住:RAG 不是重新训练模型,而是在生成答案前向模型提供检索到的相关资料。
六、模型为什么能理解上下文?
现代主流大语言模型通常采用 Transformer 架构。
你暂时不需要深入学习矩阵运算,但应该了解一个重要概念:注意力机制(Attention)。
注意力机制可以帮助模型在处理当前 Token 时,利用上下文中与它相关的信息。
例如:
小明把书放在桌子上。后来他拿起它,准备去图书馆。
这里的"它"指什么?
模型需要结合前文理解"它"与"书"的关系,而不是只孤立地处理最后一个字。
注意力机制是模型利用上下文信息的重要组成部分。不过,模型的实际能力还来自整个网络结构、训练过程及其他机制,并不只是 Attention 单独发挥作用。
那么,为什么需要上下文窗口?
因为模型一次推理能够处理的上下文长度存在限制。
假设某个模型支持一定数量的 Token,输入的系统提示词、对话历史、用户问题和生成内容都可能占用这部分容量。
如果历史记录过长,可能出现:
- 请求超过模型的上下文限制。
- 输入成本增加。
- 延迟增加。
- 重要信息被裁剪或难以有效利用。
所以在真正的 AI 应用中,我们不能无限地把所有聊天记录都塞进请求里,而需要设计历史裁剪、摘要、检索等策略。
这也是为什么后续要专门学习上下文管理和记忆系统。
七、从原理回到 Go:模型如何接入后端?
你可以把模型看作一个通过 API 提供能力的外部服务。
例如,一个简化的系统可以这样设计:
text
客户端
│ POST /api/ai/chat
▼
Gin Handler
▼
AI Service
│ HTTP 请求
▼
LLM API
│ 模型响应
▼
AI Service
│ 处理结果、错误与日志
▼
客户端
Go 后端仍然需要负责:
- 输入验证:用户问题是否为空、长度是否超限。
- 身份与权限:当前用户是否有权使用对应功能。
- 请求控制 :使用
context.Context设置超时与取消。 - 错误处理:处理超时、限流、服务不可用和无效响应。
- 结果处理:校验需要满足特定结构的模型输出。
- 成本控制:限制调用频率、输出长度和任务预算。
例如,后续我们会写出类似这样的代码:
go
func GenerateAnswer(
ctx context.Context,
question string,
) (string, error) {
if strings.TrimSpace(question) == "" {
return "", errors.New("question cannot be empty")
}
// 后续在这里调用真实的模型 API。
// 本节暂不实现模型请求。
return "", errors.New("model client not implemented")
}
这段代码目前只是一个接口设计示例,不是已经完成的模型调用程序。它说明了一点:即使模型负责生成内容,输入校验与错误处理仍由 Go 程序负责。
我们会在第二阶段真正实现 API 调用,不需要现在提前安装一堆依赖。
八、本课知识总结
| 概念 | 一句话记住 |
|---|---|
| LLM | 通过训练得到模型参数,推理时根据上下文生成内容。 |
| Token | 模型处理文本的基本单位,影响输入长度和使用成本。 |
| 逐步生成 | 根据当前上下文算下一个 Token,不断重复直到停止。 |
| 模型幻觉 | 回答看起来合理,却可能事实错误,因此必须设计验证机制。 |
| Go 工程职责 | 负责权限、校验、超时、错误处理和业务约束,而不是无条件相信模型输出。 |
九、课后练习
本课以理解为主,不要求写代码。
练习 1:理解生成原理
模型生成一段回答时,是一次性从数据库中取出完整答案,还是根据上下文逐步生成 Token?请解释你的理解。
参考思路:是逐步生成。模型每轮只算一个 Token 的概率分布,选出来之后追加到上下文末尾,再带着更长的上下文算下一个,直到遇到停止条件或达到长度上限。这也是为什么流式输出能实现------本质是把这个逐步生成的过程一段段推给前端,而不是等全部生成完再返回。
练习 2:分析模型幻觉
某模型生成了一个不存在的 Go 函数,并给出了看似正确的用法。为什么会发生这种情况?作为 Go 后端开发者,你会采取哪些措施?
参考思路:因为模型生成的是"概率上最合理的文本",而不是"查证过的 API 文档",函数名和签名完全可以被"编"得很像真的。工程措施:把模型输出当不可信输入,代码必须先编译或过类型检查;结构化输出加 schema 校验;关键信息补可核查来源;高风险动作(建表、删数据、发版)加人工确认。
练习 3:理解训练与推理
你调用在线模型 API,要求它解释 Go 的 defer。这属于训练还是推理?如果你刚刚在 Blogo 中发布了一篇新文章,模型为什么不一定知道文章内容?
参考思路:属于推理------模型参数是固定的,你的调用只是让它用已有参数算出输出,不会改变模型。新发的文章不在训练数据里,也不在你的上下文里,模型无从得知;除非你把它作为 Prompt 的一部分传进去,或者通过工具/RAG 在生成前检索给它。
下一篇:Go AI Agent 实战 03|Token、上下文窗口与模型调用成本