从一名大语言模型的“使用者“,蜕变为一名智能体系统的“构建者“。

文章目录

  AI 飞速发展,面对层出不穷的 "新词" 你是否感到厌倦或焦虑,Agent 从openclaw、hermes 到现在的claude code、codex,出现了诸如harness、prompt、context、Loop 的词汇,而那些有关 LLM 的技术栈我就不做赘述了。
  最近我看到一个比较不错的教程:Hello-Agents,是一本从零开始、理论与实战并重的智能体系统构建指南。我也相信,最好的学习方式就是动手实践,因此我根据该项目整理了一些比较重要的知识,若有疑惑处,还请大家积极讨论,谢谢。

初识智能体

智能体的运行机制和核心是智能体循环(Agent Loop),主要包含以下关联的阶段:

  1. 感知(perception),这是循环的起点,观察(Observation)环境的信息状态。
  2. 思考 (Thought) :接收到观察信息后,智能体进入其核心决策阶段。思考可进一步分为两个关键的环节:
    1. 规划 (Planning):智能体基于当前的观察和其内部记忆,更新对任务和环境的理解,并制定或调整一个行动计划。
    2. 工具选择 (Tool Selection):根据当前计划,智能体从其可用的工具库中,选择最适合执行下一步骤的工具,并确定调用该工具所需的具体参数。
  3. 行动 (Action):决策完成后,智能体通过其执行器(Actuators)执行具体的行动。这通常表现为调用一个选定的工具(如代码解释器、搜索引擎 API),从而对环境施加影响,意图改变环境的状态。

行动并非循环的终点。智能体的行动会引起环境 (Environment)状态变化 (State Change) ,环境随即会产生一个新的观察 (Observation) 作为结果反馈。这个新的观察又会在下一轮循环中被智能体的感知系统捕获,形成一个持续的"感知-思考-行动-观察"的闭环

由 Thought、Action、Observation 构成的严谨循环,LLM 智能体得以将内部的语言推理能力,与外部环境的真实信息和工具操作能力有效地结合起来。

智能体应用的协作模式主要有两类:

  1. 作为开发者工具,深度集成到工作流(workflow)。
  2. 作为自主协作者(Agent)。
    这种基于实时信息进行动态推理和决策的能力,正是 Agent 的核心价值所在。

简单来说,Workflow 是让 AI 按部就班地执行指令,而 Agent 则是赋予 AI 自由度去自主达成目标。

智能体发展史

如果智能无法被完全设计,那么它是否可以被学习出来?

  • 这一设问开启了人工智能的"学习"时代。

人工智能领域几次关键的范式革命:

  • 符号主义的探索与局限:从人工智能的古典时代出发,阐述了以专家系统为代表的早期智能体是如何尝试通过"知识+推理"来模拟智能的。
  • 分布式智能思想的萌芽:马文·明斯基的"心智社会"理论。这一革命性的思想揭示了复杂的整体智能可以从简单的局部单元的交互中涌现,为后续的多智能体系统研究提供了重要的哲学启发。
  • 学习范式的演进:从联结主义赋予智能体感知世界的能力,到强化学习使其学会在与环境的交互中进行最优决策,再到基于大规模数据预训练的大型语言模型(LLM)为其提供了前所未有的世界知识和通用推理能力。
  • 现代智能体的诞生:对LLM驱动智能体进行分析。分析其核心组件(模型、记忆、规划、工具等)和工作原理。

LLM驱动的智能体核心组件架构:

