Go AI Agent 实战 03|Token、上下文窗口与模型调用成本

本系列共 10 阶段 ,面向有 Go 后端基础的开发者,全程用 Go 手写实现。

当前进度:阶段一 大模型基础 · 第 3 课

上一篇:大语言模型(LLM)到底是怎么工作的

上一课我们学习了大语言模型如何根据上下文逐步生成 Token。这一课进一步回答三个实际开发问题:

  • 为什么中文字符数和模型处理的 Token 数量不一样?
  • 为什么多轮对话不能无限地保留历史消息?
  • 为什么同样调用一次模型,费用和响应时间可能差别很大?

这些问题不只是模型知识,也会直接影响后续 Go AI 应用的接口设计、会话存储和成本控制。

一、本课学习目标

学完本课,你应该能够:

  1. 理解 Token、上下文窗口和最大输出 Token 数的区别。
  2. 了解模型调用时哪些内容会占用上下文。
  3. 理解多轮对话为什么需要管理历史消息。
  4. 掌握模型调用成本的基本计算方式。
  5. 能够设计一个简单的 Go 对话上下文管理方案。

二、Token 到底是什么?

Token 是模型处理文本时使用的基本单位。它不严格等于汉字、英文单词或 Go 字符串中的一个字符。

例如,下面这句话:

text 复制代码
我正在学习 Go AI Agent。

不同模型使用的 Tokenizer 可能产生不同的切分结果。我们可以把它示意为:

text 复制代码
["我", "正在", "学习", " Go", " AI", " Agent", "。"]

这只是示例,不是实际模型的 Token 切分结果。

英文也一样:

text 复制代码
Go is a programming language.

可能被切分成若干 Token,其中一个 Token 可能是完整单词,也可能是单词的一部分。

为什么不能直接用字符串长度计算 Token?

假设你在 Go 中写:

go 复制代码
question := "我正在学习 Go AI Agent"

fmt.Println(len(question))

这里的 len(question) 返回的是 UTF-8 编码后的字节数,不是 Token 数,也不是 Unicode 字符数量。

如果需要计算 Unicode 码点数量,可以使用:

go 复制代码
fmt.Println(utf8.RuneCountInString(question))

但即使得到码点数量,也不能直接认为这就是 Token 数量。

因此,在实际项目中:

  • 字符串长度适合用于基础输入长度限制。
  • Rune 数量适合用于某些字符级统计。
  • Token 数量应该通过对应模型的 Tokenizer 估算,或者使用服务商提供的用量信息。

不同模型的 Tokenizer 可能不同,所以不要使用一个固定比例来精确计算所有中英文文本的 Token 数量。

三、什么是上下文窗口?

上下文窗口(Context Window)指模型在一次推理中能够处理的上下文容量,通常以 Token 为单位描述。

可以把它理解为模型在当前这次请求中能够利用的信息预算。

假设某个模型支持的上下文容量为 32,000 Token。为了便于计算,我们暂时把它当作一个示例模型。

一次请求中可能包含:

  • System 消息:系统级指令。
  • 历史对话:此前的 User 和 Assistant 消息。
  • 当前问题:用户这次提出的问题。
  • 工具信息:工具定义、工具调用结果等。
  • 模型输出:这次生成的回答。

这些内容都会影响上下文容量的使用情况。具体的计数方式和额外开销取决于模型与 API 的实现。

示例:32,000 Token 的上下文预算

以下是用于教学的假设分配,不代表某个具体模型的真实配置。

组成 占用 Token
系统指令与工具定义 2,000
历史对话 12,000
当前问题及相关资料 8,000
预留给模型输出 4,000
剩余余量 6,000

在这个例子中,已经使用了 22,000 Token,预留了 4,000 Token 输出,另外留有 6,000 Token 余量。

需要注意:这不是要求每次请求都必须填满窗口。很多时候,简洁的上下文反而更经济、更容易维护。

四、为什么多轮对话会越来越占用 Token?

先看一个常见的聊天场景。

用户第一轮问:

什么是 Go 的 Context?

第二轮问:

它和 context.WithTimeout 有什么关系?

第三轮问:

能不能给我一个实际的 HTTP 请求示例?

第三个问题中的"它"需要结合前面的聊天记录理解。

如果应用只把第三个问题发送给模型,模型可能不知道"它"具体指什么。因此,应用通常需要把相关历史消息一并发送。

