Agent 开发学习日志(一)运行机制及控制参数
在智能 Agent 的工程实践中,LLM 扮演着决策引擎的角色,负责意图理解、任务规划与行动生成。许多 Web 全栈工程师在起步阶段往往直接调用 OpenAI 或 Anthropic 等厂商的 API,将其视为黑盒服务,直接使用固然不错,但缺乏底层认知会让系统在复杂场景下频繁失控。真正可落地的 Agent 要求开发者掌握 LLM 的内部工作方式,包括 Transformer 架构如何处理序列数据、自回归生成如何逐步产出 token,以及 temperature、top-p 等参数如何精确调控输出分布。
本文对应 Agent 学习路线 https://roadmap.sh/ai-agents 中的「Transformer Models and LLMs」模块,围绕基础概念、核心运行机制与生成控制参数三个模块展开。通过原理拆解到工程落地的完整路径,帮助开发者建立起从模型内部逻辑到实际调用策略的整体认知,从而在构建 Agent 时能够主动设计提示结构、控制生成行为并有效管理资源开销。
Transformer 与 LLM
在 Agent 开发中,我们面对的几乎所有主流模型——无论是 GPT 系列、Claude 还是开源的 Llama、Qwen——底层都共享同一套架构骨架。理解这套骨架与其产物的关系,是后续讨论分词、上下文窗口、生成逻辑以及成本控制的前提。我们可以把 Transformer 比作 Web 领域的浏览器渲染引擎(如 Blink 或 Gecko),而 LLM 则是基于该引擎构建出来的完整应用(如 Chrome 或 Firefox)。引擎决定了能力边界与性能特征,应用则在其上实现具体业务逻辑。
Transformer 是 2017 年由 Google 团队在论文《Attention is All You Need》中提出的深度学习模型架构。它的核心突破在于引入了 self-attention(自注意力)机制,使得模型能够并行处理整个输入序列,并直接计算任意两个位置之间的关联权重。在此之前,主流的 RNN(Recurrent Neural Network,循环神经网络)及其变体 LSTM 必须按时间步顺序逐个处理 token,无法充分利用现代 GPU 的并行计算能力,同时在长距离依赖上容易出现梯度消失问题。Self-attention 则通过一次矩阵运算同时评估序列中所有位置的相关性,从而高效捕捉长距离语义关联。例如,在一句话的开头出现的主语,模型可以在处理句尾时仍保持对其的精确关注,而不需要经过中间多层的"记忆传递"。对 Agent 开发者而言,不必深入公式推导,但需要明确:LLM 的所有能力(上下文理解、长文本处理)与约束(上下文窗口限制、位置编码开销)都直接源于这一架构特性。后续我们看到的分词策略、位置编码、注意力掩码等工程细节,本质上都是 Transformer 骨架的延伸与优化。
从工程视角看,Web 开发者不需要从零实现 Transformer,而是通过厂商 SDK 或 HuggingFace 的 inference client 调用预训练模型来完成推理。HuggingFace 提供了 @huggingface/inference 这个 npm 包,其 API 设计与 OpenAI SDK 高度相似——都采用 chatCompletion 方法,接收 messages 数组作为输入,内部自动处理 chat template 与 tokenization。以下是一个调用开源模型(如 Qwen)的示例:
import { HfInference } from "@huggingface/inference";
const hf = new HfInference(process.env.HF_TOKEN);
const response = await hf.chatCompletion({
model: "Qwen/Qwen2.5-7B-Instruct",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "What is the capital of France?" },
],
max_tokens: 128,
temperature: 0.7,
});
console.log(response.choices[0].message.content);这个调用的底层会经过三个关键阶段:首先 HF Inference API 在服务端将 messages 数组渲染为对应模型的 chat template 格式;随后对文本执行 tokenization,将其切分为整数 ID 序列送入模型;最后由服务端执行自回归解码,逐个 token 产出最终回复。整个过程对调用方透明,开发者只需关心 messages 构造与参数配置。对于 OpenAI 等厂商的 SDK,调用方式几乎一致:
import OpenAI from "openai";
const client = new OpenAI();
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "What is the capital of France?" }],
max_tokens: 128,
temperature: 0.7,
});
console.log(response.choices[0].message.content);Transformer 的整体架构和技术细节可以参考这篇文章,大佬讲的十分详细,我这里就不赘述了: https://zhuanlan.zhihu.com/p/338817680。