图解:

  1. 感知 (Perception) :流程始于感知模块 (Perception Module) 。它通过传感器从外部环境 (Environment) 接收原始输入,形成观察 (Observation)。这些观察信息(如用户指令、API返回的数据或环境状态的变化)是智能体决策的起点,处理后将被传递给思考阶段。
  2. 思考 (Thought) :这是智能体的认知核心,对应图中的规划模块 (Planning Module)大型语言模型 (LLM) 的协同工作。
    • 规划与分解 :首先,规划模块接收观察信息,进行高级策略制定。它通过反思 (Reflection)自我批判 (Self-criticism) 等机制,将宏观目标分解为更具体、可执行的步骤。
    • 推理与决策 :随后,作为中枢的LLM 接收来自规划模块的指令,并与记忆模块 (Memory) 交互以整合历史信息。LLM进行深度推理,最终决策出下一步要执行的具体操作,这通常表现为一个工具调用 (Tool Call)
  3. 行动 (Action) :决策完成后,便进入行动阶段,由执行模块 (Execution Module) 负责。LLM生成的工具调用指令被发送到执行模块。该模块解析指令,从工具箱 (Tool Use) 中选择并调用合适的工具(如代码执行器、搜索引擎、API等)来与环境交互或执行任务。这个与环境的实际交互就是智能体的行动 (Action)
  4. 观察 (Observation) 与循环 :行动会改变环境的状态,并产生结果。
    • 工具执行后会返回一个工具结果 (Tool Result) 给LLM,这构成了对行动效果的直接反馈。同时,智能体的行动改变了环境,从而产生了一个全新的环境状态
    • 这个"工具结果"和"新的环境状态"共同构成了一轮全新的观察 (Observation) 。这个新的观察会被感知模块再次捕获,同时LLM会根据行动结果更新记忆 (Memory Update),从而启动下一轮"感知-思考-行动"的循环。

大语言模型基础

从N-gram到 RNN

N-gram 模型:N代表所考虑的上下文窗口,一个词的概率只与它前面有限的N-1个词有关

  • Bigram (当 N=2 时)
    P ( w i ∣ w 1 , ... , w i − 1 ) ≈ P ( w i ∣ w i − 1 ) P(wi∣w1,...,wi−1)≈P(wi∣wi−1) P(wi∣w1,...,wi−1)≈P(wi∣wi−1)
  • Trigram (当 N=3 时)
    P ( w i ∣ w 1 , ... , w i − 1 ) ≈ P ( w i ∣ w i − 2 , w i − 1 ) P(wi∣w1,...,wi−1)≈P(wi∣wi−2,wi−1) P(wi∣w1,...,wi−1)≈P(wi∣wi−2,wi−1)

这些概率可以通过在大型语料库中进行最大似然估计(Maximum Likelihood Estimation,MLE) 来计算。这个术语听起来很复杂,但其思想非常直观:最可能出现的,就是我们在数据中看到次数最多的。

RNN循环神经网络的缺点:梯度反向传播时经历多次连乘,会导致梯度消失或爆炸现象

  • 梯度消失使模型无法有效学习序列早期信息对后期输出的影响,
    即难以捕捉长距离的依赖关系。

长短时记忆网络(LSTM)由此而生,他是特殊的RNN

  • 引入了细胞状态(cell state)和一套精密的门控机制(Gating Mechanism)。
    • 细胞状态可以看作是一条独立于隐藏状态的信息通路,允许信息在时间步中传递。
    • 遗忘门 (Forget Gate):决定从上一时刻的细胞状态中丢弃哪些信息。
    • 输入门 (Input Gate):决定将当前输入中的哪些新信息存入细胞状态。
    • 输出门 (Output Gate):决定根据当前的细胞状态,输出哪些信息到隐藏状态。
  • 瓶颈:按顺序处理数据,无法进行大规模的 并行计算,处理长序列时效率低下。

Transformer 在2017年由谷歌团队提出,抛弃循环,依赖 注意力机制 捕捉序列内的依赖关系,实现真正意义上的并行计算。正如:Attention is all you need

Transformer 架构

经典的编码器-解码器 (Encoder-Decoder) 架构。

  • 编码器:任务是"理解"输入的整个句子。最终为每个词元生成一个富含上下文信息的向量表示。
  • 解码器:任务是"生成"目标句子。它会参考自己已经生成的前文,并"咨询"编码器的理解结果,来生成下一个词。

整体架构图:

习惯性的分为四个部分:

  1. 输入部分:词嵌入、位置编码。
  2. 输出部分:线性层、Softmax层。
  3. 编码器:由N的编码器层组成
    1. 多头注意力机制层
    2. 残差连接与层归一化
    3. 位置前馈网络(前馈全连接)层
  4. 解码器:由N个解码器层组成
    1. 包含编码器层的全部(多头注意力机制、残差连接、前馈全连接)
    2. 交叉注意力层(计算注意力时:参数列表的q来自解码器,k和v来自编码器)

