纲要
- 上下文窗口的定义与边界
- Token的本质与计算方式
- 上下文窗口限制的成因
- Transformer自注意力机制的复杂度
- 硬件显存与推理延迟
- 预训练数据的长文本分布偏差
- 长距离依赖与注意力稀释
- 突破窗口限制的主流策略
- 检索增强生成(RAG)
- 滑动窗口(Sliding Window)
- 新型模型架构探索
- LangChain中的Token用量追踪
usage_metadata与response_metadata字段- 流式输出中的
stream_usage参数
- 实战:滑动窗口长文本处理与Token监控
- 项目结构与依赖
tiktoken精确计算Token数- 滑动窗口切分算法实现
- 结合
ChatOpenAI调用并记录用量
- API速览
- 关键技术要点总结
上下文窗口的概念
大语言模型的“上下文窗口”(Context Window)指模型在单次推理中能够处理的最大Token数量。它等价于一个固定长度的注意力视野:模型仅能“看到”窗口范围内的内容,超出部分在计算过程中被丢弃。该长度是模型架构设计时确定的超参数,直接决定模型能够处理的输入与输出总长度。
不同模型的上下文窗口差异显著。以OpenAI系列模型为例,GPT-4存在8K、32K及128K等不同变体,而早期的GPT-3.5仅支持4K。更大的窗口意味着更强的长文档处理与多轮对话能力,但同时也对计算资源提出更高要求。选择模型时需在窗口长度与推理成本之间进行权衡。
Token:模型的基本信息单元
人类自然语言以字符和单词为基本单元,但大模型底层处理的并非原始文本,而是Token。Token是模型能够识别和处理的最小语义单元,它可以是一个完整的单词、单词的一部分、标点符号,甚至在多模态模型中可以是图像或音频的离散化片段。
以OpenAI的cl100k_base分词器为例,英文文本中大约1个Token对应0.75个单词或4个英文字符。例如句子 “LangChain is cool.” 会被切分为 ["Lang", "Chain", " is", " cool", "."],共计5个Token,原始字符数为18。Token的信息密度显著高于字符级别,因此在开发AI Agent时,必须使用专门的分词工具精确计算文本的Token数量,避免因输入超出上下文窗口而导致截断或异常响应。
上下文窗口的制约因素
上下文窗口不能无限扩大,主要受以下四方面因素制约:
| 制约因素 | 具体说明 |
|---|---|
| 计算复杂度 | Transformer的自注意力机制计算复杂度为 (O(n^2)),其中 (n) 为序列长度。序列长度翻倍,计算量与显存需求约增长四倍 |
| 硬件限制 | GPU显存大小直接决定可容纳的序列长度上限。长序列需存储更多注意力矩阵与中间激活值,同时推理延迟显著增加 |
| 训练数据分布 | 预训练语料中长文本占比通常较低,模型对长距离依赖的学习天然不足。过大窗口可能导致早期信息与当前信息之间的“遗忘” |
| 注意力衰减 | 随序列增长,模型对早期Token的注意力权重呈衰减趋势,长距离问答场景中引用前文信息的能力下降 |
这些因素共同决定了每个模型必须设置一个经过权衡的上下文窗口值,而非简单的技术选择。
突破窗口限制的常见策略
当实际任务需求超出模型上下文窗口时,通常采用以下三种策略:
-
检索增强生成(RAG,Retrieval-Augmented Generation):将历史对话或文档存储至外部向量数据库,每次请求时根据当前问题检索最相关的片段,仅将检索结果拼入Prompt。这种方法相当于为模型提供“外挂记忆”。
-
滑动窗口(Sliding Window):维护一个固定长度的Token窗口,新内容加入时移除窗口最左侧的旧内容。该策略确保模型始终在有限的窗口内处理最近、最相关的信息,实现简单且效果稳定。
-
新型模型架构探索:研究者尝试使用状态空间模型(State Space Models)、线性注意力(Linear Attention)等架构替代Transformer,以突破平方复杂度限制。目前这些方案仍处于快速演进阶段,尚未成为主流。
上述策略在实际Agent开发中往往组合使用。下文将重点实现一种基于滑动窗口的长文本处理方案,并配合LangChain的Token追踪功能,展示完整的窗口管理流程。
LangChain中的Token用量追踪
LangChain的大模型调用接口返回的 AIMessage 对象中包含 usage_metadata 字段,该字段以标准化结构提供输入Token数(input_tokens)、输出Token数(output_tokens)及总Token数(total_tokens)。无论底层使用OpenAI、DeepSeek还是Anthropic,该字段均被封装为统一格式(部分模型可能返回空值)。
此外,对于OpenAI等支持流式输出的模型,可在调用时设置 stream_usage=True,使每个流式响应块(chunk)附带累计的Token用量信息,便于实时监控成本。
以下代码将展示具体用法。
实战项目结构
本示例实现一个滑动窗口处理器,具备以下能力:
- 使用
tiktoken精确计算文本的Token数量; - 将超长文本按指定窗口大小和重叠步长切分为多个片段;
- 逐片段调用大模型并记录每个窗口及总体的Token消耗。
项目目录结构如下:
├── main.py # 滑动窗口处理与Token追踪主程序
└── requirements.txt # Python依赖列表
依赖清单(requirements.txt):
langchain-openai>=0.3.0
tiktoken>=0.7.0
滑动窗口切分器实现
SlidingWindowSplitter 类使用 tiktoken 将文本按Token数切分为多个带重叠的窗口,确保相邻窗口间的语义连续性。
import tiktoken
from typing import List
class SlidingWindowSplitter:
"""基于Token数的滑动窗口文本切分器"""
def __init__(self, model_name: str = "gpt-4", max_tokens: int = 2000, overlap: int = 200):
"""
初始化切分器
:param model_name: 模型名称,用于获取对应的tiktoken编码器
:param max_tokens: 每个窗口的最大Token数
:param overlap: 相邻窗口间的重叠Token数
"""
self.encoding = tiktoken.encoding_for_model(model_name)
self.max_tokens = max_tokens
self.overlap = overlap
def token_count(self, text: str) -> int:
"""返回输入文本的Token数量"""
return len(self.encoding.encode(text))
def split(self, text: str) -> List[str]:
"""
将文本切分为多个滑动窗口片段
:param text: 输入长文本
:return: 窗口字符串列表
"""
tokens = self.encoding.encode(text)
total = len(tokens)
chunks = []
start = 0
while start < total:
end = min(start + self.max_tokens, total)
chunk_tokens = tokens[start:end]
chunks.append(self.encoding.decode(chunk_tokens))
# 向后滑动时减去重叠部分,保证信息平滑过渡
start = end - self.overlap
return chunks
主程序:Token追踪与模型调用
以下代码完整演示了如何使用滑动窗口处理长文本,并利用LangChain的 usage_metadata 追踪每个窗口及整体的Token消耗。
import os
from typing import List
from langchain_openai import ChatOpenAI
from sliding_window_splitter import SlidingWindowSplitter
def demo_sliding_window_processing():
# 1. 初始化模型,须设置环境变量 OPENAI_API_KEY
llm = ChatOpenAI(
model="gpt-4",
temperature=0.3,
max_tokens=500,
api_key=os.getenv("OPENAI_API_KEY"),
)
# 2. 构造一篇超长文本(重复内容以模拟长文档)
long_text = """
在人工智能的发展历程中,大语言模型的出现是一个重要的里程碑。
它使得机器能够理解和生成自然语言,从而在对话系统、内容创作、代码生成等领域展现出巨大的潜力。
然而,大模型并不是万能的,其固有的上下文窗口限制经常导致长文档处理、多轮对话等场景中的信息丢失。
为了突破这一限制,研究者们提出了多种方案,包括检索增强生成、滑动窗口、以及探索全新的模型架构。
其中,滑动窗口是一种简单而有效的方法:它将长文本切分成固定大小的片段,每个片段之间保留一定的重叠区域,
使得模型可以在窗口内处理局部上下文,并通过对多个窗口结果的后处理来获得全局理解。
这种技术在实际的AI Agent开发中被广泛采用,尤其适用于问答、摘要等需要长上下文的任务。
""" * 50 # 重复50次,总Token数远超单次窗口上限
splitter = SlidingWindowSplitter(model_name="gpt-4", max_tokens=2000, overlap=200)
print(f"原文总Token数: {splitter.token_count(long_text)}")
windows = splitter.split(long_text)
print(f"切分成 {len(windows)} 个窗口\n")
# 3. 逐窗口调用模型,累计Token用量
total_input_tokens = 0
total_output_tokens = 0
results = []
for i, win in enumerate(windows):
print(f"--- 处理窗口 {i+1} (Token数: {splitter.token_count(win)}) ---")
msg = llm.invoke(f"请用中文总结以下文本的核心要点:\n{win}")
usage = msg.usage_metadata
if usage:
input_tokens = usage.get('input_tokens', 0)
output_tokens = usage.get('output_tokens', 0)
print(f" 输入Token: {input_tokens}, 输出Token: {output_tokens}, 总计: {usage.get('total_tokens')}")
total_input_tokens += input_tokens
total_output_tokens += output_tokens
results.append(msg.content)
# 对最后一个窗口演示流式追踪
if i == len(windows) - 1:
print(" 流式追踪演示(累计用量):")
# 注意:stream_usage 需在模型调用时启用
for chunk in llm.stream(win, stream_usage=True):
if chunk.usage_metadata:
print(f" 当前累计: {chunk.usage_metadata}")
print()
print(f"\n全部窗口总消耗 - 输入Token: {total_input_tokens}, 输出Token: {total_output_tokens}, 合计: {total_input_tokens + total_output_tokens}")
return results
if __name__ == "__main__":
demo_sliding_window_processing()
代码关键说明
-
SlidingWindowSplitter:通过tiktoken.encoding_for_model获取与模型匹配的分词器,确保Token计算与模型实际处理一致。split方法按max_tokens切分,并通过overlap参数控制相邻窗口的重叠量,保证边界语义平滑。 -
usage_metadata提取:ChatOpenAI.invoke返回的AIMessage对象中包含usage_metadata字段,可直接读取输入、输出及总Token数。 -
流式追踪:
llm.stream(win, stream_usage=True)启用了流式用量统计,每个chunk的usage_metadata携带截至当前块的累计值,便于实时监控。 -
实际开发扩展:可将滑动窗口逻辑封装为LangChain的
RunnableLambda步骤,无缝集成到Agent工作流中。
滑动窗口处理流程总览
以下流程图展示了从文本输入到窗口处理再到用量追踪的完整流程:
API速览
以下列出本文涉及的核心API及其用法:
| API / 类 | 所属库 | 方法签名 | 说明 |
|---|---|---|---|
tiktoken.encoding_for_model | tiktoken | encoding_for_model(model_name: str) -> Encoding | 根据模型名称获取对应的分词器编码器 |
Encoding.encode | tiktoken.Encoding | encode(text: str) -> List[int] | 将文本编码为Token ID列表 |
Encoding.decode | tiktoken.Encoding | decode(tokens: List[int]) -> str | 将Token ID列表解码为原始文本 |
ChatOpenAI | langchain_openai | __init__(model, temperature, max_tokens, api_key) | 初始化OpenAI聊天模型客户端 |
ChatOpenAI.invoke | langchain_openai | invoke(input: str) -> AIMessage | 同步调用模型并返回完整响应 |
ChatOpenAI.stream | langchain_openai | stream(input: str, stream_usage: bool) -> Iterator[ChatMessageChunk] | 流式调用模型,stream_usage=True时每个chunk附带usage_metadata |
AIMessage.usage_metadata | langchain_core.messages | 属性: Dict[str, int] | 包含input_tokens、output_tokens、total_tokens的标准化用量字典 |
关键技术要点
- 上下文窗口:模型单次推理可处理的最大Token数,受注意力机制复杂度、显存容量、训练数据分布及注意力衰减共同制约。
- Token计算:Token为模型的基本信息单元,需使用与模型匹配的分词器(如
tiktoken)精确计数,不可直接按字符或单词估算。 - 突破策略:RAG通过外部向量库扩展记忆,滑动窗口通过局部上下文保持信息连续性,新型架构探索则从底层突破平方复杂度。三者可根据场景组合使用。
- LangChain用量追踪:
usage_metadata提供跨模型的标准化Token统计;流式模式下设置stream_usage=True可实现实时成本监控。 - 滑动窗口实战:通过重叠切分保障边界语义,配合Token追踪可精确控制每次调用的输入长度,有效规避超出窗口导致的截断问题。
官方文档与参考链接
总结
本文从上下文窗口和Token的基本概念出发,系统分析了窗口限制的技术成因,对比了RAG、滑动窗口和新架构等突破策略,并基于LangChain和tiktoken给出了完整的滑动窗口长文本处理实现。
通过对Token用量的精确追踪,开发者可在实际AI Agent项目中有效控制输入长度,避免窗口越界,同时可视化监控推理成本。文中提供的代码可直接复用到生产环境,为长文本场景下的Agent开发提供可靠的工程实践参考。
707



被折叠的 条评论
为什么被折叠?



