AI AI树洞

Chapter 03

第三章 大语言模型基础

大语言模型是现代 Agent 的核心基础设施。本章不追求训练大模型,而是让你理解 Agent 开发中最常遇到的 LLM 概念:语言建模、向量表示、Transformer、Prompt、Token、开源模型调用、模型选择和幻觉。

本章学习目标

概念目标 理解 N-gram、词向量、注意力、Decoder-only、Prompt、BPE、上下文窗口和幻觉。
代码目标 运行 n_gram.pybpe.pytransformer_attention.py
工程目标 能为 Agent 选择模型,构造 Prompt,并把模型输出接入结构化工具调用。

3.1 语言模型与 Transformer 架构

3.1.1 从 N-gram 到 RNN

语言模型的基本任务是预测文本序列的概率。最朴素的 N-gram 模型会统计“某个词后面跟着另一个词”的频率,用局部上下文预测下一个词。虽然它很简单,但能帮助你建立一个关键直觉:语言模型不是事实数据库,而是根据上下文做概率预测。

from collections import Counter, defaultdict


class NGramLanguageModel:
    def __init__(self, n: int = 2) -> None:
        self.n = n
        self.table: dict[tuple[str, ...], Counter[str]] = defaultdict(Counter)

    def fit(self, corpus: list[str]) -> None:
        for sentence in corpus:
            tokens = ["<s>"] * (self.n - 1) + sentence.split() + ["</s>"]
            for index in range(len(tokens) - self.n + 1):
                context = tuple(tokens[index:index + self.n - 1])
                target = tokens[index + self.n - 1]
                self.table[context][target] += 1

    def next_token_distribution(self, context_words: list[str]) -> list[tuple[str, float]]:
        context = tuple(context_words[-(self.n - 1):])
        counts = self.table.get(context, Counter())
        total = sum(counts.values())
        if total == 0:
            return []
        return [(token, count / total) for token, count in counts.most_common()]


if __name__ == "__main__":
    lm = NGramLanguageModel(n=2)
    lm.fit(["agent uses tools", "agent reads memory", "agent uses memory"])
    print(lm.next_token_distribution(["agent"]))

配套文件:n_gram.py。N-gram 的问题是上下文太短,没见过的组合难以泛化。RNN、LSTM 等神经网络曾经用于建模更长序列,但并行效率和长距离依赖仍然受限。Transformer 的出现改变了这个局面。

3.1.1 补充:词向量与语义空间

神经语言模型不会直接处理字符串,而是把词或 Token 转成向量。向量可以参与加减和相似度计算,因此模型能把语义关系编码到连续空间中。下面用一个极简例子演示“向量相似度”和类比计算。

import math


VECTORS = {
    "king": [0.8, 0.6, 0.1],
    "queen": [0.75, 0.65, 0.2],
    "man": [0.7, 0.2, 0.0],
    "woman": [0.65, 0.25, 0.1],
    "agent": [0.1, 0.9, 0.8],
}


def cosine(left: list[float], right: list[float]) -> float:
    dot = sum(a * b for a, b in zip(left, right))
    left_norm = math.sqrt(sum(a * a for a in left))
    right_norm = math.sqrt(sum(b * b for b in right))
    return dot / (left_norm * right_norm)


def add(*vectors: list[float]) -> list[float]:
    return [sum(values) for values in zip(*vectors)]


def sub(left: list[float], right: list[float]) -> list[float]:
    return [a - b for a, b in zip(left, right)]


target = add(sub(VECTORS["king"], VECTORS["man"]), VECTORS["woman"])
ranked = sorted(
    ((word, cosine(vector, target)) for word, vector in VECTORS.items()),
    key=lambda item: item[1],
    reverse=True,
)
print(ranked)

真实词向量维度会高很多,训练方式也更复杂。对 Agent 来说,词向量最直接的应用是语义检索:把问题和文档都转成向量,再找最相近的内容,这就是许多 RAG 系统的基础。

3.1.2 Transformer 架构解析