注意力机制

自注意力机制为每个输入的词元向量引入了三个可学习的角色:

  • 查询 (Query, Q):代表当前词元,它正在主动地"查询"其他词元以获取信息。
  • 键 (Key, K):代表句子中可被查询的词元"标签"或"索引"。
  • 值 (Value, V):代表词元本身所携带的"内容"或"信息"。

这三个向量都是由原始的词嵌入向量乘以三个不同的、可学习的权重矩阵 (WQ,WK,WV) 得到的。

注意力的计算 可概括为一个简洁的公式: Attention ( Q , K , V ) = softmax ( Q K ⊤ d k ) V \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^\top}{\sqrt{d_k}}\right)V Attention(Q,K,V)=softmax(dk QK⊤)V

  • 计算相关性得分:要计算词A的新表示,就用词A的Q向量,去和句子中所有词(包括A自己)的K向量进行点积运算。这个得分反映了其他词对于理解词A的重要性。
  • 稳定化与归一化:将得到的所有分数除以一个缩放因子dk(dk是K向量的维度),以防止梯度过小,然后用Softmax函数将分数转换成总和为1的权重,也就是归一化的过程。
  • 加权求和:将上一步得到的权重分别乘以每个词对应的VV向量,然后将所有结果相加。最终得到的向量,就是词A融合了全局上下文信息后的新表示。

我们希望模型能同时关注多种关系(如指代关系、时态关系、从属关系等)。多头注意力机制应运而生。它的思想很简单:把一次做完变成分成几组,分开做,再合并。

它将原始的 Q, K, V 向量在维度上切分成 h 份(h 就是"头"数),每一份都独立地进行一次单头注意力的计算。

其实就是把 d_model 变为 d_k * num_heads,d_model 必须能被 num_heads 整除。

这种设计让模型能够共同关注来自不同位置、不同表示子空间的信息,极大地增强了模型的表达能力。

逐位置前馈网络(FFN)

作用是从注意力层聚合的信息中提取更高阶的特征。逐位置意味着这个前馈网络会独立地作用于序列中的每一个词元向量。这个网络的结构非常简单,由两个线性变换和一个 ReLU 激活函数组成: F F N ( x ) = max ⁡ ( 0 , x W 1 + b 1 )   W 2 + b 2 \mathrm{FFN}(x) = \max(0, xW_1 + b_1)\, W_2 + b_2 FFN(x)=max(0,xW1+b1)W2+b2

在pytorch框架中代码表示为:

复制代码
class PositionWiseFeedForward(nn.Module):
    """
    位置前馈网络模块
    """
    def __init__(self, d_model, d_ff, dropout=0.1):
        super(PositionWiseFeedForward, self).__init__()
        self.linear1 = nn.Linear(d_model, d_ff)
        self.dropout = nn.Dropout(dropout)
        self.linear2 = nn.Linear(d_ff, d_model)
        self.relu = nn.ReLU()

    def forward(self, x):
        # x 形状: (batch_size, seq_len, d_model)
        x = self.linear1(x)
        x = self.relu(x)
        x = self.dropout(x)
        x = self.linear2(x)
        # 最终输出形状: (batch_size, seq_len, d_model)
        return x

通常,d_ff 等于 4倍的 d_model,先扩大后缩小被认为有助于模型学习更丰富的特征。

残差连接与层归一化

  • 残差连接 (Add) :这一结构解决了深度神经网络中的梯度消失 (Vanishing Gradients) 问题。在反向传播时,梯度可以绕过子模块直接向前传播,从而保证了即使网络层数很深,模型也能得到有效的训练。其公式可以表示为: O u t p u t = x + S u b l a y e r ( x ) Output=x+Sublayer(x) Output=x+Sublayer(x)
  • 层归一化 (Norm) :对单个样本的所有特征进行归一化,使其均值为0,方差为1。这解决了模型训练过程中的内部协变量偏移 (Internal Covariate Shift) 问题,使每一层的输入分布保持稳定,从而加速模型收敛并提高训练的稳定性