大语言模型(Large Language Model,LLM)正是以 Transformer 架构为基础,在海量文本数据上进行预训练得到的超大规模生成式系统。它的本质工作方式可以概括为"基于上文预测下一个 token"的自回归过程:模型接收一段已有的 token 序列,输出词汇表中每个可能 token 的概率分布,然后根据采样策略选出下一个 token,再将其追加到序列中继续预测。这个循环不断重复,直到生成结束标记或达到长度上限。通过这种方式,LLM 展现出自然语言理解、文本生成、逻辑推理以及指令遵循等核心能力。自然语言理解体现在它能解析用户意图与上下文语义;文本生成则是直接的 token 序列输出;逻辑推理与指令遵循则依赖预训练阶段学到的模式与后续的对齐训练(如 RLHF,Reinforcement Learning from Human Feedback,基于人类反馈的强化学习)。
在 Agent 系统中,LLM 扮演着"决策大脑"的角色——无论是任务拆解、工具调用决策,还是多轮思考中的中间状态更新,最终都落实为一段文本的生成。Agent 框架(如 LangChain 或自研编排层)本质上是在 LLM 的生成能力之上封装了状态管理、工具接口与控制流。以下骨架以 TypeScript 直观展示这一关系——对 LLM 而言,自然语言回复与 tool_call 参数生成没有本质区别,都只是 completion:
async function agentLoop(
userQuery: string,
tools: Tool[],
maxSteps = 5
): Promise<string> {
const messages: ChatMessage[] = [
{ role: "system", content: buildSystemPrompt(tools) },
{ role: "user", content: userQuery },
];
for (let i = 0; i < maxSteps; i++) {
const response = await client.chat.completions.create({
model: "gpt-4o",
messages,
tools: formatToolsSchema(tools),
temperature: 0.3,
});
const msg = response.choices[0].message;
if (msg.content) {
messages.push({ role: "assistant", content: msg.content });
}
if (msg.tool_calls) {
for (const tc of msg.tool_calls) {
const result = executeTool(tc.function.name, JSON.parse(tc.function.arguments));
messages.push({ role: "tool", tool_call_id: tc.id, content: result });
}
continue;
}
if (response.choices[0].finish_reason === "stop") {
return msg.content ?? "";
}
}
return "Max steps exceeded";
}搞懂了 Transformer 与 LLM 的基础定义以及二者的依存关系后,我们再深入 LLM 的内部运行逻辑,从输入处理、能力边界到成本规则,全面理解它的运作机制。
LLM 核心运行机制
LLM 在 Agent 中的每一次决策最终都落脚于文本输入的处理与文本输出的生成。理解模型如何将自然语言转化为可计算的单元、单次交互能承载多少信息、以及这些操作如何转化为实际费用,直接决定了 Agent 架构中的 Prompt 设计、记忆策略、工具调用链长度与成本控制方案。这些机制共同构成了 Agent 工程化的底层硬约束,就像 Web 开发中浏览器的内存限制与网络请求计费规则一样,无法绕过,只能主动适配。
0x01 Tokenization
LLM 并不直接"阅读"人类写下的字符串,而是先将输入文本切分成一系列最小语义单元——Token,再将这些 Token 映射为模型词汇表中的整数 ID,进入后续的矩阵运算。这个切分过程称为 Tokenization。Token 的粒度通常介于字符与完整单词之间,属于 subword(子词)级别。主流模型大多采用 BPE(Byte Pair Encoding,字节对编码)或其变体:算法从字符级开始,反复合并语料中出现频率最高的相邻符号对,最终形成一套兼顾常见词与罕见词的词汇表。例如英文单词 "unhappiness" 可能被切分为 ["un", "happiness"] 或更细的 ["un", "happi", "ness"],具体取决于该模型的训练语料与词汇表大小;中文则常按字或常见词组切分,一个汉字通常对应 1 到 2 个 Token。
不同模型的分词器差异显著。OpenAI 的 GPT 系列使用 cl100k_base 或 o200k_base 编码,对英文较为高效,但中文与代码的 Token 密度偏高;Claude 与 Llama 的分词器则在多语言与代码场景下表现不同。在 Node.js 生态中,可通过 tiktoken 这个 npm 包(WASM 移植版)对不同编码做精确计数对比。注意 tiktoken 的实例用完后必须调用 .free() 释放 WASM 内存——这一点与 Web 开发中手动管理 WebSocket 或 Worker 线程的生命周期类似:
import { encoding_for_model, get_encoding } from "tiktoken";
const encGPT4 = encoding_for_model("gpt-4o");
const encCl100k = get_encoding("cl100k_base");
const textEn = "The quick brown fox jumps over the lazy dog.";
const textZh = "人工智能正在深刻改变软件工程的实践范式。";
console.log(encGPT4.encode(textEn).length, encCl100k.encode(textEn).length);
console.log(encGPT4.encode(textZh).length, encCl100k.encode(textZh).length);
encGPT4.free();
encCl100k.free();同一段英文在不同编码器下 token 数接近,但中文在 cl100k_base 下会显著膨胀(GPT-4o 约 15–18 token),而 Qwen 等中文优化的分词器则紧凑得多。原因在于 BPE 词汇表的构建语料分布——英文占比高的编码器对中文字符粒度更粗。
对 Agent 开发而言,Token 是上下文占用与成本计算的最小单位。实践中需要将系统提示、工具 schema 与历史对话分别计量,形成显式的 token 预算表:
import { encoding_for_model } from "tiktoken";
function countTokens(text: string, model: string = "gpt-4o"): number {
const enc = encoding_for_model(model);
const n = enc.encode(text).length;
enc.free();
return n;
}
const PRICING: Record<string, number> = {
"gpt-4o": 2.50 / 1_000_000,
"gpt-4o-mini": 0.15 / 1_000_000,
};
const systemTokens = countTokens(systemPrompt);
const toolTokens = countTokens(JSON.stringify(toolsSchema));
const historyTokens = countTokens(serializedHistory);
const overhead = systemTokens + toolTokens;
console.log(`Fixed overhead: ${overhead} tokens`);
console.log(`Est. input cost: $${((overhead + historyTokens) * PRICING["gpt-4o"]).toFixed(6)}`);这种预算管理在构建生产级 Agent 时不可或缺——你不知道下一次触发多少轮工具调用,但每一轮的成本必须可追溯。
0x02 Context Windows
Context Window 指模型单次前向计算可处理的最大 Token 序列长度,包含完整的输入 Prompt 与模型生成的输出回复。它是 LLM 最核心的能力边界——超过这个长度的内容会被截断或直接拒绝,模型对截断部分完全"失忆"。当前主流模型的窗口从早期的 4k、8k Token 扩展到 128k、200k 甚至百万级,但实际有效利用仍受注意力机制的二次复杂度与位置编码精度影响。窗口越大,模型理论上能同时关注更长的历史对话、更完整的文档或更多工具返回结果,语义连贯性与复杂推理能力相应提升;然而注意力计算的开销随窗口长度近似平方增长,导致延迟与显存占用上升。
留意一个重要的工程陷阱:模型声称支持 128k 窗口,不代表它在 100k 位置附近的"记忆"质量和处理速度与 1k 位置相同。学术研究表明,多数模型在窗口中部和后段的信息检索精度会显著下降,这种现象被称为"Lost in the Middle"(中部迷失)效应。在实际开发中,典型的应对策略是"从后往前保留"的滑动窗口:
const MAX_WINDOW = 120_000;
const RESERVED_OUTPUT = 4096;
const AVAILABLE = MAX_WINDOW - countTokens(SYSTEM_PROMPT) - RESERVED_OUTPUT;
function trimHistory(
messages: { content: string }[],
maxTokens: number,
): typeof messages {
const trimmed: typeof messages = [];
let total = 0;
for (let i = messages.length - 1; i >= 0; i--) {
const t = countTokens(messages[i].content);
if (total + t > maxTokens) break;
trimmed.unshift(messages[i]);
total += t;
}
return trimmed;
}最近的对话对当前任务最重要,因此优先保留末尾的消息;超出预算的早期消息可先做摘要(用小模型或同一模型的廉价版本压缩),再插入窗口顶部作为"历史概要"。
在 Agent 场景下,Context Window 直接决定了系统的"记忆容量"。多轮对话中每一轮的用户输入、模型思考、工具调用结果都必须塞进同一个窗口;一旦接近上限,早期历史就会被挤出,导致 Agent 忘记初始目标。这正是为什么生产级 Agent 几乎必然引入外部记忆系统(向量数据库 + 摘要)与 RAG(Retrieval-Augmented Generation,检索增强生成)架构:通过将长文档切块、向量化后按需检索,只把最相关的片段注入当前窗口,从而在有限容量内维持对大规模知识的访问。设计 Agent 时,必须显式规划窗口预算——系统提示占用多少、工具定义占用多少、单次工具返回截断到多少——并设置监控与自动压缩机制。
0x03 Token Based Pricing
主流云端 LLM API 几乎统一采用按 Token 计费模式:费用 = 输入 Token 数 × 输入单价 + 输出 Token 数 × 输出单价。输入(Prompt)与输出(Completion)通常存在明显单价差异,输出往往贵 2 到 5 倍,因为生成阶段的自回归计算比单纯前向推理更耗资源。成本追踪可以直接嵌入 API 调用层:
const PRICING = {
"gpt-4o": { input: 2.50e-6, output: 10.00e-6 },
"gpt-4o-mini": { input: 0.15e-6, output: 0.60e-6 },
};
const response = await client.chat.completions.create({
model: "gpt-4o", messages: [...], max_tokens: 50,
});
const usage = response.usage!;
const cost =
usage.prompt_tokens * PRICING["gpt-4o"].input +
usage.completion_tokens * PRICING["gpt-4o"].output;
console.log(`Cost: $${cost.toFixed(6)}`);在多轮 Agent 场景下,单次任务的成本远高于一次问答。假设某 Agent 需要 5 轮思考-工具-反思循环,每轮 prompt 约 3000 token(含历史),completion 约 500 token,使用 gpt-4o 则总成本约为 5 × (3000 × 2.50 + 500 × 10.00) / 1,000,000 ≈ $0.0625。单个任务看似不多,但并发 100 个任务、每天数千次运行时,成本将迅速爬升。
成本优化因此成为 Agent 工程的核心抓手:精简系统提示与工具描述、对历史对话做滑动窗口摘要、限制单次工具输出长度、缓存重复的检索结果、在低复杂度子任务上切换到更便宜的小模型(如 gpt-4o-mini)。效果与成本的平衡需要在真实流量下持续观测——既不能为了省钱过度压缩导致决策质量下降,也不能无节制堆砌上下文。
掌握了 Tokenization、Context Window 与按 Token 计费这三项核心机制,开发者就能在设计 Agent 时主动约束输入形态、规划记忆架构并控制资源开销。接下来我们将进入生成过程本身,考察模型如何从概率分布中采样出最终文本,以及 temperature、top-p 等参数如何精确调控输出的随机性与确定性。
LLM 生成控制参数
LLM 的输出并非固定的确定性结果,而是从词汇表概率分布中采样得到的。开发者可通过一组生成控制参数直接调节这个采样过程的随机性、多样性、重复倾向以及终止条件,相当于给模型装上可调旋钮。在 Agent 系统中,这些参数决定了任务执行的稳定性、结构化输出的可解析性,以及长回复中的信息密度。把它们用好比在前端框架中精细配置 React 的 concurrent 渲染策略或 Node.js 的 stream 背压控制——参数选错会导致结果抖动、格式崩溃或资源浪费,选对则能让 Agent 在确定性任务与开放生成之间平滑切换。
0x01 控制输出的随机性与创造性
Temperature 与 Top-p 是最基础也最常用的一对参数,二者通常一起调试,共同决定模型从概率分布中"挑"下一个 token 的方式。
Temperature 温度系数
Temperature 作用于模型输出的原始 logits(未归一化的分数)。计算时将每个 token 的 logit 除以 temperature 值,再通过 softmax 得到最终概率分布。当 temperature 接近 0 时,分布极度尖锐,最高概率 token 几乎必然被选中,输出呈现高度确定性与保守风格;当 temperature 升高(常见范围 0.7–1.2),分布被拉平,低概率 token 获得更多机会,输出变得多样且具创造性。极端情况下 temperature 过高会使生成接近随机噪声。对 Web 工程师来说,这类似在随机算法中调节 seed 的"锐利度"——低值像固定 seed 的确定性动画,高值像引入噪声的生成式 UI 变体。
在 Agent 的不同环节中,temperature 的使用策略差异明显:任务规划(如生成步骤列表)建议使用 0–0.3,确保可重现性和逻辑一致性;工具调用参数生成建议使用 0–0.2,避免 JSON 结构中的字段偏差;最终答案生成可在 0.5–0.8 之间调节,平衡准确性与可读性;创意类任务(如起名、文案润色)则可以推高到 0.8–1.2。
Top-p 核采样
Top-p(nucleus sampling)先将 token 按概率从高到低排序,再累加概率直到达到阈值 p(例如 0.9 或 0.95),只在这个最小"核"集合内进行采样,其余低概率 token 直接丢弃。它比单纯的 top-k 更动态:当模型对下一个词非常确信时,核集合很小;当分布平坦时,核集合自动扩大。这样既保留多样性,又过滤掉明显不合理的尾部。
实践中 Temperature 与 Top-p 常组合使用:先用 temperature 调整整体平滑度,再用 top-p 裁剪尾部。一般建议同时设置二者但不要同时调至极端值——例如 temperature=1.5 且 top_p=0.5 的组合会先过度拉平分布再强行截断,实际效果难以预测。推荐以 temperature=0.7、top_p=0.95 为起点,根据实测效果单向微调。
0x02 重复抑制参数:提升输出的信息密度与流畅度
长文本或多轮对话中,模型容易陷入局部重复(同一句式、同一短语反复出现)。Frequency Penalty 与 Presence Penalty 通过修改已生成 token 的 logits 来抑制这种现象。
Frequency Penalty 频率惩罚
该参数根据某个 token 在已生成序列中的出现次数动态施加惩罚:出现次数越多,对应 logit 被减去的值越大(通常 penalty 取值 0–2)。它侧重打击高频重复的句式和词汇,适合需要控制啰嗦程度的场景。
Presence Penalty 存在惩罚
Presence Penalty 更直接:只要某个 token 在前文出现过,就对其 logit 施加一个固定惩罚,而不管出现了几次。它更偏向鼓励模型引入新话题、新实体,提升内容丰富度。
两者可以同时开启,但需小心过度惩罚会导致模型回避常用连接词,反而破坏流畅性。在 Agent 场景中,它们能有效避免长回复、多轮规划或文档生成中出现"车轱辘话":任务规划类 Agent 生成步骤列表时,适度的 frequency_penalty(0.3–0.6)可减少机械重复;长文本总结类 Agent 则更依赖 presence_penalty 来推动覆盖更多关键点。实际调参时建议从 0 起步,逐步增加并观察输出变化,可结合 n-gram 重复率作为量化指标。
0x03 输出边界控制:规范输出的格式与长度
采样策略决定"怎么说",边界参数则决定"说到哪里停、最多说多长",是保障 Agent 输出可被程序解析的关键。
Stopping Criteria 停止条件
开发者可自定义一组 stop sequences(字符串或 token 序列),一旦模型生成匹配其中任意一个,立即终止生成。常见用法包括停止在 \n\n、```、</tool_call> 或自定义标记如 ###END###。这相当于给生成流加上"早停"钩子,避免模型在结构化输出后继续絮叨。在工具调用场景中,stop 的设置直接关联下游 JSON 解析的成功率:
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "system", content: "Output JSON only. End with ###END###" },
{ role: "user", content: "Get weather for Beijing" },
],
stop: ["###END###", "\n\n\n"],
max_tokens: 512,
temperature: 0,
});
const raw = response.choices[0].message.content!;
const data = JSON.parse(raw.split("###END###")[0].trim());如果模型在 JSON 后追加了自然语言后缀,JSON.parse 会直接抛出异常,stop sequences 能有效阻断这类多余输出。
Max Length 最大生成长度
通过 max_tokens 限制输出的最大 token 数,达到上限后强制截断。它既能防止无限制生成导致 token 浪费,也作为安全阀避免异常情况下的超长回复。在 Agent 中,不同思维阶段应分配不同的输出预算——这类似 Web 应用中不同 API endpoint 的 timeout 与 payload limit 各有不同:
const PHASE_CONFIG: Record<string, { max_tokens: number; temperature: number }> = {
plan: { max_tokens: 512, temperature: 0.2 },
tool_call: { max_tokens: 256, temperature: 0 },
reflect: { max_tokens: 1024, temperature: 0.5 },
answer: { max_tokens: 2048, temperature: 0.7 },
};规划阶段 512 token 列出步骤,工具调用 256 token 容纳典型 JSON 参数,反思阶段放宽到 1024 token 展开分析,最终回答预留 2048 token 以上保证完整输出。这样既能避免模型在简单任务上"废话连篇",也能防止复杂推理因预算不足而被截断。
结语
Transformer 构成了当前几乎所有主流大语言模型的架构底座,其 self-attention 机制决定了并行处理能力与长距离语义捕捉的边界;LLM 则是在该架构上通过大规模预训练形成的生成核心,本质是"基于上文预测下一个 Token"的自回归系统,为 Agent 提供决策与规划的文本引擎。运行机制层面的 Tokenization、Context Window 与 Token Based Pricing,让我们清晰看到输入如何被切分、单次交互的记忆容量上限以及成本如何随调用链累积,这些是 Agent 架构设计中不可逾越的硬约束。生成控制参数——Temperature、Top-p、Frequency/Presence Penalty 以及 Stopping Criteria 与 Max Length——则提供了直接可调的输出旋钮,使开发者能在稳定性与多样性、信息密度与格式可控性之间精确取舍。
参考文章
- Agent Learning Roadmap: https://roadmap.sh/ai-agents
- 知乎 - Transformer 模型详解: https://zhuanlan.zhihu.com/p/338817680
- tiktoken (Node.js WASM port): https://www.npmjs.com/package/tiktoken
- HuggingFace.js Inference: https://huggingface.co/docs/huggingface.js/inference