Transformer 的核心是注意力机制。它让模型在处理一个 Token 时,可以根据相关性动态关注输入序列中的其他 Token。注意力通常由 Query、Key、Value 三组向量计算得到。

Token文本被分词器切成 Token。
EmbeddingToken 被映射为向量。
Q/K/V向量投影成查询、键和值。
Attention计算相关性并聚合上下文。
Output生成下一层表示或下一个 Token。
import math


def dot(left: list[float], right: list[float]) -> float:
    return sum(a * b for a, b in zip(left, right))


def softmax(row: list[float]) -> list[float]:
    shifted = [item - max(row) for item in row]
    values = [math.exp(item) for item in shifted]
    total = sum(values)
    return [item / total for item in values]


def attention(query: list[list[float]], key: list[list[float]], value: list[list[float]]):
    dim = len(query[0])
    scores = [[dot(q, k) / math.sqrt(dim) for k in key] for q in query]
    weights = [softmax(row) for row in scores]
    outputs = []
    for row in weights:
        outputs.append([
            sum(weight * value[index][column] for index, weight in enumerate(row))
            for column in range(len(value[0]))
        ])
    return outputs, weights

配套文件:transformer_attention.py。注意力的工程启发是:上下文会影响输出,但上下文不是越多越好。Agent 需要把最相关的目标、工具、观察和约束放进模型输入。

3.1.3 Decoder-Only 架构

很多主流大语言模型采用 Decoder-only 架构。它们按从左到右的方式预测下一个 Token,每次生成的新 Token 又会成为后续上下文的一部分。聊天、代码补全、工具调用 JSON,本质上都可以看作“在上下文条件下继续生成”。

架构概念 直觉理解 对 Agent 的影响
自回归生成 一个 Token 接一个 Token 生成。 长输出可能偏离格式,需要流式校验或后处理。
上下文窗口 模型一次能看到的 Token 数有限。 Agent 需要摘要、检索和 checkpoint。
采样参数 temperature、top_p 会影响随机性。 工具调用通常需要低随机性,创意写作可以高一些。

3.2 与大语言模型交互

3.2.1 提示工程

Prompt 工程不是神秘咒语,而是把任务、约束、示例、输出格式和上下文组织给模型。Agent 场景下,Prompt 通常还要包含工具说明和安全边界。

提示方式 使用方法 适合场景
零样本提示 直接描述任务和输出要求。 简单分类、摘要、格式转换。
少样本提示 提供几个输入输出样例。 风格迁移、复杂格式、业务规则示范。
分步提示 要求模型先拆任务或按步骤检查。 规划、调试、推理密集任务。
结构化输出 要求模型输出 JSON 或工具调用参数。 Agent 决策、函数调用、自动化执行。
SYSTEM_PROMPT = """你是一个 Agent 决策模块。
只能输出 JSON,不要输出解释。
允许的 action: answer, call_tool, ask_human。
如果证据不足,必须 ask_human。"""

USER_PROMPT = """任务:根据搜索结果判断是否需要继续检索。
当前观察:只找到一个来源,且来源可信度未知。
输出字段:action, reason, tool, arguments。"""

3.2.2 文本分词

模型看见的不是字符或人类意义上的词,而是 Token。分词器会把文本拆成 Token ID。不同模型的分词方式不同,同一段文本的 Token 数也可能不同,这会影响成本、上下文长度和截断策略。

from collections import Counter


class SimpleBPE:
    def __init__(self) -> None:
        self.merges: list[tuple[str, str]] = []

    def best_pair(self, words: list[list[str]]) -> tuple[str, str]:
        counts: Counter[tuple[str, str]] = Counter()
        for pieces in words:
            for index in range(len(pieces) - 1):
                counts[(pieces[index], pieces[index + 1])] += 1
        return counts.most_common(1)[0][0]

    def merge_pair(self, pieces: list[str], pair: tuple[str, str]) -> list[str]:
        merged = []
        index = 0
        while index < len(pieces):
            if index + 1 < len(pieces) and (pieces[index], pieces[index + 1]) == pair:
                merged.append(pieces[index] + pieces[index + 1])
                index += 2
            else:
                merged.append(pieces[index])
                index += 1
        return merged