位置编码

Transformer 本身不包含任何关于词元顺序或位置的信息,自注意力只看"谁和谁相关",不看"谁在前谁在后",所以没有位置信息就无法区分顺序与结构。

位置编码的核心思想是,为输入序列中的每一个词元嵌入向量都额外加上一个能代表其绝对位置和相对位置信息的"位置向量"。该向量是固定、不可学习的,原论文中的位置编码使用正弦、余弦函数生成:

P E ( p o s , 2 i ) = sin ⁡ ( p o s 10000 2 i / d m o d e l ) PE_{(pos,2i)} = \sin\left(\frac{pos}{10000^{2i/d_{model}}}\right) PE(pos,2i)=sin(100002i/dmodelpos)

P E ( p o s , 2 i + 1 ) = cos ⁡ ( p o s 10000 2 i / d m o d e l ) PE_{(pos,2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{model}}}\right) PE(pos,2i+1)=cos(100002i/dmodelpos)

其中:

  • pos 是词元在序列中的位置(例如,0,1,2,...)
  • i 是位置向量中的维度索引(从 0 到 dmodel/2)
  • dmodel是词嵌入向量的维度。

Decoder-Only 架构

由来:无论是回答问题、写故事还是生成代码,本质都是在one by one的生成合理的内容。

基于这个思想,GPT 做了一个大胆的简化:它完全抛弃了编码器,只保留了解码器部分。

Decoder-Only 架构的工作模式被称为自回归 (Autoregressive),就像一个在玩"文字接龙"的游戏,它不断地"回顾"自己已经写下的内容,然后思考下一个字该写什么。

掩码自注意力 (Masked Self-Attention) 机制确保了预测的公平性和逻辑的连贯性。

  • 将当前位置之后的词元对应的分数替换为非常大的负数
  • 经过softmax后,这些位置的概率变为0,拒绝"偷看答案"。

优势:

  • 训练目标统一:模型的唯一任务就是"预测下一个词",这个简单的目标非常适合在海量的无标注文本数据上进行预训练。
  • 结构简单,易于扩展:更少的组件意味着更容易进行规模化扩展。今天的 GPT-4、Llama 等拥有数千亿甚至万亿参数的巨型模型,都是基于这种简洁的架构。
  • 天然适合生成任务:其自回归的工作模式与所有生成式任务(对话、写作、代码生成等)完美契合,这也是它能成为构建通用智能体基础的核心原因。

通过"预测下一个词"这一简单的范式,开启了我们今天所处的大语言模型时代。

与LLM进行交互

提示工程,就是研究如何设计出精准的提示,从而引导模型产生我们期望输出的回复。

模型采样参数

Temperature、Top-k、Top-p

Temperature 是控制模型输出 "随机性" 与 "确定性" 的关键参数,其原理是引入温度系数T>0T>0,将 Softmax 改写为:

p i ( T ) = e z i / T ∑ j = 1 k e z j / T p_i^{(T)} = \frac{e^{z_i/T}}{\sum_{j=1}^{k} e^{z_j/T}} pi(T)=∑j=1kezj/Tezi/T

当T变小时,分布"更加陡峭",高概率项权重放大,生成更"保守"且重复率更高的文本。当T变大时,分布"更加平坦",低概率项权重提升,生成更"多样"但可能出现不连贯的内容。

  • 低温度(0 ⩽ Temperature < 0.3)时输出更 "精准、确定"。适用场景: 事实性任务:如问答、数据计算、代码生成; 严谨性场景:法律条文解读、技术文档撰写、学术概念解释等场景。
  • 中温度(0.3 ⩽ Temperature < 0.7):输出 "平衡、自然"。适用场景: 日常对话:如客服交互、聊天机器人; 常规创作:如邮件撰写、产品文案、简单故事创作。
  • 高温度(0.7 ⩽ Temperature < 2):输出 "创新、发散"。适用场景: 创意性任务:如诗歌创作、科幻故事构思、广告 slogan brainstorm、艺术灵感启发; 发散性思考。