例如:

json 复制代码
[
  {
    "role": "user",
    "content": "什么是 Go 的 Context?"
  },
  {
    "role": "assistant",
    "content": "Context 用于传递取消信号、截止时间等信息。"
  },
  {
    "role": "user",
    "content": "它和 context.WithTimeout 有什么关系?"
  },
  {
    "role": "assistant",
    "content": "WithTimeout 可以创建带超时时间的 Context。"
  },
  {
    "role": "user",
    "content": "能不能给我一个实际的 HTTP 请求示例?"
  }
]

注意:这里的 role 表示消息角色,content 表示消息内容。后面我们会专门学习这些消息结构。

每次调用模型时,如果应用重复发送历史消息,那么早期的对话也会反复占用输入 Token。

这意味着:

  • 对话越长,输入 Token 通常越多。
  • 输入 Token 越多,调用成本可能越高。
  • 长上下文可能增加请求处理时间。
  • 上下文超过限制时,请求可能失败,或者需要先裁剪内容。

一个容易忽略的细节

如果你连续调用模型三次,并且每次都发送全部历史记录,那么第二次和第三次请求中的部分内容,其实已经在前面的请求中出现过。

但对于普通 API 请求来说,不能默认服务商会自动帮你保存对话,也不能默认重复内容完全免费。某些服务可能提供 Prompt 缓存等机制,具体计费要看服务商规则。

五、上下文管理应该怎么设计?

这是 Go 后端工程师需要掌握的内容。

假设你的 AI 对话系统使用 MySQL 保存消息,数据表可以简化为:

sql 复制代码
CREATE TABLE chat_messages (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    conversation_id BIGINT NOT NULL,
    role VARCHAR(20) NOT NULL,
    content TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

这里的 conversation_id 用于区分不同会话。

当用户发来新消息时,后端可以采用以下流程:

text 复制代码
接收用户消息
      ↓
保存当前消息
      ↓
读取当前会话的历史记录
      ↓
根据 Token 预算选择需要的历史消息
      ↓
组装模型请求
      ↓
调用 LLM API
      ↓
保存模型回答
      ↓
返回结果

注意,不能简单地把所有历史记录都读取出来并发送给模型。随着对话增长,需要根据预算选择保留哪些内容。

常见策略有三种:

策略 工作方式 适用场景
最近消息窗口 只保留最近若干轮对话 普通聊天
历史摘要 将较早的对话压缩成摘要 长时间持续的会话
检索相关历史 根据当前问题查找相关的旧消息 历史记录较多、主题跨度较大的场景

实际系统可以组合使用这些策略。

例如,保留最近 10 轮对话,同时维护一份历史摘要;如果用户提到以前讨论过的项目,再按需检索相关历史。

需要注意:摘要和检索都可能遗漏重要信息,因此涉及权限、订单状态等关键业务数据时,应该重新查询权威数据源,而不是依赖聊天历史。

六、模型调用成本怎么算?

假设某个模型服务的计价规则如下。这里的数字只是为了教学而设定的示例价格,不代表任何实际服务商的报价。

  • 输入:每百万 Token 0.50 美元。
  • 输出:每百万 Token 2.00 美元。

一次请求消耗:

  • 输入 Token:10,000。
  • 输出 Token:2,000。

计算公式为:

text 复制代码
总费用 = 输入 Token ÷ 1,000,000 × 输入单价
       + 输出 Token ÷ 1,000,000 × 输出单价

代入示例:

text 复制代码
10,000 ÷ 1,000,000 × 0.50 = 0.005
 2,000 ÷ 1,000,000 × 2.00 = 0.004
                合计     = 0.009

因此,这次请求的示例费用是 0.009 美元。

如果每天有 10,000 次类似请求,那么仅按照这个假设的价格计算:

text 复制代码
10,000 × 0.009 = 90 美元/天

真实费用还会受到模型选择、输入输出比例、缓存折扣、工具调用次数以及服务商计价规则等因素影响。

为什么 Agent 的成本更值得关注?

普通聊天可能只调用一次模型。

但一个 Agent 可能执行:

text 复制代码
第 1 次调用:分析任务
       ↓
第 2 次调用:决定调用搜索工具
       ↓
执行工具
       ↓
第 3 次调用:分析搜索结果
       ↓
第 4 次调用:生成最终回答

如果每次调用都重复携带较长的上下文,累计 Token 消耗就可能明显增加。

因此,后续开发 Agent 时,我们会引入最大迭代次数、Token 预算、超时控制等机制,避免任务无限执行或成本失控。

七、动手理解:Token 预算怎么算

下面用一个具体数字走一遍,直观感受输入和输出如何共同占用上下文。

项目 Token
上下文窗口 32,000
输入 Token 18,000
预留输出 Token 4,000
额外预留余量 2,000
合计占用 24,000
剩余 8,000

在当前简化预算内,剩余 8,000 Token。

这是简化的教学计算。实际模型还可能有额外 Token 开销、不同的输入输出限制,以及独立的最大输出长度约束。

八、本课总结

本课最重要的是掌握以下概念:

  1. Token 是模型处理文本的基本单位,不能直接等同于字符串长度。
  2. 上下文窗口限制一次推理可以处理的信息容量。
  3. 多轮对话需要管理历史消息,不能无条件地无限累积。
  4. 模型调用成本通常与输入、输出 Token 数量以及具体计价规则有关。
  5. Go 后端应该通过历史裁剪、摘要、检索、超时和预算控制,让 AI 应用保持可控。

九、课后练习

练习 1:理解上下文窗口

一个模型的上下文窗口为 16,000 Token。某次请求的输入占用 12,000 Token,预留输出为 3,000 Token,另外需要预留 1,500 Token 作为额外预算。按照本课的简化计算方式,预算是否足够?超出或剩余多少?

参考思路 :12,000 + 3,000 + 1,500 = 16,500,超过 16,000,超出 500 Token,预算不够。实际做法是把输入裁剪掉至少 500 Token,或者降低预留输出,否则请求可能被服务端直接拒绝。

练习 2:设计多轮对话

一个用户与 AI 已经交流了 200 轮。你会如何设计历史消息管理,既保留必要上下文,又避免每次请求都发送全部历史?请说明你的方案。

参考思路:三层组合。① 滑窗:始终带上最近 N 轮原文;② 摘要:更早的对话异步压缩成一段摘要,放在 System 之后;③ 检索:会话里的关键事实(项目名、版本、报错信息)单独存一张结构化表,按需注入。消息原表放 MySQL,热会话和摘要放 Redis。注意权限、余额、订单状态这类数据永远重新查权威库,不依赖聊天历史。

练习 3:成本计算

假设输入价格为每百万 Token 1 美元,输出价格为每百万 Token 4 美元。一次请求输入 5,000 Token、输出 1,000 Token,调用 1,000 次总费用是多少?(忽略缓存及其他费用)

参考思路 :单次 = 5,000 ÷ 1,000,000 × 1 + 1,000 ÷ 1,000,000 × 4 = 0.005 + 0.004 = 0.009 美元;1,000 次 = 9 美元。注意输出单价是输入的 4 倍,所以"让模型少说废话"往往比"少给点上下文"更省钱。


下一篇:Go AI Agent 实战 04|Prompt 与消息角色:System、User、Assistant

相关推荐
老金带你玩AI1 小时前
Taste Skill V2来了,AI做的网站还会一眼认出来吗?
人工智能
方方洛1 小时前
ai-agent教程-18-附录E-推荐阅读与资源
人工智能·llm·agent
const_qiu1 小时前
第四章 00:开篇 —— 从「会聊天」到「记得住、会干活、能协作」
java·开发语言·人工智能·python
Wang's Blog1 小时前
Java 项目实战: 交友项目 - 个人资料管理的更新接口实现与坑
java·开发语言·交友
石狮松松1 小时前
第 05 章 你的第一行"AI"代码:拟合一条直线
人工智能
Promising_GEO1 小时前
GEE实战02期:从卫星波段开始:利用 Sentinel-2 自己计算 NDVI
java·sentinel·遥感·空间分析
用户360055579001 小时前
Kestrel_VLLM - 14 · router 两个方向判否,与 decode 的真瓶颈
人工智能
狒狒AI1 小时前
Remotion / GSAP+Three / HyperFrames / Skia 四栈实测:代码渲染知识视频的选栈方法(附开源技能)
前端·人工智能·aigc
大文说跨境1 小时前
亚马逊多账号管理浏览器怎么选?云登/紫鸟/AdsPower 对比
java·前端·自动化