if __name__ == "__main__":
    words = [list(word) + ["</w>"] for word in ["agent", "agents", "agenda"]]
    bpe = SimpleBPE()
    pair = bpe.best_pair(words)
    print(pair)
    print([bpe.merge_pair(word, pair) for word in words])

配套文件:bpe.py。Agent 做长文档任务时,一定要考虑 Token 预算:哪些内容进入上下文,哪些内容只保留摘要,哪些内容通过检索按需取回。

3.2.3 调用开源大语言模型

学习阶段可以用开源模型理解完整流程:加载 tokenizer、加载模型、构造 messages、编码输入、生成输出、解码新 Token。生产阶段则要根据成本、延迟、部署资源和安全要求选择 API 或自部署。

# 伪代码:需要安装 transformers 和对应模型权重后运行
from transformers import AutoModelForCausalLM, AutoTokenizer


model_id = "Qwen/Qwen2.5-0.5B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id)

messages = [
    {"role": "system", "content": "你是一个简洁的 Agent 教学助手。"},
    {"role": "user", "content": "用三句话解释工具调用。"},
]
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
inputs = tokenizer(text, return_tensors="pt")
outputs = model.generate(**inputs, max_new_tokens=128)
print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:], skip_special_tokens=True))

3.2.4 模型的选择

Agent 里不同步骤可以使用不同模型。规划和复杂推理需要强模型,格式转换和简单分类可以用小模型,Embedding 检索需要专门向量模型,安全审核可能需要独立分类器。

任务 模型选择重点 常见风险
规划 推理能力、上下文理解、工具意识。 计划过度乐观、步骤不可执行。
工具参数生成 结构化输出稳定性、低随机性。 参数缺失、工具名错误、越权调用。
总结 长上下文、压缩能力、引用保真。 遗漏关键事实、混淆来源。
事实核查 检索结合、保守回答、证据意识。 幻觉、自信错误。

3.3 大语言模型的缩放法则与局限性

3.3.1 缩放法则

模型能力通常会随着参数量、数据量和计算量增长而提升,但这不是免费的。更大的模型意味着更高的推理成本、更高的延迟和更复杂的部署要求。Agent 开发中经常要做分层选择:关键决策用强模型,普通步骤用便宜模型。

3.3.2 模型幻觉

幻觉是 Agent 最需要警惕的模型局限之一。模型可能生成不存在的事实、编造引用、误读工具返回值,或者对不确定内容给出很自信的表达。Agent 一旦把幻觉结果直接送进工具层,就可能造成真实损失。

工程策略: 对事实类任务使用检索和引用,对工具调用使用结构化 schema,对高风险动作使用人工确认,对连续失败使用停止条件。

3.4 本章小结

本章从 N-gram 讲到 Transformer,再讲 Prompt、分词、开源模型调用、模型选择和幻觉。对 Agent 开发者来说,最重要的不是记住每个公式,而是理解模型是如何接收上下文、生成 Token、受 Prompt 影响,并在工程系统里表现出不稳定性。

前三章到这里完成基础铺垫。后续学习经典 Agent 范式时,你会反复用到本章概念:ReAct 需要结构化行动,Plan-and-Solve 需要规划 Prompt,RAG 需要向量和分词,上下文工程需要 Token 预算,评估需要理解幻觉和模型选择。

习题

  1. 练习 3.1:实现三元模型。NGramLanguageModel 的 n 改成 3,比较 bigram 和 trigram 的预测差异。
  2. 练习 3.2:词向量相似度。 给词向量示例增加 5 个词,观察 cosine 相似度是否符合你的直觉。
  3. 练习 3.3:多轮 BPE 合并。 连续执行 5 次最佳 pair 合并,记录每轮词表变化。
  4. 练习 3.4:Prompt 改写。 把一个自由回答 Prompt 改造成结构化 JSON 输出 Prompt,并写出校验字段。
  5. 练习 3.5:模型选择表。 为一个调研 Agent 设计模型选择表,至少包含规划、搜索参数、摘要、核查四个步骤。

参考文献