Top-k 就是把token降序排序,取排名前 k 个的 token 组成 "候选集"。

Top-p 就是在token降序排序的前提下,累加概率,直到累积和首次达到或超过阈值 p

相对于固定截断大小的 Top-k,Top-p 能动态适应不同分布的"长尾"特性,对概率分布不均匀的极端情况的适应性更好。

优先级顺序为:温度调整→Top-k→Top-p。温度调整整体分布的陡峭程度,Top-k 会先保留概率最高的 k 个候选,然后 Top-p 会从 Top-k 的结果中累加概率,获得候选集。

少样本提示

Few-shot Prompting,提供多个示例,这能让模型更准确地理解任务的细节、边界和细微差别,从而获得更好的性能。

指令调优 (Instruction Tuning) 是一种微调技术,它使用大量"指令-回答"格式的数据对预训练模型进行进一步的训练。

带来的影响:极大地简化了我们与模型交互的方式,使得直接、清晰的自然语言指令成为可能。

思维链

(Chain-of-Thought, CoT) 是一种强大的提示技巧,它通过引导模型"一步一步地思考",提升了模型在复杂任务上的推理能力。关键是在提示中加入一句简单的引导语,如"请逐步思考"或"Let's think step by step"。

文本分词

tokenizer 的作用,就是定义一套规则,将原始文本切分成一个个最小的单元,我们称之为词元 (Token) 。将文本序列转换为数字序列的过程,就叫做分词

理解分词器的实际影响十分重要,这直接关系到智能体的性能、成本和稳定性:

  • 上下文窗口限制 :模型的上下文窗口(如 8K, 128K)是以 Token 数量计算的。精确管理输入长度、避免超出上下文限制是构建长时记忆智能体的基础。
  • API 成本:大多数模型 API 都是按 Token 数量计费的。
  • 模型表现的异常 :有时模型的奇怪表现在于分词。例如,模型可能很擅长计算 2 + 2,但对于 2+2(没有空格)就可能出错,因为后者可能被分词器视为一个独立的、不常见的词元。

调用开源模型

对于许多需要处理敏感数据、希望离线运行或想精细控制成本的场景,将大语言模型直接部署在本地就显得至关重要。此次使用Hugging Face ,一个强大的开源库,我们选择小规模但功能强大的模型:Qwen/Qwen1.5-0.5B-Chat

首先安装必要的库:pip install transformers torch

下面这段代码会自动从 Hugging Face Hub 下载所需的模型文件和分词器配置,需要一些时间,具体取决于你的网络速度。

复制代码
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

# 指定模型ID
model_id = "Qwen/Qwen1.5-0.5B-Chat"

# 设置设备,优先使用GPU,根据设备选择
#device = "cuda" if torch.cuda.is_available() else "cpu"
device = "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Using device: {device}")

# 加载分词器
tokenizer = AutoTokenizer.from_pretrained(model_id)

# 加载模型,并将其移动到指定设备
model = AutoModelForCausalLM.from_pretrained(model_id).to(device)

print("模型和分词器加载完成!")

确认模型下载完毕后,执行下面这段代码(mac 系统):

复制代码
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

model_id = "Qwen/Qwen1.5-0.5B-Chat"

device = "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Using device: {device}")

# 1. 先加载 tokenizer 和 model
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id).to(device)

# 2. 补 pad_token(model 已经存在了才能 resize)
if tokenizer.pad_token is None:
  tokenizer.add_special_tokens({'pad_token': '[PAD]'})
  model.resize_token_embeddings(len(tokenizer))

print("模型和分词器加载完成!")

messages = [
  {"role": "system", "content": "You are a helpful assistant."},
  {"role": "user", "content": "你好,请介绍你自己。"}
]

text = tokenizer.apply_chat_template(
  messages,
  tokenize=False,
  add_generation_prompt=True
)

# 3. padding=True → 自动生成 attention_mask
model_inputs = tokenizer([text], return_tensors="pt", padding=True).to(device)

generated_ids = model.generate(
  model_inputs.input_ids,
  attention_mask=model_inputs.attention_mask,  # 显式传入
  max_new_tokens=512,
  pad_token_id=tokenizer.pad_token_id,         # 告诉 model pad_token 是啥
)

generated_ids = [
  output_ids[len(input_ids):]
  for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]

response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
print("模型的回答:")

模型选型考量

大语言模型是智能体的大脑,选择时建议考虑以下方面:

  • 性能与能力
  • 成本、速度(延迟)
    • 对于闭源模型,成本主要体现在 API 调用费用,通常按 Token 数量计费。对于开源模型,成本则体现在本地部署所需的硬件(GPU、内存)和运维上。需要根据应用的预期使用量和预算做出选择。
  • 上下文窗口
  • 部署方式
    • 使用 API 的方式最简单便捷,但数据需要发送给第三方,且受限于服务商的条款。本地部署则能确保数据隐私和最高程度的自主可控,但对技术和硬件要求更高。
  • 生态与工具链
    • 选择一个拥有活跃社区和完善工具链的模型,可以在遇到问题时更容易找到解决方案和资源。
  • 可微调与定制化
    • 对于需要处理特定领域数据或执行特定任务的智能体,模型的微调能力至关重要。一些模型提供了便捷的微调接口和工具,允许开发者使用自己的数据集对模型进行定制化训练,从而显著提升模型在特定场景下的性能和准确性。
  • 安全性与伦理

闭源模型提供了"开箱即用"的便捷,而开源模型则赋予了我们"随心所欲"的定制自由。

缩放法则与局限性

缩放法则(Scaling Laws) 揭示了模型性能与模型参数量、训练数据量以及计算资源之间存在着可预测的幂律关系。即增加资源投入能够系统性提升模型性能的底层逻辑。

这一发现为大模型的设计和训练提供了清晰的指导:在资源允许的范围内,尽可能地扩大模型规模和训练数据量。

  • 模型参数量和训练数据量之间存在一个最优配比
    缩放法则最令人惊奇的产物是"能力的涌现"。模型规模达到一定阈值后,会突然展现出在小规模模型中完全不存在或表现不佳的全新能力。
  • 链式思考 (Chain-of-Thought)
  • 指令遵循 (Instruction Following)
  • 多步推理、代码生成等能力
    能力的涌现意味着选择一个足够大规模的模型,是实现复杂自主决策和规划能力的前提。

模型幻觉(Hallucination) 通常指的是大语言模型生成的内容与客观事实、用户输入或上下文信息相矛盾,或者生成了不存在的事实、实体或事件。

本质是模型在生成过程中,过度自信地"编造"了信息,而非准确地检索或推理。根据其表现形式,幻觉可以被分为多种类型,例如:

  • 事实性幻觉:生成与现实世界事实不符的信息。
  • 忠实性幻觉:在文本摘要、翻译中,生成内容未能忠实于源文本的含义。
  • 内在幻觉:模型生成的内容与输入信息直接矛盾。

幻觉产生的原因是多方面因素共同作用的结果,通常考虑:

  1. 训练数据中存在错误或矛盾的信息
  2. 模型的自回归生成机制决定了它只是预测下一个最可能的词元,没有内置的核实模块
  3. 复杂推理等任务,模型可能会在逻辑链条中出错,从而"编造"错误的结论。
    此外,知识实效性不足和训练数据中存在的偏见也会导致模型幻觉。大语言模型的能力来源于训练数据,对于新事件、概念或最新的事实,模型将无法感知或正确回答。

多种检测和缓解模型幻觉的方法:

  1. 数据层面:高质量的数据清洗、引入事实性知识以及 RLHF 等方式,从源头减少幻觉。
  2. 模型层面:探索新的模型架构,或让模型能够表达其对生成内容的不确定性。
  3. 推理与生成层面:
    1. 检索增强生成(RAG)是缓解幻觉的有效方法之一。
    2. 多步推理与验证:引导模型进行多步推理,并在每一步进行自我检查或外部验证。
    3. 引入外部工具:允许模型用搜索引擎、代码解释器(python)获取信息或计算。